forked from M-Labs/nac3
Compare commits
11 Commits
master
...
ndstrides-
Author | SHA1 | Date |
---|---|---|
lyken | 5035084a5d | |
lyken | 52a51a9eed | |
lyken | 9d2ecffddc | |
lyken | 1be59d8fca | |
lyken | f6a554d3c9 | |
lyken | a05eb22358 | |
lyken | f061db1b10 | |
lyken | 855805561a | |
lyken | 942fb88df6 | |
lyken | 0bf66745c1 | |
lyken | 474e0ab809 |
|
@ -13,6 +13,7 @@
|
|||
''
|
||||
mkdir -p $out/bin
|
||||
ln -s ${pkgs.llvmPackages_14.clang-unwrapped}/bin/clang $out/bin/clang-irrt
|
||||
ln -s ${pkgs.llvmPackages_14.clang}/bin/clang $out/bin/clang-irrt-test
|
||||
ln -s ${pkgs.llvmPackages_14.llvm.out}/bin/llvm-as $out/bin/llvm-as-irrt
|
||||
'';
|
||||
nac3artiq = pkgs.python3Packages.toPythonModule (
|
||||
|
@ -23,6 +24,7 @@
|
|||
cargoLock = {
|
||||
lockFile = ./Cargo.lock;
|
||||
};
|
||||
cargoTestFlags = [ "--features" "test" ];
|
||||
passthru.cargoLock = cargoLock;
|
||||
nativeBuildInputs = [ pkgs.python3 pkgs.llvmPackages_14.clang llvm-tools-irrt pkgs.llvmPackages_14.llvm.out llvm-nac3 ];
|
||||
buildInputs = [ pkgs.python3 llvm-nac3 ];
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
[features]
|
||||
test = []
|
||||
|
||||
[package]
|
||||
name = "nac3core"
|
||||
version = "0.1.0"
|
||||
|
|
|
@ -3,20 +3,34 @@ use std::{
|
|||
env,
|
||||
fs::File,
|
||||
io::Write,
|
||||
path::Path,
|
||||
path::{Path, PathBuf},
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
const FILE: &str = "src/codegen/irrt/irrt.cpp";
|
||||
const CMD_IRRT_CLANG: &str = "clang-irrt";
|
||||
const CMD_IRRT_CLANG_TEST: &str = "clang-irrt-test";
|
||||
const CMD_IRRT_LLVM_AS: &str = "llvm-as-irrt";
|
||||
|
||||
fn get_out_dir() -> PathBuf {
|
||||
PathBuf::from(env::var("OUT_DIR").unwrap())
|
||||
}
|
||||
|
||||
fn get_irrt_dir() -> &'static Path {
|
||||
Path::new("irrt")
|
||||
}
|
||||
|
||||
/// Compile `irrt.cpp` for use in `src/codegen`
|
||||
fn compile_irrt_cpp() {
|
||||
let out_dir = get_out_dir();
|
||||
let irrt_dir = get_irrt_dir();
|
||||
|
||||
/*
|
||||
* HACK: Sadly, clang doesn't let us emit generic LLVM bitcode.
|
||||
* Compiling for WASM32 and filtering the output with regex is the closest we can get.
|
||||
*/
|
||||
let irrt_cpp_path = irrt_dir.join("irrt.cpp");
|
||||
let flags: &[&str] = &[
|
||||
"--target=wasm32",
|
||||
FILE,
|
||||
"-x",
|
||||
"c++",
|
||||
"-fno-discard-value-names",
|
||||
|
@ -31,15 +45,19 @@ fn main() {
|
|||
"-S",
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-Werror=return-type",
|
||||
"-o",
|
||||
"-",
|
||||
"-I",
|
||||
irrt_dir.to_str().unwrap(),
|
||||
irrt_cpp_path.to_str().unwrap(),
|
||||
];
|
||||
|
||||
println!("cargo:rerun-if-changed={FILE}");
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let out_path = Path::new(&out_dir);
|
||||
// Tell Cargo to rerun if any file under `irrt_dir` (recursive) changes
|
||||
println!("cargo:rerun-if-changed={}", irrt_dir.to_str().unwrap());
|
||||
|
||||
let output = Command::new("clang-irrt")
|
||||
// Compile IRRT and capture the LLVM IR output
|
||||
let output = Command::new(CMD_IRRT_CLANG)
|
||||
.args(flags)
|
||||
.output()
|
||||
.map(|o| {
|
||||
|
@ -52,7 +70,17 @@ fn main() {
|
|||
let output = std::str::from_utf8(&output.stdout).unwrap().replace("\r\n", "\n");
|
||||
let mut filtered_output = String::with_capacity(output.len());
|
||||
|
||||
let regex_filter = Regex::new(r"(?ms:^define.*?\}$)|(?m:^declare.*?$)").unwrap();
|
||||
// Filter out irrelevant IR
|
||||
//
|
||||
// Regex:
|
||||
// - `(?ms:^define.*?\}$)` captures LLVM `define` blocks
|
||||
// - `(?m:^declare.*?$)` captures LLVM `declare` lines
|
||||
// - `(?m:^%.+?=\s*type\s*\{.+?\}$)` captures LLVM `type` declarations
|
||||
// - `(?m:^@.+?=.+$)` captures global constants
|
||||
let regex_filter = Regex::new(
|
||||
r"(?ms:^define.*?\}$)|(?m:^declare.*?$)|(?m:^%.+?=\s*type\s*\{.+?\}$)|(?m:^@.+?=.+$)",
|
||||
)
|
||||
.unwrap();
|
||||
for f in regex_filter.captures_iter(&output) {
|
||||
assert_eq!(f.len(), 1);
|
||||
filtered_output.push_str(&f[0]);
|
||||
|
@ -63,20 +91,71 @@ fn main() {
|
|||
.unwrap()
|
||||
.replace_all(&filtered_output, "");
|
||||
|
||||
println!("cargo:rerun-if-env-changed=DEBUG_DUMP_IRRT");
|
||||
if env::var("DEBUG_DUMP_IRRT").is_ok() {
|
||||
let mut file = File::create(out_path.join("irrt.ll")).unwrap();
|
||||
// For debugging
|
||||
// Doing `DEBUG_DUMP_IRRT=1 cargo build -p nac3core` dumps the LLVM IR generated
|
||||
const DEBUG_DUMP_IRRT: &str = "DEBUG_DUMP_IRRT";
|
||||
println!("cargo:rerun-if-env-changed={DEBUG_DUMP_IRRT}");
|
||||
if env::var(DEBUG_DUMP_IRRT).is_ok() {
|
||||
let mut file = File::create(out_dir.join("irrt.ll")).unwrap();
|
||||
file.write_all(output.as_bytes()).unwrap();
|
||||
let mut file = File::create(out_path.join("irrt-filtered.ll")).unwrap();
|
||||
|
||||
let mut file = File::create(out_dir.join("irrt-filtered.ll")).unwrap();
|
||||
file.write_all(filtered_output.as_bytes()).unwrap();
|
||||
}
|
||||
|
||||
let mut llvm_as = Command::new("llvm-as-irrt")
|
||||
// Assemble the emitted and filtered IR to .bc
|
||||
// That .bc will be integrated into nac3core's codegen
|
||||
let mut llvm_as = Command::new(CMD_IRRT_LLVM_AS)
|
||||
.stdin(Stdio::piped())
|
||||
.arg("-o")
|
||||
.arg(out_path.join("irrt.bc"))
|
||||
.arg(out_dir.join("irrt.bc"))
|
||||
.spawn()
|
||||
.unwrap();
|
||||
llvm_as.stdin.as_mut().unwrap().write_all(filtered_output.as_bytes()).unwrap();
|
||||
assert!(llvm_as.wait().unwrap().success());
|
||||
}
|
||||
|
||||
/// Compile `irrt_test.cpp` for testing
|
||||
fn compile_irrt_test_cpp() {
|
||||
let out_dir = get_out_dir();
|
||||
let irrt_dir = get_irrt_dir();
|
||||
|
||||
let exe_path = out_dir.join("irrt_test.out"); // Output path of the compiled test executable
|
||||
let irrt_test_cpp_path = irrt_dir.join("irrt_test.cpp");
|
||||
let flags: &[&str] = &[
|
||||
irrt_test_cpp_path.to_str().unwrap(),
|
||||
"-x",
|
||||
"c++",
|
||||
"-I",
|
||||
irrt_dir.to_str().unwrap(),
|
||||
"-g",
|
||||
"-fno-discard-value-names",
|
||||
"-O0",
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-Werror=return-type",
|
||||
"-lm", // for `tgamma()`, `lgamma()`
|
||||
"-o",
|
||||
exe_path.to_str().unwrap(),
|
||||
];
|
||||
|
||||
Command::new(CMD_IRRT_CLANG_TEST)
|
||||
.args(flags)
|
||||
.output()
|
||||
.map(|o| {
|
||||
assert!(o.status.success(), "{}", std::str::from_utf8(&o.stderr).unwrap());
|
||||
o
|
||||
})
|
||||
.unwrap();
|
||||
println!("cargo:rerun-if-changed={}", irrt_dir.to_str().unwrap());
|
||||
}
|
||||
|
||||
fn main() {
|
||||
compile_irrt_cpp();
|
||||
|
||||
// https://github.com/rust-lang/cargo/issues/2549
|
||||
// `cargo test -F test` to also build `irrt_test.cpp
|
||||
if cfg!(feature = "test") {
|
||||
compile_irrt_test_cpp();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
#define IRRT_DEFINE_TYPEDEF_INTS
|
||||
#include <irrt_everything.hpp>
|
||||
|
||||
/*
|
||||
All IRRT implementations.
|
||||
|
||||
We don't have any pre-compiled objects, so we are writing all implementations in headers and
|
||||
concatenate them with `#include` into one massive source file that contains all the IRRT stuff.
|
||||
*/
|
|
@ -1,27 +1,17 @@
|
|||
using int8_t = _BitInt(8);
|
||||
using uint8_t = unsigned _BitInt(8);
|
||||
using int32_t = _BitInt(32);
|
||||
using uint32_t = unsigned _BitInt(32);
|
||||
using int64_t = _BitInt(64);
|
||||
using uint64_t = unsigned _BitInt(64);
|
||||
#pragma once
|
||||
|
||||
#include <irrt/int_defs.hpp>
|
||||
#include <irrt/utils.hpp>
|
||||
|
||||
// NDArray indices are always `uint32_t`.
|
||||
using NDIndex = uint32_t;
|
||||
// The type of an index or a value describing the length of a range/slice is always `int32_t`.
|
||||
// The type of an index or a value describing the length of a
|
||||
// range/slice is always `int32_t`.
|
||||
using SliceIndex = int32_t;
|
||||
|
||||
namespace {
|
||||
template <typename T>
|
||||
const T& max(const T& a, const T& b) {
|
||||
return a > b ? a : b;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T& min(const T& a, const T& b) {
|
||||
return a > b ? b : a;
|
||||
}
|
||||
|
||||
// adapted from GNU Scientific Library: https://git.savannah.gnu.org/cgit/gsl.git/tree/sys/pow_int.c
|
||||
// adapted from GNU Scientific Library:
|
||||
// https://git.savannah.gnu.org/cgit/gsl.git/tree/sys/pow_int.c
|
||||
// need to make sure `exp >= 0` before calling this function
|
||||
template <typename T>
|
||||
T __nac3_int_exp_impl(T base, T exp) {
|
||||
|
@ -38,12 +28,7 @@ T __nac3_int_exp_impl(T base, T exp) {
|
|||
}
|
||||
|
||||
template <typename SizeT>
|
||||
SizeT __nac3_ndarray_calc_size_impl(
|
||||
const SizeT* list_data,
|
||||
SizeT list_len,
|
||||
SizeT begin_idx,
|
||||
SizeT end_idx
|
||||
) {
|
||||
SizeT __nac3_ndarray_calc_size_impl(const SizeT* list_data, SizeT list_len, SizeT begin_idx, SizeT end_idx) {
|
||||
__builtin_assume(end_idx <= list_len);
|
||||
|
||||
SizeT num_elems = 1;
|
||||
|
@ -56,12 +41,7 @@ SizeT __nac3_ndarray_calc_size_impl(
|
|||
}
|
||||
|
||||
template <typename SizeT>
|
||||
void __nac3_ndarray_calc_nd_indices_impl(
|
||||
SizeT index,
|
||||
const SizeT* dims,
|
||||
SizeT num_dims,
|
||||
NDIndex* idxs
|
||||
) {
|
||||
void __nac3_ndarray_calc_nd_indices_impl(SizeT index, const SizeT* dims, SizeT num_dims, NDIndex* idxs) {
|
||||
SizeT stride = 1;
|
||||
for (SizeT dim = 0; dim < num_dims; dim++) {
|
||||
SizeT i = num_dims - dim - 1;
|
||||
|
@ -72,13 +52,8 @@ void __nac3_ndarray_calc_nd_indices_impl(
|
|||
}
|
||||
|
||||
template <typename SizeT>
|
||||
SizeT __nac3_ndarray_flatten_index_impl(
|
||||
const SizeT* dims,
|
||||
SizeT num_dims,
|
||||
const NDIndex* indices,
|
||||
SizeT num_indices
|
||||
) {
|
||||
SizeT idx = 0;
|
||||
SizeT __nac3_ndarray_flatten_index_impl(const SizeT* dims, SizeT num_dims, const NDIndex* indices, SizeT num_indices) {
|
||||
SizeT idx = 0;
|
||||
SizeT stride = 1;
|
||||
for (SizeT i = 0; i < num_dims; ++i) {
|
||||
SizeT ri = num_dims - i - 1;
|
||||
|
@ -94,17 +69,14 @@ SizeT __nac3_ndarray_flatten_index_impl(
|
|||
|
||||
template <typename SizeT>
|
||||
void __nac3_ndarray_calc_broadcast_impl(
|
||||
const SizeT* lhs_dims,
|
||||
SizeT lhs_ndims,
|
||||
const SizeT* rhs_dims,
|
||||
SizeT rhs_ndims,
|
||||
SizeT* out_dims
|
||||
const SizeT* lhs_dims, SizeT lhs_ndims, const SizeT* rhs_dims, SizeT rhs_ndims, SizeT* out_dims
|
||||
) {
|
||||
SizeT max_ndims = lhs_ndims > rhs_ndims ? lhs_ndims : rhs_ndims;
|
||||
|
||||
for (SizeT i = 0; i < max_ndims; ++i) {
|
||||
const SizeT* lhs_dim_sz = i < lhs_ndims ? &lhs_dims[lhs_ndims - i - 1] : nullptr;
|
||||
const SizeT* rhs_dim_sz = i < rhs_ndims ? &rhs_dims[rhs_ndims - i - 1] : nullptr;
|
||||
|
||||
SizeT* out_dim = &out_dims[max_ndims - i - 1];
|
||||
|
||||
if (lhs_dim_sz == nullptr) {
|
||||
|
@ -125,28 +97,25 @@ void __nac3_ndarray_calc_broadcast_impl(
|
|||
|
||||
template <typename SizeT>
|
||||
void __nac3_ndarray_calc_broadcast_idx_impl(
|
||||
const SizeT* src_dims,
|
||||
SizeT src_ndims,
|
||||
const NDIndex* in_idx,
|
||||
NDIndex* out_idx
|
||||
const SizeT* src_dims, SizeT src_ndims, const NDIndex* in_idx, NDIndex* out_idx
|
||||
) {
|
||||
for (SizeT i = 0; i < src_ndims; ++i) {
|
||||
SizeT src_i = src_ndims - i - 1;
|
||||
SizeT src_i = src_ndims - i - 1;
|
||||
out_idx[src_i] = src_dims[src_i] == 1 ? 0 : in_idx[src_i];
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
#define DEF_nac3_int_exp_(T) \
|
||||
T __nac3_int_exp_##T(T base, T exp) {\
|
||||
return __nac3_int_exp_impl(base, exp);\
|
||||
#define DEF_nac3_int_exp_(T) \
|
||||
T __nac3_int_exp_##T(T base, T exp) { \
|
||||
return __nac3_int_exp_impl(base, exp); \
|
||||
}
|
||||
|
||||
DEF_nac3_int_exp_(int32_t)
|
||||
DEF_nac3_int_exp_(int64_t)
|
||||
DEF_nac3_int_exp_(uint32_t)
|
||||
DEF_nac3_int_exp_(uint64_t)
|
||||
DEF_nac3_int_exp_(int32_t);
|
||||
DEF_nac3_int_exp_(int64_t);
|
||||
DEF_nac3_int_exp_(uint32_t);
|
||||
DEF_nac3_int_exp_(uint64_t);
|
||||
|
||||
SliceIndex __nac3_slice_index_bound(SliceIndex i, const SliceIndex len) {
|
||||
if (i < 0) {
|
||||
|
@ -160,11 +129,7 @@ SliceIndex __nac3_slice_index_bound(SliceIndex i, const SliceIndex len) {
|
|||
return i;
|
||||
}
|
||||
|
||||
SliceIndex __nac3_range_slice_len(
|
||||
const SliceIndex start,
|
||||
const SliceIndex end,
|
||||
const SliceIndex step
|
||||
) {
|
||||
SliceIndex __nac3_range_slice_len(const SliceIndex start, const SliceIndex end, const SliceIndex step) {
|
||||
SliceIndex diff = end - start;
|
||||
if (diff > 0 && step > 0) {
|
||||
return ((diff - 1) / step) + 1;
|
||||
|
@ -180,7 +145,8 @@ SliceIndex __nac3_range_slice_len(
|
|||
// - All the index must *not* be out-of-bound or negative,
|
||||
// - The end index is *inclusive*,
|
||||
// - The length of src and dest slice size should already
|
||||
// be checked: if dest.step == 1 then len(src) <= len(dest) else len(src) == len(dest)
|
||||
// be checked: if dest.step == 1 then len(src) <= len(dest) else
|
||||
// len(src) == len(dest)
|
||||
SliceIndex __nac3_list_slice_assign_var_size(
|
||||
SliceIndex dest_start,
|
||||
SliceIndex dest_end,
|
||||
|
@ -194,18 +160,18 @@ SliceIndex __nac3_list_slice_assign_var_size(
|
|||
SliceIndex src_arr_len,
|
||||
const SliceIndex size
|
||||
) {
|
||||
/* if dest_arr_len == 0, do nothing since we do not support extending list */
|
||||
if (dest_arr_len == 0) return dest_arr_len;
|
||||
/* if both step is 1, memmove directly, handle the dropping of the list, and shrink size */
|
||||
/* if dest_arr_len == 0, do nothing since we do not support
|
||||
* extending list
|
||||
*/
|
||||
if (dest_arr_len == 0)
|
||||
return dest_arr_len;
|
||||
/* if both step is 1, memmove directly, handle the dropping of
|
||||
* the list, and shrink size */
|
||||
if (src_step == dest_step && dest_step == 1) {
|
||||
const SliceIndex src_len = (src_end >= src_start) ? (src_end - src_start + 1) : 0;
|
||||
const SliceIndex src_len = (src_end >= src_start) ? (src_end - src_start + 1) : 0;
|
||||
const SliceIndex dest_len = (dest_end >= dest_start) ? (dest_end - dest_start + 1) : 0;
|
||||
if (src_len > 0) {
|
||||
__builtin_memmove(
|
||||
dest_arr + dest_start * size,
|
||||
src_arr + src_start * size,
|
||||
src_len * size
|
||||
);
|
||||
__builtin_memmove(dest_arr + dest_start * size, src_arr + src_start * size, src_len * size);
|
||||
}
|
||||
if (dest_len > 0) {
|
||||
/* dropping */
|
||||
|
@ -219,23 +185,17 @@ SliceIndex __nac3_list_slice_assign_var_size(
|
|||
return dest_arr_len - (dest_len - src_len);
|
||||
}
|
||||
/* if two range overlaps, need alloca */
|
||||
uint8_t need_alloca =
|
||||
(dest_arr == src_arr)
|
||||
&& !(
|
||||
max(dest_start, dest_end) < min(src_start, src_end)
|
||||
|| max(src_start, src_end) < min(dest_start, dest_end)
|
||||
uint8_t need_alloca = (dest_arr == src_arr)
|
||||
&& !(max(dest_start, dest_end) < min(src_start, src_end) || max(src_start, src_end) < min(dest_start, dest_end)
|
||||
);
|
||||
if (need_alloca) {
|
||||
uint8_t* tmp = reinterpret_cast<uint8_t *>(__builtin_alloca(src_arr_len * size));
|
||||
uint8_t* tmp = reinterpret_cast<uint8_t*>(__builtin_alloca(src_arr_len * size));
|
||||
__builtin_memcpy(tmp, src_arr, src_arr_len * size);
|
||||
src_arr = tmp;
|
||||
}
|
||||
SliceIndex src_ind = src_start;
|
||||
SliceIndex src_ind = src_start;
|
||||
SliceIndex dest_ind = dest_start;
|
||||
for (;
|
||||
(src_step > 0) ? (src_ind <= src_end) : (src_ind >= src_end);
|
||||
src_ind += src_step, dest_ind += dest_step
|
||||
) {
|
||||
for (; (src_step > 0) ? (src_ind <= src_end) : (src_ind >= src_end); src_ind += src_step, dest_ind += dest_step) {
|
||||
/* for constant optimization */
|
||||
if (size == 1) {
|
||||
__builtin_memcpy(dest_arr + dest_ind, src_arr + src_ind, 1);
|
||||
|
@ -244,7 +204,8 @@ SliceIndex __nac3_list_slice_assign_var_size(
|
|||
} else if (size == 8) {
|
||||
__builtin_memcpy(dest_arr + dest_ind * 8, src_arr + src_ind * 8, 8);
|
||||
} else {
|
||||
/* memcpy for var size, cannot overlap after previous alloca */
|
||||
/* memcpy for var size, cannot overlap after previous
|
||||
* alloca */
|
||||
__builtin_memcpy(dest_arr + dest_ind * size, src_arr + src_ind * size, size);
|
||||
}
|
||||
}
|
||||
|
@ -254,7 +215,7 @@ SliceIndex __nac3_list_slice_assign_var_size(
|
|||
__builtin_memmove(
|
||||
dest_arr + dest_ind * size,
|
||||
dest_arr + (dest_end + 1) * size,
|
||||
(dest_arr_len - dest_end - 1) * size
|
||||
(dest_arr_len - dest_end - 1) * size + size + size + size
|
||||
);
|
||||
return dest_arr_len - (dest_end - dest_ind) - 1;
|
||||
}
|
||||
|
@ -320,94 +281,53 @@ double __nac3_j0(double x) {
|
|||
return j0(x);
|
||||
}
|
||||
|
||||
uint32_t __nac3_ndarray_calc_size(
|
||||
const uint32_t* list_data,
|
||||
uint32_t list_len,
|
||||
uint32_t begin_idx,
|
||||
uint32_t end_idx
|
||||
) {
|
||||
uint32_t __nac3_ndarray_calc_size(const uint32_t* list_data, uint32_t list_len, uint32_t begin_idx, uint32_t end_idx) {
|
||||
return __nac3_ndarray_calc_size_impl(list_data, list_len, begin_idx, end_idx);
|
||||
}
|
||||
|
||||
uint64_t __nac3_ndarray_calc_size64(
|
||||
const uint64_t* list_data,
|
||||
uint64_t list_len,
|
||||
uint64_t begin_idx,
|
||||
uint64_t end_idx
|
||||
) {
|
||||
uint64_t
|
||||
__nac3_ndarray_calc_size64(const uint64_t* list_data, uint64_t list_len, uint64_t begin_idx, uint64_t end_idx) {
|
||||
return __nac3_ndarray_calc_size_impl(list_data, list_len, begin_idx, end_idx);
|
||||
}
|
||||
|
||||
void __nac3_ndarray_calc_nd_indices(
|
||||
uint32_t index,
|
||||
const uint32_t* dims,
|
||||
uint32_t num_dims,
|
||||
NDIndex* idxs
|
||||
) {
|
||||
void __nac3_ndarray_calc_nd_indices(uint32_t index, const uint32_t* dims, uint32_t num_dims, NDIndex* idxs) {
|
||||
__nac3_ndarray_calc_nd_indices_impl(index, dims, num_dims, idxs);
|
||||
}
|
||||
|
||||
void __nac3_ndarray_calc_nd_indices64(
|
||||
uint64_t index,
|
||||
const uint64_t* dims,
|
||||
uint64_t num_dims,
|
||||
NDIndex* idxs
|
||||
) {
|
||||
void __nac3_ndarray_calc_nd_indices64(uint64_t index, const uint64_t* dims, uint64_t num_dims, NDIndex* idxs) {
|
||||
__nac3_ndarray_calc_nd_indices_impl(index, dims, num_dims, idxs);
|
||||
}
|
||||
|
||||
uint32_t __nac3_ndarray_flatten_index(
|
||||
const uint32_t* dims,
|
||||
uint32_t num_dims,
|
||||
const NDIndex* indices,
|
||||
uint32_t num_indices
|
||||
) {
|
||||
uint32_t
|
||||
__nac3_ndarray_flatten_index(const uint32_t* dims, uint32_t num_dims, const NDIndex* indices, uint32_t num_indices) {
|
||||
return __nac3_ndarray_flatten_index_impl(dims, num_dims, indices, num_indices);
|
||||
}
|
||||
|
||||
uint64_t __nac3_ndarray_flatten_index64(
|
||||
const uint64_t* dims,
|
||||
uint64_t num_dims,
|
||||
const NDIndex* indices,
|
||||
uint64_t num_indices
|
||||
) {
|
||||
uint64_t
|
||||
__nac3_ndarray_flatten_index64(const uint64_t* dims, uint64_t num_dims, const NDIndex* indices, uint64_t num_indices) {
|
||||
return __nac3_ndarray_flatten_index_impl(dims, num_dims, indices, num_indices);
|
||||
}
|
||||
|
||||
void __nac3_ndarray_calc_broadcast(
|
||||
const uint32_t* lhs_dims,
|
||||
uint32_t lhs_ndims,
|
||||
const uint32_t* rhs_dims,
|
||||
uint32_t rhs_ndims,
|
||||
uint32_t* out_dims
|
||||
const uint32_t* lhs_dims, uint32_t lhs_ndims, const uint32_t* rhs_dims, uint32_t rhs_ndims, uint32_t* out_dims
|
||||
) {
|
||||
return __nac3_ndarray_calc_broadcast_impl(lhs_dims, lhs_ndims, rhs_dims, rhs_ndims, out_dims);
|
||||
}
|
||||
|
||||
void __nac3_ndarray_calc_broadcast64(
|
||||
const uint64_t* lhs_dims,
|
||||
uint64_t lhs_ndims,
|
||||
const uint64_t* rhs_dims,
|
||||
uint64_t rhs_ndims,
|
||||
uint64_t* out_dims
|
||||
const uint64_t* lhs_dims, uint64_t lhs_ndims, const uint64_t* rhs_dims, uint64_t rhs_ndims, uint64_t* out_dims
|
||||
) {
|
||||
return __nac3_ndarray_calc_broadcast_impl(lhs_dims, lhs_ndims, rhs_dims, rhs_ndims, out_dims);
|
||||
}
|
||||
|
||||
void __nac3_ndarray_calc_broadcast_idx(
|
||||
const uint32_t* src_dims,
|
||||
uint32_t src_ndims,
|
||||
const NDIndex* in_idx,
|
||||
NDIndex* out_idx
|
||||
const uint32_t* src_dims, uint32_t src_ndims, const NDIndex* in_idx, NDIndex* out_idx
|
||||
) {
|
||||
__nac3_ndarray_calc_broadcast_idx_impl(src_dims, src_ndims, in_idx, out_idx);
|
||||
}
|
||||
|
||||
void __nac3_ndarray_calc_broadcast_idx64(
|
||||
const uint64_t* src_dims,
|
||||
uint64_t src_ndims,
|
||||
const NDIndex* in_idx,
|
||||
NDIndex* out_idx
|
||||
const uint64_t* src_dims, uint64_t src_ndims, const NDIndex* in_idx, NDIndex* out_idx
|
||||
) {
|
||||
__nac3_ndarray_calc_broadcast_idx_impl(src_dims, src_ndims, in_idx, out_idx);
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
// This is made toggleable since `irrt_test.cpp` itself would include
|
||||
// headers that define these typedefs
|
||||
#ifdef IRRT_DEFINE_TYPEDEF_INTS
|
||||
using int8_t = _BitInt(8);
|
||||
using uint8_t = unsigned _BitInt(8);
|
||||
using int32_t = _BitInt(32);
|
||||
using uint32_t = unsigned _BitInt(32);
|
||||
using int64_t = _BitInt(64);
|
||||
using uint64_t = unsigned _BitInt(64);
|
||||
#endif
|
|
@ -0,0 +1,77 @@
|
|||
#pragma once
|
||||
|
||||
namespace {
|
||||
template <typename T>
|
||||
const T& max(const T& a, const T& b) {
|
||||
return a > b ? a : b;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T& min(const T& a, const T& b) {
|
||||
return a > b ? b : a;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool arrays_match(int len, T* as, T* bs) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (as[i] != bs[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace cstr_utils {
|
||||
bool is_empty(const char* str) {
|
||||
return str[0] == '\0';
|
||||
}
|
||||
|
||||
int8_t compare(const char* a, const char* b) {
|
||||
uint32_t i = 0;
|
||||
while (true) {
|
||||
if (a[i] < b[i]) {
|
||||
return -1;
|
||||
} else if (a[i] > b[i]) {
|
||||
return 1;
|
||||
} else { // a[i] == b[i]
|
||||
if (a[i] == '\0') {
|
||||
return 0;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int8_t equal(const char* a, const char* b) {
|
||||
return compare(a, b) == 0;
|
||||
}
|
||||
|
||||
uint32_t length(const char* str) {
|
||||
uint32_t length = 0;
|
||||
while (*str != '\0') {
|
||||
length++;
|
||||
str++;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
bool copy(const char* src, char* dst, uint32_t dst_max_size) {
|
||||
for (uint32_t i = 0; i < dst_max_size; i++) {
|
||||
bool is_last = i + 1 == dst_max_size;
|
||||
if (is_last && src[i] != '\0') {
|
||||
dst[i] = '\0';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (src[i] == '\0') {
|
||||
dst[i] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
dst[i] = src[i];
|
||||
}
|
||||
|
||||
__builtin_unreachable();
|
||||
}
|
||||
} // namespace cstr_utils
|
||||
} // namespace
|
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include <irrt/core.hpp>
|
||||
#include <irrt/int_defs.hpp>
|
||||
#include <irrt/utils.hpp>
|
|
@ -0,0 +1,13 @@
|
|||
// This file will be compiled like a real C++ program,
|
||||
// and we do have the luxury to use the standard libraries.
|
||||
// That is if the nix flakes do not have issues... especially on msys2...
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <test/test_core.hpp>
|
||||
|
||||
int main() {
|
||||
test::core::run();
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <test/util.hpp>
|
||||
#include <irrt_everything.hpp>
|
||||
|
||||
/*
|
||||
Include this header for every test_*.cpp
|
||||
*/
|
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include <test/includes.hpp>
|
||||
|
||||
namespace test { namespace core {
|
||||
void test_int_exp() {
|
||||
BEGIN_TEST();
|
||||
|
||||
assert_values_match(125, __nac3_int_exp_impl<int32_t>(5, 3));
|
||||
assert_values_match(3125, __nac3_int_exp_impl<int32_t>(5, 5));
|
||||
}
|
||||
|
||||
void run() {
|
||||
test_int_exp();
|
||||
}
|
||||
}} // namespace test::core
|
|
@ -0,0 +1,114 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
|
||||
template <class T>
|
||||
void print_value(const T& value) {}
|
||||
|
||||
template <>
|
||||
void print_value(const int8_t& value) {
|
||||
printf("%d", value);
|
||||
}
|
||||
|
||||
template <>
|
||||
void print_value(const int32_t& value) {
|
||||
printf("%d", value);
|
||||
}
|
||||
|
||||
template <>
|
||||
void print_value(const uint8_t& value) {
|
||||
printf("%u", value);
|
||||
}
|
||||
|
||||
template <>
|
||||
void print_value(const uint32_t& value) {
|
||||
printf("%u", value);
|
||||
}
|
||||
|
||||
template <>
|
||||
void print_value(const float& value) {
|
||||
printf("%f", value);
|
||||
}
|
||||
|
||||
template <>
|
||||
void print_value(const double& value) {
|
||||
printf("%f", value);
|
||||
}
|
||||
|
||||
void __begin_test(const char* function_name, const char* file, int line) {
|
||||
printf("######### Running %s @ %s:%d\n", function_name, file, line);
|
||||
}
|
||||
|
||||
#define BEGIN_TEST() __begin_test(__FUNCTION__, __FILE__, __LINE__)
|
||||
|
||||
void test_fail() {
|
||||
printf("[!] Test failed. Exiting with status code 1.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void debug_print_array(int len, const T* as) {
|
||||
printf("[");
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (i != 0)
|
||||
printf(", ");
|
||||
print_value(as[i]);
|
||||
}
|
||||
printf("]");
|
||||
}
|
||||
|
||||
void print_assertion_passed(const char* file, int line) {
|
||||
printf("[*] Assertion passed on %s:%d\n", file, line);
|
||||
}
|
||||
|
||||
void print_assertion_failed(const char* file, int line) {
|
||||
printf("[!] Assertion failed on %s:%d\n", file, line);
|
||||
}
|
||||
|
||||
void __assert_true(const char* file, int line, bool cond) {
|
||||
if (cond) {
|
||||
print_assertion_passed(file, line);
|
||||
} else {
|
||||
print_assertion_failed(file, line);
|
||||
test_fail();
|
||||
}
|
||||
}
|
||||
|
||||
#define assert_true(cond) __assert_true(__FILE__, __LINE__, cond)
|
||||
|
||||
template <typename T>
|
||||
void __assert_arrays_match(const char* file, int line, int len, const T* expected, const T* got) {
|
||||
if (arrays_match(len, expected, got)) {
|
||||
print_assertion_passed(file, line);
|
||||
} else {
|
||||
print_assertion_failed(file, line);
|
||||
printf("Expect = ");
|
||||
debug_print_array(len, expected);
|
||||
printf("\n");
|
||||
printf(" Got = ");
|
||||
debug_print_array(len, got);
|
||||
printf("\n");
|
||||
test_fail();
|
||||
}
|
||||
}
|
||||
|
||||
#define assert_arrays_match(len, expected, got) __assert_arrays_match(__FILE__, __LINE__, len, expected, got)
|
||||
|
||||
template <typename T>
|
||||
void __assert_values_match(const char* file, int line, T expected, T got) {
|
||||
if (expected == got) {
|
||||
print_assertion_passed(file, line);
|
||||
} else {
|
||||
print_assertion_failed(file, line);
|
||||
printf("Expect = ");
|
||||
print_value(expected);
|
||||
printf("\n");
|
||||
printf(" Got = ");
|
||||
print_value(got);
|
||||
printf("\n");
|
||||
test_fail();
|
||||
}
|
||||
}
|
||||
|
||||
#define assert_values_match(expected, got) __assert_values_match(__FILE__, __LINE__, expected, got)
|
|
@ -1,5 +1,7 @@
|
|||
use crate::typecheck::typedef::Type;
|
||||
|
||||
mod test;
|
||||
|
||||
use super::{
|
||||
classes::{
|
||||
ArrayLikeIndexer, ArrayLikeValue, ArraySliceValue, ListValue, NDArrayValue,
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{path::Path, process::Command};
|
||||
|
||||
#[test]
|
||||
fn run_irrt_test() {
|
||||
assert!(
|
||||
cfg!(feature = "test"),
|
||||
"Please do `cargo test -F test` to compile `irrt_test.out` and run test"
|
||||
);
|
||||
|
||||
let irrt_test_out_path = Path::new(concat!(env!("OUT_DIR"), "/irrt_test.out"));
|
||||
let output = Command::new(irrt_test_out_path.to_str().unwrap()).output().unwrap();
|
||||
|
||||
if !output.status.success() {
|
||||
eprintln!("irrt_test failed with status {}:", output.status);
|
||||
eprintln!("====== stdout ======");
|
||||
eprintln!("{}", String::from_utf8(output.stdout).unwrap());
|
||||
eprintln!("====== stderr ======");
|
||||
eprintln!("{}", String::from_utf8(output.stderr).unwrap());
|
||||
eprintln!("====================");
|
||||
|
||||
panic!("irrt_test failed");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -41,6 +41,7 @@ pub mod extern_fns;
|
|||
mod generator;
|
||||
pub mod irrt;
|
||||
pub mod llvm_intrinsics;
|
||||
pub mod model;
|
||||
pub mod numpy;
|
||||
pub mod stmt;
|
||||
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
use inkwell::{
|
||||
context::Context,
|
||||
types::{BasicType, BasicTypeEnum},
|
||||
values::{BasicValue, BasicValueEnum, PointerValue},
|
||||
};
|
||||
|
||||
use crate::codegen::{CodeGenContext, CodeGenerator};
|
||||
|
||||
use super::{slice::ArraySlice, Int, Pointer};
|
||||
|
||||
/*
|
||||
Explanation on the abstraction:
|
||||
|
||||
In LLVM, there are TYPES and VALUES.
|
||||
|
||||
Inkwell gives us TYPES [`BasicTypeEnum<'ctx>`] and VALUES [`BasicValueEnum<'ctx>`],
|
||||
but by themselves, they lack a lot of Rust compile-time known info.
|
||||
|
||||
e.g., You did `let ptr = builder.build_alloca(my_llvm_ndarray_struct_ty)`,
|
||||
but `ptr` is just a `PointerValue<'ctx>`, almost everything about the
|
||||
underlying `my_llvm_ndarray_struct_ty` is gone.
|
||||
|
||||
The `Model` abstraction is a wrapper around inkwell TYPES and VALUES but with
|
||||
a richer interface.
|
||||
|
||||
`Model<'ctx>` is a wrapper around for an inkwell TYPE:
|
||||
- `NIntModel<Byte>` is a i8.
|
||||
- `NIntModel<Int32>` is a i32.
|
||||
- `NIntModel<Int64>` is a i64.
|
||||
- `IntModel` is a carrier for an inkwell `IntType<'ctx>`,
|
||||
used when the type is dynamic/cannot be specified in Rust compile-time.
|
||||
- `PointerModel<'ctx, E>` is a wrapper for `PointerType<'ctx>`,
|
||||
where `E` is another `Model<'ctx>` that describes the element type of the pointer.
|
||||
- `StructModel<'ctx, NDArray>` is a wrapper for `StructType<'ctx>`,
|
||||
with additional information encoded within `NDArray`. (See `IsStruct<'ctx>`)
|
||||
|
||||
`Model<'ctx>::Value`/`ModelValue<'ctx>` is a wrapper around for an inkwell VALUE:
|
||||
- `NInt<'ctx, T>` is a value of `NIntModel<'ctx, T>`,
|
||||
where `T` could be `Byte`, `Int32`, or `Int64`.
|
||||
- `Pointer<'ctx, E>` is a value of `PointerModel<'ctx, E>`.
|
||||
|
||||
Other interesting utilities:
|
||||
- Given a `Model<'ctx>`, say, `let ndarray_model = StructModel<'ctx, NDArray>`,
|
||||
you are do `ndarray_model.alloca(ctx, "my_ndarray")` to get a `Pointer<'ctx, Struct<'ctx, NDArray>>`,
|
||||
notice that all LLVM type information are preserved.
|
||||
- For a `let my_ndarray = Pointer<'ctx, StructModel<NDArray>>`, you can access a field by doing
|
||||
`my_ndarray.gep(ctx, |f| f.itemsize).load() // or .store()`, and you can chain them
|
||||
together for nested structures.
|
||||
|
||||
A brief summary on the `Model<'ctx>` and `ModelValue<'ctx>` traits:
|
||||
- Model<'ctx>
|
||||
// The associated ModelValue of this Model
|
||||
- type Value: ModelValue<'ctx>
|
||||
|
||||
// Get the LLVM type of this Model
|
||||
- fn get_llvm_type(&self)
|
||||
|
||||
// Check if the input type is equal to the LLVM type of this Model
|
||||
// NOTE: this function is provideed through `CanCheckLLVMType<'ctx>`
|
||||
- fn check_llvm_type(&self, ty) -> Result<(), String>
|
||||
|
||||
// Check if the input value's type is equal to the LLVM type of this Model.
|
||||
//
|
||||
// If so, wrap it with `Self::Value`.
|
||||
- fn review_value<V: BasicType<'ctx>>(&self, val: V) -> Result<Self::Value, String>
|
||||
|
||||
- ModelValue<'ctx>
|
||||
// get the LLVM value of this ModelValue
|
||||
- fn get_llvm_value(&self) -> BasicValueEnum<'ctx>
|
||||
*/
|
||||
|
||||
/// A value that belongs to/produced by a [`Model<'ctx>`]
|
||||
pub trait ModelValue<'ctx>: Clone + Copy {
|
||||
/// Get the LLVM value of this [`ModelValue<'ctx>`]
|
||||
fn get_llvm_value(&self) -> BasicValueEnum<'ctx>;
|
||||
}
|
||||
|
||||
// NOTE: Should have been within [`Model<'ctx>`],
|
||||
// but rust object safety requirements made it necessary to
|
||||
// split the trait.
|
||||
pub trait CanCheckLLVMType<'ctx> {
|
||||
/// See [`Model::check_llvm_type`]
|
||||
fn check_llvm_type_impl(
|
||||
&self,
|
||||
ctx: &'ctx Context,
|
||||
ty: BasicTypeEnum<'ctx>,
|
||||
) -> Result<(), String>;
|
||||
}
|
||||
|
||||
pub trait Model<'ctx>: Clone + Copy + CanCheckLLVMType<'ctx> + Sized {
|
||||
/// The associated [`ModelValue<'ctx>`] of this Model.
|
||||
type Value: ModelValue<'ctx>;
|
||||
|
||||
/// Get the LLVM type of this [`Model<'ctx>`]
|
||||
fn get_llvm_type(&self, ctx: &'ctx Context) -> BasicTypeEnum<'ctx>;
|
||||
|
||||
/// Check if the input type is equal to the LLVM type of this Model.
|
||||
///
|
||||
/// If it doesn't match, an [`Err`] with a human-readable message is
|
||||
/// thrown explaining *how* it was different. Meant for debugging.
|
||||
fn check_llvm_type<T: BasicType<'ctx>>(&self, ctx: &'ctx Context, ty: T) -> Result<(), String> {
|
||||
self.check_llvm_type_impl(ctx, ty.as_basic_type_enum())
|
||||
}
|
||||
|
||||
/// Check if the input value's type is equal to the LLVM type of this Model
|
||||
/// (using [`Model::check_llvm_type`]).
|
||||
///
|
||||
/// If so, wrap it with [`Model::Value`].
|
||||
fn review_value<V: BasicValue<'ctx>>(
|
||||
&self,
|
||||
ctx: &'ctx Context,
|
||||
value: V,
|
||||
) -> Result<Self::Value, String>;
|
||||
|
||||
/// Check if [`Self::Value`] has the correct type described by this [`Model<'ctx>`]
|
||||
fn check_value(&self, ctx: &'ctx Context, value: Self::Value) -> Result<(), String> {
|
||||
self.review_value(ctx, value.get_llvm_value())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Build an instruction to allocate a value with the LLVM type of this [`Model<'ctx>`].
|
||||
fn alloca(&self, ctx: &CodeGenContext<'ctx, '_>, name: &str) -> Pointer<'ctx, Self> {
|
||||
Pointer {
|
||||
element: *self,
|
||||
value: ctx.builder.build_alloca(self.get_llvm_type(ctx.ctx), name).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build an instruction to allocate an array of the LLVM type of this [`Model<'ctx>`].
|
||||
fn array_alloca(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
count: Int<'ctx>,
|
||||
name: &str,
|
||||
) -> ArraySlice<'ctx, Self> {
|
||||
ArraySlice {
|
||||
num_elements: count,
|
||||
pointer: Pointer {
|
||||
element: *self,
|
||||
value: ctx
|
||||
.builder
|
||||
.build_array_alloca(self.get_llvm_type(ctx.ctx), count.0, name)
|
||||
.unwrap(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Do [`CodeGenerator::gen_var_alloc`] with the LLVM type of this [`Model<'ctx>`].
|
||||
fn var_alloc<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
name: Option<&str>,
|
||||
) -> Result<Pointer<'ctx, Self>, String> {
|
||||
let value = generator.gen_var_alloc(ctx, self.get_llvm_type(ctx.ctx), name)?;
|
||||
Ok(Pointer { element: *self, value })
|
||||
}
|
||||
|
||||
/// Do [`CodeGenerator::gen_array_var_alloc`] with the LLVM type of this [`Model<'ctx>`].
|
||||
fn array_var_alloc<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
size: Int<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> Result<Pointer<'ctx, Self>, String> {
|
||||
let slice =
|
||||
generator.gen_array_var_alloc(ctx, self.get_llvm_type(ctx.ctx), size.0, name)?;
|
||||
let ptr = PointerValue::from(slice); // TODO: Remove ArraySliceValue
|
||||
|
||||
Ok(Pointer { element: *self, value: ptr })
|
||||
}
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
use inkwell::{
|
||||
context::Context,
|
||||
types::{BasicType, BasicTypeEnum, IntType},
|
||||
values::{BasicValue, BasicValueEnum, IntValue},
|
||||
};
|
||||
|
||||
use super::{
|
||||
core::*,
|
||||
int_util::{check_int_llvm_type, review_int_llvm_value},
|
||||
Int, IntModel,
|
||||
};
|
||||
|
||||
/// A marker trait to mark a singleton struct that describes a particular fixed integer type.
|
||||
/// See [`Bool`], [`Byte`], [`Int32`], etc.
|
||||
///
|
||||
/// The [`Default`] trait is to enable auto-instantiations.
|
||||
pub trait NIntKind: Clone + Copy + Default {
|
||||
/// Get the [`IntType<'ctx>`] of this [`NIntKind`].
|
||||
fn get_int_type(ctx: &Context) -> IntType<'_>;
|
||||
|
||||
/// Get the [`IntType<'ctx>`] of this [`NIntKind`].
|
||||
///
|
||||
/// Compared to using [`NIntKind::get_int_type`], this
|
||||
/// function does not require [`Context`].
|
||||
fn get_bit_width() -> u32;
|
||||
}
|
||||
|
||||
// Some pre-defined fixed integers
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct Bool;
|
||||
pub type BoolModel = NIntModel<Bool>;
|
||||
|
||||
impl NIntKind for Bool {
|
||||
fn get_int_type(ctx: &Context) -> IntType<'_> {
|
||||
ctx.bool_type()
|
||||
}
|
||||
|
||||
fn get_bit_width() -> u32 {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct Byte;
|
||||
pub type ByteModel = NIntModel<Byte>;
|
||||
|
||||
impl NIntKind for Byte {
|
||||
fn get_int_type(ctx: &Context) -> IntType<'_> {
|
||||
ctx.i8_type()
|
||||
}
|
||||
|
||||
fn get_bit_width() -> u32 {
|
||||
8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct Int32;
|
||||
pub type Int32Model = NIntModel<Int32>;
|
||||
|
||||
impl NIntKind for Int32 {
|
||||
fn get_int_type(ctx: &Context) -> IntType<'_> {
|
||||
ctx.i32_type()
|
||||
}
|
||||
|
||||
fn get_bit_width() -> u32 {
|
||||
32
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct Int64;
|
||||
pub type Int64Model = NIntModel<Int64>;
|
||||
|
||||
impl NIntKind for Int64 {
|
||||
fn get_int_type(ctx: &Context) -> IntType<'_> {
|
||||
ctx.i64_type()
|
||||
}
|
||||
|
||||
fn get_bit_width() -> u32 {
|
||||
64
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Model`] representing an [`IntType<'ctx>`] of a specified bit width.
|
||||
///
|
||||
/// Also see [`IntModel`], which is less constrained than [`NIntModel`],
|
||||
/// but enables one to handle dynamic [`IntType<'ctx>`] at runtime.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct NIntModel<T: NIntKind>(pub T);
|
||||
|
||||
impl<'ctx, T: NIntKind> CanCheckLLVMType<'ctx> for NIntModel<T> {
|
||||
fn check_llvm_type_impl(
|
||||
&self,
|
||||
ctx: &'ctx Context,
|
||||
ty: BasicTypeEnum<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
check_int_llvm_type(ty, T::get_int_type(ctx))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx, T: NIntKind> Model<'ctx> for NIntModel<T> {
|
||||
type Value = NInt<'ctx, T>;
|
||||
|
||||
fn get_llvm_type(&self, ctx: &'ctx Context) -> BasicTypeEnum<'ctx> {
|
||||
T::get_int_type(ctx).as_basic_type_enum()
|
||||
}
|
||||
|
||||
fn review_value<V: BasicValue<'ctx>>(
|
||||
&self,
|
||||
ctx: &'ctx Context,
|
||||
value: V,
|
||||
) -> Result<Self::Value, String> {
|
||||
let value = review_int_llvm_value(value.as_basic_value_enum(), T::get_int_type(ctx))?;
|
||||
Ok(NInt { kind: self.0, value })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NIntKind> NIntModel<T> {
|
||||
/// "Demote" this [`NIntModel<T>`] to an [`IntModel`].
|
||||
///
|
||||
/// Information about the [`NIntKind`] will be lost.
|
||||
pub fn to_int_model(self, ctx: &Context) -> IntModel<'_> {
|
||||
IntModel(T::get_int_type(ctx))
|
||||
}
|
||||
|
||||
/// Create an unsigned constant of this [`NIntModel`].
|
||||
pub fn constant<'ctx>(&self, ctx: &'ctx Context, value: u64) -> NInt<'ctx, T> {
|
||||
NInt { kind: self.0, value: T::get_int_type(ctx).const_int(value, false) }
|
||||
}
|
||||
}
|
||||
|
||||
/// A value of [`NIntModel<'ctx>`]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct NInt<'ctx, T: NIntKind> {
|
||||
/// The [`NIntKind`] marker of this [`NInt`]
|
||||
pub kind: T,
|
||||
/// The LLVM value of this [`NInt`].
|
||||
pub value: IntValue<'ctx>,
|
||||
}
|
||||
|
||||
impl<'ctx, T: NIntKind> ModelValue<'ctx> for NInt<'ctx, T> {
|
||||
fn get_llvm_value(&self) -> BasicValueEnum<'ctx> {
|
||||
self.value.as_basic_value_enum()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx, T: NIntKind> NInt<'ctx, T> {
|
||||
/// "Demote" this [`NInt<T>`] to an [`Int`].
|
||||
///
|
||||
/// Information about the [`NIntKind`] will be lost.
|
||||
pub fn to_int(self) -> Int<'ctx> {
|
||||
Int(self.value)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
use inkwell::{
|
||||
types::{BasicMetadataTypeEnum, BasicType},
|
||||
values::{AnyValue, BasicMetadataValueEnum, BasicValueEnum},
|
||||
};
|
||||
|
||||
use crate::codegen::{model::*, CodeGenContext};
|
||||
|
||||
// TODO: Variadic argument?
|
||||
pub struct FunctionBuilder<'ctx, 'a> {
|
||||
ctx: &'a CodeGenContext<'ctx, 'a>,
|
||||
fn_name: &'a str,
|
||||
arguments: Vec<(BasicMetadataTypeEnum<'ctx>, BasicMetadataValueEnum<'ctx>)>,
|
||||
}
|
||||
|
||||
impl<'ctx, 'a> FunctionBuilder<'ctx, 'a> {
|
||||
pub fn begin(ctx: &'a CodeGenContext<'ctx, 'a>, fn_name: &'a str) -> Self {
|
||||
FunctionBuilder { ctx, fn_name, arguments: Vec::new() }
|
||||
}
|
||||
|
||||
// NOTE: `_name` is for self-documentation
|
||||
#[must_use]
|
||||
pub fn arg<M: Model<'ctx>>(mut self, _name: &'static str, model: M, value: M::Value) -> Self {
|
||||
model.check_value(self.ctx.ctx, value).unwrap(); // Panics if the passed `value` has the incorrect type.
|
||||
|
||||
self.arguments
|
||||
.push((model.get_llvm_type(self.ctx.ctx).into(), value.get_llvm_value().into()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn returning<M: Model<'ctx>>(self, name: &'static str, return_model: M) -> M::Value {
|
||||
let (param_tys, param_vals): (Vec<_>, Vec<_>) = self.arguments.into_iter().unzip();
|
||||
|
||||
// Get the LLVM function, create (by declaring) the function if it doesn't exist in `ctx.module`.
|
||||
let function = self.ctx.module.get_function(self.fn_name).unwrap_or_else(|| {
|
||||
let return_type = return_model.get_llvm_type(self.ctx.ctx);
|
||||
let fn_type = return_type.fn_type(¶m_tys, false);
|
||||
self.ctx.module.add_function(self.fn_name, fn_type, None)
|
||||
});
|
||||
|
||||
// Build call
|
||||
let ret = self.ctx.builder.build_call(function, ¶m_vals, name).unwrap();
|
||||
|
||||
// Check the return value/type
|
||||
let Ok(ret) = BasicValueEnum::try_from(ret.as_any_value_enum()) else {
|
||||
panic!("Return type is not a BasicValue");
|
||||
};
|
||||
return_model.review_value(self.ctx.ctx, ret).unwrap()
|
||||
}
|
||||
|
||||
// TODO: Code duplication, but otherwise returning<S: Optic<'ctx>> cannot resolve S if return_optic = None
|
||||
pub fn returning_void(self) {
|
||||
let (param_tys, param_vals): (Vec<_>, Vec<_>) = self.arguments.into_iter().unzip();
|
||||
|
||||
let function = self.ctx.module.get_function(self.fn_name).unwrap_or_else(|| {
|
||||
let return_type = self.ctx.ctx.void_type();
|
||||
let fn_type = return_type.fn_type(¶m_tys, false);
|
||||
self.ctx.module.add_function(self.fn_name, fn_type, None)
|
||||
});
|
||||
|
||||
self.ctx.builder.build_call(function, ¶m_vals, "").unwrap();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
use inkwell::{
|
||||
context::Context,
|
||||
types::{BasicType, BasicTypeEnum, IntType},
|
||||
values::{BasicValue, BasicValueEnum, IntValue},
|
||||
};
|
||||
|
||||
use crate::codegen::{model::int_util::review_int_llvm_value, CodeGenContext};
|
||||
|
||||
use super::{core::*, int_util::check_int_llvm_type};
|
||||
|
||||
/// A model representing an [`IntType<'ctx>`].
|
||||
///
|
||||
/// Also see [`NIntModel`], which is more constrained than [`IntModel`]
|
||||
/// but provides more type-safe mechanisms and even auto-derivation of [`BasicTypeEnum<'ctx>`]
|
||||
/// for creating LLVM structures.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct IntModel<'ctx>(pub IntType<'ctx>);
|
||||
|
||||
impl<'ctx> CanCheckLLVMType<'ctx> for IntModel<'ctx> {
|
||||
fn check_llvm_type_impl(
|
||||
&self,
|
||||
_ctx: &'ctx Context,
|
||||
ty: BasicTypeEnum<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
check_int_llvm_type(ty, self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> Model<'ctx> for IntModel<'ctx> {
|
||||
type Value = Int<'ctx>;
|
||||
|
||||
fn get_llvm_type(&self, _ctx: &'ctx Context) -> BasicTypeEnum<'ctx> {
|
||||
self.0.as_basic_type_enum()
|
||||
}
|
||||
|
||||
fn review_value<V: BasicValue<'ctx>>(
|
||||
&self,
|
||||
_ctx: &'ctx Context,
|
||||
value: V,
|
||||
) -> Result<Self::Value, String> {
|
||||
review_int_llvm_value(value.as_basic_value_enum(), self.0).map(Int)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> IntModel<'ctx> {
|
||||
/// Create a constant value that inhabits this [`IntModel<'ctx>`].
|
||||
#[must_use]
|
||||
pub fn constant(&self, value: u64) -> Int<'ctx> {
|
||||
Int(self.0.const_int(value, false))
|
||||
}
|
||||
|
||||
/// Check if `other` is fully compatible with this [`IntModel<'ctx>`].
|
||||
///
|
||||
/// This simply checks if the underlying [`IntType<'ctx>`] has
|
||||
/// the same number of bits.
|
||||
#[must_use]
|
||||
pub fn same_as(&self, other: IntModel<'ctx>) -> bool {
|
||||
// TODO: or `self.0 == other.0` would also work?
|
||||
self.0.get_bit_width() == other.0.get_bit_width()
|
||||
}
|
||||
}
|
||||
|
||||
/// An inhabitant of an [`IntModel<'ctx>`]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Int<'ctx>(pub IntValue<'ctx>);
|
||||
|
||||
impl<'ctx> ModelValue<'ctx> for Int<'ctx> {
|
||||
fn get_llvm_value(&self) -> BasicValueEnum<'ctx> {
|
||||
self.0.as_basic_value_enum()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> Int<'ctx> {
|
||||
#[must_use]
|
||||
pub fn signed_cast_to_int(
|
||||
self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
target_int: IntModel<'ctx>,
|
||||
name: &str,
|
||||
) -> Int<'ctx> {
|
||||
Int(ctx.builder.build_int_s_extend_or_bit_cast(self.0, target_int.0, name).unwrap())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
use inkwell::{
|
||||
types::{BasicType, BasicTypeEnum, IntType},
|
||||
values::{BasicValueEnum, IntValue},
|
||||
};
|
||||
|
||||
/// Helper function to check if `scrutinee` is the same as `expected_int_type`
|
||||
pub fn check_int_llvm_type<'ctx>(
|
||||
ty: BasicTypeEnum<'ctx>,
|
||||
expected_int_type: IntType<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
// Check if llvm_type is int type
|
||||
let BasicTypeEnum::IntType(ty) = ty else {
|
||||
return Err(format!("Expecting an int type but got {ty:?}"));
|
||||
};
|
||||
|
||||
// Check bit width
|
||||
if ty.get_bit_width() != expected_int_type.get_bit_width() {
|
||||
return Err(format!(
|
||||
"Expecting an int type of {}-bit(s) but got int type {}-bit(s)",
|
||||
expected_int_type.get_bit_width(),
|
||||
ty.get_bit_width()
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Helper function to cast `scrutinee` is into an [`IntValue<'ctx>`].
|
||||
/// The LLVM type of `scrutinee` will be checked with [`check_int_llvm_type`].
|
||||
pub fn review_int_llvm_value<'ctx>(
|
||||
value: BasicValueEnum<'ctx>,
|
||||
expected_int_type: IntType<'ctx>,
|
||||
) -> Result<IntValue<'ctx>, String> {
|
||||
// Check if value is of int type, error if that is anything else
|
||||
check_int_llvm_type(value.get_type().as_basic_type_enum(), expected_int_type)?;
|
||||
|
||||
// Ok, it is must be an int
|
||||
Ok(value.into_int_value())
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
pub mod core;
|
||||
pub mod fixed_int;
|
||||
pub mod function_builder;
|
||||
pub mod int;
|
||||
mod int_util;
|
||||
pub mod opaque;
|
||||
pub mod pointer;
|
||||
pub mod slice;
|
||||
pub mod structure;
|
||||
|
||||
pub use core::*;
|
||||
pub use fixed_int::*;
|
||||
pub use function_builder::*;
|
||||
pub use int::*;
|
||||
pub use opaque::*;
|
||||
pub use pointer::*;
|
||||
pub use slice::*;
|
||||
pub use structure::*;
|
|
@ -0,0 +1,57 @@
|
|||
use inkwell::{
|
||||
context::Context,
|
||||
types::BasicTypeEnum,
|
||||
values::{BasicValue, BasicValueEnum},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// A [`Model`] that holds an arbitrary [`BasicTypeEnum`].
|
||||
///
|
||||
/// Use this and [`Opaque`] when you are dealing with a [`BasicTypeEnum<'ctx>`]
|
||||
/// at runtime and there is no way to abstract your implementation
|
||||
/// with [`Model`].
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct OpaqueModel<'ctx>(pub BasicTypeEnum<'ctx>);
|
||||
|
||||
impl<'ctx> CanCheckLLVMType<'ctx> for OpaqueModel<'ctx> {
|
||||
fn check_llvm_type_impl(
|
||||
&self,
|
||||
_ctx: &'ctx Context,
|
||||
ty: BasicTypeEnum<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
if ty == self.0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("Expecting {}, but got {}", self.0, ty))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> Model<'ctx> for OpaqueModel<'ctx> {
|
||||
type Value = Opaque<'ctx>;
|
||||
|
||||
fn get_llvm_type(&self, _ctx: &'ctx Context) -> BasicTypeEnum<'ctx> {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn review_value<V: BasicValue<'ctx>>(
|
||||
&self,
|
||||
ctx: &'ctx Context,
|
||||
value: V,
|
||||
) -> Result<Self::Value, String> {
|
||||
let value = value.as_basic_value_enum();
|
||||
self.check_llvm_type(ctx, value.get_type())?;
|
||||
Ok(Opaque(value))
|
||||
}
|
||||
}
|
||||
|
||||
/// A value of [`OpaqueModel`]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Opaque<'ctx>(pub BasicValueEnum<'ctx>);
|
||||
|
||||
impl<'ctx> ModelValue<'ctx> for Opaque<'ctx> {
|
||||
fn get_llvm_value(&self) -> BasicValueEnum<'ctx> {
|
||||
self.0
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
use inkwell::{
|
||||
context::Context,
|
||||
types::{BasicType, BasicTypeEnum},
|
||||
values::{BasicValue, BasicValueEnum, PointerValue},
|
||||
AddressSpace,
|
||||
};
|
||||
|
||||
use crate::codegen::CodeGenContext;
|
||||
|
||||
use super::{core::*, OpaqueModel};
|
||||
|
||||
/// A [`Model<'ctx>`] representing an LLVM [`PointerType<'ctx>`]
|
||||
/// with *full* information on the element u
|
||||
///
|
||||
/// [`self.0`] contains [`Model<'ctx>`] that represents the
|
||||
/// LLVM type of element of the [`PointerType<'ctx>`] is pointing at
|
||||
/// (like `PointerType<'ctx>::get_element_type()`, but abstracted as a [`Model<'ctx>`]).
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct PointerModel<E>(pub E);
|
||||
|
||||
impl<'ctx, E: Model<'ctx>> CanCheckLLVMType<'ctx> for PointerModel<E> {
|
||||
fn check_llvm_type_impl(
|
||||
&self,
|
||||
ctx: &'ctx Context,
|
||||
ty: BasicTypeEnum<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
// Check if scrutinee is even a PointerValue
|
||||
let BasicTypeEnum::PointerType(ty) = ty else {
|
||||
return Err(format!("Expecting a pointer value, but got {ty:?}"));
|
||||
};
|
||||
|
||||
// Check the type of what the pointer is pointing at
|
||||
// TODO: This will be deprecated by inkwell > llvm14 because `get_element_type()` will be gone
|
||||
let Ok(element_ty) = BasicTypeEnum::try_from(ty.get_element_type()) else {
|
||||
return Err(format!(
|
||||
"Expecting pointer to point to an inkwell BasicValue, but got {ty:?}"
|
||||
));
|
||||
};
|
||||
|
||||
self.0.check_llvm_type(ctx, element_ty) // TODO: Include backtrace?
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx, E: Model<'ctx>> Model<'ctx> for PointerModel<E> {
|
||||
type Value = Pointer<'ctx, E>;
|
||||
|
||||
fn get_llvm_type(&self, ctx: &'ctx Context) -> BasicTypeEnum<'ctx> {
|
||||
self.0.get_llvm_type(ctx).ptr_type(AddressSpace::default()).as_basic_type_enum()
|
||||
}
|
||||
|
||||
fn review_value<V: BasicValue<'ctx>>(
|
||||
&self,
|
||||
ctx: &'ctx Context,
|
||||
value: V,
|
||||
) -> Result<Self::Value, String> {
|
||||
let value = value.as_basic_value_enum();
|
||||
|
||||
self.check_llvm_type(ctx, value.get_type())?;
|
||||
|
||||
// TODO: Check get_element_type(). For inkwell LLVM 14 at least...
|
||||
Ok(Pointer { element: self.0, value: value.into_pointer_value() })
|
||||
}
|
||||
}
|
||||
|
||||
/// An inhabitant of [`PointerModel<E>`]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Pointer<'ctx, E: Model<'ctx>> {
|
||||
pub element: E,
|
||||
pub value: PointerValue<'ctx>,
|
||||
}
|
||||
|
||||
impl<'ctx, E: Model<'ctx>> ModelValue<'ctx> for Pointer<'ctx, E> {
|
||||
fn get_llvm_value(&self) -> BasicValueEnum<'ctx> {
|
||||
self.value.as_basic_value_enum()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx, E: Model<'ctx>> Pointer<'ctx, E> {
|
||||
/// Build an instruction to store a value into this pointer
|
||||
pub fn store(&self, ctx: &CodeGenContext<'ctx, '_>, val: E::Value) {
|
||||
ctx.builder.build_store(self.value, val.get_llvm_value()).unwrap();
|
||||
}
|
||||
|
||||
/// Build an instruction to load a value from this pointer
|
||||
pub fn load(&self, ctx: &CodeGenContext<'ctx, '_>, name: &str) -> E::Value {
|
||||
let val = ctx.builder.build_load(self.value, name).unwrap();
|
||||
self.element.review_value(ctx.ctx, val).unwrap() // If unwrap() panics, there is a logic error in your code.
|
||||
}
|
||||
|
||||
/// "Demote" the [`Model`] of the thing this pointer is pointing at.
|
||||
pub fn to_opaque(self, ctx: &'ctx Context) -> Pointer<'ctx, OpaqueModel<'ctx>> {
|
||||
Pointer { element: OpaqueModel(self.element.get_llvm_type(ctx)), value: self.value }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
use crate::codegen::{CodeGenContext, CodeGenerator};
|
||||
|
||||
use super::{Int, Model, Pointer};
|
||||
|
||||
/// An LLVM "slice" - literally just a pointer and a length value.
|
||||
/// The pointer points to a location with `num_elements` **contiguously** placed
|
||||
/// values of [`E`][`Model<ctx>`] in memory.
|
||||
///
|
||||
/// NOTE: This is NOT a [`Model`]! This is simply a helper
|
||||
/// structure to aggregate a length value and a pointer together.
|
||||
pub struct ArraySlice<'ctx, E: Model<'ctx>> {
|
||||
pub pointer: Pointer<'ctx, E>,
|
||||
pub num_elements: Int<'ctx>,
|
||||
}
|
||||
|
||||
impl<'ctx, E: Model<'ctx>> ArraySlice<'ctx, E> {
|
||||
/// Get the `idx`-nth element of this [`ArraySlice`],
|
||||
/// but doesn't do an assertion to see if `idx` is
|
||||
/// out of bounds or not.
|
||||
///
|
||||
/// Also see [`ArraySlice::ix`].
|
||||
pub fn ix_unchecked(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
idx: Int<'ctx>,
|
||||
name: &str,
|
||||
) -> Pointer<'ctx, E> {
|
||||
let element_addr =
|
||||
unsafe { ctx.builder.build_in_bounds_gep(self.pointer.value, &[idx.0], name).unwrap() };
|
||||
Pointer { value: element_addr, element: self.pointer.element }
|
||||
}
|
||||
|
||||
/// Call [`ArraySlice::ix_unchecked`], but
|
||||
/// checks if `idx` is in bounds, otherwise
|
||||
/// a runtime `IndexError` will be thrown.
|
||||
pub fn ix<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
idx: Int<'ctx>,
|
||||
name: &str,
|
||||
) -> Pointer<'ctx, E> {
|
||||
let int_type = self.num_elements.0.get_type(); // NOTE: Weird get_type(), see comment under `trait Ixed`
|
||||
|
||||
assert_eq!(int_type.get_bit_width(), idx.0.get_type().get_bit_width()); // Might as well check bit width to catch bugs
|
||||
|
||||
// TODO: SGE or UGE? or make it defined by the implementee?
|
||||
|
||||
// Check `0 <= index`
|
||||
let lower_bounded = ctx
|
||||
.builder
|
||||
.build_int_compare(
|
||||
inkwell::IntPredicate::SLE,
|
||||
int_type.const_zero(),
|
||||
idx.0,
|
||||
"lower_bounded",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Check `index < num_elements`
|
||||
let upper_bounded = ctx
|
||||
.builder
|
||||
.build_int_compare(
|
||||
inkwell::IntPredicate::SLT,
|
||||
idx.0,
|
||||
self.num_elements.0,
|
||||
"upper_bounded",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Compute `0 <= index && index < num_elements`
|
||||
let bounded = ctx.builder.build_and(lower_bounded, upper_bounded, "bounded").unwrap();
|
||||
|
||||
// Assert `bounded`
|
||||
ctx.make_assert(
|
||||
generator,
|
||||
bounded,
|
||||
"0:IndexError",
|
||||
"nac3core LLVM codegen attempting to access out of bounds array index {0}. Must satisfy 0 <= index < {2}",
|
||||
[ Some(idx.0), Some(self.num_elements.0), None],
|
||||
ctx.current_loc
|
||||
);
|
||||
|
||||
// ...and finally do indexing
|
||||
self.ix_unchecked(ctx, idx, name)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,396 @@
|
|||
use inkwell::{
|
||||
context::Context,
|
||||
types::{BasicType, BasicTypeEnum, StructType},
|
||||
values::{BasicValue, BasicValueEnum, StructValue},
|
||||
};
|
||||
use itertools::{izip, Itertools};
|
||||
|
||||
use crate::codegen::CodeGenContext;
|
||||
|
||||
use super::{core::CanCheckLLVMType, Model, ModelValue, Pointer};
|
||||
|
||||
/// An LLVM struct's "field".
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Field<E> {
|
||||
/// The GEP index of the field.
|
||||
pub gep_index: u64,
|
||||
|
||||
/// The name of this field. Generally named
|
||||
/// to how the field is named in ARTIQ or IRRT.
|
||||
///
|
||||
/// NOTE: This is only used for debugging.
|
||||
pub name: &'static str,
|
||||
|
||||
/// The [`Model`] of the field.
|
||||
pub element: E,
|
||||
}
|
||||
|
||||
// A helper struct for [`FieldBuilder`]
|
||||
struct FieldLLVM<'ctx> {
|
||||
gep_index: u64,
|
||||
name: &'ctx str,
|
||||
|
||||
// Only CanCheckLLVMType is needed, dont use `Model<'ctx>`
|
||||
llvm_type_model: Box<dyn CanCheckLLVMType<'ctx> + 'ctx>,
|
||||
llvm_type: BasicTypeEnum<'ctx>,
|
||||
}
|
||||
|
||||
/// A helper struct to create [`Field`]-s in [`StructKind::build_fields`].
|
||||
///
|
||||
/// See [`StructKind`] for more details and see how [`FieldBuilder`] is put
|
||||
/// into action.
|
||||
pub struct FieldBuilder<'ctx> {
|
||||
/// The [`Context`] this [`FieldBuilder`] is under.
|
||||
///
|
||||
/// Can be used in [`StructKind::build_fields`].
|
||||
/// See [`StructKind`] for more details and see how [`FieldBuilder`] is put
|
||||
/// into action.
|
||||
pub ctx: &'ctx Context,
|
||||
|
||||
/// An incrementing counter for GEP indices when
|
||||
/// doing [`FieldBuilder::add_field`] or [`FieldBuilder::add_field_auto`].
|
||||
gep_index_counter: u64,
|
||||
|
||||
/// Name of the `struct` this [`FieldBuilder`] is currently
|
||||
/// building.
|
||||
///
|
||||
/// NOTE: This is only used for debugging.
|
||||
struct_name: &'ctx str,
|
||||
|
||||
/// The fields added so far.
|
||||
fields: Vec<FieldLLVM<'ctx>>,
|
||||
}
|
||||
|
||||
impl<'ctx> FieldBuilder<'ctx> {
|
||||
#[must_use]
|
||||
pub fn new(ctx: &'ctx Context, struct_name: &'ctx str) -> Self {
|
||||
FieldBuilder { ctx, gep_index_counter: 0, struct_name, fields: Vec::new() }
|
||||
}
|
||||
|
||||
fn next_gep_index(&mut self) -> u64 {
|
||||
let index = self.gep_index_counter;
|
||||
self.gep_index_counter += 1;
|
||||
index
|
||||
}
|
||||
|
||||
/// Add a new field.
|
||||
///
|
||||
/// - `name`: The name of the field. See [`Field::name`].
|
||||
/// - `element`: The [`Model`] of the type of the field. See [`Field::element`].
|
||||
pub fn add_field<E: Model<'ctx> + 'ctx>(&mut self, name: &'static str, element: E) -> Field<E> {
|
||||
let gep_index = self.next_gep_index();
|
||||
|
||||
self.fields.push(FieldLLVM {
|
||||
gep_index,
|
||||
name,
|
||||
llvm_type: element.get_llvm_type(self.ctx),
|
||||
llvm_type_model: Box::new(element),
|
||||
});
|
||||
|
||||
Field { gep_index, name, element }
|
||||
}
|
||||
|
||||
/// Like [`FieldBuilder::add_field`] but `element` can be **automatically derived**
|
||||
/// if it has the `Default` instance.
|
||||
///
|
||||
/// Certain [`Model`] has a [`Default`] trait - [`Model`]s that are just singletons,
|
||||
/// By deriving the [`Default`] trait on those [`Model`]s, Rust could automatically
|
||||
/// construct the [`Model`] with [`Default::default`].
|
||||
///
|
||||
/// This function is equivalent to
|
||||
/// ```ignore
|
||||
/// self.add_field(name, E::default())
|
||||
/// ```
|
||||
pub fn add_field_auto<E: Model<'ctx> + Default + 'ctx>(
|
||||
&mut self,
|
||||
name: &'static str,
|
||||
) -> Field<E> {
|
||||
self.add_field(name, E::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// A marker trait to mark singleton struct that
|
||||
/// describes a particular LLVM structure.
|
||||
///
|
||||
/// It is a powerful inkwell abstraction that can reduce
|
||||
/// a lot of inkwell boilerplate when dealing with LLVM structs,
|
||||
/// `getelementptr`, `load`-ing and `store`-ing fields.
|
||||
///
|
||||
/// ### Usage
|
||||
pub trait StructKind<'ctx>: Clone + Copy {
|
||||
/// The type of the Rust `struct` that holds all the fields of this LLVM struct.
|
||||
type Fields;
|
||||
|
||||
// TODO:
|
||||
/// The name of this [`StructKind`].
|
||||
///
|
||||
/// The name should be the name of in
|
||||
/// IRRT's `struct` or ARTIQ's definition.
|
||||
fn struct_name(&self) -> &'static str;
|
||||
|
||||
/// Define the [`Field`]s of this [`StructKind`]
|
||||
///
|
||||
///
|
||||
/// ### Syntax
|
||||
///
|
||||
/// Suppose you want to define the following C++ `struct`s in `nac3core`:
|
||||
/// ```cpp
|
||||
/// template <typename SizeT>
|
||||
/// struct Str {
|
||||
/// uint8_t* content; // NOTE: could be `void *`
|
||||
/// SizeT length;
|
||||
/// }
|
||||
///
|
||||
/// template <typename SizeT>
|
||||
/// struct Exception {
|
||||
/// uint32_t id;
|
||||
/// Str message;
|
||||
/// uint64_t param0;
|
||||
/// uint64_t param1;
|
||||
/// uint64_t param2;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// You write this in nac3core:
|
||||
/// ```ignore
|
||||
/// struct Str<'ctx> {
|
||||
/// sizet: IntModel<'ctx>,
|
||||
/// }
|
||||
///
|
||||
/// struct StrFields<'ctx> {
|
||||
/// content: Field<PointerModel<ByteModel>>, // equivalent to `NIntModel<Byte>`.
|
||||
/// length: Field<IntModel<'ctx>>, // `SizeT` is only known in runtime - `CodeGenerator::get_size_type()`. /// }
|
||||
/// }
|
||||
///
|
||||
/// impl StructKind<'ctx> for Str<'ctx> {
|
||||
/// fn struct_name() {
|
||||
/// "Str"
|
||||
/// }
|
||||
///
|
||||
/// fn build_fields(&self, builder: &mut FieldBuilder<'ctx>) -> Self::Fields {
|
||||
/// // THE order of `builder.add_field*` is IMPORTANT!!!
|
||||
/// // so the GEP indices would be correct.
|
||||
/// StrFields {
|
||||
/// content: builder.add_field_auto("content"), // `PointerModel<ByteModel>` has `Default` trait.
|
||||
/// length: builder.add_field("length", IntModel(self.sizet)), // `PointerModel<ByteModel>` has `Default` trait.
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// struct Exception<'ctx> {
|
||||
/// sizet: IntModel<'ctx>,
|
||||
/// }
|
||||
///
|
||||
/// struct ExceptionFields<'ctx> {
|
||||
/// id: Field<NIntModel<Int32>>,
|
||||
/// message: Field<StructModel<Str>>,
|
||||
/// param0: Field<NIntModel<Int64>>,
|
||||
/// param1: Field<NIntModel<Int64>>,
|
||||
/// param2: Field<NIntModel<Int64>>,
|
||||
/// }
|
||||
///
|
||||
/// impl StructKind<'ctx> for Exception<'ctx> {
|
||||
/// fn struct_name() {
|
||||
/// "Exception"
|
||||
/// }
|
||||
///
|
||||
/// fn build_fields(&self, builder: &mut FieldBuilder<'ctx>) -> Self::Fields {
|
||||
/// // THE order of `builder.add_field*` is IMPORTANT!!!
|
||||
/// // so the GEP indices would be correct.
|
||||
/// ExceptionFields {
|
||||
/// id: builder.add_field_auto("content"), // `NIntModel<Int32>` has `Default` trait.
|
||||
/// message: builder.add_field("message", StructModel(Str { sizet: self.sizet })),
|
||||
/// param0: builder.add_field_auto("param0"), // has `Default` trait
|
||||
/// param1: builder.add_field_auto("param1"), // has `Default` trait
|
||||
/// param2: builder.add_field_auto("param2"), // has `Default` trait
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Then to `alloca` an `Exception`, do this:
|
||||
/// ```ignore
|
||||
/// let generator: dyn CodeGenerator<'ctx>;
|
||||
/// let ctx: &CodeGenContext<'ctx, '_>;
|
||||
/// let sizet = generator.get_size_type();
|
||||
/// let exn_model = StructModel(Exception { sizet });
|
||||
/// let exn = exn_model.alloca(ctx, "my_exception"); // Every [`Model<'ctx>`] has an `.alloca()` function.
|
||||
/// // exn: Pointer<'ctx, StructModel<Exception>>
|
||||
/// ```
|
||||
///
|
||||
/// NOTE: In fact, it is possible to define `Str` and `Exception` like this:
|
||||
/// ```ignore
|
||||
/// struct Str<SizeT: NIntModel> {
|
||||
/// _phantom: PhantomData<SizeT>,
|
||||
/// }
|
||||
///
|
||||
/// struct Exception<T: NIntModel> {
|
||||
/// _phantom: PhantomData<SizeT>,
|
||||
/// }
|
||||
/// ```
|
||||
/// But issues arise by you don't know the nac3core
|
||||
/// `CodeGenerator`'s `get_size_type()` before hand.
|
||||
fn build_fields(&self, builder: &mut FieldBuilder<'ctx>) -> Self::Fields;
|
||||
}
|
||||
|
||||
/// A [`Model<'ctx>`] that represents an LLVM struct.
|
||||
///
|
||||
/// `self.0` contains a [`IsStruct<'ctx>`] that gives the details of the LLVM struct.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct StructModel<S>(pub S);
|
||||
|
||||
impl<'ctx, S: StructKind<'ctx>> CanCheckLLVMType<'ctx> for StructModel<S> {
|
||||
fn check_llvm_type_impl(
|
||||
&self,
|
||||
ctx: &'ctx Context,
|
||||
ty: BasicTypeEnum<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
// Check if scrutinee is even a struct type
|
||||
let BasicTypeEnum::StructType(ty) = ty else {
|
||||
return Err(format!("Expecting a struct type, but got {ty:?}"));
|
||||
};
|
||||
|
||||
// Ok. now check the struct type thoroughly
|
||||
self.check_struct_type(ctx, ty)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx, S: StructKind<'ctx>> Model<'ctx> for StructModel<S> {
|
||||
type Value = Struct<'ctx, S>;
|
||||
|
||||
fn get_llvm_type(&self, ctx: &'ctx Context) -> BasicTypeEnum<'ctx> {
|
||||
self.get_struct_type(ctx).as_basic_type_enum()
|
||||
}
|
||||
|
||||
fn review_value<V: BasicValue<'ctx>>(
|
||||
&self,
|
||||
ctx: &'ctx Context,
|
||||
value: V,
|
||||
) -> Result<Self::Value, String> {
|
||||
let value = value.as_basic_value_enum();
|
||||
|
||||
// Check that `value` is not some bogus values or an incorrect StructValue
|
||||
self.check_llvm_type(ctx, value.get_type())?;
|
||||
|
||||
Ok(Struct { kind: self.0, value: value.into_struct_value() })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx, S: StructKind<'ctx>> StructModel<S> {
|
||||
/// Get the [`S::Fields`] of this [`StructModel`].
|
||||
pub fn get_fields(&self, ctx: &'ctx Context) -> S::Fields {
|
||||
let mut builder = FieldBuilder::new(ctx, self.0.struct_name());
|
||||
self.0.build_fields(&mut builder)
|
||||
}
|
||||
|
||||
/// Get the LLVM struct type this [`IsStruct<'ctx>`] is representing.
|
||||
pub fn get_struct_type(&self, ctx: &'ctx Context) -> StructType<'ctx> {
|
||||
let mut builder = FieldBuilder::new(ctx, self.0.struct_name());
|
||||
self.0.build_fields(&mut builder); // Self::Fields is discarded
|
||||
|
||||
let field_types = builder.fields.iter().map(|f| f.llvm_type).collect_vec();
|
||||
ctx.struct_type(&field_types, false)
|
||||
}
|
||||
|
||||
/// Check if `scrutinee` matches the [`StructType<'ctx>`] this [`IsStruct<'ctx>`] is representing.
|
||||
pub fn check_struct_type(
|
||||
&self,
|
||||
ctx: &'ctx Context,
|
||||
scrutinee: StructType<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
// Details about scrutinee
|
||||
let scrutinee_field_types = scrutinee.get_field_types();
|
||||
|
||||
// Details about the defined specifications of this struct
|
||||
// We will access them through builder
|
||||
let mut builder = FieldBuilder::new(ctx, self.0.struct_name());
|
||||
self.0.build_fields(&mut builder);
|
||||
|
||||
// Check # of fields
|
||||
if builder.fields.len() != scrutinee_field_types.len() {
|
||||
return Err(format!(
|
||||
"Expecting struct to have {} field(s), but scrutinee has {} field(s)",
|
||||
builder.fields.len(),
|
||||
scrutinee_field_types.len()
|
||||
));
|
||||
}
|
||||
|
||||
// Check the types of each field
|
||||
// TODO: Traceback?
|
||||
for (f, scrutinee_field_type) in izip!(builder.fields, scrutinee_field_types) {
|
||||
f.llvm_type_model
|
||||
.check_llvm_type_impl(ctx, scrutinee_field_type.as_basic_type_enum())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A value of [`StructModel<S>`] of a particular [`StructKind`].
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Struct<'ctx, S> {
|
||||
pub kind: S,
|
||||
pub value: StructValue<'ctx>,
|
||||
}
|
||||
|
||||
impl<'ctx, S: StructKind<'ctx>> ModelValue<'ctx> for Struct<'ctx, S> {
|
||||
fn get_llvm_value(&self) -> BasicValueEnum<'ctx> {
|
||||
self.value.as_basic_value_enum()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx, S: StructKind<'ctx>> Pointer<'ctx, StructModel<S>> {
|
||||
/// Build an instruction that does `getelementptr` on an LLVM structure referenced by this pointer.
|
||||
///
|
||||
/// This provides a nice syntax to chain up `getelementptr` in an intuitive and type-safe way:
|
||||
///
|
||||
/// ```ignore
|
||||
/// let ctx: &CodeGenContext<'ctx, '_>;
|
||||
/// let ndarray: Pointer<'ctx, StructModel<NpArray<'ctx>>>;
|
||||
/// ndarray.gep(ctx, |f| f.ndims).store();
|
||||
/// ```
|
||||
///
|
||||
/// You might even write chains `gep`, i.e.,
|
||||
/// ```ignore
|
||||
/// let exn_ptr: Pointer<'ctx, StructModel<Exception>>;
|
||||
/// let value: Int<'ctx>; // Suppose it has the correct inkwell `IntType<'ctx>`.
|
||||
///
|
||||
/// // To do `exn.message.length = value`:
|
||||
/// let exn_message_ptr = exn_ptr.gep(ctx, |f| f.message);
|
||||
/// let exn_message_length_ptr = exn_message_ptr.gep(ctx, |f| f.length);
|
||||
/// exn_message_length_ptr.store(ctx, my_value);
|
||||
///
|
||||
/// // or simply:
|
||||
/// exn_ptr
|
||||
/// .gep(ctx, |f| f.message)
|
||||
/// .gep(ctx, |f| f.length)
|
||||
/// .store(ctx, my_value) // Equivalent to `my_struct.thing1.value = my_value`
|
||||
/// ```
|
||||
pub fn gep<E, GetFieldFn>(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
get_field: GetFieldFn,
|
||||
) -> Pointer<'ctx, E>
|
||||
where
|
||||
E: Model<'ctx>,
|
||||
GetFieldFn: FnOnce(S::Fields) -> Field<E>,
|
||||
{
|
||||
let fields = self.element.get_fields(ctx.ctx);
|
||||
let field = get_field(fields);
|
||||
|
||||
// TODO: I think I'm not supposed to *just* use i32 for GEP like that
|
||||
let llvm_i32 = ctx.ctx.i32_type();
|
||||
|
||||
let ptr = unsafe {
|
||||
ctx.builder
|
||||
.build_in_bounds_gep(
|
||||
self.value,
|
||||
&[llvm_i32.const_zero(), llvm_i32.const_int(field.gep_index, false)],
|
||||
field.name,
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
Pointer { element: field.element, value: ptr }
|
||||
}
|
||||
}
|
|
@ -23,3 +23,4 @@ pub mod codegen;
|
|||
pub mod symbol_resolver;
|
||||
pub mod toplevel;
|
||||
pub mod typecheck;
|
||||
pub(crate) mod util;
|
||||
|
|
|
@ -23,6 +23,7 @@ use crate::{
|
|||
symbol_resolver::SymbolValue,
|
||||
toplevel::{helper::PrimDef, numpy::make_ndarray_ty},
|
||||
typecheck::typedef::{into_var_map, iter_type_vars, TypeVar, VarMap},
|
||||
util::SizeVariant,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
@ -278,19 +279,10 @@ pub fn get_builtins(unifier: &mut Unifier, primitives: &PrimitiveStore) -> Built
|
|||
.collect()
|
||||
}
|
||||
|
||||
/// A helper enum used by [`BuiltinBuilder`]
|
||||
#[derive(Clone, Copy)]
|
||||
enum SizeVariant {
|
||||
Bits32,
|
||||
Bits64,
|
||||
}
|
||||
|
||||
impl SizeVariant {
|
||||
fn of_int(self, primitives: &PrimitiveStore) -> Type {
|
||||
match self {
|
||||
SizeVariant::Bits32 => primitives.int32,
|
||||
SizeVariant::Bits64 => primitives.int64,
|
||||
}
|
||||
fn get_size_variant_of_int(size_variant: SizeVariant, primitives: &PrimitiveStore) -> Type {
|
||||
match size_variant {
|
||||
SizeVariant::Bits32 => primitives.int32,
|
||||
SizeVariant::Bits64 => primitives.int64,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1061,7 +1053,7 @@ impl<'a> BuiltinBuilder<'a> {
|
|||
);
|
||||
|
||||
// The size variant of the function determines the size of the returned int.
|
||||
let int_sized = size_variant.of_int(self.primitives);
|
||||
let int_sized = get_size_variant_of_int(size_variant, self.primitives);
|
||||
|
||||
let ndarray_int_sized =
|
||||
make_ndarray_ty(self.unifier, self.primitives, Some(int_sized), Some(common_ndim.ty));
|
||||
|
@ -1086,7 +1078,7 @@ impl<'a> BuiltinBuilder<'a> {
|
|||
let arg_ty = fun.0.args[0].ty;
|
||||
let arg = args[0].1.clone().to_basic_value_enum(ctx, generator, arg_ty)?;
|
||||
|
||||
let ret_elem_ty = size_variant.of_int(&ctx.primitives);
|
||||
let ret_elem_ty = get_size_variant_of_int(size_variant, &ctx.primitives);
|
||||
Ok(Some(builtin_fns::call_round(generator, ctx, (arg_ty, arg), ret_elem_ty)?))
|
||||
}),
|
||||
)
|
||||
|
@ -1127,7 +1119,7 @@ impl<'a> BuiltinBuilder<'a> {
|
|||
make_ndarray_ty(self.unifier, self.primitives, Some(float), Some(common_ndim.ty));
|
||||
|
||||
// The size variant of the function determines the type of int returned
|
||||
let int_sized = size_variant.of_int(self.primitives);
|
||||
let int_sized = get_size_variant_of_int(size_variant, self.primitives);
|
||||
let ndarray_int_sized =
|
||||
make_ndarray_ty(self.unifier, self.primitives, Some(int_sized), Some(common_ndim.ty));
|
||||
|
||||
|
@ -1150,7 +1142,7 @@ impl<'a> BuiltinBuilder<'a> {
|
|||
let arg_ty = fun.0.args[0].ty;
|
||||
let arg = args[0].1.clone().to_basic_value_enum(ctx, generator, arg_ty)?;
|
||||
|
||||
let ret_elem_ty = size_variant.of_int(&ctx.primitives);
|
||||
let ret_elem_ty = get_size_variant_of_int(size_variant, &ctx.primitives);
|
||||
let func = match kind {
|
||||
Kind::Ceil => builtin_fns::call_ceil,
|
||||
Kind::Floor => builtin_fns::call_floor,
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
/// A helper enum used by [`BuiltinBuilder`]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SizeVariant {
|
||||
Bits32,
|
||||
Bits64,
|
||||
}
|
Loading…
Reference in New Issue