Compare commits

..

4 Commits

90 changed files with 5348 additions and 5648 deletions

103
Cargo.lock generated
View File

@ -126,9 +126,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.7" version = "1.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7" checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf"
dependencies = [ dependencies = [
"shlex", "shlex",
] ]
@ -170,7 +170,7 @@ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.90",
] ]
[[package]] [[package]]
@ -187,14 +187,14 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]] [[package]]
name = "console" name = "console"
version = "0.15.10" version = "0.15.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
dependencies = [ dependencies = [
"encode_unicode", "encode_unicode",
"lazy_static",
"libc", "libc",
"once_cell", "windows-sys 0.52.0",
"windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -221,18 +221,18 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-channel" name = "crossbeam-channel"
version = "0.5.14" version = "0.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2"
dependencies = [ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]] [[package]]
name = "crossbeam-deque" name = "crossbeam-deque"
version = "0.8.6" version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
dependencies = [ dependencies = [
"crossbeam-epoch", "crossbeam-epoch",
"crossbeam-utils", "crossbeam-utils",
@ -249,18 +249,18 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-queue" name = "crossbeam-queue"
version = "0.3.12" version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35"
dependencies = [ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.21" version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
@ -305,9 +305,9 @@ dependencies = [
[[package]] [[package]]
name = "encode_unicode" name = "encode_unicode"
version = "1.0.0" version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]] [[package]]
name = "equivalent" name = "equivalent"
@ -378,9 +378,9 @@ dependencies = [
[[package]] [[package]]
name = "glob" name = "glob"
version = "0.3.2" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
@ -417,11 +417,11 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]] [[package]]
name = "home" name = "home"
version = "0.5.11" version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.52.0",
] ]
[[package]] [[package]]
@ -472,7 +472,7 @@ checksum = "9dd28cfd4cfba665d47d31c08a6ba637eed16770abca2eccbbc3ca831fef1e44"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.90",
] ]
[[package]] [[package]]
@ -559,9 +559,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.169" version = "0.2.168"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
[[package]] [[package]]
name = "libloading" name = "libloading"
@ -678,7 +678,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.90",
"trybuild", "trybuild",
] ]
@ -799,7 +799,7 @@ dependencies = [
"phf_shared 0.11.2", "phf_shared 0.11.2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.90",
] ]
[[package]] [[package]]
@ -927,7 +927,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"pyo3-macros-backend", "pyo3-macros-backend",
"quote", "quote",
"syn 2.0.94", "syn 2.0.90",
] ]
[[package]] [[package]]
@ -940,14 +940,14 @@ dependencies = [
"proc-macro2", "proc-macro2",
"pyo3-build-config", "pyo3-build-config",
"quote", "quote",
"syn 2.0.94", "syn 2.0.90",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.38" version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -1062,9 +1062,9 @@ dependencies = [
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.19" version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
[[package]] [[package]]
name = "ryu" name = "ryu"
@ -1095,29 +1095,29 @@ checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.217" version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.217" version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.90",
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.134" version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@ -1226,7 +1226,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustversion", "rustversion",
"syn 2.0.94", "syn 2.0.90",
] ]
[[package]] [[package]]
@ -1242,9 +1242,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.94" version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3" checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1265,13 +1265,12 @@ checksum = "42a4d50cdb458045afc8131fd91b64904da29548bcb63c7236e0844936c13078"
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.15.0" version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",
"getrandom",
"once_cell", "once_cell",
"rustix", "rustix",
"windows-sys 0.59.0", "windows-sys 0.59.0",
@ -1279,9 +1278,9 @@ dependencies = [
[[package]] [[package]]
name = "term" name = "term"
version = "1.0.1" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3bb6001afcea98122260987f8b7b5da969ecad46dbf0b5453702f776b491a41" checksum = "4df4175de05129f31b80458c6df371a15e7fc3fd367272e6bf938e5c351c7ea0"
dependencies = [ dependencies = [
"home", "home",
"windows-sys 0.52.0", "windows-sys 0.52.0",
@ -1326,7 +1325,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.90",
] ]
[[package]] [[package]]
@ -1604,9 +1603,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.6.22" version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980" checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -1638,5 +1637,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.90",
] ]

6
flake.lock generated
View File

@ -2,11 +2,11 @@
"nodes": { "nodes": {
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1735834308, "lastModified": 1733940404,
"narHash": "sha256-dklw3AXr3OGO4/XT1Tu3Xz9n/we8GctZZ75ZWVqAVhk=", "narHash": "sha256-Pj39hSoUA86ZePPF/UXiYHHM7hMIkios8TYG29kQT4g=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "6df24922a1400241dae323af55f30e4318a6ca65", "rev": "5d67ea6b4b63378b9c13be21e2ec9d1afc921713",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -0,0 +1,37 @@
from min_artiq import *
from numpy import int32
@nac3
class NameManglingTest:
core: KernelInvariant[Core]
__var1: int32
__var2__: int32
__: int32
def __init__(self):
self.core = Core()
self.__var1 = 42
self.__var2__ = 10
self.__ = 99
@rpc
def get_var1(self) -> int32:
return self.__var1
@rpc
def get_var2(self) -> int32:
return self.__var2__
@rpc
def get_var3(self) -> int32:
return self.__
@kernel
def run(self):
assert self.get_var1() == 42
assert self.get_var2() == 10
assert self.get_var3() == 99
if __name__ == "__main__":
NameManglingTest().run()

View File

@ -1,29 +0,0 @@
from min_artiq import *
import numpy
from numpy import int32
@nac3
class NumpyBoolDecay:
core: KernelInvariant[Core]
np_true: KernelInvariant[bool]
np_false: KernelInvariant[bool]
np_int: KernelInvariant[int32]
np_float: KernelInvariant[float]
np_str: KernelInvariant[str]
def __init__(self):
self.core = Core()
self.np_true = numpy.True_
self.np_false = numpy.False_
self.np_int = numpy.int32(0)
self.np_float = numpy.float64(0.0)
self.np_str = numpy.str_("")
@kernel
def run(self):
pass
if __name__ == "__main__":
NumpyBoolDecay().run()

View File

@ -162,7 +162,7 @@ impl<'a> ArtiqCodeGenerator<'a> {
} }
} }
impl CodeGenerator for ArtiqCodeGenerator<'_> { impl<'b> CodeGenerator for ArtiqCodeGenerator<'b> {
fn get_name(&self) -> &str { fn get_name(&self) -> &str {
&self.name &self.name
} }
@ -464,7 +464,7 @@ fn format_rpc_arg<'ctx>(
let (elem_ty, ndims) = unpack_ndarray_var_tys(&mut ctx.unifier, arg_ty); let (elem_ty, ndims) = unpack_ndarray_var_tys(&mut ctx.unifier, arg_ty);
let ndims = extract_ndims(&ctx.unifier, ndims); let ndims = extract_ndims(&ctx.unifier, ndims);
let dtype = ctx.get_llvm_type(generator, elem_ty); let dtype = ctx.get_llvm_type(generator, elem_ty);
let ndarray = NDArrayType::new(generator, ctx.ctx, dtype, ndims) let ndarray = NDArrayType::new(generator, ctx.ctx, dtype, Some(ndims))
.map_value(arg.into_pointer_value(), None); .map_value(arg.into_pointer_value(), None);
let ndims = llvm_usize.const_int(ndims, false); let ndims = llvm_usize.const_int(ndims, false);
@ -597,7 +597,7 @@ fn format_rpc_ret<'ctx>(
let (dtype, ndims) = unpack_ndarray_var_tys(&mut ctx.unifier, ret_ty); let (dtype, ndims) = unpack_ndarray_var_tys(&mut ctx.unifier, ret_ty);
let dtype_llvm = ctx.get_llvm_type(generator, dtype); let dtype_llvm = ctx.get_llvm_type(generator, dtype);
let ndims = extract_ndims(&ctx.unifier, ndims); let ndims = extract_ndims(&ctx.unifier, ndims);
let ndarray = NDArrayType::new(generator, ctx.ctx, dtype_llvm, ndims) let ndarray = NDArrayType::new(generator, ctx.ctx, dtype_llvm, Some(ndims))
.construct_uninitialized(generator, ctx, None); .construct_uninitialized(generator, ctx, None);
// NOTE: Current content of `ndarray`: // NOTE: Current content of `ndarray`:
@ -1505,7 +1505,7 @@ pub fn call_rtio_log_impl<'ctx>(
/// Generates a call to `core_log`. /// Generates a call to `core_log`.
pub fn gen_core_log<'ctx>( pub fn gen_core_log<'ctx>(
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
obj: Option<&(Type, ValueEnum<'ctx>)>, obj: &Option<(Type, ValueEnum<'ctx>)>,
fun: (&FunSignature, DefinitionId), fun: (&FunSignature, DefinitionId),
args: &[(Option<StrRef>, ValueEnum<'ctx>)], args: &[(Option<StrRef>, ValueEnum<'ctx>)],
generator: &mut dyn CodeGenerator, generator: &mut dyn CodeGenerator,
@ -1522,7 +1522,7 @@ pub fn gen_core_log<'ctx>(
/// Generates a call to `rtio_log`. /// Generates a call to `rtio_log`.
pub fn gen_rtio_log<'ctx>( pub fn gen_rtio_log<'ctx>(
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
obj: Option<&(Type, ValueEnum<'ctx>)>, obj: &Option<(Type, ValueEnum<'ctx>)>,
fun: (&FunSignature, DefinitionId), fun: (&FunSignature, DefinitionId),
args: &[(Option<StrRef>, ValueEnum<'ctx>)], args: &[(Option<StrRef>, ValueEnum<'ctx>)],
generator: &mut dyn CodeGenerator, generator: &mut dyn CodeGenerator,

View File

@ -330,7 +330,7 @@ impl Nac3 {
vars: into_var_map([arg_ty]), vars: into_var_map([arg_ty]),
}, },
Arc::new(GenCall::new(Box::new(move |ctx, obj, fun, args, generator| { Arc::new(GenCall::new(Box::new(move |ctx, obj, fun, args, generator| {
gen_core_log(ctx, obj.as_ref(), fun, &args, generator)?; gen_core_log(ctx, &obj, fun, &args, generator)?;
Ok(None) Ok(None)
}))), }))),
@ -360,7 +360,7 @@ impl Nac3 {
vars: into_var_map([arg_ty]), vars: into_var_map([arg_ty]),
}, },
Arc::new(GenCall::new(Box::new(move |ctx, obj, fun, args, generator| { Arc::new(GenCall::new(Box::new(move |ctx, obj, fun, args, generator| {
gen_rtio_log(ctx, obj.as_ref(), fun, &args, generator)?; gen_rtio_log(ctx, &obj, fun, &args, generator)?;
Ok(None) Ok(None)
}))), }))),

View File

@ -931,13 +931,10 @@ impl InnerResolver {
|_| Ok(Ok(extracted_ty)), |_| Ok(Ok(extracted_ty)),
) )
} else if unifier.unioned(extracted_ty, primitives.bool) { } else if unifier.unioned(extracted_ty, primitives.bool) {
if obj.extract::<bool>().is_ok() obj.extract::<bool>().map_or_else(
|| obj.call_method("__bool__", (), None)?.extract::<bool>().is_ok() |_| Ok(Err(format!("{obj} is not in the range of bool"))),
{ |_| Ok(Ok(extracted_ty)),
Ok(Ok(extracted_ty)) )
} else {
Ok(Err(format!("{obj} is not in the range of bool")))
}
} else if unifier.unioned(extracted_ty, primitives.float) { } else if unifier.unioned(extracted_ty, primitives.float) {
obj.extract::<f64>().map_or_else( obj.extract::<f64>().map_or_else(
|_| Ok(Err(format!("{obj} is not in the range of float64"))), |_| Ok(Err(format!("{obj} is not in the range of float64"))),
@ -950,6 +947,28 @@ impl InnerResolver {
} }
} }
pub fn get_class_name(&self, field_id: StrRef, defs: &[Arc<RwLock<TopLevelDef>>]) -> Option<String> {
let field_to_val = self.field_to_val.read();
let class_id = field_to_val.iter().find_map(|(&(id, ref name), _)| {
if *name == field_id {
Some(id)
} else {
None
}
})?;
let id_to_def = self.id_to_def.read();
let def_id = id_to_def.get(&StrRef::from(class_id.to_string()))?;
let definition = defs[def_id.0].read();
if let TopLevelDef::Class { name, .. } = &*definition {
Some(name.to_string()) // return class name
} else {
None // then not a class
}
}
pub fn get_obj_value<'ctx>( pub fn get_obj_value<'ctx>(
&self, &self,
py: Python, py: Python,
@ -977,14 +996,10 @@ impl InnerResolver {
let val: u64 = obj.extract().unwrap(); let val: u64 = obj.extract().unwrap();
self.id_to_primitive.write().insert(id, PrimitiveValue::U64(val)); self.id_to_primitive.write().insert(id, PrimitiveValue::U64(val));
Ok(Some(ctx.ctx.i64_type().const_int(val, false).into())) Ok(Some(ctx.ctx.i64_type().const_int(val, false).into()))
} else if ty_id == self.primitive_ids.bool { } else if ty_id == self.primitive_ids.bool || ty_id == self.primitive_ids.np_bool_ {
let val: bool = obj.extract().unwrap(); let val: bool = obj.extract().unwrap();
self.id_to_primitive.write().insert(id, PrimitiveValue::Bool(val)); self.id_to_primitive.write().insert(id, PrimitiveValue::Bool(val));
Ok(Some(ctx.ctx.i8_type().const_int(u64::from(val), false).into())) Ok(Some(ctx.ctx.i8_type().const_int(u64::from(val), false).into()))
} else if ty_id == self.primitive_ids.np_bool_ {
let val: bool = obj.call_method("__bool__", (), None)?.extract().unwrap();
self.id_to_primitive.write().insert(id, PrimitiveValue::Bool(val));
Ok(Some(ctx.ctx.i8_type().const_int(u64::from(val), false).into()))
} else if ty_id == self.primitive_ids.string || ty_id == self.primitive_ids.np_str_ { } else if ty_id == self.primitive_ids.string || ty_id == self.primitive_ids.np_str_ {
let val: String = obj.extract().unwrap(); let val: String = obj.extract().unwrap();
self.id_to_primitive.write().insert(id, PrimitiveValue::Str(val.clone())); self.id_to_primitive.write().insert(id, PrimitiveValue::Str(val.clone()));
@ -1114,7 +1129,7 @@ impl InnerResolver {
self.global_value_ids.write().insert(id, obj.into()); self.global_value_ids.write().insert(id, obj.into());
} }
let ndims = llvm_ndarray.ndims(); let ndims = llvm_ndarray.ndims().unwrap();
// Obtain the shape of the ndarray // Obtain the shape of the ndarray
let shape_tuple: &PyTuple = obj.getattr("shape")?.downcast()?; let shape_tuple: &PyTuple = obj.getattr("shape")?.downcast()?;
@ -1131,10 +1146,7 @@ impl InnerResolver {
super::CompileError::new_err(format!("Error getting element {i}: {e}")) super::CompileError::new_err(format!("Error getting element {i}: {e}"))
})? })?
.unwrap(); .unwrap();
let value = ctx let value = value.into_int_value();
.builder
.build_int_z_extend(value.into_int_value(), llvm_usize, "")
.unwrap();
Ok(value) Ok(value)
}) })
.collect::<Result<Vec<_>, PyErr>>()?; .collect::<Result<Vec<_>, PyErr>>()?;
@ -1213,16 +1225,8 @@ impl InnerResolver {
data_global.set_initializer(&data); data_global.set_initializer(&data);
// Get the constant itemsize. // Get the constant itemsize.
// let itemsize = dtype.size_of().unwrap();
// NOTE: dtype.size_of() may return a non-constant, where `TargetData::get_store_size` let itemsize = itemsize.get_zero_extended_constant().unwrap();
// will always return a constant size.
let itemsize = ctx
.registry
.llvm_options
.create_target_machine()
.map(|tm| tm.get_target_data().get_store_size(&dtype))
.unwrap();
assert_ne!(itemsize, 0);
// Create the strides needed for ndarray.strides // Create the strides needed for ndarray.strides
let strides = make_contiguous_strides(itemsize, ndims, &shape_u64s); let strides = make_contiguous_strides(itemsize, ndims, &shape_u64s);
@ -1232,7 +1236,7 @@ impl InnerResolver {
// create a global for ndarray.strides and initialize it // create a global for ndarray.strides and initialize it
let strides_global = ctx.module.add_global( let strides_global = ctx.module.add_global(
llvm_usize.array_type(ndims as u32), llvm_i8.array_type(ndims as u32),
Some(AddressSpace::default()), Some(AddressSpace::default()),
&format!("${id_str}.strides"), &format!("${id_str}.strides"),
); );
@ -1248,30 +1252,9 @@ impl InnerResolver {
let ndarray_ndims = llvm_usize.const_int(ndims, false); let ndarray_ndims = llvm_usize.const_int(ndims, false);
// calling as_pointer_value on shape and strides returns [i64 x ndims]*
// convert into i64* to conform with expected layout of ndarray
let ndarray_shape = shape_global.as_pointer_value(); let ndarray_shape = shape_global.as_pointer_value();
let ndarray_shape = unsafe {
ctx.builder
.build_in_bounds_gep(
ndarray_shape,
&[llvm_usize.const_zero(), llvm_usize.const_zero()],
"",
)
.unwrap()
};
let ndarray_strides = strides_global.as_pointer_value(); let ndarray_strides = strides_global.as_pointer_value();
let ndarray_strides = unsafe {
ctx.builder
.build_in_bounds_gep(
ndarray_strides,
&[llvm_usize.const_zero(), llvm_usize.const_zero()],
"",
)
.unwrap()
};
let ndarray = llvm_ndarray let ndarray = llvm_ndarray
.as_base_type() .as_base_type()
@ -1452,12 +1435,9 @@ impl InnerResolver {
} else if ty_id == self.primitive_ids.uint64 { } else if ty_id == self.primitive_ids.uint64 {
let val: u64 = obj.extract()?; let val: u64 = obj.extract()?;
Ok(SymbolValue::U64(val)) Ok(SymbolValue::U64(val))
} else if ty_id == self.primitive_ids.bool { } else if ty_id == self.primitive_ids.bool || ty_id == self.primitive_ids.np_bool_ {
let val: bool = obj.extract()?; let val: bool = obj.extract()?;
Ok(SymbolValue::Bool(val)) Ok(SymbolValue::Bool(val))
} else if ty_id == self.primitive_ids.np_bool_ {
let val: bool = obj.call_method("__bool__", (), None)?.extract()?;
Ok(SymbolValue::Bool(val))
} else if ty_id == self.primitive_ids.string || ty_id == self.primitive_ids.np_str_ { } else if ty_id == self.primitive_ids.string || ty_id == self.primitive_ids.np_str_ {
let val: String = obj.extract()?; let val: String = obj.extract()?;
Ok(SymbolValue::Str(val)) Ok(SymbolValue::Str(val))
@ -1555,12 +1535,25 @@ impl SymbolResolver for Resolver {
fn get_symbol_value<'ctx>( fn get_symbol_value<'ctx>(
&self, &self,
id: StrRef, id: StrRef,
_: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
_: &mut dyn CodeGenerator, _: &mut dyn CodeGenerator,
) -> Option<ValueEnum<'ctx>> { ) -> Option<ValueEnum<'ctx>> {
let (resolved_id, _is_dunder) = if id.to_string().starts_with("__") && !id.to_string().ends_with("__") && !id.to_string().contains('.') {
let inner_resolver = &self.0;
if let Some(class_name) = inner_resolver.get_class_name(id, &ctx.top_level.definitions.read()) {
let stripped_class_name = class_name.trim_start_matches('_');
let mangled_id: StrRef = format!("_{}{}", stripped_class_name, id).into();
(mangled_id, true)
} else {
(id, false)
}
} else {
(id, false)
};
let sym_value = { let sym_value = {
let id_to_val = self.0.id_to_pyval.read(); let id_to_val = self.0.id_to_pyval.read();
id_to_val.get(&id).cloned() id_to_val.get(&resolved_id).cloned()
} }
.or_else(|| { .or_else(|| {
Python::with_gil(|py| -> PyResult<Option<(u64, PyObject)>> { Python::with_gil(|py| -> PyResult<Option<(u64, PyObject)>> {
@ -1569,14 +1562,14 @@ impl SymbolResolver for Resolver {
let members: &PyDict = obj.getattr("__dict__").unwrap().downcast().unwrap(); let members: &PyDict = obj.getattr("__dict__").unwrap().downcast().unwrap();
for (key, val) in members { for (key, val) in members {
let key: &str = key.extract()?; let key: &str = key.extract()?;
if key == id.to_string() { if key == resolved_id.to_string() {
let id = self.0.helper.id_fn.call1(py, (val,))?.extract(py)?; let pyid = self.0.helper.id_fn.call1(py, (val,))?.extract(py)?;
sym_value = Some((id, val.extract()?)); sym_value = Some((pyid, val.extract()?));
break; break;
} }
} }
if let Some((pyid, val)) = &sym_value { if let Some((pyid, val)) = &sym_value {
self.0.id_to_pyval.write().insert(id, (*pyid, val.clone())); self.0.id_to_pyval.write().insert(resolved_id, (*pyid, val.clone()));
} }
Ok(sym_value) Ok(sym_value)
}) })

View File

@ -1,15 +1,10 @@
#include "irrt/exception.hpp" #include "irrt/exception.hpp"
#include "irrt/list.hpp" #include "irrt/list.hpp"
#include "irrt/math.hpp" #include "irrt/math.hpp"
#include "irrt/ndarray.hpp"
#include "irrt/range.hpp" #include "irrt/range.hpp"
#include "irrt/slice.hpp" #include "irrt/slice.hpp"
#include "irrt/string.hpp"
#include "irrt/ndarray/basic.hpp" #include "irrt/ndarray/basic.hpp"
#include "irrt/ndarray/def.hpp" #include "irrt/ndarray/def.hpp"
#include "irrt/ndarray/iter.hpp" #include "irrt/ndarray/iter.hpp"
#include "irrt/ndarray/indexing.hpp" #include "irrt/ndarray/indexing.hpp"
#include "irrt/ndarray/array.hpp"
#include "irrt/ndarray/reshape.hpp"
#include "irrt/ndarray/broadcast.hpp"
#include "irrt/ndarray/transpose.hpp"
#include "irrt/ndarray/matmul.hpp"

View File

@ -21,5 +21,7 @@ using uint64_t = unsigned _ExtInt(64);
#endif #endif
// NDArray indices are always `uint32_t`.
using NDIndexInt = 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; using SliceIndex = int32_t;

View File

@ -2,21 +2,6 @@
#include "irrt/int_types.hpp" #include "irrt/int_types.hpp"
#include "irrt/math_util.hpp" #include "irrt/math_util.hpp"
#include "irrt/slice.hpp"
namespace {
/**
* @brief A list in NAC3.
*
* The `items` field is opaque. You must rely on external contexts to
* know how to interpret it.
*/
template<typename SizeT>
struct List {
uint8_t* items;
SizeT len;
};
} // namespace
extern "C" { extern "C" {
// Handle list assignment and dropping part of the list when // Handle list assignment and dropping part of the list when

View File

@ -1,7 +1,5 @@
#pragma once #pragma once
#include "irrt/int_types.hpp"
namespace { namespace {
// 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 // need to make sure `exp >= 0` before calling this function

View File

@ -0,0 +1,151 @@
#pragma once
#include "irrt/int_types.hpp"
// TODO: To be deleted since NDArray with strides is done.
namespace {
template<typename SizeT>
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;
for (SizeT i = begin_idx; i < end_idx; ++i) {
SizeT val = list_data[i];
__builtin_assume(val > 0);
num_elems *= val;
}
return num_elems;
}
template<typename SizeT>
void __nac3_ndarray_calc_nd_indices_impl(SizeT index, const SizeT* dims, SizeT num_dims, NDIndexInt* idxs) {
SizeT stride = 1;
for (SizeT dim = 0; dim < num_dims; dim++) {
SizeT i = num_dims - dim - 1;
__builtin_assume(dims[i] > 0);
idxs[i] = (index / stride) % dims[i];
stride *= dims[i];
}
}
template<typename SizeT>
SizeT __nac3_ndarray_flatten_index_impl(const SizeT* dims,
SizeT num_dims,
const NDIndexInt* indices,
SizeT num_indices) {
SizeT idx = 0;
SizeT stride = 1;
for (SizeT i = 0; i < num_dims; ++i) {
SizeT ri = num_dims - i - 1;
if (ri < num_indices) {
idx += stride * indices[ri];
}
__builtin_assume(dims[i] > 0);
stride *= dims[ri];
}
return idx;
}
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) {
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) {
*out_dim = *rhs_dim_sz;
} else if (rhs_dim_sz == nullptr) {
*out_dim = *lhs_dim_sz;
} else if (*lhs_dim_sz == 1) {
*out_dim = *rhs_dim_sz;
} else if (*rhs_dim_sz == 1) {
*out_dim = *lhs_dim_sz;
} else if (*lhs_dim_sz == *rhs_dim_sz) {
*out_dim = *lhs_dim_sz;
} else {
__builtin_unreachable();
}
}
}
template<typename SizeT>
void __nac3_ndarray_calc_broadcast_idx_impl(const SizeT* src_dims,
SizeT src_ndims,
const NDIndexInt* in_idx,
NDIndexInt* out_idx) {
for (SizeT i = 0; i < src_ndims; ++i) {
SizeT src_i = src_ndims - i - 1;
out_idx[src_i] = src_dims[src_i] == 1 ? 0 : in_idx[src_i];
}
}
} // namespace
extern "C" {
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) {
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, NDIndexInt* 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, NDIndexInt* 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 NDIndexInt* 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 NDIndexInt* 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) {
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) {
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 NDIndexInt* in_idx,
NDIndexInt* 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 NDIndexInt* in_idx,
NDIndexInt* out_idx) {
__nac3_ndarray_calc_broadcast_idx_impl(src_dims, src_ndims, in_idx, out_idx);
}
} // namespace

View File

@ -1,132 +0,0 @@
#pragma once
#include "irrt/debug.hpp"
#include "irrt/exception.hpp"
#include "irrt/int_types.hpp"
#include "irrt/list.hpp"
#include "irrt/ndarray/basic.hpp"
#include "irrt/ndarray/def.hpp"
namespace {
namespace ndarray::array {
/**
* @brief In the context of `np.array(<list>)`, deduce the ndarray's shape produced by `<list>` and raise
* an exception if there is anything wrong with `<shape>` (e.g., inconsistent dimensions `np.array([[1.0, 2.0],
* [3.0]])`)
*
* If this function finds no issues with `<list>`, the deduced shape is written to `shape`. The caller has the
* responsibility to allocate `[SizeT; ndims]` for `shape`. The caller must also initialize `shape` with `-1`s because
* of implementation details.
*/
template<typename SizeT>
void set_and_validate_list_shape_helper(SizeT axis, List<SizeT>* list, SizeT ndims, SizeT* shape) {
if (shape[axis] == -1) {
// Dimension is unspecified. Set it.
shape[axis] = list->len;
} else {
// Dimension is specified. Check.
if (shape[axis] != list->len) {
// Mismatch, throw an error.
// NOTE: NumPy's error message is more complex and needs more PARAMS to display.
raise_exception(SizeT, EXN_VALUE_ERROR,
"The requested array has an inhomogenous shape "
"after {0} dimension(s).",
axis, shape[axis], list->len);
}
}
if (axis + 1 == ndims) {
// `list` has type `list[ItemType]`
// Do nothing
} else {
// `list` has type `list[list[...]]`
List<SizeT>** lists = (List<SizeT>**)(list->items);
for (SizeT i = 0; i < list->len; i++) {
set_and_validate_list_shape_helper<SizeT>(axis + 1, lists[i], ndims, shape);
}
}
}
/**
* @brief See `set_and_validate_list_shape_helper`.
*/
template<typename SizeT>
void set_and_validate_list_shape(List<SizeT>* list, SizeT ndims, SizeT* shape) {
for (SizeT axis = 0; axis < ndims; axis++) {
shape[axis] = -1; // Sentinel to say this dimension is unspecified.
}
set_and_validate_list_shape_helper<SizeT>(0, list, ndims, shape);
}
/**
* @brief In the context of `np.array(<list>)`, copied the contents stored in `list` to `ndarray`.
*
* `list` is assumed to be "legal". (i.e., no inconsistent dimensions)
*
* # Notes on `ndarray`
* The caller is responsible for allocating space for `ndarray`.
* Here is what this function expects from `ndarray` when called:
* - `ndarray->data` has to be allocated, contiguous, and may contain uninitialized values.
* - `ndarray->itemsize` has to be initialized.
* - `ndarray->ndims` has to be initialized.
* - `ndarray->shape` has to be initialized.
* - `ndarray->strides` is ignored, but note that `ndarray->data` is contiguous.
* When this function call ends:
* - `ndarray->data` is written with contents from `<list>`.
*/
template<typename SizeT>
void write_list_to_array_helper(SizeT axis, SizeT* index, List<SizeT>* list, NDArray<SizeT>* ndarray) {
debug_assert_eq(SizeT, list->len, ndarray->shape[axis]);
if (IRRT_DEBUG_ASSERT_BOOL) {
if (!ndarray::basic::is_c_contiguous(ndarray)) {
raise_debug_assert(SizeT, "ndarray is not C-contiguous", ndarray->strides[0], ndarray->strides[1],
NO_PARAM);
}
}
if (axis + 1 == ndarray->ndims) {
// `list` has type `list[scalar]`
// `ndarray` is contiguous, so we can do this, and this is fast.
uint8_t* dst = static_cast<uint8_t*>(ndarray->data) + (ndarray->itemsize * (*index));
__builtin_memcpy(dst, list->items, ndarray->itemsize * list->len);
*index += list->len;
} else {
// `list` has type `list[list[...]]`
List<SizeT>** lists = (List<SizeT>**)(list->items);
for (SizeT i = 0; i < list->len; i++) {
write_list_to_array_helper<SizeT>(axis + 1, index, lists[i], ndarray);
}
}
}
/**
* @brief See `write_list_to_array_helper`.
*/
template<typename SizeT>
void write_list_to_array(List<SizeT>* list, NDArray<SizeT>* ndarray) {
SizeT index = 0;
write_list_to_array_helper<SizeT>((SizeT)0, &index, list, ndarray);
}
} // namespace ndarray::array
} // namespace
extern "C" {
using namespace ndarray::array;
void __nac3_ndarray_array_set_and_validate_list_shape(List<int32_t>* list, int32_t ndims, int32_t* shape) {
set_and_validate_list_shape(list, ndims, shape);
}
void __nac3_ndarray_array_set_and_validate_list_shape64(List<int64_t>* list, int64_t ndims, int64_t* shape) {
set_and_validate_list_shape(list, ndims, shape);
}
void __nac3_ndarray_array_write_list_to_array(List<int32_t>* list, NDArray<int32_t>* ndarray) {
write_list_to_array(list, ndarray);
}
void __nac3_ndarray_array_write_list_to_array64(List<int64_t>* list, NDArray<int64_t>* ndarray) {
write_list_to_array(list, ndarray);
}
}

View File

@ -6,7 +6,8 @@
#include "irrt/ndarray/def.hpp" #include "irrt/ndarray/def.hpp"
namespace { namespace {
namespace ndarray::basic { namespace ndarray {
namespace basic {
/** /**
* @brief Assert that `shape` does not contain negative dimensions. * @brief Assert that `shape` does not contain negative dimensions.
* *
@ -246,7 +247,8 @@ void copy_data(const NDArray<SizeT>* src_ndarray, NDArray<SizeT>* dst_ndarray) {
ndarray::basic::set_pelement_value(dst_ndarray, dst_element, src_element); ndarray::basic::set_pelement_value(dst_ndarray, dst_element, src_element);
} }
} }
} // namespace ndarray::basic } // namespace basic
} // namespace ndarray
} // namespace } // namespace
extern "C" { extern "C" {

View File

@ -1,165 +0,0 @@
#pragma once
#include "irrt/int_types.hpp"
#include "irrt/ndarray/def.hpp"
#include "irrt/slice.hpp"
namespace {
template<typename SizeT>
struct ShapeEntry {
SizeT ndims;
SizeT* shape;
};
} // namespace
namespace {
namespace ndarray::broadcast {
/**
* @brief Return true if `src_shape` can broadcast to `dst_shape`.
*
* See https://numpy.org/doc/stable/user/basics.broadcasting.html
*/
template<typename SizeT>
bool can_broadcast_shape_to(SizeT target_ndims, const SizeT* target_shape, SizeT src_ndims, const SizeT* src_shape) {
if (src_ndims > target_ndims) {
return false;
}
for (SizeT i = 0; i < src_ndims; i++) {
SizeT target_dim = target_shape[target_ndims - i - 1];
SizeT src_dim = src_shape[src_ndims - i - 1];
if (!(src_dim == 1 || target_dim == src_dim)) {
return false;
}
}
return true;
}
/**
* @brief Performs `np.broadcast_shapes(<shapes>)`
*
* @param num_shapes Number of entries in `shapes`
* @param shapes The list of shape to do `np.broadcast_shapes` on.
* @param dst_ndims The length of `dst_shape`.
* `dst_ndims` must be `max([shape.ndims for shape in shapes])`, but the caller has to calculate it/provide it.
* for this function since they should already know in order to allocate `dst_shape` in the first place.
* @param dst_shape The resulting shape. Must be pre-allocated by the caller. This function calculate the result
* of `np.broadcast_shapes` and write it here.
*/
template<typename SizeT>
void broadcast_shapes(SizeT num_shapes, const ShapeEntry<SizeT>* shapes, SizeT dst_ndims, SizeT* dst_shape) {
for (SizeT dst_axis = 0; dst_axis < dst_ndims; dst_axis++) {
dst_shape[dst_axis] = 1;
}
#ifdef IRRT_DEBUG_ASSERT
SizeT max_ndims_found = 0;
#endif
for (SizeT i = 0; i < num_shapes; i++) {
ShapeEntry<SizeT> entry = shapes[i];
// Check pre-condition: `dst_ndims` must be `max([shape.ndims for shape in shapes])`
debug_assert(SizeT, entry.ndims <= dst_ndims);
#ifdef IRRT_DEBUG_ASSERT
max_ndims_found = max(max_ndims_found, entry.ndims);
#endif
for (SizeT j = 0; j < entry.ndims; j++) {
SizeT entry_axis = entry.ndims - j - 1;
SizeT dst_axis = dst_ndims - j - 1;
SizeT entry_dim = entry.shape[entry_axis];
SizeT dst_dim = dst_shape[dst_axis];
if (dst_dim == 1) {
dst_shape[dst_axis] = entry_dim;
} else if (entry_dim == 1 || entry_dim == dst_dim) {
// Do nothing
} else {
raise_exception(SizeT, EXN_VALUE_ERROR,
"shape mismatch: objects cannot be broadcast "
"to a single shape.",
NO_PARAM, NO_PARAM, NO_PARAM);
}
}
}
#ifdef IRRT_DEBUG_ASSERT
// Check pre-condition: `dst_ndims` must be `max([shape.ndims for shape in shapes])`
debug_assert_eq(SizeT, max_ndims_found, dst_ndims);
#endif
}
/**
* @brief Perform `np.broadcast_to(<ndarray>, <target_shape>)` and appropriate assertions.
*
* This function attempts to broadcast `src_ndarray` to a new shape defined by `dst_ndarray.shape`,
* and return the result by modifying `dst_ndarray`.
*
* # Notes on `dst_ndarray`
* The caller is responsible for allocating space for the resulting ndarray.
* Here is what this function expects from `dst_ndarray` when called:
* - `dst_ndarray->data` does not have to be initialized.
* - `dst_ndarray->itemsize` does not have to be initialized.
* - `dst_ndarray->ndims` must be initialized, determining the length of `dst_ndarray->shape`
* - `dst_ndarray->shape` must be allocated, and must contain the desired target broadcast shape.
* - `dst_ndarray->strides` must be allocated, through it can contain uninitialized values.
* When this function call ends:
* - `dst_ndarray->data` is set to `src_ndarray->data` (`dst_ndarray` is just a view to `src_ndarray`)
* - `dst_ndarray->itemsize` is set to `src_ndarray->itemsize`
* - `dst_ndarray->ndims` is unchanged.
* - `dst_ndarray->shape` is unchanged.
* - `dst_ndarray->strides` is updated accordingly by how ndarray broadcast_to works.
*/
template<typename SizeT>
void broadcast_to(const NDArray<SizeT>* src_ndarray, NDArray<SizeT>* dst_ndarray) {
if (!ndarray::broadcast::can_broadcast_shape_to(dst_ndarray->ndims, dst_ndarray->shape, src_ndarray->ndims,
src_ndarray->shape)) {
raise_exception(SizeT, EXN_VALUE_ERROR, "operands could not be broadcast together", NO_PARAM, NO_PARAM,
NO_PARAM);
}
dst_ndarray->data = src_ndarray->data;
dst_ndarray->itemsize = src_ndarray->itemsize;
for (SizeT i = 0; i < dst_ndarray->ndims; i++) {
SizeT src_axis = src_ndarray->ndims - i - 1;
SizeT dst_axis = dst_ndarray->ndims - i - 1;
if (src_axis < 0 || (src_ndarray->shape[src_axis] == 1 && dst_ndarray->shape[dst_axis] != 1)) {
// Freeze the steps in-place
dst_ndarray->strides[dst_axis] = 0;
} else {
dst_ndarray->strides[dst_axis] = src_ndarray->strides[src_axis];
}
}
}
} // namespace ndarray::broadcast
} // namespace
extern "C" {
using namespace ndarray::broadcast;
void __nac3_ndarray_broadcast_to(NDArray<int32_t>* src_ndarray, NDArray<int32_t>* dst_ndarray) {
broadcast_to(src_ndarray, dst_ndarray);
}
void __nac3_ndarray_broadcast_to64(NDArray<int64_t>* src_ndarray, NDArray<int64_t>* dst_ndarray) {
broadcast_to(src_ndarray, dst_ndarray);
}
void __nac3_ndarray_broadcast_shapes(int32_t num_shapes,
const ShapeEntry<int32_t>* shapes,
int32_t dst_ndims,
int32_t* dst_shape) {
broadcast_shapes(num_shapes, shapes, dst_ndims, dst_shape);
}
void __nac3_ndarray_broadcast_shapes64(int64_t num_shapes,
const ShapeEntry<int64_t>* shapes,
int64_t dst_ndims,
int64_t* dst_shape) {
broadcast_shapes(num_shapes, shapes, dst_ndims, dst_shape);
}
}

View File

@ -65,7 +65,8 @@ struct NDIndex {
} // namespace } // namespace
namespace { namespace {
namespace ndarray::indexing { namespace ndarray {
namespace indexing {
/** /**
* @brief Perform ndarray "basic indexing" (https://numpy.org/doc/stable/user/basics.indexing.html#basic-indexing) * @brief Perform ndarray "basic indexing" (https://numpy.org/doc/stable/user/basics.indexing.html#basic-indexing)
* *
@ -161,8 +162,7 @@ void index(SizeT num_indices, const NDIndex* indices, const NDArray<SizeT>* src_
Range<int32_t> range = slice->indices_checked<SizeT>(src_ndarray->shape[src_axis]); Range<int32_t> range = slice->indices_checked<SizeT>(src_ndarray->shape[src_axis]);
dst_ndarray->data = dst_ndarray->data = static_cast<uint8_t*>(dst_ndarray->data) + (SizeT)range.start * src_ndarray->strides[src_axis];
static_cast<uint8_t*>(dst_ndarray->data) + (SizeT)range.start * src_ndarray->strides[src_axis];
dst_ndarray->strides[dst_axis] = ((SizeT)range.step) * src_ndarray->strides[src_axis]; dst_ndarray->strides[dst_axis] = ((SizeT)range.step) * src_ndarray->strides[src_axis];
dst_ndarray->shape[dst_axis] = (SizeT)range.len<SizeT>(); dst_ndarray->shape[dst_axis] = (SizeT)range.len<SizeT>();
@ -197,7 +197,8 @@ void index(SizeT num_indices, const NDIndex* indices, const NDArray<SizeT>* src_
debug_assert_eq(SizeT, src_ndarray->ndims, src_axis); debug_assert_eq(SizeT, src_ndarray->ndims, src_axis);
debug_assert_eq(SizeT, dst_ndarray->ndims, dst_axis); debug_assert_eq(SizeT, dst_ndarray->ndims, dst_axis);
} }
} // namespace ndarray::indexing } // namespace indexing
} // namespace ndarray
} // namespace } // namespace
extern "C" { extern "C" {

View File

@ -1,98 +0,0 @@
#pragma once
#include "irrt/debug.hpp"
#include "irrt/exception.hpp"
#include "irrt/int_types.hpp"
#include "irrt/ndarray/basic.hpp"
#include "irrt/ndarray/broadcast.hpp"
#include "irrt/ndarray/iter.hpp"
// NOTE: Everything would be much easier and elegant if einsum is implemented.
namespace {
namespace ndarray::matmul {
/**
* @brief Perform the broadcast in `np.einsum("...ij,...jk->...ik", a, b)`.
*
* Example:
* Suppose `a_shape == [1, 97, 4, 2]`
* and `b_shape == [99, 98, 1, 2, 5]`,
*
* ...then `new_a_shape == [99, 98, 97, 4, 2]`,
* `new_b_shape == [99, 98, 97, 2, 5]`,
* and `dst_shape == [99, 98, 97, 4, 5]`.
* ^^^^^^^^^^ ^^^^
* (broadcasted) (4x2 @ 2x5 => 4x5)
*
* @param a_ndims Length of `a_shape`.
* @param a_shape Shape of `a`.
* @param b_ndims Length of `b_shape`.
* @param b_shape Shape of `b`.
* @param final_ndims Should be equal to `max(a_ndims, b_ndims)`. This is the length of `new_a_shape`,
* `new_b_shape`, and `dst_shape` - the number of dimensions after broadcasting.
*/
template<typename SizeT>
void calculate_shapes(SizeT a_ndims,
SizeT* a_shape,
SizeT b_ndims,
SizeT* b_shape,
SizeT final_ndims,
SizeT* new_a_shape,
SizeT* new_b_shape,
SizeT* dst_shape) {
debug_assert(SizeT, a_ndims >= 2);
debug_assert(SizeT, b_ndims >= 2);
debug_assert_eq(SizeT, max(a_ndims, b_ndims), final_ndims);
// Check that a and b are compatible for matmul
if (a_shape[a_ndims - 1] != b_shape[b_ndims - 2]) {
// This is a custom error message. Different from NumPy.
raise_exception(SizeT, EXN_VALUE_ERROR, "Cannot multiply LHS (shape ?x{0}) with RHS (shape {1}x?})",
a_shape[a_ndims - 1], b_shape[b_ndims - 2], NO_PARAM);
}
const SizeT num_entries = 2;
ShapeEntry<SizeT> entries[num_entries] = {{.ndims = a_ndims - 2, .shape = a_shape},
{.ndims = b_ndims - 2, .shape = b_shape}};
// TODO: Optimize this
ndarray::broadcast::broadcast_shapes<SizeT>(num_entries, entries, final_ndims - 2, new_a_shape);
ndarray::broadcast::broadcast_shapes<SizeT>(num_entries, entries, final_ndims - 2, new_b_shape);
ndarray::broadcast::broadcast_shapes<SizeT>(num_entries, entries, final_ndims - 2, dst_shape);
new_a_shape[final_ndims - 2] = a_shape[a_ndims - 2];
new_a_shape[final_ndims - 1] = a_shape[a_ndims - 1];
new_b_shape[final_ndims - 2] = b_shape[b_ndims - 2];
new_b_shape[final_ndims - 1] = b_shape[b_ndims - 1];
dst_shape[final_ndims - 2] = a_shape[a_ndims - 2];
dst_shape[final_ndims - 1] = b_shape[b_ndims - 1];
}
} // namespace ndarray::matmul
} // namespace
extern "C" {
using namespace ndarray::matmul;
void __nac3_ndarray_matmul_calculate_shapes(int32_t a_ndims,
int32_t* a_shape,
int32_t b_ndims,
int32_t* b_shape,
int32_t final_ndims,
int32_t* new_a_shape,
int32_t* new_b_shape,
int32_t* dst_shape) {
calculate_shapes(a_ndims, a_shape, b_ndims, b_shape, final_ndims, new_a_shape, new_b_shape, dst_shape);
}
void __nac3_ndarray_matmul_calculate_shapes64(int64_t a_ndims,
int64_t* a_shape,
int64_t b_ndims,
int64_t* b_shape,
int64_t final_ndims,
int64_t* new_a_shape,
int64_t* new_b_shape,
int64_t* dst_shape) {
calculate_shapes(a_ndims, a_shape, b_ndims, b_shape, final_ndims, new_a_shape, new_b_shape, dst_shape);
}
}

View File

@ -1,97 +0,0 @@
#pragma once
#include "irrt/exception.hpp"
#include "irrt/int_types.hpp"
#include "irrt/ndarray/def.hpp"
namespace {
namespace ndarray::reshape {
/**
* @brief Perform assertions on and resolve unknown dimensions in `new_shape` in `np.reshape(<ndarray>, new_shape)`
*
* If `new_shape` indeed contains unknown dimensions (specified with `-1`, just like numpy), `new_shape` will be
* modified to contain the resolved dimension.
*
* To perform assertions on and resolve unknown dimensions in `new_shape`, we don't need the actual
* `<ndarray>` object itself, but only the `.size` of the `<ndarray>`.
*
* @param size The `.size` of `<ndarray>`
* @param new_ndims Number of elements in `new_shape`
* @param new_shape Target shape to reshape to
*/
template<typename SizeT>
void resolve_and_check_new_shape(SizeT size, SizeT new_ndims, SizeT* new_shape) {
// Is there a -1 in `new_shape`?
bool neg1_exists = false;
// Location of -1, only initialized if `neg1_exists` is true
SizeT neg1_axis_i;
// The computed ndarray size of `new_shape`
SizeT new_size = 1;
for (SizeT axis_i = 0; axis_i < new_ndims; axis_i++) {
SizeT dim = new_shape[axis_i];
if (dim < 0) {
if (dim == -1) {
if (neg1_exists) {
// Multiple `-1` found. Throw an error.
raise_exception(SizeT, EXN_VALUE_ERROR, "can only specify one unknown dimension", NO_PARAM,
NO_PARAM, NO_PARAM);
} else {
neg1_exists = true;
neg1_axis_i = axis_i;
}
} else {
// TODO: What? In `np.reshape` any negative dimensions is
// treated like its `-1`.
//
// Try running `np.zeros((3, 4)).reshape((-999, 2))`
//
// It is not documented by numpy.
// Throw an error for now...
raise_exception(SizeT, EXN_VALUE_ERROR, "Found non -1 negative dimension {0} on axis {1}", dim, axis_i,
NO_PARAM);
}
} else {
new_size *= dim;
}
}
bool can_reshape;
if (neg1_exists) {
// Let `x` be the unknown dimension
// Solve `x * <new_size> = <size>`
if (new_size == 0 && size == 0) {
// `x` has infinitely many solutions
can_reshape = false;
} else if (new_size == 0 && size != 0) {
// `x` has no solutions
can_reshape = false;
} else if (size % new_size != 0) {
// `x` has no integer solutions
can_reshape = false;
} else {
can_reshape = true;
new_shape[neg1_axis_i] = size / new_size; // Resolve dimension
}
} else {
can_reshape = (new_size == size);
}
if (!can_reshape) {
raise_exception(SizeT, EXN_VALUE_ERROR, "cannot reshape array of size {0} into given shape", size, NO_PARAM,
NO_PARAM);
}
}
} // namespace ndarray::reshape
} // namespace
extern "C" {
void __nac3_ndarray_reshape_resolve_and_check_new_shape(int32_t size, int32_t new_ndims, int32_t* new_shape) {
ndarray::reshape::resolve_and_check_new_shape(size, new_ndims, new_shape);
}
void __nac3_ndarray_reshape_resolve_and_check_new_shape64(int64_t size, int64_t new_ndims, int64_t* new_shape) {
ndarray::reshape::resolve_and_check_new_shape(size, new_ndims, new_shape);
}
}

View File

@ -1,143 +0,0 @@
#pragma once
#include "irrt/debug.hpp"
#include "irrt/exception.hpp"
#include "irrt/int_types.hpp"
#include "irrt/ndarray/def.hpp"
#include "irrt/slice.hpp"
/*
* Notes on `np.transpose(<array>, <axes>)`
*
* TODO: `axes`, if specified, can actually contain negative indices,
* but it is not documented in numpy.
*
* Supporting it for now.
*/
namespace {
namespace ndarray::transpose {
/**
* @brief Do assertions on `<axes>` in `np.transpose(<array>, <axes>)`.
*
* Note that `np.transpose`'s `<axe>` argument is optional. If the argument
* is specified but the user, use this function to do assertions on it.
*
* @param ndims The number of dimensions of `<array>`
* @param num_axes Number of elements in `<axes>` as specified by the user.
* This should be equal to `ndims`. If not, a "ValueError: axes don't match array" is thrown.
* @param axes The user specified `<axes>`.
*/
template<typename SizeT>
void assert_transpose_axes(SizeT ndims, SizeT num_axes, const SizeT* axes) {
if (ndims != num_axes) {
raise_exception(SizeT, EXN_VALUE_ERROR, "axes don't match array", NO_PARAM, NO_PARAM, NO_PARAM);
}
// TODO: Optimize this
bool* axe_specified = (bool*)__builtin_alloca(sizeof(bool) * ndims);
for (SizeT i = 0; i < ndims; i++)
axe_specified[i] = false;
for (SizeT i = 0; i < ndims; i++) {
SizeT axis = slice::resolve_index_in_length(ndims, axes[i]);
if (axis == -1) {
// TODO: numpy actually throws a `numpy.exceptions.AxisError`
raise_exception(SizeT, EXN_VALUE_ERROR, "axis {0} is out of bounds for array of dimension {1}", axis, ndims,
NO_PARAM);
}
if (axe_specified[axis]) {
raise_exception(SizeT, EXN_VALUE_ERROR, "repeated axis in transpose", NO_PARAM, NO_PARAM, NO_PARAM);
}
axe_specified[axis] = true;
}
}
/**
* @brief Create a transpose view of `src_ndarray` and perform proper assertions.
*
* This function is very similar to doing `dst_ndarray = np.transpose(src_ndarray, <axes>)`.
* If `<axes>` is supposed to be `None`, caller can pass in a `nullptr` to `<axes>`.
*
* The transpose view created is returned by modifying `dst_ndarray`.
*
* The caller is responsible for setting up `dst_ndarray` before calling this function.
* Here is what this function expects from `dst_ndarray` when called:
* - `dst_ndarray->data` does not have to be initialized.
* - `dst_ndarray->itemsize` does not have to be initialized.
* - `dst_ndarray->ndims` must be initialized, must be equal to `src_ndarray->ndims`.
* - `dst_ndarray->shape` must be allocated, through it can contain uninitialized values.
* - `dst_ndarray->strides` must be allocated, through it can contain uninitialized values.
* When this function call ends:
* - `dst_ndarray->data` is set to `src_ndarray->data` (`dst_ndarray` is just a view to `src_ndarray`)
* - `dst_ndarray->itemsize` is set to `src_ndarray->itemsize`
* - `dst_ndarray->ndims` is unchanged
* - `dst_ndarray->shape` is updated according to how `np.transpose` works
* - `dst_ndarray->strides` is updated according to how `np.transpose` works
*
* @param src_ndarray The NDArray to build a transpose view on
* @param dst_ndarray The resulting NDArray after transpose. Further details in the comments above,
* @param num_axes Number of elements in axes. Unused if `axes` is nullptr.
* @param axes Axes permutation. Set it to `nullptr` if `<axes>` is `None`.
*/
template<typename SizeT>
void transpose(const NDArray<SizeT>* src_ndarray, NDArray<SizeT>* dst_ndarray, SizeT num_axes, const SizeT* axes) {
debug_assert_eq(SizeT, src_ndarray->ndims, dst_ndarray->ndims);
const auto ndims = src_ndarray->ndims;
if (axes != nullptr)
assert_transpose_axes(ndims, num_axes, axes);
dst_ndarray->data = src_ndarray->data;
dst_ndarray->itemsize = src_ndarray->itemsize;
// Check out https://ajcr.net/stride-guide-part-2/ to see how `np.transpose` works behind the scenes.
if (axes == nullptr) {
// `np.transpose(<array>, axes=None)`
/*
* Minor note: `np.transpose(<array>, axes=None)` is equivalent to
* `np.transpose(<array>, axes=[N-1, N-2, ..., 0])` - basically it
* is reversing the order of strides and shape.
*
* This is a fast implementation to handle this special (but very common) case.
*/
for (SizeT axis = 0; axis < ndims; axis++) {
dst_ndarray->shape[axis] = src_ndarray->shape[ndims - axis - 1];
dst_ndarray->strides[axis] = src_ndarray->strides[ndims - axis - 1];
}
} else {
// `np.transpose(<array>, <axes>)`
// Permute strides and shape according to `axes`, while resolving negative indices in `axes`
for (SizeT axis = 0; axis < ndims; axis++) {
// `i` cannot be OUT_OF_BOUNDS because of assertions
SizeT i = slice::resolve_index_in_length(ndims, axes[axis]);
dst_ndarray->shape[axis] = src_ndarray->shape[i];
dst_ndarray->strides[axis] = src_ndarray->strides[i];
}
}
}
} // namespace ndarray::transpose
} // namespace
extern "C" {
using namespace ndarray::transpose;
void __nac3_ndarray_transpose(const NDArray<int32_t>* src_ndarray,
NDArray<int32_t>* dst_ndarray,
int32_t num_axes,
const int32_t* axes) {
transpose(src_ndarray, dst_ndarray, num_axes, axes);
}
void __nac3_ndarray_transpose64(const NDArray<int64_t>* src_ndarray,
NDArray<int64_t>* dst_ndarray,
int64_t num_axes,
const int64_t* axes) {
transpose(src_ndarray, dst_ndarray, num_axes, axes);
}
}

View File

@ -1,23 +0,0 @@
#pragma once
#include "irrt/int_types.hpp"
namespace {
template<typename SizeT>
bool __nac3_str_eq_impl(const char* str1, SizeT len1, const char* str2, SizeT len2) {
if (len1 != len2) {
return 0;
}
return __builtin_memcmp(str1, str2, static_cast<SizeT>(len1)) == 0;
}
} // namespace
extern "C" {
bool nac3_str_eq(const char* str1, uint32_t len1, const char* str2, uint32_t len2) {
return __nac3_str_eq_impl<uint32_t>(str1, len1, str2, len2);
}
bool nac3_str_eq64(const char* str1, uint64_t len1, const char* str2, uint64_t len2) {
return __nac3_str_eq_impl<uint64_t>(str1, len1, str2, len2);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,7 @@ use inkwell::{
values::{BasicValueEnum, CallSiteValue, FunctionValue, IntValue, PointerValue, StructValue}, values::{BasicValueEnum, CallSiteValue, FunctionValue, IntValue, PointerValue, StructValue},
AddressSpace, IntPredicate, OptimizationLevel, AddressSpace, IntPredicate, OptimizationLevel,
}; };
use itertools::{izip, Either, Itertools}; use itertools::{chain, izip, Either, Itertools};
use nac3parser::ast::{ use nac3parser::ast::{
self, Boolop, Cmpop, Comprehension, Constant, Expr, ExprKind, Location, Operator, StrRef, self, Boolop, Cmpop, Comprehension, Constant, Expr, ExprKind, Location, Operator, StrRef,
@ -24,26 +24,26 @@ use super::{
irrt::*, irrt::*,
llvm_intrinsics::{ llvm_intrinsics::{
call_expect, call_float_floor, call_float_pow, call_float_powi, call_int_smax, call_expect, call_float_floor, call_float_pow, call_float_powi, call_int_smax,
call_memcpy_generic, call_int_umin, call_memcpy_generic,
}, },
macros::codegen_unreachable, macros::codegen_unreachable,
need_sret, need_sret, numpy,
stmt::{ stmt::{
gen_for_callback_incrementing, gen_if_callback, gen_if_else_expr_callback, gen_raise, gen_for_callback_incrementing, gen_if_callback, gen_if_else_expr_callback, gen_raise,
gen_var, gen_var,
}, },
types::{ndarray::NDArrayType, ListType}, types::{ndarray::NDArrayType, ListType},
values::{ values::{
ndarray::{NDArrayOut, RustNDIndex, ScalarOrNDArray}, ndarray::{NDArrayValue, RustNDIndex},
ArrayLikeIndexer, ArrayLikeValue, ListValue, ProxyValue, RangeValue, ArrayLikeIndexer, ArrayLikeValue, ListValue, ProxyValue, RangeValue,
UntypedArrayLikeAccessor, TypedArrayLikeAccessor, UntypedArrayLikeAccessor,
}, },
CodeGenContext, CodeGenTask, CodeGenerator, CodeGenContext, CodeGenTask, CodeGenerator,
}; };
use crate::{ use crate::{
symbol_resolver::{SymbolValue, ValueEnum}, symbol_resolver::{SymbolValue, ValueEnum},
toplevel::{ toplevel::{
helper::{arraylike_flatten_element_type, PrimDef}, helper::{extract_ndims, PrimDef},
numpy::unpack_ndarray_var_tys, numpy::unpack_ndarray_var_tys,
DefinitionId, TopLevelDef, DefinitionId, TopLevelDef,
}, },
@ -79,7 +79,7 @@ pub fn get_subst_key(
.join(", ") .join(", ")
} }
impl<'ctx> CodeGenContext<'ctx, '_> { impl<'ctx, 'a> CodeGenContext<'ctx, 'a> {
/// Builds a sequence of `getelementptr` and `load` instructions which stores the value of a /// Builds a sequence of `getelementptr` and `load` instructions which stores the value of a
/// struct field into an LLVM value. /// struct field into an LLVM value.
pub fn build_gep_and_load( pub fn build_gep_and_load(
@ -1095,6 +1095,33 @@ pub fn destructure_range<'ctx>(
(start, end, step) (start, end, step)
} }
/// Allocates a List structure with the given [type][ty] and [length]. The name of the resulting
/// LLVM value is `{name}.addr`, or `list.addr` if [name] is not specified.
///
/// Setting `ty` to [`None`] implies that the list is empty **and** does not have a known element
/// type, and will therefore set the `list.data` type as `size_t*`. It is undefined behavior to
/// generate a sized list with an unknown element type.
pub fn allocate_list<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
ty: Option<BasicTypeEnum<'ctx>>,
length: IntValue<'ctx>,
name: Option<&'ctx str>,
) -> ListValue<'ctx> {
let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_elem_ty = ty.unwrap_or(llvm_usize.into());
// List structure; type { ty*, size_t }
let arr_ty = ListType::new(generator, ctx.ctx, llvm_elem_ty);
let list = arr_ty.alloca(generator, ctx, name);
let length = ctx.builder.build_int_z_extend(length, llvm_usize, "").unwrap();
list.store_size(ctx, generator, length);
list.create_data(ctx, llvm_elem_ty, None);
list
}
/// Generates LLVM IR for a [list comprehension expression][expr]. /// Generates LLVM IR for a [list comprehension expression][expr].
pub fn gen_comprehension<'ctx, G: CodeGenerator>( pub fn gen_comprehension<'ctx, G: CodeGenerator>(
generator: &mut G, generator: &mut G,
@ -1167,11 +1194,12 @@ pub fn gen_comprehension<'ctx, G: CodeGenerator>(
"listcomp.alloc_size", "listcomp.alloc_size",
) )
.unwrap(); .unwrap();
list = ListType::new(generator, ctx.ctx, elem_ty).construct( list = allocate_list(
generator, generator,
ctx, ctx,
Some(elem_ty),
list_alloc_size.into_int_value(), list_alloc_size.into_int_value(),
Some("listcomp"), Some("listcomp.addr"),
); );
let i = generator.gen_store_target(ctx, target, Some("i.addr"))?.unwrap(); let i = generator.gen_store_target(ctx, target, Some("i.addr"))?.unwrap();
@ -1218,12 +1246,7 @@ pub fn gen_comprehension<'ctx, G: CodeGenerator>(
Some("length"), Some("length"),
) )
.into_int_value(); .into_int_value();
list = ListType::new(generator, ctx.ctx, elem_ty).construct( list = allocate_list(generator, ctx, Some(elem_ty), length, Some("listcomp"));
generator,
ctx,
length,
Some("listcomp"),
);
let counter = generator.gen_var_alloc(ctx, size_t.into(), Some("counter.addr"))?; let counter = generator.gen_var_alloc(ctx, size_t.into(), Some("counter.addr"))?;
// counter = -1 // counter = -1
@ -1388,8 +1411,7 @@ pub fn gen_binop_expr_with_values<'ctx, G: CodeGenerator>(
.build_int_add(lhs.load_size(ctx, None), rhs.load_size(ctx, None), "") .build_int_add(lhs.load_size(ctx, None), rhs.load_size(ctx, None), "")
.unwrap(); .unwrap();
let new_list = ListType::new(generator, ctx.ctx, llvm_elem_ty) let new_list = allocate_list(generator, ctx, Some(llvm_elem_ty), size, None);
.construct(generator, ctx, size, None);
let lhs_size = ctx let lhs_size = ctx
.builder .builder
@ -1476,9 +1498,10 @@ pub fn gen_binop_expr_with_values<'ctx, G: CodeGenerator>(
let elem_llvm_ty = ctx.get_llvm_type(generator, elem_ty); let elem_llvm_ty = ctx.get_llvm_type(generator, elem_ty);
let sizeof_elem = elem_llvm_ty.size_of().unwrap(); let sizeof_elem = elem_llvm_ty.size_of().unwrap();
let new_list = ListType::new(generator, ctx.ctx, elem_llvm_ty).construct( let new_list = allocate_list(
generator, generator,
ctx, ctx,
Some(elem_llvm_ty),
ctx.builder.build_int_mul(list_val.load_size(ctx, None), int_val, "").unwrap(), ctx.builder.build_int_mul(list_val.load_size(ctx, None), int_val, "").unwrap(),
None, None,
); );
@ -1531,77 +1554,98 @@ pub fn gen_binop_expr_with_values<'ctx, G: CodeGenerator>(
} else if ty1.obj_id(&ctx.unifier).is_some_and(|id| id == PrimDef::NDArray.id()) } else if ty1.obj_id(&ctx.unifier).is_some_and(|id| id == PrimDef::NDArray.id())
|| ty2.obj_id(&ctx.unifier).is_some_and(|id| id == PrimDef::NDArray.id()) || ty2.obj_id(&ctx.unifier).is_some_and(|id| id == PrimDef::NDArray.id())
{ {
let left = ScalarOrNDArray::from_value(generator, ctx, (ty1, left_val)); let is_ndarray1 = ty1.obj_id(&ctx.unifier).is_some_and(|id| id == PrimDef::NDArray.id());
let right = ScalarOrNDArray::from_value(generator, ctx, (ty2, right_val)); let is_ndarray2 = ty2.obj_id(&ctx.unifier).is_some_and(|id| id == PrimDef::NDArray.id());
let ty1_dtype = arraylike_flatten_element_type(&mut ctx.unifier, ty1); if is_ndarray1 && is_ndarray2 {
let ty2_dtype = arraylike_flatten_element_type(&mut ctx.unifier, ty2); let (ndarray_dtype1, _) = unpack_ndarray_var_tys(&mut ctx.unifier, ty1);
let (ndarray_dtype2, _) = unpack_ndarray_var_tys(&mut ctx.unifier, ty2);
// Inhomogeneous binary operations are not supported. assert!(ctx.unifier.unioned(ndarray_dtype1, ndarray_dtype2));
assert!(ctx.unifier.unioned(ty1_dtype, ty2_dtype));
let common_dtype = ty1_dtype; let left_val = NDArrayType::from_unifier_type(generator, ctx, ty1)
let llvm_common_dtype = left.get_dtype(); .map_value(left_val.into_pointer_value(), None);
let right_val = NDArrayType::from_unifier_type(generator, ctx, ty2)
.map_value(right_val.into_pointer_value(), None);
let out = match op.variant { let res = if op.base == Operator::MatMult {
BinopVariant::Normal => NDArrayOut::NewNDArray { dtype: llvm_common_dtype }, // MatMult is the only binop which is not an elementwise op
BinopVariant::AugAssign => { numpy::ndarray_matmul_2d(
// Augmented assignment - `left` has to be an ndarray. If it were a scalar then NAC3
// simply doesn't support it.
if let ScalarOrNDArray::NDArray(out_ndarray) = left {
NDArrayOut::WriteToNDArray { ndarray: out_ndarray }
} else {
panic!("left must be an ndarray")
}
}
};
if op.base == Operator::MatMult {
let left = left.to_ndarray(generator, ctx);
let right = right.to_ndarray(generator, ctx);
let result = left
.matmul(generator, ctx, ty1, (ty2, right), (common_dtype, out))
.split_unsized(generator, ctx);
Ok(Some(result.to_basic_value_enum().into()))
} else {
// For other operations, they are all elementwise operations.
// There are only three cases:
// - LHS is a scalar, RHS is an ndarray.
// - LHS is an ndarray, RHS is a scalar.
// - LHS is an ndarray, RHS is an ndarray.
//
// For all cases, the scalar operand is promoted to an ndarray,
// the two are then broadcasted, and starmapped through.
let left = left.to_ndarray(generator, ctx);
let right = right.to_ndarray(generator, ctx);
let result = NDArrayType::new_broadcast(
generator,
ctx.ctx,
llvm_common_dtype,
&[left.get_type(), right.get_type()],
)
.broadcast_starmap(generator, ctx, &[left, right], out, |generator, ctx, scalars| {
let left_value = scalars[0];
let right_value = scalars[1];
let result = gen_binop_expr_with_values(
generator, generator,
ctx, ctx,
(&Some(ty1_dtype), left_value), ndarray_dtype1,
op, match op.variant {
(&Some(ty2_dtype), right_value), BinopVariant::Normal => None,
ctx.current_loc, BinopVariant::AugAssign => Some(left_val),
},
left_val,
right_val,
)? )?
.unwrap() } else {
.to_basic_value_enum(ctx, generator, common_dtype)?; numpy::ndarray_elementwise_binop_impl(
generator,
ctx,
ndarray_dtype1,
match op.variant {
BinopVariant::Normal => None,
BinopVariant::AugAssign => Some(left_val),
},
(ty1, left_val.as_base_value().into(), false),
(ty2, right_val.as_base_value().into(), false),
|generator, ctx, (lhs, rhs)| {
gen_binop_expr_with_values(
generator,
ctx,
(&Some(ndarray_dtype1), lhs),
op,
(&Some(ndarray_dtype2), rhs),
ctx.current_loc,
)?
.unwrap()
.to_basic_value_enum(
ctx,
generator,
ndarray_dtype1,
)
},
)?
};
Ok(result) Ok(Some(res.as_base_value().into()))
}) } else {
.unwrap(); let (ndarray_dtype, _) =
Ok(Some(result.as_base_value().into())) unpack_ndarray_var_tys(&mut ctx.unifier, if is_ndarray1 { ty1 } else { ty2 });
let ndarray_val =
NDArrayType::from_unifier_type(generator, ctx, if is_ndarray1 { ty1 } else { ty2 })
.map_value(
if is_ndarray1 { left_val } else { right_val }.into_pointer_value(),
None,
);
let res = numpy::ndarray_elementwise_binop_impl(
generator,
ctx,
ndarray_dtype,
match op.variant {
BinopVariant::Normal => None,
BinopVariant::AugAssign => Some(ndarray_val),
},
(ty1, left_val, !is_ndarray1),
(ty2, right_val, !is_ndarray2),
|generator, ctx, (lhs, rhs)| {
gen_binop_expr_with_values(
generator,
ctx,
(&Some(ndarray_dtype), lhs),
op,
(&Some(ndarray_dtype), rhs),
ctx.current_loc,
)?
.unwrap()
.to_basic_value_enum(ctx, generator, ndarray_dtype)
},
)?;
Ok(Some(res.as_base_value().into()))
} }
} else { } else {
let left_ty_enum = ctx.unifier.get_ty_immutable(left_ty.unwrap()); let left_ty_enum = ctx.unifier.get_ty_immutable(left_ty.unwrap());
@ -1764,10 +1808,10 @@ pub fn gen_unaryop_expr_with_values<'ctx, G: CodeGenerator>(
_ => val.into(), _ => val.into(),
} }
} else if ty.obj_id(&ctx.unifier).is_some_and(|id| id == PrimDef::NDArray.id()) { } else if ty.obj_id(&ctx.unifier).is_some_and(|id| id == PrimDef::NDArray.id()) {
let llvm_ndarray_ty = NDArrayType::from_unifier_type(generator, ctx, ty);
let (ndarray_dtype, _) = unpack_ndarray_var_tys(&mut ctx.unifier, ty); let (ndarray_dtype, _) = unpack_ndarray_var_tys(&mut ctx.unifier, ty);
let ndarray = NDArrayType::from_unifier_type(generator, ctx, ty) let val = llvm_ndarray_ty.map_value(val.into_pointer_value(), None);
.map_value(val.into_pointer_value(), None);
// ndarray uses `~` rather than `not` to perform elementwise inversion, convert it before // ndarray uses `~` rather than `not` to perform elementwise inversion, convert it before
// passing it to the elementwise codegen function // passing it to the elementwise codegen function
@ -1785,18 +1829,20 @@ pub fn gen_unaryop_expr_with_values<'ctx, G: CodeGenerator>(
op op
}; };
let mapped_ndarray = ndarray.map( let res = numpy::ndarray_elementwise_unaryop_impl(
generator, generator,
ctx, ctx,
NDArrayOut::NewNDArray { dtype: ndarray.get_type().element_type() }, ndarray_dtype,
|generator, ctx, scalar| { None,
gen_unaryop_expr_with_values(generator, ctx, op, (&Some(ndarray_dtype), scalar))? val,
.map(|val| val.to_basic_value_enum(ctx, generator, ndarray_dtype)) |generator, ctx, val| {
gen_unaryop_expr_with_values(generator, ctx, op, (&Some(ndarray_dtype), val))?
.unwrap() .unwrap()
.to_basic_value_enum(ctx, generator, ndarray_dtype)
}, },
)?; )?;
mapped_ndarray.as_base_value().into() res.as_base_value().into()
} else { } else {
unimplemented!() unimplemented!()
})) }))
@ -1839,56 +1885,87 @@ pub fn gen_cmpop_expr_with_values<'ctx, G: CodeGenerator>(
if left_ty.obj_id(&ctx.unifier).is_some_and(|id| id == PrimDef::NDArray.id()) if left_ty.obj_id(&ctx.unifier).is_some_and(|id| id == PrimDef::NDArray.id())
|| right_ty.obj_id(&ctx.unifier).is_some_and(|id| id == PrimDef::NDArray.id()) || right_ty.obj_id(&ctx.unifier).is_some_and(|id| id == PrimDef::NDArray.id())
{ {
let (Some(left_ty), left) = left else { codegen_unreachable!(ctx) }; let (Some(left_ty), lhs) = left else { codegen_unreachable!(ctx) };
let (Some(right_ty), right) = comparators[0] else { codegen_unreachable!(ctx) }; let (Some(right_ty), rhs) = comparators[0] else { codegen_unreachable!(ctx) };
let op = ops[0]; let op = ops[0];
let left_ty_dtype = arraylike_flatten_element_type(&mut ctx.unifier, left_ty); let is_ndarray1 =
let right_ty_dtype = arraylike_flatten_element_type(&mut ctx.unifier, right_ty); left_ty.obj_id(&ctx.unifier).is_some_and(|id| id == PrimDef::NDArray.id());
let is_ndarray2 =
right_ty.obj_id(&ctx.unifier).is_some_and(|id| id == PrimDef::NDArray.id());
let left = ScalarOrNDArray::from_value(generator, ctx, (left_ty, left)) return if is_ndarray1 && is_ndarray2 {
.to_ndarray(generator, ctx); let (ndarray_dtype1, _) = unpack_ndarray_var_tys(&mut ctx.unifier, left_ty);
let right = ScalarOrNDArray::from_value(generator, ctx, (right_ty, right)) let (ndarray_dtype2, _) = unpack_ndarray_var_tys(&mut ctx.unifier, right_ty);
.to_ndarray(generator, ctx);
let result_ndarray = NDArrayType::new_broadcast( assert!(ctx.unifier.unioned(ndarray_dtype1, ndarray_dtype2));
generator,
ctx.ctx,
ctx.ctx.i8_type().into(),
&[left.get_type(), right.get_type()],
)
.broadcast_starmap(
generator,
ctx,
&[left, right],
NDArrayOut::NewNDArray { dtype: ctx.ctx.i8_type().into() },
|generator, ctx, scalars| {
let left_scalar = scalars[0];
let right_scalar = scalars[1];
let val = gen_cmpop_expr_with_values( let left_val = NDArrayType::from_unifier_type(generator, ctx, left_ty)
generator, .map_value(lhs.into_pointer_value(), None);
ctx, let res = numpy::ndarray_elementwise_binop_impl(
(Some(left_ty_dtype), left_scalar), generator,
&[op], ctx,
&[(Some(right_ty_dtype), right_scalar)], ctx.primitives.bool,
)? None,
.unwrap() (left_ty, left_val.as_base_value().into(), false),
.to_basic_value_enum( (right_ty, rhs, false),
ctx, |generator, ctx, (lhs, rhs)| {
generator, let val = gen_cmpop_expr_with_values(
ctx.primitives.bool, generator,
)?; ctx,
(Some(ndarray_dtype1), lhs),
&[op],
&[(Some(ndarray_dtype2), rhs)],
)?
.unwrap()
.to_basic_value_enum(
ctx,
generator,
ctx.primitives.bool,
)?;
Ok(generator.bool_to_i8(ctx, val.into_int_value()).into()) Ok(generator.bool_to_i8(ctx, val.into_int_value()).into())
}, },
)?; )?;
return Ok(Some(result_ndarray.as_base_value().into())); Ok(Some(res.as_base_value().into()))
} else {
let (ndarray_dtype, _) = unpack_ndarray_var_tys(
&mut ctx.unifier,
if is_ndarray1 { left_ty } else { right_ty },
);
let res = numpy::ndarray_elementwise_binop_impl(
generator,
ctx,
ctx.primitives.bool,
None,
(left_ty, lhs, !is_ndarray1),
(right_ty, rhs, !is_ndarray2),
|generator, ctx, (lhs, rhs)| {
let val = gen_cmpop_expr_with_values(
generator,
ctx,
(Some(ndarray_dtype), lhs),
&[op],
&[(Some(ndarray_dtype), rhs)],
)?
.unwrap()
.to_basic_value_enum(
ctx,
generator,
ctx.primitives.bool,
)?;
Ok(generator.bool_to_i8(ctx, val.into_int_value()).into())
},
)?;
Ok(Some(res.as_base_value().into()))
};
} }
} }
let cmp_val = izip!(once(&left).chain(comparators.iter()), comparators.iter(), ops.iter(),) let cmp_val = izip!(chain(once(&left), comparators.iter()), comparators.iter(), ops.iter(),)
.fold(Ok(None), |prev: Result<Option<_>, String>, (lhs, rhs, op)| { .fold(Ok(None), |prev: Result<Option<_>, String>, (lhs, rhs, op)| {
let (left_ty, lhs) = lhs; let (left_ty, lhs) = lhs;
let (right_ty, rhs) = rhs; let (right_ty, rhs) = rhs;
@ -1968,43 +2045,111 @@ pub fn gen_cmpop_expr_with_values<'ctx, G: CodeGenerator>(
} else if left_ty == ctx.primitives.str { } else if left_ty == ctx.primitives.str {
assert!(ctx.unifier.unioned(left_ty, right_ty)); assert!(ctx.unifier.unioned(left_ty, right_ty));
let lhs = lhs.into_struct_value(); let llvm_i1 = ctx.ctx.bool_type();
let rhs = rhs.into_struct_value();
let llvm_i32 = ctx.ctx.i32_type(); let llvm_i32 = ctx.ctx.i32_type();
let llvm_usize = generator.get_size_type(ctx.ctx); let llvm_usize = generator.get_size_type(ctx.ctx);
let lhs = lhs.into_struct_value();
let rhs = rhs.into_struct_value();
let plhs = generator.gen_var_alloc(ctx, lhs.get_type().into(), None).unwrap(); let plhs = generator.gen_var_alloc(ctx, lhs.get_type().into(), None).unwrap();
ctx.builder.build_store(plhs, lhs).unwrap(); ctx.builder.build_store(plhs, lhs).unwrap();
let prhs = generator.gen_var_alloc(ctx, lhs.get_type().into(), None).unwrap(); let prhs = generator.gen_var_alloc(ctx, lhs.get_type().into(), None).unwrap();
ctx.builder.build_store(prhs, rhs).unwrap(); ctx.builder.build_store(prhs, rhs).unwrap();
let lhs_ptr = ctx.build_in_bounds_gep_and_load(
plhs,
&[llvm_usize.const_zero(), llvm_i32.const_zero()],
None,
).into_pointer_value();
let lhs_len = ctx.build_in_bounds_gep_and_load( let lhs_len = ctx.build_in_bounds_gep_and_load(
plhs, plhs,
&[llvm_usize.const_zero(), llvm_i32.const_int(1, false)], &[llvm_i32.const_zero(), llvm_i32.const_int(1, false)],
None,
).into_int_value();
let rhs_len = ctx.build_in_bounds_gep_and_load(
prhs,
&[llvm_i32.const_zero(), llvm_i32.const_int(1, false)],
None, None,
).into_int_value(); ).into_int_value();
let rhs_ptr = ctx.build_in_bounds_gep_and_load( let len = call_int_umin(ctx, lhs_len, rhs_len, None);
prhs,
&[llvm_usize.const_zero(), llvm_i32.const_zero()], let current_bb = ctx.builder.get_insert_block().unwrap();
let post_foreach_cmp = ctx.ctx.insert_basic_block_after(current_bb, "foreach.cmp.end");
ctx.builder.position_at_end(post_foreach_cmp);
let cmp_phi = ctx.builder.build_phi(llvm_i1, "").unwrap();
ctx.builder.position_at_end(current_bb);
gen_for_callback_incrementing(
generator,
ctx,
None, None,
).into_pointer_value(); llvm_usize.const_zero(),
let rhs_len = ctx.build_in_bounds_gep_and_load( (len, false),
prhs, |generator, ctx, _, i| {
&[llvm_usize.const_zero(), llvm_i32.const_int(1, false)], let lhs_char = {
None, let plhs_data = ctx.build_in_bounds_gep_and_load(
).into_int_value(); plhs,
let result = call_string_eq(generator, ctx, lhs_ptr, lhs_len, rhs_ptr, rhs_len); &[llvm_i32.const_zero(), llvm_i32.const_zero()],
None,
).into_pointer_value();
ctx.build_in_bounds_gep_and_load(
plhs_data,
&[i],
None
).into_int_value()
};
let rhs_char = {
let prhs_data = ctx.build_in_bounds_gep_and_load(
prhs,
&[llvm_i32.const_zero(), llvm_i32.const_zero()],
None,
).into_pointer_value();
ctx.build_in_bounds_gep_and_load(
prhs_data,
&[i],
None
).into_int_value()
};
gen_if_callback(
generator,
ctx,
|_, ctx| {
Ok(ctx.builder.build_int_compare(IntPredicate::NE, lhs_char, rhs_char, "").unwrap())
},
|_, ctx| {
let bb = ctx.builder.get_insert_block().unwrap();
cmp_phi.add_incoming(&[(&llvm_i1.const_zero(), bb)]);
ctx.builder.build_unconditional_branch(post_foreach_cmp).unwrap();
Ok(())
},
|_, _| Ok(()),
)?;
Ok(())
},
llvm_usize.const_int(1, false),
)?;
let bb = ctx.builder.get_insert_block().unwrap();
let is_len_eq = ctx.builder.build_int_compare(
IntPredicate::EQ,
lhs_len,
rhs_len,
"",
).unwrap();
cmp_phi.add_incoming(&[(&is_len_eq, bb)]);
ctx.builder.build_unconditional_branch(post_foreach_cmp).unwrap();
ctx.builder.position_at_end(post_foreach_cmp);
let cmp_phi = cmp_phi.as_basic_value().into_int_value();
// Invert the final value if __ne__
if *op == Cmpop::NotEq { if *op == Cmpop::NotEq {
ctx.builder.build_not(result, "").unwrap() ctx.builder.build_not(cmp_phi, "").unwrap()
} else { } else {
result cmp_phi
} }
} else if [left_ty, right_ty] } else if [left_ty, right_ty]
.iter() .iter()
@ -2367,6 +2512,319 @@ pub fn gen_cmpop_expr<'ctx, G: CodeGenerator>(
) )
} }
/// Generates code for a subscript expression on an `ndarray`.
///
/// * `ty` - The `Type` of the `NDArray` elements.
/// * `ndims` - The `Type` of the `NDArray` number-of-dimensions `Literal`.
/// * `v` - The `NDArray` value.
/// * `slice` - The slice expression used to subscript into the `ndarray`.
fn gen_ndarray_subscript_expr<'ctx, G: CodeGenerator>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
ty: Type,
ndims_ty: Type,
v: NDArrayValue<'ctx>,
slice: &Expr<Option<Type>>,
) -> Result<Option<ValueEnum<'ctx>>, String> {
let llvm_i1 = ctx.ctx.bool_type();
let llvm_i32 = ctx.ctx.i32_type();
let llvm_usize = generator.get_size_type(ctx.ctx);
let TypeEnum::TLiteral { values, .. } = &*ctx.unifier.get_ty_immutable(ndims_ty) else {
codegen_unreachable!(ctx)
};
let ndims = values
.iter()
.map(|ndim| u64::try_from(ndim.clone()).map_err(|()| ndim.clone()))
.collect::<Result<Vec<_>, _>>()
.map_err(|val| {
format!(
"Expected non-negative literal for ndarray.ndims, got {}",
i128::try_from(val).unwrap()
)
})?;
assert!(!ndims.is_empty());
// The number of dimensions subscripted by the index expression.
// Slicing a ndarray will yield the same number of dimensions, whereas indexing into a
// dimension will remove a dimension.
let subscripted_dims = match &slice.node {
ExprKind::Tuple { elts, .. } => elts.iter().fold(0, |acc, value_subexpr| {
if let ExprKind::Slice { .. } = &value_subexpr.node {
acc
} else {
acc + 1
}
}),
ExprKind::Slice { .. } => 0,
_ => 1,
};
let llvm_ndarray_data_t = ctx.get_llvm_type(generator, ty).as_basic_type_enum();
let sizeof_elem = llvm_ndarray_data_t.size_of().unwrap();
// Check that len is non-zero
let len = v.load_ndims(ctx);
ctx.make_assert(
generator,
ctx.builder.build_int_compare(IntPredicate::SGT, len, llvm_usize.const_zero(), "").unwrap(),
"0:IndexError",
"too many indices for array: array is {0}-dimensional but 1 were indexed",
[Some(len), None, None],
slice.location,
);
// Normalizes a possibly-negative index to its corresponding positive index
let normalize_index = |generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
index: IntValue<'ctx>,
dim: u64| {
gen_if_else_expr_callback(
generator,
ctx,
|_, ctx| {
Ok(ctx
.builder
.build_int_compare(IntPredicate::SGE, index, index.get_type().const_zero(), "")
.unwrap())
},
|_, _| Ok(Some(index)),
|generator, ctx| {
let llvm_i32 = ctx.ctx.i32_type();
let len = unsafe {
v.shape().get_typed_unchecked(
ctx,
generator,
&llvm_usize.const_int(dim, true),
None,
)
};
let index = ctx
.builder
.build_int_add(
len,
ctx.builder.build_int_s_extend(index, llvm_usize, "").unwrap(),
"",
)
.unwrap();
Ok(Some(ctx.builder.build_int_truncate(index, llvm_i32, "").unwrap()))
},
)
.map(|v| v.map(BasicValueEnum::into_int_value))
};
// Converts a slice expression into a slice-range tuple
let expr_to_slice = |generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
node: &ExprKind<Option<Type>>,
dim: u64| {
match node {
ExprKind::Constant { value: Constant::Int(v), .. } => {
let Some(index) =
normalize_index(generator, ctx, llvm_i32.const_int(*v as u64, true), dim)?
else {
return Ok(None);
};
Ok(Some((index, index, llvm_i32.const_int(1, true))))
}
ExprKind::Slice { lower, upper, step } => {
let dim_sz = unsafe {
v.shape().get_typed_unchecked(
ctx,
generator,
&llvm_usize.const_int(dim, false),
None,
)
};
handle_slice_indices(lower, upper, step, ctx, generator, dim_sz)
}
_ => {
let Some(index) = generator.gen_expr(ctx, slice)? else { return Ok(None) };
let index = index
.to_basic_value_enum(ctx, generator, slice.custom.unwrap())?
.into_int_value();
let Some(index) = normalize_index(generator, ctx, index, dim)? else {
return Ok(None);
};
Ok(Some((index, index, llvm_i32.const_int(1, true))))
}
}
};
let make_indices_arr = |generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>|
-> Result<_, String> {
Ok(if let ExprKind::Tuple { elts, .. } = &slice.node {
let llvm_int_ty = ctx.get_llvm_type(generator, elts[0].custom.unwrap());
let index_addr = generator.gen_array_var_alloc(
ctx,
llvm_int_ty,
llvm_usize.const_int(elts.len() as u64, false),
None,
)?;
for (i, elt) in elts.iter().enumerate() {
let Some(index) = generator.gen_expr(ctx, elt)? else {
return Ok(None);
};
let index = index
.to_basic_value_enum(ctx, generator, elt.custom.unwrap())?
.into_int_value();
let Some(index) = normalize_index(generator, ctx, index, 0)? else {
return Ok(None);
};
let store_ptr = unsafe {
index_addr.ptr_offset_unchecked(
ctx,
generator,
&llvm_usize.const_int(i as u64, false),
None,
)
};
ctx.builder.build_store(store_ptr, index).unwrap();
}
Some(index_addr)
} else if let Some(index) = generator.gen_expr(ctx, slice)? {
let llvm_int_ty = ctx.get_llvm_type(generator, slice.custom.unwrap());
let index_addr = generator.gen_array_var_alloc(
ctx,
llvm_int_ty,
llvm_usize.const_int(1u64, false),
None,
)?;
let index =
index.to_basic_value_enum(ctx, generator, slice.custom.unwrap())?.into_int_value();
let Some(index) = normalize_index(generator, ctx, index, 0)? else { return Ok(None) };
let store_ptr = unsafe {
index_addr.ptr_offset_unchecked(ctx, generator, &llvm_usize.const_zero(), None)
};
ctx.builder.build_store(store_ptr, index).unwrap();
Some(index_addr)
} else {
None
})
};
Ok(Some(if ndims.len() == 1 && ndims[0] - subscripted_dims == 0 {
let Some(index_addr) = make_indices_arr(generator, ctx)? else { return Ok(None) };
v.data().get(ctx, generator, &index_addr, None).into()
} else {
match &slice.node {
ExprKind::Tuple { elts, .. } => {
let slices = elts
.iter()
.enumerate()
.map(|(dim, elt)| expr_to_slice(generator, ctx, &elt.node, dim as u64))
.take_while_inclusive(|slice| slice.as_ref().is_ok_and(Option::is_some))
.collect::<Result<Vec<_>, _>>()?;
if slices.len() < elts.len() {
return Ok(None);
}
let slices = slices.into_iter().map(Option::unwrap).collect_vec();
numpy::ndarray_sliced_copy(generator, ctx, ty, v, &slices)?.as_base_value().into()
}
ExprKind::Slice { .. } => {
let Some(slice) = expr_to_slice(generator, ctx, &slice.node, 0)? else {
return Ok(None);
};
numpy::ndarray_sliced_copy(generator, ctx, ty, v, &[slice])?.as_base_value().into()
}
_ => {
// Accessing an element from a multi-dimensional `ndarray`
let Some(index_addr) = make_indices_arr(generator, ctx)? else { return Ok(None) };
let num_dims = extract_ndims(&ctx.unifier, ndims_ty) - 1;
// Create a new array, remove the top dimension from the dimension-size-list, and copy the
// elements over
let ndarray =
NDArrayType::new(generator, ctx.ctx, llvm_ndarray_data_t, Some(num_dims))
.construct_uninitialized(generator, ctx, None);
let ndarray_num_dims = ctx
.builder
.build_int_z_extend_or_bit_cast(
ndarray.load_ndims(ctx),
llvm_usize.size_of().get_type(),
"",
)
.unwrap();
let v_dims_src_ptr = unsafe {
v.shape().ptr_offset_unchecked(
ctx,
generator,
&llvm_usize.const_int(1, false),
None,
)
};
call_memcpy_generic(
ctx,
ndarray.shape().base_ptr(ctx, generator),
v_dims_src_ptr,
ctx.builder
.build_int_mul(ndarray_num_dims, llvm_usize.size_of(), "")
.map(Into::into)
.unwrap(),
llvm_i1.const_zero(),
);
let ndarray_num_elems = ndarray::call_ndarray_calc_size(
generator,
ctx,
&ndarray.shape().as_slice_value(ctx, generator),
(None, None),
);
let ndarray_num_elems = ctx
.builder
.build_int_z_extend_or_bit_cast(ndarray_num_elems, sizeof_elem.get_type(), "")
.unwrap();
unsafe { ndarray.create_data(generator, ctx) };
let v_data_src_ptr = v.data().ptr_offset(ctx, generator, &index_addr, None);
call_memcpy_generic(
ctx,
ndarray.data().base_ptr(ctx, generator),
v_data_src_ptr,
ctx.builder
.build_int_mul(
ndarray_num_elems,
llvm_ndarray_data_t.size_of().unwrap(),
"",
)
.map(Into::into)
.unwrap(),
llvm_i1.const_zero(),
);
ndarray.as_base_value().into()
}
}
}))
}
/// See [`CodeGenerator::gen_expr`]. /// See [`CodeGenerator::gen_expr`].
pub fn gen_expr<'ctx, G: CodeGenerator>( pub fn gen_expr<'ctx, G: CodeGenerator>(
generator: &mut G, generator: &mut G,
@ -2481,20 +2939,7 @@ pub fn gen_expr<'ctx, G: CodeGenerator>(
Some(elements[0].get_type()) Some(elements[0].get_type())
}; };
let length = generator.get_size_type(ctx.ctx).const_int(elements.len() as u64, false); let length = generator.get_size_type(ctx.ctx).const_int(elements.len() as u64, false);
let arr_str_ptr = if let Some(ty) = ty { let arr_str_ptr = allocate_list(generator, ctx, ty, length, Some("list"));
ListType::new(generator, ctx.ctx, ty).construct(
generator,
ctx,
length,
Some("list"),
)
} else {
ListType::new_untyped(generator, ctx.ctx).construct_empty(
generator,
ctx,
Some("list"),
)
};
let arr_ptr = arr_str_ptr.data(); let arr_ptr = arr_str_ptr.data();
for (i, v) in elements.iter().enumerate() { for (i, v) in elements.iter().enumerate() {
let elem_ptr = arr_ptr.ptr_offset( let elem_ptr = arr_ptr.ptr_offset(
@ -2972,12 +3417,8 @@ pub fn gen_expr<'ctx, G: CodeGenerator>(
.unwrap(), .unwrap(),
step, step,
); );
let res_array_ret = ListType::new(generator, ctx.ctx, ty).construct( let res_array_ret =
generator, allocate_list(generator, ctx, Some(ty), length, Some("ret"));
ctx,
length,
Some("ret"),
);
let Some(res_ind) = handle_slice_indices( let Some(res_ind) = handle_slice_indices(
&None, &None,
&None, &None,

View File

@ -17,7 +17,6 @@ pub trait CodeGenerator {
/// Return the module name for the code generator. /// Return the module name for the code generator.
fn get_name(&self) -> &str; fn get_name(&self) -> &str;
/// Return an instance of [`IntType`] corresponding to the type of `size_t` for this instance.
fn get_size_type<'ctx>(&self, ctx: &'ctx Context) -> IntType<'ctx>; fn get_size_type<'ctx>(&self, ctx: &'ctx Context) -> IntType<'ctx>;
/// Generate function call and returns the function return value. /// Generate function call and returns the function return value.

View File

@ -24,52 +24,42 @@ pub fn list_slice_assignment<'ctx, G: CodeGenerator + ?Sized>(
src_arr: ListValue<'ctx>, src_arr: ListValue<'ctx>,
src_idx: (IntValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>), src_idx: (IntValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>),
) { ) {
let llvm_usize = generator.get_size_type(ctx.ctx); let size_ty = generator.get_size_type(ctx.ctx);
let llvm_pi8 = ctx.ctx.i8_type().ptr_type(AddressSpace::default()); let int8_ptr = ctx.ctx.i8_type().ptr_type(AddressSpace::default());
let llvm_i32 = ctx.ctx.i32_type(); let int32 = ctx.ctx.i32_type();
let (fun_symbol, elem_ptr_type) = ("__nac3_list_slice_assign_var_size", int8_ptr);
assert_eq!(dest_idx.0.get_type(), llvm_i32);
assert_eq!(dest_idx.1.get_type(), llvm_i32);
assert_eq!(dest_idx.2.get_type(), llvm_i32);
assert_eq!(src_idx.0.get_type(), llvm_i32);
assert_eq!(src_idx.1.get_type(), llvm_i32);
assert_eq!(src_idx.2.get_type(), llvm_i32);
let (fun_symbol, elem_ptr_type) = ("__nac3_list_slice_assign_var_size", llvm_pi8);
let slice_assign_fun = { let slice_assign_fun = {
let ty_vec = vec![ let ty_vec = vec![
llvm_i32.into(), // dest start idx int32.into(), // dest start idx
llvm_i32.into(), // dest end idx int32.into(), // dest end idx
llvm_i32.into(), // dest step int32.into(), // dest step
elem_ptr_type.into(), // dest arr ptr elem_ptr_type.into(), // dest arr ptr
llvm_i32.into(), // dest arr len int32.into(), // dest arr len
llvm_i32.into(), // src start idx int32.into(), // src start idx
llvm_i32.into(), // src end idx int32.into(), // src end idx
llvm_i32.into(), // src step int32.into(), // src step
elem_ptr_type.into(), // src arr ptr elem_ptr_type.into(), // src arr ptr
llvm_i32.into(), // src arr len int32.into(), // src arr len
llvm_i32.into(), // size int32.into(), // size
]; ];
ctx.module.get_function(fun_symbol).unwrap_or_else(|| { ctx.module.get_function(fun_symbol).unwrap_or_else(|| {
let fn_t = llvm_i32.fn_type(ty_vec.as_slice(), false); let fn_t = int32.fn_type(ty_vec.as_slice(), false);
ctx.module.add_function(fun_symbol, fn_t, None) ctx.module.add_function(fun_symbol, fn_t, None)
}) })
}; };
let zero = llvm_i32.const_zero(); let zero = int32.const_zero();
let one = llvm_i32.const_int(1, false); let one = int32.const_int(1, false);
let dest_arr_ptr = dest_arr.data().base_ptr(ctx, generator); let dest_arr_ptr = dest_arr.data().base_ptr(ctx, generator);
let dest_arr_ptr = let dest_arr_ptr =
ctx.builder.build_pointer_cast(dest_arr_ptr, elem_ptr_type, "dest_arr_ptr_cast").unwrap(); ctx.builder.build_pointer_cast(dest_arr_ptr, elem_ptr_type, "dest_arr_ptr_cast").unwrap();
let dest_len = dest_arr.load_size(ctx, Some("dest.len")); let dest_len = dest_arr.load_size(ctx, Some("dest.len"));
let dest_len = let dest_len = ctx.builder.build_int_truncate_or_bit_cast(dest_len, int32, "srclen32").unwrap();
ctx.builder.build_int_truncate_or_bit_cast(dest_len, llvm_i32, "srclen32").unwrap();
let src_arr_ptr = src_arr.data().base_ptr(ctx, generator); let src_arr_ptr = src_arr.data().base_ptr(ctx, generator);
let src_arr_ptr = let src_arr_ptr =
ctx.builder.build_pointer_cast(src_arr_ptr, elem_ptr_type, "src_arr_ptr_cast").unwrap(); ctx.builder.build_pointer_cast(src_arr_ptr, elem_ptr_type, "src_arr_ptr_cast").unwrap();
let src_len = src_arr.load_size(ctx, Some("src.len")); let src_len = src_arr.load_size(ctx, Some("src.len"));
let src_len = let src_len = ctx.builder.build_int_truncate_or_bit_cast(src_len, int32, "srclen32").unwrap();
ctx.builder.build_int_truncate_or_bit_cast(src_len, llvm_i32, "srclen32").unwrap();
// index in bound and positive should be done // index in bound and positive should be done
// assert if dest.step == 1 then len(src) <= len(dest) else len(src) == len(dest), and // assert if dest.step == 1 then len(src) <= len(dest) else len(src) == len(dest), and
@ -146,7 +136,7 @@ pub fn list_slice_assignment<'ctx, G: CodeGenerator + ?Sized>(
BasicTypeEnum::StructType(t) => t.size_of().unwrap(), BasicTypeEnum::StructType(t) => t.size_of().unwrap(),
_ => codegen_unreachable!(ctx), _ => codegen_unreachable!(ctx),
}; };
ctx.builder.build_int_truncate_or_bit_cast(s, llvm_i32, "size").unwrap() ctx.builder.build_int_truncate_or_bit_cast(s, int32, "size").unwrap()
} }
.into(), .into(),
]; ];
@ -157,7 +147,6 @@ pub fn list_slice_assignment<'ctx, G: CodeGenerator + ?Sized>(
.map(Either::unwrap_left) .map(Either::unwrap_left)
.unwrap() .unwrap()
}; };
// update length // update length
let need_update = let need_update =
ctx.builder.build_int_compare(IntPredicate::NE, new_len, dest_len, "need_update").unwrap(); ctx.builder.build_int_compare(IntPredicate::NE, new_len, dest_len, "need_update").unwrap();
@ -166,8 +155,7 @@ pub fn list_slice_assignment<'ctx, G: CodeGenerator + ?Sized>(
let cont_bb = ctx.ctx.append_basic_block(current, "cont"); let cont_bb = ctx.ctx.append_basic_block(current, "cont");
ctx.builder.build_conditional_branch(need_update, update_bb, cont_bb).unwrap(); ctx.builder.build_conditional_branch(need_update, update_bb, cont_bb).unwrap();
ctx.builder.position_at_end(update_bb); ctx.builder.position_at_end(update_bb);
let new_len = let new_len = ctx.builder.build_int_z_extend_or_bit_cast(new_len, size_ty, "new_len").unwrap();
ctx.builder.build_int_z_extend_or_bit_cast(new_len, llvm_usize, "new_len").unwrap();
dest_arr.store_size(ctx, generator, new_len); dest_arr.store_size(ctx, generator, new_len);
ctx.builder.build_unconditional_branch(cont_bb).unwrap(); ctx.builder.build_unconditional_branch(cont_bb).unwrap();
ctx.builder.position_at_end(cont_bb); ctx.builder.position_at_end(cont_bb);

View File

@ -62,13 +62,8 @@ pub fn call_isinf<'ctx, G: CodeGenerator + ?Sized>(
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
v: FloatValue<'ctx>, v: FloatValue<'ctx>,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
let llvm_i32 = ctx.ctx.i32_type();
let llvm_f64 = ctx.ctx.f64_type();
assert_eq!(v.get_type(), llvm_f64);
let intrinsic_fn = ctx.module.get_function("__nac3_isinf").unwrap_or_else(|| { let intrinsic_fn = ctx.module.get_function("__nac3_isinf").unwrap_or_else(|| {
let fn_type = llvm_i32.fn_type(&[llvm_f64.into()], false); let fn_type = ctx.ctx.i32_type().fn_type(&[ctx.ctx.f64_type().into()], false);
ctx.module.add_function("__nac3_isinf", fn_type, None) ctx.module.add_function("__nac3_isinf", fn_type, None)
}); });
@ -89,13 +84,8 @@ pub fn call_isnan<'ctx, G: CodeGenerator + ?Sized>(
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
v: FloatValue<'ctx>, v: FloatValue<'ctx>,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
let llvm_i32 = ctx.ctx.i32_type();
let llvm_f64 = ctx.ctx.f64_type();
assert_eq!(v.get_type(), llvm_f64);
let intrinsic_fn = ctx.module.get_function("__nac3_isnan").unwrap_or_else(|| { let intrinsic_fn = ctx.module.get_function("__nac3_isnan").unwrap_or_else(|| {
let fn_type = llvm_i32.fn_type(&[llvm_f64.into()], false); let fn_type = ctx.ctx.i32_type().fn_type(&[ctx.ctx.f64_type().into()], false);
ctx.module.add_function("__nac3_isnan", fn_type, None) ctx.module.add_function("__nac3_isnan", fn_type, None)
}); });
@ -114,8 +104,6 @@ pub fn call_isnan<'ctx, G: CodeGenerator + ?Sized>(
pub fn call_gamma<'ctx>(ctx: &CodeGenContext<'ctx, '_>, v: FloatValue<'ctx>) -> FloatValue<'ctx> { pub fn call_gamma<'ctx>(ctx: &CodeGenContext<'ctx, '_>, v: FloatValue<'ctx>) -> FloatValue<'ctx> {
let llvm_f64 = ctx.ctx.f64_type(); let llvm_f64 = ctx.ctx.f64_type();
assert_eq!(v.get_type(), llvm_f64);
let intrinsic_fn = ctx.module.get_function("__nac3_gamma").unwrap_or_else(|| { let intrinsic_fn = ctx.module.get_function("__nac3_gamma").unwrap_or_else(|| {
let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false); let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false);
ctx.module.add_function("__nac3_gamma", fn_type, None) ctx.module.add_function("__nac3_gamma", fn_type, None)
@ -133,8 +121,6 @@ pub fn call_gamma<'ctx>(ctx: &CodeGenContext<'ctx, '_>, v: FloatValue<'ctx>) ->
pub fn call_gammaln<'ctx>(ctx: &CodeGenContext<'ctx, '_>, v: FloatValue<'ctx>) -> FloatValue<'ctx> { pub fn call_gammaln<'ctx>(ctx: &CodeGenContext<'ctx, '_>, v: FloatValue<'ctx>) -> FloatValue<'ctx> {
let llvm_f64 = ctx.ctx.f64_type(); let llvm_f64 = ctx.ctx.f64_type();
assert_eq!(v.get_type(), llvm_f64);
let intrinsic_fn = ctx.module.get_function("__nac3_gammaln").unwrap_or_else(|| { let intrinsic_fn = ctx.module.get_function("__nac3_gammaln").unwrap_or_else(|| {
let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false); let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false);
ctx.module.add_function("__nac3_gammaln", fn_type, None) ctx.module.add_function("__nac3_gammaln", fn_type, None)
@ -152,8 +138,6 @@ pub fn call_gammaln<'ctx>(ctx: &CodeGenContext<'ctx, '_>, v: FloatValue<'ctx>) -
pub fn call_j0<'ctx>(ctx: &CodeGenContext<'ctx, '_>, v: FloatValue<'ctx>) -> FloatValue<'ctx> { pub fn call_j0<'ctx>(ctx: &CodeGenContext<'ctx, '_>, v: FloatValue<'ctx>) -> FloatValue<'ctx> {
let llvm_f64 = ctx.ctx.f64_type(); let llvm_f64 = ctx.ctx.f64_type();
assert_eq!(v.get_type(), llvm_f64);
let intrinsic_fn = ctx.module.get_function("__nac3_j0").unwrap_or_else(|| { let intrinsic_fn = ctx.module.get_function("__nac3_j0").unwrap_or_else(|| {
let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false); let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false);
ctx.module.add_function("__nac3_j0", fn_type, None) ctx.module.add_function("__nac3_j0", fn_type, None)

View File

@ -15,14 +15,12 @@ pub use list::*;
pub use math::*; pub use math::*;
pub use range::*; pub use range::*;
pub use slice::*; pub use slice::*;
pub use string::*;
mod list; mod list;
mod math; mod math;
pub mod ndarray; pub mod ndarray;
mod range; mod range;
mod slice; mod slice;
mod string;
#[must_use] #[must_use]
pub fn load_irrt<'ctx>(ctx: &'ctx Context, symbol_resolver: &dyn SymbolResolver) -> Module<'ctx> { pub fn load_irrt<'ctx>(ctx: &'ctx Context, symbol_resolver: &dyn SymbolResolver) -> Module<'ctx> {
@ -132,11 +130,10 @@ pub fn handle_slice_indices<'ctx, G: CodeGenerator>(
generator: &mut G, generator: &mut G,
length: IntValue<'ctx>, length: IntValue<'ctx>,
) -> Result<Option<(IntValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>)>, String> { ) -> Result<Option<(IntValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>)>, String> {
let llvm_i32 = ctx.ctx.i32_type(); let int32 = ctx.ctx.i32_type();
let zero = int32.const_zero();
let zero = llvm_i32.const_zero(); let one = int32.const_int(1, false);
let one = llvm_i32.const_int(1, false); let length = ctx.builder.build_int_truncate_or_bit_cast(length, int32, "leni32").unwrap();
let length = ctx.builder.build_int_truncate_or_bit_cast(length, llvm_i32, "leni32").unwrap();
Ok(Some(match (start, end, step) { Ok(Some(match (start, end, step) {
(s, e, None) => ( (s, e, None) => (
if let Some(s) = s.as_ref() { if let Some(s) = s.as_ref() {
@ -145,7 +142,7 @@ pub fn handle_slice_indices<'ctx, G: CodeGenerator>(
None => return Ok(None), None => return Ok(None),
} }
} else { } else {
llvm_i32.const_zero() int32.const_zero()
}, },
{ {
let e = if let Some(s) = e.as_ref() { let e = if let Some(s) = e.as_ref() {

View File

@ -1,80 +0,0 @@
use inkwell::{types::BasicTypeEnum, values::IntValue};
use crate::codegen::{
expr::infer_and_call_function,
irrt::get_usize_dependent_function_name,
values::{ndarray::NDArrayValue, ListValue, ProxyValue, TypedArrayLikeAccessor},
CodeGenContext, CodeGenerator,
};
/// Generates a call to `__nac3_ndarray_array_set_and_validate_list_shape`.
///
/// Deduces the target shape of the `ndarray` from the provided `list`, raising an exception if
/// there is any issue with the resultant `shape`.
///
/// `shape` must be pre-allocated by the caller of this function to `[usize; ndims]`, and must be
/// initialized to all `-1`s.
pub fn call_nac3_ndarray_array_set_and_validate_list_shape<'ctx, G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
list: ListValue<'ctx>,
ndims: IntValue<'ctx>,
shape: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>,
) {
let llvm_usize = generator.get_size_type(ctx.ctx);
assert_eq!(list.get_type().element_type().unwrap(), ctx.ctx.i8_type().into());
assert_eq!(ndims.get_type(), llvm_usize);
assert_eq!(
BasicTypeEnum::try_from(shape.element_type(ctx, generator)).unwrap(),
llvm_usize.into()
);
let name = get_usize_dependent_function_name(
generator,
ctx,
"__nac3_ndarray_array_set_and_validate_list_shape",
);
infer_and_call_function(
ctx,
&name,
None,
&[list.as_base_value().into(), ndims.into(), shape.base_ptr(ctx, generator).into()],
None,
None,
);
}
/// Generates a call to `__nac3_ndarray_array_write_list_to_array`.
///
/// Copies the contents stored in `list` into `ndarray`.
///
/// The `ndarray` must fulfill the following preconditions:
///
/// - `ndarray.itemsize`: Must be initialized.
/// - `ndarray.ndims`: Must be initialized.
/// - `ndarray.shape`: Must be initialized.
/// - `ndarray.data`: Must be allocated and contiguous.
pub fn call_nac3_ndarray_array_write_list_to_array<'ctx, G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
list: ListValue<'ctx>,
ndarray: NDArrayValue<'ctx>,
) {
assert_eq!(list.get_type().element_type().unwrap(), ctx.ctx.i8_type().into());
let name = get_usize_dependent_function_name(
generator,
ctx,
"__nac3_ndarray_array_write_list_to_array",
);
infer_and_call_function(
ctx,
&name,
None,
&[list.as_base_value().into(), ndarray.as_base_value().into()],
None,
None,
);
}

View File

@ -1,5 +1,4 @@
use inkwell::{ use inkwell::{
types::BasicTypeEnum,
values::{BasicValueEnum, IntValue, PointerValue}, values::{BasicValueEnum, IntValue, PointerValue},
AddressSpace, AddressSpace,
}; };
@ -8,26 +7,19 @@ use crate::codegen::{
expr::{create_and_call_function, infer_and_call_function}, expr::{create_and_call_function, infer_and_call_function},
irrt::get_usize_dependent_function_name, irrt::get_usize_dependent_function_name,
types::ProxyType, types::ProxyType,
values::{ndarray::NDArrayValue, ProxyValue, TypedArrayLikeAccessor}, values::{ndarray::NDArrayValue, ProxyValue},
CodeGenContext, CodeGenerator, CodeGenContext, CodeGenerator,
}; };
/// Generates a call to `__nac3_ndarray_util_assert_shape_no_negative`.
///
/// Assets that `shape` does not contain negative dimensions.
pub fn call_nac3_ndarray_util_assert_shape_no_negative<'ctx, G: CodeGenerator + ?Sized>( pub fn call_nac3_ndarray_util_assert_shape_no_negative<'ctx, G: CodeGenerator + ?Sized>(
generator: &G, generator: &G,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
shape: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>, ndims: IntValue<'ctx>,
shape: PointerValue<'ctx>,
) { ) {
let llvm_usize = generator.get_size_type(ctx.ctx); let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default()); let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
assert_eq!(
BasicTypeEnum::try_from(shape.element_type(ctx, generator)).unwrap(),
llvm_usize.into()
);
let name = get_usize_dependent_function_name( let name = get_usize_dependent_function_name(
generator, generator,
ctx, ctx,
@ -38,37 +30,23 @@ pub fn call_nac3_ndarray_util_assert_shape_no_negative<'ctx, G: CodeGenerator +
ctx, ctx,
&name, &name,
Some(llvm_usize.into()), Some(llvm_usize.into()),
&[ &[(llvm_usize.into(), ndims.into()), (llvm_pusize.into(), shape.into())],
(llvm_usize.into(), shape.size(ctx, generator).into()),
(llvm_pusize.into(), shape.base_ptr(ctx, generator).into()),
],
None, None,
None, None,
); );
} }
/// Generates a call to `__nac3_ndarray_util_assert_shape_output_shape_same`.
///
/// Asserts that `ndarray_shape` and `output_shape` are the same in the context of writing output to
/// an `ndarray`.
pub fn call_nac3_ndarray_util_assert_output_shape_same<'ctx, G: CodeGenerator + ?Sized>( pub fn call_nac3_ndarray_util_assert_output_shape_same<'ctx, G: CodeGenerator + ?Sized>(
generator: &G, generator: &G,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
ndarray_shape: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>, ndarray_ndims: IntValue<'ctx>,
output_shape: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>, ndarray_shape: PointerValue<'ctx>,
output_ndims: IntValue<'ctx>,
output_shape: IntValue<'ctx>,
) { ) {
let llvm_usize = generator.get_size_type(ctx.ctx); let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default()); let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
assert_eq!(
BasicTypeEnum::try_from(ndarray_shape.element_type(ctx, generator)).unwrap(),
llvm_usize.into()
);
assert_eq!(
BasicTypeEnum::try_from(output_shape.element_type(ctx, generator)).unwrap(),
llvm_usize.into()
);
let name = get_usize_dependent_function_name( let name = get_usize_dependent_function_name(
generator, generator,
ctx, ctx,
@ -80,20 +58,16 @@ pub fn call_nac3_ndarray_util_assert_output_shape_same<'ctx, G: CodeGenerator +
&name, &name,
Some(llvm_usize.into()), Some(llvm_usize.into()),
&[ &[
(llvm_usize.into(), ndarray_shape.size(ctx, generator).into()), (llvm_usize.into(), ndarray_ndims.into()),
(llvm_pusize.into(), ndarray_shape.base_ptr(ctx, generator).into()), (llvm_pusize.into(), ndarray_shape.into()),
(llvm_usize.into(), output_shape.size(ctx, generator).into()), (llvm_usize.into(), output_ndims.into()),
(llvm_pusize.into(), output_shape.base_ptr(ctx, generator).into()), (llvm_pusize.into(), output_shape.into()),
], ],
None, None,
None, None,
); );
} }
/// Generates a call to `__nac3_ndarray_size`.
///
/// Returns a [`usize`][CodeGenerator::get_size_type] value of the number of elements of an
/// `ndarray`, corresponding to the value of `ndarray.size`.
pub fn call_nac3_ndarray_size<'ctx, G: CodeGenerator + ?Sized>( pub fn call_nac3_ndarray_size<'ctx, G: CodeGenerator + ?Sized>(
generator: &G, generator: &G,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
@ -116,10 +90,6 @@ pub fn call_nac3_ndarray_size<'ctx, G: CodeGenerator + ?Sized>(
.unwrap() .unwrap()
} }
/// Generates a call to `__nac3_ndarray_nbytes`.
///
/// Returns a [`usize`][CodeGenerator::get_size_type] value of the number of bytes consumed by the
/// data of the `ndarray`, corresponding to the value of `ndarray.nbytes`.
pub fn call_nac3_ndarray_nbytes<'ctx, G: CodeGenerator + ?Sized>( pub fn call_nac3_ndarray_nbytes<'ctx, G: CodeGenerator + ?Sized>(
generator: &G, generator: &G,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
@ -142,10 +112,6 @@ pub fn call_nac3_ndarray_nbytes<'ctx, G: CodeGenerator + ?Sized>(
.unwrap() .unwrap()
} }
/// Generates a call to `__nac3_ndarray_len`.
///
/// Returns a [`usize`][CodeGenerator::get_size_type] value of the size of the topmost dimension of
/// the `ndarray`, corresponding to the value of `ndarray.__len__`.
pub fn call_nac3_ndarray_len<'ctx, G: CodeGenerator + ?Sized>( pub fn call_nac3_ndarray_len<'ctx, G: CodeGenerator + ?Sized>(
generator: &G, generator: &G,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
@ -168,9 +134,6 @@ pub fn call_nac3_ndarray_len<'ctx, G: CodeGenerator + ?Sized>(
.unwrap() .unwrap()
} }
/// Generates a call to `__nac3_ndarray_is_c_contiguous`.
///
/// Returns an `i1` value indicating whether the `ndarray` is C-contiguous.
pub fn call_nac3_ndarray_is_c_contiguous<'ctx, G: CodeGenerator + ?Sized>( pub fn call_nac3_ndarray_is_c_contiguous<'ctx, G: CodeGenerator + ?Sized>(
generator: &G, generator: &G,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
@ -193,9 +156,6 @@ pub fn call_nac3_ndarray_is_c_contiguous<'ctx, G: CodeGenerator + ?Sized>(
.unwrap() .unwrap()
} }
/// Generates a call to `__nac3_ndarray_get_nth_pelement`.
///
/// Returns a [`PointerValue`] to the `index`-th flattened element of the `ndarray`.
pub fn call_nac3_ndarray_get_nth_pelement<'ctx, G: CodeGenerator + ?Sized>( pub fn call_nac3_ndarray_get_nth_pelement<'ctx, G: CodeGenerator + ?Sized>(
generator: &G, generator: &G,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
@ -207,8 +167,6 @@ pub fn call_nac3_ndarray_get_nth_pelement<'ctx, G: CodeGenerator + ?Sized>(
let llvm_usize = generator.get_size_type(ctx.ctx); let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_ndarray = ndarray.get_type().as_base_type(); let llvm_ndarray = ndarray.get_type().as_base_type();
assert_eq!(index.get_type(), llvm_usize);
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_get_nth_pelement"); let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_get_nth_pelement");
create_and_call_function( create_and_call_function(
@ -223,16 +181,11 @@ pub fn call_nac3_ndarray_get_nth_pelement<'ctx, G: CodeGenerator + ?Sized>(
.unwrap() .unwrap()
} }
/// Generates a call to `__nac3_ndarray_get_pelement_by_indices`.
///
/// `indices` must have the same number of elements as the number of dimensions in `ndarray`.
///
/// Returns a [`PointerValue`] to the element indexed by `indices`.
pub fn call_nac3_ndarray_get_pelement_by_indices<'ctx, G: CodeGenerator + ?Sized>( pub fn call_nac3_ndarray_get_pelement_by_indices<'ctx, G: CodeGenerator + ?Sized>(
generator: &G, generator: &G,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
ndarray: NDArrayValue<'ctx>, ndarray: NDArrayValue<'ctx>,
indices: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>, indices: PointerValue<'ctx>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
let llvm_i8 = ctx.ctx.i8_type(); let llvm_i8 = ctx.ctx.i8_type();
let llvm_pi8 = llvm_i8.ptr_type(AddressSpace::default()); let llvm_pi8 = llvm_i8.ptr_type(AddressSpace::default());
@ -240,11 +193,6 @@ pub fn call_nac3_ndarray_get_pelement_by_indices<'ctx, G: CodeGenerator + ?Sized
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default()); let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
let llvm_ndarray = ndarray.get_type().as_base_type(); let llvm_ndarray = ndarray.get_type().as_base_type();
assert_eq!(
BasicTypeEnum::try_from(indices.element_type(ctx, generator)).unwrap(),
llvm_usize.into()
);
let name = let name =
get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_get_pelement_by_indices"); get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_get_pelement_by_indices");
@ -254,7 +202,7 @@ pub fn call_nac3_ndarray_get_pelement_by_indices<'ctx, G: CodeGenerator + ?Sized
Some(llvm_pi8.into()), Some(llvm_pi8.into()),
&[ &[
(llvm_ndarray.into(), ndarray.as_base_value().into()), (llvm_ndarray.into(), ndarray.as_base_value().into()),
(llvm_pusize.into(), indices.base_ptr(ctx, generator).into()), (llvm_pusize.into(), indices.into()),
], ],
Some("pelement"), Some("pelement"),
None, None,
@ -263,9 +211,6 @@ pub fn call_nac3_ndarray_get_pelement_by_indices<'ctx, G: CodeGenerator + ?Sized
.unwrap() .unwrap()
} }
/// Generates a call to `__nac3_ndarray_set_strides_by_shape`.
///
/// Sets `ndarray.strides` assuming that `ndarray.shape` is C-contiguous.
pub fn call_nac3_ndarray_set_strides_by_shape<'ctx, G: CodeGenerator + ?Sized>( pub fn call_nac3_ndarray_set_strides_by_shape<'ctx, G: CodeGenerator + ?Sized>(
generator: &G, generator: &G,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
@ -286,11 +231,6 @@ pub fn call_nac3_ndarray_set_strides_by_shape<'ctx, G: CodeGenerator + ?Sized>(
); );
} }
/// Generates a call to `__nac3_ndarray_copy_data`.
///
/// Copies all elements from `src_ndarray` to `dst_ndarray` using their flattened views. The number
/// of elements in `src_ndarray` must be greater than or equal to the number of elements in
/// `dst_ndarray`.
pub fn call_nac3_ndarray_copy_data<'ctx, G: CodeGenerator + ?Sized>( pub fn call_nac3_ndarray_copy_data<'ctx, G: CodeGenerator + ?Sized>(
generator: &G, generator: &G,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,

View File

@ -1,82 +0,0 @@
use inkwell::values::IntValue;
use crate::codegen::{
expr::infer_and_call_function,
irrt::get_usize_dependent_function_name,
types::{ndarray::ShapeEntryType, ProxyType},
values::{
ndarray::NDArrayValue, ArrayLikeValue, ArraySliceValue, ProxyValue, TypedArrayLikeAccessor,
TypedArrayLikeMutator,
},
CodeGenContext, CodeGenerator,
};
/// Generates a call to `__nac3_ndarray_broadcast_to`.
///
/// Attempts to broadcast `src_ndarray` to the new shape defined by `dst_ndarray`.
///
/// `dst_ndarray` must meet the following preconditions:
///
/// - `dst_ndarray.ndims` must be initialized and matching the length of `dst_ndarray.shape`.
/// - `dst_ndarray.shape` must be initialized and contains the target broadcast shape.
/// - `dst_ndarray.strides` must be allocated and may contain uninitialized values.
pub fn call_nac3_ndarray_broadcast_to<'ctx, G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
src_ndarray: NDArrayValue<'ctx>,
dst_ndarray: NDArrayValue<'ctx>,
) {
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_broadcast_to");
infer_and_call_function(
ctx,
&name,
None,
&[src_ndarray.as_base_value().into(), dst_ndarray.as_base_value().into()],
None,
None,
);
}
/// Generates a call to `__nac3_ndarray_broadcast_shapes`.
///
/// Attempts to calculate the resultant shape from broadcasting all shapes in `shape_entries`,
/// writing the result to `dst_shape`.
pub fn call_nac3_ndarray_broadcast_shapes<'ctx, G, Shape>(
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
num_shape_entries: IntValue<'ctx>,
shape_entries: ArraySliceValue<'ctx>,
dst_ndims: IntValue<'ctx>,
dst_shape: &Shape,
) where
G: CodeGenerator + ?Sized,
Shape: TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>
+ TypedArrayLikeMutator<'ctx, G, IntValue<'ctx>>,
{
let llvm_usize = generator.get_size_type(ctx.ctx);
assert_eq!(num_shape_entries.get_type(), llvm_usize);
assert!(ShapeEntryType::is_type(
generator,
ctx.ctx,
shape_entries.base_ptr(ctx, generator).get_type()
)
.is_ok());
assert_eq!(dst_ndims.get_type(), llvm_usize);
assert_eq!(dst_shape.element_type(ctx, generator), llvm_usize.into());
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_broadcast_shapes");
infer_and_call_function(
ctx,
&name,
None,
&[
num_shape_entries.into(),
shape_entries.base_ptr(ctx, generator).into(),
dst_ndims.into(),
dst_shape.base_ptr(ctx, generator).into(),
],
None,
None,
);
}

View File

@ -5,11 +5,6 @@ use crate::codegen::{
CodeGenContext, CodeGenerator, CodeGenContext, CodeGenerator,
}; };
/// Generates a call to `__nac3_ndarray_index`.
///
/// Performs [basic indexing](https://numpy.org/doc/stable/user/basics.indexing.html#basic-indexing)
/// on `src_ndarray` using `indices`, writing the result to `dst_ndarray`, corresponding to the
/// operation `dst_ndarray = src_ndarray[indices]`.
pub fn call_nac3_ndarray_index<'ctx, G: CodeGenerator + ?Sized>( pub fn call_nac3_ndarray_index<'ctx, G: CodeGenerator + ?Sized>(
generator: &G, generator: &G,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,

View File

@ -1,5 +1,4 @@
use inkwell::{ use inkwell::{
types::BasicTypeEnum,
values::{BasicValueEnum, IntValue}, values::{BasicValueEnum, IntValue},
AddressSpace, AddressSpace,
}; };
@ -10,29 +9,21 @@ use crate::codegen::{
types::ProxyType, types::ProxyType,
values::{ values::{
ndarray::{NDArrayValue, NDIterValue}, ndarray::{NDArrayValue, NDIterValue},
ProxyValue, TypedArrayLikeAccessor, ArrayLikeValue, ArraySliceValue, ProxyValue,
}, },
CodeGenContext, CodeGenerator, CodeGenContext, CodeGenerator,
}; };
/// Generates a call to `__nac3_nditer_initialize`.
///
/// Initializes the `iter` object.
pub fn call_nac3_nditer_initialize<'ctx, G: CodeGenerator + ?Sized>( pub fn call_nac3_nditer_initialize<'ctx, G: CodeGenerator + ?Sized>(
generator: &G, generator: &G,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
iter: NDIterValue<'ctx>, iter: NDIterValue<'ctx>,
ndarray: NDArrayValue<'ctx>, ndarray: NDArrayValue<'ctx>,
indices: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>, indices: ArraySliceValue<'ctx>,
) { ) {
let llvm_usize = generator.get_size_type(ctx.ctx); let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default()); let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
assert_eq!(
BasicTypeEnum::try_from(indices.element_type(ctx, generator)).unwrap(),
llvm_usize.into()
);
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_nditer_initialize"); let name = get_usize_dependent_function_name(generator, ctx, "__nac3_nditer_initialize");
create_and_call_function( create_and_call_function(
@ -49,10 +40,6 @@ pub fn call_nac3_nditer_initialize<'ctx, G: CodeGenerator + ?Sized>(
); );
} }
/// Generates a call to `__nac3_nditer_initialize_has_element`.
///
/// Returns an `i1` value indicating whether there are elements left to traverse for the `iter`
/// object.
pub fn call_nac3_nditer_has_element<'ctx, G: CodeGenerator + ?Sized>( pub fn call_nac3_nditer_has_element<'ctx, G: CodeGenerator + ?Sized>(
generator: &G, generator: &G,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
@ -72,9 +59,6 @@ pub fn call_nac3_nditer_has_element<'ctx, G: CodeGenerator + ?Sized>(
.unwrap() .unwrap()
} }
/// Generates a call to `__nac3_nditer_next`.
///
/// Moves `iter` to point to the next element.
pub fn call_nac3_nditer_next<'ctx, G: CodeGenerator + ?Sized>( pub fn call_nac3_nditer_next<'ctx, G: CodeGenerator + ?Sized>(
generator: &G, generator: &G,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,

View File

@ -1,66 +0,0 @@
use inkwell::{types::BasicTypeEnum, values::IntValue};
use crate::codegen::{
expr::infer_and_call_function, irrt::get_usize_dependent_function_name,
values::TypedArrayLikeAccessor, CodeGenContext, CodeGenerator,
};
/// Generates a call to `__nac3_ndarray_matmul_calculate_shapes`.
///
/// Calculates the broadcasted shapes for `a`, `b`, and the `ndarray` holding the final values of
/// `a @ b`.
#[allow(clippy::too_many_arguments)]
pub fn call_nac3_ndarray_matmul_calculate_shapes<'ctx, G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
a_shape: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>,
b_shape: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>,
final_ndims: IntValue<'ctx>,
new_a_shape: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>,
new_b_shape: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>,
dst_shape: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>,
) {
let llvm_usize = generator.get_size_type(ctx.ctx);
assert_eq!(
BasicTypeEnum::try_from(a_shape.element_type(ctx, generator)).unwrap(),
llvm_usize.into()
);
assert_eq!(
BasicTypeEnum::try_from(b_shape.element_type(ctx, generator)).unwrap(),
llvm_usize.into()
);
assert_eq!(
BasicTypeEnum::try_from(new_a_shape.element_type(ctx, generator)).unwrap(),
llvm_usize.into()
);
assert_eq!(
BasicTypeEnum::try_from(new_b_shape.element_type(ctx, generator)).unwrap(),
llvm_usize.into()
);
assert_eq!(
BasicTypeEnum::try_from(dst_shape.element_type(ctx, generator)).unwrap(),
llvm_usize.into()
);
let name =
get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_matmul_calculate_shapes");
infer_and_call_function(
ctx,
&name,
None,
&[
a_shape.size(ctx, generator).into(),
a_shape.base_ptr(ctx, generator).into(),
b_shape.size(ctx, generator).into(),
b_shape.base_ptr(ctx, generator).into(),
final_ndims.into(),
new_a_shape.base_ptr(ctx, generator).into(),
new_b_shape.base_ptr(ctx, generator).into(),
dst_shape.base_ptr(ctx, generator).into(),
],
None,
None,
);
}

View File

@ -1,17 +1,391 @@
pub use array::*; use inkwell::{
types::IntType,
values::{BasicValueEnum, CallSiteValue, IntValue},
AddressSpace, IntPredicate,
};
use itertools::Either;
use crate::codegen::{
llvm_intrinsics,
macros::codegen_unreachable,
stmt::gen_for_callback_incrementing,
values::{
ndarray::NDArrayValue, ArrayLikeIndexer, ArrayLikeValue, ArraySliceValue,
TypedArrayLikeAccessor, TypedArrayLikeAdapter, UntypedArrayLikeAccessor,
},
CodeGenContext, CodeGenerator,
};
pub use basic::*; pub use basic::*;
pub use broadcast::*;
pub use indexing::*; pub use indexing::*;
pub use iter::*; pub use iter::*;
pub use matmul::*;
pub use reshape::*;
pub use transpose::*;
mod array;
mod basic; mod basic;
mod broadcast;
mod indexing; mod indexing;
mod iter; mod iter;
mod matmul;
mod reshape; /// Generates a call to `__nac3_ndarray_calc_size`. Returns an [`IntValue`] representing the
mod transpose; /// calculated total size.
///
/// * `dims` - An [`ArrayLikeIndexer`] containing the size of each dimension.
/// * `range` - The dimension index to begin and end (exclusively) calculating the dimensions for,
/// or [`None`] if starting from the first dimension and ending at the last dimension
/// respectively.
pub fn call_ndarray_calc_size<'ctx, G, Dims>(
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
dims: &Dims,
(begin, end): (Option<IntValue<'ctx>>, Option<IntValue<'ctx>>),
) -> IntValue<'ctx>
where
G: CodeGenerator + ?Sized,
Dims: ArrayLikeIndexer<'ctx>,
{
let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
let ndarray_calc_size_fn_name = match llvm_usize.get_bit_width() {
32 => "__nac3_ndarray_calc_size",
64 => "__nac3_ndarray_calc_size64",
bw => codegen_unreachable!(ctx, "Unsupported size type bit width: {}", bw),
};
let ndarray_calc_size_fn_t = llvm_usize.fn_type(
&[llvm_pusize.into(), llvm_usize.into(), llvm_usize.into(), llvm_usize.into()],
false,
);
let ndarray_calc_size_fn =
ctx.module.get_function(ndarray_calc_size_fn_name).unwrap_or_else(|| {
ctx.module.add_function(ndarray_calc_size_fn_name, ndarray_calc_size_fn_t, None)
});
let begin = begin.unwrap_or_else(|| llvm_usize.const_zero());
let end = end.unwrap_or_else(|| dims.size(ctx, generator));
ctx.builder
.build_call(
ndarray_calc_size_fn,
&[
dims.base_ptr(ctx, generator).into(),
dims.size(ctx, generator).into(),
begin.into(),
end.into(),
],
"",
)
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_int_value))
.map(Either::unwrap_left)
.unwrap()
}
/// Generates a call to `__nac3_ndarray_calc_nd_indices`. Returns a [`TypeArrayLikeAdpater`]
/// containing `i32` indices of the flattened index.
///
/// * `index` - The index to compute the multidimensional index for.
/// * `ndarray` - LLVM pointer to the `NDArray`. This value must be the LLVM representation of an
/// `NDArray`.
pub fn call_ndarray_calc_nd_indices<'ctx, G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
index: IntValue<'ctx>,
ndarray: NDArrayValue<'ctx>,
) -> TypedArrayLikeAdapter<'ctx, IntValue<'ctx>> {
let llvm_void = ctx.ctx.void_type();
let llvm_i32 = ctx.ctx.i32_type();
let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_pi32 = llvm_i32.ptr_type(AddressSpace::default());
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
let ndarray_calc_nd_indices_fn_name = match llvm_usize.get_bit_width() {
32 => "__nac3_ndarray_calc_nd_indices",
64 => "__nac3_ndarray_calc_nd_indices64",
bw => codegen_unreachable!(ctx, "Unsupported size type bit width: {}", bw),
};
let ndarray_calc_nd_indices_fn =
ctx.module.get_function(ndarray_calc_nd_indices_fn_name).unwrap_or_else(|| {
let fn_type = llvm_void.fn_type(
&[llvm_usize.into(), llvm_pusize.into(), llvm_usize.into(), llvm_pi32.into()],
false,
);
ctx.module.add_function(ndarray_calc_nd_indices_fn_name, fn_type, None)
});
let ndarray_num_dims = ndarray.load_ndims(ctx);
let ndarray_dims = ndarray.shape();
let indices = ctx.builder.build_array_alloca(llvm_i32, ndarray_num_dims, "").unwrap();
ctx.builder
.build_call(
ndarray_calc_nd_indices_fn,
&[
index.into(),
ndarray_dims.base_ptr(ctx, generator).into(),
ndarray_num_dims.into(),
indices.into(),
],
"",
)
.unwrap();
TypedArrayLikeAdapter::from(
ArraySliceValue::from_ptr_val(indices, ndarray_num_dims, None),
Box::new(|_, v| v.into_int_value()),
Box::new(|_, v| v.into()),
)
}
fn call_ndarray_flatten_index_impl<'ctx, G, Indices>(
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
ndarray: NDArrayValue<'ctx>,
indices: &Indices,
) -> IntValue<'ctx>
where
G: CodeGenerator + ?Sized,
Indices: ArrayLikeIndexer<'ctx>,
{
let llvm_i32 = ctx.ctx.i32_type();
let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_pi32 = llvm_i32.ptr_type(AddressSpace::default());
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
debug_assert_eq!(
IntType::try_from(indices.element_type(ctx, generator))
.map(IntType::get_bit_width)
.unwrap_or_default(),
llvm_i32.get_bit_width(),
"Expected i32 value for argument `indices` to `call_ndarray_flatten_index_impl`"
);
debug_assert_eq!(
indices.size(ctx, generator).get_type().get_bit_width(),
llvm_usize.get_bit_width(),
"Expected usize integer value for argument `indices_size` to `call_ndarray_flatten_index_impl`"
);
let ndarray_flatten_index_fn_name = match llvm_usize.get_bit_width() {
32 => "__nac3_ndarray_flatten_index",
64 => "__nac3_ndarray_flatten_index64",
bw => codegen_unreachable!(ctx, "Unsupported size type bit width: {}", bw),
};
let ndarray_flatten_index_fn =
ctx.module.get_function(ndarray_flatten_index_fn_name).unwrap_or_else(|| {
let fn_type = llvm_usize.fn_type(
&[llvm_pusize.into(), llvm_usize.into(), llvm_pi32.into(), llvm_usize.into()],
false,
);
ctx.module.add_function(ndarray_flatten_index_fn_name, fn_type, None)
});
let ndarray_num_dims = ndarray.load_ndims(ctx);
let ndarray_dims = ndarray.shape();
let index = ctx
.builder
.build_call(
ndarray_flatten_index_fn,
&[
ndarray_dims.base_ptr(ctx, generator).into(),
ndarray_num_dims.into(),
indices.base_ptr(ctx, generator).into(),
indices.size(ctx, generator).into(),
],
"",
)
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_int_value))
.map(Either::unwrap_left)
.unwrap();
index
}
/// Generates a call to `__nac3_ndarray_flatten_index`. Returns the flattened index for the
/// multidimensional index.
///
/// * `ndarray` - LLVM pointer to the `NDArray`. This value must be the LLVM representation of an
/// `NDArray`.
/// * `indices` - The multidimensional index to compute the flattened index for.
pub fn call_ndarray_flatten_index<'ctx, G, Index>(
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
ndarray: NDArrayValue<'ctx>,
indices: &Index,
) -> IntValue<'ctx>
where
G: CodeGenerator + ?Sized,
Index: ArrayLikeIndexer<'ctx>,
{
call_ndarray_flatten_index_impl(generator, ctx, ndarray, indices)
}
/// Generates a call to `__nac3_ndarray_calc_broadcast`. Returns a tuple containing the number of
/// dimension and size of each dimension of the resultant `ndarray`.
pub fn call_ndarray_calc_broadcast<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
lhs: NDArrayValue<'ctx>,
rhs: NDArrayValue<'ctx>,
) -> TypedArrayLikeAdapter<'ctx, IntValue<'ctx>> {
let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
let ndarray_calc_broadcast_fn_name = match llvm_usize.get_bit_width() {
32 => "__nac3_ndarray_calc_broadcast",
64 => "__nac3_ndarray_calc_broadcast64",
bw => codegen_unreachable!(ctx, "Unsupported size type bit width: {}", bw),
};
let ndarray_calc_broadcast_fn =
ctx.module.get_function(ndarray_calc_broadcast_fn_name).unwrap_or_else(|| {
let fn_type = llvm_usize.fn_type(
&[
llvm_pusize.into(),
llvm_usize.into(),
llvm_pusize.into(),
llvm_usize.into(),
llvm_pusize.into(),
],
false,
);
ctx.module.add_function(ndarray_calc_broadcast_fn_name, fn_type, None)
});
let lhs_ndims = lhs.load_ndims(ctx);
let rhs_ndims = rhs.load_ndims(ctx);
let min_ndims = llvm_intrinsics::call_int_umin(ctx, lhs_ndims, rhs_ndims, None);
gen_for_callback_incrementing(
generator,
ctx,
None,
llvm_usize.const_zero(),
(min_ndims, false),
|generator, ctx, _, idx| {
let idx = ctx.builder.build_int_sub(min_ndims, idx, "").unwrap();
let (lhs_dim_sz, rhs_dim_sz) = unsafe {
(
lhs.shape().get_typed_unchecked(ctx, generator, &idx, None),
rhs.shape().get_typed_unchecked(ctx, generator, &idx, None),
)
};
let llvm_usize_const_one = llvm_usize.const_int(1, false);
let lhs_eqz = ctx
.builder
.build_int_compare(IntPredicate::EQ, lhs_dim_sz, llvm_usize_const_one, "")
.unwrap();
let rhs_eqz = ctx
.builder
.build_int_compare(IntPredicate::EQ, rhs_dim_sz, llvm_usize_const_one, "")
.unwrap();
let lhs_or_rhs_eqz = ctx.builder.build_or(lhs_eqz, rhs_eqz, "").unwrap();
let lhs_eq_rhs = ctx
.builder
.build_int_compare(IntPredicate::EQ, lhs_dim_sz, rhs_dim_sz, "")
.unwrap();
let is_compatible = ctx.builder.build_or(lhs_or_rhs_eqz, lhs_eq_rhs, "").unwrap();
ctx.make_assert(
generator,
is_compatible,
"0:ValueError",
"operands could not be broadcast together",
[None, None, None],
ctx.current_loc,
);
Ok(())
},
llvm_usize.const_int(1, false),
)
.unwrap();
let max_ndims = llvm_intrinsics::call_int_umax(ctx, lhs_ndims, rhs_ndims, None);
let lhs_dims = lhs.shape().base_ptr(ctx, generator);
let lhs_ndims = lhs.load_ndims(ctx);
let rhs_dims = rhs.shape().base_ptr(ctx, generator);
let rhs_ndims = rhs.load_ndims(ctx);
let out_dims = ctx.builder.build_array_alloca(llvm_usize, max_ndims, "").unwrap();
let out_dims = ArraySliceValue::from_ptr_val(out_dims, max_ndims, None);
ctx.builder
.build_call(
ndarray_calc_broadcast_fn,
&[
lhs_dims.into(),
lhs_ndims.into(),
rhs_dims.into(),
rhs_ndims.into(),
out_dims.base_ptr(ctx, generator).into(),
],
"",
)
.unwrap();
TypedArrayLikeAdapter::from(
out_dims,
Box::new(|_, v| v.into_int_value()),
Box::new(|_, v| v.into()),
)
}
/// Generates a call to `__nac3_ndarray_calc_broadcast_idx`. Returns an [`ArrayAllocaValue`]
/// containing the indices used for accessing `array` corresponding to the index of the broadcasted
/// array `broadcast_idx`.
pub fn call_ndarray_calc_broadcast_index<
'ctx,
G: CodeGenerator + ?Sized,
BroadcastIdx: UntypedArrayLikeAccessor<'ctx>,
>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
array: NDArrayValue<'ctx>,
broadcast_idx: &BroadcastIdx,
) -> TypedArrayLikeAdapter<'ctx, IntValue<'ctx>> {
let llvm_i32 = ctx.ctx.i32_type();
let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_pi32 = llvm_i32.ptr_type(AddressSpace::default());
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
let ndarray_calc_broadcast_fn_name = match llvm_usize.get_bit_width() {
32 => "__nac3_ndarray_calc_broadcast_idx",
64 => "__nac3_ndarray_calc_broadcast_idx64",
bw => codegen_unreachable!(ctx, "Unsupported size type bit width: {}", bw),
};
let ndarray_calc_broadcast_fn =
ctx.module.get_function(ndarray_calc_broadcast_fn_name).unwrap_or_else(|| {
let fn_type = llvm_usize.fn_type(
&[llvm_pusize.into(), llvm_usize.into(), llvm_pi32.into(), llvm_pi32.into()],
false,
);
ctx.module.add_function(ndarray_calc_broadcast_fn_name, fn_type, None)
});
let broadcast_size = broadcast_idx.size(ctx, generator);
let out_idx = ctx.builder.build_array_alloca(llvm_i32, broadcast_size, "").unwrap();
let array_dims = array.shape().base_ptr(ctx, generator);
let array_ndims = array.load_ndims(ctx);
let broadcast_idx_ptr = unsafe {
broadcast_idx.ptr_offset_unchecked(ctx, generator, &llvm_usize.const_zero(), None)
};
ctx.builder
.build_call(
ndarray_calc_broadcast_fn,
&[array_dims.into(), array_ndims.into(), broadcast_idx_ptr.into(), out_idx.into()],
"",
)
.unwrap();
TypedArrayLikeAdapter::from(
ArraySliceValue::from_ptr_val(out_idx, broadcast_size, None),
Box::new(|_, v| v.into_int_value()),
Box::new(|_, v| v.into()),
)
}

View File

@ -1,40 +0,0 @@
use inkwell::values::IntValue;
use crate::codegen::{
expr::infer_and_call_function,
irrt::get_usize_dependent_function_name,
values::{ArrayLikeValue, ArraySliceValue},
CodeGenContext, CodeGenerator,
};
/// Generates a call to `__nac3_ndarray_reshape_resolve_and_check_new_shape`.
///
/// Resolves unknown dimensions in `new_shape` for `numpy.reshape(<ndarray>, new_shape)`, raising an
/// assertion if multiple dimensions are unknown (`-1`).
pub fn call_nac3_ndarray_reshape_resolve_and_check_new_shape<'ctx, G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
size: IntValue<'ctx>,
new_ndims: IntValue<'ctx>,
new_shape: ArraySliceValue<'ctx>,
) {
let llvm_usize = generator.get_size_type(ctx.ctx);
assert_eq!(size.get_type(), llvm_usize);
assert_eq!(new_ndims.get_type(), llvm_usize);
assert_eq!(new_shape.element_type(ctx, generator), llvm_usize.into());
let name = get_usize_dependent_function_name(
generator,
ctx,
"__nac3_ndarray_reshape_resolve_and_check_new_shape",
);
infer_and_call_function(
ctx,
&name,
None,
&[size.into(), new_ndims.into(), new_shape.base_ptr(ctx, generator).into()],
None,
None,
);
}

View File

@ -1,48 +0,0 @@
use inkwell::{values::IntValue, AddressSpace};
use crate::codegen::{
expr::infer_and_call_function,
irrt::get_usize_dependent_function_name,
values::{ndarray::NDArrayValue, ProxyValue, TypedArrayLikeAccessor},
CodeGenContext, CodeGenerator,
};
/// Generates a call to `__nac3_ndarray_transpose`.
///
/// Creates a transpose view of `src_ndarray` and writes the result to `dst_ndarray`.
///
/// `dst_ndarray` must fulfill the following preconditions:
///
/// - `dst_ndarray.ndims` must be initialized and must be equal to `src_ndarray.ndims`.
/// - `dst_ndarray.shape` must be allocated and may contain uninitialized values.
/// - `dst_ndarray.strides` must be allocated and may contain uninitialized values.
pub fn call_nac3_ndarray_transpose<'ctx, G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
src_ndarray: NDArrayValue<'ctx>,
dst_ndarray: NDArrayValue<'ctx>,
axes: Option<&impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>>,
) {
let llvm_usize = generator.get_size_type(ctx.ctx);
assert!(axes.is_none_or(|axes| axes.size(ctx, generator).get_type() == llvm_usize));
assert!(axes.is_none_or(|axes| axes.element_type(ctx, generator) == llvm_usize.into()));
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_transpose");
infer_and_call_function(
ctx,
&name,
None,
&[
src_ndarray.as_base_value().into(),
dst_ndarray.as_base_value().into(),
axes.map_or(llvm_usize.const_zero(), |axes| axes.size(ctx, generator)).into(),
axes.map_or(llvm_usize.ptr_type(AddressSpace::default()).const_null(), |axes| {
axes.base_ptr(ctx, generator)
})
.into(),
],
None,
None,
);
}

View File

@ -6,13 +6,6 @@ use itertools::Either;
use crate::codegen::{CodeGenContext, CodeGenerator}; use crate::codegen::{CodeGenContext, CodeGenerator};
/// Invokes the `__nac3_range_slice_len` in IRRT.
///
/// - `start`: The `i32` start value for the slice.
/// - `end`: The `i32` end value for the slice.
/// - `step`: The `i32` step value for the slice.
///
/// Returns an `i32` value of the length of the slice.
pub fn calculate_len_for_slice_range<'ctx, G: CodeGenerator + ?Sized>( pub fn calculate_len_for_slice_range<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
@ -21,15 +14,9 @@ pub fn calculate_len_for_slice_range<'ctx, G: CodeGenerator + ?Sized>(
step: IntValue<'ctx>, step: IntValue<'ctx>,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
const SYMBOL: &str = "__nac3_range_slice_len"; const SYMBOL: &str = "__nac3_range_slice_len";
let llvm_i32 = ctx.ctx.i32_type();
assert_eq!(start.get_type(), llvm_i32);
assert_eq!(end.get_type(), llvm_i32);
assert_eq!(step.get_type(), llvm_i32);
let len_func = ctx.module.get_function(SYMBOL).unwrap_or_else(|| { let len_func = ctx.module.get_function(SYMBOL).unwrap_or_else(|| {
let fn_t = llvm_i32.fn_type(&[llvm_i32.into(), llvm_i32.into(), llvm_i32.into()], false); let i32_t = ctx.ctx.i32_type();
let fn_t = i32_t.fn_type(&[i32_t.into(), i32_t.into(), i32_t.into()], false);
ctx.module.add_function(SYMBOL, fn_t, None) ctx.module.add_function(SYMBOL, fn_t, None)
}); });
@ -46,7 +33,6 @@ pub fn calculate_len_for_slice_range<'ctx, G: CodeGenerator + ?Sized>(
[None, None, None], [None, None, None],
ctx.current_loc, ctx.current_loc,
); );
ctx.builder ctx.builder
.build_call(len_func, &[start.into(), end.into(), step.into()], "calc_len") .build_call(len_func, &[start.into(), end.into(), step.into()], "calc_len")
.map(CallSiteValue::try_as_basic_value) .map(CallSiteValue::try_as_basic_value)

View File

@ -1,46 +0,0 @@
use inkwell::values::{BasicValueEnum, CallSiteValue, IntValue, PointerValue};
use itertools::Either;
use super::get_usize_dependent_function_name;
use crate::codegen::{CodeGenContext, CodeGenerator};
/// Generates a call to string equality comparison. Returns an `i1` representing whether the strings are equal.
pub fn call_string_eq<'ctx, G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
str1_ptr: PointerValue<'ctx>,
str1_len: IntValue<'ctx>,
str2_ptr: PointerValue<'ctx>,
str2_len: IntValue<'ctx>,
) -> IntValue<'ctx> {
let llvm_i1 = ctx.ctx.bool_type();
let func_name = get_usize_dependent_function_name(generator, ctx, "nac3_str_eq");
let func = ctx.module.get_function(&func_name).unwrap_or_else(|| {
ctx.module.add_function(
&func_name,
llvm_i1.fn_type(
&[
str1_ptr.get_type().into(),
str1_len.get_type().into(),
str2_ptr.get_type().into(),
str2_len.get_type().into(),
],
false,
),
None,
)
});
ctx.builder
.build_call(
func,
&[str1_ptr.into(), str1_len.into(), str2_ptr.into(), str2_len.into()],
"str_eq_call",
)
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_int_value))
.map(Either::unwrap_left)
.unwrap()
}

View File

@ -1,6 +1,7 @@
use inkwell::{ use inkwell::{
context::Context,
intrinsics::Intrinsic, intrinsics::Intrinsic,
types::AnyTypeEnum::IntType, types::{AnyTypeEnum::IntType, FloatType},
values::{BasicValueEnum, CallSiteValue, FloatValue, IntValue, PointerValue}, values::{BasicValueEnum, CallSiteValue, FloatValue, IntValue, PointerValue},
AddressSpace, AddressSpace,
}; };
@ -8,6 +9,34 @@ use itertools::Either;
use super::CodeGenContext; use super::CodeGenContext;
/// Returns the string representation for the floating-point type `ft` when used in intrinsic
/// functions.
fn get_float_intrinsic_repr(ctx: &Context, ft: FloatType) -> &'static str {
// Standard LLVM floating-point types
if ft == ctx.f16_type() {
return "f16";
}
if ft == ctx.f32_type() {
return "f32";
}
if ft == ctx.f64_type() {
return "f64";
}
if ft == ctx.f128_type() {
return "f128";
}
// Non-standard floating-point types
if ft == ctx.x86_f80_type() {
return "f80";
}
if ft == ctx.ppc_f128_type() {
return "ppcf128";
}
unreachable!()
}
/// Invokes the [`llvm.va_start`](https://llvm.org/docs/LangRef.html#llvm-va-start-intrinsic) /// Invokes the [`llvm.va_start`](https://llvm.org/docs/LangRef.html#llvm-va-start-intrinsic)
/// intrinsic. /// intrinsic.
pub fn call_va_start<'ctx>(ctx: &CodeGenContext<'ctx, '_>, arglist: PointerValue<'ctx>) { pub fn call_va_start<'ctx>(ctx: &CodeGenContext<'ctx, '_>, arglist: PointerValue<'ctx>) {
@ -25,7 +54,7 @@ pub fn call_va_start<'ctx>(ctx: &CodeGenContext<'ctx, '_>, arglist: PointerValue
ctx.builder.build_call(intrinsic_fn, &[arglist.into()], "").unwrap(); ctx.builder.build_call(intrinsic_fn, &[arglist.into()], "").unwrap();
} }
/// Invokes the [`llvm.va_end`](https://llvm.org/docs/LangRef.html#llvm-va-end-intrinsic) /// Invokes the [`llvm.va_start`](https://llvm.org/docs/LangRef.html#llvm-va-start-intrinsic)
/// intrinsic. /// intrinsic.
pub fn call_va_end<'ctx>(ctx: &CodeGenContext<'ctx, '_>, arglist: PointerValue<'ctx>) { pub fn call_va_end<'ctx>(ctx: &CodeGenContext<'ctx, '_>, arglist: PointerValue<'ctx>) {
const FN_NAME: &str = "llvm.va_end"; const FN_NAME: &str = "llvm.va_end";

View File

@ -42,7 +42,7 @@ use crate::{
}; };
use concrete_type::{ConcreteType, ConcreteTypeEnum, ConcreteTypeStore}; use concrete_type::{ConcreteType, ConcreteTypeEnum, ConcreteTypeStore};
pub use generator::{CodeGenerator, DefaultCodeGenerator}; pub use generator::{CodeGenerator, DefaultCodeGenerator};
use types::{ndarray::NDArrayType, ListType, ProxyType, RangeType, TupleType}; use types::{ndarray::NDArrayType, ListType, ProxyType, RangeType};
pub mod builtin_fns; pub mod builtin_fns;
pub mod concrete_type; pub mod concrete_type;
@ -228,7 +228,7 @@ pub struct CodeGenContext<'ctx, 'a> {
pub current_loc: Location, pub current_loc: Location,
} }
impl CodeGenContext<'_, '_> { impl<'ctx, 'a> CodeGenContext<'ctx, 'a> {
/// Whether the [current basic block][Builder::get_insert_block] referenced by `builder` /// Whether the [current basic block][Builder::get_insert_block] referenced by `builder`
/// contains a [terminator statement][BasicBlock::get_terminator]. /// contains a [terminator statement][BasicBlock::get_terminator].
pub fn is_terminated(&self) -> bool { pub fn is_terminated(&self) -> bool {
@ -520,7 +520,7 @@ fn get_llvm_type<'ctx, G: CodeGenerator + ?Sized>(
ctx, module, generator, unifier, top_level, type_cache, dtype, ctx, module, generator, unifier, top_level, type_cache, dtype,
); );
NDArrayType::new(generator, ctx, element_type, ndims).as_base_type().into() NDArrayType::new(generator, ctx, element_type, Some(ndims)).as_base_type().into()
} }
_ => unreachable!( _ => unreachable!(
@ -574,7 +574,7 @@ fn get_llvm_type<'ctx, G: CodeGenerator + ?Sized>(
get_llvm_type(ctx, module, generator, unifier, top_level, type_cache, *ty) get_llvm_type(ctx, module, generator, unifier, top_level, type_cache, *ty)
}) })
.collect_vec(); .collect_vec();
TupleType::new(generator, ctx, &fields).as_base_type().into() ctx.struct_type(&fields, false).into()
} }
TVirtual { .. } => unimplemented!(), TVirtual { .. } => unimplemented!(),
_ => unreachable!("{}", ty_enum.get_type_name()), _ => unreachable!("{}", ty_enum.get_type_name()),

File diff suppressed because it is too large Load Diff

View File

@ -16,11 +16,7 @@ use super::{
gen_in_range_check, gen_in_range_check,
irrt::{handle_slice_indices, list_slice_assignment}, irrt::{handle_slice_indices, list_slice_assignment},
macros::codegen_unreachable, macros::codegen_unreachable,
types::ndarray::NDArrayType, values::{ArrayLikeIndexer, ArraySliceValue, ListValue, RangeValue},
values::{
ndarray::{RustNDIndex, ScalarOrNDArray},
ArrayLikeIndexer, ArraySliceValue, ListValue, ProxyValue, RangeValue,
},
CodeGenContext, CodeGenerator, CodeGenContext, CodeGenerator,
}; };
use crate::{ use crate::{
@ -415,52 +411,7 @@ pub fn gen_setitem<'ctx, G: CodeGenerator>(
if *obj_id == ctx.primitives.ndarray.obj_id(&ctx.unifier).unwrap() => if *obj_id == ctx.primitives.ndarray.obj_id(&ctx.unifier).unwrap() =>
{ {
// Handle NDArray item assignment // Handle NDArray item assignment
// Process target todo!("ndarray subscript assignment is not yet implemented");
let target = generator
.gen_expr(ctx, target)?
.unwrap()
.to_basic_value_enum(ctx, generator, target_ty)?;
// Process key
let key = RustNDIndex::from_subscript_expr(generator, ctx, key)?;
// Process value
let value = value.to_basic_value_enum(ctx, generator, value_ty)?;
// Reference code:
// ```python
// target = target[key]
// value = np.asarray(value)
//
// shape = np.broadcast_shape((target, value))
//
// target = np.broadcast_to(target, shape)
// value = np.broadcast_to(value, shape)
//
// # ...and finally copy 1-1 from value to target.
// ```
let target = NDArrayType::from_unifier_type(generator, ctx, target_ty)
.map_value(target.into_pointer_value(), None);
let target = target.index(generator, ctx, &key);
let value = ScalarOrNDArray::from_value(generator, ctx, (value_ty, value))
.to_ndarray(generator, ctx);
let broadcast_ndims =
[target.get_type().ndims(), value.get_type().ndims()].into_iter().max().unwrap();
let broadcast_result = NDArrayType::new(
generator,
ctx.ctx,
value.get_type().element_type(),
broadcast_ndims,
)
.broadcast(generator, ctx, &[target, value]);
let target = broadcast_result.ndarrays[0];
let value = broadcast_result.ndarrays[1];
target.copy_data_from(generator, ctx, value);
} }
_ => { _ => {
panic!("encountered unknown target type: {}", ctx.unifier.stringify(target_ty)); panic!("encountered unknown target type: {}", ctx.unifier.stringify(target_ty));

View File

@ -36,6 +36,7 @@ use crate::{
struct Resolver { struct Resolver {
id_to_type: HashMap<StrRef, Type>, id_to_type: HashMap<StrRef, Type>,
id_to_def: RwLock<HashMap<StrRef, DefinitionId>>, id_to_def: RwLock<HashMap<StrRef, DefinitionId>>,
class_names: HashMap<StrRef, Type>,
} }
impl Resolver { impl Resolver {
@ -103,9 +104,11 @@ fn test_primitives() {
let top_level = Arc::new(composer.make_top_level_context()); let top_level = Arc::new(composer.make_top_level_context());
unifier.top_level = Some(top_level.clone()); unifier.top_level = Some(top_level.clone());
let resolver = let resolver = Arc::new(Resolver {
Arc::new(Resolver { id_to_type: HashMap::new(), id_to_def: RwLock::new(HashMap::new()) }) id_to_type: HashMap::new(),
as Arc<dyn SymbolResolver + Send + Sync>; id_to_def: RwLock::new(HashMap::new()),
class_names: HashMap::default(),
}) as Arc<dyn SymbolResolver + Send + Sync>;
let threads = vec![DefaultCodeGenerator::new("test".into(), 32).into()]; let threads = vec![DefaultCodeGenerator::new("test".into(), 32).into()];
let signature = FunSignature { let signature = FunSignature {
@ -295,7 +298,11 @@ fn test_simple_call() {
loc: None, loc: None,
}))); })));
let resolver = Resolver { id_to_type: HashMap::new(), id_to_def: RwLock::new(HashMap::new()) }; let resolver = Resolver {
id_to_type: HashMap::new(),
id_to_def: RwLock::new(HashMap::new()),
class_names: HashMap::default(),
};
resolver.add_id_def("foo".into(), DefinitionId(foo_id)); resolver.add_id_def("foo".into(), DefinitionId(foo_id));
let resolver = Arc::new(resolver) as Arc<dyn SymbolResolver + Send + Sync>; let resolver = Arc::new(resolver) as Arc<dyn SymbolResolver + Send + Sync>;
@ -464,6 +471,6 @@ fn test_classes_ndarray_type_new() {
let llvm_i32 = ctx.i32_type(); let llvm_i32 = ctx.i32_type();
let llvm_usize = generator.get_size_type(&ctx); let llvm_usize = generator.get_size_type(&ctx);
let llvm_ndarray = NDArrayType::new(&generator, &ctx, llvm_i32.into(), 2); let llvm_ndarray = NDArrayType::new(&generator, &ctx, llvm_i32.into(), None);
assert!(NDArrayType::is_representable(llvm_ndarray.as_base_type(), llvm_usize).is_ok()); assert!(NDArrayType::is_representable(llvm_ndarray.as_base_type(), llvm_usize).is_ok());
} }

View File

@ -1,113 +1,69 @@
use inkwell::{ use inkwell::{
context::{AsContextRef, Context}, context::Context,
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType}, types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType},
values::{IntValue, PointerValue}, values::IntValue,
AddressSpace, IntPredicate, OptimizationLevel, AddressSpace,
}; };
use itertools::Itertools;
use nac3core_derive::StructFields;
use super::ProxyType; use super::ProxyType;
use crate::{ use crate::codegen::{
codegen::{ values::{ArraySliceValue, ListValue, ProxyValue},
types::structure::{ CodeGenContext, CodeGenerator,
check_struct_type_matches_fields, FieldIndexCounter, StructField, StructFields,
},
values::{ListValue, ProxyValue},
CodeGenContext, CodeGenerator,
},
typecheck::typedef::{iter_type_vars, Type, TypeEnum},
}; };
/// Proxy type for a `list` type in LLVM. /// Proxy type for a `list` type in LLVM.
#[derive(Debug, PartialEq, Eq, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct ListType<'ctx> { pub struct ListType<'ctx> {
ty: PointerType<'ctx>, ty: PointerType<'ctx>,
item: Option<BasicTypeEnum<'ctx>>,
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
} }
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
pub struct ListStructFields<'ctx> {
/// Array pointer to content.
#[value_type(i8_type().ptr_type(AddressSpace::default()))]
pub items: StructField<'ctx, PointerValue<'ctx>>,
/// Number of items in the array.
#[value_type(usize)]
pub len: StructField<'ctx, IntValue<'ctx>>,
}
impl<'ctx> ListStructFields<'ctx> {
#[must_use]
pub fn new_typed(item: BasicTypeEnum<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
let mut counter = FieldIndexCounter::default();
ListStructFields {
items: StructField::create(
&mut counter,
"items",
item.ptr_type(AddressSpace::default()),
),
len: StructField::create(&mut counter, "len", llvm_usize),
}
}
}
impl<'ctx> ListType<'ctx> { impl<'ctx> ListType<'ctx> {
/// Checks whether `llvm_ty` represents a `list` type, returning [Err] if it does not. /// Checks whether `llvm_ty` represents a `list` type, returning [Err] if it does not.
pub fn is_representable( pub fn is_representable(
llvm_ty: PointerType<'ctx>, llvm_ty: PointerType<'ctx>,
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
) -> Result<(), String> { ) -> Result<(), String> {
let ctx = llvm_ty.get_context(); let llvm_list_ty = llvm_ty.get_element_type();
let AnyTypeEnum::StructType(llvm_list_ty) = llvm_list_ty else {
return Err(format!("Expected struct type for `list` type, got {llvm_list_ty}"));
};
if llvm_list_ty.count_fields() != 2 {
return Err(format!(
"Expected 2 fields in `list`, got {}",
llvm_list_ty.count_fields()
));
}
let llvm_ty = llvm_ty.get_element_type(); let list_size_ty = llvm_list_ty.get_field_type_at_index(0).unwrap();
let AnyTypeEnum::StructType(llvm_ty) = llvm_ty else { let Ok(_) = PointerType::try_from(list_size_ty) else {
return Err(format!("Expected struct type for `list` type, got {llvm_ty}")); return Err(format!("Expected pointer type for `list.0`, got {list_size_ty}"));
}; };
let fields = ListStructFields::new(ctx, llvm_usize); let list_data_ty = llvm_list_ty.get_field_type_at_index(1).unwrap();
let Ok(list_data_ty) = IntType::try_from(list_data_ty) else {
return Err(format!("Expected int type for `list.1`, got {list_data_ty}"));
};
if list_data_ty.get_bit_width() != llvm_usize.get_bit_width() {
return Err(format!(
"Expected {}-bit int type for `list.1`, got {}-bit int",
llvm_usize.get_bit_width(),
list_data_ty.get_bit_width()
));
}
check_struct_type_matches_fields( Ok(())
fields,
llvm_ty,
"list",
&[(fields.items.name(), &|ty| {
if ty.is_pointer_type() {
Ok(())
} else {
Err(format!("Expected T* for `list.items`, got {ty}"))
}
})],
)
}
/// Returns an instance of [`StructFields`] containing all field accessors for this type.
#[must_use]
fn fields(item: BasicTypeEnum<'ctx>, llvm_usize: IntType<'ctx>) -> ListStructFields<'ctx> {
ListStructFields::new_typed(item, llvm_usize)
}
/// See [`ListType::fields`].
// TODO: Move this into e.g. StructProxyType
#[must_use]
pub fn get_fields(&self, _ctx: &impl AsContextRef<'ctx>) -> ListStructFields<'ctx> {
Self::fields(self.item.unwrap_or(self.llvm_usize.into()), self.llvm_usize)
} }
/// Creates an LLVM type corresponding to the expected structure of a `List`. /// Creates an LLVM type corresponding to the expected structure of a `List`.
#[must_use] #[must_use]
fn llvm_type( fn llvm_type(
ctx: &'ctx Context, ctx: &'ctx Context,
element_type: Option<BasicTypeEnum<'ctx>>, element_type: BasicTypeEnum<'ctx>,
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
) -> PointerType<'ctx> { ) -> PointerType<'ctx> {
let element_type = element_type.unwrap_or(llvm_usize.into()); // struct List { data: T*, size: size_t }
let field_tys = [element_type.ptr_type(AddressSpace::default()).into(), llvm_usize.into()];
let field_tys =
Self::fields(element_type, llvm_usize).into_iter().map(|field| field.1).collect_vec();
ctx.struct_type(&field_tys, false).ptr_type(AddressSpace::default()) ctx.struct_type(&field_tys, false).ptr_type(AddressSpace::default())
} }
@ -120,50 +76,9 @@ impl<'ctx> ListType<'ctx> {
element_type: BasicTypeEnum<'ctx>, element_type: BasicTypeEnum<'ctx>,
) -> Self { ) -> Self {
let llvm_usize = generator.get_size_type(ctx); let llvm_usize = generator.get_size_type(ctx);
let llvm_list = Self::llvm_type(ctx, Some(element_type), llvm_usize); let llvm_list = Self::llvm_type(ctx, element_type, llvm_usize);
Self { ty: llvm_list, item: Some(element_type), llvm_usize } ListType::from_type(llvm_list, llvm_usize)
}
/// Creates an instance of [`ListType`] with an unknown element type.
#[must_use]
pub fn new_untyped<G: CodeGenerator + ?Sized>(generator: &G, ctx: &'ctx Context) -> Self {
let llvm_usize = generator.get_size_type(ctx);
let llvm_list = Self::llvm_type(ctx, None, llvm_usize);
Self { ty: llvm_list, item: None, llvm_usize }
}
/// Creates an [`ListType`] from a [unifier type][Type].
#[must_use]
pub fn from_unifier_type<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &mut CodeGenContext<'ctx, '_>,
ty: Type,
) -> Self {
// Check unifier type and extract `item_type`
let elem_type = match &*ctx.unifier.get_ty_immutable(ty) {
TypeEnum::TObj { obj_id, params, .. }
if *obj_id == ctx.primitives.list.obj_id(&ctx.unifier).unwrap() =>
{
iter_type_vars(params).next().unwrap().ty
}
_ => panic!("Expected `list` type, but got {}", ctx.unifier.stringify(ty)),
};
let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_elem_type = if let TypeEnum::TVar { .. } = &*ctx.unifier.get_ty_immutable(ty) {
None
} else {
Some(ctx.get_llvm_type(generator, elem_type))
};
Self {
ty: Self::llvm_type(ctx.ctx, llvm_elem_type, llvm_usize),
item: llvm_elem_type,
llvm_usize,
}
} }
/// Creates an [`ListType`] from a [`PointerType`]. /// Creates an [`ListType`] from a [`PointerType`].
@ -171,141 +86,47 @@ impl<'ctx> ListType<'ctx> {
pub fn from_type(ptr_ty: PointerType<'ctx>, llvm_usize: IntType<'ctx>) -> Self { pub fn from_type(ptr_ty: PointerType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
debug_assert!(Self::is_representable(ptr_ty, llvm_usize).is_ok()); debug_assert!(Self::is_representable(ptr_ty, llvm_usize).is_ok());
let ctx = ptr_ty.get_context(); ListType { ty: ptr_ty, llvm_usize }
// We are just searching for the index off a field - Slot an arbitrary element type in.
let item_field_idx =
Self::fields(ctx.i8_type().into(), llvm_usize).index_of_field(|f| f.items);
let item = unsafe {
ptr_ty
.get_element_type()
.into_struct_type()
.get_field_type_at_index_unchecked(item_field_idx)
.into_pointer_type()
.get_element_type()
};
let item = BasicTypeEnum::try_from(item).unwrap_or_else(|()| {
panic!(
"Expected BasicTypeEnum for list element type, got {}",
ptr_ty.get_element_type().print_to_string()
)
});
ListType { ty: ptr_ty, item: Some(item), llvm_usize }
} }
/// Returns the type of the `size` field of this `list` type. /// Returns the type of the `size` field of this `list` type.
#[must_use] #[must_use]
pub fn size_type(&self) -> IntType<'ctx> { pub fn size_type(&self) -> IntType<'ctx> {
self.llvm_usize self.as_base_type()
.get_element_type()
.into_struct_type()
.get_field_type_at_index(1)
.map(BasicTypeEnum::into_int_type)
.unwrap()
} }
/// Returns the element type of this `list` type. /// Returns the element type of this `list` type.
#[must_use] #[must_use]
pub fn element_type(&self) -> Option<BasicTypeEnum<'ctx>> { pub fn element_type(&self) -> AnyTypeEnum<'ctx> {
self.item self.as_base_type()
.get_element_type()
.into_struct_type()
.get_field_type_at_index(0)
.map(BasicTypeEnum::into_pointer_type)
.map(PointerType::get_element_type)
.unwrap()
} }
/// Allocates an instance of [`ListValue`] as if by calling `alloca` on the base type. /// Allocates an instance of [`ListValue`] as if by calling `alloca` on the base type.
///
/// See [`ProxyType::raw_alloca`].
#[must_use] #[must_use]
pub fn alloca( pub fn alloca<G: CodeGenerator + ?Sized>(
&self,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value(
self.raw_alloca(ctx, name),
self.llvm_usize,
name,
)
}
/// Allocates an instance of [`ListValue`] as if by calling `alloca` on the base type.
///
/// See [`ProxyType::raw_alloca_var`].
#[must_use]
pub fn alloca_var<G: CodeGenerator + ?Sized>(
&self, &self,
generator: &mut G, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>, name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value { ) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value( <Self as ProxyType<'ctx>>::Value::from_pointer_value(
self.raw_alloca_var(generator, ctx, name), self.raw_alloca(generator, ctx, name),
self.llvm_usize, self.llvm_usize,
name, name,
) )
} }
/// Allocates a [`ListValue`] on the stack using `item` of this [`ListType`] instance.
///
/// The returned list will contain:
///
/// - `data`: Allocated with `len` number of elements.
/// - `len`: Initialized to the value of `len` passed to this function.
#[must_use]
pub fn construct<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
len: IntValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
let len = ctx.builder.build_int_z_extend(len, self.llvm_usize, "").unwrap();
// Generate a runtime assertion if allocating a non-empty list with unknown element type
if ctx.registry.llvm_options.opt_level == OptimizationLevel::None && self.item.is_none() {
let len_eqz = ctx
.builder
.build_int_compare(IntPredicate::EQ, len, self.llvm_usize.const_zero(), "")
.unwrap();
ctx.make_assert(
generator,
len_eqz,
"0:AssertionError",
"Cannot allocate a non-empty list with unknown element type",
[None, None, None],
ctx.current_loc,
);
}
let plist = self.alloca_var(generator, ctx, name);
plist.store_size(ctx, generator, len);
let item = self.item.unwrap_or(self.llvm_usize.into());
plist.create_data(ctx, item, None);
plist
}
/// Convenience function for creating a list with zero elements.
///
/// This function is preferred over [`ListType::construct`] if the length is known to always be
/// 0, as this function avoids injecting an IR assertion for checking if a non-empty untyped
/// list is being allocated.
///
/// The returned list will contain:
///
/// - `data`: Initialized to `(T*) 0`.
/// - `len`: Initialized to `0`.
#[must_use]
pub fn construct_empty<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
let plist = self.alloca_var(generator, ctx, name);
plist.store_size(ctx, generator, self.llvm_usize.const_zero());
plist.create_data(ctx, self.item.unwrap_or(self.llvm_usize.into()), None);
plist
}
/// Converts an existing value into a [`ListValue`]. /// Converts an existing value into a [`ListValue`].
#[must_use] #[must_use]
pub fn map_value( pub fn map_value(
@ -341,8 +162,36 @@ impl<'ctx> ProxyType<'ctx> for ListType<'ctx> {
Self::is_representable(llvm_ty, generator.get_size_type(ctx)) Self::is_representable(llvm_ty, generator.get_size_type(ctx))
} }
fn alloca_type(&self) -> impl BasicType<'ctx> { fn raw_alloca<G: CodeGenerator + ?Sized>(
self.as_base_type().get_element_type().into_struct_type() &self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>,
) -> <Self::Value as ProxyValue<'ctx>>::Base {
generator
.gen_var_alloc(
ctx,
self.as_base_type().get_element_type().into_struct_type().into(),
name,
)
.unwrap()
}
fn array_alloca<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
size: IntValue<'ctx>,
name: Option<&'ctx str>,
) -> ArraySliceValue<'ctx> {
generator
.gen_array_var_alloc(
ctx,
self.as_base_type().get_element_type().into_struct_type().into(),
size,
name,
)
.unwrap()
} }
fn as_base_type(&self) -> Self::Base { fn as_base_type(&self) -> Self::Base {

View File

@ -16,11 +16,7 @@
//! the returned object. This is similar to a `new` expression in C++ but the object is allocated //! the returned object. This is similar to a `new` expression in C++ but the object is allocated
//! on the stack. //! on the stack.
use inkwell::{ use inkwell::{context::Context, types::BasicType, values::IntValue};
context::Context,
types::BasicType,
values::{IntValue, PointerValue},
};
use super::{ use super::{
values::{ArraySliceValue, ProxyValue}, values::{ArraySliceValue, ProxyValue},
@ -28,13 +24,11 @@ use super::{
}; };
pub use list::*; pub use list::*;
pub use range::*; pub use range::*;
pub use tuple::*;
mod list; mod list;
pub mod ndarray; pub mod ndarray;
mod range; mod range;
pub mod structure; pub mod structure;
mod tuple;
pub mod utils; pub mod utils;
/// A LLVM type that is used to represent a corresponding type in NAC3. /// A LLVM type that is used to represent a corresponding type in NAC3.
@ -59,66 +53,23 @@ pub trait ProxyType<'ctx>: Into<Self::Base> {
llvm_ty: Self::Base, llvm_ty: Self::Base,
) -> Result<(), String>; ) -> Result<(), String>;
/// Returns the type that should be used in `alloca` IR statements. /// Creates a new value of this type, returning the LLVM instance of this value.
fn alloca_type(&self) -> impl BasicType<'ctx>; fn raw_alloca<G: CodeGenerator + ?Sized>(
/// Creates a new value of this type by invoking `alloca` at the current builder location,
/// returning a [`PointerValue`] instance representing the allocated value.
fn raw_alloca(
&self,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>,
) -> PointerValue<'ctx> {
ctx.builder
.build_alloca(self.alloca_type().as_basic_type_enum(), name.unwrap_or_default())
.unwrap()
}
/// Creates a new value of this type by invoking `alloca` at the beginning of the function,
/// returning a [`PointerValue`] instance representing the allocated value.
fn raw_alloca_var<G: CodeGenerator + ?Sized>(
&self, &self,
generator: &mut G, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>, name: Option<&'ctx str>,
) -> PointerValue<'ctx> { ) -> <Self::Value as ProxyValue<'ctx>>::Base;
generator.gen_var_alloc(ctx, self.alloca_type().as_basic_type_enum(), name).unwrap()
}
/// Creates a new array value of this type by invoking `alloca` at the current builder location, /// Creates a new array value of this type, returning an [`ArraySliceValue`] encapsulating the
/// returning an [`ArraySliceValue`] encapsulating the resulting array. /// resulting array.
fn array_alloca( fn array_alloca<G: CodeGenerator + ?Sized>(
&self,
ctx: &mut CodeGenContext<'ctx, '_>,
size: IntValue<'ctx>,
name: Option<&'ctx str>,
) -> ArraySliceValue<'ctx> {
ArraySliceValue::from_ptr_val(
ctx.builder
.build_array_alloca(
self.alloca_type().as_basic_type_enum(),
size,
name.unwrap_or_default(),
)
.unwrap(),
size,
name,
)
}
/// Creates a new array value of this type by invoking `alloca` at the beginning of the
/// function, returning an [`ArraySliceValue`] encapsulating the resulting array.
fn array_alloca_var<G: CodeGenerator + ?Sized>(
&self, &self,
generator: &mut G, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
size: IntValue<'ctx>, size: IntValue<'ctx>,
name: Option<&'ctx str>, name: Option<&'ctx str>,
) -> ArraySliceValue<'ctx> { ) -> ArraySliceValue<'ctx>;
generator
.gen_array_var_alloc(ctx, self.alloca_type().as_basic_type_enum(), size, name)
.unwrap()
}
/// Returns the [base type][Self::Base] of this proxy. /// Returns the [base type][Self::Base] of this proxy.
fn as_base_type(&self) -> Self::Base; fn as_base_type(&self) -> Self::Base;

View File

@ -1,243 +0,0 @@
use inkwell::{
types::BasicTypeEnum,
values::{BasicValueEnum, IntValue},
AddressSpace,
};
use crate::{
codegen::{
irrt,
stmt::gen_if_else_expr_callback,
types::{ndarray::NDArrayType, ListType, ProxyType},
values::{
ndarray::NDArrayValue, ArrayLikeValue, ArraySliceValue, ListValue, ProxyValue,
TypedArrayLikeAdapter, TypedArrayLikeMutator,
},
CodeGenContext, CodeGenerator,
},
toplevel::helper::{arraylike_flatten_element_type, arraylike_get_ndims},
typecheck::typedef::{Type, TypeEnum},
};
/// Get the expected `dtype` and `ndims` of the ndarray returned by `np_array(<list>)`.
fn get_list_object_dtype_and_ndims<'ctx, G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &mut CodeGenContext<'ctx, '_>,
list_ty: Type,
) -> (BasicTypeEnum<'ctx>, u64) {
let dtype = arraylike_flatten_element_type(&mut ctx.unifier, list_ty);
let ndims = arraylike_get_ndims(&mut ctx.unifier, list_ty);
(ctx.get_llvm_type(generator, dtype), ndims)
}
impl<'ctx> NDArrayType<'ctx> {
/// Implementation of `np_array(<list>, copy=True)`
fn construct_numpy_array_from_list_copy_true_impl<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
(list_ty, list): (Type, ListValue<'ctx>),
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
let (dtype, ndims_int) = get_list_object_dtype_and_ndims(generator, ctx, list_ty);
assert!(self.ndims >= ndims_int);
assert_eq!(dtype, self.dtype);
let list_value = list.as_i8_list(generator, ctx);
// Validate `list` has a consistent shape.
// Raise an exception if `list` is something abnormal like `[[1, 2], [3]]`.
// If `list` has a consistent shape, deduce the shape and write it to `shape`.
let ndims = self.llvm_usize.const_int(ndims_int, false);
let shape = ctx.builder.build_array_alloca(self.llvm_usize, ndims, "").unwrap();
let shape = ArraySliceValue::from_ptr_val(shape, ndims, None);
let shape = TypedArrayLikeAdapter::from(
shape,
|_, _, val| val.into_int_value(),
|_, _, val| val.into(),
);
irrt::ndarray::call_nac3_ndarray_array_set_and_validate_list_shape(
generator, ctx, list_value, ndims, &shape,
);
let ndarray = Self::new(generator, ctx.ctx, dtype, ndims_int)
.construct_uninitialized(generator, ctx, name);
ndarray.copy_shape_from_array(generator, ctx, shape.base_ptr(ctx, generator));
unsafe { ndarray.create_data(generator, ctx) };
// Copy all contents from the list.
irrt::ndarray::call_nac3_ndarray_array_write_list_to_array(
generator, ctx, list_value, ndarray,
);
ndarray
}
/// Implementation of `np_array(<list>, copy=None)`
fn construct_numpy_array_from_list_copy_none_impl<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
(list_ty, list): (Type, ListValue<'ctx>),
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
// np_array without copying is only possible `list` is not nested.
//
// If `list` is `list[T]`, we can create an ndarray with `data` set
// to the array pointer of `list`.
//
// If `list` is `list[list[T]]` or worse, copy.
let (dtype, ndims) = get_list_object_dtype_and_ndims(generator, ctx, list_ty);
if ndims == 1 {
// `list` is not nested
assert_eq!(ndims, 1);
assert!(self.ndims >= ndims);
assert_eq!(dtype, self.dtype);
let llvm_pi8 = ctx.ctx.i8_type().ptr_type(AddressSpace::default());
let ndarray = Self::new(generator, ctx.ctx, dtype, 1)
.construct_uninitialized(generator, ctx, name);
// Set data
let data = ctx
.builder
.build_pointer_cast(list.data().base_ptr(ctx, generator), llvm_pi8, "")
.unwrap();
ndarray.store_data(ctx, data);
// ndarray->shape[0] = list->len;
let shape = ndarray.shape();
let list_len = list.load_size(ctx, None);
unsafe {
shape.set_typed_unchecked(ctx, generator, &self.llvm_usize.const_zero(), list_len);
}
// Set strides, the `data` is contiguous
ndarray.set_strides_contiguous(generator, ctx);
ndarray
} else {
// `list` is nested, copy
self.construct_numpy_array_from_list_copy_true_impl(
generator,
ctx,
(list_ty, list),
name,
)
}
}
/// Implementation of `np_array(<list>, copy=copy)`
fn construct_numpy_array_list_impl<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
(list_ty, list): (Type, ListValue<'ctx>),
copy: IntValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
assert_eq!(copy.get_type(), ctx.ctx.bool_type());
let (dtype, ndims) = get_list_object_dtype_and_ndims(generator, ctx, list_ty);
let ndarray = gen_if_else_expr_callback(
generator,
ctx,
|_generator, _ctx| Ok(copy),
|generator, ctx| {
let ndarray = self.construct_numpy_array_from_list_copy_true_impl(
generator,
ctx,
(list_ty, list),
name,
);
Ok(Some(ndarray.as_base_value()))
},
|generator, ctx| {
let ndarray = self.construct_numpy_array_from_list_copy_none_impl(
generator,
ctx,
(list_ty, list),
name,
);
Ok(Some(ndarray.as_base_value()))
},
)
.unwrap()
.map(BasicValueEnum::into_pointer_value)
.unwrap();
NDArrayType::new(generator, ctx.ctx, dtype, ndims).map_value(ndarray, None)
}
/// Implementation of `np_array(<ndarray>, copy=copy)`.
pub fn construct_numpy_array_ndarray_impl<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
ndarray: NDArrayValue<'ctx>,
copy: IntValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
assert_eq!(ndarray.get_type().dtype, self.dtype);
assert!(self.ndims >= ndarray.get_type().ndims);
assert_eq!(copy.get_type(), ctx.ctx.bool_type());
let ndarray_val = gen_if_else_expr_callback(
generator,
ctx,
|_generator, _ctx| Ok(copy),
|generator, ctx| {
let ndarray = ndarray.make_copy(generator, ctx); // Force copy
Ok(Some(ndarray.as_base_value()))
},
|_generator, _ctx| {
// No need to copy. Return `ndarray` itself.
Ok(Some(ndarray.as_base_value()))
},
)
.unwrap()
.map(BasicValueEnum::into_pointer_value)
.unwrap();
ndarray.get_type().map_value(ndarray_val, name)
}
/// Create a new ndarray like
/// [`np.array()`](https://numpy.org/doc/stable/reference/generated/numpy.array.html).
///
/// Note that the returned [`NDArrayValue`] may have fewer dimensions than is specified by this
/// instance. Use [`NDArrayValue::atleast_nd`] on the returned value if an `ndarray` instance
/// with the exact number of dimensions is needed.
pub fn construct_numpy_array<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
(object_ty, object): (Type, BasicValueEnum<'ctx>),
copy: IntValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
match &*ctx.unifier.get_ty_immutable(object_ty) {
TypeEnum::TObj { obj_id, .. }
if *obj_id == ctx.primitives.list.obj_id(&ctx.unifier).unwrap() =>
{
let list = ListType::from_unifier_type(generator, ctx, object_ty)
.map_value(object.into_pointer_value(), None);
self.construct_numpy_array_list_impl(generator, ctx, (object_ty, list), copy, name)
}
TypeEnum::TObj { obj_id, .. }
if *obj_id == ctx.primitives.ndarray.obj_id(&ctx.unifier).unwrap() =>
{
let ndarray = NDArrayType::from_unifier_type(generator, ctx, object_ty)
.map_value(object.into_pointer_value(), None);
self.construct_numpy_array_ndarray_impl(generator, ctx, ndarray, copy, name)
}
_ => panic!("Unrecognized object type: {}", ctx.unifier.stringify(object_ty)), // Typechecker ensures this
}
}
}

View File

@ -1,176 +0,0 @@
use inkwell::{
context::{AsContextRef, Context},
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType},
values::{IntValue, PointerValue},
AddressSpace,
};
use itertools::Itertools;
use nac3core_derive::StructFields;
use crate::codegen::{
types::{
structure::{check_struct_type_matches_fields, StructField, StructFields},
ProxyType,
},
values::{ndarray::ShapeEntryValue, ProxyValue},
CodeGenContext, CodeGenerator,
};
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct ShapeEntryType<'ctx> {
ty: PointerType<'ctx>,
llvm_usize: IntType<'ctx>,
}
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
pub struct ShapeEntryStructFields<'ctx> {
#[value_type(usize)]
pub ndims: StructField<'ctx, IntValue<'ctx>>,
#[value_type(usize.ptr_type(AddressSpace::default()))]
pub shape: StructField<'ctx, PointerValue<'ctx>>,
}
impl<'ctx> ShapeEntryType<'ctx> {
/// Checks whether `llvm_ty` represents a [`ShapeEntryType`], returning [Err] if it does not.
pub fn is_representable(
llvm_ty: PointerType<'ctx>,
llvm_usize: IntType<'ctx>,
) -> Result<(), String> {
let ctx = llvm_ty.get_context();
let llvm_ndarray_ty = llvm_ty.get_element_type();
let AnyTypeEnum::StructType(llvm_ndarray_ty) = llvm_ndarray_ty else {
return Err(format!(
"Expected struct type for `ShapeEntry` type, got {llvm_ndarray_ty}"
));
};
check_struct_type_matches_fields(
Self::fields(ctx, llvm_usize),
llvm_ndarray_ty,
"NDArray",
&[],
)
}
/// Returns an instance of [`StructFields`] containing all field accessors for this type.
#[must_use]
fn fields(
ctx: impl AsContextRef<'ctx>,
llvm_usize: IntType<'ctx>,
) -> ShapeEntryStructFields<'ctx> {
ShapeEntryStructFields::new(ctx, llvm_usize)
}
/// See [`ShapeEntryStructFields::fields`].
// TODO: Move this into e.g. StructProxyType
#[must_use]
pub fn get_fields(&self, ctx: impl AsContextRef<'ctx>) -> ShapeEntryStructFields<'ctx> {
Self::fields(ctx, self.llvm_usize)
}
/// Creates an LLVM type corresponding to the expected structure of a `ShapeEntry`.
#[must_use]
fn llvm_type(ctx: &'ctx Context, llvm_usize: IntType<'ctx>) -> PointerType<'ctx> {
let field_tys =
Self::fields(ctx, llvm_usize).into_iter().map(|field| field.1).collect_vec();
ctx.struct_type(&field_tys, false).ptr_type(AddressSpace::default())
}
/// Creates an instance of [`ShapeEntryType`].
#[must_use]
pub fn new<G: CodeGenerator + ?Sized>(generator: &G, ctx: &'ctx Context) -> Self {
let llvm_usize = generator.get_size_type(ctx);
let llvm_ty = Self::llvm_type(ctx, llvm_usize);
Self { ty: llvm_ty, llvm_usize }
}
/// Creates a [`ShapeEntryType`] from a [`PointerType`] representing an `ShapeEntry`.
#[must_use]
pub fn from_type(ptr_ty: PointerType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
debug_assert!(Self::is_representable(ptr_ty, llvm_usize).is_ok());
Self { ty: ptr_ty, llvm_usize }
}
/// Allocates an instance of [`ShapeEntryValue`] as if by calling `alloca` on the base type.
#[must_use]
pub fn alloca(
&self,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value(
self.raw_alloca(ctx, name),
self.llvm_usize,
name,
)
}
/// Allocates an instance of [`ShapeEntryValue`] as if by calling `alloca` on the base type.
#[must_use]
pub fn alloca_var<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value(
self.raw_alloca_var(generator, ctx, name),
self.llvm_usize,
name,
)
}
/// Converts an existing value into a [`ShapeEntryValue`].
#[must_use]
pub fn map_value(
&self,
value: <<Self as ProxyType<'ctx>>::Value as ProxyValue<'ctx>>::Base,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value(value, self.llvm_usize, name)
}
}
impl<'ctx> ProxyType<'ctx> for ShapeEntryType<'ctx> {
type Base = PointerType<'ctx>;
type Value = ShapeEntryValue<'ctx>;
fn is_type<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
llvm_ty: impl BasicType<'ctx>,
) -> Result<(), String> {
if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() {
<Self as ProxyType<'ctx>>::is_representable(generator, ctx, ty)
} else {
Err(format!("Expected pointer type, got {llvm_ty:?}"))
}
}
fn is_representable<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
llvm_ty: Self::Base,
) -> Result<(), String> {
Self::is_representable(llvm_ty, generator.get_size_type(ctx))
}
fn alloca_type(&self) -> impl BasicType<'ctx> {
self.as_base_type().get_element_type().into_struct_type()
}
fn as_base_type(&self) -> Self::Base {
self.ty
}
}
impl<'ctx> From<ShapeEntryType<'ctx>> for PointerType<'ctx> {
fn from(value: ShapeEntryType<'ctx>) -> Self {
value.as_base_type()
}
}

View File

@ -16,7 +16,7 @@ use crate::{
}, },
ProxyType, ProxyType,
}, },
values::{ndarray::ContiguousNDArrayValue, ProxyValue}, values::{ndarray::ContiguousNDArrayValue, ArraySliceValue, ProxyValue},
CodeGenContext, CodeGenerator, CodeGenContext, CodeGenerator,
}, },
toplevel::numpy::unpack_ndarray_var_tys, toplevel::numpy::unpack_ndarray_var_tys,
@ -31,7 +31,7 @@ pub struct ContiguousNDArrayType<'ctx> {
} }
#[derive(PartialEq, Eq, Clone, Copy, StructFields)] #[derive(PartialEq, Eq, Clone, Copy, StructFields)]
pub struct ContiguousNDArrayStructFields<'ctx> { pub struct ContiguousNDArrayFields<'ctx> {
#[value_type(usize)] #[value_type(usize)]
pub ndims: StructField<'ctx, IntValue<'ctx>>, pub ndims: StructField<'ctx, IntValue<'ctx>>,
#[value_type(usize.ptr_type(AddressSpace::default()))] #[value_type(usize.ptr_type(AddressSpace::default()))]
@ -40,12 +40,12 @@ pub struct ContiguousNDArrayStructFields<'ctx> {
pub data: StructField<'ctx, PointerValue<'ctx>>, pub data: StructField<'ctx, PointerValue<'ctx>>,
} }
impl<'ctx> ContiguousNDArrayStructFields<'ctx> { impl<'ctx> ContiguousNDArrayFields<'ctx> {
#[must_use] #[must_use]
pub fn new_typed(item: BasicTypeEnum<'ctx>, llvm_usize: IntType<'ctx>) -> Self { pub fn new_typed(item: BasicTypeEnum<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
let mut counter = FieldIndexCounter::default(); let mut counter = FieldIndexCounter::default();
ContiguousNDArrayStructFields { ContiguousNDArrayFields {
ndims: StructField::create(&mut counter, "ndims", llvm_usize), ndims: StructField::create(&mut counter, "ndims", llvm_usize),
shape: StructField::create( shape: StructField::create(
&mut counter, &mut counter,
@ -72,7 +72,7 @@ impl<'ctx> ContiguousNDArrayType<'ctx> {
)); ));
}; };
let fields = ContiguousNDArrayStructFields::new(ctx, llvm_usize); let fields = ContiguousNDArrayFields::new(ctx, llvm_usize);
check_struct_type_matches_fields( check_struct_type_matches_fields(
fields, fields,
@ -93,14 +93,14 @@ impl<'ctx> ContiguousNDArrayType<'ctx> {
fn fields( fn fields(
item: BasicTypeEnum<'ctx>, item: BasicTypeEnum<'ctx>,
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
) -> ContiguousNDArrayStructFields<'ctx> { ) -> ContiguousNDArrayFields<'ctx> {
ContiguousNDArrayStructFields::new_typed(item, llvm_usize) ContiguousNDArrayFields::new_typed(item, llvm_usize)
} }
/// See [`NDArrayType::fields`]. /// See [`NDArrayType::fields`].
// TODO: Move this into e.g. StructProxyType // TODO: Move this into e.g. StructProxyType
#[must_use] #[must_use]
pub fn get_fields(&self) -> ContiguousNDArrayStructFields<'ctx> { pub fn get_fields(&self) -> ContiguousNDArrayFields<'ctx> {
Self::fields(self.item, self.llvm_usize) Self::fields(self.item, self.llvm_usize)
} }
@ -157,37 +157,16 @@ impl<'ctx> ContiguousNDArrayType<'ctx> {
Self { ty: ptr_ty, item, llvm_usize } Self { ty: ptr_ty, item, llvm_usize }
} }
/// Allocates an instance of [`ContiguousNDArrayValue`] as if by calling `alloca` on the base /// Allocates an instance of [`ContiguousNDArrayValue`] as if by calling `alloca` on the base type.
/// type.
///
/// See [`ProxyType::raw_alloca`].
#[must_use] #[must_use]
pub fn alloca( pub fn alloca<G: CodeGenerator + ?Sized>(
&self,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value(
self.raw_alloca(ctx, name),
self.item,
self.llvm_usize,
name,
)
}
/// Allocates an instance of [`ContiguousNDArrayValue`] as if by calling `alloca` on the base
/// type.
///
/// See [`ProxyType::raw_alloca_var`].
#[must_use]
pub fn alloca_var<G: CodeGenerator + ?Sized>(
&self, &self,
generator: &mut G, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>, name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value { ) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value( <Self as ProxyType<'ctx>>::Value::from_pointer_value(
self.raw_alloca_var(generator, ctx, name), self.raw_alloca(generator, ctx, name),
self.item, self.item,
self.llvm_usize, self.llvm_usize,
name, name,
@ -234,8 +213,36 @@ impl<'ctx> ProxyType<'ctx> for ContiguousNDArrayType<'ctx> {
Self::is_representable(llvm_ty, generator.get_size_type(ctx)) Self::is_representable(llvm_ty, generator.get_size_type(ctx))
} }
fn alloca_type(&self) -> impl BasicType<'ctx> { fn raw_alloca<G: CodeGenerator + ?Sized>(
self.as_base_type().get_element_type().into_struct_type() &self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>,
) -> <Self::Value as ProxyValue<'ctx>>::Base {
generator
.gen_var_alloc(
ctx,
self.as_base_type().get_element_type().into_struct_type().into(),
name,
)
.unwrap()
}
fn array_alloca<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
size: IntValue<'ctx>,
name: Option<&'ctx str>,
) -> ArraySliceValue<'ctx> {
generator
.gen_array_var_alloc(
ctx,
self.as_base_type().get_element_type().into_struct_type().into(),
size,
name,
)
.unwrap()
} }
fn as_base_type(&self) -> Self::Base { fn as_base_type(&self) -> Self::Base {

View File

@ -1,236 +0,0 @@
use inkwell::{
values::{BasicValueEnum, IntValue},
IntPredicate,
};
use super::NDArrayType;
use crate::{
codegen::{
irrt, types::ProxyType, values::TypedArrayLikeAccessor, CodeGenContext, CodeGenerator,
},
typecheck::typedef::Type,
};
/// Get the zero value in `np.zeros()` of a `dtype`.
fn ndarray_zero_value<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
dtype: Type,
) -> BasicValueEnum<'ctx> {
if [ctx.primitives.int32, ctx.primitives.uint32]
.iter()
.any(|ty| ctx.unifier.unioned(dtype, *ty))
{
ctx.ctx.i32_type().const_zero().into()
} else if [ctx.primitives.int64, ctx.primitives.uint64]
.iter()
.any(|ty| ctx.unifier.unioned(dtype, *ty))
{
ctx.ctx.i64_type().const_zero().into()
} else if ctx.unifier.unioned(dtype, ctx.primitives.float) {
ctx.ctx.f64_type().const_zero().into()
} else if ctx.unifier.unioned(dtype, ctx.primitives.bool) {
ctx.ctx.bool_type().const_zero().into()
} else if ctx.unifier.unioned(dtype, ctx.primitives.str) {
ctx.gen_string(generator, "").into()
} else {
panic!("unrecognized dtype: {}", ctx.unifier.stringify(dtype));
}
}
/// Get the one value in `np.ones()` of a `dtype`.
fn ndarray_one_value<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
dtype: Type,
) -> BasicValueEnum<'ctx> {
if [ctx.primitives.int32, ctx.primitives.uint32]
.iter()
.any(|ty| ctx.unifier.unioned(dtype, *ty))
{
let is_signed = ctx.unifier.unioned(dtype, ctx.primitives.int32);
ctx.ctx.i32_type().const_int(1, is_signed).into()
} else if [ctx.primitives.int64, ctx.primitives.uint64]
.iter()
.any(|ty| ctx.unifier.unioned(dtype, *ty))
{
let is_signed = ctx.unifier.unioned(dtype, ctx.primitives.int64);
ctx.ctx.i64_type().const_int(1, is_signed).into()
} else if ctx.unifier.unioned(dtype, ctx.primitives.float) {
ctx.ctx.f64_type().const_float(1.0).into()
} else if ctx.unifier.unioned(dtype, ctx.primitives.bool) {
ctx.ctx.bool_type().const_int(1, false).into()
} else if ctx.unifier.unioned(dtype, ctx.primitives.str) {
ctx.gen_string(generator, "1").into()
} else {
panic!("unrecognized dtype: {}", ctx.unifier.stringify(dtype));
}
}
impl<'ctx> NDArrayType<'ctx> {
/// Create an ndarray like
/// [`np.empty`](https://numpy.org/doc/stable/reference/generated/numpy.empty.html).
pub fn construct_numpy_empty<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
shape: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
let ndarray = self.construct_uninitialized(generator, ctx, name);
// Validate `shape`
irrt::ndarray::call_nac3_ndarray_util_assert_shape_no_negative(generator, ctx, shape);
ndarray.copy_shape_from_array(generator, ctx, shape.base_ptr(ctx, generator));
unsafe { ndarray.create_data(generator, ctx) };
ndarray
}
/// Create an ndarray like
/// [`np.full`](https://numpy.org/doc/stable/reference/generated/numpy.full.html).
pub fn construct_numpy_full<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
shape: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>,
fill_value: BasicValueEnum<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
let ndarray = self.construct_numpy_empty(generator, ctx, shape, name);
ndarray.fill(generator, ctx, fill_value);
ndarray
}
/// Create an ndarray like
/// [`np.zero`](https://numpy.org/doc/stable/reference/generated/numpy.zeros.html).
pub fn construct_numpy_zeros<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
dtype: Type,
shape: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
assert_eq!(
ctx.get_llvm_type(generator, dtype),
self.dtype,
"Expected LLVM dtype={} but got {}",
self.dtype.print_to_string(),
ctx.get_llvm_type(generator, dtype).print_to_string(),
);
let fill_value = ndarray_zero_value(generator, ctx, dtype);
self.construct_numpy_full(generator, ctx, shape, fill_value, name)
}
/// Create an ndarray like
/// [`np.ones`](https://numpy.org/doc/stable/reference/generated/numpy.ones.html).
pub fn construct_numpy_ones<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
dtype: Type,
shape: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
assert_eq!(
ctx.get_llvm_type(generator, dtype),
self.dtype,
"Expected LLVM dtype={} but got {}",
self.dtype.print_to_string(),
ctx.get_llvm_type(generator, dtype).print_to_string(),
);
let fill_value = ndarray_one_value(generator, ctx, dtype);
self.construct_numpy_full(generator, ctx, shape, fill_value, name)
}
/// Create an ndarray like
/// [`np.eye`](https://numpy.org/doc/stable/reference/generated/numpy.eye.html).
#[allow(clippy::too_many_arguments)]
pub fn construct_numpy_eye<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
dtype: Type,
nrows: IntValue<'ctx>,
ncols: IntValue<'ctx>,
offset: IntValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
assert_eq!(
ctx.get_llvm_type(generator, dtype),
self.dtype,
"Expected LLVM dtype={} but got {}",
self.dtype.print_to_string(),
ctx.get_llvm_type(generator, dtype).print_to_string(),
);
assert_eq!(nrows.get_type(), self.llvm_usize);
assert_eq!(ncols.get_type(), self.llvm_usize);
assert_eq!(offset.get_type(), self.llvm_usize);
let ndzero = ndarray_zero_value(generator, ctx, dtype);
let ndone = ndarray_one_value(generator, ctx, dtype);
let ndarray = self.construct_dyn_shape(generator, ctx, &[nrows, ncols], name);
// Create data and make the matrix like look np.eye()
unsafe {
ndarray.create_data(generator, ctx);
}
ndarray
.foreach(generator, ctx, |generator, ctx, _, nditer| {
// NOTE: rows and cols can never be zero here, since this ndarray's `np.size` would be zero
// and this loop would not execute.
let indices = nditer.get_indices();
let row_i = unsafe {
indices.get_typed_unchecked(ctx, generator, &self.llvm_usize.const_zero(), None)
};
let col_i = unsafe {
indices.get_typed_unchecked(
ctx,
generator,
&self.llvm_usize.const_int(1, false),
None,
)
};
let be_one = ctx
.builder
.build_int_compare(
IntPredicate::EQ,
ctx.builder.build_int_add(row_i, offset, "").unwrap(),
col_i,
"",
)
.unwrap();
let value = ctx.builder.build_select(be_one, ndone, ndzero, "value").unwrap();
let p = nditer.get_pointer(ctx);
ctx.builder.build_store(p, value).unwrap();
Ok(())
})
.unwrap();
ndarray
}
/// Create an ndarray like
/// [`np.identity`](https://numpy.org/doc/stable/reference/generated/numpy.identity.html).
pub fn construct_numpy_identity<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
dtype: Type,
size: IntValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
let offset = self.llvm_usize.const_zero();
self.construct_numpy_eye(generator, ctx, dtype, size, size, offset, name)
}
}

View File

@ -90,33 +90,15 @@ impl<'ctx> NDIndexType<'ctx> {
Self { ty: ptr_ty, llvm_usize } Self { ty: ptr_ty, llvm_usize }
} }
/// Allocates an instance of [`NDIndexValue`] as if by calling `alloca` on the base type.
///
/// See [`ProxyType::raw_alloca`].
#[must_use] #[must_use]
pub fn alloca( pub fn alloca<G: CodeGenerator + ?Sized>(
&self,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value(
self.raw_alloca(ctx, name),
self.llvm_usize,
name,
)
}
/// Allocates an instance of [`NDIndexValue`] as if by calling `alloca` on the base type.
///
/// See [`ProxyType::raw_alloca_var`].
#[must_use]
pub fn alloca_var<G: CodeGenerator + ?Sized>(
&self, &self,
generator: &mut G, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>, name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value { ) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value( <Self as ProxyType<'ctx>>::Value::from_pointer_value(
self.raw_alloca_var(generator, ctx, name), self.raw_alloca(generator, ctx, name),
self.llvm_usize, self.llvm_usize,
name, name,
) )
@ -132,7 +114,7 @@ impl<'ctx> NDIndexType<'ctx> {
) -> ArraySliceValue<'ctx> { ) -> ArraySliceValue<'ctx> {
// Allocate the LLVM ndindices. // Allocate the LLVM ndindices.
let num_ndindices = self.llvm_usize.const_int(in_ndindices.len() as u64, false); let num_ndindices = self.llvm_usize.const_int(in_ndindices.len() as u64, false);
let ndindices = self.array_alloca_var(generator, ctx, num_ndindices, None); let ndindices = self.array_alloca(generator, ctx, num_ndindices, None);
// Initialize all of them. // Initialize all of them.
for (i, in_ndindex) in in_ndindices.iter().enumerate() { for (i, in_ndindex) in in_ndindices.iter().enumerate() {
@ -189,8 +171,36 @@ impl<'ctx> ProxyType<'ctx> for NDIndexType<'ctx> {
Self::is_representable(llvm_ty, generator.get_size_type(ctx)) Self::is_representable(llvm_ty, generator.get_size_type(ctx))
} }
fn alloca_type(&self) -> impl BasicType<'ctx> { fn raw_alloca<G: CodeGenerator + ?Sized>(
self.as_base_type().get_element_type().into_struct_type() &self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>,
) -> <Self::Value as ProxyValue<'ctx>>::Base {
generator
.gen_var_alloc(
ctx,
self.as_base_type().get_element_type().into_struct_type().into(),
name,
)
.unwrap()
}
fn array_alloca<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
size: IntValue<'ctx>,
name: Option<&'ctx str>,
) -> ArraySliceValue<'ctx> {
generator
.gen_array_var_alloc(
ctx,
self.as_base_type().get_element_type().into_struct_type().into(),
size,
name,
)
.unwrap()
} }
fn as_base_type(&self) -> Self::Base { fn as_base_type(&self) -> Self::Base {

View File

@ -1,187 +0,0 @@
use inkwell::{types::BasicTypeEnum, values::BasicValueEnum};
use itertools::Itertools;
use crate::codegen::{
stmt::gen_for_callback,
types::{
ndarray::{NDArrayType, NDIterType},
ProxyType,
},
values::{
ndarray::{NDArrayOut, NDArrayValue, ScalarOrNDArray},
ArrayLikeValue, ProxyValue,
},
CodeGenContext, CodeGenerator,
};
impl<'ctx> NDArrayType<'ctx> {
/// Generate LLVM IR to broadcast `ndarray`s together, and starmap through them with `mapping`
/// elementwise.
///
/// `mapping` is an LLVM IR generator. The input of `mapping` is the list of elements when
/// iterating through the input `ndarrays` after broadcasting. The output of `mapping` is the
/// result of the elementwise operation.
///
/// `out` specifies whether the result should be a new ndarray or to be written an existing
/// ndarray.
pub fn broadcast_starmap<'a, G, MappingFn>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,
ndarrays: &[NDArrayValue<'ctx>],
out: NDArrayOut<'ctx>,
mapping: MappingFn,
) -> Result<<Self as ProxyType<'ctx>>::Value, String>
where
G: CodeGenerator + ?Sized,
MappingFn: FnOnce(
&mut G,
&mut CodeGenContext<'ctx, 'a>,
&[BasicValueEnum<'ctx>],
) -> Result<BasicValueEnum<'ctx>, String>,
{
// Broadcast inputs
let broadcast_result = self.broadcast(generator, ctx, ndarrays);
let out_ndarray = match out {
NDArrayOut::NewNDArray { dtype } => {
// Create a new ndarray based on the broadcast shape.
let result_ndarray =
NDArrayType::new(generator, ctx.ctx, dtype, broadcast_result.ndims)
.construct_uninitialized(generator, ctx, None);
result_ndarray.copy_shape_from_array(
generator,
ctx,
broadcast_result.shape.base_ptr(ctx, generator),
);
unsafe {
result_ndarray.create_data(generator, ctx);
}
result_ndarray
}
NDArrayOut::WriteToNDArray { ndarray: result_ndarray } => {
// Use an existing ndarray.
// Check that its shape is compatible with the broadcast shape.
result_ndarray.assert_can_be_written_by_out(generator, ctx, broadcast_result.shape);
result_ndarray
}
};
// Map element-wise and store results into `mapped_ndarray`.
let nditer = NDIterType::new(generator, ctx.ctx).construct(generator, ctx, out_ndarray);
gen_for_callback(
generator,
ctx,
Some("broadcast_starmap"),
|generator, ctx| {
// Create NDIters for all broadcasted input ndarrays.
let other_nditers = broadcast_result
.ndarrays
.iter()
.map(|ndarray| {
NDIterType::new(generator, ctx.ctx).construct(generator, ctx, *ndarray)
})
.collect_vec();
Ok((nditer, other_nditers))
},
|generator, ctx, (out_nditer, _in_nditers)| {
// We can simply use `out_nditer`'s `has_element()`.
// `in_nditers`' `has_element()`s should return the same value.
Ok(out_nditer.has_element(generator, ctx))
},
|generator, ctx, _hooks, (out_nditer, in_nditers)| {
// Get all the scalars from the broadcasted input ndarrays, pass them to `mapping`,
// and write to `out_ndarray`.
let in_scalars =
in_nditers.iter().map(|nditer| nditer.get_scalar(ctx)).collect_vec();
let result = mapping(generator, ctx, &in_scalars)?;
let p = out_nditer.get_pointer(ctx);
ctx.builder.build_store(p, result).unwrap();
Ok(())
},
|generator, ctx, (out_nditer, in_nditers)| {
// Advance all iterators
out_nditer.next(generator, ctx);
in_nditers.iter().for_each(|nditer| nditer.next(generator, ctx));
Ok(())
},
)?;
Ok(out_ndarray)
}
}
impl<'ctx> ScalarOrNDArray<'ctx> {
/// Starmap through a list of inputs using `mapping`, where an input could be an ndarray, a
/// scalar.
///
/// This function is very helpful when implementing NumPy functions that takes on either scalars
/// or ndarrays or a mix of them as their inputs and produces either an ndarray with broadcast,
/// or a scalar if all its inputs are all scalars.
///
/// For example ,this function can be used to implement `np.add`, which has the following
/// behaviors:
///
/// - `np.add(3, 4) = 7` # (scalar, scalar) -> scalar
/// - `np.add(3, np.array([4, 5, 6]))` # (scalar, ndarray) -> ndarray; the first `scalar` is
/// converted into an ndarray and broadcasted.
/// - `np.add(np.array([[1], [2], [3]]), np.array([[4, 5, 6]]))` # (ndarray, ndarray) ->
/// ndarray; there is broadcasting.
///
/// ## Details:
///
/// If `inputs` are all [`ScalarOrNDArray::Scalar`], the output will be a
/// [`ScalarOrNDArray::Scalar`] with type `ret_dtype`.
///
/// Otherwise (if there are any [`ScalarOrNDArray::NDArray`] in `inputs`), all inputs will be
/// 'as-ndarray'-ed into ndarrays, then all inputs (now all ndarrays) will be passed to
/// [`NDArrayValue::broadcasting_starmap`] and **create** a new ndarray with dtype `ret_dtype`.
pub fn broadcasting_starmap<'a, G, MappingFn>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,
inputs: &[ScalarOrNDArray<'ctx>],
ret_dtype: BasicTypeEnum<'ctx>,
mapping: MappingFn,
) -> Result<ScalarOrNDArray<'ctx>, String>
where
G: CodeGenerator + ?Sized,
MappingFn: FnOnce(
&mut G,
&mut CodeGenContext<'ctx, 'a>,
&[BasicValueEnum<'ctx>],
) -> Result<BasicValueEnum<'ctx>, String>,
{
// Check if all inputs are Scalars
let all_scalars: Option<Vec<_>> =
inputs.iter().map(BasicValueEnum::<'ctx>::try_from).try_collect().ok();
if let Some(scalars) = all_scalars {
let scalars = scalars.iter().copied().collect_vec();
let value = mapping(generator, ctx, &scalars)?;
Ok(ScalarOrNDArray::Scalar(value))
} else {
// Promote all input to ndarrays and map through them.
let inputs = inputs.iter().map(|input| input.to_ndarray(generator, ctx)).collect_vec();
let ndarray = NDArrayType::new_broadcast(
generator,
ctx.ctx,
ret_dtype,
&inputs.iter().map(NDArrayValue::get_type).collect_vec(),
)
.broadcast_starmap(
generator,
ctx,
&inputs,
NDArrayOut::NewNDArray { dtype: ret_dtype },
mapping,
)?;
Ok(ScalarOrNDArray::NDArray(ndarray))
}
}
}

View File

@ -14,23 +14,18 @@ use super::{
}; };
use crate::{ use crate::{
codegen::{ codegen::{
values::{ndarray::NDArrayValue, ProxyValue, TypedArrayLikeMutator}, values::{ndarray::NDArrayValue, ArraySliceValue, ProxyValue, TypedArrayLikeMutator},
{CodeGenContext, CodeGenerator}, {CodeGenContext, CodeGenerator},
}, },
toplevel::{helper::extract_ndims, numpy::unpack_ndarray_var_tys}, toplevel::{helper::extract_ndims, numpy::unpack_ndarray_var_tys},
typecheck::typedef::Type, typecheck::typedef::Type,
}; };
pub use broadcast::*;
pub use contiguous::*; pub use contiguous::*;
pub use indexing::*; pub use indexing::*;
pub use nditer::*; pub use nditer::*;
mod array;
mod broadcast;
mod contiguous; mod contiguous;
pub mod factory;
mod indexing; mod indexing;
mod map;
mod nditer; mod nditer;
/// Proxy type for a `ndarray` type in LLVM. /// Proxy type for a `ndarray` type in LLVM.
@ -38,7 +33,7 @@ mod nditer;
pub struct NDArrayType<'ctx> { pub struct NDArrayType<'ctx> {
ty: PointerType<'ctx>, ty: PointerType<'ctx>,
dtype: BasicTypeEnum<'ctx>, dtype: BasicTypeEnum<'ctx>,
ndims: u64, ndims: Option<u64>,
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
} }
@ -113,7 +108,7 @@ impl<'ctx> NDArrayType<'ctx> {
generator: &G, generator: &G,
ctx: &'ctx Context, ctx: &'ctx Context,
dtype: BasicTypeEnum<'ctx>, dtype: BasicTypeEnum<'ctx>,
ndims: u64, ndims: Option<u64>,
) -> Self { ) -> Self {
let llvm_usize = generator.get_size_type(ctx); let llvm_usize = generator.get_size_type(ctx);
let llvm_ndarray = Self::llvm_type(ctx, llvm_usize); let llvm_ndarray = Self::llvm_type(ctx, llvm_usize);
@ -121,20 +116,6 @@ impl<'ctx> NDArrayType<'ctx> {
NDArrayType { ty: llvm_ndarray, dtype, ndims, llvm_usize } NDArrayType { ty: llvm_ndarray, dtype, ndims, llvm_usize }
} }
/// Creates an instance of [`NDArrayType`] as a result of a broadcast operation over one or more
/// `ndarray` operands.
#[must_use]
pub fn new_broadcast<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
dtype: BasicTypeEnum<'ctx>,
inputs: &[NDArrayType<'ctx>],
) -> Self {
assert!(!inputs.is_empty());
Self::new(generator, ctx, dtype, inputs.iter().map(NDArrayType::ndims).max().unwrap())
}
/// Creates an instance of [`NDArrayType`] with `ndims` of 0. /// Creates an instance of [`NDArrayType`] with `ndims` of 0.
#[must_use] #[must_use]
pub fn new_unsized<G: CodeGenerator + ?Sized>( pub fn new_unsized<G: CodeGenerator + ?Sized>(
@ -145,7 +126,7 @@ impl<'ctx> NDArrayType<'ctx> {
let llvm_usize = generator.get_size_type(ctx); let llvm_usize = generator.get_size_type(ctx);
let llvm_ndarray = Self::llvm_type(ctx, llvm_usize); let llvm_ndarray = Self::llvm_type(ctx, llvm_usize);
NDArrayType { ty: llvm_ndarray, dtype, ndims: 0, llvm_usize } NDArrayType { ty: llvm_ndarray, dtype, ndims: Some(0), llvm_usize }
} }
/// Creates an [`NDArrayType`] from a [unifier type][Type]. /// Creates an [`NDArrayType`] from a [unifier type][Type].
@ -164,7 +145,7 @@ impl<'ctx> NDArrayType<'ctx> {
NDArrayType { NDArrayType {
ty: Self::llvm_type(ctx.ctx, llvm_usize), ty: Self::llvm_type(ctx.ctx, llvm_usize),
dtype: llvm_dtype, dtype: llvm_dtype,
ndims, ndims: Some(ndims),
llvm_usize, llvm_usize,
} }
} }
@ -174,7 +155,7 @@ impl<'ctx> NDArrayType<'ctx> {
pub fn from_type( pub fn from_type(
ptr_ty: PointerType<'ctx>, ptr_ty: PointerType<'ctx>,
dtype: BasicTypeEnum<'ctx>, dtype: BasicTypeEnum<'ctx>,
ndims: u64, ndims: Option<u64>,
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
) -> Self { ) -> Self {
debug_assert!(Self::is_representable(ptr_ty, llvm_usize).is_ok()); debug_assert!(Self::is_representable(ptr_ty, llvm_usize).is_ok());
@ -196,40 +177,20 @@ impl<'ctx> NDArrayType<'ctx> {
/// Returns the number of dimensions of this `ndarray` type. /// Returns the number of dimensions of this `ndarray` type.
#[must_use] #[must_use]
pub fn ndims(&self) -> u64 { pub fn ndims(&self) -> Option<u64> {
self.ndims self.ndims
} }
/// Allocates an instance of [`NDArrayValue`] as if by calling `alloca` on the base type. /// Allocates an instance of [`NDArrayValue`] as if by calling `alloca` on the base type.
///
/// See [`ProxyType::raw_alloca`].
#[must_use] #[must_use]
pub fn alloca( pub fn alloca<G: CodeGenerator + ?Sized>(
&self,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value(
self.raw_alloca(ctx, name),
self.dtype,
self.ndims,
self.llvm_usize,
name,
)
}
/// Allocates an instance of [`NDArrayValue`] as if by calling `alloca` on the base type.
///
/// See [`ProxyType::raw_alloca_var`].
#[must_use]
pub fn alloca_var<G: CodeGenerator + ?Sized>(
&self, &self,
generator: &mut G, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>, name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value { ) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value( <Self as ProxyType<'ctx>>::Value::from_pointer_value(
self.raw_alloca_var(generator, ctx, name), self.raw_alloca(generator, ctx, name),
self.dtype, self.dtype,
self.ndims, self.ndims,
self.llvm_usize, self.llvm_usize,
@ -253,7 +214,7 @@ impl<'ctx> NDArrayType<'ctx> {
ndims: IntValue<'ctx>, ndims: IntValue<'ctx>,
name: Option<&'ctx str>, name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value { ) -> <Self as ProxyType<'ctx>>::Value {
let ndarray = self.alloca_var(generator, ctx, name); let ndarray = self.alloca(generator, ctx, name);
let itemsize = ctx let itemsize = ctx
.builder .builder
@ -286,7 +247,35 @@ impl<'ctx> NDArrayType<'ctx> {
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>, name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value { ) -> <Self as ProxyType<'ctx>>::Value {
let ndims = self.llvm_usize.const_int(self.ndims, false); assert!(self.ndims.is_some(), "NDArrayType::construct can only be called on an instance with compile-time known ndims (self.ndims = Some(ndims))");
let Some(ndims) = self.ndims.map(|ndims| self.llvm_usize.const_int(ndims, false)) else {
unreachable!()
};
self.construct_impl(generator, ctx, ndims, name)
}
/// Allocate an [`NDArrayValue`] on the stack given its `ndims` and `dtype`.
///
/// `shape` and `strides` will be automatically allocated onto the stack.
///
/// The returned ndarray's content will be:
/// - `data`: uninitialized.
/// - `itemsize`: set to the size of `dtype`.
/// - `ndims`: set to the value of `ndims`.
/// - `shape`: allocated with an array of length `ndims` with uninitialized values.
/// - `strides`: allocated with an array of length `ndims` with uninitialized values.
#[deprecated = "Prefer construct_uninitialized or construct_*_shape."]
#[must_use]
pub fn construct_dyn_ndims<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
ndims: IntValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
assert!(self.ndims.is_none(), "NDArrayType::construct_dyn_ndims can only be called on an instance with compile-time unknown ndims (self.ndims = None)");
self.construct_impl(generator, ctx, ndims, name) self.construct_impl(generator, ctx, ndims, name)
} }
@ -302,9 +291,9 @@ impl<'ctx> NDArrayType<'ctx> {
shape: &[u64], shape: &[u64],
name: Option<&'ctx str>, name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value { ) -> <Self as ProxyType<'ctx>>::Value {
assert_eq!(shape.len() as u64, self.ndims); assert!(self.ndims.is_none_or(|ndims| shape.len() as u64 == ndims));
let ndarray = Self::new(generator, ctx.ctx, self.dtype, shape.len() as u64) let ndarray = Self::new(generator, ctx.ctx, self.dtype, Some(shape.len() as u64))
.construct_uninitialized(generator, ctx, name); .construct_uninitialized(generator, ctx, name);
let llvm_usize = generator.get_size_type(ctx.ctx); let llvm_usize = generator.get_size_type(ctx.ctx);
@ -337,9 +326,9 @@ impl<'ctx> NDArrayType<'ctx> {
shape: &[IntValue<'ctx>], shape: &[IntValue<'ctx>],
name: Option<&'ctx str>, name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value { ) -> <Self as ProxyType<'ctx>>::Value {
assert_eq!(shape.len() as u64, self.ndims); assert!(self.ndims.is_none_or(|ndims| shape.len() as u64 == ndims));
let ndarray = Self::new(generator, ctx.ctx, self.dtype, shape.len() as u64) let ndarray = Self::new(generator, ctx.ctx, self.dtype, Some(shape.len() as u64))
.construct_uninitialized(generator, ctx, name); .construct_uninitialized(generator, ctx, name);
let llvm_usize = generator.get_size_type(ctx.ctx); let llvm_usize = generator.get_size_type(ctx.ctx);
@ -379,7 +368,7 @@ impl<'ctx> NDArrayType<'ctx> {
let value = value.as_basic_value_enum(); let value = value.as_basic_value_enum();
assert_eq!(value.get_type(), self.dtype); assert_eq!(value.get_type(), self.dtype);
assert_eq!(self.ndims, 0); assert!(self.ndims.is_none_or(|ndims| ndims == 0));
// We have to put the value on the stack to get a data pointer. // We have to put the value on the stack to get a data pointer.
let data = ctx.builder.build_alloca(value.get_type(), "construct_unsized").unwrap(); let data = ctx.builder.build_alloca(value.get_type(), "construct_unsized").unwrap();
@ -436,8 +425,36 @@ impl<'ctx> ProxyType<'ctx> for NDArrayType<'ctx> {
Self::is_representable(llvm_ty, generator.get_size_type(ctx)) Self::is_representable(llvm_ty, generator.get_size_type(ctx))
} }
fn alloca_type(&self) -> impl BasicType<'ctx> { fn raw_alloca<G: CodeGenerator + ?Sized>(
self.as_base_type().get_element_type().into_struct_type() &self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>,
) -> <Self::Value as ProxyValue<'ctx>>::Base {
generator
.gen_var_alloc(
ctx,
self.as_base_type().get_element_type().into_struct_type().into(),
name,
)
.unwrap()
}
fn array_alloca<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
size: IntValue<'ctx>,
name: Option<&'ctx str>,
) -> ArraySliceValue<'ctx> {
generator
.gen_array_var_alloc(
ctx,
self.as_base_type().get_element_type().into_struct_type().into(),
size,
name,
)
.unwrap()
} }
fn as_base_type(&self) -> Self::Base { fn as_base_type(&self) -> Self::Base {

View File

@ -14,7 +14,7 @@ use crate::codegen::{
types::structure::{check_struct_type_matches_fields, StructField, StructFields}, types::structure::{check_struct_type_matches_fields, StructField, StructFields},
values::{ values::{
ndarray::{NDArrayValue, NDIterValue}, ndarray::{NDArrayValue, NDIterValue},
ArrayLikeValue, ArraySliceValue, ProxyValue, TypedArrayLikeAdapter, ArraySliceValue, ProxyValue,
}, },
CodeGenContext, CodeGenerator, CodeGenContext, CodeGenerator,
}; };
@ -109,31 +109,8 @@ impl<'ctx> NDIterType<'ctx> {
self.llvm_usize self.llvm_usize
} }
/// Allocates an instance of [`NDIterValue`] as if by calling `alloca` on the base type.
///
/// See [`ProxyType::raw_alloca`].
#[must_use] #[must_use]
pub fn alloca( pub fn alloca<G: CodeGenerator + ?Sized>(
&self,
ctx: &mut CodeGenContext<'ctx, '_>,
parent: NDArrayValue<'ctx>,
indices: ArraySliceValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value(
self.raw_alloca(ctx, name),
parent,
indices,
self.llvm_usize,
name,
)
}
/// Allocates an instance of [`NDIterValue`] as if by calling `alloca` on the base type.
///
/// See [`ProxyType::raw_alloca_var`].
#[must_use]
pub fn alloca_var<G: CodeGenerator + ?Sized>(
&self, &self,
generator: &mut G, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
@ -142,7 +119,7 @@ impl<'ctx> NDIterType<'ctx> {
name: Option<&'ctx str>, name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value { ) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value( <Self as ProxyType<'ctx>>::Value::from_pointer_value(
self.raw_alloca_var(generator, ctx, name), self.raw_alloca(generator, ctx, name),
parent, parent,
indices, indices,
self.llvm_usize, self.llvm_usize,
@ -151,11 +128,6 @@ impl<'ctx> NDIterType<'ctx> {
} }
/// Allocate an [`NDIter`] that iterates through the given `ndarray`. /// Allocate an [`NDIter`] that iterates through the given `ndarray`.
///
/// Note: This function allocates an array on the stack at the current builder location, which
/// may lead to stack explosion if called in a hot loop. Therefore, callers are recommended to
/// call `llvm.stacksave` before calling this function and call `llvm.stackrestore` after the
/// [`NDIter`] is no longer needed.
#[must_use] #[must_use]
pub fn construct<G: CodeGenerator + ?Sized>( pub fn construct<G: CodeGenerator + ?Sized>(
&self, &self,
@ -163,18 +135,22 @@ impl<'ctx> NDIterType<'ctx> {
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
ndarray: NDArrayValue<'ctx>, ndarray: NDArrayValue<'ctx>,
) -> <Self as ProxyType<'ctx>>::Value { ) -> <Self as ProxyType<'ctx>>::Value {
let nditer = self.raw_alloca_var(generator, ctx, None); let nditer = self.raw_alloca(generator, ctx, None);
let ndims = self.llvm_usize.const_int(ndarray.get_type().ndims(), false); let ndims = ndarray.load_ndims(ctx);
// The caller has the responsibility to allocate 'indices' for `NDIter`. // The caller has the responsibility to allocate 'indices' for `NDIter`.
let indices = let indices =
generator.gen_array_var_alloc(ctx, self.llvm_usize.into(), ndims, None).unwrap(); generator.gen_array_var_alloc(ctx, self.llvm_usize.into(), ndims, None).unwrap();
let indices =
TypedArrayLikeAdapter::from(indices, |_, _, v| v.into_int_value(), |_, _, v| v.into());
let nditer = self.map_value(nditer, ndarray, indices.as_slice_value(ctx, generator), None); let nditer = <Self as ProxyType<'ctx>>::Value::from_pointer_value(
nditer,
ndarray,
indices,
self.llvm_usize,
None,
);
irrt::ndarray::call_nac3_nditer_initialize(generator, ctx, nditer, ndarray, &indices); irrt::ndarray::call_nac3_nditer_initialize(generator, ctx, nditer, ndarray, indices);
nditer nditer
} }
@ -221,8 +197,36 @@ impl<'ctx> ProxyType<'ctx> for NDIterType<'ctx> {
Self::is_representable(llvm_ty, generator.get_size_type(ctx)) Self::is_representable(llvm_ty, generator.get_size_type(ctx))
} }
fn alloca_type(&self) -> impl BasicType<'ctx> { fn raw_alloca<G: CodeGenerator + ?Sized>(
self.as_base_type().get_element_type().into_struct_type() &self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>,
) -> <Self::Value as ProxyValue<'ctx>>::Base {
generator
.gen_var_alloc(
ctx,
self.as_base_type().get_element_type().into_struct_type().into(),
name,
)
.unwrap()
}
fn array_alloca<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
size: IntValue<'ctx>,
name: Option<&'ctx str>,
) -> ArraySliceValue<'ctx> {
generator
.gen_array_var_alloc(
ctx,
self.as_base_type().get_element_type().into_struct_type().into(),
size,
name,
)
.unwrap()
} }
fn as_base_type(&self) -> Self::Base { fn as_base_type(&self) -> Self::Base {

View File

@ -1,12 +1,13 @@
use inkwell::{ use inkwell::{
context::Context, context::Context,
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType}, types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType},
values::IntValue,
AddressSpace, AddressSpace,
}; };
use super::ProxyType; use super::ProxyType;
use crate::codegen::{ use crate::codegen::{
values::{ProxyValue, RangeValue}, values::{ArraySliceValue, ProxyValue, RangeValue},
{CodeGenContext, CodeGenerator}, {CodeGenContext, CodeGenerator},
}; };
@ -77,29 +78,15 @@ impl<'ctx> RangeType<'ctx> {
} }
/// Allocates an instance of [`RangeValue`] as if by calling `alloca` on the base type. /// Allocates an instance of [`RangeValue`] as if by calling `alloca` on the base type.
///
/// See [`ProxyType::raw_alloca`].
#[must_use] #[must_use]
pub fn alloca<G: CodeGenerator + ?Sized>( pub fn alloca<G: CodeGenerator + ?Sized>(
&self,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value(self.raw_alloca(ctx, name), name)
}
/// Allocates an instance of [`RangeValue`] as if by calling `alloca` on the base type.
///
/// See [`ProxyType::raw_alloca_var`].
#[must_use]
pub fn alloca_var<G: CodeGenerator + ?Sized>(
&self, &self,
generator: &mut G, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>, name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value { ) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value( <Self as ProxyType<'ctx>>::Value::from_pointer_value(
self.raw_alloca_var(generator, ctx, name), self.raw_alloca(generator, ctx, name),
name, name,
) )
} }
@ -139,8 +126,36 @@ impl<'ctx> ProxyType<'ctx> for RangeType<'ctx> {
Self::is_representable(llvm_ty) Self::is_representable(llvm_ty)
} }
fn alloca_type(&self) -> impl BasicType<'ctx> { fn raw_alloca<G: CodeGenerator + ?Sized>(
self.as_base_type().get_element_type().into_struct_type() &self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>,
) -> <Self::Value as ProxyValue<'ctx>>::Base {
generator
.gen_var_alloc(
ctx,
self.as_base_type().get_element_type().into_struct_type().into(),
name,
)
.unwrap()
}
fn array_alloca<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
size: IntValue<'ctx>,
name: Option<&'ctx str>,
) -> ArraySliceValue<'ctx> {
generator
.gen_array_var_alloc(
ctx,
self.as_base_type().get_element_type().into_struct_type().into(),
size,
name,
)
.unwrap()
} }
fn as_base_type(&self) -> Self::Base { fn as_base_type(&self) -> Self::Base {

View File

@ -5,7 +5,6 @@ use inkwell::{
types::{BasicTypeEnum, IntType, StructType}, types::{BasicTypeEnum, IntType, StructType},
values::{BasicValue, BasicValueEnum, IntValue, PointerValue, StructValue}, values::{BasicValue, BasicValueEnum, IntValue, PointerValue, StructValue},
}; };
use itertools::Itertools;
use crate::codegen::CodeGenContext; use crate::codegen::CodeGenContext;
@ -56,20 +55,6 @@ pub trait StructFields<'ctx>: Eq + Copy {
{ {
self.into_vec().into_iter() self.into_vec().into_iter()
} }
/// Returns the field index of a field in this structure.
fn index_of_field<V>(&self, name: impl FnOnce(&Self) -> StructField<'ctx, V>) -> u32
where
V: BasicValue<'ctx> + TryFrom<BasicValueEnum<'ctx>, Error = ()>,
{
let field_name = name(self).name;
self.index_of_field_name(field_name).unwrap()
}
/// Returns the field index of a field with the given name in this structure.
fn index_of_field_name(&self, field_name: &str) -> Option<u32> {
self.iter().find_position(|(name, _)| *name == field_name).map(|(idx, _)| idx as u32)
}
} }
/// A single field of an LLVM structure. /// A single field of an LLVM structure.

View File

@ -1,184 +0,0 @@
use inkwell::{
context::Context,
types::{BasicType, BasicTypeEnum, IntType, StructType},
values::BasicValueEnum,
};
use itertools::Itertools;
use super::ProxyType;
use crate::{
codegen::{
values::{ProxyValue, TupleValue},
CodeGenContext, CodeGenerator,
},
typecheck::typedef::{Type, TypeEnum},
};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct TupleType<'ctx> {
ty: StructType<'ctx>,
llvm_usize: IntType<'ctx>,
}
impl<'ctx> TupleType<'ctx> {
/// Checks whether `llvm_ty` represents any tuple type, returning [Err] if it does not.
pub fn is_representable(_value: StructType<'ctx>) -> Result<(), String> {
Ok(())
}
/// Creates an LLVM type corresponding to the expected structure of a tuple.
#[must_use]
fn llvm_type(ctx: &'ctx Context, tys: &[BasicTypeEnum<'ctx>]) -> StructType<'ctx> {
ctx.struct_type(tys, false)
}
/// Creates an instance of [`TupleType`].
#[must_use]
pub fn new<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
tys: &[BasicTypeEnum<'ctx>],
) -> Self {
let llvm_usize = generator.get_size_type(ctx);
let llvm_tuple = Self::llvm_type(ctx, tys);
Self { ty: llvm_tuple, llvm_usize }
}
/// Creates an [`TupleType`] from a [unifier type][Type].
#[must_use]
pub fn from_unifier_type<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &mut CodeGenContext<'ctx, '_>,
ty: Type,
) -> Self {
let llvm_usize = generator.get_size_type(ctx.ctx);
// Sanity check on object type.
let TypeEnum::TTuple { ty: tys, .. } = &*ctx.unifier.get_ty_immutable(ty) else {
panic!("Expected type to be a TypeEnum::TTuple, got {}", ctx.unifier.stringify(ty));
};
let llvm_tys = tys.iter().map(|ty| ctx.get_llvm_type(generator, *ty)).collect_vec();
Self { ty: Self::llvm_type(ctx.ctx, &llvm_tys), llvm_usize }
}
/// Creates an [`TupleType`] from a [`StructType`].
#[must_use]
pub fn from_type(struct_ty: StructType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
debug_assert!(Self::is_representable(struct_ty).is_ok());
TupleType { ty: struct_ty, llvm_usize }
}
/// Returns the number of elements present in this [`TupleType`].
#[must_use]
pub fn num_elements(&self) -> u32 {
self.ty.count_fields()
}
/// Returns the type of the tuple element at the given `index`, or [`None`] if `index` is out of
/// range.
#[must_use]
pub fn type_at_index(&self, index: u32) -> Option<BasicTypeEnum<'ctx>> {
if index < self.num_elements() {
Some(unsafe { self.type_at_index_unchecked(index) })
} else {
None
}
}
/// Returns the type of the tuple element at the given `index`.
///
/// # Safety
///
/// The caller must ensure that the index is valid.
#[must_use]
pub unsafe fn type_at_index_unchecked(&self, index: u32) -> BasicTypeEnum<'ctx> {
self.ty.get_field_type_at_index_unchecked(index)
}
/// Constructs a [`TupleValue`] from this type by zero-initializing the tuple value.
#[must_use]
pub fn construct(
&self,
ctx: &CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
self.map_value(Self::llvm_type(ctx.ctx, &self.ty.get_field_types()).const_zero(), name)
}
/// Constructs a [`TupleValue`] from `objects`. The resulting tuple preserves the order of
/// objects.
#[must_use]
pub fn construct_from_objects<I: IntoIterator<Item = BasicValueEnum<'ctx>>>(
&self,
ctx: &CodeGenContext<'ctx, '_>,
objects: I,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
let values = objects.into_iter().collect_vec();
assert_eq!(values.len(), self.num_elements() as usize);
assert!(values
.iter()
.enumerate()
.all(|(i, v)| { v.get_type() == unsafe { self.type_at_index_unchecked(i as u32) } }));
let mut value = self.construct(ctx, name);
for (i, val) in values.into_iter().enumerate() {
value.store_element(ctx, i as u32, val);
}
value
}
/// Converts an existing value into a [`ListValue`].
#[must_use]
pub fn map_value(
&self,
value: <<Self as ProxyType<'ctx>>::Value as ProxyValue<'ctx>>::Base,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_struct_value(value, self.llvm_usize, name)
}
}
impl<'ctx> ProxyType<'ctx> for TupleType<'ctx> {
type Base = StructType<'ctx>;
type Value = TupleValue<'ctx>;
fn is_type<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
llvm_ty: impl BasicType<'ctx>,
) -> Result<(), String> {
if let BasicTypeEnum::StructType(ty) = llvm_ty.as_basic_type_enum() {
<Self as ProxyType<'ctx>>::is_representable(generator, ctx, ty)
} else {
Err(format!("Expected struct type, got {llvm_ty:?}"))
}
}
fn is_representable<G: CodeGenerator + ?Sized>(
_generator: &G,
_ctx: &'ctx Context,
llvm_ty: Self::Base,
) -> Result<(), String> {
Self::is_representable(llvm_ty)
}
fn alloca_type(&self) -> impl BasicType<'ctx> {
self.as_base_type()
}
fn as_base_type(&self) -> Self::Base {
self.ty
}
}
impl<'ctx> From<TupleType<'ctx>> for StructType<'ctx> {
fn from(value: TupleType<'ctx>) -> Self {
value.as_base_type()
}
}

View File

@ -15,7 +15,7 @@ use crate::codegen::{
}, },
ProxyType, ProxyType,
}, },
values::{utils::SliceValue, ProxyValue}, values::{utils::SliceValue, ArraySliceValue, ProxyValue},
CodeGenContext, CodeGenerator, CodeGenContext, CodeGenerator,
}; };
@ -154,35 +154,16 @@ impl<'ctx> SliceType<'ctx> {
self.int_ty self.int_ty
} }
/// Allocates an instance of [`SliceValue`] as if by calling `alloca` on the base type. /// Allocates an instance of [`ContiguousNDArrayValue`] as if by calling `alloca` on the base type.
///
/// See [`ProxyType::raw_alloca`].
#[must_use] #[must_use]
pub fn alloca( pub fn alloca<G: CodeGenerator + ?Sized>(
&self,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value(
self.raw_alloca(ctx, name),
self.int_ty,
self.llvm_usize,
name,
)
}
/// Allocates an instance of [`SliceValue`] as if by calling `alloca` on the base type.
///
/// See [`ProxyType::raw_alloca_var`].
#[must_use]
pub fn alloca_var<G: CodeGenerator + ?Sized>(
&self, &self,
generator: &mut G, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>, name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value { ) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value( <Self as ProxyType<'ctx>>::Value::from_pointer_value(
self.raw_alloca_var(generator, ctx, name), self.raw_alloca(generator, ctx, name),
self.int_ty, self.int_ty,
self.llvm_usize, self.llvm_usize,
name, name,
@ -229,8 +210,36 @@ impl<'ctx> ProxyType<'ctx> for SliceType<'ctx> {
Self::is_representable(llvm_ty, generator.get_size_type(ctx)) Self::is_representable(llvm_ty, generator.get_size_type(ctx))
} }
fn alloca_type(&self) -> impl BasicType<'ctx> { fn raw_alloca<G: CodeGenerator + ?Sized>(
self.as_base_type().get_element_type().into_struct_type() &self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>,
) -> <Self::Value as ProxyValue<'ctx>>::Base {
generator
.gen_var_alloc(
ctx,
self.as_base_type().get_element_type().into_struct_type().into(),
name,
)
.unwrap()
}
fn array_alloca<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
size: IntValue<'ctx>,
name: Option<&'ctx str>,
) -> ArraySliceValue<'ctx> {
generator
.gen_array_var_alloc(
ctx,
self.as_base_type().get_element_type().into_struct_type().into(),
size,
name,
)
.unwrap()
} }
fn as_base_type(&self) -> Self::Base { fn as_base_type(&self) -> Self::Base {

View File

@ -51,8 +51,8 @@ pub trait ArrayLikeIndexer<'ctx, Index = IntValue<'ctx>>: ArrayLikeValue<'ctx> {
/// This function should be called with a valid index. /// This function should be called with a valid index.
unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>( unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
generator: &G, generator: &mut G,
idx: &Index, idx: &Index,
name: Option<&str>, name: Option<&str>,
) -> PointerValue<'ctx>; ) -> PointerValue<'ctx>;
@ -76,8 +76,8 @@ pub trait UntypedArrayLikeAccessor<'ctx, Index = IntValue<'ctx>>:
/// This function should be called with a valid index. /// This function should be called with a valid index.
unsafe fn get_unchecked<G: CodeGenerator + ?Sized>( unsafe fn get_unchecked<G: CodeGenerator + ?Sized>(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
generator: &G, generator: &mut G,
idx: &Index, idx: &Index,
name: Option<&str>, name: Option<&str>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
@ -107,8 +107,8 @@ pub trait UntypedArrayLikeMutator<'ctx, Index = IntValue<'ctx>>:
/// This function should be called with a valid index. /// This function should be called with a valid index.
unsafe fn set_unchecked<G: CodeGenerator + ?Sized>( unsafe fn set_unchecked<G: CodeGenerator + ?Sized>(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
generator: &G, generator: &mut G,
idx: &Index, idx: &Index,
value: BasicValueEnum<'ctx>, value: BasicValueEnum<'ctx>,
) { ) {
@ -130,33 +130,32 @@ pub trait UntypedArrayLikeMutator<'ctx, Index = IntValue<'ctx>>:
} }
/// An array-like value that can have its array elements accessed as an arbitrary type `T`. /// An array-like value that can have its array elements accessed as an arbitrary type `T`.
pub trait TypedArrayLikeAccessor<'ctx, G: CodeGenerator + ?Sized, T, Index = IntValue<'ctx>>: pub trait TypedArrayLikeAccessor<'ctx, T, Index = IntValue<'ctx>>:
UntypedArrayLikeAccessor<'ctx, Index> UntypedArrayLikeAccessor<'ctx, Index>
{ {
/// Casts an element from [`BasicValueEnum`] into `T`. /// Casts an element from [`BasicValueEnum`] into `T`.
fn downcast_to_type( fn downcast_to_type(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
generator: &G,
value: BasicValueEnum<'ctx>, value: BasicValueEnum<'ctx>,
) -> T; ) -> T;
/// # Safety /// # Safety
/// ///
/// This function should be called with a valid index. /// This function should be called with a valid index.
unsafe fn get_typed_unchecked( unsafe fn get_typed_unchecked<G: CodeGenerator + ?Sized>(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
generator: &G, generator: &mut G,
idx: &Index, idx: &Index,
name: Option<&str>, name: Option<&str>,
) -> T { ) -> T {
let value = unsafe { self.get_unchecked(ctx, generator, idx, name) }; let value = unsafe { self.get_unchecked(ctx, generator, idx, name) };
self.downcast_to_type(ctx, generator, value) self.downcast_to_type(ctx, value)
} }
/// Returns the data at the `idx`-th index. /// Returns the data at the `idx`-th index.
fn get_typed( fn get_typed<G: CodeGenerator + ?Sized>(
&self, &self,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
generator: &mut G, generator: &mut G,
@ -164,63 +163,62 @@ pub trait TypedArrayLikeAccessor<'ctx, G: CodeGenerator + ?Sized, T, Index = Int
name: Option<&str>, name: Option<&str>,
) -> T { ) -> T {
let value = self.get(ctx, generator, idx, name); let value = self.get(ctx, generator, idx, name);
self.downcast_to_type(ctx, generator, value) self.downcast_to_type(ctx, value)
} }
} }
/// An array-like value that can have its array elements mutated as an arbitrary type `T`. /// An array-like value that can have its array elements mutated as an arbitrary type `T`.
pub trait TypedArrayLikeMutator<'ctx, G: CodeGenerator + ?Sized, T, Index = IntValue<'ctx>>: pub trait TypedArrayLikeMutator<'ctx, T, Index = IntValue<'ctx>>:
UntypedArrayLikeMutator<'ctx, Index> UntypedArrayLikeMutator<'ctx, Index>
{ {
/// Casts an element from T into [`BasicValueEnum`]. /// Casts an element from T into [`BasicValueEnum`].
fn upcast_from_type( fn upcast_from_type(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
generator: &G,
value: T, value: T,
) -> BasicValueEnum<'ctx>; ) -> BasicValueEnum<'ctx>;
/// # Safety /// # Safety
/// ///
/// This function should be called with a valid index. /// This function should be called with a valid index.
unsafe fn set_typed_unchecked( unsafe fn set_typed_unchecked<G: CodeGenerator + ?Sized>(
&self,
ctx: &CodeGenContext<'ctx, '_>,
generator: &G,
idx: &Index,
value: T,
) {
let value = self.upcast_from_type(ctx, generator, value);
unsafe { self.set_unchecked(ctx, generator, idx, value) }
}
/// Sets the data at the `idx`-th index.
fn set_typed(
&self, &self,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
generator: &mut G, generator: &mut G,
idx: &Index, idx: &Index,
value: T, value: T,
) { ) {
let value = self.upcast_from_type(ctx, generator, value); let value = self.upcast_from_type(ctx, value);
unsafe { self.set_unchecked(ctx, generator, idx, value) }
}
/// Sets the data at the `idx`-th index.
fn set_typed<G: CodeGenerator + ?Sized>(
&self,
ctx: &mut CodeGenContext<'ctx, '_>,
generator: &mut G,
idx: &Index,
value: T,
) {
let value = self.upcast_from_type(ctx, value);
self.set(ctx, generator, idx, value); self.set(ctx, generator, idx, value);
} }
} }
/// Type alias for a function that casts a [`BasicValueEnum`] into a `T`.
type ValueDowncastFn<'ctx, T> =
Box<dyn Fn(&mut CodeGenContext<'ctx, '_>, BasicValueEnum<'ctx>) -> T + 'ctx>;
/// Type alias for a function that casts a `T` into a [`BasicValueEnum`].
type ValueUpcastFn<'ctx, T> = Box<dyn Fn(&mut CodeGenContext<'ctx, '_>, T) -> BasicValueEnum<'ctx>>;
/// An adapter for constraining untyped array values as typed values. /// An adapter for constraining untyped array values as typed values.
#[derive(Copy, Clone)] pub struct TypedArrayLikeAdapter<'ctx, T, Adapted: ArrayLikeValue<'ctx> = ArraySliceValue<'ctx>> {
pub struct TypedArrayLikeAdapter<
'ctx,
G: CodeGenerator + ?Sized,
T,
Adapted: ArrayLikeValue<'ctx> = ArraySliceValue<'ctx>,
> {
adapted: Adapted, adapted: Adapted,
downcast_fn: fn(&CodeGenContext<'ctx, '_>, &G, BasicValueEnum<'ctx>) -> T, downcast_fn: ValueDowncastFn<'ctx, T>,
upcast_fn: fn(&CodeGenContext<'ctx, '_>, &G, T) -> BasicValueEnum<'ctx>, upcast_fn: ValueUpcastFn<'ctx, T>,
} }
impl<'ctx, G: CodeGenerator + ?Sized, T, Adapted> TypedArrayLikeAdapter<'ctx, G, T, Adapted> impl<'ctx, T, Adapted> TypedArrayLikeAdapter<'ctx, T, Adapted>
where where
Adapted: ArrayLikeValue<'ctx>, Adapted: ArrayLikeValue<'ctx>,
{ {
@ -231,70 +229,61 @@ where
/// * `upcast_fn` - The function converting a T into a [`BasicValueEnum`]. /// * `upcast_fn` - The function converting a T into a [`BasicValueEnum`].
pub fn from( pub fn from(
adapted: Adapted, adapted: Adapted,
downcast_fn: fn(&CodeGenContext<'ctx, '_>, &G, BasicValueEnum<'ctx>) -> T, downcast_fn: ValueDowncastFn<'ctx, T>,
upcast_fn: fn(&CodeGenContext<'ctx, '_>, &G, T) -> BasicValueEnum<'ctx>, upcast_fn: ValueUpcastFn<'ctx, T>,
) -> Self { ) -> Self {
TypedArrayLikeAdapter { adapted, downcast_fn, upcast_fn } TypedArrayLikeAdapter { adapted, downcast_fn, upcast_fn }
} }
} }
impl<'ctx, G: CodeGenerator + ?Sized, T, Adapted> ArrayLikeValue<'ctx> impl<'ctx, T, Adapted> ArrayLikeValue<'ctx> for TypedArrayLikeAdapter<'ctx, T, Adapted>
for TypedArrayLikeAdapter<'ctx, G, T, Adapted>
where where
Adapted: ArrayLikeValue<'ctx>, Adapted: ArrayLikeValue<'ctx>,
{ {
fn element_type<CG: CodeGenerator + ?Sized>( fn element_type<G: CodeGenerator + ?Sized>(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
generator: &CG, generator: &G,
) -> AnyTypeEnum<'ctx> { ) -> AnyTypeEnum<'ctx> {
self.adapted.element_type(ctx, generator) self.adapted.element_type(ctx, generator)
} }
fn base_ptr<CG: CodeGenerator + ?Sized>( fn base_ptr<G: CodeGenerator + ?Sized>(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
generator: &CG, generator: &G,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
self.adapted.base_ptr(ctx, generator) self.adapted.base_ptr(ctx, generator)
} }
fn size<CG: CodeGenerator + ?Sized>( fn size<G: CodeGenerator + ?Sized>(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
generator: &CG, generator: &G,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
self.adapted.size(ctx, generator) self.adapted.size(ctx, generator)
} }
fn as_slice_value<CG: CodeGenerator + ?Sized>(
&self,
ctx: &CodeGenContext<'ctx, '_>,
generator: &CG,
) -> ArraySliceValue<'ctx> {
self.adapted.as_slice_value(ctx, generator)
}
} }
impl<'ctx, G: CodeGenerator + ?Sized, T, Index, Adapted> ArrayLikeIndexer<'ctx, Index> impl<'ctx, T, Index, Adapted> ArrayLikeIndexer<'ctx, Index>
for TypedArrayLikeAdapter<'ctx, G, T, Adapted> for TypedArrayLikeAdapter<'ctx, T, Adapted>
where where
Adapted: ArrayLikeIndexer<'ctx, Index>, Adapted: ArrayLikeIndexer<'ctx, Index>,
{ {
unsafe fn ptr_offset_unchecked<CG: CodeGenerator + ?Sized>( unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
generator: &CG, generator: &mut G,
idx: &Index, idx: &Index,
name: Option<&str>, name: Option<&str>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
unsafe { self.adapted.ptr_offset_unchecked(ctx, generator, idx, name) } unsafe { self.adapted.ptr_offset_unchecked(ctx, generator, idx, name) }
} }
fn ptr_offset<CG: CodeGenerator + ?Sized>( fn ptr_offset<G: CodeGenerator + ?Sized>(
&self, &self,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
generator: &mut CG, generator: &mut G,
idx: &Index, idx: &Index,
name: Option<&str>, name: Option<&str>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
@ -302,46 +291,44 @@ where
} }
} }
impl<'ctx, G: CodeGenerator + ?Sized, T, Index, Adapted> UntypedArrayLikeAccessor<'ctx, Index> impl<'ctx, T, Index, Adapted> UntypedArrayLikeAccessor<'ctx, Index>
for TypedArrayLikeAdapter<'ctx, G, T, Adapted> for TypedArrayLikeAdapter<'ctx, T, Adapted>
where where
Adapted: UntypedArrayLikeAccessor<'ctx, Index>, Adapted: UntypedArrayLikeAccessor<'ctx, Index>,
{ {
} }
impl<'ctx, G: CodeGenerator + ?Sized, T, Index, Adapted> UntypedArrayLikeMutator<'ctx, Index> impl<'ctx, T, Index, Adapted> UntypedArrayLikeMutator<'ctx, Index>
for TypedArrayLikeAdapter<'ctx, G, T, Adapted> for TypedArrayLikeAdapter<'ctx, T, Adapted>
where where
Adapted: UntypedArrayLikeMutator<'ctx, Index>, Adapted: UntypedArrayLikeMutator<'ctx, Index>,
{ {
} }
impl<'ctx, G: CodeGenerator + ?Sized, T, Index, Adapted> TypedArrayLikeAccessor<'ctx, G, T, Index> impl<'ctx, T, Index, Adapted> TypedArrayLikeAccessor<'ctx, T, Index>
for TypedArrayLikeAdapter<'ctx, G, T, Adapted> for TypedArrayLikeAdapter<'ctx, T, Adapted>
where where
Adapted: UntypedArrayLikeAccessor<'ctx, Index>, Adapted: UntypedArrayLikeAccessor<'ctx, Index>,
{ {
fn downcast_to_type( fn downcast_to_type(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
generator: &G,
value: BasicValueEnum<'ctx>, value: BasicValueEnum<'ctx>,
) -> T { ) -> T {
(self.downcast_fn)(ctx, generator, value) (self.downcast_fn)(ctx, value)
} }
} }
impl<'ctx, G: CodeGenerator + ?Sized, T, Index, Adapted> TypedArrayLikeMutator<'ctx, G, T, Index> impl<'ctx, T, Index, Adapted> TypedArrayLikeMutator<'ctx, T, Index>
for TypedArrayLikeAdapter<'ctx, G, T, Adapted> for TypedArrayLikeAdapter<'ctx, T, Adapted>
where where
Adapted: UntypedArrayLikeMutator<'ctx, Index>, Adapted: UntypedArrayLikeMutator<'ctx, Index>,
{ {
fn upcast_from_type( fn upcast_from_type(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
generator: &G,
value: T, value: T,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
(self.upcast_fn)(ctx, generator, value) (self.upcast_fn)(ctx, value)
} }
} }
@ -397,12 +384,12 @@ impl<'ctx> ArrayLikeValue<'ctx> for ArraySliceValue<'ctx> {
impl<'ctx> ArrayLikeIndexer<'ctx> for ArraySliceValue<'ctx> { impl<'ctx> ArrayLikeIndexer<'ctx> for ArraySliceValue<'ctx> {
unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>( unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
generator: &G, generator: &mut G,
idx: &IntValue<'ctx>, idx: &IntValue<'ctx>,
name: Option<&str>, name: Option<&str>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
let var_name = name.or(self.2).map(|v| format!("{v}.addr")).unwrap_or_default(); let var_name = name.map(|v| format!("{v}.addr")).unwrap_or_default();
unsafe { unsafe {
ctx.builder ctx.builder

View File

@ -8,7 +8,7 @@ use super::{
ArrayLikeIndexer, ArrayLikeValue, ProxyValue, UntypedArrayLikeAccessor, UntypedArrayLikeMutator, ArrayLikeIndexer, ArrayLikeValue, ProxyValue, UntypedArrayLikeAccessor, UntypedArrayLikeMutator,
}; };
use crate::codegen::{ use crate::codegen::{
types::{structure::StructField, ListType, ProxyType}, types::ListType,
{CodeGenContext, CodeGenerator}, {CodeGenContext, CodeGenerator},
}; };
@ -42,26 +42,48 @@ impl<'ctx> ListValue<'ctx> {
ListValue { value: ptr, llvm_usize, name } ListValue { value: ptr, llvm_usize, name }
} }
fn items_field(&self, ctx: &CodeGenContext<'ctx, '_>) -> StructField<'ctx, PointerValue<'ctx>> {
self.get_type().get_fields(&ctx.ctx).items
}
/// Returns the double-indirection pointer to the `data` array, as if by calling `getelementptr` /// Returns the double-indirection pointer to the `data` array, as if by calling `getelementptr`
/// on the field. /// on the field.
fn pptr_to_data(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> { fn pptr_to_data(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
self.items_field(ctx).ptr_by_gep(ctx, self.value, self.name) let llvm_i32 = ctx.ctx.i32_type();
let var_name = self.name.map(|v| format!("{v}.data.addr")).unwrap_or_default();
unsafe {
ctx.builder
.build_in_bounds_gep(
self.as_base_value(),
&[llvm_i32.const_zero(), llvm_i32.const_zero()],
var_name.as_str(),
)
.unwrap()
}
}
/// Returns the pointer to the field storing the size of this `list`.
fn ptr_to_size(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
let llvm_i32 = ctx.ctx.i32_type();
let var_name = self.name.map(|v| format!("{v}.size.addr")).unwrap_or_default();
unsafe {
ctx.builder
.build_in_bounds_gep(
self.as_base_value(),
&[llvm_i32.const_zero(), llvm_i32.const_int(1, true)],
var_name.as_str(),
)
.unwrap()
}
} }
/// Stores the array of data elements `data` into this instance. /// Stores the array of data elements `data` into this instance.
fn store_data(&self, ctx: &CodeGenContext<'ctx, '_>, data: PointerValue<'ctx>) { fn store_data(&self, ctx: &CodeGenContext<'ctx, '_>, data: PointerValue<'ctx>) {
self.items_field(ctx).set(ctx, self.value, data, self.name); ctx.builder.build_store(self.pptr_to_data(ctx), data).unwrap();
} }
/// Convenience method for creating a new array storing data elements with the given element /// Convenience method for creating a new array storing data elements with the given element
/// type `elem_ty` and `size`. /// type `elem_ty` and `size`.
/// ///
/// If `size` is [None], the size stored in the field of this instance is used instead. If /// If `size` is [None], the size stored in the field of this instance is used instead.
/// `size` is resolved to `0` at runtime, `(T*) 0` will be assigned to `data`.
pub fn create_data( pub fn create_data(
&self, &self,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
@ -92,10 +114,6 @@ impl<'ctx> ListValue<'ctx> {
ListDataProxy(self) ListDataProxy(self)
} }
fn len_field(&self, ctx: &CodeGenContext<'ctx, '_>) -> StructField<'ctx, IntValue<'ctx>> {
self.get_type().get_fields(&ctx.ctx).len
}
/// Stores the `size` of this `list` into this instance. /// Stores the `size` of this `list` into this instance.
pub fn store_size<G: CodeGenerator + ?Sized>( pub fn store_size<G: CodeGenerator + ?Sized>(
&self, &self,
@ -105,33 +123,22 @@ impl<'ctx> ListValue<'ctx> {
) { ) {
debug_assert_eq!(size.get_type(), generator.get_size_type(ctx.ctx)); debug_assert_eq!(size.get_type(), generator.get_size_type(ctx.ctx));
self.len_field(ctx).set(ctx, self.value, size, self.name); let psize = self.ptr_to_size(ctx);
ctx.builder.build_store(psize, size).unwrap();
} }
/// Returns the size of this `list` as a value. /// Returns the size of this `list` as a value.
pub fn load_size( pub fn load_size(&self, ctx: &CodeGenContext<'ctx, '_>, name: Option<&str>) -> IntValue<'ctx> {
&self, let psize = self.ptr_to_size(ctx);
ctx: &CodeGenContext<'ctx, '_>, let var_name = name
name: Option<&'ctx str>, .map(ToString::to_string)
) -> IntValue<'ctx> { .or_else(|| self.name.map(|v| format!("{v}.size")))
self.len_field(ctx).get(ctx, self.value, name) .unwrap_or_default();
}
/// Returns an instance of [`ListValue`] with the `items` pointer cast to `i8*`. ctx.builder
#[must_use] .build_load(psize, var_name.as_str())
pub fn as_i8_list<G: CodeGenerator + ?Sized>( .map(BasicValueEnum::into_int_value)
&self, .unwrap()
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
) -> ListValue<'ctx> {
let llvm_i8 = ctx.ctx.i8_type();
let llvm_list_i8 = <Self as ProxyValue>::Type::new(generator, ctx.ctx, llvm_i8.into());
Self::from_pointer_value(
ctx.builder.build_pointer_cast(self.value, llvm_list_i8.as_base_type(), "").unwrap(),
self.llvm_usize,
self.name,
)
} }
} }
@ -192,8 +199,8 @@ impl<'ctx> ArrayLikeValue<'ctx> for ListDataProxy<'ctx, '_> {
impl<'ctx> ArrayLikeIndexer<'ctx> for ListDataProxy<'ctx, '_> { impl<'ctx> ArrayLikeIndexer<'ctx> for ListDataProxy<'ctx, '_> {
unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>( unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
generator: &G, generator: &mut G,
idx: &IntValue<'ctx>, idx: &IntValue<'ctx>,
name: Option<&str>, name: Option<&str>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {

View File

@ -5,13 +5,11 @@ use crate::codegen::CodeGenerator;
pub use array::*; pub use array::*;
pub use list::*; pub use list::*;
pub use range::*; pub use range::*;
pub use tuple::*;
mod array; mod array;
mod list; mod list;
pub mod ndarray; pub mod ndarray;
mod range; mod range;
mod tuple;
pub mod utils; pub mod utils;
/// A LLVM type that is used to represent a non-primitive value in NAC3. /// A LLVM type that is used to represent a non-primitive value in NAC3.

View File

@ -1,243 +0,0 @@
use inkwell::{
types::IntType,
values::{IntValue, PointerValue},
};
use itertools::Itertools;
use crate::codegen::{
irrt,
types::{
ndarray::{NDArrayType, ShapeEntryType},
structure::StructField,
ProxyType,
},
values::{
ndarray::NDArrayValue, ArrayLikeIndexer, ArrayLikeValue, ArraySliceValue, ProxyValue,
TypedArrayLikeAccessor, TypedArrayLikeAdapter, TypedArrayLikeMutator,
},
CodeGenContext, CodeGenerator,
};
#[derive(Copy, Clone)]
pub struct ShapeEntryValue<'ctx> {
value: PointerValue<'ctx>,
llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>,
}
impl<'ctx> ShapeEntryValue<'ctx> {
/// Checks whether `value` is an instance of `ShapeEntry`, returning [Err] if `value` is
/// not an instance.
pub fn is_representable(
value: PointerValue<'ctx>,
llvm_usize: IntType<'ctx>,
) -> Result<(), String> {
<Self as ProxyValue<'ctx>>::Type::is_representable(value.get_type(), llvm_usize)
}
/// Creates an [`ShapeEntryValue`] from a [`PointerValue`].
#[must_use]
pub fn from_pointer_value(
ptr: PointerValue<'ctx>,
llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>,
) -> Self {
debug_assert!(Self::is_representable(ptr, llvm_usize).is_ok());
Self { value: ptr, llvm_usize, name }
}
fn ndims_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
self.get_type().get_fields(self.value.get_type().get_context()).ndims
}
/// Stores the number of dimensions into this value.
pub fn store_ndims(&self, ctx: &CodeGenContext<'ctx, '_>, value: IntValue<'ctx>) {
self.ndims_field().set(ctx, self.value, value, self.name);
}
fn shape_field(&self) -> StructField<'ctx, PointerValue<'ctx>> {
self.get_type().get_fields(self.value.get_type().get_context()).shape
}
/// Stores the shape into this value.
pub fn store_shape(&self, ctx: &CodeGenContext<'ctx, '_>, value: PointerValue<'ctx>) {
self.shape_field().set(ctx, self.value, value, self.name);
}
}
impl<'ctx> ProxyValue<'ctx> for ShapeEntryValue<'ctx> {
type Base = PointerValue<'ctx>;
type Type = ShapeEntryType<'ctx>;
fn get_type(&self) -> Self::Type {
Self::Type::from_type(self.value.get_type(), self.llvm_usize)
}
fn as_base_value(&self) -> Self::Base {
self.value
}
}
impl<'ctx> From<ShapeEntryValue<'ctx>> for PointerValue<'ctx> {
fn from(value: ShapeEntryValue<'ctx>) -> Self {
value.as_base_value()
}
}
impl<'ctx> NDArrayValue<'ctx> {
/// Create a broadcast view on this ndarray with a target shape.
///
/// The input shape will be checked to make sure that it contains no negative values.
///
/// * `target_ndims` - The ndims type after broadcasting to the given shape.
/// The caller has to figure this out for this function.
/// * `target_shape` - An array pointer pointing to the target shape.
#[must_use]
pub fn broadcast_to<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
target_ndims: u64,
target_shape: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>,
) -> Self {
assert!(self.ndims <= target_ndims);
assert_eq!(target_shape.element_type(ctx, generator), self.llvm_usize.into());
let broadcast_ndarray = NDArrayType::new(generator, ctx.ctx, self.dtype, target_ndims)
.construct_uninitialized(generator, ctx, None);
broadcast_ndarray.copy_shape_from_array(
generator,
ctx,
target_shape.base_ptr(ctx, generator),
);
irrt::ndarray::call_nac3_ndarray_broadcast_to(generator, ctx, *self, broadcast_ndarray);
broadcast_ndarray
}
}
/// A result produced by [`broadcast_all_ndarrays`]
#[derive(Clone)]
pub struct BroadcastAllResult<'ctx, G: CodeGenerator + ?Sized> {
/// The statically known `ndims` of the broadcast result.
pub ndims: u64,
/// The broadcasting shape.
pub shape: TypedArrayLikeAdapter<'ctx, G, IntValue<'ctx>>,
/// Broadcasted views on the inputs.
///
/// All of them will have `shape` [`BroadcastAllResult::shape`] and
/// `ndims` [`BroadcastAllResult::ndims`]. The length of the vector
/// is the same as the input.
pub ndarrays: Vec<NDArrayValue<'ctx>>,
}
/// Helper function to call [`irrt::ndarray::call_nac3_ndarray_broadcast_shapes`].
fn broadcast_shapes<'ctx, G, Shape>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
in_shape_entries: &[(ArraySliceValue<'ctx>, u64)], // (shape, shape's length/ndims)
broadcast_ndims: u64,
broadcast_shape: &Shape,
) where
G: CodeGenerator + ?Sized,
Shape: TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>
+ TypedArrayLikeMutator<'ctx, G, IntValue<'ctx>>,
{
let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_shape_ty = ShapeEntryType::new(generator, ctx.ctx);
assert!(in_shape_entries
.iter()
.all(|entry| entry.0.element_type(ctx, generator) == llvm_usize.into()));
assert_eq!(broadcast_shape.element_type(ctx, generator), llvm_usize.into());
// Prepare input shape entries to be passed to `call_nac3_ndarray_broadcast_shapes`.
let num_shape_entries =
llvm_usize.const_int(u64::try_from(in_shape_entries.len()).unwrap(), false);
let shape_entries = llvm_shape_ty.array_alloca(ctx, num_shape_entries, None);
for (i, (in_shape, in_ndims)) in in_shape_entries.iter().enumerate() {
let pshape_entry = unsafe {
shape_entries.ptr_offset_unchecked(
ctx,
generator,
&llvm_usize.const_int(i as u64, false),
None,
)
};
let shape_entry = llvm_shape_ty.map_value(pshape_entry, None);
let in_ndims = llvm_usize.const_int(*in_ndims, false);
shape_entry.store_ndims(ctx, in_ndims);
shape_entry.store_shape(ctx, in_shape.base_ptr(ctx, generator));
}
let broadcast_ndims = llvm_usize.const_int(broadcast_ndims, false);
irrt::ndarray::call_nac3_ndarray_broadcast_shapes(
generator,
ctx,
num_shape_entries,
shape_entries,
broadcast_ndims,
broadcast_shape,
);
}
impl<'ctx> NDArrayType<'ctx> {
/// Broadcast all ndarrays according to
/// [`np.broadcast()`](https://numpy.org/doc/stable/reference/generated/numpy.broadcast.html)
/// and return a [`BroadcastAllResult`] containing all the information of the result of the
/// broadcast operation.
pub fn broadcast<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
ndarrays: &[NDArrayValue<'ctx>],
) -> BroadcastAllResult<'ctx, G> {
assert!(!ndarrays.is_empty());
let llvm_usize = generator.get_size_type(ctx.ctx);
// Infer the broadcast output ndims.
let broadcast_ndims_int =
ndarrays.iter().map(|ndarray| ndarray.get_type().ndims()).max().unwrap();
assert!(self.ndims() >= broadcast_ndims_int);
let broadcast_ndims = llvm_usize.const_int(broadcast_ndims_int, false);
let broadcast_shape = ArraySliceValue::from_ptr_val(
ctx.builder.build_array_alloca(llvm_usize, broadcast_ndims, "").unwrap(),
broadcast_ndims,
None,
);
let broadcast_shape = TypedArrayLikeAdapter::from(
broadcast_shape,
|_, _, val| val.into_int_value(),
|_, _, val| val.into(),
);
let shape_entries = ndarrays
.iter()
.map(|ndarray| {
(ndarray.shape().as_slice_value(ctx, generator), ndarray.get_type().ndims())
})
.collect_vec();
broadcast_shapes(generator, ctx, &shape_entries, broadcast_ndims_int, &broadcast_shape);
// Broadcast all the inputs to shape `dst_shape`.
let broadcast_ndarrays = ndarrays
.iter()
.map(|ndarray| {
ndarray.broadcast_to(generator, ctx, broadcast_ndims_int, &broadcast_shape)
})
.collect_vec();
BroadcastAllResult {
ndims: broadcast_ndims_int,
shape: broadcast_shape,
ndarrays: broadcast_ndarrays,
}
}
}

View File

@ -118,10 +118,12 @@ impl<'ctx> NDArrayValue<'ctx> {
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
) -> ContiguousNDArrayValue<'ctx> { ) -> ContiguousNDArrayValue<'ctx> {
let result = ContiguousNDArrayType::new(generator, ctx.ctx, self.dtype) let result = ContiguousNDArrayType::new(generator, ctx.ctx, self.dtype)
.alloca_var(generator, ctx, self.name); .alloca(generator, ctx, self.name);
// Set ndims and shape. // Set ndims and shape.
let ndims = self.llvm_usize.const_int(self.ndims, false); let ndims = self
.ndims
.map_or_else(|| self.load_ndims(ctx), |ndims| self.llvm_usize.const_int(ndims, false));
result.store_ndims(ctx, ndims); result.store_ndims(ctx, ndims);
let shape = self.shape(); let shape = self.shape();
@ -178,7 +180,7 @@ impl<'ctx> NDArrayValue<'ctx> {
// TODO: Debug assert `ndims == carray.ndims` to catch bugs. // TODO: Debug assert `ndims == carray.ndims` to catch bugs.
// Allocate the resulting ndarray. // Allocate the resulting ndarray.
let ndarray = NDArrayType::new(generator, ctx.ctx, carray.item, ndims) let ndarray = NDArrayType::new(generator, ctx.ctx, carray.item, Some(ndims))
.construct_uninitialized(generator, ctx, carray.name); .construct_uninitialized(generator, ctx, carray.name);
// Copy shape and update strides // Copy shape and update strides

View File

@ -98,8 +98,8 @@ impl<'ctx> From<NDIndexValue<'ctx>> for PointerValue<'ctx> {
impl<'ctx> NDArrayValue<'ctx> { impl<'ctx> NDArrayValue<'ctx> {
/// Get the expected `ndims` after indexing with `indices`. /// Get the expected `ndims` after indexing with `indices`.
#[must_use] #[must_use]
fn deduce_ndims_after_indexing_with(&self, indices: &[RustNDIndex<'ctx>]) -> u64 { fn deduce_ndims_after_indexing_with(&self, indices: &[RustNDIndex<'ctx>]) -> Option<u64> {
let mut ndims = self.ndims; let mut ndims = self.ndims?;
for index in indices { for index in indices {
match index { match index {
@ -113,7 +113,7 @@ impl<'ctx> NDArrayValue<'ctx> {
} }
} }
ndims Some(ndims)
} }
/// Index into the ndarray, and return a newly-allocated view on this ndarray. /// Index into the ndarray, and return a newly-allocated view on this ndarray.
@ -127,6 +127,8 @@ impl<'ctx> NDArrayValue<'ctx> {
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
indices: &[RustNDIndex<'ctx>], indices: &[RustNDIndex<'ctx>],
) -> Self { ) -> Self {
assert!(self.ndims.is_some(), "NDArrayValue::index is only supported for instances with compile-time known ndims (self.ndims = Some(...))");
let dst_ndims = self.deduce_ndims_after_indexing_with(indices); let dst_ndims = self.deduce_ndims_after_indexing_with(indices);
let dst_ndarray = NDArrayType::new(generator, ctx.ctx, self.dtype, dst_ndims) let dst_ndarray = NDArrayType::new(generator, ctx.ctx, self.dtype, dst_ndims)
.construct_uninitialized(generator, ctx, None); .construct_uninitialized(generator, ctx, None);
@ -246,7 +248,7 @@ impl<'ctx> RustNDIndex<'ctx> {
RustNDIndex::Slice(in_rust_slice) => { RustNDIndex::Slice(in_rust_slice) => {
let user_slice_ptr = let user_slice_ptr =
SliceType::new(ctx.ctx, ctx.ctx.i32_type(), generator.get_size_type(ctx.ctx)) SliceType::new(ctx.ctx, ctx.ctx.i32_type(), generator.get_size_type(ctx.ctx))
.alloca_var(generator, ctx, None); .alloca(generator, ctx, None);
in_rust_slice.write_to_slice(ctx, user_slice_ptr); in_rust_slice.write_to_slice(ctx, user_slice_ptr);
dst_ndindex.store_data( dst_ndindex.store_data(

View File

@ -1,69 +0,0 @@
use inkwell::{types::BasicTypeEnum, values::BasicValueEnum};
use crate::codegen::{
values::{
ndarray::{NDArrayOut, NDArrayValue, ScalarOrNDArray},
ProxyValue,
},
CodeGenContext, CodeGenerator,
};
impl<'ctx> NDArrayValue<'ctx> {
/// Map through this ndarray with an elementwise function.
pub fn map<'a, G, Mapping>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,
out: NDArrayOut<'ctx>,
mapping: Mapping,
) -> Result<Self, String>
where
G: CodeGenerator + ?Sized,
Mapping: FnOnce(
&mut G,
&mut CodeGenContext<'ctx, 'a>,
BasicValueEnum<'ctx>,
) -> Result<BasicValueEnum<'ctx>, String>,
{
self.get_type().broadcast_starmap(
generator,
ctx,
&[*self],
out,
|generator, ctx, scalars| mapping(generator, ctx, scalars[0]),
)
}
}
impl<'ctx> ScalarOrNDArray<'ctx> {
/// Map through this [`ScalarOrNDArray`] with an elementwise function.
///
/// If this is a scalar, `mapping` will directly act on the scalar. This function will return a
/// [`ScalarOrNDArray::Scalar`] of that result.
///
/// If this is an ndarray, `mapping` will be applied to the elements of the ndarray. A new
/// ndarray of the results will be created and returned as a [`ScalarOrNDArray::NDArray`].
pub fn map<'a, G, Mapping>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,
ret_dtype: BasicTypeEnum<'ctx>,
mapping: Mapping,
) -> Result<ScalarOrNDArray<'ctx>, String>
where
G: CodeGenerator + ?Sized,
Mapping: FnOnce(
&mut G,
&mut CodeGenContext<'ctx, 'a>,
BasicValueEnum<'ctx>,
) -> Result<BasicValueEnum<'ctx>, String>,
{
ScalarOrNDArray::broadcasting_starmap(
generator,
ctx,
&[*self],
ret_dtype,
|generator, ctx, scalars| mapping(generator, ctx, scalars[0]),
)
}
}

View File

@ -1,323 +0,0 @@
use std::cmp::max;
use nac3parser::ast::Operator;
use super::{NDArrayOut, NDArrayValue, RustNDIndex};
use crate::{
codegen::{
expr::gen_binop_expr_with_values,
irrt,
stmt::gen_for_callback_incrementing,
types::ndarray::NDArrayType,
values::{
ArrayLikeValue, ArraySliceValue, TypedArrayLikeAccessor, TypedArrayLikeAdapter,
UntypedArrayLikeAccessor, UntypedArrayLikeMutator,
},
CodeGenContext, CodeGenerator,
},
toplevel::helper::arraylike_flatten_element_type,
typecheck::{magic_methods::Binop, typedef::Type},
};
/// Perform `np.einsum("...ij,...jk->...ik", in_a, in_b)`.
///
/// `dst_dtype` defines the dtype of the returned ndarray.
fn matmul_at_least_2d<'ctx, G: CodeGenerator>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
dst_dtype: Type,
(in_a_ty, in_a): (Type, NDArrayValue<'ctx>),
(in_b_ty, in_b): (Type, NDArrayValue<'ctx>),
) -> NDArrayValue<'ctx> {
assert!(in_a.ndims >= 2, "in_a (which is {}) must be >= 2", in_a.ndims);
assert!(in_b.ndims >= 2, "in_b (which is {}) must be >= 2", in_b.ndims);
let lhs_dtype = arraylike_flatten_element_type(&mut ctx.unifier, in_a_ty);
let rhs_dtype = arraylike_flatten_element_type(&mut ctx.unifier, in_b_ty);
let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_dst_dtype = ctx.get_llvm_type(generator, dst_dtype);
// Deduce ndims of the result of matmul.
let ndims_int = max(in_a.ndims, in_b.ndims);
let ndims = llvm_usize.const_int(ndims_int, false);
// Broadcasts `in_a.shape[:-2]` and `in_b.shape[:-2]` together and allocate the
// destination ndarray to store the result of matmul.
let (lhs, rhs, dst) = {
let in_lhs_ndims = llvm_usize.const_int(in_a.ndims, false);
let in_lhs_shape = TypedArrayLikeAdapter::from(
ArraySliceValue::from_ptr_val(
in_a.shape().base_ptr(ctx, generator),
in_lhs_ndims,
None,
),
|_, _, val| val.into_int_value(),
|_, _, val| val.into(),
);
let in_rhs_ndims = llvm_usize.const_int(in_b.ndims, false);
let in_rhs_shape = TypedArrayLikeAdapter::from(
ArraySliceValue::from_ptr_val(
in_b.shape().base_ptr(ctx, generator),
in_rhs_ndims,
None,
),
|_, _, val| val.into_int_value(),
|_, _, val| val.into(),
);
let lhs_shape = TypedArrayLikeAdapter::from(
ArraySliceValue::from_ptr_val(
ctx.builder.build_array_alloca(llvm_usize, ndims, "").unwrap(),
ndims,
None,
),
|_, _, val| val.into_int_value(),
|_, _, val| val.into(),
);
let rhs_shape = TypedArrayLikeAdapter::from(
ArraySliceValue::from_ptr_val(
ctx.builder.build_array_alloca(llvm_usize, ndims, "").unwrap(),
ndims,
None,
),
|_, _, val| val.into_int_value(),
|_, _, val| val.into(),
);
let dst_shape = TypedArrayLikeAdapter::from(
ArraySliceValue::from_ptr_val(
ctx.builder.build_array_alloca(llvm_usize, ndims, "").unwrap(),
ndims,
None,
),
|_, _, val| val.into_int_value(),
|_, _, val| val.into(),
);
// Matmul dimension compatibility is checked here.
irrt::ndarray::call_nac3_ndarray_matmul_calculate_shapes(
generator,
ctx,
&in_lhs_shape,
&in_rhs_shape,
ndims,
&lhs_shape,
&rhs_shape,
&dst_shape,
);
let lhs = in_a.broadcast_to(generator, ctx, ndims_int, &lhs_shape);
let rhs = in_b.broadcast_to(generator, ctx, ndims_int, &rhs_shape);
let dst = NDArrayType::new(generator, ctx.ctx, llvm_dst_dtype, ndims_int)
.construct_uninitialized(generator, ctx, None);
dst.copy_shape_from_array(generator, ctx, dst_shape.base_ptr(ctx, generator));
unsafe {
dst.create_data(generator, ctx);
}
(lhs, rhs, dst)
};
let len = unsafe {
lhs.shape().get_typed_unchecked(
ctx,
generator,
&llvm_usize.const_int(ndims_int - 1, false),
None,
)
};
let at_row = i64::try_from(ndims_int - 2).unwrap();
let at_col = i64::try_from(ndims_int - 1).unwrap();
let dst_dtype_llvm = ctx.get_llvm_type(generator, dst_dtype);
let dst_zero = dst_dtype_llvm.const_zero();
dst.foreach(generator, ctx, |generator, ctx, _, hdl| {
let pdst_ij = hdl.get_pointer(ctx);
ctx.builder.build_store(pdst_ij, dst_zero).unwrap();
let indices = hdl.get_indices::<G>();
let i = unsafe {
indices.get_unchecked(ctx, generator, &llvm_usize.const_int(at_row as u64, true), None)
};
let j = unsafe {
indices.get_unchecked(ctx, generator, &llvm_usize.const_int(at_col as u64, true), None)
};
let num_0 = llvm_usize.const_int(0, false);
let num_1 = llvm_usize.const_int(1, false);
gen_for_callback_incrementing(
generator,
ctx,
None,
num_0,
(len, false),
|generator, ctx, _, k| {
// `indices` is modified to index into `a` and `b`, and restored.
unsafe {
indices.set_unchecked(
ctx,
generator,
&llvm_usize.const_int(at_row as u64, true),
i,
);
indices.set_unchecked(
ctx,
generator,
&llvm_usize.const_int(at_col as u64, true),
k.into(),
);
}
let a_ik = unsafe { lhs.data().get_unchecked(ctx, generator, &indices, None) };
unsafe {
indices.set_unchecked(
ctx,
generator,
&llvm_usize.const_int(at_row as u64, true),
k.into(),
);
indices.set_unchecked(
ctx,
generator,
&llvm_usize.const_int(at_col as u64, true),
j,
);
}
let b_kj = unsafe { rhs.data().get_unchecked(ctx, generator, &indices, None) };
// Restore `indices`.
unsafe {
indices.set_unchecked(
ctx,
generator,
&llvm_usize.const_int(at_row as u64, true),
i,
);
indices.set_unchecked(
ctx,
generator,
&llvm_usize.const_int(at_col as u64, true),
j,
);
}
// x = a_[...]ik * b_[...]kj
let x = gen_binop_expr_with_values(
generator,
ctx,
(&Some(lhs_dtype), a_ik),
Binop::normal(Operator::Mult),
(&Some(rhs_dtype), b_kj),
ctx.current_loc,
)?
.unwrap()
.to_basic_value_enum(ctx, generator, dst_dtype)?;
// dst_[...]ij += x
let dst_ij = ctx.builder.build_load(pdst_ij, "").unwrap();
let dst_ij = gen_binop_expr_with_values(
generator,
ctx,
(&Some(dst_dtype), dst_ij),
Binop::normal(Operator::Add),
(&Some(dst_dtype), x),
ctx.current_loc,
)?
.unwrap()
.to_basic_value_enum(ctx, generator, dst_dtype)?;
ctx.builder.build_store(pdst_ij, dst_ij).unwrap();
Ok(())
},
num_1,
)
})
.unwrap();
dst
}
impl<'ctx> NDArrayValue<'ctx> {
/// Perform [`np.matmul`](https://numpy.org/doc/stable/reference/generated/numpy.matmul.html).
///
/// This function always return an [`NDArrayValue`]. You may want to use
/// [`NDArrayValue::split_unsized`] to handle when the output could be a scalar.
///
/// `dst_dtype` defines the dtype of the returned ndarray.
#[must_use]
pub fn matmul<G: CodeGenerator>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
self_ty: Type,
(other_ty, other): (Type, Self),
(out_dtype, out): (Type, NDArrayOut<'ctx>),
) -> Self {
// Sanity check, but type inference should prevent this.
assert!(self.ndims > 0 && other.ndims > 0, "np.matmul disallows scalar input");
// If both arguments are 2-D they are multiplied like conventional matrices.
//
// If either argument is N-D, N > 2, it is treated as a stack of matrices residing in the
// last two indices and broadcast accordingly.
//
// If the first argument is 1-D, it is promoted to a matrix by prepending a 1 to its
// dimensions. After matrix multiplication the prepended 1 is removed.
//
// If the second argument is 1-D, it is promoted to a matrix by appending a 1 to its
// dimensions. After matrix multiplication the appended 1 is removed.
let new_a = if self.ndims == 1 {
// Prepend 1 to its dimensions
self.index(generator, ctx, &[RustNDIndex::NewAxis, RustNDIndex::Ellipsis])
} else {
*self
};
let new_b = if other.ndims == 1 {
// Append 1 to its dimensions
other.index(generator, ctx, &[RustNDIndex::Ellipsis, RustNDIndex::NewAxis])
} else {
other
};
// NOTE: `result` will always be a newly allocated ndarray.
// Current implementation cannot do in-place matrix muliplication.
let mut result =
matmul_at_least_2d(generator, ctx, out_dtype, (self_ty, new_a), (other_ty, new_b));
// Postprocessing on the result to remove prepended/appended axes.
let mut postindices = vec![];
let zero = ctx.ctx.i32_type().const_zero();
if self.ndims == 1 {
// Remove the prepended 1
postindices.push(RustNDIndex::SingleElement(zero));
}
if other.ndims == 1 {
// Remove the appended 1
postindices.push(RustNDIndex::Ellipsis);
postindices.push(RustNDIndex::SingleElement(zero));
}
if !postindices.is_empty() {
result = result.index(generator, ctx, &postindices);
}
match out {
NDArrayOut::NewNDArray { .. } => result,
NDArrayOut::WriteToNDArray { ndarray: out_ndarray } => {
let result_shape = result.shape();
out_ndarray.assert_can_be_written_by_out(generator, ctx, result_shape);
out_ndarray.copy_data_from(generator, ctx, result);
out_ndarray
}
}
}
}

View File

@ -1,40 +1,29 @@
use std::iter::repeat_n;
use inkwell::{ use inkwell::{
types::{AnyType, AnyTypeEnum, BasicType, BasicTypeEnum, IntType}, types::{AnyType, AnyTypeEnum, BasicType, BasicTypeEnum, IntType},
values::{BasicValue, BasicValueEnum, IntValue, PointerValue}, values::{BasicValueEnum, IntValue, PointerValue},
AddressSpace, IntPredicate, AddressSpace, IntPredicate,
}; };
use itertools::Itertools;
use super::{ use super::{
ArrayLikeIndexer, ArrayLikeValue, ProxyValue, TupleValue, TypedArrayLikeAccessor, ArrayLikeIndexer, ArrayLikeValue, ProxyValue, TypedArrayLikeAccessor, TypedArrayLikeMutator,
TypedArrayLikeAdapter, TypedArrayLikeMutator, UntypedArrayLikeAccessor, UntypedArrayLikeAccessor, UntypedArrayLikeMutator,
UntypedArrayLikeMutator,
}; };
use crate::{ use crate::codegen::{
codegen::{ irrt,
irrt, llvm_intrinsics::{call_int_umin, call_memcpy_generic_array},
llvm_intrinsics::{call_int_umin, call_memcpy_generic_array}, stmt::gen_for_callback_incrementing,
stmt::gen_for_callback_incrementing, type_aligned_alloca,
type_aligned_alloca, types::{ndarray::NDArrayType, structure::StructField},
types::{ndarray::NDArrayType, structure::StructField, TupleType}, CodeGenContext, CodeGenerator,
CodeGenContext, CodeGenerator,
},
typecheck::typedef::{Type, TypeEnum},
}; };
pub use broadcast::*;
pub use contiguous::*; pub use contiguous::*;
pub use indexing::*; pub use indexing::*;
pub use nditer::*; pub use nditer::*;
pub use view::*;
mod broadcast;
mod contiguous; mod contiguous;
mod indexing; mod indexing;
mod map;
mod matmul;
mod nditer; mod nditer;
pub mod shape;
mod view; mod view;
/// Proxy type for accessing an `NDArray` value in LLVM. /// Proxy type for accessing an `NDArray` value in LLVM.
@ -42,7 +31,7 @@ mod view;
pub struct NDArrayValue<'ctx> { pub struct NDArrayValue<'ctx> {
value: PointerValue<'ctx>, value: PointerValue<'ctx>,
dtype: BasicTypeEnum<'ctx>, dtype: BasicTypeEnum<'ctx>,
ndims: u64, ndims: Option<u64>,
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>, name: Option<&'ctx str>,
} }
@ -62,7 +51,7 @@ impl<'ctx> NDArrayValue<'ctx> {
pub fn from_pointer_value( pub fn from_pointer_value(
ptr: PointerValue<'ctx>, ptr: PointerValue<'ctx>,
dtype: BasicTypeEnum<'ctx>, dtype: BasicTypeEnum<'ctx>,
ndims: u64, ndims: Option<u64>,
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>, name: Option<&'ctx str>,
) -> Self { ) -> Self {
@ -124,6 +113,12 @@ impl<'ctx> NDArrayValue<'ctx> {
self.get_type().get_fields(ctx.ctx).shape self.get_type().get_fields(ctx.ctx).shape
} }
/// Returns the double-indirection pointer to the `shape` array, as if by calling
/// `getelementptr` on the field.
fn ptr_to_shape(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
self.shape_field(ctx).ptr_by_gep(ctx, self.value, self.name)
}
/// Stores the array of dimension sizes `dims` into this instance. /// Stores the array of dimension sizes `dims` into this instance.
fn store_shape(&self, ctx: &CodeGenContext<'ctx, '_>, dims: PointerValue<'ctx>) { fn store_shape(&self, ctx: &CodeGenContext<'ctx, '_>, dims: PointerValue<'ctx>) {
self.shape_field(ctx).set(ctx, self.as_base_value(), dims, self.name); self.shape_field(ctx).set(ctx, self.as_base_value(), dims, self.name);
@ -152,6 +147,12 @@ impl<'ctx> NDArrayValue<'ctx> {
self.get_type().get_fields(ctx.ctx).strides self.get_type().get_fields(ctx.ctx).strides
} }
/// Returns the double-indirection pointer to the `strides` array, as if by calling
/// `getelementptr` on the field.
fn ptr_to_strides(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
self.strides_field(ctx).ptr_by_gep(ctx, self.value, self.name)
}
/// Stores the array of stride sizes `strides` into this instance. /// Stores the array of stride sizes `strides` into this instance.
fn store_strides(&self, ctx: &CodeGenContext<'ctx, '_>, strides: PointerValue<'ctx>) { fn store_strides(&self, ctx: &CodeGenContext<'ctx, '_>, strides: PointerValue<'ctx>) {
self.strides_field(ctx).set(ctx, self.as_base_value(), strides, self.name); self.strides_field(ctx).set(ctx, self.as_base_value(), strides, self.name);
@ -184,7 +185,7 @@ impl<'ctx> NDArrayValue<'ctx> {
} }
/// Stores the array of data elements `data` into this instance. /// Stores the array of data elements `data` into this instance.
pub fn store_data(&self, ctx: &CodeGenContext<'ctx, '_>, data: PointerValue<'ctx>) { fn store_data(&self, ctx: &CodeGenContext<'ctx, '_>, data: PointerValue<'ctx>) {
let data = ctx let data = ctx
.builder .builder
.build_bit_cast(data, ctx.ctx.i8_type().ptr_type(AddressSpace::default()), "") .build_bit_cast(data, ctx.ctx.i8_type().ptr_type(AddressSpace::default()), "")
@ -245,7 +246,26 @@ impl<'ctx> NDArrayValue<'ctx> {
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
src_ndarray: NDArrayValue<'ctx>, src_ndarray: NDArrayValue<'ctx>,
) { ) {
assert_eq!(self.ndims, src_ndarray.ndims); if self.ndims.is_some() && src_ndarray.ndims.is_some() {
assert_eq!(self.ndims, src_ndarray.ndims);
} else {
let self_ndims = self.load_ndims(ctx);
let src_ndims = src_ndarray.load_ndims(ctx);
ctx.make_assert(
generator,
ctx.builder.build_int_compare(
IntPredicate::EQ,
self_ndims,
src_ndims,
""
).unwrap(),
"0:AssertionError",
"NDArrayValue::copy_shape_from_ndarray: Expected self.ndims ({0}) == src_ndarray.ndims ({1})",
[Some(self_ndims), Some(src_ndims), None],
ctx.current_loc
);
}
let src_shape = src_ndarray.shape().base_ptr(ctx, generator); let src_shape = src_ndarray.shape().base_ptr(ctx, generator);
self.copy_shape_from_array(generator, ctx, src_shape); self.copy_shape_from_array(generator, ctx, src_shape);
@ -277,7 +297,26 @@ impl<'ctx> NDArrayValue<'ctx> {
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
src_ndarray: NDArrayValue<'ctx>, src_ndarray: NDArrayValue<'ctx>,
) { ) {
assert_eq!(self.ndims, src_ndarray.ndims); if self.ndims.is_some() && src_ndarray.ndims.is_some() {
assert_eq!(self.ndims, src_ndarray.ndims);
} else {
let self_ndims = self.load_ndims(ctx);
let src_ndims = src_ndarray.load_ndims(ctx);
ctx.make_assert(
generator,
ctx.builder.build_int_compare(
IntPredicate::EQ,
self_ndims,
src_ndims,
""
).unwrap(),
"0:AssertionError",
"NDArrayValue::copy_shape_from_ndarray: Expected self.ndims ({0}) == src_ndarray.ndims ({1})",
[Some(self_ndims), Some(src_ndims), None],
ctx.current_loc
);
}
let src_strides = src_ndarray.strides().base_ptr(ctx, generator); let src_strides = src_ndarray.strides().base_ptr(ctx, generator);
self.copy_strides_from_array(generator, ctx, src_strides); self.copy_strides_from_array(generator, ctx, src_strides);
@ -332,17 +371,17 @@ impl<'ctx> NDArrayValue<'ctx> {
irrt::ndarray::call_nac3_ndarray_set_strides_by_shape(generator, ctx, *self); irrt::ndarray::call_nac3_ndarray_set_strides_by_shape(generator, ctx, *self);
} }
/// Clone/Copy this ndarray - Allocate a new ndarray with the same shape as this ndarray and
/// copy the contents over.
///
/// The new ndarray will own its data and will be C-contiguous.
#[must_use] #[must_use]
pub fn make_copy<G: CodeGenerator + ?Sized>( pub fn make_copy<G: CodeGenerator + ?Sized>(
&self, &self,
generator: &mut G, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
) -> Self { ) -> Self {
let clone = self.get_type().construct_uninitialized(generator, ctx, None); let clone = if self.ndims.is_some() {
self.get_type().construct_uninitialized(generator, ctx, None)
} else {
self.get_type().construct_dyn_ndims(generator, ctx, self.load_ndims(ctx), None)
};
let shape = self.shape(); let shape = self.shape();
clone.copy_shape_from_array(generator, ctx, shape.base_ptr(ctx, generator)); clone.copy_shape_from_array(generator, ctx, shape.base_ptr(ctx, generator));
@ -367,149 +406,32 @@ impl<'ctx> NDArrayValue<'ctx> {
irrt::ndarray::call_nac3_ndarray_copy_data(generator, ctx, src, *self); irrt::ndarray::call_nac3_ndarray_copy_data(generator, ctx, src, *self);
} }
/// Fill the ndarray with a scalar.
///
/// `fill_value` must have the same LLVM type as the `dtype` of this ndarray.
pub fn fill<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
value: BasicValueEnum<'ctx>,
) {
// TODO: It is possible to optimize this by exploiting contiguous strides with memset.
// Probably best to implement in IRRT.
self.foreach(generator, ctx, |_, ctx, _, nditer| {
let p = nditer.get_pointer(ctx);
ctx.builder.build_store(p, value).unwrap();
Ok(())
})
.unwrap();
}
/// Create the shape tuple of this ndarray like
/// [`np.shape(<ndarray>)`](https://numpy.org/doc/stable/reference/generated/numpy.shape.html).
///
/// All elements in the tuple are `i32`.
pub fn make_shape_tuple<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) -> TupleValue<'ctx> {
let llvm_i32 = ctx.ctx.i32_type();
let objects = (0..self.ndims)
.map(|i| {
let dim = unsafe {
self.shape().get_typed_unchecked(
ctx,
generator,
&self.llvm_usize.const_int(i, false),
None,
)
};
ctx.builder.build_int_truncate_or_bit_cast(dim, llvm_i32, "").unwrap()
})
.map(|obj| obj.as_basic_value_enum())
.collect_vec();
TupleType::new(
generator,
ctx.ctx,
&repeat_n(llvm_i32.into(), self.ndims as usize).collect_vec(),
)
.construct_from_objects(ctx, objects, None)
}
/// Create the strides tuple of this ndarray like
/// [`<ndarray>.strides`](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.strides.html).
///
/// All elements in the tuple are `i32`.
pub fn make_strides_tuple<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) -> TupleValue<'ctx> {
let llvm_i32 = ctx.ctx.i32_type();
let objects = (0..self.ndims)
.map(|i| {
let dim = unsafe {
self.strides().get_typed_unchecked(
ctx,
generator,
&self.llvm_usize.const_int(i, false),
None,
)
};
ctx.builder.build_int_truncate_or_bit_cast(dim, llvm_i32, "").unwrap()
})
.map(|obj| obj.as_basic_value_enum())
.collect_vec();
TupleType::new(
generator,
ctx.ctx,
&repeat_n(llvm_i32.into(), self.ndims as usize).collect_vec(),
)
.construct_from_objects(ctx, objects, None)
}
/// Returns true if this ndarray is unsized - `ndims == 0` and only contains a scalar. /// Returns true if this ndarray is unsized - `ndims == 0` and only contains a scalar.
#[must_use] #[must_use]
pub fn is_unsized(&self) -> bool { pub fn is_unsized(&self) -> Option<bool> {
self.ndims == 0 self.ndims.map(|ndims| ndims == 0)
} }
/// Returns the element present in this `ndarray` if this is unsized. /// If this ndarray is unsized, return its sole value as an [`AnyObject`].
pub fn get_unsized_element<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) -> Option<BasicValueEnum<'ctx>> {
if self.is_unsized() {
// NOTE: `np.size(self) == 0` here is never possible.
let zero = generator.get_size_type(ctx.ctx).const_zero();
let value = unsafe { self.data().get_unchecked(ctx, generator, &zero, None) };
Some(value)
} else {
None
}
}
/// If this ndarray is unsized, return its sole value as an [`BasicValueEnum`].
/// Otherwise, do nothing and return the ndarray itself. /// Otherwise, do nothing and return the ndarray itself.
// TODO: Rename to get_unsized_element
pub fn split_unsized<G: CodeGenerator + ?Sized>( pub fn split_unsized<G: CodeGenerator + ?Sized>(
&self, &self,
generator: &mut G, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
) -> ScalarOrNDArray<'ctx> { ) -> ScalarOrNDArray<'ctx> {
if let Some(unsized_elem) = self.get_unsized_element(generator, ctx) { let Some(is_unsized) = self.is_unsized() else { todo!() };
ScalarOrNDArray::Scalar(unsized_elem)
if is_unsized {
// NOTE: `np.size(self) == 0` here is never possible.
let zero = generator.get_size_type(ctx.ctx).const_zero();
let value = unsafe { self.data().get_unchecked(ctx, generator, &zero, None) };
ScalarOrNDArray::Scalar(value)
} else { } else {
ScalarOrNDArray::NDArray(*self) ScalarOrNDArray::NDArray(*self)
} }
} }
/// Check if this `NDArray` can be used as an `out` ndarray for an operation.
///
/// Raise an exception if the shapes do not match.
pub fn assert_can_be_written_by_out<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
out_shape: impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>,
) {
let ndarray_shape = self.shape();
let output_shape = out_shape;
irrt::ndarray::call_nac3_ndarray_util_assert_output_shape_same(
generator,
ctx,
&ndarray_shape,
&output_shape,
);
}
} }
impl<'ctx> ProxyValue<'ctx> for NDArrayValue<'ctx> { impl<'ctx> ProxyValue<'ctx> for NDArrayValue<'ctx> {
@ -569,8 +491,8 @@ impl<'ctx> ArrayLikeValue<'ctx> for NDArrayShapeProxy<'ctx, '_> {
impl<'ctx> ArrayLikeIndexer<'ctx, IntValue<'ctx>> for NDArrayShapeProxy<'ctx, '_> { impl<'ctx> ArrayLikeIndexer<'ctx, IntValue<'ctx>> for NDArrayShapeProxy<'ctx, '_> {
unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>( unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
generator: &G, generator: &mut G,
idx: &IntValue<'ctx>, idx: &IntValue<'ctx>,
name: Option<&str>, name: Option<&str>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
@ -608,26 +530,20 @@ impl<'ctx> ArrayLikeIndexer<'ctx, IntValue<'ctx>> for NDArrayShapeProxy<'ctx, '_
impl<'ctx> UntypedArrayLikeAccessor<'ctx, IntValue<'ctx>> for NDArrayShapeProxy<'ctx, '_> {} impl<'ctx> UntypedArrayLikeAccessor<'ctx, IntValue<'ctx>> for NDArrayShapeProxy<'ctx, '_> {}
impl<'ctx> UntypedArrayLikeMutator<'ctx, IntValue<'ctx>> for NDArrayShapeProxy<'ctx, '_> {} impl<'ctx> UntypedArrayLikeMutator<'ctx, IntValue<'ctx>> for NDArrayShapeProxy<'ctx, '_> {}
impl<'ctx, G: CodeGenerator + ?Sized> TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>> impl<'ctx> TypedArrayLikeAccessor<'ctx, IntValue<'ctx>> for NDArrayShapeProxy<'ctx, '_> {
for NDArrayShapeProxy<'ctx, '_>
{
fn downcast_to_type( fn downcast_to_type(
&self, &self,
_: &CodeGenContext<'ctx, '_>, _: &mut CodeGenContext<'ctx, '_>,
_: &G,
value: BasicValueEnum<'ctx>, value: BasicValueEnum<'ctx>,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
value.into_int_value() value.into_int_value()
} }
} }
impl<'ctx, G: CodeGenerator + ?Sized> TypedArrayLikeMutator<'ctx, G, IntValue<'ctx>> impl<'ctx> TypedArrayLikeMutator<'ctx, IntValue<'ctx>> for NDArrayShapeProxy<'ctx, '_> {
for NDArrayShapeProxy<'ctx, '_>
{
fn upcast_from_type( fn upcast_from_type(
&self, &self,
_: &CodeGenContext<'ctx, '_>, _: &mut CodeGenContext<'ctx, '_>,
_: &G,
value: IntValue<'ctx>, value: IntValue<'ctx>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
value.into() value.into()
@ -667,8 +583,8 @@ impl<'ctx> ArrayLikeValue<'ctx> for NDArrayStridesProxy<'ctx, '_> {
impl<'ctx> ArrayLikeIndexer<'ctx, IntValue<'ctx>> for NDArrayStridesProxy<'ctx, '_> { impl<'ctx> ArrayLikeIndexer<'ctx, IntValue<'ctx>> for NDArrayStridesProxy<'ctx, '_> {
unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>( unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
generator: &G, generator: &mut G,
idx: &IntValue<'ctx>, idx: &IntValue<'ctx>,
name: Option<&str>, name: Option<&str>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
@ -706,26 +622,20 @@ impl<'ctx> ArrayLikeIndexer<'ctx, IntValue<'ctx>> for NDArrayStridesProxy<'ctx,
impl<'ctx> UntypedArrayLikeAccessor<'ctx, IntValue<'ctx>> for NDArrayStridesProxy<'ctx, '_> {} impl<'ctx> UntypedArrayLikeAccessor<'ctx, IntValue<'ctx>> for NDArrayStridesProxy<'ctx, '_> {}
impl<'ctx> UntypedArrayLikeMutator<'ctx, IntValue<'ctx>> for NDArrayStridesProxy<'ctx, '_> {} impl<'ctx> UntypedArrayLikeMutator<'ctx, IntValue<'ctx>> for NDArrayStridesProxy<'ctx, '_> {}
impl<'ctx, G: CodeGenerator + ?Sized> TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>> impl<'ctx> TypedArrayLikeAccessor<'ctx, IntValue<'ctx>> for NDArrayStridesProxy<'ctx, '_> {
for NDArrayStridesProxy<'ctx, '_>
{
fn downcast_to_type( fn downcast_to_type(
&self, &self,
_: &CodeGenContext<'ctx, '_>, _: &mut CodeGenContext<'ctx, '_>,
_: &G,
value: BasicValueEnum<'ctx>, value: BasicValueEnum<'ctx>,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
value.into_int_value() value.into_int_value()
} }
} }
impl<'ctx, G: CodeGenerator + ?Sized> TypedArrayLikeMutator<'ctx, G, IntValue<'ctx>> impl<'ctx> TypedArrayLikeMutator<'ctx, IntValue<'ctx>> for NDArrayStridesProxy<'ctx, '_> {
for NDArrayStridesProxy<'ctx, '_>
{
fn upcast_from_type( fn upcast_from_type(
&self, &self,
_: &CodeGenContext<'ctx, '_>, _: &mut CodeGenContext<'ctx, '_>,
_: &G,
value: IntValue<'ctx>, value: IntValue<'ctx>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
value.into() value.into()
@ -758,19 +668,41 @@ impl<'ctx> ArrayLikeValue<'ctx> for NDArrayDataProxy<'ctx, '_> {
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
generator: &G, generator: &G,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
irrt::ndarray::call_nac3_ndarray_len(generator, ctx, *self.0) irrt::ndarray::call_ndarray_calc_size(
generator,
ctx,
&self.as_slice_value(ctx, generator),
(None, None),
)
} }
} }
impl<'ctx> ArrayLikeIndexer<'ctx> for NDArrayDataProxy<'ctx, '_> { impl<'ctx> ArrayLikeIndexer<'ctx> for NDArrayDataProxy<'ctx, '_> {
unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>( unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
generator: &G, generator: &mut G,
idx: &IntValue<'ctx>, idx: &IntValue<'ctx>,
name: Option<&str>, name: Option<&str>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
let ptr = irrt::ndarray::call_nac3_ndarray_get_nth_pelement(generator, ctx, *self.0, *idx); let sizeof_elem = ctx
.builder
.build_int_truncate_or_bit_cast(
self.element_type(ctx, generator).size_of().unwrap(),
idx.get_type(),
"",
)
.unwrap();
let idx = ctx.builder.build_int_mul(*idx, sizeof_elem, "").unwrap();
let ptr = unsafe {
ctx.builder
.build_in_bounds_gep(
self.base_ptr(ctx, generator),
&[idx],
name.unwrap_or_default(),
)
.unwrap()
};
// Current implementation is transparent - The returned pointer type is // Current implementation is transparent - The returned pointer type is
// already cast into the expected type, allowing for immediately // already cast into the expected type, allowing for immediately
@ -781,7 +713,7 @@ impl<'ctx> ArrayLikeIndexer<'ctx> for NDArrayDataProxy<'ctx, '_> {
BasicTypeEnum::try_from(self.element_type(ctx, generator)) BasicTypeEnum::try_from(self.element_type(ctx, generator))
.unwrap() .unwrap()
.ptr_type(AddressSpace::default()), .ptr_type(AddressSpace::default()),
name.unwrap_or_default(), "",
) )
.unwrap() .unwrap()
} }
@ -829,33 +761,55 @@ impl<'ctx, Index: UntypedArrayLikeAccessor<'ctx>> ArrayLikeIndexer<'ctx, Index>
{ {
unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>( unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
generator: &G, generator: &mut G,
indices: &Index, indices: &Index,
name: Option<&str>, name: Option<&str>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
assert_eq!(indices.element_type(ctx, generator), generator.get_size_type(ctx.ctx).into()); let llvm_usize = generator.get_size_type(ctx.ctx);
let indices = TypedArrayLikeAdapter::from( let indices_elem_ty = indices
indices.as_slice_value(ctx, generator), .ptr_offset(ctx, generator, &llvm_usize.const_zero(), None)
|_, _, v| v.into_int_value(), .get_type()
|_, _, v| v.into(), .get_element_type();
let Ok(indices_elem_ty) = IntType::try_from(indices_elem_ty) else {
panic!("Expected list[int32] but got {indices_elem_ty}")
};
assert_eq!(
indices_elem_ty.get_bit_width(),
32,
"Expected list[int32] but got list[int{}]",
indices_elem_ty.get_bit_width()
); );
let ptr = irrt::ndarray::call_nac3_ndarray_get_pelement_by_indices( let index = irrt::ndarray::call_ndarray_flatten_index(generator, ctx, *self.0, indices);
generator, ctx, *self.0, &indices, let sizeof_elem = ctx
); .builder
.build_int_truncate_or_bit_cast(
self.element_type(ctx, generator).size_of().unwrap(),
index.get_type(),
"",
)
.unwrap();
let index = ctx.builder.build_int_mul(index, sizeof_elem, "").unwrap();
// Current implementation is transparent - The returned pointer type is let ptr = unsafe {
// already cast into the expected type, allowing for immediately ctx.builder
// load/store. .build_in_bounds_gep(
self.base_ptr(ctx, generator),
&[index],
name.unwrap_or_default(),
)
.unwrap()
};
// TODO: Current implementation is transparent
ctx.builder ctx.builder
.build_pointer_cast( .build_pointer_cast(
ptr, ptr,
BasicTypeEnum::try_from(self.element_type(ctx, generator)) BasicTypeEnum::try_from(self.element_type(ctx, generator))
.unwrap() .unwrap()
.ptr_type(AddressSpace::default()), .ptr_type(AddressSpace::default()),
name.unwrap_or_default(), "",
) )
.unwrap() .unwrap()
} }
@ -950,9 +904,10 @@ impl<'ctx, Index: UntypedArrayLikeAccessor<'ctx>> UntypedArrayLikeMutator<'ctx,
/// This function is used generating strides for globally defined contiguous ndarrays. /// This function is used generating strides for globally defined contiguous ndarrays.
#[must_use] #[must_use]
pub fn make_contiguous_strides(itemsize: u64, ndims: u64, shape: &[u64]) -> Vec<u64> { pub fn make_contiguous_strides(itemsize: u64, ndims: u64, shape: &[u64]) -> Vec<u64> {
let mut strides = vec![0u64; ndims as usize]; let mut strides = Vec::with_capacity(ndims as usize);
let mut stride_product = 1u64; let mut stride_product = 1u64;
for axis in (0..ndims).rev() { for i in 0..ndims {
let axis = ndims - i - 1;
strides[axis as usize] = stride_product * itemsize; strides[axis as usize] = stride_product * itemsize;
stride_product *= shape[axis as usize]; stride_product *= shape[axis as usize];
} }
@ -966,52 +921,7 @@ pub enum ScalarOrNDArray<'ctx> {
NDArray(NDArrayValue<'ctx>), NDArray(NDArrayValue<'ctx>),
} }
impl<'ctx> TryFrom<&ScalarOrNDArray<'ctx>> for BasicValueEnum<'ctx> {
type Error = ();
fn try_from(value: &ScalarOrNDArray<'ctx>) -> Result<Self, Self::Error> {
match value {
ScalarOrNDArray::Scalar(scalar) => Ok(*scalar),
ScalarOrNDArray::NDArray(_) => Err(()),
}
}
}
impl<'ctx> TryFrom<&ScalarOrNDArray<'ctx>> for NDArrayValue<'ctx> {
type Error = ();
fn try_from(value: &ScalarOrNDArray<'ctx>) -> Result<Self, Self::Error> {
match value {
ScalarOrNDArray::Scalar(_) => Err(()),
ScalarOrNDArray::NDArray(ndarray) => Ok(*ndarray),
}
}
}
impl<'ctx> ScalarOrNDArray<'ctx> { impl<'ctx> ScalarOrNDArray<'ctx> {
/// Split on `object` either into a scalar or an ndarray.
///
/// If `object` is an ndarray, [`ScalarOrNDArray::NDArray`].
///
/// For everything else, it is wrapped with [`ScalarOrNDArray::Scalar`].
pub fn from_value<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
(object_ty, object): (Type, BasicValueEnum<'ctx>),
) -> ScalarOrNDArray<'ctx> {
match &*ctx.unifier.get_ty(object_ty) {
TypeEnum::TObj { obj_id, .. }
if *obj_id == ctx.primitives.ndarray.obj_id(&ctx.unifier).unwrap() =>
{
let ndarray = NDArrayType::from_unifier_type(generator, ctx, object_ty)
.map_value(object.into_pointer_value(), None);
ScalarOrNDArray::NDArray(ndarray)
}
_ => ScalarOrNDArray::Scalar(object),
}
}
/// Get the underlying [`BasicValueEnum<'ctx>`] of this [`ScalarOrNDArray`]. /// Get the underlying [`BasicValueEnum<'ctx>`] of this [`ScalarOrNDArray`].
#[must_use] #[must_use]
pub fn to_basic_value_enum(self) -> BasicValueEnum<'ctx> { pub fn to_basic_value_enum(self) -> BasicValueEnum<'ctx> {
@ -1020,57 +930,4 @@ impl<'ctx> ScalarOrNDArray<'ctx> {
ScalarOrNDArray::NDArray(ndarray) => ndarray.as_base_value().into(), ScalarOrNDArray::NDArray(ndarray) => ndarray.as_base_value().into(),
} }
} }
/// Convert this [`ScalarOrNDArray`] to an ndarray - behaves like `np.asarray`.
///
/// - If this is an ndarray, the ndarray is returned.
/// - If this is a scalar, this function returns new ndarray created with
/// [`NDArrayType::construct_unsized`].
pub fn to_ndarray<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) -> NDArrayValue<'ctx> {
match self {
ScalarOrNDArray::NDArray(ndarray) => *ndarray,
ScalarOrNDArray::Scalar(scalar) => {
NDArrayType::new_unsized(generator, ctx.ctx, scalar.get_type())
.construct_unsized(generator, ctx, scalar, None)
}
}
}
/// Get the dtype of the ndarray created if this were called with
/// [`ScalarOrNDArray::to_ndarray`].
#[must_use]
pub fn get_dtype(&self) -> BasicTypeEnum<'ctx> {
match self {
ScalarOrNDArray::NDArray(ndarray) => ndarray.dtype,
ScalarOrNDArray::Scalar(scalar) => scalar.get_type(),
}
}
}
/// An helper enum specifying how a function should produce its output.
///
/// Many functions in NumPy has an optional `out` parameter (e.g., `matmul`). If `out` is specified
/// with an ndarray, the result of a function will be written to `out`. If `out` is not specified, a
/// function will create a new ndarray and store the result in it.
#[derive(Clone, Copy)]
pub enum NDArrayOut<'ctx> {
/// Tell a function should create a new ndarray with the expected element type `dtype`.
NewNDArray { dtype: BasicTypeEnum<'ctx> },
/// Tell a function to write the result to `ndarray`.
WriteToNDArray { ndarray: NDArrayValue<'ctx> },
}
impl<'ctx> NDArrayOut<'ctx> {
/// Get the dtype of this output.
#[must_use]
pub fn get_dtype(&self) -> BasicTypeEnum<'ctx> {
match self {
NDArrayOut::NewNDArray { dtype } => *dtype,
NDArrayOut::WriteToNDArray { ndarray } => ndarray.dtype,
}
}
} }

View File

@ -4,7 +4,7 @@ use inkwell::{
AddressSpace, AddressSpace,
}; };
use super::{NDArrayValue, ProxyValue}; use super::{NDArrayValue, ProxyValue, TypedArrayLikeAccessor, TypedArrayLikeMutator};
use crate::codegen::{ use crate::codegen::{
irrt, irrt,
stmt::{gen_for_callback, BreakContinueHooks}, stmt::{gen_for_callback, BreakContinueHooks},
@ -69,10 +69,7 @@ impl<'ctx> NDIterValue<'ctx> {
irrt::ndarray::call_nac3_nditer_next(generator, ctx, *self); irrt::ndarray::call_nac3_nditer_next(generator, ctx, *self);
} }
fn element_field( fn element(&self, ctx: &CodeGenContext<'ctx, '_>) -> StructField<'ctx, PointerValue<'ctx>> {
&self,
ctx: &CodeGenContext<'ctx, '_>,
) -> StructField<'ctx, PointerValue<'ctx>> {
self.get_type().get_fields(ctx.ctx).element self.get_type().get_fields(ctx.ctx).element
} }
@ -81,7 +78,7 @@ impl<'ctx> NDIterValue<'ctx> {
pub fn get_pointer(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> { pub fn get_pointer(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
let elem_ty = self.parent.dtype; let elem_ty = self.parent.dtype;
let p = self.element_field(ctx).get(ctx, self.as_base_value(), self.name); let p = self.element(ctx).get(ctx, self.as_base_value(), None);
ctx.builder ctx.builder
.build_pointer_cast(p, elem_ty.ptr_type(AddressSpace::default()), "element") .build_pointer_cast(p, elem_ty.ptr_type(AddressSpace::default()), "element")
.unwrap() .unwrap()
@ -94,25 +91,30 @@ impl<'ctx> NDIterValue<'ctx> {
ctx.builder.build_load(p, "value").unwrap() ctx.builder.build_load(p, "value").unwrap()
} }
fn nth_field(&self, ctx: &CodeGenContext<'ctx, '_>) -> StructField<'ctx, IntValue<'ctx>> { fn nth(&self, ctx: &CodeGenContext<'ctx, '_>) -> StructField<'ctx, IntValue<'ctx>> {
self.get_type().get_fields(ctx.ctx).nth self.get_type().get_fields(ctx.ctx).nth
} }
/// Get the index of the current element if this ndarray were a flat ndarray. /// Get the index of the current element if this ndarray were a flat ndarray.
#[must_use] #[must_use]
pub fn get_index(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> { pub fn get_index(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
self.nth_field(ctx).get(ctx, self.as_base_value(), self.name) self.nth(ctx).get(ctx, self.as_base_value(), None)
} }
/// Get the indices of the current element. /// Get the indices of the current element.
#[must_use] #[must_use]
pub fn get_indices<G: CodeGenerator + ?Sized>( pub fn get_indices(
&self, &'ctx self,
) -> TypedArrayLikeAdapter<'ctx, G, IntValue<'ctx>> { ) -> impl TypedArrayLikeAccessor<'ctx, IntValue<'ctx>> + TypedArrayLikeMutator<'ctx, IntValue<'ctx>>
{
TypedArrayLikeAdapter::from( TypedArrayLikeAdapter::from(
self.indices, self.indices,
|_, _, val| val.into_int_value(), Box::new(|ctx, val| {
|_, _, val| val.into(), ctx.builder
.build_int_z_extend_or_bit_cast(val.into_int_value(), self.llvm_usize, "")
.unwrap()
}),
Box::new(|_, val| val.into()),
) )
} }
} }
@ -141,10 +143,6 @@ impl<'ctx> NDArrayValue<'ctx> {
/// ///
/// `body` has access to [`BreakContinueHooks`] to short-circuit and [`NDIterValue`] to /// `body` has access to [`BreakContinueHooks`] to short-circuit and [`NDIterValue`] to
/// get properties of the current iteration (e.g., the current element, indices, etc.) /// get properties of the current iteration (e.g., the current element, indices, etc.)
///
/// Note: The caller is recommended to call `llvm.stacksave` and `llvm.stackrestore` before and
/// after invoking this function respectively. See [`NDIterType::construct`] for an explanation
/// on why this is suggested.
pub fn foreach<'a, G, F>( pub fn foreach<'a, G, F>(
&self, &self,
generator: &mut G, generator: &mut G,

View File

@ -1,152 +0,0 @@
use inkwell::values::{BasicValueEnum, IntValue};
use crate::{
codegen::{
stmt::gen_for_callback_incrementing,
types::{ListType, TupleType},
values::{
ArraySliceValue, ProxyValue, TypedArrayLikeAccessor, TypedArrayLikeAdapter,
TypedArrayLikeMutator, UntypedArrayLikeAccessor,
},
CodeGenContext, CodeGenerator,
},
typecheck::typedef::{Type, TypeEnum},
};
/// Parse a NumPy-like "int sequence" input and return the int sequence as an array and its length.
///
/// * `sequence` - The `sequence` parameter.
/// * `sequence_ty` - The typechecker type of `sequence`
///
/// The `sequence` argument type may only be one of the following:
/// 1. A list of `int32`; e.g., `np.empty([600, 800, 3])`
/// 2. A tuple of `int32`; e.g., `np.empty((600, 800, 3))`
/// 3. A scalar `int32`; e.g., `np.empty(3)`, this is functionally equivalent to
/// `np.empty([3])`
///
/// All `int32` values will be sign-extended to `SizeT`.
pub fn parse_numpy_int_sequence<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
(input_seq_ty, input_seq): (Type, BasicValueEnum<'ctx>),
) -> impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>> {
let llvm_usize = generator.get_size_type(ctx.ctx);
let zero = llvm_usize.const_zero();
let one = llvm_usize.const_int(1, false);
// The result `list` to return.
match &*ctx.unifier.get_ty_immutable(input_seq_ty) {
TypeEnum::TObj { obj_id, .. }
if *obj_id == ctx.primitives.list.obj_id(&ctx.unifier).unwrap() =>
{
// 1. A list of `int32`; e.g., `np.empty([600, 800, 3])`
let input_seq = ListType::from_unifier_type(generator, ctx, input_seq_ty)
.map_value(input_seq.into_pointer_value(), None);
let len = input_seq.load_size(ctx, None);
// TODO: Find a way to remove this mid-BB allocation
let result = ctx.builder.build_array_alloca(llvm_usize, len, "").unwrap();
let result = TypedArrayLikeAdapter::from(
ArraySliceValue::from_ptr_val(result, len, None),
|_, _, val| val.into_int_value(),
|_, _, val| val.into(),
);
// Load all the `int32`s from the input_sequence, cast them to `SizeT`, and store them into `result`
gen_for_callback_incrementing(
generator,
ctx,
None,
zero,
(len, false),
|generator, ctx, _, i| {
// Load the i-th int32 in the input sequence
let int = unsafe {
input_seq.data().get_unchecked(ctx, generator, &i, None).into_int_value()
};
// Cast to SizeT
let int =
ctx.builder.build_int_s_extend_or_bit_cast(int, llvm_usize, "").unwrap();
// Store
unsafe { result.set_typed_unchecked(ctx, generator, &i, int) };
Ok(())
},
one,
)
.unwrap();
result
}
TypeEnum::TTuple { .. } => {
// 2. A tuple of ints; e.g., `np.empty((600, 800, 3))`
let input_seq = TupleType::from_unifier_type(generator, ctx, input_seq_ty)
.map_value(input_seq.into_struct_value(), None);
let len = input_seq.get_type().num_elements();
let result = generator
.gen_array_var_alloc(
ctx,
llvm_usize.into(),
llvm_usize.const_int(u64::from(len), false),
None,
)
.unwrap();
let result = TypedArrayLikeAdapter::from(
result,
|_, _, val| val.into_int_value(),
|_, _, val| val.into(),
);
for i in 0..input_seq.get_type().num_elements() {
// Get the i-th element off of the tuple and load it into `result`.
let int = input_seq.load_element(ctx, i).into_int_value();
let int = ctx.builder.build_int_s_extend_or_bit_cast(int, llvm_usize, "").unwrap();
unsafe {
result.set_typed_unchecked(
ctx,
generator,
&llvm_usize.const_int(u64::from(i), false),
int,
);
}
}
result
}
TypeEnum::TObj { obj_id, .. }
if *obj_id == ctx.primitives.int32.obj_id(&ctx.unifier).unwrap() =>
{
// 3. A scalar int; e.g., `np.empty(3)`, this is functionally equivalent to `np.empty([3])`
let input_int = input_seq.into_int_value();
let len = one;
let result = generator.gen_array_var_alloc(ctx, llvm_usize.into(), len, None).unwrap();
let result = TypedArrayLikeAdapter::from(
result,
|_, _, val| val.into_int_value(),
|_, _, val| val.into(),
);
let int =
ctx.builder.build_int_s_extend_or_bit_cast(input_int, llvm_usize, "").unwrap();
// Storing into result[0]
unsafe {
result.set_typed_unchecked(ctx, generator, &zero, int);
}
result
}
_ => panic!("encountered unknown sequence type: {}", ctx.unifier.stringify(input_seq_ty)),
}
}

View File

@ -1,16 +1,9 @@
use std::iter::{once, repeat_n}; use std::iter::{once, repeat_n};
use inkwell::values::{IntValue, PointerValue};
use itertools::Itertools; use itertools::Itertools;
use crate::codegen::{ use crate::codegen::{
irrt, values::ndarray::{NDArrayValue, RustNDIndex},
stmt::gen_if_callback,
types::ndarray::NDArrayType,
values::{
ndarray::{NDArrayValue, RustNDIndex},
ArrayLikeValue, ArraySliceValue, ProxyValue, TypedArrayLikeAccessor, TypedArrayLikeAdapter,
},
CodeGenContext, CodeGenerator, CodeGenContext, CodeGenerator,
}; };
@ -26,7 +19,9 @@ impl<'ctx> NDArrayValue<'ctx> {
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
ndmin: u64, ndmin: u64,
) -> Self { ) -> Self {
let ndims = self.ndims; assert!(self.ndims.is_some(), "NDArrayValue::atleast_nd is only supported for instances with compile-time known ndims (self.ndims = Some(...))");
let ndims = self.ndims.unwrap();
if ndims < ndmin { if ndims < ndmin {
// Extend the dimensions with np.newaxis. // Extend the dimensions with np.newaxis.
@ -38,117 +33,4 @@ impl<'ctx> NDArrayValue<'ctx> {
*self *self
} }
} }
/// Create a reshaped view on this ndarray like
/// [`np.reshape()`](https://numpy.org/doc/stable/reference/generated/numpy.reshape.html).
///
/// If there is a `-1` in `new_shape`, it will be resolved; `new_shape` would **NOT** be
/// modified as a result.
///
/// If reshape without copying is impossible, this function will allocate a new ndarray and copy
/// contents.
///
/// * `new_ndims` - The number of dimensions of `new_shape` as a [`Type`].
/// * `new_shape` - The target shape to do `np.reshape()`.
#[must_use]
pub fn reshape_or_copy<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
new_ndims: u64,
new_shape: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>,
) -> Self {
assert_eq!(new_shape.element_type(ctx, generator), self.llvm_usize.into());
// TODO: The current criterion for whether to do a full copy or not is by checking
// `is_c_contiguous`, but this is not optimal - there are cases when the ndarray is
// not contiguous but could be reshaped without copying data. Look into how numpy does
// it.
let dst_ndarray = NDArrayType::new(generator, ctx.ctx, self.dtype, new_ndims)
.construct_uninitialized(generator, ctx, None);
dst_ndarray.copy_shape_from_array(generator, ctx, new_shape.base_ptr(ctx, generator));
// Resolve negative indices
let size = self.size(generator, ctx);
let dst_ndims = self.llvm_usize.const_int(dst_ndarray.get_type().ndims(), false);
let dst_shape = dst_ndarray.shape();
irrt::ndarray::call_nac3_ndarray_reshape_resolve_and_check_new_shape(
generator,
ctx,
size,
dst_ndims,
dst_shape.as_slice_value(ctx, generator),
);
gen_if_callback(
generator,
ctx,
|generator, ctx| Ok(self.is_c_contiguous(generator, ctx)),
|generator, ctx| {
// Reshape is possible without copying
dst_ndarray.set_strides_contiguous(generator, ctx);
dst_ndarray.store_data(ctx, self.data().base_ptr(ctx, generator));
Ok(())
},
|generator, ctx| {
// Reshape is impossible without copying
unsafe {
dst_ndarray.create_data(generator, ctx);
}
dst_ndarray.copy_data_from(generator, ctx, *self);
Ok(())
},
)
.unwrap();
dst_ndarray
}
/// Create a transposed view on this ndarray like
/// [`np.transpose(<ndarray>, <axes> = None)`](https://numpy.org/doc/stable/reference/generated/numpy.transpose.html).
///
/// * `axes` - If specified, should be an array of the permutation (negative indices are
/// **allowed**).
#[must_use]
pub fn transpose<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
axes: Option<PointerValue<'ctx>>,
) -> Self {
assert!(
axes.is_none_or(|axes| axes.get_type().get_element_type() == self.llvm_usize.into())
);
// Define models
let transposed_ndarray = self.get_type().construct_uninitialized(generator, ctx, None);
let axes = if let Some(axes) = axes {
let num_axes = self.llvm_usize.const_int(self.ndims, false);
// `axes = nullptr` if `axes` is unspecified.
let axes = ArraySliceValue::from_ptr_val(axes, num_axes, None);
Some(TypedArrayLikeAdapter::from(
axes,
|_, _, val| val.into_int_value(),
|_, _, val| val.into(),
))
} else {
None
};
irrt::ndarray::call_nac3_ndarray_transpose(
generator,
ctx,
*self,
transposed_ndarray,
axes.as_ref(),
);
transposed_ndarray
}
} }

View File

@ -1,85 +0,0 @@
use inkwell::{
types::IntType,
values::{BasicValue, BasicValueEnum, StructValue},
};
use super::ProxyValue;
use crate::codegen::{types::TupleType, CodeGenContext};
#[derive(Copy, Clone)]
pub struct TupleValue<'ctx> {
value: StructValue<'ctx>,
llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>,
}
impl<'ctx> TupleValue<'ctx> {
/// Checks whether `value` is an instance of `tuple`, returning [Err] if `value` is not an
/// instance.
pub fn is_representable(
value: StructValue<'ctx>,
_llvm_usize: IntType<'ctx>,
) -> Result<(), String> {
TupleType::is_representable(value.get_type())
}
/// Creates an [`TupleValue`] from a [`StructValue`].
#[must_use]
pub fn from_struct_value(
value: StructValue<'ctx>,
llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>,
) -> Self {
debug_assert!(Self::is_representable(value, llvm_usize).is_ok());
Self { value, llvm_usize, name }
}
/// Stores a value into the tuple element at the given `index`.
pub fn store_element(
&mut self,
ctx: &CodeGenContext<'ctx, '_>,
index: u32,
element: impl BasicValue<'ctx>,
) {
assert_eq!(element.as_basic_value_enum().get_type(), unsafe {
self.get_type().type_at_index_unchecked(index)
});
let new_value = ctx
.builder
.build_insert_value(self.value, element, index, self.name.unwrap_or_default())
.unwrap();
self.value = new_value.into_struct_value();
}
/// Loads a value from the tuple element at the given `index`.
pub fn load_element(&self, ctx: &CodeGenContext<'ctx, '_>, index: u32) -> BasicValueEnum<'ctx> {
ctx.builder
.build_extract_value(
self.value,
index,
&format!("{}[{{i}}]", self.name.unwrap_or("tuple")),
)
.unwrap()
}
}
impl<'ctx> ProxyValue<'ctx> for TupleValue<'ctx> {
type Base = StructValue<'ctx>;
type Type = TupleType<'ctx>;
fn get_type(&self) -> Self::Type {
TupleType::from_type(self.as_base_value().get_type(), self.llvm_usize)
}
fn as_base_value(&self) -> Self::Base {
self.value
}
}
impl<'ctx> From<TupleValue<'ctx>> for StructValue<'ctx> {
fn from(value: TupleValue<'ctx>) -> Self {
value.as_base_value()
}
}

View File

@ -6,7 +6,7 @@ use std::{
}; };
use inkwell::values::{BasicValueEnum, FloatValue, IntValue, PointerValue, StructValue}; use inkwell::values::{BasicValueEnum, FloatValue, IntValue, PointerValue, StructValue};
use itertools::{izip, Itertools}; use itertools::{chain, izip, Itertools};
use parking_lot::RwLock; use parking_lot::RwLock;
use nac3parser::ast::{Constant, Expr, Location, StrRef}; use nac3parser::ast::{Constant, Expr, Location, StrRef};
@ -452,11 +452,11 @@ pub fn parse_type_annotation<T>(
type_vars.len() type_vars.len()
)])); )]));
} }
let fields = fields let fields = chain(
.iter() fields.iter().map(|(k, v, m)| (*k, (*v, *m))),
.map(|(k, v, m)| (*k, (*v, *m))) methods.iter().map(|(k, v, _)| (*k, (*v, false))),
.chain(methods.iter().map(|(k, v, _)| (*k, (*v, false)))) )
.collect(); .collect();
Ok(unifier.add_ty(TypeEnum::TObj { obj_id, fields, params: VarMap::default() })) Ok(unifier.add_ty(TypeEnum::TObj { obj_id, fields, params: VarMap::default() }))
} else { } else {
Err(HashSet::from([format!("Cannot use function name as type at {loc}")])) Err(HashSet::from([format!("Cannot use function name as type at {loc}")]))

View File

@ -1,14 +1,18 @@
use std::iter::once; use std::iter::once;
use indexmap::IndexMap; use indexmap::IndexMap;
use inkwell::{values::BasicValue, IntPredicate}; use inkwell::{
attributes::{Attribute, AttributeLoc},
types::{BasicMetadataTypeEnum, BasicType},
values::{BasicMetadataValueEnum, BasicValue, CallSiteValue},
IntPredicate,
};
use itertools::Either;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use super::{ use super::{
helper::{ helper::{debug_assert_prim_is_allowed, make_exception_fields, PrimDef, PrimDefDetails},
debug_assert_prim_is_allowed, extract_ndims, make_exception_fields, PrimDef, PrimDefDetails, numpy::make_ndarray_ty,
},
numpy::{make_ndarray_ty, unpack_ndarray_var_tys},
*, *,
}; };
use crate::{ use crate::{
@ -16,8 +20,7 @@ use crate::{
builtin_fns, builtin_fns,
numpy::*, numpy::*,
stmt::exn_constructor, stmt::exn_constructor,
types::ndarray::NDArrayType, values::{ProxyValue, RangeValue},
values::{ndarray::shape::parse_numpy_int_sequence, ProxyValue, RangeValue},
}, },
symbol_resolver::SymbolValue, symbol_resolver::SymbolValue,
typecheck::typedef::{into_var_map, iter_type_vars, TypeVar, VarMap}, typecheck::typedef::{into_var_map, iter_type_vars, TypeVar, VarMap},
@ -145,6 +148,144 @@ fn create_fn_by_codegen(
} }
} }
/// Creates a NumPy [`TopLevelDef`] function using an LLVM intrinsic.
///
/// * `name`: The name of the implemented NumPy function.
/// * `ret_ty`: The return type of this function.
/// * `param_ty`: The parameters accepted by this function, represented by a tuple of the
/// [parameter type][Type] and the parameter symbol name.
/// * `intrinsic_fn`: The fully-qualified name of the LLVM intrinsic function.
fn create_fn_by_intrinsic(
unifier: &mut Unifier,
var_map: &VarMap,
name: &'static str,
ret_ty: Type,
params: &[(Type, &'static str)],
intrinsic_fn: &'static str,
) -> TopLevelDef {
let param_tys = params.iter().map(|p| p.0).collect_vec();
create_fn_by_codegen(
unifier,
var_map,
name,
ret_ty,
params,
Box::new(move |ctx, _, fun, args, generator| {
let args_ty = fun.0.args.iter().map(|a| a.ty).collect_vec();
assert!(param_tys
.iter()
.zip(&args_ty)
.all(|(expected, actual)| ctx.unifier.unioned(*expected, *actual)));
let args_val = args_ty
.iter()
.zip_eq(args.iter())
.map(|(ty, arg)| arg.1.clone().to_basic_value_enum(ctx, generator, *ty).unwrap())
.map_into::<BasicMetadataValueEnum>()
.collect_vec();
let intrinsic_fn = ctx.module.get_function(intrinsic_fn).unwrap_or_else(|| {
let ret_llvm_ty = ctx.get_llvm_abi_type(generator, ret_ty);
let param_llvm_ty = param_tys
.iter()
.map(|p| ctx.get_llvm_abi_type(generator, *p))
.map_into::<BasicMetadataTypeEnum>()
.collect_vec();
let fn_type = ret_llvm_ty.fn_type(param_llvm_ty.as_slice(), false);
ctx.module.add_function(intrinsic_fn, fn_type, None)
});
let val = ctx
.builder
.build_call(intrinsic_fn, args_val.as_slice(), name)
.map(CallSiteValue::try_as_basic_value)
.map(Either::unwrap_left)
.unwrap();
Ok(val.into())
}),
)
}
/// Creates a unary NumPy [`TopLevelDef`] function using an extern function (e.g. from `libc` or
/// `libm`).
///
/// * `name`: The name of the implemented NumPy function.
/// * `ret_ty`: The return type of this function.
/// * `param_ty`: The parameters accepted by this function, represented by a tuple of the
/// [parameter type][Type] and the parameter symbol name.
/// * `extern_fn`: The fully-qualified name of the extern function used as the implementation.
/// * `attrs`: The list of attributes to apply to this function declaration. Note that `nounwind` is
/// already implied by the C ABI.
fn create_fn_by_extern(
unifier: &mut Unifier,
var_map: &VarMap,
name: &'static str,
ret_ty: Type,
params: &[(Type, &'static str)],
extern_fn: &'static str,
attrs: &'static [&str],
) -> TopLevelDef {
let param_tys = params.iter().map(|p| p.0).collect_vec();
create_fn_by_codegen(
unifier,
var_map,
name,
ret_ty,
params,
Box::new(move |ctx, _, fun, args, generator| {
let args_ty = fun.0.args.iter().map(|a| a.ty).collect_vec();
assert!(param_tys
.iter()
.zip(&args_ty)
.all(|(expected, actual)| ctx.unifier.unioned(*expected, *actual)));
let args_val = args_ty
.iter()
.zip_eq(args.iter())
.map(|(ty, arg)| arg.1.clone().to_basic_value_enum(ctx, generator, *ty).unwrap())
.map_into::<BasicMetadataValueEnum>()
.collect_vec();
let intrinsic_fn = ctx.module.get_function(extern_fn).unwrap_or_else(|| {
let ret_llvm_ty = ctx.get_llvm_abi_type(generator, ret_ty);
let param_llvm_ty = param_tys
.iter()
.map(|p| ctx.get_llvm_abi_type(generator, *p))
.map_into::<BasicMetadataTypeEnum>()
.collect_vec();
let fn_type = ret_llvm_ty.fn_type(param_llvm_ty.as_slice(), false);
let func = ctx.module.add_function(extern_fn, fn_type, None);
func.add_attribute(
AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id("nounwind"), 0),
);
for attr in attrs {
func.add_attribute(
AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0),
);
}
func
});
let val = ctx
.builder
.build_call(intrinsic_fn, &args_val, name)
.map(CallSiteValue::try_as_basic_value)
.map(Either::unwrap_left)
.unwrap();
Ok(val.into())
}),
)
}
pub fn get_builtins(unifier: &mut Unifier, primitives: &PrimitiveStore) -> BuiltinInfo { pub fn get_builtins(unifier: &mut Unifier, primitives: &PrimitiveStore) -> BuiltinInfo {
BuiltinBuilder::new(unifier, primitives) BuiltinBuilder::new(unifier, primitives)
.build_all_builtins() .build_all_builtins()
@ -195,6 +336,7 @@ struct BuiltinBuilder<'a> {
ndarray_float: Type, ndarray_float: Type,
ndarray_float_2d: Type, ndarray_float_2d: Type,
ndarray_num_ty: Type,
float_or_ndarray_ty: TypeVar, float_or_ndarray_ty: TypeVar,
float_or_ndarray_var_map: VarMap, float_or_ndarray_var_map: VarMap,
@ -308,6 +450,7 @@ impl<'a> BuiltinBuilder<'a> {
ndarray_float, ndarray_float,
ndarray_float_2d, ndarray_float_2d,
ndarray_num_ty,
float_or_ndarray_ty, float_or_ndarray_ty,
float_or_ndarray_var_map, float_or_ndarray_var_map,
@ -369,14 +512,6 @@ impl<'a> BuiltinBuilder<'a> {
| PrimDef::FunNpEye | PrimDef::FunNpEye
| PrimDef::FunNpIdentity => self.build_ndarray_other_factory_function(prim), | PrimDef::FunNpIdentity => self.build_ndarray_other_factory_function(prim),
PrimDef::FunNpSize | PrimDef::FunNpShape | PrimDef::FunNpStrides => {
self.build_ndarray_property_getter_function(prim)
}
PrimDef::FunNpBroadcastTo | PrimDef::FunNpTranspose | PrimDef::FunNpReshape => {
self.build_ndarray_view_function(prim)
}
PrimDef::FunStr => self.build_str_function(), PrimDef::FunStr => self.build_str_function(),
PrimDef::FunFloor | PrimDef::FunFloor64 | PrimDef::FunCeil | PrimDef::FunCeil64 => { PrimDef::FunFloor | PrimDef::FunFloor64 | PrimDef::FunCeil | PrimDef::FunCeil64 => {
@ -442,6 +577,10 @@ impl<'a> BuiltinBuilder<'a> {
| PrimDef::FunNpHypot | PrimDef::FunNpHypot
| PrimDef::FunNpNextAfter => self.build_np_2ary_function(prim), | PrimDef::FunNpNextAfter => self.build_np_2ary_function(prim),
PrimDef::FunNpTranspose | PrimDef::FunNpReshape => {
self.build_np_sp_ndarray_function(prim)
}
PrimDef::FunNpDot PrimDef::FunNpDot
| PrimDef::FunNpLinalgCholesky | PrimDef::FunNpLinalgCholesky
| PrimDef::FunNpLinalgQr | PrimDef::FunNpLinalgQr
@ -1247,176 +1386,6 @@ impl<'a> BuiltinBuilder<'a> {
} }
} }
fn build_ndarray_property_getter_function(&mut self, prim: PrimDef) -> TopLevelDef {
debug_assert_prim_is_allowed(
prim,
&[PrimDef::FunNpSize, PrimDef::FunNpShape, PrimDef::FunNpStrides],
);
let in_ndarray_ty = self.unifier.get_fresh_var_with_range(
&[self.primitives.ndarray],
Some("T".into()),
None,
);
match prim {
PrimDef::FunNpSize => create_fn_by_codegen(
self.unifier,
&into_var_map([in_ndarray_ty]),
prim.name(),
self.primitives.int32,
&[(in_ndarray_ty.ty, "a")],
Box::new(|ctx, obj, fun, args, generator| {
assert!(obj.is_none());
assert_eq!(args.len(), 1);
let ndarray_ty = fun.0.args[0].ty;
let ndarray =
args[0].1.clone().to_basic_value_enum(ctx, generator, ndarray_ty)?;
let ndarray = NDArrayType::from_unifier_type(generator, ctx, ndarray_ty)
.map_value(ndarray.into_pointer_value(), None);
let size = ctx
.builder
.build_int_truncate_or_bit_cast(
ndarray.size(generator, ctx),
ctx.ctx.i32_type(),
"",
)
.unwrap();
Ok(Some(size.into()))
}),
),
PrimDef::FunNpShape | PrimDef::FunNpStrides => {
// The function signatures of `np_shape` an `np_size` are the same.
// Mixed together for convenience.
// The return type is a tuple of variable length depending on the ndims of the input ndarray.
let ret_ty = self.unifier.get_dummy_var().ty; // Handled by special folding
create_fn_by_codegen(
self.unifier,
&into_var_map([in_ndarray_ty]),
prim.name(),
ret_ty,
&[(in_ndarray_ty.ty, "a")],
Box::new(move |ctx, obj, fun, args, generator| {
assert!(obj.is_none());
assert_eq!(args.len(), 1);
let ndarray_ty = fun.0.args[0].ty;
let ndarray =
args[0].1.clone().to_basic_value_enum(ctx, generator, ndarray_ty)?;
let ndarray = NDArrayType::from_unifier_type(generator, ctx, ndarray_ty)
.map_value(ndarray.into_pointer_value(), None);
let result_tuple = match prim {
PrimDef::FunNpShape => ndarray.make_shape_tuple(generator, ctx),
PrimDef::FunNpStrides => ndarray.make_strides_tuple(generator, ctx),
_ => unreachable!(),
};
Ok(Some(result_tuple.as_base_value().into()))
}),
)
}
_ => unreachable!(),
}
}
/// Build np/sp functions that take as input `NDArray` only
fn build_ndarray_view_function(&mut self, prim: PrimDef) -> TopLevelDef {
debug_assert_prim_is_allowed(
prim,
&[PrimDef::FunNpBroadcastTo, PrimDef::FunNpTranspose, PrimDef::FunNpReshape],
);
let in_ndarray_ty = self.unifier.get_fresh_var_with_range(
&[self.primitives.ndarray],
Some("T".into()),
None,
);
match prim {
PrimDef::FunNpTranspose => create_fn_by_codegen(
self.unifier,
&into_var_map([in_ndarray_ty]),
prim.name(),
in_ndarray_ty.ty,
&[(in_ndarray_ty.ty, "x")],
Box::new(move |ctx, _, fun, args, generator| {
let arg_ty = fun.0.args[0].ty;
let arg_val = args[0].1.clone().to_basic_value_enum(ctx, generator, arg_ty)?;
let ndarray = NDArrayType::from_unifier_type(generator, ctx, arg_ty)
.map_value(arg_val.into_pointer_value(), None);
let ndarray = ndarray.transpose(generator, ctx, None); // TODO: Add axes argument
Ok(Some(ndarray.as_base_value().into()))
}),
),
// NOTE: on `ndarray_factory_fn_shape_arg_tvar` and
// the `param_ty` for `create_fn_by_codegen`.
//
// Similar to `build_ndarray_from_shape_factory_function` we delegate the responsibility of typechecking
// to [`typecheck::type_inferencer::Inferencer::fold_numpy_function_call_shape_argument`],
// and use a dummy [`TypeVar`] `ndarray_factory_fn_shape_arg_tvar` as a placeholder for `param_ty`.
PrimDef::FunNpBroadcastTo | PrimDef::FunNpReshape => {
// These two functions have the same function signature.
// Mixed together for convenience.
let ret_ty = self.unifier.get_dummy_var().ty; // Handled by special holding
create_fn_by_codegen(
self.unifier,
&VarMap::new(),
prim.name(),
ret_ty,
&[
(in_ndarray_ty.ty, "x"),
(self.ndarray_factory_fn_shape_arg_tvar.ty, "shape"), // Handled by special folding
],
Box::new(move |ctx, _, fun, args, generator| {
let ndarray_ty = fun.0.args[0].ty;
let ndarray_val =
args[0].1.clone().to_basic_value_enum(ctx, generator, ndarray_ty)?;
let shape_ty = fun.0.args[1].ty;
let shape_val =
args[1].1.clone().to_basic_value_enum(ctx, generator, shape_ty)?;
let ndarray = NDArrayType::from_unifier_type(generator, ctx, ndarray_ty)
.map_value(ndarray_val.into_pointer_value(), None);
let shape = parse_numpy_int_sequence(generator, ctx, (shape_ty, shape_val));
// The ndims after reshaping is gotten from the return type of the call.
let (_, ndims) = unpack_ndarray_var_tys(&mut ctx.unifier, fun.0.ret);
let ndims = extract_ndims(&ctx.unifier, ndims);
let new_ndarray = match prim {
PrimDef::FunNpBroadcastTo => {
ndarray.broadcast_to(generator, ctx, ndims, &shape)
}
PrimDef::FunNpReshape => {
ndarray.reshape_or_copy(generator, ctx, ndims, &shape)
}
_ => unreachable!(),
};
Ok(Some(new_ndarray.as_base_value().as_basic_value_enum()))
}),
)
}
_ => unreachable!(),
}
}
/// Build the `str()` function. /// Build the `str()` function.
fn build_str_function(&mut self) -> TopLevelDef { fn build_str_function(&mut self) -> TopLevelDef {
let prim = PrimDef::FunStr; let prim = PrimDef::FunStr;
@ -1904,6 +1873,57 @@ impl<'a> BuiltinBuilder<'a> {
} }
} }
/// Build np/sp functions that take as input `NDArray` only
fn build_np_sp_ndarray_function(&mut self, prim: PrimDef) -> TopLevelDef {
debug_assert_prim_is_allowed(prim, &[PrimDef::FunNpTranspose, PrimDef::FunNpReshape]);
match prim {
PrimDef::FunNpTranspose => {
let ndarray_ty = self.unifier.get_fresh_var_with_range(
&[self.ndarray_num_ty],
Some("T".into()),
None,
);
create_fn_by_codegen(
self.unifier,
&into_var_map([ndarray_ty]),
prim.name(),
ndarray_ty.ty,
&[(ndarray_ty.ty, "x")],
Box::new(move |ctx, _, fun, args, generator| {
let arg_ty = fun.0.args[0].ty;
let arg_val =
args[0].1.clone().to_basic_value_enum(ctx, generator, arg_ty)?;
Ok(Some(ndarray_transpose(generator, ctx, (arg_ty, arg_val))?))
}),
)
}
// NOTE: on `ndarray_factory_fn_shape_arg_tvar` and
// the `param_ty` for `create_fn_by_codegen`.
//
// Similar to `build_ndarray_from_shape_factory_function` we delegate the responsibility of typechecking
// to [`typecheck::type_inferencer::Inferencer::fold_numpy_function_call_shape_argument`],
// and use a dummy [`TypeVar`] `ndarray_factory_fn_shape_arg_tvar` as a placeholder for `param_ty`.
PrimDef::FunNpReshape => create_fn_by_codegen(
self.unifier,
&VarMap::new(),
prim.name(),
self.ndarray_num_ty,
&[(self.ndarray_num_ty, "x"), (self.ndarray_factory_fn_shape_arg_tvar.ty, "shape")],
Box::new(move |ctx, _, fun, args, generator| {
let x1_ty = fun.0.args[0].ty;
let x1_val = args[0].1.clone().to_basic_value_enum(ctx, generator, x1_ty)?;
let x2_ty = fun.0.args[1].ty;
let x2_val = args[1].1.clone().to_basic_value_enum(ctx, generator, x2_ty)?;
Ok(Some(ndarray_reshape(generator, ctx, (x1_ty, x1_val), (x2_ty, x2_val))?))
}),
),
_ => unreachable!(),
}
}
/// Build `np_linalg` and `sp_linalg` functions /// Build `np_linalg` and `sp_linalg` functions
/// ///
/// The input to these functions must be floating point `NDArray` /// The input to these functions must be floating point `NDArray`
@ -1935,12 +1955,10 @@ impl<'a> BuiltinBuilder<'a> {
Box::new(move |ctx, _, fun, args, generator| { Box::new(move |ctx, _, fun, args, generator| {
let x1_ty = fun.0.args[0].ty; let x1_ty = fun.0.args[0].ty;
let x1_val = args[0].1.clone().to_basic_value_enum(ctx, generator, x1_ty)?; let x1_val = args[0].1.clone().to_basic_value_enum(ctx, generator, x1_ty)?;
let x2_ty = fun.0.args[1].ty; let x2_ty = fun.0.args[1].ty;
let x2_val = args[1].1.clone().to_basic_value_enum(ctx, generator, x2_ty)?; let x2_val = args[1].1.clone().to_basic_value_enum(ctx, generator, x2_ty)?;
let result = ndarray_dot(generator, ctx, (x1_ty, x1_val), (x2_ty, x2_val))?; Ok(Some(ndarray_dot(generator, ctx, (x1_ty, x1_val), (x2_ty, x2_val))?))
Ok(Some(result))
}), }),
), ),

View File

@ -1052,7 +1052,7 @@ impl TopLevelComposer {
} }
let mut result = Vec::new(); let mut result = Vec::new();
let no_defaults = args.args.len() - args.defaults.len() - 1; let no_defaults = args.args.len() - args.defaults.len() - 1;
for (idx, x) in args.args.iter().skip(1).enumerate() { for (idx, x) in itertools::enumerate(args.args.iter().skip(1)) {
let type_ann = { let type_ann = {
let Some(annotation_expr) = x.node.annotation.as_ref() else {return Err(HashSet::from([format!("type annotation needed for `{}` (at {})", x.node.arg, x.location)]));}; let Some(annotation_expr) = x.node.annotation.as_ref() else {return Err(HashSet::from([format!("type annotation needed for `{}` (at {})", x.node.arg, x.location)]));};
parse_ast_to_type_annotation_kinds( parse_ast_to_type_annotation_kinds(

View File

@ -54,16 +54,6 @@ pub enum PrimDef {
FunNpEye, FunNpEye,
FunNpIdentity, FunNpIdentity,
// NumPy ndarray property getters
FunNpSize,
FunNpShape,
FunNpStrides,
// NumPy ndarray view functions
FunNpBroadcastTo,
FunNpTranspose,
FunNpReshape,
// Miscellaneous NumPy & SciPy functions // Miscellaneous NumPy & SciPy functions
FunNpRound, FunNpRound,
FunNpFloor, FunNpFloor,
@ -111,6 +101,8 @@ pub enum PrimDef {
FunNpLdExp, FunNpLdExp,
FunNpHypot, FunNpHypot,
FunNpNextAfter, FunNpNextAfter,
FunNpTranspose,
FunNpReshape,
// Linalg functions // Linalg functions
FunNpDot, FunNpDot,
@ -248,16 +240,6 @@ impl PrimDef {
PrimDef::FunNpEye => fun("np_eye", None), PrimDef::FunNpEye => fun("np_eye", None),
PrimDef::FunNpIdentity => fun("np_identity", None), PrimDef::FunNpIdentity => fun("np_identity", None),
// NumPy NDArray property getters,
PrimDef::FunNpSize => fun("np_size", None),
PrimDef::FunNpShape => fun("np_shape", None),
PrimDef::FunNpStrides => fun("np_strides", None),
// NumPy NDArray view functions
PrimDef::FunNpBroadcastTo => fun("np_broadcast_to", None),
PrimDef::FunNpTranspose => fun("np_transpose", None),
PrimDef::FunNpReshape => fun("np_reshape", None),
// Miscellaneous NumPy & SciPy functions // Miscellaneous NumPy & SciPy functions
PrimDef::FunNpRound => fun("np_round", None), PrimDef::FunNpRound => fun("np_round", None),
PrimDef::FunNpFloor => fun("np_floor", None), PrimDef::FunNpFloor => fun("np_floor", None),
@ -305,6 +287,8 @@ impl PrimDef {
PrimDef::FunNpLdExp => fun("np_ldexp", None), PrimDef::FunNpLdExp => fun("np_ldexp", None),
PrimDef::FunNpHypot => fun("np_hypot", None), PrimDef::FunNpHypot => fun("np_hypot", None),
PrimDef::FunNpNextAfter => fun("np_nextafter", None), PrimDef::FunNpNextAfter => fun("np_nextafter", None),
PrimDef::FunNpTranspose => fun("np_transpose", None),
PrimDef::FunNpReshape => fun("np_reshape", None),
// Linalg functions // Linalg functions
PrimDef::FunNpDot => fun("np_dot", None), PrimDef::FunNpDot => fun("np_dot", None),

View File

@ -8,5 +8,5 @@ expression: res_vec
"Function {\nname: \"B.foo\",\nsig: \"fn[[b:T], none]\",\nvar_id: []\n}\n", "Function {\nname: \"B.foo\",\nsig: \"fn[[b:T], none]\",\nvar_id: []\n}\n",
"Class {\nname: \"Generic_A\",\nancestors: [\"Generic_A[V]\", \"B\"],\nfields: [\"aa\", \"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"foo\", \"fn[[b:T], none]\"), (\"fun\", \"fn[[a:int32], V]\")],\ntype_vars: [\"V\"]\n}\n", "Class {\nname: \"Generic_A\",\nancestors: [\"Generic_A[V]\", \"B\"],\nfields: [\"aa\", \"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"foo\", \"fn[[b:T], none]\"), (\"fun\", \"fn[[a:int32], V]\")],\ntype_vars: [\"V\"]\n}\n",
"Function {\nname: \"Generic_A.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n", "Function {\nname: \"Generic_A.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n",
"Function {\nname: \"Generic_A.fun\",\nsig: \"fn[[a:int32], V]\",\nvar_id: [TypeVarId(261)]\n}\n", "Function {\nname: \"Generic_A.fun\",\nsig: \"fn[[a:int32], V]\",\nvar_id: [TypeVarId(246)]\n}\n",
] ]

View File

@ -7,7 +7,7 @@ expression: res_vec
"Function {\nname: \"A.__init__\",\nsig: \"fn[[t:T], none]\",\nvar_id: []\n}\n", "Function {\nname: \"A.__init__\",\nsig: \"fn[[t:T], none]\",\nvar_id: []\n}\n",
"Function {\nname: \"A.fun\",\nsig: \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\",\nvar_id: []\n}\n", "Function {\nname: \"A.fun\",\nsig: \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\",\nvar_id: []\n}\n",
"Function {\nname: \"A.foo\",\nsig: \"fn[[c:C], none]\",\nvar_id: []\n}\n", "Function {\nname: \"A.foo\",\nsig: \"fn[[c:C], none]\",\nvar_id: []\n}\n",
"Class {\nname: \"B\",\nancestors: [\"B[typevar245]\", \"A[float]\"],\nfields: [\"a\", \"b\", \"c\", \"d\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\"), (\"foo\", \"fn[[c:C], none]\")],\ntype_vars: [\"typevar245\"]\n}\n", "Class {\nname: \"B\",\nancestors: [\"B[typevar230]\", \"A[float]\"],\nfields: [\"a\", \"b\", \"c\", \"d\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\"), (\"foo\", \"fn[[c:C], none]\")],\ntype_vars: [\"typevar230\"]\n}\n",
"Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n", "Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n",
"Function {\nname: \"B.fun\",\nsig: \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\",\nvar_id: []\n}\n", "Function {\nname: \"B.fun\",\nsig: \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\",\nvar_id: []\n}\n",
"Class {\nname: \"C\",\nancestors: [\"C\", \"B[bool]\", \"A[float]\"],\nfields: [\"a\", \"b\", \"c\", \"d\", \"e\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\"), (\"foo\", \"fn[[c:C], none]\")],\ntype_vars: []\n}\n", "Class {\nname: \"C\",\nancestors: [\"C\", \"B[bool]\", \"A[float]\"],\nfields: [\"a\", \"b\", \"c\", \"d\", \"e\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\"), (\"foo\", \"fn[[c:C], none]\")],\ntype_vars: []\n}\n",

View File

@ -5,8 +5,8 @@ expression: res_vec
[ [
"Function {\nname: \"foo\",\nsig: \"fn[[a:list[int32], b:tuple[T, float]], A[B, bool]]\",\nvar_id: []\n}\n", "Function {\nname: \"foo\",\nsig: \"fn[[a:list[int32], b:tuple[T, float]], A[B, bool]]\",\nvar_id: []\n}\n",
"Class {\nname: \"A\",\nancestors: [\"A[T, V]\"],\nfields: [\"a\", \"b\"],\nmethods: [(\"__init__\", \"fn[[v:V], none]\"), (\"fun\", \"fn[[a:T], V]\")],\ntype_vars: [\"T\", \"V\"]\n}\n", "Class {\nname: \"A\",\nancestors: [\"A[T, V]\"],\nfields: [\"a\", \"b\"],\nmethods: [(\"__init__\", \"fn[[v:V], none]\"), (\"fun\", \"fn[[a:T], V]\")],\ntype_vars: [\"T\", \"V\"]\n}\n",
"Function {\nname: \"A.__init__\",\nsig: \"fn[[v:V], none]\",\nvar_id: [TypeVarId(258)]\n}\n", "Function {\nname: \"A.__init__\",\nsig: \"fn[[v:V], none]\",\nvar_id: [TypeVarId(243)]\n}\n",
"Function {\nname: \"A.fun\",\nsig: \"fn[[a:T], V]\",\nvar_id: [TypeVarId(263)]\n}\n", "Function {\nname: \"A.fun\",\nsig: \"fn[[a:T], V]\",\nvar_id: [TypeVarId(248)]\n}\n",
"Function {\nname: \"gfun\",\nsig: \"fn[[a:A[list[float], int32]], none]\",\nvar_id: []\n}\n", "Function {\nname: \"gfun\",\nsig: \"fn[[a:A[list[float], int32]], none]\",\nvar_id: []\n}\n",
"Class {\nname: \"B\",\nancestors: [\"B\"],\nfields: [],\nmethods: [(\"__init__\", \"fn[[], none]\")],\ntype_vars: []\n}\n", "Class {\nname: \"B\",\nancestors: [\"B\"],\nfields: [],\nmethods: [(\"__init__\", \"fn[[], none]\")],\ntype_vars: []\n}\n",
"Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n", "Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n",

View File

@ -3,7 +3,7 @@ source: nac3core/src/toplevel/test.rs
expression: res_vec expression: res_vec
--- ---
[ [
"Class {\nname: \"A\",\nancestors: [\"A[typevar244, typevar245]\"],\nfields: [\"a\", \"b\"],\nmethods: [(\"__init__\", \"fn[[a:A[float, bool], b:B], none]\"), (\"fun\", \"fn[[a:A[float, bool]], A[bool, int32]]\")],\ntype_vars: [\"typevar244\", \"typevar245\"]\n}\n", "Class {\nname: \"A\",\nancestors: [\"A[typevar229, typevar230]\"],\nfields: [\"a\", \"b\"],\nmethods: [(\"__init__\", \"fn[[a:A[float, bool], b:B], none]\"), (\"fun\", \"fn[[a:A[float, bool]], A[bool, int32]]\")],\ntype_vars: [\"typevar229\", \"typevar230\"]\n}\n",
"Function {\nname: \"A.__init__\",\nsig: \"fn[[a:A[float, bool], b:B], none]\",\nvar_id: []\n}\n", "Function {\nname: \"A.__init__\",\nsig: \"fn[[a:A[float, bool], b:B], none]\",\nvar_id: []\n}\n",
"Function {\nname: \"A.fun\",\nsig: \"fn[[a:A[float, bool]], A[bool, int32]]\",\nvar_id: []\n}\n", "Function {\nname: \"A.fun\",\nsig: \"fn[[a:A[float, bool]], A[bool, int32]]\",\nvar_id: []\n}\n",
"Class {\nname: \"B\",\nancestors: [\"B\", \"A[int64, bool]\"],\nfields: [\"a\", \"b\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[a:A[float, bool]], A[bool, int32]]\"), (\"foo\", \"fn[[b:B], B]\"), (\"bar\", \"fn[[a:A[list[B], int32]], tuple[A[virtual[A[B, int32]], bool], B]]\")],\ntype_vars: []\n}\n", "Class {\nname: \"B\",\nancestors: [\"B\", \"A[int64, bool]\"],\nfields: [\"a\", \"b\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[a:A[float, bool]], A[bool, int32]]\"), (\"foo\", \"fn[[b:B], B]\"), (\"bar\", \"fn[[a:A[list[B], int32]], tuple[A[virtual[A[B, int32]], bool], B]]\")],\ntype_vars: []\n}\n",

View File

@ -6,12 +6,12 @@ expression: res_vec
"Class {\nname: \"A\",\nancestors: [\"A\"],\nfields: [\"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[b:B], none]\"), (\"foo\", \"fn[[a:T, b:V], none]\")],\ntype_vars: []\n}\n", "Class {\nname: \"A\",\nancestors: [\"A\"],\nfields: [\"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[b:B], none]\"), (\"foo\", \"fn[[a:T, b:V], none]\")],\ntype_vars: []\n}\n",
"Function {\nname: \"A.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n", "Function {\nname: \"A.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n",
"Function {\nname: \"A.fun\",\nsig: \"fn[[b:B], none]\",\nvar_id: []\n}\n", "Function {\nname: \"A.fun\",\nsig: \"fn[[b:B], none]\",\nvar_id: []\n}\n",
"Function {\nname: \"A.foo\",\nsig: \"fn[[a:T, b:V], none]\",\nvar_id: [TypeVarId(264)]\n}\n", "Function {\nname: \"A.foo\",\nsig: \"fn[[a:T, b:V], none]\",\nvar_id: [TypeVarId(249)]\n}\n",
"Class {\nname: \"C\",\nancestors: [\"C\", \"A\"],\nfields: [\"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[b:B], none]\"), (\"foo\", \"fn[[a:T, b:V], none]\")],\ntype_vars: []\n}\n", "Class {\nname: \"C\",\nancestors: [\"C\", \"A\"],\nfields: [\"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[b:B], none]\"), (\"foo\", \"fn[[a:T, b:V], none]\")],\ntype_vars: []\n}\n",
"Function {\nname: \"C.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n", "Function {\nname: \"C.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n",
"Function {\nname: \"C.fun\",\nsig: \"fn[[b:B], none]\",\nvar_id: []\n}\n", "Function {\nname: \"C.fun\",\nsig: \"fn[[b:B], none]\",\nvar_id: []\n}\n",
"Class {\nname: \"B\",\nancestors: [\"B\", \"C\", \"A\"],\nfields: [\"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[b:B], none]\"), (\"foo\", \"fn[[a:T, b:V], none]\")],\ntype_vars: []\n}\n", "Class {\nname: \"B\",\nancestors: [\"B\", \"C\", \"A\"],\nfields: [\"a\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[b:B], none]\"), (\"foo\", \"fn[[a:T, b:V], none]\")],\ntype_vars: []\n}\n",
"Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n", "Function {\nname: \"B.__init__\",\nsig: \"fn[[], none]\",\nvar_id: []\n}\n",
"Function {\nname: \"foo\",\nsig: \"fn[[a:A], none]\",\nvar_id: []\n}\n", "Function {\nname: \"foo\",\nsig: \"fn[[a:A], none]\",\nvar_id: []\n}\n",
"Function {\nname: \"ff\",\nsig: \"fn[[a:T], V]\",\nvar_id: [TypeVarId(272)]\n}\n", "Function {\nname: \"ff\",\nsig: \"fn[[a:T], V]\",\nvar_id: [TypeVarId(257)]\n}\n",
] ]

View File

@ -15,13 +15,14 @@ use crate::{
symbol_resolver::{SymbolResolver, ValueEnum}, symbol_resolver::{SymbolResolver, ValueEnum},
typecheck::{ typecheck::{
type_inferencer::PrimitiveStore, type_inferencer::PrimitiveStore,
typedef::{Type, Unifier}, typedef::{into_var_map, Type, Unifier},
}, },
}; };
struct ResolverInternal { struct ResolverInternal {
id_to_type: Mutex<HashMap<StrRef, Type>>, id_to_type: Mutex<HashMap<StrRef, Type>>,
id_to_def: Mutex<HashMap<StrRef, DefinitionId>>, id_to_def: Mutex<HashMap<StrRef, DefinitionId>>,
class_names: Mutex<HashMap<StrRef, Type>>,
} }
impl ResolverInternal { impl ResolverInternal {
@ -178,8 +179,11 @@ fn test_simple_function_analyze(source: &[&str], tys: &[&str], names: &[&str]) {
let mut composer = let mut composer =
TopLevelComposer::new(Vec::new(), Vec::new(), ComposerConfig::default(), 64).0; TopLevelComposer::new(Vec::new(), Vec::new(), ComposerConfig::default(), 64).0;
let internal_resolver = let internal_resolver = Arc::new(ResolverInternal {
Arc::new(ResolverInternal { id_to_def: Mutex::default(), id_to_type: Mutex::default() }); id_to_def: Mutex::default(),
id_to_type: Mutex::default(),
class_names: Mutex::default(),
});
let resolver = let resolver =
Arc::new(Resolver(internal_resolver.clone())) as Arc<dyn SymbolResolver + Send + Sync>; Arc::new(Resolver(internal_resolver.clone())) as Arc<dyn SymbolResolver + Send + Sync>;
@ -780,6 +784,13 @@ fn make_internal_resolver_with_tvar(
unifier: &mut Unifier, unifier: &mut Unifier,
print: bool, print: bool,
) -> Arc<ResolverInternal> { ) -> Arc<ResolverInternal> {
let list_elem_tvar = unifier.get_fresh_var(Some("list_elem".into()), None);
let list = unifier.add_ty(TypeEnum::TObj {
obj_id: PrimDef::List.id(),
fields: HashMap::new(),
params: into_var_map([list_elem_tvar]),
});
let res: Arc<ResolverInternal> = ResolverInternal { let res: Arc<ResolverInternal> = ResolverInternal {
id_to_def: Mutex::new(HashMap::from([("list".into(), PrimDef::List.id())])), id_to_def: Mutex::new(HashMap::from([("list".into(), PrimDef::List.id())])),
id_to_type: tvars id_to_type: tvars
@ -795,6 +806,7 @@ fn make_internal_resolver_with_tvar(
}) })
.collect::<HashMap<_, _>>() .collect::<HashMap<_, _>>()
.into(), .into(),
class_names: Mutex::new(HashMap::from([("list".into(), list)])),
} }
.into(); .into();
if print { if print {
@ -807,7 +819,7 @@ struct TypeToStringFolder<'a> {
unifier: &'a mut Unifier, unifier: &'a mut Unifier,
} }
impl Fold<Option<Type>> for TypeToStringFolder<'_> { impl<'a> Fold<Option<Type>> for TypeToStringFolder<'a> {
type TargetU = String; type TargetU = String;
type Error = String; type Error = String;
fn map_user(&mut self, user: Option<Type>) -> Result<Self::TargetU, Self::Error> { fn map_user(&mut self, user: Option<Type>) -> Result<Self::TargetU, Self::Error> {

View File

@ -15,7 +15,7 @@ use super::{
}; };
use crate::toplevel::helper::PrimDef; use crate::toplevel::helper::PrimDef;
impl Inferencer<'_> { impl<'a> Inferencer<'a> {
fn should_have_value(&mut self, expr: &Expr<Option<Type>>) -> Result<(), HashSet<String>> { fn should_have_value(&mut self, expr: &Expr<Option<Type>>) -> Result<(), HashSet<String>> {
if matches!(expr.custom, Some(ty) if self.unifier.unioned(ty, self.primitives.none)) { if matches!(expr.custom, Some(ty) if self.unifier.unioned(ty, self.primitives.none)) {
Err(HashSet::from([format!("Error at {}: cannot have value none", expr.location)])) Err(HashSet::from([format!("Error at {}: cannot have value none", expr.location)]))
@ -94,7 +94,7 @@ impl Inferencer<'_> {
// there are some cases where the custom field is None // there are some cases where the custom field is None
if let Some(ty) = &expr.custom { if let Some(ty) = &expr.custom {
if !matches!(&expr.node, ExprKind::Constant { value: Constant::Ellipsis, .. }) if !matches!(&expr.node, ExprKind::Constant { value: Constant::Ellipsis, .. })
&& ty.obj_id(self.unifier).is_none_or(|id| id != PrimDef::List.id()) && !ty.obj_id(self.unifier).is_some_and(|id| id == PrimDef::List.id())
&& !self.unifier.is_concrete(*ty, &self.function_data.bound_variables) && !self.unifier.is_concrete(*ty, &self.function_data.bound_variables)
{ {
return Err(HashSet::from([format!( return Err(HashSet::from([format!(

View File

@ -7,12 +7,12 @@ use nac3parser::ast::{Cmpop, Operator, StrRef, Unaryop};
use super::{ use super::{
type_inferencer::*, type_inferencer::*,
typedef::{into_var_map, FunSignature, FuncArg, Type, TypeEnum, Unifier, VarMap}, typedef::{FunSignature, FuncArg, Type, TypeEnum, Unifier, VarMap},
}; };
use crate::{ use crate::{
symbol_resolver::SymbolValue, symbol_resolver::SymbolValue,
toplevel::{ toplevel::{
helper::{extract_ndims, PrimDef}, helper::PrimDef,
numpy::{make_ndarray_ty, unpack_ndarray_var_tys}, numpy::{make_ndarray_ty, unpack_ndarray_var_tys},
}, },
}; };
@ -175,8 +175,19 @@ pub fn impl_binop(
ops: &[Operator], ops: &[Operator],
) { ) {
with_fields(unifier, ty, |unifier, fields| { with_fields(unifier, ty, |unifier, fields| {
let other_tvar = unifier.get_fresh_var_with_range(other_ty, Some("N".into()), None); let (other_ty, other_var_id) = if other_ty.len() == 1 {
let function_vars = into_var_map([other_tvar]); (other_ty[0], None)
} else {
let tvar = unifier.get_fresh_var_with_range(other_ty, Some("N".into()), None);
(tvar.ty, Some(tvar.id))
};
let function_vars = if let Some(var_id) = other_var_id {
vec![(var_id, other_ty)].into_iter().collect::<VarMap>()
} else {
VarMap::new()
};
let ret_ty = ret_ty.unwrap_or_else(|| unifier.get_fresh_var(None, None).ty); let ret_ty = ret_ty.unwrap_or_else(|| unifier.get_fresh_var(None, None).ty);
for (base_op, variant) in iproduct!(ops, [BinopVariant::Normal, BinopVariant::AugAssign]) { for (base_op, variant) in iproduct!(ops, [BinopVariant::Normal, BinopVariant::AugAssign]) {
@ -187,7 +198,7 @@ pub fn impl_binop(
ret: ret_ty, ret: ret_ty,
vars: function_vars.clone(), vars: function_vars.clone(),
args: vec![FuncArg { args: vec![FuncArg {
ty: other_tvar.ty, ty: other_ty,
default_value: None, default_value: None,
name: "other".into(), name: "other".into(),
is_vararg: false, is_vararg: false,
@ -530,43 +541,36 @@ pub fn typeof_binop(
} }
} }
let (lhs_dtype, lhs_ndims) = unpack_ndarray_var_tys(unifier, lhs); let (_, lhs_ndims) = unpack_ndarray_var_tys(unifier, lhs);
let lhs_ndims = extract_ndims(unifier, lhs_ndims); let lhs_ndims = match &*unifier.get_ty_immutable(lhs_ndims) {
TypeEnum::TLiteral { values, .. } => {
let (rhs_dtype, rhs_ndims) = unpack_ndarray_var_tys(unifier, rhs); assert_eq!(values.len(), 1);
let rhs_ndims = extract_ndims(unifier, rhs_ndims); u64::try_from(values[0].clone()).unwrap()
if !(unifier.unioned(lhs_dtype, primitives.float)
&& unifier.unioned(rhs_dtype, primitives.float))
{
return Err(format!(
"ndarray.__matmul__ only supports float64 operations, but LHS has type {} and RHS has type {}",
unifier.stringify(lhs),
unifier.stringify(rhs)
));
}
// Deduce the ndims of the resulting ndarray.
// If this is 0 (an unsized ndarray), matmul returns a scalar just like NumPy.
let result_ndims = match (lhs_ndims, rhs_ndims) {
(0, _) | (_, 0) => {
return Err(
"ndarray.__matmul__ does not allow unsized ndarray input".to_string()
)
} }
(1, 1) => 0, _ => unreachable!(),
(1, _) => rhs_ndims - 1, };
(_, 1) => lhs_ndims - 1, let (_, rhs_ndims) = unpack_ndarray_var_tys(unifier, rhs);
(m, n) => max(m, n), let rhs_ndims = match &*unifier.get_ty_immutable(rhs_ndims) {
TypeEnum::TLiteral { values, .. } => {
assert_eq!(values.len(), 1);
u64::try_from(values[0].clone()).unwrap()
}
_ => unreachable!(),
}; };
if result_ndims == 0 { match (lhs_ndims, rhs_ndims) {
// If the result is unsized, NumPy returns a scalar. (2, 2) => typeof_ndarray_broadcast(unifier, primitives, lhs, rhs)?,
primitives.float (lhs, rhs) if lhs == 0 || rhs == 0 => {
} else { return Err(format!(
let result_ndims_ty = "Input operand {} does not have enough dimensions (has {lhs}, requires {rhs})",
unifier.get_fresh_literal(vec![SymbolValue::U64(result_ndims)], None); u8::from(rhs == 0)
make_ndarray_ty(unifier, primitives, Some(primitives.float), Some(result_ndims_ty)) ))
}
(lhs, rhs) => {
return Err(format!(
"ndarray.__matmul__ on {lhs}D and {rhs}D operands not supported"
))
}
} }
} }
@ -769,7 +773,7 @@ pub fn set_primitives_magic_methods(store: &PrimitiveStore, unifier: &mut Unifie
impl_div(unifier, store, ndarray_t, &[ndarray_t, ndarray_dtype_t], None); impl_div(unifier, store, ndarray_t, &[ndarray_t, ndarray_dtype_t], None);
impl_floordiv(unifier, store, ndarray_t, &[ndarray_unsized_t, ndarray_unsized_dtype_t], None); impl_floordiv(unifier, store, ndarray_t, &[ndarray_unsized_t, ndarray_unsized_dtype_t], None);
impl_mod(unifier, store, ndarray_t, &[ndarray_unsized_t, ndarray_unsized_dtype_t], None); impl_mod(unifier, store, ndarray_t, &[ndarray_unsized_t, ndarray_unsized_dtype_t], None);
impl_matmul(unifier, store, ndarray_t, &[ndarray_unsized_t], None); impl_matmul(unifier, store, ndarray_t, &[ndarray_t], Some(ndarray_t));
impl_sign(unifier, store, ndarray_t, Some(ndarray_t)); impl_sign(unifier, store, ndarray_t, Some(ndarray_t));
impl_invert(unifier, store, ndarray_t, Some(ndarray_t)); impl_invert(unifier, store, ndarray_t, Some(ndarray_t));
impl_eq(unifier, store, ndarray_t, &[ndarray_unsized_t, ndarray_unsized_dtype_t], None); impl_eq(unifier, store, ndarray_t, &[ndarray_unsized_t, ndarray_unsized_dtype_t], None);

View File

@ -94,7 +94,7 @@ fn loc_to_str(loc: Option<Location>) -> String {
} }
} }
impl Display for DisplayTypeError<'_> { impl<'a> Display for DisplayTypeError<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use TypeErrorKind::*; use TypeErrorKind::*;
let mut notes = Some(HashMap::new()); let mut notes = Some(HashMap::new());

View File

@ -3,7 +3,7 @@ use std::{
cmp::max, cmp::max,
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
convert::{From, TryInto}, convert::{From, TryInto},
iter::{once, repeat_n}, iter::once,
sync::Arc, sync::Arc,
}; };
@ -187,7 +187,7 @@ fn fix_assignment_target_context(node: &mut ast::Located<ExprKind>) {
} }
} }
impl Fold<()> for Inferencer<'_> { impl<'a> Fold<()> for Inferencer<'a> {
type TargetU = Option<Type>; type TargetU = Option<Type>;
type Error = InferenceError; type Error = InferenceError;
@ -657,7 +657,7 @@ impl Fold<()> for Inferencer<'_> {
type InferenceResult = Result<Type, InferenceError>; type InferenceResult = Result<Type, InferenceError>;
impl Inferencer<'_> { impl<'a> Inferencer<'a> {
/// Constrain a <: b /// Constrain a <: b
/// Currently implemented as unification /// Currently implemented as unification
fn constrain(&mut self, a: Type, b: Type, location: &Location) -> Result<(), InferenceError> { fn constrain(&mut self, a: Type, b: Type, location: &Location) -> Result<(), InferenceError> {
@ -1234,45 +1234,6 @@ impl Inferencer<'_> {
})); }));
} }
if ["np_shape".into(), "np_strides".into()].contains(id) && args.len() == 1 {
let ndarray = self.fold_expr(args.remove(0))?;
let ndims = arraylike_get_ndims(self.unifier, ndarray.custom.unwrap());
// Make a tuple of size `ndims` full of int32 (TODO: Make it usize)
let ret_ty = TypeEnum::TTuple {
ty: repeat_n(self.primitives.int32, ndims as usize).collect_vec(),
is_vararg_ctx: false,
};
let ret_ty = self.unifier.add_ty(ret_ty);
let func_ty = TypeEnum::TFunc(FunSignature {
args: vec![FuncArg {
name: "a".into(),
default_value: None,
ty: ndarray.custom.unwrap(),
is_vararg: false,
}],
ret: ret_ty,
vars: VarMap::new(),
});
let func_ty = self.unifier.add_ty(func_ty);
return Ok(Some(Located {
location,
custom: Some(ret_ty),
node: ExprKind::Call {
func: Box::new(Located {
custom: Some(func_ty),
location: func.location,
node: ExprKind::Name { id: *id, ctx: *ctx },
}),
args: vec![ndarray],
keywords: vec![],
},
}));
}
if id == &"np_dot".into() { if id == &"np_dot".into() {
let arg0 = self.fold_expr(args.remove(0))?; let arg0 = self.fold_expr(args.remove(0))?;
let arg1 = self.fold_expr(args.remove(0))?; let arg1 = self.fold_expr(args.remove(0))?;
@ -1594,7 +1555,7 @@ impl Inferencer<'_> {
})); }));
} }
// 2-argument ndarray n-dimensional factory functions // 2-argument ndarray n-dimensional factory functions
if ["np_reshape".into(), "np_broadcast_to".into()].contains(id) && args.len() == 2 { if id == &"np_reshape".into() && args.len() == 2 {
let arg0 = self.fold_expr(args.remove(0))?; let arg0 = self.fold_expr(args.remove(0))?;
let shape_expr = args.remove(0); let shape_expr = args.remove(0);

View File

@ -18,6 +18,7 @@ use crate::{
struct Resolver { struct Resolver {
id_to_type: HashMap<StrRef, Type>, id_to_type: HashMap<StrRef, Type>,
id_to_def: HashMap<StrRef, DefinitionId>, id_to_def: HashMap<StrRef, DefinitionId>,
class_names: HashMap<StrRef, Type>,
} }
impl SymbolResolver for Resolver { impl SymbolResolver for Resolver {
@ -197,6 +198,7 @@ impl TestEnvironment {
let resolver = Arc::new(Resolver { let resolver = Arc::new(Resolver {
id_to_type: identifier_mapping.clone(), id_to_type: identifier_mapping.clone(),
id_to_def: HashMap::default(), id_to_def: HashMap::default(),
class_names: HashMap::default(),
}) as Arc<dyn SymbolResolver + Send + Sync>; }) as Arc<dyn SymbolResolver + Send + Sync>;
TestEnvironment { TestEnvironment {
@ -452,6 +454,7 @@ impl TestEnvironment {
vars: IndexMap::default(), vars: IndexMap::default(),
})), })),
); );
let class_names: HashMap<_, _> = [("Bar".into(), bar), ("Bar2".into(), bar2)].into();
let id_to_name = [ let id_to_name = [
"int32".into(), "int32".into(),
@ -489,6 +492,7 @@ impl TestEnvironment {
("Bar2".into(), DefinitionId(defs + 3)), ("Bar2".into(), DefinitionId(defs + 3)),
] ]
.into(), .into(),
class_names,
}) as Arc<dyn SymbolResolver + Send + Sync>; }) as Arc<dyn SymbolResolver + Send + Sync>;
TestEnvironment { TestEnvironment {

View File

@ -3,13 +3,13 @@ use std::{
cell::RefCell, cell::RefCell,
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
fmt::{self, Display}, fmt::{self, Display},
iter::{repeat, repeat_n, zip}, iter::{repeat, zip},
rc::Rc, rc::Rc,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use indexmap::IndexMap; use indexmap::IndexMap;
use itertools::Itertools; use itertools::{repeat_n, Itertools};
use nac3parser::ast::{Cmpop, Location, StrRef, Unaryop}; use nac3parser::ast::{Cmpop, Location, StrRef, Unaryop};

View File

@ -30,7 +30,7 @@ pub struct DwarfReader<'a> {
pub virt_addr: u32, pub virt_addr: u32,
} }
impl DwarfReader<'_> { impl<'a> DwarfReader<'a> {
pub fn new(slice: &[u8], virt_addr: u32) -> DwarfReader { pub fn new(slice: &[u8], virt_addr: u32) -> DwarfReader {
DwarfReader { slice, virt_addr } DwarfReader { slice, virt_addr }
} }
@ -113,7 +113,7 @@ pub struct DwarfWriter<'a> {
pub offset: usize, pub offset: usize,
} }
impl DwarfWriter<'_> { impl<'a> DwarfWriter<'a> {
pub fn new(slice: &mut [u8]) -> DwarfWriter { pub fn new(slice: &mut [u8]) -> DwarfWriter {
DwarfWriter { slice, offset: 0 } DwarfWriter { slice, offset: 0 }
} }
@ -375,7 +375,7 @@ pub struct FDE_Records<'a> {
available: usize, available: usize,
} }
impl Iterator for FDE_Records<'_> { impl<'a> Iterator for FDE_Records<'a> {
type Item = (u32, u32); type Item = (u32, u32);
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
@ -423,7 +423,7 @@ pub struct EH_Frame_Hdr<'a> {
fdes: Vec<(u32, u32)>, fdes: Vec<(u32, u32)>,
} }
impl EH_Frame_Hdr<'_> { impl<'a> EH_Frame_Hdr<'a> {
/// Create a [EH_Frame_Hdr] object, and write out the fixed fields of `.eh_frame_hdr` to memory. /// Create a [EH_Frame_Hdr] object, and write out the fixed fields of `.eh_frame_hdr` to memory.
/// ///
/// Load address is not known at this point. /// Load address is not known at this point.

View File

@ -159,7 +159,7 @@ struct SymbolTableReader<'a> {
strtab: &'a [u8], strtab: &'a [u8],
} }
impl SymbolTableReader<'_> { impl<'a> SymbolTableReader<'a> {
pub fn find_index_by_name(&self, sym_name: &[u8]) -> Option<usize> { pub fn find_index_by_name(&self, sym_name: &[u8]) -> Option<usize> {
self.symtab.iter().position(|sym| { self.symtab.iter().position(|sym| {
if let Ok(dynsym_name) = name_starting_at_slice(self.strtab, sym.st_name as usize) { if let Ok(dynsym_name) = name_starting_at_slice(self.strtab, sym.st_name as usize) {

View File

@ -67,7 +67,7 @@ def _bool(x):
def _float(x): def _float(x):
if isinstance(x, np.ndarray): if isinstance(x, np.ndarray):
return np.float64(x) return np.float_(x)
else: else:
return float(x) return float(x)
@ -111,9 +111,6 @@ def patch(module):
def output_strln(x): def output_strln(x):
print(x, end='') print(x, end='')
def output_int32_list(x):
print([int(e) for e in x])
def dbg_stack_address(_): def dbg_stack_address(_):
return 0 return 0
@ -129,12 +126,11 @@ def patch(module):
return output_float return output_float
elif name == "output_str": elif name == "output_str":
return output_strln return output_strln
elif name == "output_int32_list":
return output_int32_list
elif name in { elif name in {
"output_bool", "output_bool",
"output_int32", "output_int32",
"output_int64", "output_int64",
"output_int32_list",
"output_uint32", "output_uint32",
"output_uint64", "output_uint64",
"output_strln", "output_strln",
@ -183,16 +179,6 @@ def patch(module):
module.np_identity = np.identity module.np_identity = np.identity
module.np_array = np.array module.np_array = np.array
# NumPy NDArray view functions
module.np_broadcast_to = np.broadcast_to
module.np_transpose = np.transpose
module.np_reshape = np.reshape
# NumPy NDArray property getters
module.np_size = np.size
module.np_shape = np.shape
module.np_strides = lambda ndarray: ndarray.strides
# NumPy Math functions # NumPy Math functions
module.np_isnan = np.isnan module.np_isnan = np.isnan
module.np_isinf = np.isinf module.np_isinf = np.isinf
@ -232,6 +218,8 @@ def patch(module):
module.np_ldexp = np.ldexp module.np_ldexp = np.ldexp
module.np_hypot = np.hypot module.np_hypot = np.hypot
module.np_nextafter = np.nextafter module.np_nextafter = np.nextafter
module.np_transpose = np.transpose
module.np_reshape = np.reshape
# SciPy Math functions # SciPy Math functions
module.sp_spec_erf = special.erf module.sp_spec_erf = special.erf

View File

@ -68,19 +68,6 @@ def output_ndarray_float_2(n: ndarray[float, Literal[2]]):
for c in range(len(n[r])): for c in range(len(n[r])):
output_float64(n[r][c]) output_float64(n[r][c])
def output_ndarray_float_3(n: ndarray[float, Literal[3]]):
for d in range(len(n)):
for r in range(len(n[d])):
for c in range(len(n[d][r])):
output_float64(n[d][r][c])
def output_ndarray_float_4(n: ndarray[float, Literal[4]]):
for x in range(len(n)):
for y in range(len(n[x])):
for z in range(len(n[x][y])):
for w in range(len(n[x][y][z])):
output_float64(n[x][y][z][w])
def consume_ndarray_1(n: ndarray[float, Literal[1]]): def consume_ndarray_1(n: ndarray[float, Literal[1]]):
pass pass
@ -210,104 +197,6 @@ def test_ndarray_nd_idx():
output_float64(x[1, 0]) output_float64(x[1, 0])
output_float64(x[1, 1]) output_float64(x[1, 1])
def test_ndarray_transpose():
x: ndarray[float, 2] = np_array([[1., 2., 3.], [4., 5., 6.]])
y = np_transpose(x)
z = np_transpose(y)
output_int32(np_shape(x)[0])
output_int32(np_shape(x)[1])
output_ndarray_float_2(x)
output_int32(np_shape(y)[0])
output_int32(np_shape(y)[1])
output_ndarray_float_2(y)
output_int32(np_shape(z)[0])
output_int32(np_shape(z)[1])
output_ndarray_float_2(z)
def test_ndarray_reshape():
w: ndarray[float, 1] = np_array([1., 2., 3., 4., 5., 6., 7., 8., 9., 10.])
x = np_reshape(w, (1, 2, 1, -1))
y = np_reshape(x, [2, -1])
z = np_reshape(y, 10)
output_int32(np_shape(w)[0])
output_ndarray_float_1(w)
output_int32(np_shape(x)[0])
output_int32(np_shape(x)[1])
output_int32(np_shape(x)[2])
output_int32(np_shape(x)[3])
output_ndarray_float_4(x)
output_int32(np_shape(y)[0])
output_int32(np_shape(y)[1])
output_ndarray_float_2(y)
output_int32(np_shape(z)[0])
output_ndarray_float_1(z)
x1: ndarray[int32, 1] = np_array([1, 2, 3, 4])
x2: ndarray[int32, 2] = np_reshape(x1, (2, 2))
output_int32(np_shape(x1)[0])
output_ndarray_int32_1(x1)
output_int32(np_shape(x2)[0])
output_int32(np_shape(x2)[1])
output_ndarray_int32_2(x2)
def test_ndarray_broadcast_to():
xs = np_array([1.0, 2.0, 3.0])
ys = np_broadcast_to(xs, (1, 3))
zs = np_broadcast_to(ys, (2, 4, 3))
output_int32(np_shape(xs)[0])
output_ndarray_float_1(xs)
output_int32(np_shape(ys)[0])
output_int32(np_shape(ys)[1])
output_ndarray_float_2(ys)
output_int32(np_shape(zs)[0])
output_int32(np_shape(zs)[1])
output_int32(np_shape(zs)[2])
output_ndarray_float_3(zs)
def test_ndarray_subscript_assignment():
xs = np_array([[11.0, 22.0, 33.0, 44.0], [55.0, 66.0, 77.0, 88.0]])
xs[0, 0] = 99.0
output_ndarray_float_2(xs)
xs[0] = 100.0
output_ndarray_float_2(xs)
xs[:, ::2] = 101.0
output_ndarray_float_2(xs)
xs[1:, 0] = 102.0
output_ndarray_float_2(xs)
xs[0] = np_array([-1.0, -2.0, -3.0, -4.0])
output_ndarray_float_2(xs)
xs[:] = np_array([-5.0, -6.0, -7.0, -8.0])
output_ndarray_float_2(xs)
# Test assignment with memory sharing
ys1 = np_reshape(xs, (2, 4))
ys2 = np_transpose(ys1)
ys3 = ys2[::-1, 0]
ys3[0] = -999.0
output_ndarray_float_2(xs)
output_ndarray_float_2(ys1)
output_ndarray_float_2(ys2)
output_ndarray_float_1(ys3)
def test_ndarray_add(): def test_ndarray_add():
x = np_identity(2) x = np_identity(2)
y = x + np_ones([2, 2]) y = x + np_ones([2, 2])
@ -1551,6 +1440,27 @@ def test_ndarray_nextafter_broadcast_rhs_scalar():
output_ndarray_float_2(nextafter_x_zeros) output_ndarray_float_2(nextafter_x_zeros)
output_ndarray_float_2(nextafter_x_ones) output_ndarray_float_2(nextafter_x_ones)
def test_ndarray_transpose():
x: ndarray[float, 2] = np_array([[1., 2., 3.], [4., 5., 6.]])
y = np_transpose(x)
z = np_transpose(y)
output_ndarray_float_2(x)
output_ndarray_float_2(y)
def test_ndarray_reshape():
w: ndarray[float, 1] = np_array([1., 2., 3., 4., 5., 6., 7., 8., 9., 10.])
x = np_reshape(w, (1, 2, 1, -1))
y = np_reshape(x, [2, -1])
z = np_reshape(y, 10)
x1: ndarray[int32, 1] = np_array([1, 2, 3, 4])
x2: ndarray[int32, 2] = np_reshape(x1, (2, 2))
output_ndarray_float_1(w)
output_ndarray_float_2(y)
output_ndarray_float_1(z)
def test_ndarray_dot(): def test_ndarray_dot():
x1: ndarray[float, 1] = np_array([5.0, 1.0, 4.0, 2.0]) x1: ndarray[float, 1] = np_array([5.0, 1.0, 4.0, 2.0])
y1: ndarray[float, 1] = np_array([5.0, 1.0, 6.0, 6.0]) y1: ndarray[float, 1] = np_array([5.0, 1.0, 6.0, 6.0])
@ -1682,11 +1592,6 @@ def run() -> int32:
test_ndarray_slices() test_ndarray_slices()
test_ndarray_nd_idx() test_ndarray_nd_idx()
test_ndarray_transpose()
test_ndarray_reshape()
test_ndarray_broadcast_to()
test_ndarray_subscript_assignment()
test_ndarray_add() test_ndarray_add()
test_ndarray_add_broadcast() test_ndarray_add_broadcast()
test_ndarray_add_broadcast_lhs_scalar() test_ndarray_add_broadcast_lhs_scalar()
@ -1850,6 +1755,8 @@ def run() -> int32:
test_ndarray_nextafter_broadcast() test_ndarray_nextafter_broadcast()
test_ndarray_nextafter_broadcast_lhs_scalar() test_ndarray_nextafter_broadcast_lhs_scalar()
test_ndarray_nextafter_broadcast_rhs_scalar() test_ndarray_nextafter_broadcast_rhs_scalar()
test_ndarray_transpose()
test_ndarray_reshape()
test_ndarray_dot() test_ndarray_dot()
test_ndarray_cholesky() test_ndarray_cholesky()

View File

@ -29,10 +29,10 @@ def u32_max() -> uint32:
return ~uint32(0) return ~uint32(0)
def i32_min() -> int32: def i32_min() -> int32:
return int32(-(1 << 31)) return int32(1 << 31)
def i32_max() -> int32: def i32_max() -> int32:
return int32((1 << 31)-1) return int32(~(1 << 31))
def u64_min() -> uint64: def u64_min() -> uint64:
return uint64(0) return uint64(0)
@ -63,9 +63,8 @@ def test_conv_from_i32():
i32_max() i32_max()
]: ]:
output_int64(int64(x)) output_int64(int64(x))
if x >= 0: output_uint32(uint32(x))
output_uint32(uint32(x)) output_uint64(uint64(x))
output_uint64(uint64(x))
output_float64(float(x)) output_float64(float(x))
def test_conv_from_u32(): def test_conv_from_u32():
@ -109,6 +108,7 @@ def test_conv_from_u64():
def test_f64toi32(): def test_f64toi32():
for x in [ for x in [
float(i32_min()) - 1.0,
float(i32_min()), float(i32_min()),
float(i32_min()) + 1.0, float(i32_min()) + 1.0,
-1.5, -1.5,
@ -117,6 +117,7 @@ def test_f64toi32():
1.5, 1.5,
float(i32_max()) - 1.0, float(i32_max()) - 1.0,
float(i32_max()), float(i32_max()),
float(i32_max()) + 1.0
]: ]:
output_int32(int32(x)) output_int32(int32(x))
@ -137,17 +138,24 @@ def test_f64toi64():
def test_f64tou32(): def test_f64tou32():
for x in [ for x in [
-1.5,
float(u32_min()) - 1.0,
-0.5,
float(u32_min()), float(u32_min()),
0.5, 0.5,
float(u32_min()) + 1.0, float(u32_min()) + 1.0,
1.5, 1.5,
float(u32_max()) - 1.0, float(u32_max()) - 1.0,
float(u32_max()), float(u32_max()),
float(u32_max()) + 1.0
]: ]:
output_uint32(uint32(x)) output_uint32(uint32(x))
def test_f64tou64(): def test_f64tou64():
for x in [ for x in [
-1.5,
float(u64_min()) - 1.0,
-0.5,
float(u64_min()), float(u64_min()),
0.5, 0.5,
float(u64_min()) + 1.0, float(u64_min()) + 1.0,
@ -173,4 +181,4 @@ def run() -> int32:
test_f64tou32() test_f64tou32()
test_f64tou64() test_f64tou64()
return 0 return 0