1
0
forked from M-Labs/nac3

Compare commits

..

97 Commits

Author SHA1 Message Date
6265d53ad5 [artiq] Fix intermittent class resolution failures
In the past, modules (and therefore its members) are not added or
analyzed in order of appearance, as it is stored in a HashMap with the
PythonId as its key.

While this never posed an issue in the past, the refactoring performed
in #535 assumed that the classes *are* ordered by appearance, causing
the bug to manifest. Furthermore, this bug will only manifest
iff a base class has a PythonId greater than the derived class,
explaining why the bug only occurs on occasion.

Fix this by using an IndexMap, which preserves the order of insertion
while also performing deduplication.
2025-02-11 17:02:57 +08:00
36d502dc31 [meta] Apply clippy suggestions 2025-02-11 16:52:28 +08:00
82a580c5c6 flake: update ARTIQ source used for PGO 2025-02-10 16:53:35 +08:00
715dc71396 nac3artiq: acquire special python identifiers 2025-02-10 16:42:49 +08:00
064aa0411f [core] codegen: Add Exception{Type,Value} 2025-02-10 11:29:58 +08:00
57552fb2f6 [core] codegen: Add Option{Type,Value} 2025-02-10 11:29:58 +08:00
35e9c5b38e [core] codegen: Add String{Type,Value} 2025-02-10 11:29:58 +08:00
0a761cb263 [core] Use more TupleType constructors 2025-02-10 11:29:58 +08:00
67f42185de [core] codegen/expr: Add concrete ndims value to error message 2025-02-10 11:29:58 +08:00
69542c38a2 [core] codegen: Rename TupleValue::{store,load} -> {insert,extract}
Better matches the underlying operation.
2025-02-10 11:29:55 +08:00
2df22e29f7 [core] codegen: Simplify TupleType::construct 2025-02-10 11:26:45 +08:00
a078481cd2 [meta] Minor simplification for PrimStore extraction 2025-02-10 11:26:45 +08:00
c37c7e8975 [core] codegen/expr: Simplify gen_*_expr_with_values return value
These functions always return `BasicValueEnum` because they operate on
`BasicValueEnum`s, and they also always return a value.
2025-02-10 11:26:45 +08:00
0d8cb909dd [core] codegen/expr: Fix and use gen_unaryop_expr for boolean not ops
While refactoring, I ran into the issue where `!true == true`, which was
caused by the same upper 7-bit of booleans being undefined issue that
was encountered before. It turns out the implementation in
`gen_unaryop_expr` is also inadequate, as `(~v & (i1) 0x1)`` will still
leave upper 7 bits undefined (for whatever reason).

This commit fixes this issue once and for all by using a combination of
`icmp` + `zext` to ensure that the resulting value must be `0 | 1`, and
refactor to use that whenever we need to invert boolean values.
2025-02-10 11:26:45 +08:00
529fa67855 [core] codegen: Add bool_to_int_type to replace bool_to_{i1,i8}
Unifies the implementation for both functions.
2025-02-10 11:26:45 +08:00
f52ba9f151 [core] codegen/irrt: Refactor IRRT to use more create/infer fns 2025-02-10 10:56:24 +08:00
6bcdc3ce00 [core] codegen/extern_fns: Change expansion pattern
Makes more sense to attach the parameter delimiter to the end of each
parameter.
2025-02-10 10:56:22 +08:00
c32c68b0b0 flake: update dependencies 2025-02-05 15:42:23 +08:00
d394b24304 [meta] flake: Add LLVM bintools to artiq-{instrumented,pgo} 2025-02-03 13:10:13 +08:00
68da9b0ecf [core] codegen: Implement StructProxy on existing proxies 2025-02-03 11:51:57 +08:00
eec62c3bbb [core] codegen: Refactor StructField getters and setters 2025-02-03 11:51:57 +08:00
b521bc0c82 [core] codegen: Add Proxy{Type,Value}::as_abi_{type,value}
Needed for PtrToOrBasic{Type,Value}.
2025-02-03 11:51:57 +08:00
96e98947cc [core] codegen: Add StructProxy{Type,Value} 2025-02-03 11:51:57 +08:00
87a637b448 [core] codegen: Refactor Proxy{Type,Value} for StructProxy{Type,Value} 2025-02-03 11:51:57 +08:00
bdeeced122 [core] codegen: Normalize RangeType factory functions
Better matches factory functions of other ProxyTypes.
2025-02-03 11:51:57 +08:00
37df08b803 [meta] Update dependencies 2025-02-03 11:51:57 +08:00
05fd1a5199 [meta] Use lld as linker 2025-02-03 10:56:35 +08:00
f817d3347b [artiq] cleanup module functionality tests 2025-01-20 10:24:08 +08:00
2d275949b8 move tests from artiq to standalone 2025-01-17 13:10:35 +08:00
2783834cb1 nac3artiq/demo: merge EmbeddingMap into min_artiq 2025-01-17 12:45:51 +08:00
879b063968 [artiq] add tests for module support 2025-01-16 12:42:13 +08:00
14e80dfab7 update snapshots 2025-01-16 12:41:30 +08:00
5fdbc34b43 [core] implement codegen for modules 2025-01-16 12:40:56 +08:00
32f24261f2 [artiq] add global variables to modules 2025-01-16 12:40:14 +08:00
ce40a46f8a [core] add module type 2025-01-16 12:40:06 +08:00
f15a64cc1b [artiq] register modules 2025-01-16 11:13:04 +08:00
7fac801936 [artiq] add module primitive type 2025-01-16 11:13:04 +08:00
febfd1241d [core] add module type 2025-01-16 11:13:04 +08:00
4bd5349381 [core] add attributes to class string 2025-01-16 11:13:04 +08:00
c15062ab4c msys2: update 2025-01-15 21:33:58 +08:00
933804e270 update dependencies 2025-01-15 21:18:45 +08:00
1cfaa1a779 [core] toplevel: Implement np_{any,all} 2025-01-15 16:09:32 +08:00
18e8e5269f [core] codegen/values/ndarray: Add fold utilities
Needed for np_{any,all}.
2025-01-15 16:09:32 +08:00
357970a793 [core] codegen/stmt: Add build_{break,continue}_branch functions 2025-01-15 16:09:32 +08:00
762a2447c3 [core] codegen: Remove obsolete comments
Comments regarding the need for `llvm.stack{save,restore}` is obsolete
now that `NDIter::indices` is allocated at the beginning of the
function.
2025-01-15 16:09:32 +08:00
8e614d83de [core] codegen: Add ProxyType::new overloads and refactor to use them 2025-01-15 13:23:19 +08:00
bd66fe48d8 [core] codegen: Refactor to use CodeGenContext::get_size_type
Simplifies a lot of API usage.
2025-01-15 13:23:19 +08:00
c59fd286ff [artiq] Move get_llvm_* to Isa, use TargetMachine to infer size_t 2025-01-15 13:23:19 +08:00
f8530e0ef6 [core] codegen: Add CodeGenContext::get_size_type
Convenience method for getting the `size_t` LLVM type without the use of
`CodeGenerator`.
2025-01-15 13:22:50 +08:00
3ebd4ba5d1 [core] codegen: Add assertion verifying size_t is compatible 2025-01-14 18:25:00 +08:00
d1dcfa19ff CodeGenerator: Add with_target_machine factory function
Allows creating CodeGenerator with the LLVM target machine to infer the
expected type for size_t.
2025-01-13 14:55:33 +08:00
8baf111734 [meta] Apply clippy suggestions 2025-01-06 17:11:31 +08:00
eaaa194a87 [artiq] symbol_resolver: Cast ndarray.{shape,strides} globals to usize*
This is needed as ndarray.{shapes,strides} are ArrayValues, and so a GEP
operation is required to convert them into pointers to their first
elements.
2025-01-06 16:53:33 +08:00
352c7c880b [artiq] symbol_resolver: Fix incorrect global type for ndarray.strides 2025-01-06 16:53:33 +08:00
3c5e247195 [artiq] symbol_resolver: Use TargetData to get size of dtype
dtype.size_of() may not return a constant value.
2025-01-06 16:53:33 +08:00
4e21def1a0 [artiq] symbol_resolver: Add missing promotion for host compilation
Shape tuple is always in i32, so a zero-extension to i64 is
necessary when assigning the shape tuple into the shape field of the
ndarray.
2025-01-06 16:53:33 +08:00
2271b46b96 [core] codegen/values/ndarray: Fix Vec allocation 2025-01-06 16:53:33 +08:00
d9c180ed13 [artiq] symbol_resolver: Fix support for np.bool_ -> bool decay 2025-01-06 16:53:33 +08:00
8322d457c6 standalone/demo: numpy2 compatibility 2025-01-04 15:30:24 +08:00
e480081e4b update dependencies 2025-01-04 10:28:41 +08:00
12fddc3533 [core] codegen/ndarray: Make ndims non-optional
Now that everything is ported to use strided impl, dynamic-ndim ndarray
instances do not exist anymore.
2025-01-03 15:43:08 +08:00
3ac1083734 [core] codegen: Reimplement np_dot() for scalars and 1D
Based on 693b7f37: core/ndstrides: implement np_dot() for scalars and 1D
2025-01-03 15:43:08 +08:00
66b8a5e01d [core] codegen/ndarray: Reimplement matmul
Based on 73c2203b: core/ndstrides: implement general matmul
2025-01-03 15:43:06 +08:00
ebbadc2d74 [core] codegen: Reimplement ndarray cmpop
Based on 56cccce1: core/ndstrides: implement cmpop
2025-01-03 15:15:13 +08:00
a2f1b25fd8 [core] codegen: Reimplement ndarray unary op
Based on bb992704: core/ndstrides: implement unary op
2025-01-03 15:15:12 +08:00
59f19e29df [core] codegen: Reimplement ndarray binop
Based on 9e40c834: core/ndstrides: implement binop
2025-01-03 15:15:12 +08:00
6cbba8fdde [core] codegen: Reimplement builtin funcs to support strided ndarrays
Based on 7f3c4530: core/ndstrides: update builtin_fns to use ndarray
with strides
2025-01-03 15:15:12 +08:00
e6dab25a57 [core] codegen/ndarray: Add NDArrayOut, broadcast_map, map
Based on fbfc0b29: core/ndstrides: add NDArrayOut, broadcast_map and map
2025-01-03 15:15:11 +08:00
2dc5e79a23 [core] codegen/ndarray: Implement subscript assignment
Based on 5bed394e: core/ndstrides: implement subscript assignment

Overlapping is not handled. Currently it has undefined behavior.
2025-01-03 15:15:11 +08:00
dcde1d9c87 [core] codegen/values/ndarray: Add more ScalarOrNDArray utils
Based on f731e604: core/ndstrides: add more ScalarOrNDArray and
NDArrayObject utils
2025-01-03 15:15:10 +08:00
7375983e0c [core] codegen/ndarray: Implement np_transpose without axes argument
Based on 052b67c8: core/ndstrides: implement np_transpose() (no axes
argument)

The IRRT implementation knows how to handle axes. But the argument is
not in NAC3 yet.
2025-01-03 15:15:08 +08:00
43e440d2fd [core] codegen/ndarray: Reimplement broadcasting
Based on 9359ed96: core/ndstrides: implement broadcasting &
np_broadcast_to()
2025-01-03 15:14:59 +08:00
8d975b5ff3 [core] codegen/ndarray: Implement np_reshape
Based on 926e7e93: core/ndstrides: implement np_reshape()
2025-01-03 14:56:16 +08:00
aae41eef6a [core] toplevel: Add view functions category
Based on 9e0f636d: core: categorize np_{transpose,reshape} as 'view
functions'
2025-01-03 14:47:59 +08:00
132ba1942f [core] toplevel: Implement np_size
Based on 2c1030d1: core/ndstrides: implement np_size()
2025-01-03 14:16:29 +08:00
12358c57b1 [core] codegen/ndarray: Implement np_{shape,strides}
Based on 40c24486: core/ndstrides: implement np_shape() and np_strides()

These functions are not important, but they are handy for debugging.

`np.strides()` is not an actual NumPy function, but `ndarray.strides` is
used.
2025-01-03 13:58:47 +08:00
9ffa2d6552 [core] codegen/ndarray: Reimplement np_{copy,fill}
Based on 18db85fa: core/ndstrides: implement ndarray.fill() and .copy()
2025-01-03 13:58:47 +08:00
acb437919d [core] codegen/ndarray: Reimplement np_{eye,identity}
Based on fa047d50: core/ndstrides: implement np_identity() and np_eye()
2025-01-03 13:58:47 +08:00
fadadd7505 [core] codegen/ndarray: Reimplement np_array()
Based on 8f0084ac: core/ndstrides: implement np_array()

It also checks for inconsistent dimensions if the input is a list.
e.g., rejecting `[[1.0, 2.0], [3.0]]`.

However, currently only `np_array(<input>, copy=False)` and `np_array
(<input>, copy=True)` are supported. In NumPy, copy could be false,
true, or None. Right now, NAC3's `np_array(<input>, copy=False)` behaves
like NumPy's `np.array(<input>, copy=None)`.
2025-01-03 13:58:47 +08:00
26f1428739 [core] codegen: Refactor len()
Based on 54a842a9: core/ndstrides: implement len(ndarray) & refactor
len()
2025-01-03 13:58:47 +08:00
5880f964bb [core] codegen/ndarray: Reimplement np_{zeros,ones,full,empty}
Based on 792374fa: core/ndstrides: implement np_{zeros,ones,full,empty}.
2025-01-03 13:58:47 +08:00
7d02f5833d [core] codegen: Implement Tuple{Type,Value} 2025-01-03 13:58:47 +08:00
822f9d33f8 [core] codegen: Refactor ListType to use derive(StructFields) 2025-01-03 13:58:47 +08:00
805a9d23b3 [core] codegen: Add derive(Copy, Clone) to TypedArrayLikeAdapter 2025-01-03 13:58:46 +08:00
1ffe2fcc7f [core] irrt: Minor reformat 2025-01-03 13:26:51 +08:00
2f0847d77b [core] codegen/types: Refactor ProxyType
- Add alloca_type() function to obtain the type that should be passed
into a `build_alloca` call
- Provide default implementations for raw_alloca and array_alloca
- Add raw_alloca_var and array_alloca_var to distinguish alloca
instructions placed at the front of the function vs at the current
builder location
2024-12-30 17:00:17 +08:00
dc9efa9e8c [core] codegen/ndarray: Use IRRT for size() and indexing operations
Also refactor some usages of call_ndarray_calc_size with ndarray.size().
2024-12-30 16:58:33 +08:00
3c0ce3031f [core] codegen: Update raw_alloca to return PointerValue
Better match the expected behavior of alloca.
2024-12-30 16:51:34 +08:00
d5e8df070a [core] Minor improvements to IRRT and add missing documentation 2024-12-30 16:51:17 +08:00
dc413dfa43 [core] codegen: Refactor TypedArrayLikeAdapter to use fn
Allows for greater flexibility when TypedArrayLikeAdapter is used with
custom value types.
2024-12-30 16:50:22 +08:00
19122e2905 [core] codegen: Rename classes/functions for consistency
- ContiguousNDArrayFields -> ContiguousNDArrayStructFields
- ndarray/nditer: Add _field suffix to field accessors
2024-12-30 16:50:18 +08:00
318371a509 [core] irrt: Minor cleanup 2024-12-30 14:13:48 +08:00
35e3042435 [core] Refactor/Remove redundant and unused constructs
- Use ProxyValue.name where necessary
- Remove NDArrayValue::ptr_to_{shape,strides}
- Remove functions made obsolete by ndstrides
- Remove use statement for ndarray::views as it only contain an impl
block.
- Remove class_names field in Resolvers of test sources
2024-12-30 14:13:48 +08:00
0e5940c49d [meta] Refactor itertools::{chain,enumerate,repeat_n} with std equiv 2024-12-30 14:13:48 +08:00
fbf0053c24 [core] irrt/string: Minor cleanup
- Refactor __nac3_str_eq to always return bool
- Use `get_usize_dependent_function_name` to get IRRT func name
2024-12-30 14:04:42 +08:00
456aefa6ee clean up duplicate include 2024-12-30 13:03:31 +08:00
ram
49a7469b4a use memcmp for string comparison
Co-authored-by: ram <RAMTEJ001@e.ntu.edu.sg>
Co-committed-by: ram <RAMTEJ001@e.ntu.edu.sg>
2024-12-30 13:02:09 +08:00
120 changed files with 9712 additions and 7323 deletions

2
.cargo/config.toml Normal file
View File

@ -0,0 +1,2 @@
[target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "link-arg=-fuse-ld=lld"]

317
Cargo.lock generated
View File

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "ahash" name = "ahash"
@ -9,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"getrandom", "getrandom 0.2.15",
"once_cell", "once_cell",
"version_check", "version_check",
"zerocopy", "zerocopy",
@ -65,11 +65,12 @@ dependencies = [
[[package]] [[package]]
name = "anstyle-wincon" name = "anstyle-wincon"
version = "3.0.6" version = "3.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"once_cell",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
@ -105,9 +106,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.6.0" version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
@ -126,9 +127,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.4" version = "1.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229"
dependencies = [ dependencies = [
"shlex", "shlex",
] ]
@ -141,9 +142,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.23" version = "4.5.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -151,9 +152,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.23" version = "4.5.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -163,14 +164,14 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.18" version = "4.5.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c"
dependencies = [ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.90", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -187,21 +188,21 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]] [[package]]
name = "console" name = "console"
version = "0.15.8" version = "0.15.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b"
dependencies = [ dependencies = [
"encode_unicode", "encode_unicode",
"lazy_static",
"libc", "libc",
"windows-sys 0.52.0", "once_cell",
"windows-sys 0.59.0",
] ]
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.16" version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -221,18 +222,18 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-channel" name = "crossbeam-channel"
version = "0.5.13" version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471"
dependencies = [ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]] [[package]]
name = "crossbeam-deque" name = "crossbeam-deque"
version = "0.8.5" version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [ dependencies = [
"crossbeam-epoch", "crossbeam-epoch",
"crossbeam-utils", "crossbeam-utils",
@ -249,18 +250,18 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-queue" name = "crossbeam-queue"
version = "0.3.11" version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
dependencies = [ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.20" version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
@ -305,9 +306,9 @@ dependencies = [
[[package]] [[package]]
name = "encode_unicode" name = "encode_unicode"
version = "0.3.6" 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 = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]] [[package]]
name = "equivalent" name = "equivalent"
@ -333,9 +334,15 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]] [[package]]
name = "fixedbitset" name = "fixedbitset"
version = "0.4.2" version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]]
name = "foldhash"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
[[package]] [[package]]
name = "fxhash" name = "fxhash"
@ -373,14 +380,26 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
dependencies = [
"cfg-if",
"libc",
"wasi 0.13.3+wasi-0.2.2",
"windows-targets",
] ]
[[package]] [[package]]
name = "glob" name = "glob"
version = "0.3.1" version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
@ -388,20 +407,14 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.2" version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
dependencies = [
"foldhash",
]
[[package]] [[package]]
name = "heck" name = "heck"
@ -417,11 +430,11 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]] [[package]]
name = "home" name = "home"
version = "0.5.9" version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
dependencies = [ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -436,9 +449,9 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.7.0" version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.15.2", "hashbrown 0.15.2",
@ -472,7 +485,7 @@ checksum = "9dd28cfd4cfba665d47d31c08a6ba637eed16770abca2eccbbc3ca831fef1e44"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.90", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -497,9 +510,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.13.0" version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [ dependencies = [
"either", "either",
] ]
@ -521,9 +534,9 @@ dependencies = [
[[package]] [[package]]
name = "lalrpop" name = "lalrpop"
version = "0.22.0" version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06093b57658c723a21da679530e061a8c25340fa5a6f98e313b542268c7e2a1f" checksum = "7047a26de42016abf8f181b46b398aef0b77ad46711df41847f6ed869a2a1d5b"
dependencies = [ dependencies = [
"ascii-canvas", "ascii-canvas",
"bit-set", "bit-set",
@ -543,9 +556,9 @@ dependencies = [
[[package]] [[package]]
name = "lalrpop-util" name = "lalrpop-util"
version = "0.22.0" version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "feee752d43abd0f4807a921958ab4131f692a44d4d599733d4419c5d586176ce" checksum = "e8d05b3fe34b8bd562c338db725dfa9beb9451a48f65f129ccb9538b48d2c93b"
dependencies = [ dependencies = [
"regex-automata", "regex-automata",
"rustversion", "rustversion",
@ -559,9 +572,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.168" version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]] [[package]]
name = "libloading" name = "libloading"
@ -581,9 +594,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.14" version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]] [[package]]
name = "llvm-sys" name = "llvm-sys"
@ -610,9 +623,9 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.22" version = "0.4.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
[[package]] [[package]]
name = "memchr" name = "memchr"
@ -633,6 +646,7 @@ dependencies = [
name = "nac3artiq" name = "nac3artiq"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"indexmap 2.7.1",
"itertools", "itertools",
"nac3core", "nac3core",
"nac3ld", "nac3ld",
@ -655,7 +669,7 @@ name = "nac3core"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"crossbeam", "crossbeam",
"indexmap 2.7.0", "indexmap 2.7.1",
"indoc", "indoc",
"inkwell", "inkwell",
"insta", "insta",
@ -663,7 +677,6 @@ dependencies = [
"nac3core_derive", "nac3core_derive",
"nac3parser", "nac3parser",
"parking_lot", "parking_lot",
"rayon",
"regex", "regex",
"strum", "strum",
"strum_macros", "strum_macros",
@ -678,7 +691,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.90", "syn 2.0.96",
"trybuild", "trybuild",
] ]
@ -751,55 +764,55 @@ dependencies = [
[[package]] [[package]]
name = "petgraph" name = "petgraph"
version = "0.6.5" version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
dependencies = [ dependencies = [
"fixedbitset", "fixedbitset",
"indexmap 2.7.0", "indexmap 2.7.1",
] ]
[[package]] [[package]]
name = "phf" name = "phf"
version = "0.11.2" version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
dependencies = [ dependencies = [
"phf_macros", "phf_macros",
"phf_shared 0.11.2", "phf_shared 0.11.3",
] ]
[[package]] [[package]]
name = "phf_codegen" name = "phf_codegen"
version = "0.11.2" version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
dependencies = [ dependencies = [
"phf_generator", "phf_generator",
"phf_shared 0.11.2", "phf_shared 0.11.3",
] ]
[[package]] [[package]]
name = "phf_generator" name = "phf_generator"
version = "0.11.2" version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
dependencies = [ dependencies = [
"phf_shared 0.11.2", "phf_shared 0.11.3",
"rand", "rand",
] ]
[[package]] [[package]]
name = "phf_macros" name = "phf_macros"
version = "0.11.2" version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
dependencies = [ dependencies = [
"phf_generator", "phf_generator",
"phf_shared 0.11.2", "phf_shared 0.11.3",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.90", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -808,16 +821,16 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
dependencies = [ dependencies = [
"siphasher", "siphasher 0.3.11",
] ]
[[package]] [[package]]
name = "phf_shared" name = "phf_shared"
version = "0.11.2" version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
dependencies = [ dependencies = [
"siphasher", "siphasher 1.0.1",
] ]
[[package]] [[package]]
@ -873,9 +886,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.92" version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -927,7 +940,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"pyo3-macros-backend", "pyo3-macros-backend",
"quote", "quote",
"syn 2.0.90", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -940,14 +953,14 @@ dependencies = [
"proc-macro2", "proc-macro2",
"pyo3-build-config", "pyo3-build-config",
"quote", "quote",
"syn 2.0.90", "syn 2.0.96",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.37" version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -979,27 +992,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.2.15",
]
[[package]]
name = "rayon"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
] ]
[[package]] [[package]]
@ -1049,9 +1042,9 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.42" version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"errno", "errno",
@ -1062,15 +1055,15 @@ dependencies = [
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.18" version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.18" version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
[[package]] [[package]]
name = "same-file" name = "same-file"
@ -1089,35 +1082,35 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.24" version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.216" version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.216" version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.90", "syn 2.0.96",
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.133" version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@ -1164,9 +1157,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]] [[package]]
name = "similar" name = "similar"
version = "2.6.0" version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
[[package]] [[package]]
name = "siphasher" name = "siphasher"
@ -1174,6 +1167,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 = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]]
name = "siphasher"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.13.2" version = "1.13.2"
@ -1182,12 +1181,11 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]] [[package]]
name = "string-interner" name = "string-interner"
version = "0.17.0" version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c6a0d765f5807e98a091107bae0a56ea3799f66a5de47b2c84c94a39c09974e" checksum = "1a3275464d7a9f2d4cac57c89c2ef96a8524dba2864c8d6f82e3980baf136f9b"
dependencies = [ dependencies = [
"cfg-if", "hashbrown 0.15.2",
"hashbrown 0.14.5",
"serde", "serde",
] ]
@ -1226,7 +1224,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustversion", "rustversion",
"syn 2.0.90", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -1242,9 +1240,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.90" version = "2.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1265,12 +1263,13 @@ checksum = "42a4d50cdb458045afc8131fd91b64904da29548bcb63c7236e0844936c13078"
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.14.0" version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",
"getrandom 0.3.1",
"once_cell", "once_cell",
"rustix", "rustix",
"windows-sys 0.59.0", "windows-sys 0.59.0",
@ -1278,9 +1277,9 @@ dependencies = [
[[package]] [[package]]
name = "term" name = "term"
version = "1.0.0" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4df4175de05129f31b80458c6df371a15e7fc3fd367272e6bf938e5c351c7ea0" checksum = "a3bb6001afcea98122260987f8b7b5da969ecad46dbf0b5453702f776b491a41"
dependencies = [ dependencies = [
"home", "home",
"windows-sys 0.52.0", "windows-sys 0.52.0",
@ -1325,7 +1324,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.90", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -1355,7 +1354,7 @@ version = "0.22.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
dependencies = [ dependencies = [
"indexmap 2.7.0", "indexmap 2.7.1",
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
@ -1364,9 +1363,9 @@ dependencies = [
[[package]] [[package]]
name = "trybuild" name = "trybuild"
version = "1.0.101" version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dcd332a5496c026f1e14b7f3d2b7bd98e509660c04239c58b0ba38a12daded4" checksum = "b812699e0c4f813b872b373a4471717d9eb550da14b311058a4d9cf4173cbca6"
dependencies = [ dependencies = [
"dissimilar", "dissimilar",
"glob", "glob",
@ -1438,9 +1437,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.14" version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
@ -1510,6 +1509,15 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasi"
version = "0.13.3+wasi-0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
dependencies = [
"wit-bindgen-rt",
]
[[package]] [[package]]
name = "winapi-util" name = "winapi-util"
version = "0.1.9" version = "0.1.9"
@ -1603,13 +1611,22 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.6.20" version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" checksum = "ad699df48212c6cc6eb4435f35500ac6fd3b9913324f938aea302022ce19d310"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "wit-bindgen-rt"
version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
dependencies = [
"bitflags",
]
[[package]] [[package]]
name = "yaml-rust" name = "yaml-rust"
version = "0.4.5" version = "0.4.5"
@ -1637,5 +1654,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.90", "syn 2.0.96",
] ]

6
flake.lock generated
View File

@ -2,11 +2,11 @@
"nodes": { "nodes": {
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1733940404, "lastModified": 1738680400,
"narHash": "sha256-Pj39hSoUA86ZePPF/UXiYHHM7hMIkios8TYG29kQT4g=", "narHash": "sha256-ooLh+XW8jfa+91F1nhf9OF7qhuA/y1ChLx6lXDNeY5U=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "5d67ea6b4b63378b9c13be21e2ec9d1afc921713", "rev": "799ba5bffed04ced7067a91798353d360788b30d",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -41,7 +41,7 @@
lockFile = ./Cargo.lock; lockFile = ./Cargo.lock;
}; };
passthru.cargoLock = cargoLock; passthru.cargoLock = cargoLock;
nativeBuildInputs = [ pkgs.python3 (pkgs.wrapClangMulti pkgs.llvmPackages_14.clang) llvm-tools-irrt pkgs.llvmPackages_14.llvm.out llvm-nac3 ]; nativeBuildInputs = [ pkgs.python3 (pkgs.wrapClangMulti pkgs.llvmPackages_14.clang) llvm-tools-irrt pkgs.llvmPackages_14.llvm.out pkgs.llvmPackages_14.bintools llvm-nac3 ];
buildInputs = [ pkgs.python3 llvm-nac3 ]; buildInputs = [ pkgs.python3 llvm-nac3 ];
checkInputs = [ (pkgs.python3.withPackages(ps: [ ps.numpy ps.scipy ])) ]; checkInputs = [ (pkgs.python3.withPackages(ps: [ ps.numpy ps.scipy ])) ];
checkPhase = checkPhase =
@ -85,7 +85,7 @@
name = "nac3artiq-instrumented"; name = "nac3artiq-instrumented";
src = self; src = self;
inherit (nac3artiq) cargoLock; inherit (nac3artiq) cargoLock;
nativeBuildInputs = [ pkgs.python3 packages.x86_64-linux.llvm-tools-irrt llvm-nac3-instrumented ]; nativeBuildInputs = [ pkgs.python3 packages.x86_64-linux.llvm-tools-irrt pkgs.llvmPackages_14.bintools llvm-nac3-instrumented ];
buildInputs = [ pkgs.python3 llvm-nac3-instrumented ]; buildInputs = [ pkgs.python3 llvm-nac3-instrumented ];
cargoBuildFlags = [ "--package" "nac3artiq" "--features" "init-llvm-profile" ]; cargoBuildFlags = [ "--package" "nac3artiq" "--features" "init-llvm-profile" ];
doCheck = false; doCheck = false;
@ -113,13 +113,14 @@
(pkgs.fetchFromGitHub { (pkgs.fetchFromGitHub {
owner = "m-labs"; owner = "m-labs";
repo = "artiq"; repo = "artiq";
rev = "28c9de3e251daa89a8c9fd79d5ab64a3ec03bac6"; rev = "554b0749ca5985bf4d006c4f29a05e83de0a226d";
sha256 = "sha256-vAvpbHc5B+1wtG8zqN7j9dQE1ON+i22v+uqA+tw6Gak="; sha256 = "sha256-3eSNHTSlmdzLMcEMIspxqjmjrcQe4aIGqIfRgquUg18=";
}) })
]; ];
buildInputs = [ buildInputs = [
(python3-mimalloc.withPackages(ps: [ ps.numpy ps.scipy ps.jsonschema ps.lmdb ps.platformdirs nac3artiq-instrumented ])) (python3-mimalloc.withPackages(ps: [ ps.numpy ps.scipy ps.jsonschema ps.lmdb ps.platformdirs nac3artiq-instrumented ]))
pkgs.llvmPackages_14.llvm.out pkgs.llvmPackages_14.llvm.out
pkgs.llvmPackages_14.bintools
]; ];
phases = [ "buildPhase" "installPhase" ]; phases = [ "buildPhase" "installPhase" ];
buildPhase = buildPhase =
@ -147,7 +148,7 @@
name = "nac3artiq-pgo"; name = "nac3artiq-pgo";
src = self; src = self;
inherit (nac3artiq) cargoLock; inherit (nac3artiq) cargoLock;
nativeBuildInputs = [ pkgs.python3 packages.x86_64-linux.llvm-tools-irrt llvm-nac3-pgo ]; nativeBuildInputs = [ pkgs.python3 packages.x86_64-linux.llvm-tools-irrt pkgs.llvmPackages_14.bintools llvm-nac3-pgo ];
buildInputs = [ pkgs.python3 llvm-nac3-pgo ]; buildInputs = [ pkgs.python3 llvm-nac3-pgo ];
cargoBuildFlags = [ "--package" "nac3artiq" ]; cargoBuildFlags = [ "--package" "nac3artiq" ];
cargoTestFlags = [ "--package" "nac3ast" "--package" "nac3parser" "--package" "nac3core" "--package" "nac3artiq" ]; cargoTestFlags = [ "--package" "nac3ast" "--package" "nac3parser" "--package" "nac3core" "--package" "nac3artiq" ];
@ -168,7 +169,7 @@
buildInputs = with pkgs; [ buildInputs = with pkgs; [
# build dependencies # build dependencies
packages.x86_64-linux.llvm-nac3 packages.x86_64-linux.llvm-nac3
(pkgs.wrapClangMulti llvmPackages_14.clang) llvmPackages_14.llvm.out # for running nac3standalone demos (pkgs.wrapClangMulti llvmPackages_14.clang) llvmPackages_14.llvm.out llvmPackages_14.bintools # for running nac3standalone demos
packages.x86_64-linux.llvm-tools-irrt packages.x86_64-linux.llvm-tools-irrt
cargo cargo
rustc rustc

View File

@ -9,10 +9,11 @@ name = "nac3artiq"
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
itertools = "0.13" indexmap = "2.7"
itertools = "0.14"
pyo3 = { version = "0.21", features = ["extension-module", "gil-refs"] } pyo3 = { version = "0.21", features = ["extension-module", "gil-refs"] }
parking_lot = "0.12" parking_lot = "0.12"
tempfile = "3.13" tempfile = "3.16"
nac3core = { path = "../nac3core" } nac3core = { path = "../nac3core" }
nac3ld = { path = "../nac3ld" } nac3ld = { path = "../nac3ld" }

View File

@ -1,39 +0,0 @@
class EmbeddingMap:
def __init__(self):
self.object_inverse_map = {}
self.object_map = {}
self.string_map = {}
self.string_reverse_map = {}
self.function_map = {}
self.attributes_writeback = []
def store_function(self, key, fun):
self.function_map[key] = fun
return key
def store_object(self, obj):
obj_id = id(obj)
if obj_id in self.object_inverse_map:
return self.object_inverse_map[obj_id]
key = len(self.object_map) + 1
self.object_map[key] = obj
self.object_inverse_map[obj_id] = key
return key
def store_str(self, s):
if s in self.string_reverse_map:
return self.string_reverse_map[s]
key = len(self.string_map)
self.string_map[key] = s
self.string_reverse_map[s] = key
return key
def retrieve_function(self, key):
return self.function_map[key]
def retrieve_object(self, key):
return self.object_map[key]
def retrieve_str(self, key):
return self.string_map[key]

View File

@ -6,7 +6,6 @@ from typing import Generic, TypeVar
from math import floor, ceil from math import floor, ceil
import nac3artiq import nac3artiq
from embedding_map import EmbeddingMap
__all__ = [ __all__ = [
@ -17,7 +16,7 @@ __all__ = [
"rpc", "ms", "us", "ns", "rpc", "ms", "us", "ns",
"print_int32", "print_int64", "print_int32", "print_int64",
"Core", "TTLOut", "Core", "TTLOut",
"parallel", "sequential" "parallel", "legacy_parallel", "sequential"
] ]
@ -193,6 +192,46 @@ def print_int64(x: int64):
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
class EmbeddingMap:
def __init__(self):
self.object_inverse_map = {}
self.object_map = {}
self.string_map = {}
self.string_reverse_map = {}
self.function_map = {}
self.attributes_writeback = []
def store_function(self, key, fun):
self.function_map[key] = fun
return key
def store_object(self, obj):
obj_id = id(obj)
if obj_id in self.object_inverse_map:
return self.object_inverse_map[obj_id]
key = len(self.object_map) + 1
self.object_map[key] = obj
self.object_inverse_map[obj_id] = key
return key
def store_str(self, s):
if s in self.string_reverse_map:
return self.string_reverse_map[s]
key = len(self.string_map)
self.string_map[key] = s
self.string_reverse_map[s] = key
return key
def retrieve_function(self, key):
return self.function_map[key]
def retrieve_object(self, key):
return self.object_map[key]
def retrieve_str(self, key):
return self.string_map[key]
@nac3 @nac3
class Core: class Core:
ref_period: KernelInvariant[float] ref_period: KernelInvariant[float]
@ -206,7 +245,7 @@ class Core:
embedding = EmbeddingMap() embedding = EmbeddingMap()
if allow_registration: if allow_registration:
compiler.analyze(registered_functions, registered_classes, set()) compiler.analyze(registered_functions, registered_classes, special_ids, set())
allow_registration = False allow_registration = False
if hasattr(method, "__self__"): if hasattr(method, "__self__"):
@ -297,4 +336,11 @@ class UnwrapNoneError(Exception):
artiq_builtin = True artiq_builtin = True
parallel = KernelContextManager() parallel = KernelContextManager()
legacy_parallel = KernelContextManager()
sequential = KernelContextManager() sequential = KernelContextManager()
special_ids = {
"parallel": id(parallel),
"legacy_parallel": id(legacy_parallel),
"sequential": id(sequential),
}

26
nac3artiq/demo/module.py Normal file
View File

@ -0,0 +1,26 @@
from min_artiq import *
from numpy import int32
# Global Variable Definition
X: Kernel[int32] = 1
# TopLevelFunction Defintion
@kernel
def display_X():
print_int32(X)
# TopLevel Class Definition
@nac3
class A:
@kernel
def __init__(self):
self.set_x(1)
@kernel
def set_x(self, new_val: int32):
global X
X = new_val
@kernel
def get_X(self) -> int32:
return X

View File

@ -0,0 +1,26 @@
from min_artiq import *
import module as module_definition
@nac3
class TestModuleSupport:
core: KernelInvariant[Core]
def __init__(self):
self.core = Core()
@kernel
def run(self):
# Accessing classes
obj = module_definition.A()
obj.get_X()
obj.set_x(2)
# Calling functions
module_definition.display_X()
# Updating global variables
module_definition.X = 9
module_definition.display_X()
if __name__ == "__main__":
TestModuleSupport().run()

View File

@ -0,0 +1,29 @@
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

@ -1,24 +0,0 @@
from min_artiq import *
from numpy import int32
@nac3
class Demo:
core: KernelInvariant[Core]
attr1: KernelInvariant[str]
attr2: KernelInvariant[int32]
def __init__(self):
self.core = Core()
self.attr2 = 32
self.attr1 = "SAMPLE"
@kernel
def run(self):
print_int32(self.attr2)
self.attr1
if __name__ == "__main__":
Demo().run()

View File

@ -1,40 +0,0 @@
from min_artiq import *
from numpy import int32
@nac3
class Demo:
attr1: KernelInvariant[int32] = 2
attr2: int32 = 4
attr3: Kernel[int32]
@kernel
def __init__(self):
self.attr3 = 8
@nac3
class NAC3Devices:
core: KernelInvariant[Core]
attr4: KernelInvariant[int32] = 16
def __init__(self):
self.core = Core()
@kernel
def run(self):
Demo.attr1 # Supported
# Demo.attr2 # Field not accessible on Kernel
# Demo.attr3 # Only attributes can be accessed in this way
# Demo.attr1 = 2 # Attributes are immutable
self.attr4 # Attributes can be accessed within class
obj = Demo()
obj.attr1 # Attributes can be accessed by class objects
NAC3Devices.attr4 # Attributes accessible for classes without __init__
if __name__ == "__main__":
NAC3Devices().run()

View File

@ -12,16 +12,16 @@ use pyo3::{
PyObject, PyResult, Python, PyObject, PyResult, Python,
}; };
use super::{symbol_resolver::InnerResolver, timeline::TimeFns}; use super::{symbol_resolver::InnerResolver, timeline::TimeFns, SpecialPythonId};
use nac3core::{ use nac3core::{
codegen::{ codegen::{
expr::{destructure_range, gen_call}, expr::{create_fn_and_call, destructure_range, gen_call, infer_and_call_function},
llvm_intrinsics::{call_int_smax, call_memcpy, call_stackrestore, call_stacksave}, llvm_intrinsics::{call_int_smax, call_memcpy, call_stackrestore, call_stacksave},
stmt::{gen_block, gen_for_callback_incrementing, gen_if_callback, gen_with}, stmt::{gen_block, gen_for_callback_incrementing, gen_if_callback, gen_with},
type_aligned_alloca, type_aligned_alloca,
types::ndarray::NDArrayType, types::{ndarray::NDArrayType, RangeType},
values::{ values::{
ArrayLikeIndexer, ArrayLikeValue, ArraySliceValue, ListValue, ProxyValue, RangeValue, ArrayLikeIndexer, ArrayLikeValue, ArraySliceValue, ListValue, ProxyValue,
UntypedArrayLikeAccessor, UntypedArrayLikeAccessor,
}, },
CodeGenContext, CodeGenerator, CodeGenContext, CodeGenerator,
@ -29,6 +29,7 @@ use nac3core::{
inkwell::{ inkwell::{
context::Context, context::Context,
module::Linkage, module::Linkage,
targets::TargetMachine,
types::{BasicType, IntType}, types::{BasicType, IntType},
values::{BasicValueEnum, IntValue, PointerValue, StructValue}, values::{BasicValueEnum, IntValue, PointerValue, StructValue},
AddressSpace, IntPredicate, OptimizationLevel, AddressSpace, IntPredicate, OptimizationLevel,
@ -40,7 +41,10 @@ use nac3core::{
numpy::unpack_ndarray_var_tys, numpy::unpack_ndarray_var_tys,
DefinitionId, GenCall, DefinitionId, GenCall,
}, },
typecheck::typedef::{iter_type_vars, FunSignature, FuncArg, Type, TypeEnum, VarMap}, typecheck::{
type_inferencer::PrimitiveStore,
typedef::{iter_type_vars, FunSignature, FuncArg, Type, TypeEnum, VarMap},
},
}; };
/// The parallelism mode within a block. /// The parallelism mode within a block.
@ -82,26 +86,43 @@ pub struct ArtiqCodeGenerator<'a> {
/// The current parallel context refers to the nearest `with parallel` or `with legacy_parallel` /// The current parallel context refers to the nearest `with parallel` or `with legacy_parallel`
/// statement, which is used to determine when and how the timeline should be updated. /// statement, which is used to determine when and how the timeline should be updated.
parallel_mode: ParallelMode, parallel_mode: ParallelMode,
/// Specially treated python IDs to identify `with parallel` and `with sequential` blocks.
special_ids: SpecialPythonId,
} }
impl<'a> ArtiqCodeGenerator<'a> { impl<'a> ArtiqCodeGenerator<'a> {
pub fn new( pub fn new(
name: String, name: String,
size_t: u32, size_t: IntType<'_>,
timeline: &'a (dyn TimeFns + Sync), timeline: &'a (dyn TimeFns + Sync),
special_ids: SpecialPythonId,
) -> ArtiqCodeGenerator<'a> { ) -> ArtiqCodeGenerator<'a> {
assert!(size_t == 32 || size_t == 64); assert!(matches!(size_t.get_bit_width(), 32 | 64));
ArtiqCodeGenerator { ArtiqCodeGenerator {
name, name,
size_t, size_t: size_t.get_bit_width(),
name_counter: 0, name_counter: 0,
start: None, start: None,
end: None, end: None,
timeline, timeline,
parallel_mode: ParallelMode::None, parallel_mode: ParallelMode::None,
special_ids,
} }
} }
#[must_use]
pub fn with_target_machine(
name: String,
ctx: &Context,
target_machine: &TargetMachine,
timeline: &'a (dyn TimeFns + Sync),
special_ids: SpecialPythonId,
) -> ArtiqCodeGenerator<'a> {
let llvm_usize = ctx.ptr_sized_int_type(&target_machine.get_target_data(), None);
Self::new(name, llvm_usize, timeline, special_ids)
}
/// If the generator is currently in a direct-`parallel` block context, emits IR that resets the /// If the generator is currently in a direct-`parallel` block context, emits IR that resets the
/// position of the timeline to the initial timeline position before entering the `parallel` /// position of the timeline to the initial timeline position before entering the `parallel`
/// block. /// block.
@ -162,7 +183,7 @@ impl<'a> ArtiqCodeGenerator<'a> {
} }
} }
impl<'b> CodeGenerator for ArtiqCodeGenerator<'b> { impl CodeGenerator for ArtiqCodeGenerator<'_> {
fn get_name(&self) -> &str { fn get_name(&self) -> &str {
&self.name &self.name
} }
@ -245,7 +266,22 @@ impl<'b> CodeGenerator for ArtiqCodeGenerator<'b> {
// - If there is a end variable, it indicates that we are (indirectly) inside a // - If there is a end variable, it indicates that we are (indirectly) inside a
// parallel block, and we should update the max end value. // parallel block, and we should update the max end value.
if let ExprKind::Name { id, ctx: name_ctx } = &item.context_expr.node { if let ExprKind::Name { id, ctx: name_ctx } = &item.context_expr.node {
if id == &"parallel".into() || id == &"legacy_parallel".into() { let resolver = ctx.resolver.clone();
if let Some(static_value) =
if let Some((_ptr, static_value, _counter)) = ctx.var_assignment.get(id) {
static_value.clone()
} else if let Some(ValueEnum::Static(val)) =
resolver.get_symbol_value(*id, ctx, self)
{
Some(val)
} else {
None
}
{
let python_id = static_value.get_unique_identifier();
if python_id == self.special_ids.parallel
|| python_id == self.special_ids.legacy_parallel
{
let old_start = self.start.take(); let old_start = self.start.take();
let old_end = self.end.take(); let old_end = self.end.take();
let old_parallel_mode = self.parallel_mode; let old_parallel_mode = self.parallel_mode;
@ -295,10 +331,12 @@ impl<'b> CodeGenerator for ArtiqCodeGenerator<'b> {
ctx.builder.build_store(end, now).unwrap(); ctx.builder.build_store(end, now).unwrap();
self.end = Some(end_expr); self.end = Some(end_expr);
self.name_counter += 1; self.name_counter += 1;
self.parallel_mode = match id.to_string().as_str() { self.parallel_mode = if python_id == self.special_ids.parallel {
"parallel" => ParallelMode::Deep, ParallelMode::Deep
"legacy_parallel" => ParallelMode::Legacy, } else if python_id == self.special_ids.legacy_parallel {
_ => unreachable!(), ParallelMode::Legacy
} else {
unreachable!()
}; };
self.gen_block(ctx, body.iter())?; self.gen_block(ctx, body.iter())?;
@ -344,7 +382,7 @@ impl<'b> CodeGenerator for ArtiqCodeGenerator<'b> {
} }
return Ok(()); return Ok(());
} else if id == &"sequential".into() { } else if python_id == self.special_ids.sequential {
// For deep parallel, temporarily take away start to avoid function calls in // For deep parallel, temporarily take away start to avoid function calls in
// the block from resetting the timeline. // the block from resetting the timeline.
// This does not affect legacy parallel, as the timeline will be reset after // This does not affect legacy parallel, as the timeline will be reset after
@ -364,6 +402,7 @@ impl<'b> CodeGenerator for ArtiqCodeGenerator<'b> {
} }
} }
} }
}
// not parallel/sequential // not parallel/sequential
gen_with(self, ctx, stmt) gen_with(self, ctx, stmt)
@ -377,12 +416,7 @@ fn gen_rpc_tag(
) -> Result<(), String> { ) -> Result<(), String> {
use nac3core::typecheck::typedef::TypeEnum::*; use nac3core::typecheck::typedef::TypeEnum::*;
let int32 = ctx.primitives.int32; let PrimitiveStore { int32, int64, float, bool, str, none, .. } = ctx.primitives;
let int64 = ctx.primitives.int64;
let float = ctx.primitives.float;
let bool = ctx.primitives.bool;
let str = ctx.primitives.str;
let none = ctx.primitives.none;
if ctx.unifier.unioned(ty, int32) { if ctx.unifier.unioned(ty, int32) {
buffer.push(b'i'); buffer.push(b'i');
@ -459,13 +493,13 @@ fn format_rpc_arg<'ctx>(
// libproto_artiq: NDArray = [data[..], dim_sz[..]] // libproto_artiq: NDArray = [data[..], dim_sz[..]]
let llvm_i1 = ctx.ctx.bool_type(); let llvm_i1 = ctx.ctx.bool_type();
let llvm_usize = generator.get_size_type(ctx.ctx); let llvm_usize = ctx.get_size_type();
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, Some(ndims)) let ndarray = NDArrayType::new(ctx, dtype, ndims)
.map_value(arg.into_pointer_value(), None); .map_pointer_value(arg.into_pointer_value(), None);
let ndims = llvm_usize.const_int(ndims, false); let ndims = llvm_usize.const_int(ndims, false);
@ -544,7 +578,7 @@ fn format_rpc_ret<'ctx>(
let llvm_i32 = ctx.ctx.i32_type(); let llvm_i32 = ctx.ctx.i32_type();
let llvm_i8_8 = ctx.ctx.struct_type(&[llvm_i8.array_type(8).into()], false); let llvm_i8_8 = ctx.ctx.struct_type(&[llvm_i8.array_type(8).into()], false);
let llvm_pi8 = llvm_i8.ptr_type(AddressSpace::default()); let llvm_pi8 = llvm_i8.ptr_type(AddressSpace::default());
let llvm_usize = generator.get_size_type(ctx.ctx); let llvm_usize = ctx.get_size_type();
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default()); let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
let rpc_recv = ctx.module.get_function("rpc_recv").unwrap_or_else(|| { let rpc_recv = ctx.module.get_function("rpc_recv").unwrap_or_else(|| {
@ -597,7 +631,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, Some(ndims)) let ndarray = NDArrayType::new(ctx, dtype_llvm, ndims)
.construct_uninitialized(generator, ctx, None); .construct_uninitialized(generator, ctx, None);
// NOTE: Current content of `ndarray`: // NOTE: Current content of `ndarray`:
@ -685,7 +719,7 @@ fn format_rpc_ret<'ctx>(
// debug_assert(nelems * sizeof(T) >= ndarray_nbytes) // debug_assert(nelems * sizeof(T) >= ndarray_nbytes)
if ctx.registry.llvm_options.opt_level == OptimizationLevel::None { if ctx.registry.llvm_options.opt_level == OptimizationLevel::None {
let num_elements = ndarray.size(generator, ctx); let num_elements = ndarray.size(ctx);
let expected_ndarray_nbytes = let expected_ndarray_nbytes =
ctx.builder.build_int_mul(num_elements, itemsize, "").unwrap(); ctx.builder.build_int_mul(num_elements, itemsize, "").unwrap();
@ -749,7 +783,7 @@ fn format_rpc_ret<'ctx>(
ctx.builder.build_unconditional_branch(head_bb).unwrap(); ctx.builder.build_unconditional_branch(head_bb).unwrap();
ctx.builder.position_at_end(tail_bb); ctx.builder.position_at_end(tail_bb);
ndarray.as_base_value().into() ndarray.as_abi_value(ctx).into()
} }
_ => { _ => {
@ -797,7 +831,7 @@ fn rpc_codegen_callback_fn<'ctx>(
) -> Result<Option<BasicValueEnum<'ctx>>, String> { ) -> Result<Option<BasicValueEnum<'ctx>>, String> {
let int8 = ctx.ctx.i8_type(); let int8 = ctx.ctx.i8_type();
let int32 = ctx.ctx.i32_type(); let int32 = ctx.ctx.i32_type();
let size_type = generator.get_size_type(ctx.ctx); let size_type = ctx.get_size_type();
let ptr_type = int8.ptr_type(AddressSpace::default()); let ptr_type = int8.ptr_type(AddressSpace::default());
let tag_ptr_type = ctx.ctx.struct_type(&[ptr_type.into(), size_type.into()], false); let tag_ptr_type = ctx.ctx.struct_type(&[ptr_type.into(), size_type.into()], false);
@ -902,47 +936,14 @@ fn rpc_codegen_callback_fn<'ctx>(
} }
// call // call
if is_async { infer_and_call_function(
let rpc_send_async = ctx.module.get_function("rpc_send_async").unwrap_or_else(|| { ctx,
ctx.module.add_function( if is_async { "rpc_send_async" } else { "rpc_send" },
"rpc_send_async",
ctx.ctx.void_type().fn_type(
&[
int32.into(),
tag_ptr_type.ptr_type(AddressSpace::default()).into(),
ptr_type.ptr_type(AddressSpace::default()).into(),
],
false,
),
None, None,
)
});
ctx.builder
.build_call(
rpc_send_async,
&[service_id.into(), tag_ptr.into(), args_ptr.into()], &[service_id.into(), tag_ptr.into(), args_ptr.into()],
"rpc.send", Some("rpc.send"),
)
.unwrap();
} else {
let rpc_send = ctx.module.get_function("rpc_send").unwrap_or_else(|| {
ctx.module.add_function(
"rpc_send",
ctx.ctx.void_type().fn_type(
&[
int32.into(),
tag_ptr_type.ptr_type(AddressSpace::default()).into(),
ptr_type.ptr_type(AddressSpace::default()).into(),
],
false,
),
None, None,
) );
});
ctx.builder
.build_call(rpc_send, &[service_id.into(), tag_ptr.into(), args_ptr.into()], "rpc.send")
.unwrap();
}
// reclaim stack space used by arguments // reclaim stack space used by arguments
call_stackrestore(ctx, stackptr); call_stackrestore(ctx, stackptr);
@ -1040,6 +1041,34 @@ pub fn attributes_writeback<'ctx>(
)); ));
} }
} }
TypeEnum::TModule { attributes, .. } => {
let mut fields = Vec::new();
let obj = inner_resolver.get_obj_value(py, val, ctx, generator, ty)?.unwrap();
for (name, (field_ty, is_method)) in attributes {
if *is_method {
continue;
}
if gen_rpc_tag(ctx, *field_ty, &mut scratch_buffer).is_ok() {
fields.push(name.to_string());
let (index, _) = ctx.get_attr_index(ty, *name);
values.push((
*field_ty,
ctx.build_gep_and_load(
obj.into_pointer_value(),
&[zero, int32.const_int(index as u64, false)],
None,
),
));
}
}
if !fields.is_empty() {
let pydict = PyDict::new(py);
pydict.set_item("obj", val)?;
pydict.set_item("fields", fields)?;
host_attributes.append(pydict)?;
}
}
_ => {} _ => {}
} }
} }
@ -1128,34 +1157,27 @@ fn polymorphic_print<'ctx>(
debug_assert!(!fmt.is_empty()); debug_assert!(!fmt.is_empty());
debug_assert_eq!(fmt.as_bytes().last().unwrap(), &0u8); debug_assert_eq!(fmt.as_bytes().last().unwrap(), &0u8);
let fn_name = if as_rtio { "rtio_log" } else { "core_log" };
let print_fn = ctx.module.get_function(fn_name).unwrap_or_else(|| {
let llvm_pi8 = ctx.ctx.i8_type().ptr_type(AddressSpace::default());
let fn_t = if as_rtio {
let llvm_void = ctx.ctx.void_type();
llvm_void.fn_type(&[llvm_pi8.into()], true)
} else {
let llvm_i32 = ctx.ctx.i32_type(); let llvm_i32 = ctx.ctx.i32_type();
llvm_i32.fn_type(&[llvm_pi8.into()], true) let llvm_pi8 = ctx.ctx.i8_type().ptr_type(AddressSpace::default());
};
ctx.module.add_function(fn_name, fn_t, None)
});
let fmt = ctx.gen_string(generator, fmt); let fmt = ctx.gen_string(generator, fmt);
let fmt = unsafe { fmt.get_field_at_index_unchecked(0) }.into_pointer_value(); let fmt = unsafe { fmt.get_field_at_index_unchecked(0) }.into_pointer_value();
ctx.builder create_fn_and_call(
.build_call( ctx,
print_fn, if as_rtio { "rtio_log" } else { "core_log" },
if as_rtio { None } else { Some(llvm_i32.into()) },
&[llvm_pi8.into()],
&once(fmt.into()).chain(args).map(BasicValueEnum::into).collect_vec(), &once(fmt.into()).chain(args).map(BasicValueEnum::into).collect_vec(),
"", true,
) None,
.unwrap(); None,
);
}; };
let llvm_i32 = ctx.ctx.i32_type(); let llvm_i32 = ctx.ctx.i32_type();
let llvm_i64 = ctx.ctx.i64_type(); let llvm_i64 = ctx.ctx.i64_type();
let llvm_usize = generator.get_size_type(ctx.ctx); let llvm_usize = ctx.get_size_type();
let suffix = suffix.unwrap_or_default(); let suffix = suffix.unwrap_or_default();
@ -1343,7 +1365,7 @@ fn polymorphic_print<'ctx>(
let (dtype, _) = unpack_ndarray_var_tys(&mut ctx.unifier, ty); let (dtype, _) = unpack_ndarray_var_tys(&mut ctx.unifier, ty);
let ndarray = NDArrayType::from_unifier_type(generator, ctx, ty) let ndarray = NDArrayType::from_unifier_type(generator, ctx, ty)
.map_value(value.into_pointer_value(), None); .map_pointer_value(value.into_pointer_value(), None);
let num_0 = llvm_usize.const_zero(); let num_0 = llvm_usize.const_zero();
@ -1391,7 +1413,7 @@ fn polymorphic_print<'ctx>(
fmt.push_str("range("); fmt.push_str("range(");
flush(ctx, generator, &mut fmt, &mut args); flush(ctx, generator, &mut fmt, &mut args);
let val = RangeValue::from_pointer_value(value.into_pointer_value(), None); let val = RangeType::new(ctx).map_pointer_value(value.into_pointer_value(), None);
let (start, stop, step) = destructure_range(ctx, val); let (start, stop, step) = destructure_range(ctx, val);
@ -1505,7 +1527,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 +1544,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

@ -19,6 +19,7 @@ use std::{
sync::Arc, sync::Arc,
}; };
use indexmap::IndexMap;
use itertools::Itertools; use itertools::Itertools;
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
use pyo3::{ use pyo3::{
@ -43,7 +44,7 @@ use nac3core::{
OptimizationLevel, OptimizationLevel,
}, },
nac3parser::{ nac3parser::{
ast::{Constant, ExprKind, Located, Stmt, StmtKind, StrRef}, ast::{self, Constant, ExprKind, Located, Stmt, StmtKind, StrRef},
parser::parse_program, parser::parse_program,
}, },
symbol_resolver::SymbolResolver, symbol_resolver::SymbolResolver,
@ -78,14 +79,62 @@ enum Isa {
} }
impl Isa { impl Isa {
/// Returns the number of bits in `size_t` for the [`Isa`]. /// Returns the [`TargetTriple`] used for compiling to this ISA.
fn get_size_type(self) -> u32 { pub fn get_llvm_target_triple(self) -> TargetTriple {
if self == Isa::Host { match self {
64u32 Isa::Host => TargetMachine::get_default_triple(),
} else { Isa::RiscV32G | Isa::RiscV32IMA => TargetTriple::create("riscv32-unknown-linux"),
32u32 Isa::CortexA9 => TargetTriple::create("armv7-unknown-linux-gnueabihf"),
} }
} }
/// Returns the [`String`] representing the target CPU used for compiling to this ISA.
pub fn get_llvm_target_cpu(self) -> String {
match self {
Isa::Host => TargetMachine::get_host_cpu_name().to_string(),
Isa::RiscV32G | Isa::RiscV32IMA => "generic-rv32".to_string(),
Isa::CortexA9 => "cortex-a9".to_string(),
}
}
/// Returns the [`String`] representing the target features used for compiling to this ISA.
pub fn get_llvm_target_features(self) -> String {
match self {
Isa::Host => TargetMachine::get_host_cpu_features().to_string(),
Isa::RiscV32G => "+a,+m,+f,+d".to_string(),
Isa::RiscV32IMA => "+a,+m".to_string(),
Isa::CortexA9 => "+dsp,+fp16,+neon,+vfp3,+long-calls".to_string(),
}
}
/// Returns an instance of [`CodeGenTargetMachineOptions`] representing the target machine
/// options used for compiling to this ISA.
pub fn get_llvm_target_options(self) -> CodeGenTargetMachineOptions {
CodeGenTargetMachineOptions {
triple: self.get_llvm_target_triple().as_str().to_string_lossy().into_owned(),
cpu: self.get_llvm_target_cpu(),
features: self.get_llvm_target_features(),
reloc_mode: RelocMode::PIC,
..CodeGenTargetMachineOptions::from_host()
}
}
/// Returns an instance of [`TargetMachine`] used in compiling and linking of a program of this
/// ISA.
pub fn create_llvm_target_machine(self, opt_level: OptimizationLevel) -> TargetMachine {
self.get_llvm_target_options()
.create_target_machine(opt_level)
.expect("couldn't create target machine")
}
/// Returns the number of bits in `size_t` for this ISA.
fn get_size_type(self, ctx: &Context) -> u32 {
ctx.ptr_sized_int_type(
&self.create_llvm_target_machine(OptimizationLevel::Default).get_target_data(),
None,
)
.get_bit_width()
}
} }
#[derive(Clone)] #[derive(Clone)]
@ -111,6 +160,14 @@ pub struct PrimitivePythonId {
generic_alias: (u64, u64), generic_alias: (u64, u64),
virtual_id: u64, virtual_id: u64,
option: u64, option: u64,
module: u64,
}
#[derive(Clone, Default)]
pub struct SpecialPythonId {
parallel: u64,
legacy_parallel: u64,
sequential: u64,
} }
type TopLevelComponent = (Stmt, String, PyObject); type TopLevelComponent = (Stmt, String, PyObject);
@ -130,6 +187,7 @@ struct Nac3 {
string_store: Arc<RwLock<HashMap<String, i32>>>, string_store: Arc<RwLock<HashMap<String, i32>>>,
exception_ids: Arc<RwLock<HashMap<usize, usize>>>, exception_ids: Arc<RwLock<HashMap<usize, usize>>>,
deferred_eval_store: DeferredEvaluationStore, deferred_eval_store: DeferredEvaluationStore,
special_ids: SpecialPythonId,
/// LLVM-related options for code generation. /// LLVM-related options for code generation.
llvm_options: CodeGenLLVMOptions, llvm_options: CodeGenLLVMOptions,
} }
@ -228,6 +286,10 @@ impl Nac3 {
} }
}) })
} }
// Allow global variable declaration with `Kernel` type annotation
StmtKind::AnnAssign { ref annotation, .. } => {
matches!(&annotation.node, ExprKind::Subscript { value, .. } if matches!(&value.node, ExprKind::Name {id, ..} if id == &"Kernel".into()))
}
_ => false, _ => false,
}; };
@ -330,7 +392,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, fun, &args, generator)?; gen_core_log(ctx, obj.as_ref(), fun, &args, generator)?;
Ok(None) Ok(None)
}))), }))),
@ -360,7 +422,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, fun, &args, generator)?; gen_rtio_log(ctx, obj.as_ref(), fun, &args, generator)?;
Ok(None) Ok(None)
}))), }))),
@ -378,7 +440,7 @@ impl Nac3 {
py: Python, py: Python,
link_fn: &dyn Fn(&Module) -> PyResult<T>, link_fn: &dyn Fn(&Module) -> PyResult<T>,
) -> PyResult<T> { ) -> PyResult<T> {
let size_t = self.isa.get_size_type(); let size_t = self.isa.get_size_type(&Context::create());
let (mut composer, mut builtins_def, mut builtins_ty) = TopLevelComposer::new( let (mut composer, mut builtins_def, mut builtins_ty) = TopLevelComposer::new(
self.builtins.clone(), self.builtins.clone(),
Self::get_lateinit_builtins(), Self::get_lateinit_builtins(),
@ -421,12 +483,14 @@ impl Nac3 {
]; ];
add_exceptions(&mut composer, &mut builtins_def, &mut builtins_ty, &exception_names); add_exceptions(&mut composer, &mut builtins_def, &mut builtins_ty, &exception_names);
// Stores a mapping from module id to attributes
let mut module_to_resolver_cache: HashMap<u64, _> = HashMap::new(); let mut module_to_resolver_cache: HashMap<u64, _> = HashMap::new();
let mut rpc_ids = vec![]; let mut rpc_ids = vec![];
for (stmt, path, module) in &self.top_levels { for (stmt, path, module) in &self.top_levels {
let py_module: &PyAny = module.extract(py)?; let py_module: &PyAny = module.extract(py)?;
let module_id: u64 = id_fn.call1((py_module,))?.extract()?; let module_id: u64 = id_fn.call1((py_module,))?.extract()?;
let module_name: String = py_module.getattr("__name__")?.extract()?;
let helper = helper.clone(); let helper = helper.clone();
let class_obj; let class_obj;
if let StmtKind::ClassDef { name, .. } = &stmt.node { if let StmtKind::ClassDef { name, .. } = &stmt.node {
@ -441,7 +505,7 @@ impl Nac3 {
} else { } else {
class_obj = None; class_obj = None;
} }
let (name_to_pyid, resolver) = let (name_to_pyid, resolver, _, _) =
module_to_resolver_cache.get(&module_id).cloned().unwrap_or_else(|| { module_to_resolver_cache.get(&module_id).cloned().unwrap_or_else(|| {
let mut name_to_pyid: HashMap<StrRef, u64> = HashMap::new(); let mut name_to_pyid: HashMap<StrRef, u64> = HashMap::new();
let members: &PyDict = let members: &PyDict =
@ -470,9 +534,17 @@ impl Nac3 {
}))) })))
as Arc<dyn SymbolResolver + Send + Sync>; as Arc<dyn SymbolResolver + Send + Sync>;
let name_to_pyid = Rc::new(name_to_pyid); let name_to_pyid = Rc::new(name_to_pyid);
module_to_resolver_cache let module_location = ast::Location::new(1, 1, stmt.location.file);
.insert(module_id, (name_to_pyid.clone(), resolver.clone())); module_to_resolver_cache.insert(
(name_to_pyid, resolver) module_id,
(
name_to_pyid.clone(),
resolver.clone(),
module_name.clone(),
Some(module_location),
),
);
(name_to_pyid, resolver, module_name, Some(module_location))
}); });
let (name, def_id, ty) = composer let (name, def_id, ty) = composer
@ -546,6 +618,24 @@ impl Nac3 {
} }
} }
// Adding top level module definitions
for (module_id, (module_name_to_pyid, module_resolver, module_name, module_location)) in
module_to_resolver_cache
{
let def_id = composer
.register_top_level_module(
&module_name,
&module_name_to_pyid,
module_resolver,
module_location,
)
.map_err(|e| {
CompileError::new_err(format!("compilation failed\n----------\n{e}"))
})?;
self.pyid_to_def.write().insert(module_id, def_id);
}
let id_fun = PyModule::import(py, "builtins")?.getattr("id")?; let id_fun = PyModule::import(py, "builtins")?.getattr("id")?;
let mut name_to_pyid: HashMap<StrRef, u64> = HashMap::new(); let mut name_to_pyid: HashMap<StrRef, u64> = HashMap::new();
let module = PyModule::new(py, "tmp")?; let module = PyModule::new(py, "tmp")?;
@ -665,6 +755,9 @@ impl Nac3 {
"Unsupported @rpc annotation on global variable", "Unsupported @rpc annotation on global variable",
))) )))
} }
TopLevelDef::Module { .. } => {
unreachable!("Type module cannot be decorated with @rpc")
}
} }
} }
} }
@ -703,14 +796,19 @@ impl Nac3 {
let buffer = buffer.as_slice().into(); let buffer = buffer.as_slice().into();
membuffer.lock().push(buffer); membuffer.lock().push(buffer);
}))); })));
let size_t = context
.ptr_sized_int_type(&self.get_llvm_target_machine().get_target_data(), None)
.get_bit_width();
let num_threads = if is_multithreaded() { 4 } else { 1 }; let num_threads = if is_multithreaded() { 4 } else { 1 };
let thread_names: Vec<String> = (0..num_threads).map(|_| "main".to_string()).collect(); let thread_names: Vec<String> = (0..num_threads).map(|_| "main".to_string()).collect();
let threads: Vec<_> = thread_names let threads: Vec<_> = thread_names
.iter() .iter()
.map(|s| Box::new(ArtiqCodeGenerator::new(s.to_string(), size_t, self.time_fns))) .map(|s| {
Box::new(ArtiqCodeGenerator::with_target_machine(
s.to_string(),
&context,
&self.get_llvm_target_machine(),
self.time_fns,
self.special_ids.clone(),
))
})
.collect(); .collect();
let membuffer = membuffers.clone(); let membuffer = membuffers.clone();
@ -719,8 +817,14 @@ impl Nac3 {
let (registry, handles) = let (registry, handles) =
WorkerRegistry::create_workers(threads, top_level.clone(), &self.llvm_options, &f); WorkerRegistry::create_workers(threads, top_level.clone(), &self.llvm_options, &f);
let mut generator = ArtiqCodeGenerator::new("main".to_string(), size_t, self.time_fns);
let context = Context::create(); let context = Context::create();
let mut generator = ArtiqCodeGenerator::with_target_machine(
"main".to_string(),
&context,
&self.get_llvm_target_machine(),
self.time_fns,
self.special_ids.clone(),
);
let module = context.create_module("main"); let module = context.create_module("main");
let target_machine = self.llvm_options.create_target_machine().unwrap(); let target_machine = self.llvm_options.create_target_machine().unwrap();
module.set_data_layout(&target_machine.get_target_data().get_data_layout()); module.set_data_layout(&target_machine.get_target_data().get_data_layout());
@ -839,52 +943,10 @@ impl Nac3 {
link_fn(&main) link_fn(&main)
} }
/// Returns the [`TargetTriple`] used for compiling to [isa].
fn get_llvm_target_triple(isa: Isa) -> TargetTriple {
match isa {
Isa::Host => TargetMachine::get_default_triple(),
Isa::RiscV32G | Isa::RiscV32IMA => TargetTriple::create("riscv32-unknown-linux"),
Isa::CortexA9 => TargetTriple::create("armv7-unknown-linux-gnueabihf"),
}
}
/// Returns the [`String`] representing the target CPU used for compiling to [isa].
fn get_llvm_target_cpu(isa: Isa) -> String {
match isa {
Isa::Host => TargetMachine::get_host_cpu_name().to_string(),
Isa::RiscV32G | Isa::RiscV32IMA => "generic-rv32".to_string(),
Isa::CortexA9 => "cortex-a9".to_string(),
}
}
/// Returns the [`String`] representing the target features used for compiling to [isa].
fn get_llvm_target_features(isa: Isa) -> String {
match isa {
Isa::Host => TargetMachine::get_host_cpu_features().to_string(),
Isa::RiscV32G => "+a,+m,+f,+d".to_string(),
Isa::RiscV32IMA => "+a,+m".to_string(),
Isa::CortexA9 => "+dsp,+fp16,+neon,+vfp3,+long-calls".to_string(),
}
}
/// Returns an instance of [`CodeGenTargetMachineOptions`] representing the target machine
/// options used for compiling to [isa].
fn get_llvm_target_options(isa: Isa) -> CodeGenTargetMachineOptions {
CodeGenTargetMachineOptions {
triple: Nac3::get_llvm_target_triple(isa).as_str().to_string_lossy().into_owned(),
cpu: Nac3::get_llvm_target_cpu(isa),
features: Nac3::get_llvm_target_features(isa),
reloc_mode: RelocMode::PIC,
..CodeGenTargetMachineOptions::from_host()
}
}
/// Returns an instance of [`TargetMachine`] used in compiling and linking of a program to the /// Returns an instance of [`TargetMachine`] used in compiling and linking of a program to the
/// target [isa]. /// target [ISA][isa].
fn get_llvm_target_machine(&self) -> TargetMachine { fn get_llvm_target_machine(&self) -> TargetMachine {
Nac3::get_llvm_target_options(self.isa) self.isa.create_llvm_target_machine(self.llvm_options.opt_level)
.create_target_machine(self.llvm_options.opt_level)
.expect("couldn't create target machine")
} }
} }
@ -992,7 +1054,8 @@ impl Nac3 {
Isa::RiscV32IMA => &timeline::NOW_PINNING_TIME_FNS, Isa::RiscV32IMA => &timeline::NOW_PINNING_TIME_FNS,
Isa::CortexA9 | Isa::Host => &timeline::EXTERN_TIME_FNS, Isa::CortexA9 | Isa::Host => &timeline::EXTERN_TIME_FNS,
}; };
let (primitive, _) = TopLevelComposer::make_primitives(isa.get_size_type()); let (primitive, _) =
TopLevelComposer::make_primitives(isa.get_size_type(&Context::create()));
let builtins = vec![ let builtins = vec![
( (
"now_mu".into(), "now_mu".into(),
@ -1080,6 +1143,7 @@ impl Nac3 {
tuple: get_attr_id(builtins_mod, "tuple"), tuple: get_attr_id(builtins_mod, "tuple"),
exception: get_attr_id(builtins_mod, "Exception"), exception: get_attr_id(builtins_mod, "Exception"),
option: get_id(artiq_builtins.get_item("Option").ok().flatten().unwrap()), option: get_id(artiq_builtins.get_item("Option").ok().flatten().unwrap()),
module: get_attr_id(types_mod, "ModuleType"),
}; };
let working_directory = tempfile::Builder::new().prefix("nac3-").tempdir().unwrap(); let working_directory = tempfile::Builder::new().prefix("nac3-").tempdir().unwrap();
@ -1139,9 +1203,10 @@ impl Nac3 {
string_store: Arc::new(string_store.into()), string_store: Arc::new(string_store.into()),
exception_ids: Arc::default(), exception_ids: Arc::default(),
deferred_eval_store: DeferredEvaluationStore::new(), deferred_eval_store: DeferredEvaluationStore::new(),
special_ids: SpecialPythonId::default(),
llvm_options: CodeGenLLVMOptions { llvm_options: CodeGenLLVMOptions {
opt_level: OptimizationLevel::Default, opt_level: OptimizationLevel::Default,
target: Nac3::get_llvm_target_options(isa), target: isa.get_llvm_target_options(),
}, },
}) })
} }
@ -1150,11 +1215,12 @@ impl Nac3 {
&mut self, &mut self,
functions: &PySet, functions: &PySet,
classes: &PySet, classes: &PySet,
special_ids: &PyDict,
content_modules: &PySet, content_modules: &PySet,
) -> PyResult<()> { ) -> PyResult<()> {
let (modules, class_ids) = let (modules, class_ids) =
Python::with_gil(|py| -> PyResult<(HashMap<u64, PyObject>, HashSet<u64>)> { Python::with_gil(|py| -> PyResult<(IndexMap<u64, PyObject>, HashSet<u64>)> {
let mut modules: HashMap<u64, PyObject> = HashMap::new(); let mut modules: IndexMap<u64, PyObject> = IndexMap::new();
let mut class_ids: HashSet<u64> = HashSet::new(); let mut class_ids: HashSet<u64> = HashSet::new();
let id_fn = PyModule::import(py, "builtins")?.getattr("id")?; let id_fn = PyModule::import(py, "builtins")?.getattr("id")?;
@ -1183,6 +1249,25 @@ impl Nac3 {
for module in modules.into_values() { for module in modules.into_values() {
self.register_module(&module, &class_ids)?; self.register_module(&module, &class_ids)?;
} }
self.special_ids = SpecialPythonId {
parallel: special_ids.get_item("parallel").ok().flatten().unwrap().extract().unwrap(),
legacy_parallel: special_ids
.get_item("legacy_parallel")
.ok()
.flatten()
.unwrap()
.extract()
.unwrap(),
sequential: special_ids
.get_item("sequential")
.ok()
.flatten()
.unwrap()
.extract()
.unwrap(),
};
Ok(()) Ok(())
} }

View File

@ -16,14 +16,14 @@ use pyo3::{
use super::PrimitivePythonId; use super::PrimitivePythonId;
use nac3core::{ use nac3core::{
codegen::{ codegen::{
types::{ndarray::NDArrayType, ProxyType}, types::{ndarray::NDArrayType, structure::StructProxyType, ProxyType},
values::ndarray::make_contiguous_strides, values::ndarray::make_contiguous_strides,
CodeGenContext, CodeGenerator, CodeGenContext, CodeGenerator,
}, },
inkwell::{ inkwell::{
module::Linkage, module::Linkage,
types::{BasicType, BasicTypeEnum}, types::{BasicType, BasicTypeEnum},
values::BasicValueEnum, values::{BasicValue, BasicValueEnum},
AddressSpace, AddressSpace,
}, },
nac3parser::ast::{self, StrRef}, nac3parser::ast::{self, StrRef},
@ -674,6 +674,48 @@ impl InnerResolver {
}) })
}); });
// check if obj is module
if self.helper.id_fn.call1(py, (ty.clone(),))?.extract::<u64>(py)?
== self.primitive_ids.module
&& self.pyid_to_def.read().contains_key(&py_obj_id)
{
let def_id = self.pyid_to_def.read()[&py_obj_id];
let def = defs[def_id.0].read();
let TopLevelDef::Module { name: module_name, module_id, attributes, methods, .. } =
&*def
else {
unreachable!("must be a module here");
};
// Construct the module return type
let mut module_attributes = HashMap::new();
for (name, _) in attributes {
let attribute_obj = obj.getattr(name.to_string().as_str())?;
let attribute_ty =
self.get_obj_type(py, attribute_obj, unifier, defs, primitives)?;
if let Ok(attribute_ty) = attribute_ty {
module_attributes.insert(*name, (attribute_ty, false));
} else {
return Ok(Err(format!("Unable to resolve {module_name}.{name}")));
}
}
for name in methods.keys() {
let method_obj = obj.getattr(name.to_string().as_str())?;
let method_ty = self.get_obj_type(py, method_obj, unifier, defs, primitives)?;
if let Ok(method_ty) = method_ty {
module_attributes.insert(*name, (method_ty, true));
} else {
return Ok(Err(format!("Unable to resolve {module_name}.{name}")));
}
}
let module_ty =
TypeEnum::TModule { module_id: *module_id, attributes: module_attributes };
let ty = unifier.add_ty(module_ty);
return Ok(Ok(ty));
}
if let Some(ty) = constructor_ty { if let Some(ty) = constructor_ty {
self.pyid_to_type.write().insert(py_obj_id, ty); self.pyid_to_type.write().insert(py_obj_id, ty);
return Ok(Ok(ty)); return Ok(Ok(ty));
@ -931,10 +973,13 @@ 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) {
obj.extract::<bool>().map_or_else( if obj.extract::<bool>().is_ok()
|_| Ok(Err(format!("{obj} is not in the range of bool"))), || obj.call_method("__bool__", (), None)?.extract::<bool>().is_ok()
|_| 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"))),
@ -974,10 +1019,14 @@ 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 || ty_id == self.primitive_ids.np_bool_ { } else if ty_id == self.primitive_ids.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()));
@ -1000,7 +1049,7 @@ impl InnerResolver {
} }
_ => unreachable!("must be list"), _ => unreachable!("must be list"),
}; };
let size_t = generator.get_size_type(ctx.ctx); let size_t = ctx.get_size_type();
let ty = if len == 0 let ty = if len == 0
&& matches!(&*ctx.unifier.get_ty_immutable(elem_ty), TypeEnum::TVar { .. }) && matches!(&*ctx.unifier.get_ty_immutable(elem_ty), TypeEnum::TVar { .. })
{ {
@ -1089,7 +1138,7 @@ impl InnerResolver {
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());
let llvm_usize = generator.get_size_type(ctx.ctx); let llvm_usize = ctx.get_size_type();
let llvm_ndarray = NDArrayType::from_unifier_type(generator, ctx, ndarray_ty); let llvm_ndarray = NDArrayType::from_unifier_type(generator, ctx, ndarray_ty);
let dtype = llvm_ndarray.element_type(); let dtype = llvm_ndarray.element_type();
@ -1097,7 +1146,7 @@ impl InnerResolver {
if self.global_value_ids.read().contains_key(&id) { if self.global_value_ids.read().contains_key(&id) {
let global = ctx.module.get_global(&id_str).unwrap_or_else(|| { let global = ctx.module.get_global(&id_str).unwrap_or_else(|| {
ctx.module.add_global( ctx.module.add_global(
llvm_ndarray.as_base_type().get_element_type().into_struct_type(), llvm_ndarray.as_abi_type().get_element_type().into_struct_type(),
Some(AddressSpace::default()), Some(AddressSpace::default()),
&id_str, &id_str,
) )
@ -1107,7 +1156,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().unwrap(); let ndims = llvm_ndarray.ndims();
// 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()?;
@ -1124,7 +1173,10 @@ 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 = value.into_int_value(); let value = ctx
.builder
.build_int_z_extend(value.into_int_value(), llvm_usize, "")
.unwrap();
Ok(value) Ok(value)
}) })
.collect::<Result<Vec<_>, PyErr>>()?; .collect::<Result<Vec<_>, PyErr>>()?;
@ -1203,8 +1255,16 @@ 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(); //
let itemsize = itemsize.get_zero_extended_constant().unwrap(); // NOTE: dtype.size_of() may return a non-constant, where `TargetData::get_store_size`
// 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);
@ -1214,7 +1274,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_i8.array_type(ndims as u32), llvm_usize.array_type(ndims as u32),
Some(AddressSpace::default()), Some(AddressSpace::default()),
&format!("${id_str}.strides"), &format!("${id_str}.strides"),
); );
@ -1230,15 +1290,32 @@ 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.get_struct_type().const_named_struct(&[
.as_base_type()
.get_element_type()
.into_struct_type()
.const_named_struct(&[
ndarray_itemsize.into(), ndarray_itemsize.into(),
ndarray_ndims.into(), ndarray_ndims.into(),
ndarray_shape.into(), ndarray_shape.into(),
@ -1247,7 +1324,7 @@ impl InnerResolver {
]); ]);
let ndarray_global = ctx.module.add_global( let ndarray_global = ctx.module.add_global(
llvm_ndarray.as_base_type().get_element_type().into_struct_type(), llvm_ndarray.as_abi_type().get_element_type().into_struct_type(),
Some(AddressSpace::default()), Some(AddressSpace::default()),
&id_str, &id_str,
); );
@ -1334,6 +1411,77 @@ impl InnerResolver {
None => Ok(None), None => Ok(None),
} }
} }
} else if ty_id == self.primitive_ids.module {
let id_str = id.to_string();
if let Some(global) = ctx.module.get_global(&id_str) {
return Ok(Some(global.as_pointer_value().into()));
}
let top_level_defs = ctx.top_level.definitions.read();
let ty = self
.get_obj_type(py, obj, &mut ctx.unifier, &top_level_defs, &ctx.primitives)?
.unwrap();
let ty = ctx
.get_llvm_type(generator, ty)
.into_pointer_type()
.get_element_type()
.into_struct_type();
{
if self.global_value_ids.read().contains_key(&id) {
let global = ctx.module.get_global(&id_str).unwrap_or_else(|| {
ctx.module.add_global(ty, Some(AddressSpace::default()), &id_str)
});
return Ok(Some(global.as_pointer_value().into()));
}
self.global_value_ids.write().insert(id, obj.into());
}
let fields = {
let definition =
top_level_defs.get(self.pyid_to_def.read().get(&id).unwrap().0).unwrap().read();
let TopLevelDef::Module { attributes, .. } = &*definition else { unreachable!() };
attributes
.iter()
.filter_map(|f| {
let definition = top_level_defs.get(f.1 .0).unwrap().read();
if let TopLevelDef::Variable { ty, .. } = &*definition {
Some((f.0, *ty))
} else {
None
}
})
.collect_vec()
};
let values: Result<Option<Vec<_>>, _> = fields
.iter()
.map(|(name, ty)| {
self.get_obj_value(
py,
obj.getattr(name.to_string().as_str())?,
ctx,
generator,
*ty,
)
.map_err(|e| {
super::CompileError::new_err(format!("Error getting field {name}: {e}"))
})
})
.collect();
let values = values?;
if let Some(values) = values {
let val = ty.const_named_struct(&values);
let global = ctx.module.get_global(&id_str).unwrap_or_else(|| {
ctx.module.add_global(ty, Some(AddressSpace::default()), &id_str)
});
global.set_initializer(&val);
Ok(Some(global.as_pointer_value().into()))
} else {
Ok(None)
}
} else { } else {
let id_str = id.to_string(); let id_str = id.to_string();
@ -1413,9 +1561,12 @@ 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 || ty_id == self.primitive_ids.np_bool_ { } else if ty_id == self.primitive_ids.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))
@ -1513,9 +1664,50 @@ 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, generator: &mut dyn CodeGenerator,
) -> Option<ValueEnum<'ctx>> { ) -> Option<ValueEnum<'ctx>> {
if let Some(def_id) = self.0.id_to_def.read().get(&id) {
let top_levels = ctx.top_level.definitions.read();
if matches!(&*top_levels[def_id.0].read(), TopLevelDef::Variable { .. }) {
let module_val = &self.0.module;
let ret = Python::with_gil(|py| -> PyResult<Result<BasicValueEnum, String>> {
let module_val = module_val.as_ref(py);
let ty = self.0.get_obj_type(
py,
module_val,
&mut ctx.unifier,
&top_levels,
&ctx.primitives,
)?;
if let Err(ty) = ty {
return Ok(Err(ty));
}
let ty = ty.unwrap();
let obj = self.0.get_obj_value(py, module_val, ctx, generator, ty)?.unwrap();
let (idx, _) = ctx.get_attr_index(ty, id);
let ret = unsafe {
ctx.builder.build_gep(
obj.into_pointer_value(),
&[
ctx.ctx.i32_type().const_zero(),
ctx.ctx.i32_type().const_int(idx as u64, false),
],
id.to_string().as_str(),
)
}
.unwrap();
Ok(Ok(ret.as_basic_value_enum()))
})
.unwrap();
if ret.is_err() {
return None;
}
return Some(ret.unwrap().into());
}
}
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(&id).cloned()

View File

@ -1,11 +1,6 @@
use itertools::Either;
use nac3core::{ use nac3core::{
codegen::CodeGenContext, codegen::{expr::infer_and_call_function, CodeGenContext},
inkwell::{ inkwell::{values::BasicValueEnum, AddressSpace, AtomicOrdering},
values::{BasicValueEnum, CallSiteValue},
AddressSpace, AtomicOrdering,
},
}; };
/// Functions for manipulating the timeline. /// Functions for manipulating the timeline.
@ -288,36 +283,27 @@ pub struct ExternTimeFns {}
impl TimeFns for ExternTimeFns { impl TimeFns for ExternTimeFns {
fn emit_now_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>) -> BasicValueEnum<'ctx> { fn emit_now_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>) -> BasicValueEnum<'ctx> {
let now_mu = ctx.module.get_function("now_mu").unwrap_or_else(|| { infer_and_call_function(
ctx.module.add_function("now_mu", ctx.ctx.i64_type().fn_type(&[], false), None) ctx,
}); "now_mu",
ctx.builder Some(ctx.ctx.i64_type().into()),
.build_call(now_mu, &[], "now_mu") &[],
.map(CallSiteValue::try_as_basic_value) Some("now_mu"),
.map(Either::unwrap_left) None,
)
.unwrap() .unwrap()
} }
fn emit_at_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>, t: BasicValueEnum<'ctx>) { fn emit_at_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>, t: BasicValueEnum<'ctx>) {
let at_mu = ctx.module.get_function("at_mu").unwrap_or_else(|| { assert_eq!(t.get_type(), ctx.ctx.i64_type().into());
ctx.module.add_function(
"at_mu", infer_and_call_function(ctx, "at_mu", None, &[t], Some("at_mu"), None);
ctx.ctx.void_type().fn_type(&[ctx.ctx.i64_type().into()], false),
None,
)
});
ctx.builder.build_call(at_mu, &[t.into()], "at_mu").unwrap();
} }
fn emit_delay_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>, dt: BasicValueEnum<'ctx>) { fn emit_delay_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>, dt: BasicValueEnum<'ctx>) {
let delay_mu = ctx.module.get_function("delay_mu").unwrap_or_else(|| { assert_eq!(dt.get_type(), ctx.ctx.i64_type().into());
ctx.module.add_function(
"delay_mu", infer_and_call_function(ctx, "delay_mu", None, &[dt], Some("delay_mu"), None);
ctx.ctx.void_type().fn_type(&[ctx.ctx.i64_type().into()], false),
None,
)
});
ctx.builder.build_call(delay_mu, &[dt.into()], "delay_mu").unwrap();
} }
} }

View File

@ -11,5 +11,5 @@ fold = []
[dependencies] [dependencies]
parking_lot = "0.12" parking_lot = "0.12"
string-interner = "0.17" string-interner = "0.18"
fxhash = "0.2" fxhash = "0.2"

View File

@ -10,11 +10,10 @@ derive = ["dep:nac3core_derive"]
no-escape-analysis = [] no-escape-analysis = []
[dependencies] [dependencies]
itertools = "0.13" itertools = "0.14"
crossbeam = "0.8" crossbeam = "0.8"
indexmap = "2.6" indexmap = "2.7"
parking_lot = "0.12" parking_lot = "0.12"
rayon = "1.10"
nac3core_derive = { path = "nac3core_derive", optional = true } nac3core_derive = { path = "nac3core_derive", optional = true }
nac3parser = { path = "../nac3parser" } nac3parser = { path = "../nac3parser" }
strum = "0.26" strum = "0.26"
@ -31,4 +30,4 @@ indoc = "2.0"
insta = "=1.11.0" insta = "=1.11.0"
[build-dependencies] [build-dependencies]
regex = "1.10" regex = "1.11"

View File

@ -1,10 +1,15 @@
#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,7 +21,5 @@ 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,6 +2,21 @@
#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,5 +1,7 @@
#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

@ -1,151 +0,0 @@
#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

@ -0,0 +1,132 @@
#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,8 +6,7 @@
#include "irrt/ndarray/def.hpp" #include "irrt/ndarray/def.hpp"
namespace { namespace {
namespace ndarray { namespace ndarray::basic {
namespace basic {
/** /**
* @brief Assert that `shape` does not contain negative dimensions. * @brief Assert that `shape` does not contain negative dimensions.
* *
@ -247,8 +246,7 @@ 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 basic } // namespace ndarray::basic
} // namespace ndarray
} // namespace } // namespace
extern "C" { extern "C" {

View File

@ -0,0 +1,165 @@
#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,8 +65,7 @@ struct NDIndex {
} // namespace } // namespace
namespace { namespace {
namespace ndarray { namespace ndarray::indexing {
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)
* *
@ -162,7 +161,8 @@ 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 = static_cast<uint8_t*>(dst_ndarray->data) + (SizeT)range.start * src_ndarray->strides[src_axis]; dst_ndarray->data =
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,8 +197,7 @@ 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 indexing } // namespace ndarray::indexing
} // namespace ndarray
} // namespace } // namespace
extern "C" { extern "C" {

View File

@ -0,0 +1,98 @@
#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

@ -0,0 +1,97 @@
#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

@ -0,0 +1,143 @@
#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

@ -0,0 +1,23 @@
#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

@ -56,6 +56,10 @@ pub enum ConcreteTypeEnum {
fields: HashMap<StrRef, (ConcreteType, bool)>, fields: HashMap<StrRef, (ConcreteType, bool)>,
params: IndexMap<TypeVarId, ConcreteType>, params: IndexMap<TypeVarId, ConcreteType>,
}, },
TModule {
module_id: DefinitionId,
methods: HashMap<StrRef, (ConcreteType, bool)>,
},
TVirtual { TVirtual {
ty: ConcreteType, ty: ConcreteType,
}, },
@ -205,6 +209,19 @@ impl ConcreteTypeStore {
}) })
.collect(), .collect(),
}, },
TypeEnum::TModule { module_id, attributes } => ConcreteTypeEnum::TModule {
module_id: *module_id,
methods: attributes
.iter()
.filter_map(|(name, ty)| match &*unifier.get_ty(ty.0) {
TypeEnum::TFunc(..) | TypeEnum::TObj { .. } => None,
_ => Some((
*name,
(self.from_unifier_type(unifier, primitives, ty.0, cache), ty.1),
)),
})
.collect(),
},
TypeEnum::TVirtual { ty } => ConcreteTypeEnum::TVirtual { TypeEnum::TVirtual { ty } => ConcreteTypeEnum::TVirtual {
ty: self.from_unifier_type(unifier, primitives, *ty, cache), ty: self.from_unifier_type(unifier, primitives, *ty, cache),
}, },
@ -284,6 +301,15 @@ impl ConcreteTypeStore {
TypeVar { id, ty } TypeVar { id, ty }
})), })),
}, },
ConcreteTypeEnum::TModule { module_id, methods } => TypeEnum::TModule {
module_id: *module_id,
attributes: methods
.iter()
.map(|(name, cty)| {
(*name, (self.to_unifier_type(unifier, primitives, cty.0, cache), cty.1))
})
.collect::<HashMap<_, _>>(),
},
ConcreteTypeEnum::TFunc { args, ret, vars } => TypeEnum::TFunc(FunSignature { ConcreteTypeEnum::TFunc { args, ret, vars } => TypeEnum::TFunc(FunSignature {
args: args args: args
.iter() .iter()

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,9 @@
use inkwell::{ use inkwell::{
attributes::{Attribute, AttributeLoc}, attributes::{Attribute, AttributeLoc},
values::{BasicValueEnum, CallSiteValue, FloatValue, IntValue}, values::{BasicValueEnum, FloatValue, IntValue},
}; };
use itertools::Either;
use super::CodeGenContext; use super::{expr::infer_and_call_function, CodeGenContext};
/// Macro to generate extern function /// Macro to generate extern function
/// Both function return type and function parameter type are `FloatValue` /// Both function return type and function parameter type are `FloatValue`
@ -37,8 +36,8 @@ macro_rules! generate_extern_fn {
($fn_name:ident, $extern_fn:literal $(,$args:ident)* $(,$attributes:literal)*) => { ($fn_name:ident, $extern_fn:literal $(,$args:ident)* $(,$attributes:literal)*) => {
#[doc = concat!("Invokes the [`", stringify!($extern_fn), "`](https://en.cppreference.com/w/c/numeric/math/", stringify!($llvm_name), ") function." )] #[doc = concat!("Invokes the [`", stringify!($extern_fn), "`](https://en.cppreference.com/w/c/numeric/math/", stringify!($llvm_name), ") function." )]
pub fn $fn_name<'ctx>( pub fn $fn_name<'ctx>(
ctx: &CodeGenContext<'ctx, '_> ctx: &CodeGenContext<'ctx, '_>,
$(,$args: FloatValue<'ctx>)*, $($args: FloatValue<'ctx>,)*
name: Option<&str>, name: Option<&str>,
) -> FloatValue<'ctx> { ) -> FloatValue<'ctx> {
const FN_NAME: &str = $extern_fn; const FN_NAME: &str = $extern_fn;
@ -46,23 +45,22 @@ macro_rules! generate_extern_fn {
let llvm_f64 = ctx.ctx.f64_type(); let llvm_f64 = ctx.ctx.f64_type();
$(debug_assert_eq!($args.get_type(), llvm_f64);)* $(debug_assert_eq!($args.get_type(), llvm_f64);)*
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| { infer_and_call_function(
let fn_type = llvm_f64.fn_type(&[$($args.get_type().into()),*], false); ctx,
let func = ctx.module.add_function(FN_NAME, fn_type, None); FN_NAME,
Some(llvm_f64.into()),
&[$($args.into()),*],
name,
Some(&|func| {
for attr in [$($attributes),*] { for attr in [$($attributes),*] {
func.add_attribute( func.add_attribute(
AttributeLoc::Function, AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0), ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0),
); );
} }
func })
}); )
.map(BasicValueEnum::into_float_value)
ctx.builder
.build_call(extern_fn, &[$($args.into()),*], name.unwrap_or_default())
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_float_value))
.map(Either::unwrap_left)
.unwrap() .unwrap()
} }
}; };
@ -112,24 +110,22 @@ pub fn call_ldexp<'ctx>(
debug_assert_eq!(arg.get_type(), llvm_f64); debug_assert_eq!(arg.get_type(), llvm_f64);
debug_assert_eq!(exp.get_type(), llvm_i32); debug_assert_eq!(exp.get_type(), llvm_i32);
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| { infer_and_call_function(
let fn_type = llvm_f64.fn_type(&[llvm_f64.into(), llvm_i32.into()], false); ctx,
let func = ctx.module.add_function(FN_NAME, fn_type, None); FN_NAME,
Some(llvm_f64.into()),
&[arg.into(), exp.into()],
name,
Some(&|func| {
for attr in ["mustprogress", "nofree", "nounwind", "willreturn"] { for attr in ["mustprogress", "nofree", "nounwind", "willreturn"] {
func.add_attribute( func.add_attribute(
AttributeLoc::Function, AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0), ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0),
); );
} }
}),
func )
}); .map(BasicValueEnum::into_float_value)
ctx.builder
.build_call(extern_fn, &[arg.into(), exp.into()], name.unwrap_or_default())
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_float_value))
.map(Either::unwrap_left)
.unwrap() .unwrap()
} }
@ -158,25 +154,27 @@ macro_rules! generate_linalg_extern_fn {
($fn_name:ident, $extern_fn:literal $(,$input_matrix:ident)*) => { ($fn_name:ident, $extern_fn:literal $(,$input_matrix:ident)*) => {
#[doc = concat!("Invokes the linalg `", stringify!($extern_fn), " function." )] #[doc = concat!("Invokes the linalg `", stringify!($extern_fn), " function." )]
pub fn $fn_name<'ctx>( pub fn $fn_name<'ctx>(
ctx: &mut CodeGenContext<'ctx, '_> ctx: &mut CodeGenContext<'ctx, '_>,
$(,$input_matrix: BasicValueEnum<'ctx>)*, $($input_matrix: BasicValueEnum<'ctx>,)*
name: Option<&str>, name: Option<&str>,
){ ){
const FN_NAME: &str = $extern_fn; const FN_NAME: &str = $extern_fn;
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
let fn_type = ctx.ctx.void_type().fn_type(&[$($input_matrix.get_type().into()),*], false);
let func = ctx.module.add_function(FN_NAME, fn_type, None); infer_and_call_function(
ctx,
FN_NAME,
None,
&[$($input_matrix.into(),)*],
name,
Some(&|func| {
for attr in ["mustprogress", "nofree", "nounwind", "willreturn", "writeonly"] { for attr in ["mustprogress", "nofree", "nounwind", "willreturn", "writeonly"] {
func.add_attribute( func.add_attribute(
AttributeLoc::Function, AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0), ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0),
); );
} }
func }),
}); );
ctx.builder.build_call(extern_fn, &[$($input_matrix.into(),)*], name.unwrap_or_default()).unwrap();
} }
}; };
} }

View File

@ -1,12 +1,13 @@
use inkwell::{ use inkwell::{
context::Context, context::Context,
targets::TargetMachine,
types::{BasicTypeEnum, IntType}, types::{BasicTypeEnum, IntType},
values::{BasicValueEnum, IntValue, PointerValue}, values::{BasicValueEnum, IntValue, PointerValue},
}; };
use nac3parser::ast::{Expr, Stmt, StrRef}; use nac3parser::ast::{Expr, Stmt, StrRef};
use super::{bool_to_i1, bool_to_i8, expr::*, stmt::*, values::ArraySliceValue, CodeGenContext}; use super::{bool_to_int_type, expr::*, stmt::*, values::ArraySliceValue, CodeGenContext};
use crate::{ use crate::{
symbol_resolver::ValueEnum, symbol_resolver::ValueEnum,
toplevel::{DefinitionId, TopLevelDef}, toplevel::{DefinitionId, TopLevelDef},
@ -17,6 +18,10 @@ 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.
///
/// Prefer using [`CodeGenContext::get_size_type`] if [`CodeGenContext`] is available, as it is
/// equivalent to this function in a more concise syntax.
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.
@ -243,22 +248,32 @@ pub trait CodeGenerator {
gen_block(self, ctx, stmts) gen_block(self, ctx, stmts)
} }
/// See [`bool_to_i1`]. /// Converts the value of a boolean-like value `bool_value` into an `i1`.
fn bool_to_i1<'ctx>( fn bool_to_i1<'ctx>(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
bool_value: IntValue<'ctx>, bool_value: IntValue<'ctx>,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
bool_to_i1(&ctx.builder, bool_value) self.bool_to_int_type(ctx, bool_value, ctx.ctx.bool_type())
} }
/// See [`bool_to_i8`]. /// Converts the value of a boolean-like value `bool_value` into an `i8`.
fn bool_to_i8<'ctx>( fn bool_to_i8<'ctx>(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
bool_value: IntValue<'ctx>, bool_value: IntValue<'ctx>,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
bool_to_i8(&ctx.builder, ctx.ctx, bool_value) self.bool_to_int_type(ctx, bool_value, ctx.ctx.i8_type())
}
/// See [`bool_to_int_type`].
fn bool_to_int_type<'ctx>(
&self,
ctx: &CodeGenContext<'ctx, '_>,
bool_value: IntValue<'ctx>,
ty: IntType<'ctx>,
) -> IntValue<'ctx> {
bool_to_int_type(&ctx.builder, bool_value, ty)
} }
} }
@ -269,19 +284,27 @@ pub struct DefaultCodeGenerator {
impl DefaultCodeGenerator { impl DefaultCodeGenerator {
#[must_use] #[must_use]
pub fn new(name: String, size_t: u32) -> DefaultCodeGenerator { pub fn new(name: String, size_t: IntType<'_>) -> DefaultCodeGenerator {
assert!(matches!(size_t, 32 | 64)); assert!(matches!(size_t.get_bit_width(), 32 | 64));
DefaultCodeGenerator { name, size_t } DefaultCodeGenerator { name, size_t: size_t.get_bit_width() }
}
#[must_use]
pub fn with_target_machine(
name: String,
ctx: &Context,
target_machine: &TargetMachine,
) -> DefaultCodeGenerator {
let llvm_usize = ctx.ptr_sized_int_type(&target_machine.get_target_data(), None);
Self::new(name, llvm_usize)
} }
} }
impl CodeGenerator for DefaultCodeGenerator { impl CodeGenerator for DefaultCodeGenerator {
/// Returns the name for this [`CodeGenerator`].
fn get_name(&self) -> &str { fn get_name(&self) -> &str {
&self.name &self.name
} }
/// Returns an LLVM integer type representing `size_t`.
fn get_size_type<'ctx>(&self, ctx: &'ctx Context) -> IntType<'ctx> { fn get_size_type<'ctx>(&self, ctx: &'ctx Context) -> IntType<'ctx> {
// it should be unsigned, but we don't really need unsigned and this could save us from // it should be unsigned, but we don't really need unsigned and this could save us from
// having to do a bit cast... // having to do a bit cast...

View File

@ -1,13 +1,14 @@
use inkwell::{ use inkwell::{
types::BasicTypeEnum, types::BasicTypeEnum,
values::{BasicValueEnum, CallSiteValue, IntValue}, values::{BasicValueEnum, IntValue},
AddressSpace, IntPredicate, AddressSpace, IntPredicate,
}; };
use itertools::Either;
use super::calculate_len_for_slice_range; use super::calculate_len_for_slice_range;
use crate::codegen::{ use crate::codegen::{
expr::infer_and_call_function,
macros::codegen_unreachable, macros::codegen_unreachable,
stmt::gen_if_callback,
values::{ArrayLikeValue, ListValue}, values::{ArrayLikeValue, ListValue},
CodeGenContext, CodeGenerator, CodeGenContext, CodeGenerator,
}; };
@ -24,42 +25,33 @@ 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 size_ty = generator.get_size_type(ctx.ctx); let llvm_usize = ctx.get_size_type();
let int8_ptr = ctx.ctx.i8_type().ptr_type(AddressSpace::default()); let llvm_pi8 = ctx.ctx.i8_type().ptr_type(AddressSpace::default());
let int32 = ctx.ctx.i32_type(); let llvm_i32 = ctx.ctx.i32_type();
let (fun_symbol, elem_ptr_type) = ("__nac3_list_slice_assign_var_size", int8_ptr);
let slice_assign_fun = {
let ty_vec = vec![
int32.into(), // dest start idx
int32.into(), // dest end idx
int32.into(), // dest step
elem_ptr_type.into(), // dest arr ptr
int32.into(), // dest arr len
int32.into(), // src start idx
int32.into(), // src end idx
int32.into(), // src step
elem_ptr_type.into(), // src arr ptr
int32.into(), // src arr len
int32.into(), // size
];
ctx.module.get_function(fun_symbol).unwrap_or_else(|| {
let fn_t = int32.fn_type(ty_vec.as_slice(), false);
ctx.module.add_function(fun_symbol, fn_t, None)
})
};
let zero = int32.const_zero(); assert_eq!(dest_idx.0.get_type(), llvm_i32);
let one = int32.const_int(1, false); 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 zero = llvm_i32.const_zero();
let one = llvm_i32.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 = ctx.builder.build_int_truncate_or_bit_cast(dest_len, int32, "srclen32").unwrap(); let dest_len =
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 = ctx.builder.build_int_truncate_or_bit_cast(src_len, int32, "srclen32").unwrap(); let src_len =
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
@ -117,7 +109,7 @@ pub fn list_slice_assignment<'ctx, G: CodeGenerator + ?Sized>(
); );
let new_len = { let new_len = {
let args = vec![ let args = [
dest_idx.0.into(), // dest start idx dest_idx.0.into(), // dest start idx
dest_idx.1.into(), // dest end idx dest_idx.1.into(), // dest end idx
dest_idx.2.into(), // dest step dest_idx.2.into(), // dest step
@ -136,27 +128,39 @@ 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, int32, "size").unwrap() ctx.builder.build_int_truncate_or_bit_cast(s, llvm_i32, "size").unwrap()
} }
.into(), .into(),
]; ];
ctx.builder infer_and_call_function(
.build_call(slice_assign_fun, args.as_slice(), "slice_assign") ctx,
.map(CallSiteValue::try_as_basic_value) fun_symbol,
.map(|v| v.map_left(BasicValueEnum::into_int_value)) Some(llvm_i32.into()),
.map(Either::unwrap_left) &args,
Some("slice_assign"),
None,
)
.map(BasicValueEnum::into_int_value)
.unwrap() .unwrap()
}; };
// update length // update length
let need_update = gen_if_callback(
ctx.builder.build_int_compare(IntPredicate::NE, new_len, dest_len, "need_update").unwrap(); generator,
let current = ctx.builder.get_insert_block().unwrap().get_parent().unwrap(); ctx,
let update_bb = ctx.ctx.append_basic_block(current, "update"); |_, ctx| {
let cont_bb = ctx.ctx.append_basic_block(current, "cont"); Ok(ctx
ctx.builder.build_conditional_branch(need_update, update_bb, cont_bb).unwrap(); .builder
ctx.builder.position_at_end(update_bb); .build_int_compare(IntPredicate::NE, new_len, dest_len, "need_update")
let new_len = ctx.builder.build_int_z_extend_or_bit_cast(new_len, size_ty, "new_len").unwrap(); .unwrap())
dest_arr.store_size(ctx, generator, new_len); },
ctx.builder.build_unconditional_branch(cont_bb).unwrap(); |_, ctx| {
ctx.builder.position_at_end(cont_bb); let new_len =
ctx.builder.build_int_z_extend_or_bit_cast(new_len, llvm_usize, "new_len").unwrap();
dest_arr.store_size(ctx, new_len);
Ok(())
},
|_, _| Ok(()),
)
.unwrap();
} }

View File

@ -1,10 +1,10 @@
use inkwell::{ use inkwell::{
values::{BasicValueEnum, CallSiteValue, FloatValue, IntValue}, values::{BasicValueEnum, FloatValue, IntValue},
IntPredicate, IntPredicate,
}; };
use itertools::Either;
use crate::codegen::{ use crate::codegen::{
expr::infer_and_call_function,
macros::codegen_unreachable, macros::codegen_unreachable,
{CodeGenContext, CodeGenerator}, {CodeGenContext, CodeGenerator},
}; };
@ -18,18 +18,16 @@ pub fn integer_power<'ctx, G: CodeGenerator + ?Sized>(
exp: IntValue<'ctx>, exp: IntValue<'ctx>,
signed: bool, signed: bool,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
let symbol = match (base.get_type().get_bit_width(), exp.get_type().get_bit_width(), signed) { let base_type = base.get_type();
let symbol = match (base_type.get_bit_width(), exp.get_type().get_bit_width(), signed) {
(32, 32, true) => "__nac3_int_exp_int32_t", (32, 32, true) => "__nac3_int_exp_int32_t",
(64, 64, true) => "__nac3_int_exp_int64_t", (64, 64, true) => "__nac3_int_exp_int64_t",
(32, 32, false) => "__nac3_int_exp_uint32_t", (32, 32, false) => "__nac3_int_exp_uint32_t",
(64, 64, false) => "__nac3_int_exp_uint64_t", (64, 64, false) => "__nac3_int_exp_uint64_t",
_ => codegen_unreachable!(ctx), _ => codegen_unreachable!(ctx),
}; };
let base_type = base.get_type();
let pow_fun = ctx.module.get_function(symbol).unwrap_or_else(|| {
let fn_type = base_type.fn_type(&[base_type.into(), base_type.into()], false);
ctx.module.add_function(symbol, fn_type, None)
});
// throw exception when exp < 0 // throw exception when exp < 0
let ge_zero = ctx let ge_zero = ctx
.builder .builder
@ -48,11 +46,16 @@ pub fn integer_power<'ctx, G: CodeGenerator + ?Sized>(
[None, None, None], [None, None, None],
ctx.current_loc, ctx.current_loc,
); );
ctx.builder
.build_call(pow_fun, &[base.into(), exp.into()], "call_int_pow") infer_and_call_function(
.map(CallSiteValue::try_as_basic_value) ctx,
.map(|v| v.map_left(BasicValueEnum::into_int_value)) symbol,
.map(Either::unwrap_left) Some(base_type.into()),
&[base.into(), exp.into()],
Some("call_int_pow"),
None,
)
.map(BasicValueEnum::into_int_value)
.unwrap() .unwrap()
} }
@ -62,20 +65,22 @@ 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 intrinsic_fn = ctx.module.get_function("__nac3_isinf").unwrap_or_else(|| { let llvm_i32 = ctx.ctx.i32_type();
let fn_type = ctx.ctx.i32_type().fn_type(&[ctx.ctx.f64_type().into()], false); let llvm_f64 = ctx.ctx.f64_type();
ctx.module.add_function("__nac3_isinf", fn_type, None)
});
let ret = ctx assert_eq!(v.get_type(), llvm_f64);
.builder
.build_call(intrinsic_fn, &[v.into()], "isinf")
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_int_value))
.map(Either::unwrap_left)
.unwrap();
generator.bool_to_i1(ctx, ret) infer_and_call_function(
ctx,
"__nac3_isinf",
Some(llvm_i32.into()),
&[v.into()],
Some("isinf"),
None,
)
.map(BasicValueEnum::into_int_value)
.map(|ret| generator.bool_to_i1(ctx, ret))
.unwrap()
} }
/// Generates a call to `isnan` in IR. Returns an `i1` representing the result. /// Generates a call to `isnan` in IR. Returns an `i1` representing the result.
@ -84,36 +89,39 @@ 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 intrinsic_fn = ctx.module.get_function("__nac3_isnan").unwrap_or_else(|| { let llvm_i32 = ctx.ctx.i32_type();
let fn_type = ctx.ctx.i32_type().fn_type(&[ctx.ctx.f64_type().into()], false); let llvm_f64 = ctx.ctx.f64_type();
ctx.module.add_function("__nac3_isnan", fn_type, None)
});
let ret = ctx assert_eq!(v.get_type(), llvm_f64);
.builder
.build_call(intrinsic_fn, &[v.into()], "isnan")
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_int_value))
.map(Either::unwrap_left)
.unwrap();
generator.bool_to_i1(ctx, ret) infer_and_call_function(
ctx,
"__nac3_isnan",
Some(llvm_i32.into()),
&[v.into()],
Some("isnan"),
None,
)
.map(BasicValueEnum::into_int_value)
.map(|ret| generator.bool_to_i1(ctx, ret))
.unwrap()
} }
/// Generates a call to `gamma` in IR. Returns an `f64` representing the result. /// Generates a call to `gamma` in IR. Returns an `f64` representing the result.
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();
let intrinsic_fn = ctx.module.get_function("__nac3_gamma").unwrap_or_else(|| { assert_eq!(v.get_type(), llvm_f64);
let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false);
ctx.module.add_function("__nac3_gamma", fn_type, None)
});
ctx.builder infer_and_call_function(
.build_call(intrinsic_fn, &[v.into()], "gamma") ctx,
.map(CallSiteValue::try_as_basic_value) "__nac3_gamma",
.map(|v| v.map_left(BasicValueEnum::into_float_value)) Some(llvm_f64.into()),
.map(Either::unwrap_left) &[v.into()],
Some("gamma"),
None,
)
.map(BasicValueEnum::into_float_value)
.unwrap() .unwrap()
} }
@ -121,16 +129,17 @@ 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();
let intrinsic_fn = ctx.module.get_function("__nac3_gammaln").unwrap_or_else(|| { assert_eq!(v.get_type(), llvm_f64);
let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false);
ctx.module.add_function("__nac3_gammaln", fn_type, None)
});
ctx.builder infer_and_call_function(
.build_call(intrinsic_fn, &[v.into()], "gammaln") ctx,
.map(CallSiteValue::try_as_basic_value) "__nac3_gammaln",
.map(|v| v.map_left(BasicValueEnum::into_float_value)) Some(llvm_f64.into()),
.map(Either::unwrap_left) &[v.into()],
Some("gammaln"),
None,
)
.map(BasicValueEnum::into_float_value)
.unwrap() .unwrap()
} }
@ -138,15 +147,9 @@ 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();
let intrinsic_fn = ctx.module.get_function("__nac3_j0").unwrap_or_else(|| { assert_eq!(v.get_type(), llvm_f64);
let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false);
ctx.module.add_function("__nac3_j0", fn_type, None)
});
ctx.builder infer_and_call_function(ctx, "__nac3_j0", Some(llvm_f64.into()), &[v.into()], Some("j0"), None)
.build_call(intrinsic_fn, &[v.into()], "j0") .map(BasicValueEnum::into_float_value)
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_float_value))
.map(Either::unwrap_left)
.unwrap() .unwrap()
} }

View File

@ -15,12 +15,14 @@ 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> {
@ -66,13 +68,9 @@ pub fn load_irrt<'ctx>(ctx: &'ctx Context, symbol_resolver: &dyn SymbolResolver)
/// - When [`TypeContext::size_type`] is 32-bits, the function name is `fn_name}`. /// - When [`TypeContext::size_type`] is 32-bits, the function name is `fn_name}`.
/// - When [`TypeContext::size_type`] is 64-bits, the function name is `{fn_name}64`. /// - When [`TypeContext::size_type`] is 64-bits, the function name is `{fn_name}64`.
#[must_use] #[must_use]
pub fn get_usize_dependent_function_name<G: CodeGenerator + ?Sized>( pub fn get_usize_dependent_function_name(ctx: &CodeGenContext<'_, '_>, name: &str) -> String {
generator: &G,
ctx: &CodeGenContext<'_, '_>,
name: &str,
) -> String {
let mut name = name.to_owned(); let mut name = name.to_owned();
match generator.get_size_type(ctx.ctx).get_bit_width() { match ctx.get_size_type().get_bit_width() {
32 => {} 32 => {}
64 => name.push_str("64"), 64 => name.push_str("64"),
bit_width => { bit_width => {
@ -130,10 +128,11 @@ 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 int32 = ctx.ctx.i32_type(); let llvm_i32 = ctx.ctx.i32_type();
let zero = int32.const_zero();
let one = int32.const_int(1, false); let zero = llvm_i32.const_zero();
let length = ctx.builder.build_int_truncate_or_bit_cast(length, int32, "leni32").unwrap(); let one = llvm_i32.const_int(1, false);
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() {
@ -142,7 +141,7 @@ pub fn handle_slice_indices<'ctx, G: CodeGenerator>(
None => return Ok(None), None => return Ok(None),
} }
} else { } else {
int32.const_zero() llvm_i32.const_zero()
}, },
{ {
let e = if let Some(s) = e.as_ref() { let e = if let Some(s) = e.as_ref() {

View File

@ -0,0 +1,72 @@
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 = ctx.get_size_type();
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(ctx, "__nac3_ndarray_array_set_and_validate_list_shape");
infer_and_call_function(
ctx,
&name,
None,
&[list.as_abi_value(ctx).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>(
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(ctx, "__nac3_ndarray_array_write_list_to_array");
infer_and_call_function(
ctx,
&name,
None,
&[list.as_abi_value(ctx).into(), ndarray.as_abi_value(ctx).into()],
None,
None,
);
}

View File

@ -4,85 +4,87 @@ use inkwell::{
}; };
use crate::codegen::{ use crate::codegen::{
expr::{create_and_call_function, infer_and_call_function}, expr::infer_and_call_function,
irrt::get_usize_dependent_function_name, irrt::get_usize_dependent_function_name,
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, '_>,
ndims: IntValue<'ctx>, shape: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>,
shape: PointerValue<'ctx>,
) { ) {
let llvm_usize = generator.get_size_type(ctx.ctx); let llvm_usize = ctx.get_size_type();
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
let name = get_usize_dependent_function_name( assert_eq!(shape.element_type(ctx, generator), llvm_usize.into());
generator,
ctx,
"__nac3_ndarray_util_assert_shape_no_negative",
);
create_and_call_function( let name =
get_usize_dependent_function_name(ctx, "__nac3_ndarray_util_assert_shape_no_negative");
infer_and_call_function(
ctx, ctx,
&name, &name,
Some(llvm_usize.into()), Some(llvm_usize.into()),
&[(llvm_usize.into(), ndims.into()), (llvm_pusize.into(), shape.into())], &[shape.size(ctx, generator).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_ndims: IntValue<'ctx>, ndarray_shape: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>,
ndarray_shape: PointerValue<'ctx>, output_shape: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>,
output_ndims: IntValue<'ctx>,
output_shape: IntValue<'ctx>,
) { ) {
let llvm_usize = generator.get_size_type(ctx.ctx); let llvm_usize = ctx.get_size_type();
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
let name = get_usize_dependent_function_name( assert_eq!(ndarray_shape.element_type(ctx, generator), llvm_usize.into());
generator, assert_eq!(output_shape.element_type(ctx, generator), llvm_usize.into());
ctx,
"__nac3_ndarray_util_assert_output_shape_same",
);
create_and_call_function( let name =
get_usize_dependent_function_name(ctx, "__nac3_ndarray_util_assert_output_shape_same");
infer_and_call_function(
ctx, ctx,
&name, &name,
Some(llvm_usize.into()), Some(llvm_usize.into()),
&[ &[
(llvm_usize.into(), ndarray_ndims.into()), ndarray_shape.size(ctx, generator).into(),
(llvm_pusize.into(), ndarray_shape.into()), ndarray_shape.base_ptr(ctx, generator).into(),
(llvm_usize.into(), output_ndims.into()), output_shape.size(ctx, generator).into(),
(llvm_pusize.into(), output_shape.into()), output_shape.base_ptr(ctx, generator).into(),
], ],
None, None,
None, None,
); );
} }
pub fn call_nac3_ndarray_size<'ctx, G: CodeGenerator + ?Sized>( /// Generates a call to `__nac3_ndarray_size`.
generator: &G, ///
/// 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>(
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
ndarray: NDArrayValue<'ctx>, ndarray: NDArrayValue<'ctx>,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
let llvm_usize = generator.get_size_type(ctx.ctx); let llvm_usize = ctx.get_size_type();
let llvm_ndarray = ndarray.get_type().as_base_type();
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_size"); let name = get_usize_dependent_function_name(ctx, "__nac3_ndarray_size");
create_and_call_function( infer_and_call_function(
ctx, ctx,
&name, &name,
Some(llvm_usize.into()), Some(llvm_usize.into()),
&[(llvm_ndarray.into(), ndarray.as_base_value().into())], &[ndarray.as_abi_value(ctx).into()],
Some("size"), Some("size"),
None, None,
) )
@ -90,21 +92,23 @@ pub fn call_nac3_ndarray_size<'ctx, G: CodeGenerator + ?Sized>(
.unwrap() .unwrap()
} }
pub fn call_nac3_ndarray_nbytes<'ctx, G: CodeGenerator + ?Sized>( /// Generates a call to `__nac3_ndarray_nbytes`.
generator: &G, ///
/// 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>(
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
ndarray: NDArrayValue<'ctx>, ndarray: NDArrayValue<'ctx>,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
let llvm_usize = generator.get_size_type(ctx.ctx); let llvm_usize = ctx.get_size_type();
let llvm_ndarray = ndarray.get_type().as_base_type();
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_nbytes"); let name = get_usize_dependent_function_name(ctx, "__nac3_ndarray_nbytes");
create_and_call_function( infer_and_call_function(
ctx, ctx,
&name, &name,
Some(llvm_usize.into()), Some(llvm_usize.into()),
&[(llvm_ndarray.into(), ndarray.as_base_value().into())], &[ndarray.as_abi_value(ctx).into()],
Some("nbytes"), Some("nbytes"),
None, None,
) )
@ -112,21 +116,23 @@ pub fn call_nac3_ndarray_nbytes<'ctx, G: CodeGenerator + ?Sized>(
.unwrap() .unwrap()
} }
pub fn call_nac3_ndarray_len<'ctx, G: CodeGenerator + ?Sized>( /// Generates a call to `__nac3_ndarray_len`.
generator: &G, ///
/// 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>(
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
ndarray: NDArrayValue<'ctx>, ndarray: NDArrayValue<'ctx>,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
let llvm_usize = generator.get_size_type(ctx.ctx); let llvm_usize = ctx.get_size_type();
let llvm_ndarray = ndarray.get_type().as_base_type();
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_len"); let name = get_usize_dependent_function_name(ctx, "__nac3_ndarray_len");
create_and_call_function( infer_and_call_function(
ctx, ctx,
&name, &name,
Some(llvm_usize.into()), Some(llvm_usize.into()),
&[(llvm_ndarray.into(), ndarray.as_base_value().into())], &[ndarray.as_abi_value(ctx).into()],
Some("len"), Some("len"),
None, None,
) )
@ -134,21 +140,22 @@ pub fn call_nac3_ndarray_len<'ctx, G: CodeGenerator + ?Sized>(
.unwrap() .unwrap()
} }
pub fn call_nac3_ndarray_is_c_contiguous<'ctx, G: CodeGenerator + ?Sized>( /// Generates a call to `__nac3_ndarray_is_c_contiguous`.
generator: &G, ///
/// Returns an `i1` value indicating whether the `ndarray` is C-contiguous.
pub fn call_nac3_ndarray_is_c_contiguous<'ctx>(
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
ndarray: NDArrayValue<'ctx>, ndarray: NDArrayValue<'ctx>,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
let llvm_i1 = ctx.ctx.bool_type(); let llvm_i1 = ctx.ctx.bool_type();
let llvm_ndarray = ndarray.get_type().as_base_type();
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_is_c_contiguous"); let name = get_usize_dependent_function_name(ctx, "__nac3_ndarray_is_c_contiguous");
create_and_call_function( infer_and_call_function(
ctx, ctx,
&name, &name,
Some(llvm_i1.into()), Some(llvm_i1.into()),
&[(llvm_ndarray.into(), ndarray.as_base_value().into())], &[ndarray.as_abi_value(ctx).into()],
Some("is_c_contiguous"), Some("is_c_contiguous"),
None, None,
) )
@ -156,24 +163,27 @@ pub fn call_nac3_ndarray_is_c_contiguous<'ctx, G: CodeGenerator + ?Sized>(
.unwrap() .unwrap()
} }
pub fn call_nac3_ndarray_get_nth_pelement<'ctx, G: CodeGenerator + ?Sized>( /// Generates a call to `__nac3_ndarray_get_nth_pelement`.
generator: &G, ///
/// Returns a [`PointerValue`] to the `index`-th flattened element of the `ndarray`.
pub fn call_nac3_ndarray_get_nth_pelement<'ctx>(
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
ndarray: NDArrayValue<'ctx>, ndarray: NDArrayValue<'ctx>,
index: IntValue<'ctx>, index: IntValue<'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());
let llvm_usize = generator.get_size_type(ctx.ctx); let llvm_usize = ctx.get_size_type();
let llvm_ndarray = ndarray.get_type().as_base_type();
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_get_nth_pelement"); assert_eq!(index.get_type(), llvm_usize);
create_and_call_function( let name = get_usize_dependent_function_name(ctx, "__nac3_ndarray_get_nth_pelement");
infer_and_call_function(
ctx, ctx,
&name, &name,
Some(llvm_pi8.into()), Some(llvm_pi8.into()),
&[(llvm_ndarray.into(), ndarray.as_base_value().into()), (llvm_usize.into(), index.into())], &[ndarray.as_abi_value(ctx).into(), index.into()],
Some("pelement"), Some("pelement"),
None, None,
) )
@ -181,29 +191,30 @@ 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: PointerValue<'ctx>, indices: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'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());
let llvm_usize = generator.get_size_type(ctx.ctx); let llvm_usize = ctx.get_size_type();
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
let llvm_ndarray = ndarray.get_type().as_base_type();
let name = assert_eq!(indices.element_type(ctx, generator), llvm_usize.into());
get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_get_pelement_by_indices");
create_and_call_function( let name = get_usize_dependent_function_name(ctx, "__nac3_ndarray_get_pelement_by_indices");
infer_and_call_function(
ctx, ctx,
&name, &name,
Some(llvm_pi8.into()), Some(llvm_pi8.into()),
&[ &[ndarray.as_abi_value(ctx).into(), indices.base_ptr(ctx, generator).into()],
(llvm_ndarray.into(), ndarray.as_base_value().into()),
(llvm_pusize.into(), indices.into()),
],
Some("pelement"), Some("pelement"),
None, None,
) )
@ -211,39 +222,35 @@ pub fn call_nac3_ndarray_get_pelement_by_indices<'ctx, G: CodeGenerator + ?Sized
.unwrap() .unwrap()
} }
pub fn call_nac3_ndarray_set_strides_by_shape<'ctx, G: CodeGenerator + ?Sized>( /// Generates a call to `__nac3_ndarray_set_strides_by_shape`.
generator: &G, ///
/// Sets `ndarray.strides` assuming that `ndarray.shape` is C-contiguous.
pub fn call_nac3_ndarray_set_strides_by_shape<'ctx>(
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
ndarray: NDArrayValue<'ctx>, ndarray: NDArrayValue<'ctx>,
) { ) {
let llvm_ndarray = ndarray.get_type().as_base_type(); let name = get_usize_dependent_function_name(ctx, "__nac3_ndarray_set_strides_by_shape");
let name = infer_and_call_function(ctx, &name, None, &[ndarray.as_abi_value(ctx).into()], None, None);
get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_set_strides_by_shape");
create_and_call_function(
ctx,
&name,
None,
&[(llvm_ndarray.into(), ndarray.as_base_value().into())],
None,
None,
);
} }
pub fn call_nac3_ndarray_copy_data<'ctx, G: CodeGenerator + ?Sized>( /// Generates a call to `__nac3_ndarray_copy_data`.
generator: &G, ///
/// 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>(
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
src_ndarray: NDArrayValue<'ctx>, src_ndarray: NDArrayValue<'ctx>,
dst_ndarray: NDArrayValue<'ctx>, dst_ndarray: NDArrayValue<'ctx>,
) { ) {
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_copy_data"); let name = get_usize_dependent_function_name(ctx, "__nac3_ndarray_copy_data");
infer_and_call_function( infer_and_call_function(
ctx, ctx,
&name, &name,
None, None,
&[src_ndarray.as_base_value().into(), dst_ndarray.as_base_value().into()], &[src_ndarray.as_abi_value(ctx).into(), dst_ndarray.as_abi_value(ctx).into()],
None, None,
None, None,
); );

View File

@ -0,0 +1,80 @@
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>(
ctx: &CodeGenContext<'ctx, '_>,
src_ndarray: NDArrayValue<'ctx>,
dst_ndarray: NDArrayValue<'ctx>,
) {
let name = get_usize_dependent_function_name(ctx, "__nac3_ndarray_broadcast_to");
infer_and_call_function(
ctx,
&name,
None,
&[src_ndarray.as_abi_value(ctx).into(), dst_ndarray.as_abi_value(ctx).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 = ctx.get_size_type();
assert_eq!(num_shape_entries.get_type(), llvm_usize);
assert!(ShapeEntryType::is_representable(
shape_entries.base_ptr(ctx, generator).get_type(),
llvm_usize,
)
.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(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,6 +5,11 @@ 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, '_>,
@ -12,7 +17,7 @@ pub fn call_nac3_ndarray_index<'ctx, G: CodeGenerator + ?Sized>(
src_ndarray: NDArrayValue<'ctx>, src_ndarray: NDArrayValue<'ctx>,
dst_ndarray: NDArrayValue<'ctx>, dst_ndarray: NDArrayValue<'ctx>,
) { ) {
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_index"); let name = get_usize_dependent_function_name(ctx, "__nac3_ndarray_index");
infer_and_call_function( infer_and_call_function(
ctx, ctx,
&name, &name,
@ -20,8 +25,8 @@ pub fn call_nac3_ndarray_index<'ctx, G: CodeGenerator + ?Sized>(
&[ &[
indices.size(ctx, generator).into(), indices.size(ctx, generator).into(),
indices.base_ptr(ctx, generator).into(), indices.base_ptr(ctx, generator).into(),
src_ndarray.as_base_value().into(), src_ndarray.as_abi_value(ctx).into(),
dst_ndarray.as_base_value().into(), dst_ndarray.as_abi_value(ctx).into(),
], ],
None, None,
None, None,

View File

@ -1,57 +1,60 @@
use inkwell::{ use inkwell::values::{BasicValueEnum, IntValue};
values::{BasicValueEnum, IntValue},
AddressSpace,
};
use crate::codegen::{ use crate::codegen::{
expr::{create_and_call_function, infer_and_call_function}, expr::infer_and_call_function,
irrt::get_usize_dependent_function_name, irrt::get_usize_dependent_function_name,
types::ProxyType,
values::{ values::{
ndarray::{NDArrayValue, NDIterValue}, ndarray::{NDArrayValue, NDIterValue},
ArrayLikeValue, ArraySliceValue, ProxyValue, ProxyValue, TypedArrayLikeAccessor,
}, },
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: ArraySliceValue<'ctx>, indices: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>,
) { ) {
let llvm_usize = generator.get_size_type(ctx.ctx); let llvm_usize = ctx.get_size_type();
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_nditer_initialize"); assert_eq!(indices.element_type(ctx, generator), llvm_usize.into());
create_and_call_function( let name = get_usize_dependent_function_name(ctx, "__nac3_nditer_initialize");
infer_and_call_function(
ctx, ctx,
&name, &name,
None, None,
&[ &[
(iter.get_type().as_base_type().into(), iter.as_base_value().into()), iter.as_abi_value(ctx).into(),
(ndarray.get_type().as_base_type().into(), ndarray.as_base_value().into()), ndarray.as_abi_value(ctx).into(),
(llvm_pusize.into(), indices.base_ptr(ctx, generator).into()), indices.base_ptr(ctx, generator).into(),
], ],
None, None,
None, None,
); );
} }
pub fn call_nac3_nditer_has_element<'ctx, G: CodeGenerator + ?Sized>( /// Generates a call to `__nac3_nditer_initialize_has_element`.
generator: &G, ///
/// Returns an `i1` value indicating whether there are elements left to traverse for the `iter`
/// object.
pub fn call_nac3_nditer_has_element<'ctx>(
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
iter: NDIterValue<'ctx>, iter: NDIterValue<'ctx>,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_nditer_has_element"); let name = get_usize_dependent_function_name(ctx, "__nac3_nditer_has_element");
infer_and_call_function( infer_and_call_function(
ctx, ctx,
&name, &name,
Some(ctx.ctx.bool_type().into()), Some(ctx.ctx.bool_type().into()),
&[iter.as_base_value().into()], &[iter.as_abi_value(ctx).into()],
None, None,
None, None,
) )
@ -59,12 +62,11 @@ pub fn call_nac3_nditer_has_element<'ctx, G: CodeGenerator + ?Sized>(
.unwrap() .unwrap()
} }
pub fn call_nac3_nditer_next<'ctx, G: CodeGenerator + ?Sized>( /// Generates a call to `__nac3_nditer_next`.
generator: &G, ///
ctx: &CodeGenContext<'ctx, '_>, /// Moves `iter` to point to the next element.
iter: NDIterValue<'ctx>, pub fn call_nac3_nditer_next<'ctx>(ctx: &CodeGenContext<'ctx, '_>, iter: NDIterValue<'ctx>) {
) { let name = get_usize_dependent_function_name(ctx, "__nac3_nditer_next");
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_nditer_next");
infer_and_call_function(ctx, &name, None, &[iter.as_base_value().into()], None, None); infer_and_call_function(ctx, &name, None, &[iter.as_abi_value(ctx).into()], None, None);
} }

View File

@ -0,0 +1,51 @@
use inkwell::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 = ctx.get_size_type();
assert_eq!(a_shape.element_type(ctx, generator), llvm_usize.into());
assert_eq!(b_shape.element_type(ctx, generator), llvm_usize.into());
assert_eq!(final_ndims.get_type(), llvm_usize);
assert_eq!(new_a_shape.element_type(ctx, generator), llvm_usize.into());
assert_eq!(new_b_shape.element_type(ctx, generator), llvm_usize.into());
assert_eq!(dst_shape.element_type(ctx, generator), llvm_usize.into());
let name = get_usize_dependent_function_name(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,391 +1,17 @@
use inkwell::{ pub use array::*;
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;
/// Generates a call to `__nac3_ndarray_calc_size`. Returns an [`IntValue`] representing the mod reshape;
/// calculated total size. mod transpose;
///
/// * `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

@ -0,0 +1,39 @@
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 = ctx.get_size_type();
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(
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

@ -0,0 +1,48 @@
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 = ctx.get_size_type();
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(ctx, "__nac3_ndarray_transpose");
infer_and_call_function(
ctx,
&name,
None,
&[
src_ndarray.as_abi_value(ctx).into(),
dst_ndarray.as_abi_value(ctx).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

@ -1,11 +1,17 @@
use inkwell::{ use inkwell::{
values::{BasicValueEnum, CallSiteValue, IntValue}, values::{BasicValueEnum, IntValue},
IntPredicate, IntPredicate,
}; };
use itertools::Either;
use crate::codegen::{CodeGenContext, CodeGenerator}; use crate::codegen::{expr::infer_and_call_function, 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, '_>,
@ -14,11 +20,11 @@ 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 len_func = ctx.module.get_function(SYMBOL).unwrap_or_else(|| {
let i32_t = ctx.ctx.i32_type(); let llvm_i32 = ctx.ctx.i32_type();
let fn_t = i32_t.fn_type(&[i32_t.into(), i32_t.into(), i32_t.into()], false); assert_eq!(start.get_type(), llvm_i32);
ctx.module.add_function(SYMBOL, fn_t, None) assert_eq!(end.get_type(), llvm_i32);
}); assert_eq!(step.get_type(), llvm_i32);
// assert step != 0, throw exception if not // assert step != 0, throw exception if not
let not_zero = ctx let not_zero = ctx
@ -33,10 +39,15 @@ 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
.build_call(len_func, &[start.into(), end.into(), step.into()], "calc_len") infer_and_call_function(
.map(CallSiteValue::try_as_basic_value) ctx,
.map(|v| v.map_left(BasicValueEnum::into_int_value)) SYMBOL,
.map(Either::unwrap_left) Some(llvm_i32.into()),
&[start.into(), end.into(), step.into()],
Some("calc_len"),
None,
)
.map(BasicValueEnum::into_int_value)
.unwrap() .unwrap()
} }

View File

@ -1,10 +1,9 @@
use inkwell::values::{BasicValueEnum, CallSiteValue, IntValue}; use inkwell::values::{BasicValueEnum, IntValue};
use itertools::Either;
use nac3parser::ast::Expr; use nac3parser::ast::Expr;
use crate::{ use crate::{
codegen::{CodeGenContext, CodeGenerator}, codegen::{expr::infer_and_call_function, CodeGenContext, CodeGenerator},
typecheck::typedef::Type, typecheck::typedef::Type,
}; };
@ -17,23 +16,26 @@ pub fn handle_slice_index_bound<'ctx, G: CodeGenerator>(
length: IntValue<'ctx>, length: IntValue<'ctx>,
) -> Result<Option<IntValue<'ctx>>, String> { ) -> Result<Option<IntValue<'ctx>>, String> {
const SYMBOL: &str = "__nac3_slice_index_bound"; const SYMBOL: &str = "__nac3_slice_index_bound";
let func = ctx.module.get_function(SYMBOL).unwrap_or_else(|| {
let i32_t = ctx.ctx.i32_type(); let llvm_i32 = ctx.ctx.i32_type();
let fn_t = i32_t.fn_type(&[i32_t.into(), i32_t.into()], false); assert_eq!(length.get_type(), llvm_i32);
ctx.module.add_function(SYMBOL, fn_t, None)
});
let i = if let Some(v) = generator.gen_expr(ctx, i)? { let i = if let Some(v) = generator.gen_expr(ctx, i)? {
v.to_basic_value_enum(ctx, generator, i.custom.unwrap())? v.to_basic_value_enum(ctx, generator, i.custom.unwrap())?
} else { } else {
return Ok(None); return Ok(None);
}; };
Ok(Some( Ok(Some(
ctx.builder infer_and_call_function(
.build_call(func, &[i.into(), length.into()], "bounded_ind") ctx,
.map(CallSiteValue::try_as_basic_value) SYMBOL,
.map(|v| v.map_left(BasicValueEnum::into_int_value)) Some(llvm_i32.into()),
.map(Either::unwrap_left) &[i, length.into()],
Some("bounded_ind"),
None,
)
.map(BasicValueEnum::into_int_value)
.unwrap(), .unwrap(),
)) ))
} }

View File

@ -0,0 +1,31 @@
use inkwell::values::{BasicValueEnum, IntValue};
use super::get_usize_dependent_function_name;
use crate::codegen::{expr::infer_and_call_function, values::StringValue, CodeGenContext};
/// Generates a call to string equality comparison. Returns an `i1` representing whether the strings are equal.
pub fn call_string_eq<'ctx>(
ctx: &CodeGenContext<'ctx, '_>,
str1: StringValue<'ctx>,
str2: StringValue<'ctx>,
) -> IntValue<'ctx> {
let llvm_i1 = ctx.ctx.bool_type();
let func_name = get_usize_dependent_function_name(ctx, "nac3_str_eq");
infer_and_call_function(
ctx,
&func_name,
Some(llvm_i1.into()),
&[
str1.extract_ptr(ctx).into(),
str1.extract_len(ctx).into(),
str2.extract_ptr(ctx).into(),
str2.extract_len(ctx).into(),
],
Some("str_eq_call"),
None,
)
.map(BasicValueEnum::into_int_value)
.unwrap()
}

View File

@ -1,7 +1,6 @@
use inkwell::{ use inkwell::{
context::Context,
intrinsics::Intrinsic, intrinsics::Intrinsic,
types::{AnyTypeEnum::IntType, FloatType}, types::AnyTypeEnum::IntType,
values::{BasicValueEnum, CallSiteValue, FloatValue, IntValue, PointerValue}, values::{BasicValueEnum, CallSiteValue, FloatValue, IntValue, PointerValue},
AddressSpace, AddressSpace,
}; };
@ -9,34 +8,6 @@ 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>) {
@ -54,7 +25,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_start`](https://llvm.org/docs/LangRef.html#llvm-va-start-intrinsic) /// Invokes the [`llvm.va_end`](https://llvm.org/docs/LangRef.html#llvm-va-end-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

@ -1,4 +1,5 @@
use std::{ use std::{
cell::OnceCell,
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
sync::{ sync::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
@ -19,7 +20,7 @@ use inkwell::{
module::Module, module::Module,
passes::PassBuilderOptions, passes::PassBuilderOptions,
targets::{CodeModel, RelocMode, Target, TargetMachine, TargetTriple}, targets::{CodeModel, RelocMode, Target, TargetMachine, TargetTriple},
types::{AnyType, BasicType, BasicTypeEnum}, types::{AnyType, BasicType, BasicTypeEnum, IntType},
values::{BasicValueEnum, FunctionValue, IntValue, PhiValue, PointerValue}, values::{BasicValueEnum, FunctionValue, IntValue, PhiValue, PointerValue},
AddressSpace, IntPredicate, OptimizationLevel, AddressSpace, IntPredicate, OptimizationLevel,
}; };
@ -42,7 +43,9 @@ 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}; use types::{
ndarray::NDArrayType, ListType, OptionType, ProxyType, RangeType, StringType, TupleType,
};
pub mod builtin_fns; pub mod builtin_fns;
pub mod concrete_type; pub mod concrete_type;
@ -226,14 +229,33 @@ pub struct CodeGenContext<'ctx, 'a> {
/// The current source location. /// The current source location.
pub current_loc: Location, pub current_loc: Location,
/// The cached type of `size_t`.
llvm_usize: OnceCell<IntType<'ctx>>,
} }
impl<'ctx, 'a> CodeGenContext<'ctx, 'a> { impl<'ctx> CodeGenContext<'ctx, '_> {
/// 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 {
self.builder.get_insert_block().and_then(BasicBlock::get_terminator).is_some() self.builder.get_insert_block().and_then(BasicBlock::get_terminator).is_some()
} }
/// Returns a [`IntType`] representing `size_t` for the compilation target as specified by
/// [`self.registry`][WorkerRegistry].
pub fn get_size_type(&self) -> IntType<'ctx> {
*self.llvm_usize.get_or_init(|| {
self.ctx.ptr_sized_int_type(
&self
.registry
.llvm_options
.create_target_machine()
.map(|tm| tm.get_target_data())
.unwrap(),
None,
)
})
}
} }
type Fp = Box<dyn Fn(&Module) + Send + Sync>; type Fp = Box<dyn Fn(&Module) + Send + Sync>;
@ -481,11 +503,22 @@ fn get_llvm_type<'ctx, G: CodeGenerator + ?Sized>(
type_cache.get(&unifier.get_representative(ty)).copied().unwrap_or_else(|| { type_cache.get(&unifier.get_representative(ty)).copied().unwrap_or_else(|| {
let ty_enum = unifier.get_ty(ty); let ty_enum = unifier.get_ty(ty);
let result = match &*ty_enum { let result = match &*ty_enum {
TObj { obj_id, fields, .. } => { TModule {module_id, attributes} => {
// check to avoid treating non-class primitives as classes let top_level_defs = top_level.definitions.read();
if PrimDef::contains_id(*obj_id) { let definition = top_level_defs.get(module_id.0).unwrap();
return match &*unifier.get_ty_immutable(ty) { let TopLevelDef::Module { name, attributes: attribute_fields, .. } = &*definition.read() else {
TObj { obj_id, params, .. } if *obj_id == PrimDef::Option.id() => { unreachable!()
};
let ty: BasicTypeEnum<'_> = if let Some(t) = module.get_struct_type(&name.to_string()) {
t.ptr_type(AddressSpace::default()).into()
} else {
let struct_type = ctx.opaque_struct_type(&name.to_string());
type_cache.insert(
unifier.get_representative(ty),
struct_type.ptr_type(AddressSpace::default()).into(),
);
let module_fields: Vec<BasicTypeEnum<'_>> = attribute_fields.iter()
.map(|f| {
get_llvm_type( get_llvm_type(
ctx, ctx,
module, module,
@ -493,10 +526,31 @@ fn get_llvm_type<'ctx, G: CodeGenerator + ?Sized>(
unifier, unifier,
top_level, top_level,
type_cache, type_cache,
*params.iter().next().unwrap().1, attributes[&f.0].0,
) )
.ptr_type(AddressSpace::default()) })
.into() .collect_vec();
struct_type.set_body(&module_fields, false);
struct_type.ptr_type(AddressSpace::default()).into()
};
return ty;
},
TObj { obj_id, fields, .. } => {
// check to avoid treating non-class primitives as classes
if PrimDef::contains_id(*obj_id) {
return match &*unifier.get_ty_immutable(ty) {
TObj { obj_id, params, .. } if *obj_id == PrimDef::Option.id() => {
let element_type = get_llvm_type(
ctx,
module,
generator,
unifier,
top_level,
type_cache,
*params.iter().next().unwrap().1,
);
OptionType::new_with_generator(generator, ctx, &element_type).as_abi_type().into()
} }
TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => { TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => {
@ -510,7 +564,7 @@ fn get_llvm_type<'ctx, G: CodeGenerator + ?Sized>(
*params.iter().next().unwrap().1, *params.iter().next().unwrap().1,
); );
ListType::new(generator, ctx, element_type).as_base_type().into() ListType::new_with_generator(generator, ctx, element_type).as_abi_type().into()
} }
TObj { obj_id, .. } if *obj_id == PrimDef::NDArray.id() => { TObj { obj_id, .. } if *obj_id == PrimDef::NDArray.id() => {
@ -520,7 +574,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, Some(ndims)).as_base_type().into() NDArrayType::new_with_generator(generator, ctx, element_type, ndims).as_abi_type().into()
} }
_ => unreachable!( _ => unreachable!(
@ -574,7 +628,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();
ctx.struct_type(&fields, false).into() TupleType::new_with_generator(generator, ctx, &fields).as_abi_type().into()
} }
TVirtual { .. } => unimplemented!(), TVirtual { .. } => unimplemented!(),
_ => unreachable!("{}", ty_enum.get_type_name()), _ => unreachable!("{}", ty_enum.get_type_name()),
@ -734,21 +788,9 @@ pub fn gen_func_impl<
(primitives.float, context.f64_type().into()), (primitives.float, context.f64_type().into()),
(primitives.bool, context.i8_type().into()), (primitives.bool, context.i8_type().into()),
(primitives.str, { (primitives.str, {
let name = "str"; StringType::new_with_generator(generator, context).as_abi_type().into()
match module.get_struct_type(name) {
None => {
let str_type = context.opaque_struct_type("str");
let fields = [
context.i8_type().ptr_type(AddressSpace::default()).into(),
generator.get_size_type(context).into(),
];
str_type.set_body(&fields, false);
str_type.into()
}
Some(t) => t.as_basic_type_enum(),
}
}), }),
(primitives.range, RangeType::new(context).as_base_type().into()), (primitives.range, RangeType::new_with_generator(generator, context).as_abi_type().into()),
(primitives.exception, { (primitives.exception, {
let name = "Exception"; let name = "Exception";
if let Some(t) = module.get_struct_type(name) { if let Some(t) = module.get_struct_type(name) {
@ -881,7 +923,7 @@ pub fn gen_func_impl<
let param_val = param.into_int_value(); let param_val = param.into_int_value();
if expected_ty.get_bit_width() == 8 && param_val.get_type().get_bit_width() == 1 { if expected_ty.get_bit_width() == 8 && param_val.get_type().get_bit_width() == 1 {
bool_to_i8(&builder, context, param_val) bool_to_int_type(&builder, param_val, context.i8_type())
} else { } else {
param_val param_val
} }
@ -987,8 +1029,20 @@ pub fn gen_func_impl<
need_sret: has_sret, need_sret: has_sret,
current_loc: Location::default(), current_loc: Location::default(),
debug_info: (dibuilder, compile_unit, func_scope.as_debug_info_scope()), debug_info: (dibuilder, compile_unit, func_scope.as_debug_info_scope()),
llvm_usize: OnceCell::default(),
}; };
let target_llvm_usize = context.ptr_sized_int_type(
&registry.llvm_options.create_target_machine().map(|tm| tm.get_target_data()).unwrap(),
None,
);
let generator_llvm_usize = generator.get_size_type(context);
assert_eq!(
generator_llvm_usize,
target_llvm_usize,
"CodeGenerator (size_t = {generator_llvm_usize}) is not compatible with CodeGen Target (size_t = {target_llvm_usize})",
);
let loc = code_gen_context.debug_info.0.create_debug_location( let loc = code_gen_context.debug_info.0.create_debug_location(
context, context,
row as u32, row as u32,
@ -1039,43 +1093,29 @@ pub fn gen_func<'ctx, G: CodeGenerator>(
}) })
} }
/// Converts the value of a boolean-like value `bool_value` into an `i1`. /// Converts the value of a boolean-like value `value` into an arbitrary [`IntType`].
fn bool_to_i1<'ctx>(builder: &Builder<'ctx>, bool_value: IntValue<'ctx>) -> IntValue<'ctx> { ///
if bool_value.get_type().get_bit_width() == 1 { /// This has the same semantics as `(ty)(value != 0)` in C.
bool_value ///
} else { /// The returned value is guaranteed to either be `0` or `1`, except for `ty == i1` where only the
builder /// least-significant bit would be guaranteed to be `0` or `1`.
.build_int_compare( fn bool_to_int_type<'ctx>(
IntPredicate::NE,
bool_value,
bool_value.get_type().const_zero(),
"tobool",
)
.unwrap()
}
}
/// Converts the value of a boolean-like value `bool_value` into an `i8`.
fn bool_to_i8<'ctx>(
builder: &Builder<'ctx>, builder: &Builder<'ctx>,
ctx: &'ctx Context, value: IntValue<'ctx>,
bool_value: IntValue<'ctx>, ty: IntType<'ctx>,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
let value_bits = bool_value.get_type().get_bit_width(); // i1 -> i1 : %value ; no-op
match value_bits { // i1 -> i<N> : zext i1 %value to i<N> ; guaranteed to be 0 or 1 - see docs
8 => bool_value, // i<M> -> i<N>: zext i1 (icmp eq i<M> %value, 0) to i<N> ; same as i<M> -> i1 -> i<N>
1 => builder.build_int_z_extend(bool_value, ctx.i8_type(), "frombool").unwrap(), match (value.get_type().get_bit_width(), ty.get_bit_width()) {
_ => bool_to_i8( (1, 1) => value,
(1, _) => builder.build_int_z_extend(value, ty, "frombool").unwrap(),
_ => bool_to_int_type(
builder, builder,
ctx,
builder builder
.build_int_compare( .build_int_compare(IntPredicate::NE, value, value.get_type().const_zero(), "tobool")
IntPredicate::NE,
bool_value,
bool_value.get_type().const_zero(),
"",
)
.unwrap(), .unwrap(),
ty,
), ),
} }
} }
@ -1180,7 +1220,7 @@ pub fn type_aligned_alloca<'ctx, G: CodeGenerator + ?Sized>(
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());
let llvm_usize = generator.get_size_type(ctx.ctx); let llvm_usize = ctx.get_size_type();
let align_ty = align_ty.into(); let align_ty = align_ty.into();
let size = ctx.builder.build_int_truncate_or_bit_cast(size, llvm_usize, "").unwrap(); let size = ctx.builder.build_int_truncate_or_bit_cast(size, llvm_usize, "").unwrap();

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
use inkwell::{ use inkwell::{
attributes::{Attribute, AttributeLoc}, attributes::{Attribute, AttributeLoc},
basic_block::BasicBlock, basic_block::BasicBlock,
builder::Builder,
types::{BasicType, BasicTypeEnum}, types::{BasicType, BasicTypeEnum},
values::{BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue}, values::{BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue},
IntPredicate, IntPredicate,
@ -16,7 +17,11 @@ 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,
values::{ArrayLikeIndexer, ArraySliceValue, ListValue, RangeValue}, types::{ndarray::NDArrayType, ExceptionType, RangeType},
values::{
ndarray::{RustNDIndex, ScalarOrNDArray},
ArrayLikeIndexer, ArraySliceValue, ExceptionValue, ListValue, ProxyValue,
},
CodeGenContext, CodeGenerator, CodeGenContext, CodeGenerator,
}; };
use crate::{ use crate::{
@ -302,7 +307,7 @@ pub fn gen_setitem<'ctx, G: CodeGenerator>(
if *obj_id == ctx.primitives.list.obj_id(&ctx.unifier).unwrap() => if *obj_id == ctx.primitives.list.obj_id(&ctx.unifier).unwrap() =>
{ {
// Handle list item assignment // Handle list item assignment
let llvm_usize = generator.get_size_type(ctx.ctx); let llvm_usize = ctx.get_size_type();
let target_item_ty = iter_type_vars(list_params).next().unwrap().ty; let target_item_ty = iter_type_vars(list_params).next().unwrap().ty;
let target = generator let target = generator
@ -363,10 +368,8 @@ pub fn gen_setitem<'ctx, G: CodeGenerator>(
.unwrap() .unwrap()
.to_basic_value_enum(ctx, generator, key_ty)? .to_basic_value_enum(ctx, generator, key_ty)?
.into_int_value(); .into_int_value();
let index = ctx let index =
.builder ctx.builder.build_int_s_extend(index, ctx.get_size_type(), "sext").unwrap();
.build_int_s_extend(index, generator.get_size_type(ctx.ctx), "sext")
.unwrap();
// handle negative index // handle negative index
let is_negative = ctx let is_negative = ctx
@ -374,7 +377,7 @@ pub fn gen_setitem<'ctx, G: CodeGenerator>(
.build_int_compare( .build_int_compare(
IntPredicate::SLT, IntPredicate::SLT,
index, index,
generator.get_size_type(ctx.ctx).const_zero(), ctx.get_size_type().const_zero(),
"is_neg", "is_neg",
) )
.unwrap(); .unwrap();
@ -411,7 +414,51 @@ 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
todo!("ndarray subscript assignment is not yet implemented"); // Process target
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_pointer_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(
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(ctx, value);
} }
_ => { _ => {
panic!("encountered unknown target type: {}", ctx.unifier.stringify(target_ty)); panic!("encountered unknown target type: {}", ctx.unifier.stringify(target_ty));
@ -435,7 +482,7 @@ pub fn gen_for<G: CodeGenerator>(
let var_assignment = ctx.var_assignment.clone(); let var_assignment = ctx.var_assignment.clone();
let int32 = ctx.ctx.i32_type(); let int32 = ctx.ctx.i32_type();
let size_t = generator.get_size_type(ctx.ctx); let size_t = ctx.get_size_type();
let zero = int32.const_zero(); let zero = int32.const_zero();
let current = ctx.builder.get_insert_block().and_then(BasicBlock::get_parent).unwrap(); let current = ctx.builder.get_insert_block().and_then(BasicBlock::get_parent).unwrap();
let body_bb = ctx.ctx.append_basic_block(current, "for.body"); let body_bb = ctx.ctx.append_basic_block(current, "for.body");
@ -464,7 +511,7 @@ pub fn gen_for<G: CodeGenerator>(
if *obj_id == ctx.primitives.range.obj_id(&ctx.unifier).unwrap() => if *obj_id == ctx.primitives.range.obj_id(&ctx.unifier).unwrap() =>
{ {
let iter_val = let iter_val =
RangeValue::from_pointer_value(iter_val.into_pointer_value(), Some("range")); RangeType::new(ctx).map_pointer_value(iter_val.into_pointer_value(), Some("range"));
// Internal variable for loop; Cannot be assigned // Internal variable for loop; Cannot be assigned
let i = generator.gen_var_alloc(ctx, int32.into(), Some("for.i.addr"))?; let i = generator.gen_var_alloc(ctx, int32.into(), Some("for.i.addr"))?;
// Variable declared in "target" expression of the loop; Can be reassigned *or* shadowed // Variable declared in "target" expression of the loop; Can be reassigned *or* shadowed
@ -616,11 +663,25 @@ pub fn gen_for<G: CodeGenerator>(
#[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)] #[derive(PartialEq, Eq, Debug, Clone, Copy, Hash)]
pub struct BreakContinueHooks<'ctx> { pub struct BreakContinueHooks<'ctx> {
/// The [exit block][`BasicBlock`] to branch to when `break`-ing out of a loop. /// The [exit block][`BasicBlock`] to branch to when `break`-ing out of a loop.
pub exit_bb: BasicBlock<'ctx>, exit_bb: BasicBlock<'ctx>,
/// The [latch basic block][`BasicBlock`] to branch to for `continue`-ing to the next iteration /// The [latch basic block][`BasicBlock`] to branch to for `continue`-ing to the next iteration
/// of the loop. /// of the loop.
pub latch_bb: BasicBlock<'ctx>, latch_bb: BasicBlock<'ctx>,
}
impl<'ctx> BreakContinueHooks<'ctx> {
/// Creates a [`br` instruction][Builder::build_unconditional_branch] to the exit
/// [`BasicBlock`], as if by calling `break`.
pub fn build_break_branch(&self, builder: &Builder<'ctx>) {
builder.build_unconditional_branch(self.exit_bb).unwrap();
}
/// Creates a [`br` instruction][Builder::build_unconditional_branch] to the latch
/// [`BasicBlock`], as if by calling `continue`.
pub fn build_continue_branch(&self, builder: &Builder<'ctx>) {
builder.build_unconditional_branch(self.latch_bb).unwrap();
}
} }
/// Generates a C-style `for` construct using lambdas, similar to the following C code: /// Generates a C-style `for` construct using lambdas, similar to the following C code:
@ -1276,43 +1337,19 @@ pub fn exn_constructor<'ctx>(
pub fn gen_raise<'ctx, G: CodeGenerator + ?Sized>( pub fn gen_raise<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
exception: Option<&BasicValueEnum<'ctx>>, exception: Option<&ExceptionValue<'ctx>>,
loc: Location, loc: Location,
) { ) {
if let Some(exception) = exception { if let Some(exception) = exception {
unsafe { exception.store_location(generator, ctx, loc);
let int32 = ctx.ctx.i32_type();
let zero = int32.const_zero();
let exception = exception.into_pointer_value();
let file_ptr = ctx
.builder
.build_in_bounds_gep(exception, &[zero, int32.const_int(1, false)], "file_ptr")
.unwrap();
let filename = ctx.gen_string(generator, loc.file.0);
ctx.builder.build_store(file_ptr, filename).unwrap();
let row_ptr = ctx
.builder
.build_in_bounds_gep(exception, &[zero, int32.const_int(2, false)], "row_ptr")
.unwrap();
ctx.builder.build_store(row_ptr, int32.const_int(loc.row as u64, false)).unwrap();
let col_ptr = ctx
.builder
.build_in_bounds_gep(exception, &[zero, int32.const_int(3, false)], "col_ptr")
.unwrap();
ctx.builder.build_store(col_ptr, int32.const_int(loc.column as u64, false)).unwrap();
let current_fun = ctx.builder.get_insert_block().unwrap().get_parent().unwrap(); let current_fun = ctx.builder.get_insert_block().and_then(BasicBlock::get_parent).unwrap();
let fun_name = ctx.gen_string(generator, current_fun.get_name().to_str().unwrap()); let fun_name = ctx.gen_string(generator, current_fun.get_name().to_str().unwrap());
let name_ptr = ctx exception.store_func(ctx, fun_name);
.builder
.build_in_bounds_gep(exception, &[zero, int32.const_int(4, false)], "name_ptr")
.unwrap();
ctx.builder.build_store(name_ptr, fun_name).unwrap();
}
let raise = get_builtins(generator, ctx, "__nac3_raise"); let raise = get_builtins(generator, ctx, "__nac3_raise");
let exception = *exception; let exception = *exception;
ctx.build_call_or_invoke(raise, &[exception], "raise"); ctx.build_call_or_invoke(raise, &[exception.as_abi_value(ctx).into()], "raise");
} else { } else {
let resume = get_builtins(generator, ctx, "__nac3_resume"); let resume = get_builtins(generator, ctx, "__nac3_resume");
ctx.build_call_or_invoke(resume, &[], "resume"); ctx.build_call_or_invoke(resume, &[], "resume");
@ -1799,6 +1836,8 @@ pub fn gen_stmt<G: CodeGenerator>(
} else { } else {
return Ok(()); return Ok(());
}; };
let exc = ExceptionType::get_instance(generator, ctx)
.map_pointer_value(exc.into_pointer_value(), None);
gen_raise(generator, ctx, Some(&exc), stmt.location); gen_raise(generator, ctx, Some(&exc), stmt.location);
} else { } else {
gen_raise(generator, ctx, None, stmt.location); gen_raise(generator, ctx, None, stmt.location);

View File

@ -36,7 +36,6 @@ 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 {
@ -98,19 +97,18 @@ fn test_primitives() {
"}; "};
let statements = parse_program(source, FileName::default()).unwrap(); let statements = parse_program(source, FileName::default()).unwrap();
let composer = TopLevelComposer::new(Vec::new(), Vec::new(), ComposerConfig::default(), 32).0; let context = inkwell::context::Context::create();
let composer = TopLevelComposer::new(Vec::new(), Vec::new(), ComposerConfig::default(), 64).0;
let mut unifier = composer.unifier.clone(); let mut unifier = composer.unifier.clone();
let primitives = composer.primitives_ty; let primitives = composer.primitives_ty;
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 = Arc::new(Resolver { let resolver =
id_to_type: HashMap::new(), Arc::new(Resolver { id_to_type: HashMap::new(), id_to_def: RwLock::new(HashMap::new()) })
id_to_def: RwLock::new(HashMap::new()), as Arc<dyn SymbolResolver + Send + Sync>;
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(), context.i64_type()).into()];
let signature = FunSignature { let signature = FunSignature {
args: vec![ args: vec![
FuncArg { FuncArg {
@ -263,7 +261,8 @@ fn test_simple_call() {
"}; "};
let statements_2 = parse_program(source_2, FileName::default()).unwrap(); let statements_2 = parse_program(source_2, FileName::default()).unwrap();
let composer = TopLevelComposer::new(Vec::new(), Vec::new(), ComposerConfig::default(), 32).0; let context = inkwell::context::Context::create();
let composer = TopLevelComposer::new(Vec::new(), Vec::new(), ComposerConfig::default(), 64).0;
let mut unifier = composer.unifier.clone(); let mut unifier = composer.unifier.clone();
let primitives = composer.primitives_ty; let primitives = composer.primitives_ty;
let top_level = Arc::new(composer.make_top_level_context()); let top_level = Arc::new(composer.make_top_level_context());
@ -298,11 +297,7 @@ fn test_simple_call() {
loc: None, loc: None,
}))); })));
let resolver = Resolver { let resolver = Resolver { id_to_type: HashMap::new(), id_to_def: RwLock::new(HashMap::new()) };
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>;
@ -314,7 +309,7 @@ fn test_simple_call() {
unreachable!() unreachable!()
} }
let threads = vec![DefaultCodeGenerator::new("test".into(), 32).into()]; let threads = vec![DefaultCodeGenerator::new("test".into(), context.i64_type()).into()];
let mut function_data = FunctionData { let mut function_data = FunctionData {
resolver: resolver.clone(), resolver: resolver.clone(),
bound_variables: Vec::new(), bound_variables: Vec::new(),
@ -446,31 +441,34 @@ fn test_simple_call() {
#[test] #[test]
fn test_classes_list_type_new() { fn test_classes_list_type_new() {
let ctx = inkwell::context::Context::create(); let ctx = inkwell::context::Context::create();
let generator = DefaultCodeGenerator::new(String::new(), 64); let generator = DefaultCodeGenerator::new(String::new(), ctx.i64_type());
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_list = ListType::new(&generator, &ctx, llvm_i32.into()); let llvm_list = ListType::new_with_generator(&generator, &ctx, llvm_i32.into());
assert!(ListType::is_representable(llvm_list.as_base_type(), llvm_usize).is_ok()); assert!(ListType::is_representable(llvm_list.as_abi_type(), llvm_usize).is_ok());
} }
#[test] #[test]
fn test_classes_range_type_new() { fn test_classes_range_type_new() {
let ctx = inkwell::context::Context::create(); let ctx = inkwell::context::Context::create();
let generator = DefaultCodeGenerator::new(String::new(), ctx.i64_type());
let llvm_range = RangeType::new(&ctx); let llvm_usize = generator.get_size_type(&ctx);
assert!(RangeType::is_representable(llvm_range.as_base_type()).is_ok());
let llvm_range = RangeType::new_with_generator(&generator, &ctx);
assert!(RangeType::is_representable(llvm_range.as_abi_type(), llvm_usize).is_ok());
} }
#[test] #[test]
fn test_classes_ndarray_type_new() { fn test_classes_ndarray_type_new() {
let ctx = inkwell::context::Context::create(); let ctx = inkwell::context::Context::create();
let generator = DefaultCodeGenerator::new(String::new(), 64); let generator = DefaultCodeGenerator::new(String::new(), ctx.i64_type());
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(), None); let llvm_ndarray = NDArrayType::new_with_generator(&generator, &ctx, llvm_i32.into(), 2);
assert!(NDArrayType::is_representable(llvm_ndarray.as_base_type(), llvm_usize).is_ok()); assert!(NDArrayType::is_representable(llvm_ndarray.as_abi_type(), llvm_usize).is_ok());
} }

View File

@ -0,0 +1,257 @@
use inkwell::{
context::{AsContextRef, Context},
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType, StructType},
values::{IntValue, PointerValue, StructValue},
AddressSpace,
};
use itertools::Itertools;
use nac3core_derive::StructFields;
use super::{
structure::{check_struct_type_matches_fields, StructField, StructFields, StructProxyType},
ProxyType,
};
use crate::{
codegen::{values::ExceptionValue, CodeGenContext, CodeGenerator},
typecheck::typedef::{Type, TypeEnum},
};
/// Proxy type for an `Exception` in LLVM.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct ExceptionType<'ctx> {
ty: PointerType<'ctx>,
llvm_usize: IntType<'ctx>,
}
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
pub struct ExceptionStructFields<'ctx> {
/// The ID of the exception name.
#[value_type(i32_type())]
pub name: StructField<'ctx, IntValue<'ctx>>,
/// The file where the exception originated from.
#[value_type(get_struct_type("str").unwrap())]
pub file: StructField<'ctx, StructValue<'ctx>>,
/// The line number where the exception originated from.
#[value_type(i32_type())]
pub line: StructField<'ctx, IntValue<'ctx>>,
/// The column number where the exception originated from.
#[value_type(i32_type())]
pub col: StructField<'ctx, IntValue<'ctx>>,
/// The function name where the exception originated from.
#[value_type(get_struct_type("str").unwrap())]
pub func: StructField<'ctx, StructValue<'ctx>>,
/// The exception message.
#[value_type(get_struct_type("str").unwrap())]
pub message: StructField<'ctx, StructValue<'ctx>>,
#[value_type(i64_type())]
pub param0: StructField<'ctx, IntValue<'ctx>>,
#[value_type(i64_type())]
pub param1: StructField<'ctx, IntValue<'ctx>>,
#[value_type(i64_type())]
pub param2: StructField<'ctx, IntValue<'ctx>>,
}
impl<'ctx> ExceptionType<'ctx> {
/// Returns an instance of [`StructFields`] containing all field accessors for this type.
#[must_use]
fn fields(
ctx: impl AsContextRef<'ctx>,
llvm_usize: IntType<'ctx>,
) -> ExceptionStructFields<'ctx> {
ExceptionStructFields::new(ctx, llvm_usize)
}
/// Creates an LLVM type corresponding to the expected structure of an `Exception`.
#[must_use]
fn llvm_type(ctx: &'ctx Context, llvm_usize: IntType<'ctx>) -> PointerType<'ctx> {
assert!(ctx.get_struct_type("str").is_some());
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())
}
fn new_impl(ctx: &'ctx Context, llvm_usize: IntType<'ctx>) -> Self {
let llvm_str = Self::llvm_type(ctx, llvm_usize);
Self { ty: llvm_str, llvm_usize }
}
/// Creates an instance of [`ExceptionType`].
#[must_use]
pub fn new(ctx: &CodeGenContext<'ctx, '_>) -> Self {
Self::new_impl(ctx.ctx, ctx.get_size_type())
}
/// Creates an instance of [`ExceptionType`].
#[must_use]
pub fn new_with_generator<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
) -> Self {
Self::new_impl(ctx, generator.get_size_type(ctx))
}
/// Creates an [`ExceptionType`] from a [unifier type][Type].
#[must_use]
pub fn from_unifier_type(ctx: &mut CodeGenContext<'ctx, '_>, ty: Type) -> Self {
// Check unifier type
assert!(
matches!(&*ctx.unifier.get_ty_immutable(ty), TypeEnum::TObj { obj_id, .. } if *obj_id == ctx.primitives.exception.obj_id(&ctx.unifier).unwrap())
);
Self::new_impl(ctx.ctx, ctx.get_size_type())
}
/// Creates an [`ExceptionType`] from a [`StructType`] representing an `Exception`.
#[must_use]
pub fn from_struct_type(ty: StructType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
Self::from_pointer_type(ty.ptr_type(AddressSpace::default()), llvm_usize)
}
/// Creates an [`ExceptionType`] from a [`PointerType`] representing an `Exception`.
#[must_use]
pub fn from_pointer_type(ptr_ty: PointerType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
debug_assert!(Self::has_same_repr(ptr_ty, llvm_usize).is_ok());
Self { ty: ptr_ty, llvm_usize }
}
/// Returns an instance of [`ExceptionType`] by obtaining the LLVM representation of the builtin
/// `Exception` type.
#[must_use]
pub fn get_instance<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) -> Self {
Self::from_pointer_type(
ctx.get_llvm_type(generator, ctx.primitives.exception).into_pointer_type(),
ctx.get_size_type(),
)
}
/// Allocates an instance of [`ExceptionValue`] as if by calling `alloca` on the base type.
///
/// See [`ProxyType::raw_alloca`].
#[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 [`ExceptionValue`] as if by calling `alloca` on the base type.
///
/// See [`ProxyType::raw_alloca_var`].
#[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 [`ExceptionValue`].
#[must_use]
pub fn map_struct_value<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
value: StructValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_struct_value(
generator,
ctx,
value,
self.llvm_usize,
name,
)
}
/// Converts an existing value into a [`ExceptionValue`].
#[must_use]
pub fn map_pointer_value(
&self,
value: PointerValue<'ctx>,
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 ExceptionType<'ctx> {
type ABI = PointerType<'ctx>;
type Base = PointerType<'ctx>;
type Value = ExceptionValue<'ctx>;
fn is_representable(
llvm_ty: impl BasicType<'ctx>,
llvm_usize: IntType<'ctx>,
) -> Result<(), String> {
if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() {
Self::has_same_repr(ty, llvm_usize)
} else {
Err(format!("Expected pointer type, got {llvm_ty:?}"))
}
}
fn has_same_repr(ty: Self::Base, llvm_usize: IntType<'ctx>) -> Result<(), String> {
let ctx = ty.get_context();
let llvm_ty = ty.get_element_type();
let AnyTypeEnum::StructType(llvm_ty) = llvm_ty else {
return Err(format!("Expected struct type for `list` type, got {llvm_ty}"));
};
check_struct_type_matches_fields(Self::fields(ctx, llvm_usize), llvm_ty, "exception", &[])
}
fn alloca_type(&self) -> impl BasicType<'ctx> {
self.as_abi_type().get_element_type().into_struct_type()
}
fn as_base_type(&self) -> Self::Base {
self.ty
}
fn as_abi_type(&self) -> Self::ABI {
self.as_base_type()
}
}
impl<'ctx> StructProxyType<'ctx> for ExceptionType<'ctx> {
type StructFields = ExceptionStructFields<'ctx>;
fn get_fields(&self) -> Self::StructFields {
Self::fields(self.ty.get_context(), self.llvm_usize)
}
}
impl<'ctx> From<ExceptionType<'ctx>> for PointerType<'ctx> {
fn from(value: ExceptionType<'ctx>) -> Self {
value.as_base_type()
}
}

View File

@ -1,127 +1,311 @@
use inkwell::{ use inkwell::{
context::Context, context::Context,
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType}, types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType, StructType},
values::IntValue, values::{IntValue, PointerValue, StructValue},
AddressSpace, AddressSpace, IntPredicate, OptimizationLevel,
}; };
use itertools::Itertools;
use nac3core_derive::StructFields;
use super::ProxyType; use super::ProxyType;
use crate::codegen::{ use crate::{
values::{ArraySliceValue, ListValue, ProxyValue}, codegen::{
types::structure::{
check_struct_type_matches_fields, FieldIndexCounter, StructField, StructFields,
StructProxyType,
},
values::ListValue,
CodeGenContext, CodeGenerator, 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. /// Returns an instance of [`StructFields`] containing all field accessors for this type.
pub fn is_representable( #[must_use]
llvm_ty: PointerType<'ctx>, fn fields(item: BasicTypeEnum<'ctx>, llvm_usize: IntType<'ctx>) -> ListStructFields<'ctx> {
llvm_usize: IntType<'ctx>, ListStructFields::new_typed(item, llvm_usize)
) -> Result<(), String> {
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 list_size_ty = llvm_list_ty.get_field_type_at_index(0).unwrap();
let Ok(_) = PointerType::try_from(list_size_ty) else {
return Err(format!("Expected pointer type for `list.0`, got {list_size_ty}"));
};
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()
));
}
Ok(())
} }
/// 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: BasicTypeEnum<'ctx>, element_type: Option<BasicTypeEnum<'ctx>>,
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
) -> PointerType<'ctx> { ) -> PointerType<'ctx> {
// struct List { data: T*, size: size_t } let element_type = element_type.map_or(llvm_usize.into(), |ty| ty.as_basic_type_enum());
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())
} }
fn new_impl(
ctx: &'ctx Context,
element_type: Option<BasicTypeEnum<'ctx>>,
llvm_usize: IntType<'ctx>,
) -> Self {
let llvm_list = Self::llvm_type(ctx, element_type, llvm_usize);
Self { ty: llvm_list, item: element_type, llvm_usize }
}
/// Creates an instance of [`ListType`]. /// Creates an instance of [`ListType`].
#[must_use] #[must_use]
pub fn new<G: CodeGenerator + ?Sized>( pub fn new(ctx: &CodeGenContext<'ctx, '_>, element_type: &impl BasicType<'ctx>) -> Self {
Self::new_impl(ctx.ctx, Some(element_type.as_basic_type_enum()), ctx.get_size_type())
}
/// Creates an instance of [`ListType`].
#[must_use]
pub fn new_with_generator<G: CodeGenerator + ?Sized>(
generator: &G, generator: &G,
ctx: &'ctx Context, ctx: &'ctx Context,
element_type: BasicTypeEnum<'ctx>, element_type: BasicTypeEnum<'ctx>,
) -> Self { ) -> Self {
let llvm_usize = generator.get_size_type(ctx); Self::new_impl(ctx, Some(element_type.as_basic_type_enum()), generator.get_size_type(ctx))
let llvm_list = Self::llvm_type(ctx, 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(ctx: &CodeGenContext<'ctx, '_>) -> Self {
Self::new_impl(ctx.ctx, None, ctx.get_size_type())
}
/// Creates an instance of [`ListType`] with an unknown element type.
#[must_use]
pub fn new_untyped_with_generator<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
) -> Self {
Self::new_impl(ctx, None, generator.get_size_type(ctx))
}
/// 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 = ctx.get_size_type();
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::new_impl(ctx.ctx, llvm_elem_type, llvm_usize)
}
/// Creates an [`ListType`] from a [`StructType`].
#[must_use]
pub fn from_struct_type(ty: StructType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
Self::from_pointer_type(ty.ptr_type(AddressSpace::default()), llvm_usize)
} }
/// Creates an [`ListType`] from a [`PointerType`]. /// Creates an [`ListType`] from a [`PointerType`].
#[must_use] #[must_use]
pub fn from_type(ptr_ty: PointerType<'ctx>, llvm_usize: IntType<'ctx>) -> Self { pub fn from_pointer_type(ptr_ty: PointerType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
debug_assert!(Self::is_representable(ptr_ty, llvm_usize).is_ok()); debug_assert!(Self::has_same_repr(ptr_ty, llvm_usize).is_ok());
ListType { ty: ptr_ty, llvm_usize } let ctx = ptr_ty.get_context();
// 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.as_base_type() self.llvm_usize
.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) -> AnyTypeEnum<'ctx> { pub fn element_type(&self) -> Option<BasicTypeEnum<'ctx>> {
self.as_base_type() self.item
.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<G: CodeGenerator + ?Sized>( 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 [`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(generator, ctx, name), self.raw_alloca_var(generator, ctx, name),
self.llvm_usize,
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, 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, 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`].
#[must_use]
pub fn map_struct_value<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
value: StructValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_struct_value(
generator,
ctx,
value,
self.llvm_usize, self.llvm_usize,
name, name,
) )
@ -129,9 +313,9 @@ impl<'ctx> ListType<'ctx> {
/// 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_pointer_value(
&self, &self,
value: <<Self as ProxyType<'ctx>>::Value as ProxyValue<'ctx>>::Base, value: PointerValue<'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(value, self.llvm_usize, name) <Self as ProxyType<'ctx>>::Value::from_pointer_value(value, self.llvm_usize, name)
@ -139,64 +323,64 @@ impl<'ctx> ListType<'ctx> {
} }
impl<'ctx> ProxyType<'ctx> for ListType<'ctx> { impl<'ctx> ProxyType<'ctx> for ListType<'ctx> {
type ABI = PointerType<'ctx>;
type Base = PointerType<'ctx>; type Base = PointerType<'ctx>;
type Value = ListValue<'ctx>; type Value = ListValue<'ctx>;
fn is_type<G: CodeGenerator + ?Sized>( fn is_representable(
generator: &G,
ctx: &'ctx Context,
llvm_ty: impl BasicType<'ctx>, llvm_ty: impl BasicType<'ctx>,
llvm_usize: IntType<'ctx>,
) -> Result<(), String> { ) -> Result<(), String> {
if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() { if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() {
<Self as ProxyType<'ctx>>::is_representable(generator, ctx, ty) Self::has_same_repr(ty, llvm_usize)
} else { } else {
Err(format!("Expected pointer type, got {llvm_ty:?}")) Err(format!("Expected pointer type, got {llvm_ty:?}"))
} }
} }
fn is_representable<G: CodeGenerator + ?Sized>( fn has_same_repr(ty: Self::Base, llvm_usize: IntType<'ctx>) -> Result<(), String> {
generator: &G, let ctx = ty.get_context();
ctx: &'ctx Context,
llvm_ty: Self::Base, let llvm_ty = ty.get_element_type();
) -> Result<(), String> { let AnyTypeEnum::StructType(llvm_ty) = llvm_ty else {
Self::is_representable(llvm_ty, generator.get_size_type(ctx)) return Err(format!("Expected struct type for `list` type, got {llvm_ty}"));
};
let fields = ListStructFields::new(ctx, llvm_usize);
check_struct_type_matches_fields(
fields,
llvm_ty,
"list",
&[(fields.items.name(), &|ty| {
if ty.is_pointer_type() {
Ok(())
} else {
Err(format!("Expected T* for `list.items`, got {ty}"))
}
})],
)
} }
fn raw_alloca<G: CodeGenerator + ?Sized>( fn alloca_type(&self) -> impl BasicType<'ctx> {
&self, self.as_abi_type().get_element_type().into_struct_type()
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 {
self.ty self.ty
} }
fn as_abi_type(&self) -> Self::ABI {
self.as_base_type()
}
}
impl<'ctx> StructProxyType<'ctx> for ListType<'ctx> {
type StructFields = ListStructFields<'ctx>;
fn get_fields(&self) -> Self::StructFields {
Self::fields(self.item.unwrap_or(self.llvm_usize.into()), self.llvm_usize)
}
} }
impl<'ctx> From<ListType<'ctx>> for PointerType<'ctx> { impl<'ctx> From<ListType<'ctx>> for PointerType<'ctx> {

View File

@ -16,61 +16,120 @@
//! 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::{context::Context, types::BasicType, values::IntValue}; use inkwell::{
types::{BasicType, IntType},
values::{IntValue, PointerValue},
};
use super::{ use super::{
values::{ArraySliceValue, ProxyValue}, values::{ArraySliceValue, ProxyValue},
{CodeGenContext, CodeGenerator}, {CodeGenContext, CodeGenerator},
}; };
pub use exception::*;
pub use list::*; pub use list::*;
pub use option::*;
pub use range::*; pub use range::*;
pub use string::*;
pub use tuple::*;
mod exception;
mod list; mod list;
pub mod ndarray; pub mod ndarray;
mod option;
mod range; mod range;
mod string;
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.
pub trait ProxyType<'ctx>: Into<Self::Base> { pub trait ProxyType<'ctx>: Into<Self::Base> {
/// The LLVM type of which values of this type possess. This is usually a /// The ABI type of which values of this type possess.
/// [LLVM pointer type][PointerType] for any non-primitive types. type ABI: BasicType<'ctx>;
/// The LLVM type of which values of this type possess.
type Base: BasicType<'ctx>; type Base: BasicType<'ctx>;
/// The type of values represented by this type. /// The type of values represented by this type.
type Value: ProxyValue<'ctx, Type = Self>; type Value: ProxyValue<'ctx, Type = Self>;
fn is_type<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
llvm_ty: impl BasicType<'ctx>,
) -> Result<(), String>;
/// Checks whether `llvm_ty` can be represented by this [`ProxyType`]. /// Checks whether `llvm_ty` can be represented by this [`ProxyType`].
fn is_representable<G: CodeGenerator + ?Sized>( fn is_representable(
generator: &G, llvm_ty: impl BasicType<'ctx>,
ctx: &'ctx Context, llvm_usize: IntType<'ctx>,
llvm_ty: Self::Base,
) -> Result<(), String>; ) -> Result<(), String>;
/// Creates a new value of this type, returning the LLVM instance of this value. /// Checks whether the type represented by `ty` expresses the same type represented by this
fn raw_alloca<G: CodeGenerator + ?Sized>( /// [`ProxyType`].
fn has_same_repr(ty: Self::Base, llvm_usize: IntType<'ctx>) -> Result<(), String>;
/// Returns the type that should be used in `alloca` IR statements.
fn alloca_type(&self) -> impl BasicType<'ctx>;
/// 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>,
) -> <Self::Value as ProxyValue<'ctx>>::Base; ) -> PointerValue<'ctx> {
generator.gen_var_alloc(ctx, self.alloca_type().as_basic_type_enum(), name).unwrap()
}
/// Creates a new array value of this type, returning an [`ArraySliceValue`] encapsulating the /// Creates a new array value of this type by invoking `alloca` at the current builder location,
/// resulting array. /// returning an [`ArraySliceValue`] encapsulating the resulting array.
fn array_alloca<G: CodeGenerator + ?Sized>( fn array_alloca(
&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;
/// Returns this proxy as its ABI type, i.e. the expected type representation if a value of this
/// [`ProxyType`] is being passed into or returned from a function.
///
/// See [`CodeGenContext::get_llvm_abi_type`].
fn as_abi_type(&self) -> Self::ABI;
} }

View File

@ -0,0 +1,240 @@
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(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(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(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(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(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_abi_value(ctx)))
},
|generator, ctx| {
let ndarray = self.construct_numpy_array_from_list_copy_none_impl(
generator,
ctx,
(list_ty, list),
name,
);
Ok(Some(ndarray.as_abi_value(ctx)))
},
)
.unwrap()
.map(BasicValueEnum::into_pointer_value)
.unwrap();
NDArrayType::new(ctx, dtype, ndims).map_pointer_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_abi_value(ctx)))
},
|_generator, ctx| {
// No need to copy. Return `ndarray` itself.
Ok(Some(ndarray.as_abi_value(ctx)))
},
)
.unwrap()
.map(BasicValueEnum::into_pointer_value)
.unwrap();
ndarray.get_type().map_pointer_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_pointer_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_pointer_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

@ -0,0 +1,205 @@
use inkwell::{
context::{AsContextRef, Context},
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType, StructType},
values::{IntValue, PointerValue, StructValue},
AddressSpace,
};
use itertools::Itertools;
use nac3core_derive::StructFields;
use crate::codegen::{
types::{
structure::{check_struct_type_matches_fields, StructField, StructFields, StructProxyType},
ProxyType,
},
values::ndarray::ShapeEntryValue,
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> {
/// 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)
}
/// 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())
}
fn new_impl(ctx: &'ctx Context, llvm_usize: IntType<'ctx>) -> Self {
let llvm_ty = Self::llvm_type(ctx, llvm_usize);
Self { ty: llvm_ty, llvm_usize }
}
/// Creates an instance of [`ShapeEntryType`].
#[must_use]
pub fn new(ctx: &CodeGenContext<'ctx, '_>) -> Self {
Self::new_impl(ctx.ctx, ctx.get_size_type())
}
/// Creates an instance of [`ShapeEntryType`].
#[must_use]
pub fn new_with_generator<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
) -> Self {
Self::new_impl(ctx, generator.get_size_type(ctx))
}
/// Creates a [`ShapeEntryType`] from a [`StructType`] representing an `ShapeEntry`.
#[must_use]
pub fn from_struct_type(ty: StructType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
Self::from_pointer_type(ty.ptr_type(AddressSpace::default()), llvm_usize)
}
/// Creates a [`ShapeEntryType`] from a [`PointerType`] representing an `ShapeEntry`.
#[must_use]
pub fn from_pointer_type(ptr_ty: PointerType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
debug_assert!(Self::has_same_repr(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_struct_value<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
value: StructValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_struct_value(
generator,
ctx,
value,
self.llvm_usize,
name,
)
}
/// Converts an existing value into a [`ShapeEntryValue`].
#[must_use]
pub fn map_pointer_value(
&self,
value: PointerValue<'ctx>,
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 ABI = PointerType<'ctx>;
type Base = PointerType<'ctx>;
type Value = ShapeEntryValue<'ctx>;
fn is_representable(
llvm_ty: impl BasicType<'ctx>,
llvm_usize: IntType<'ctx>,
) -> Result<(), String> {
if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() {
Self::has_same_repr(ty, llvm_usize)
} else {
Err(format!("Expected pointer type, got {llvm_ty:?}"))
}
}
fn has_same_repr(ty: Self::Base, llvm_usize: IntType<'ctx>) -> Result<(), String> {
let ctx = ty.get_context();
let llvm_ndarray_ty = 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",
&[],
)
}
fn alloca_type(&self) -> impl BasicType<'ctx> {
self.as_abi_type().get_element_type().into_struct_type()
}
fn as_base_type(&self) -> Self::Base {
self.ty
}
fn as_abi_type(&self) -> Self::ABI {
self.as_base_type()
}
}
impl<'ctx> StructProxyType<'ctx> for ShapeEntryType<'ctx> {
type StructFields = ShapeEntryStructFields<'ctx>;
fn get_fields(&self) -> Self::StructFields {
Self::fields(self.ty.get_context(), self.llvm_usize)
}
}
impl<'ctx> From<ShapeEntryType<'ctx>> for PointerType<'ctx> {
fn from(value: ShapeEntryType<'ctx>) -> Self {
value.as_base_type()
}
}

View File

@ -1,7 +1,7 @@
use inkwell::{ use inkwell::{
context::Context, context::Context,
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType}, types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType, StructType},
values::{IntValue, PointerValue}, values::{IntValue, PointerValue, StructValue},
AddressSpace, AddressSpace,
}; };
use itertools::Itertools; use itertools::Itertools;
@ -13,10 +13,11 @@ use crate::{
types::{ types::{
structure::{ structure::{
check_struct_type_matches_fields, FieldIndexCounter, StructField, StructFields, check_struct_type_matches_fields, FieldIndexCounter, StructField, StructFields,
StructProxyType,
}, },
ProxyType, ProxyType,
}, },
values::{ndarray::ContiguousNDArrayValue, ArraySliceValue, ProxyValue}, values::ndarray::ContiguousNDArrayValue,
CodeGenContext, CodeGenerator, CodeGenContext, CodeGenerator,
}, },
toplevel::numpy::unpack_ndarray_var_tys, toplevel::numpy::unpack_ndarray_var_tys,
@ -31,7 +32,7 @@ pub struct ContiguousNDArrayType<'ctx> {
} }
#[derive(PartialEq, Eq, Clone, Copy, StructFields)] #[derive(PartialEq, Eq, Clone, Copy, StructFields)]
pub struct ContiguousNDArrayFields<'ctx> { pub struct ContiguousNDArrayStructFields<'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 +41,12 @@ pub struct ContiguousNDArrayFields<'ctx> {
pub data: StructField<'ctx, PointerValue<'ctx>>, pub data: StructField<'ctx, PointerValue<'ctx>>,
} }
impl<'ctx> ContiguousNDArrayFields<'ctx> { impl<'ctx> ContiguousNDArrayStructFields<'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();
ContiguousNDArrayFields { ContiguousNDArrayStructFields {
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,
@ -58,50 +59,13 @@ impl<'ctx> ContiguousNDArrayFields<'ctx> {
} }
impl<'ctx> ContiguousNDArrayType<'ctx> { impl<'ctx> ContiguousNDArrayType<'ctx> {
/// Checks whether `llvm_ty` represents a `ndarray` type, 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_ty = llvm_ty.get_element_type();
let AnyTypeEnum::StructType(llvm_ty) = llvm_ty else {
return Err(format!(
"Expected struct type for `ContiguousNDArray` type, got {llvm_ty}"
));
};
let fields = ContiguousNDArrayFields::new(ctx, llvm_usize);
check_struct_type_matches_fields(
fields,
llvm_ty,
"ContiguousNDArray",
&[(fields.data.name(), &|ty| {
if ty.is_pointer_type() {
Ok(())
} else {
Err(format!("Expected T* for `ContiguousNDArray.data`, got {ty}"))
}
})],
)
}
/// Returns an instance of [`StructFields`] containing all field accessors for this type. /// Returns an instance of [`StructFields`] containing all field accessors for this type.
#[must_use] #[must_use]
fn fields( fn fields(
item: BasicTypeEnum<'ctx>, item: BasicTypeEnum<'ctx>,
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
) -> ContiguousNDArrayFields<'ctx> { ) -> ContiguousNDArrayStructFields<'ctx> {
ContiguousNDArrayFields::new_typed(item, llvm_usize) ContiguousNDArrayStructFields::new_typed(item, llvm_usize)
}
/// See [`NDArrayType::fields`].
// TODO: Move this into e.g. StructProxyType
#[must_use]
pub fn get_fields(&self) -> ContiguousNDArrayFields<'ctx> {
Self::fields(self.item, self.llvm_usize)
} }
/// Creates an LLVM type corresponding to the expected structure of an `NDArray`. /// Creates an LLVM type corresponding to the expected structure of an `NDArray`.
@ -117,17 +81,26 @@ impl<'ctx> ContiguousNDArrayType<'ctx> {
ctx.struct_type(&field_tys, false).ptr_type(AddressSpace::default()) ctx.struct_type(&field_tys, false).ptr_type(AddressSpace::default())
} }
fn new_impl(ctx: &'ctx Context, item: BasicTypeEnum<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
let llvm_cndarray = Self::llvm_type(ctx, item, llvm_usize);
Self { ty: llvm_cndarray, item, llvm_usize }
}
/// Creates an instance of [`ContiguousNDArrayType`]. /// Creates an instance of [`ContiguousNDArrayType`].
#[must_use] #[must_use]
pub fn new<G: CodeGenerator + ?Sized>( pub fn new(ctx: &CodeGenContext<'ctx, '_>, item: &impl BasicType<'ctx>) -> Self {
Self::new_impl(ctx.ctx, item.as_basic_type_enum(), ctx.get_size_type())
}
/// Creates an instance of [`ContiguousNDArrayType`].
#[must_use]
pub fn new_with_generator<G: CodeGenerator + ?Sized>(
generator: &G, generator: &G,
ctx: &'ctx Context, ctx: &'ctx Context,
item: BasicTypeEnum<'ctx>, item: BasicTypeEnum<'ctx>,
) -> Self { ) -> Self {
let llvm_usize = generator.get_size_type(ctx); Self::new_impl(ctx, item, generator.get_size_type(ctx))
let llvm_cndarray = Self::llvm_type(ctx, item, llvm_usize);
Self { ty: llvm_cndarray, item, llvm_usize }
} }
/// Creates an [`ContiguousNDArrayType`] from a [unifier type][Type]. /// Creates an [`ContiguousNDArrayType`] from a [unifier type][Type].
@ -140,33 +113,63 @@ impl<'ctx> ContiguousNDArrayType<'ctx> {
let (dtype, _) = unpack_ndarray_var_tys(&mut ctx.unifier, ty); let (dtype, _) = unpack_ndarray_var_tys(&mut ctx.unifier, ty);
let llvm_dtype = ctx.get_llvm_type(generator, dtype); let llvm_dtype = ctx.get_llvm_type(generator, dtype);
let llvm_usize = generator.get_size_type(ctx.ctx);
Self { ty: Self::llvm_type(ctx.ctx, llvm_dtype, llvm_usize), item: llvm_dtype, llvm_usize } Self::new_impl(ctx.ctx, llvm_dtype, ctx.get_size_type())
}
/// Creates an [`ContiguousNDArrayType`] from a [`StructType`] representing an `NDArray`.
#[must_use]
pub fn from_struct_type(
ty: StructType<'ctx>,
item: BasicTypeEnum<'ctx>,
llvm_usize: IntType<'ctx>,
) -> Self {
Self::from_pointer_type(ty.ptr_type(AddressSpace::default()), item, llvm_usize)
} }
/// Creates an [`ContiguousNDArrayType`] from a [`PointerType`] representing an `NDArray`. /// Creates an [`ContiguousNDArrayType`] from a [`PointerType`] representing an `NDArray`.
#[must_use] #[must_use]
pub fn from_type( pub fn from_pointer_type(
ptr_ty: PointerType<'ctx>, ptr_ty: PointerType<'ctx>,
item: BasicTypeEnum<'ctx>, item: BasicTypeEnum<'ctx>,
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
) -> Self { ) -> Self {
debug_assert!(Self::is_representable(ptr_ty, llvm_usize).is_ok()); debug_assert!(Self::has_same_repr(ptr_ty, llvm_usize).is_ok());
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 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<G: CodeGenerator + ?Sized>( 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.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(generator, ctx, name), self.raw_alloca_var(generator, ctx, name),
self.item, self.item,
self.llvm_usize, self.llvm_usize,
name, name,
@ -175,9 +178,28 @@ impl<'ctx> ContiguousNDArrayType<'ctx> {
/// Converts an existing value into a [`ContiguousNDArrayValue`]. /// Converts an existing value into a [`ContiguousNDArrayValue`].
#[must_use] #[must_use]
pub fn map_value( pub fn map_struct_value<G: CodeGenerator + ?Sized>(
&self, &self,
value: <<Self as ProxyType<'ctx>>::Value as ProxyValue<'ctx>>::Base, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
value: StructValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_struct_value(
generator,
ctx,
value,
self.item,
self.llvm_usize,
name,
)
}
/// Converts an existing value into a [`ContiguousNDArrayValue`].
#[must_use]
pub fn map_pointer_value(
&self,
value: PointerValue<'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(
@ -190,64 +212,66 @@ impl<'ctx> ContiguousNDArrayType<'ctx> {
} }
impl<'ctx> ProxyType<'ctx> for ContiguousNDArrayType<'ctx> { impl<'ctx> ProxyType<'ctx> for ContiguousNDArrayType<'ctx> {
type ABI = PointerType<'ctx>;
type Base = PointerType<'ctx>; type Base = PointerType<'ctx>;
type Value = ContiguousNDArrayValue<'ctx>; type Value = ContiguousNDArrayValue<'ctx>;
fn is_type<G: CodeGenerator + ?Sized>( fn is_representable(
generator: &G,
ctx: &'ctx Context,
llvm_ty: impl BasicType<'ctx>, llvm_ty: impl BasicType<'ctx>,
llvm_usize: IntType<'ctx>,
) -> Result<(), String> { ) -> Result<(), String> {
if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() { if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() {
<Self as ProxyType<'ctx>>::is_representable(generator, ctx, ty) Self::has_same_repr(ty, llvm_usize)
} else { } else {
Err(format!("Expected pointer type, got {llvm_ty:?}")) Err(format!("Expected pointer type, got {llvm_ty:?}"))
} }
} }
fn is_representable<G: CodeGenerator + ?Sized>( fn has_same_repr(ty: Self::Base, llvm_usize: IntType<'ctx>) -> Result<(), String> {
generator: &G, let ctx = ty.get_context();
ctx: &'ctx Context,
llvm_ty: Self::Base, let llvm_ty = ty.get_element_type();
) -> Result<(), String> { let AnyTypeEnum::StructType(llvm_ty) = llvm_ty else {
Self::is_representable(llvm_ty, generator.get_size_type(ctx)) return Err(format!(
"Expected struct type for `ContiguousNDArray` type, got {llvm_ty}"
));
};
let fields = ContiguousNDArrayStructFields::new(ctx, llvm_usize);
check_struct_type_matches_fields(
fields,
llvm_ty,
"ContiguousNDArray",
&[(fields.data.name(), &|ty| {
if ty.is_pointer_type() {
Ok(())
} else {
Err(format!("Expected T* for `ContiguousNDArray.data`, got {ty}"))
}
})],
)
} }
fn raw_alloca<G: CodeGenerator + ?Sized>( fn alloca_type(&self) -> impl BasicType<'ctx> {
&self, self.as_abi_type().get_element_type().into_struct_type()
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 {
self.ty self.ty
} }
fn as_abi_type(&self) -> Self::ABI {
self.as_base_type()
}
}
impl<'ctx> StructProxyType<'ctx> for ContiguousNDArrayType<'ctx> {
type StructFields = ContiguousNDArrayStructFields<'ctx>;
fn get_fields(&self) -> Self::StructFields {
Self::fields(self.item, self.llvm_usize)
}
} }
impl<'ctx> From<ContiguousNDArrayType<'ctx>> for PointerType<'ctx> { impl<'ctx> From<ContiguousNDArrayType<'ctx>> for PointerType<'ctx> {

View File

@ -0,0 +1,236 @@
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

@ -1,7 +1,7 @@
use inkwell::{ use inkwell::{
context::{AsContextRef, Context}, context::{AsContextRef, Context},
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType}, types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType, StructType},
values::{IntValue, PointerValue}, values::{IntValue, PointerValue, StructValue},
AddressSpace, AddressSpace,
}; };
use itertools::Itertools; use itertools::Itertools;
@ -10,12 +10,12 @@ use nac3core_derive::StructFields;
use crate::codegen::{ use crate::codegen::{
types::{ types::{
structure::{check_struct_type_matches_fields, StructField, StructFields}, structure::{check_struct_type_matches_fields, StructField, StructFields, StructProxyType},
ProxyType, ProxyType,
}, },
values::{ values::{
ndarray::{NDIndexValue, RustNDIndex}, ndarray::{NDIndexValue, RustNDIndex},
ArrayLikeIndexer, ArraySliceValue, ProxyValue, ArrayLikeIndexer, ArraySliceValue,
}, },
CodeGenContext, CodeGenerator, CodeGenContext, CodeGenerator,
}; };
@ -35,25 +35,6 @@ pub struct NDIndexStructFields<'ctx> {
} }
impl<'ctx> NDIndexType<'ctx> { impl<'ctx> NDIndexType<'ctx> {
/// Checks whether `llvm_ty` represents a `ndindex` type, 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_ty = llvm_ty.get_element_type();
let AnyTypeEnum::StructType(llvm_ty) = llvm_ty else {
return Err(format!(
"Expected struct type for `ContiguousNDArray` type, got {llvm_ty}"
));
};
let fields = NDIndexStructFields::new(ctx, llvm_usize);
check_struct_type_matches_fields(fields, llvm_ty, "NDIndex", &[])
}
#[must_use] #[must_use]
fn fields( fn fields(
ctx: impl AsContextRef<'ctx>, ctx: impl AsContextRef<'ctx>,
@ -62,11 +43,6 @@ impl<'ctx> NDIndexType<'ctx> {
NDIndexStructFields::new(ctx, llvm_usize) NDIndexStructFields::new(ctx, llvm_usize)
} }
#[must_use]
pub fn get_fields(&self) -> NDIndexStructFields<'ctx> {
Self::fields(self.ty.get_context(), self.llvm_usize)
}
#[must_use] #[must_use]
fn llvm_type(ctx: &'ctx Context, llvm_usize: IntType<'ctx>) -> PointerType<'ctx> { fn llvm_type(ctx: &'ctx Context, llvm_usize: IntType<'ctx>) -> PointerType<'ctx> {
let field_tys = let field_tys =
@ -75,30 +51,64 @@ impl<'ctx> NDIndexType<'ctx> {
ctx.struct_type(&field_tys, false).ptr_type(AddressSpace::default()) ctx.struct_type(&field_tys, false).ptr_type(AddressSpace::default())
} }
#[must_use] fn new_impl(ctx: &'ctx Context, llvm_usize: IntType<'ctx>) -> Self {
pub fn new<G: CodeGenerator + ?Sized>(generator: &G, ctx: &'ctx Context) -> Self {
let llvm_usize = generator.get_size_type(ctx);
let llvm_ndindex = Self::llvm_type(ctx, llvm_usize); let llvm_ndindex = Self::llvm_type(ctx, llvm_usize);
Self { ty: llvm_ndindex, llvm_usize } Self { ty: llvm_ndindex, llvm_usize }
} }
#[must_use] #[must_use]
pub fn from_type(ptr_ty: PointerType<'ctx>, llvm_usize: IntType<'ctx>) -> Self { pub fn new(ctx: &CodeGenContext<'ctx, '_>) -> Self {
debug_assert!(Self::is_representable(ptr_ty, llvm_usize).is_ok()); Self::new_impl(ctx.ctx, ctx.get_size_type())
}
#[must_use]
pub fn new_with_generator<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
) -> Self {
Self::new_impl(ctx, generator.get_size_type(ctx))
}
#[must_use]
pub fn from_struct_type(ty: StructType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
Self::from_pointer_type(ty.ptr_type(AddressSpace::default()), llvm_usize)
}
#[must_use]
pub fn from_pointer_type(ptr_ty: PointerType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
debug_assert!(Self::has_same_repr(ptr_ty, llvm_usize).is_ok());
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<G: CodeGenerator + ?Sized>( 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 [`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(generator, ctx, name), self.raw_alloca_var(generator, ctx, name),
self.llvm_usize, self.llvm_usize,
name, name,
) )
@ -114,7 +124,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(generator, ctx, num_ndindices, None); let ndindices = self.array_alloca_var(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() {
@ -138,9 +148,26 @@ impl<'ctx> NDIndexType<'ctx> {
} }
#[must_use] #[must_use]
pub fn map_value( pub fn map_struct_value<G: CodeGenerator + ?Sized>(
&self, &self,
value: <<Self as ProxyType<'ctx>>::Value as ProxyValue<'ctx>>::Base, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
value: StructValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_struct_value(
generator,
ctx,
value,
self.llvm_usize,
name,
)
}
#[must_use]
pub fn map_pointer_value(
&self,
value: PointerValue<'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(value, self.llvm_usize, name) <Self as ProxyType<'ctx>>::Value::from_pointer_value(value, self.llvm_usize, name)
@ -148,64 +175,55 @@ impl<'ctx> NDIndexType<'ctx> {
} }
impl<'ctx> ProxyType<'ctx> for NDIndexType<'ctx> { impl<'ctx> ProxyType<'ctx> for NDIndexType<'ctx> {
type ABI = PointerType<'ctx>;
type Base = PointerType<'ctx>; type Base = PointerType<'ctx>;
type Value = NDIndexValue<'ctx>; type Value = NDIndexValue<'ctx>;
fn is_type<G: CodeGenerator + ?Sized>( fn is_representable(
generator: &G,
ctx: &'ctx Context,
llvm_ty: impl BasicType<'ctx>, llvm_ty: impl BasicType<'ctx>,
llvm_usize: IntType<'ctx>,
) -> Result<(), String> { ) -> Result<(), String> {
if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() { if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() {
<Self as ProxyType<'ctx>>::is_representable(generator, ctx, ty) Self::has_same_repr(ty, llvm_usize)
} else { } else {
Err(format!("Expected pointer type, got {llvm_ty:?}")) Err(format!("Expected pointer type, got {llvm_ty:?}"))
} }
} }
fn is_representable<G: CodeGenerator + ?Sized>( fn has_same_repr(ty: Self::Base, llvm_usize: IntType<'ctx>) -> Result<(), String> {
generator: &G, let ctx = ty.get_context();
ctx: &'ctx Context,
llvm_ty: Self::Base, let llvm_ty = ty.get_element_type();
) -> Result<(), String> { let AnyTypeEnum::StructType(llvm_ty) = llvm_ty else {
Self::is_representable(llvm_ty, generator.get_size_type(ctx)) return Err(format!(
"Expected struct type for `ContiguousNDArray` type, got {llvm_ty}"
));
};
let fields = NDIndexStructFields::new(ctx, llvm_usize);
check_struct_type_matches_fields(fields, llvm_ty, "NDIndex", &[])
} }
fn raw_alloca<G: CodeGenerator + ?Sized>( fn alloca_type(&self) -> impl BasicType<'ctx> {
&self, self.as_abi_type().get_element_type().into_struct_type()
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 {
self.ty self.ty
} }
fn as_abi_type(&self) -> Self::ABI {
self.as_base_type()
}
}
impl<'ctx> StructProxyType<'ctx> for NDIndexType<'ctx> {
type StructFields = NDIndexStructFields<'ctx>;
fn get_fields(&self) -> Self::StructFields {
Self::fields(self.ty.get_context(), self.llvm_usize)
}
} }
impl<'ctx> From<NDIndexType<'ctx>> for PointerType<'ctx> { impl<'ctx> From<NDIndexType<'ctx>> for PointerType<'ctx> {

View File

@ -0,0 +1,183 @@
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(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(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(ctx).construct(generator, ctx, *ndarray))
.collect_vec();
Ok((nditer, other_nditers))
},
|_, 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(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(())
},
|_, ctx, (out_nditer, in_nditers)| {
// Advance all iterators
out_nditer.next(ctx);
in_nditers.iter().for_each(|nditer| nditer.next(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(
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

@ -1,7 +1,7 @@
use inkwell::{ use inkwell::{
context::{AsContextRef, Context}, context::{AsContextRef, Context},
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType}, types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType, StructType},
values::{BasicValue, IntValue, PointerValue}, values::{BasicValue, IntValue, PointerValue, StructValue},
AddressSpace, AddressSpace,
}; };
use itertools::Itertools; use itertools::Itertools;
@ -9,23 +9,28 @@ use itertools::Itertools;
use nac3core_derive::StructFields; use nac3core_derive::StructFields;
use super::{ use super::{
structure::{check_struct_type_matches_fields, StructField, StructFields}, structure::{check_struct_type_matches_fields, StructField, StructFields, StructProxyType},
ProxyType, ProxyType,
}; };
use crate::{ use crate::{
codegen::{ codegen::{
values::{ndarray::NDArrayValue, ArraySliceValue, ProxyValue, TypedArrayLikeMutator}, values::{ndarray::NDArrayValue, 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.
@ -33,7 +38,7 @@ mod nditer;
pub struct NDArrayType<'ctx> { pub struct NDArrayType<'ctx> {
ty: PointerType<'ctx>, ty: PointerType<'ctx>,
dtype: BasicTypeEnum<'ctx>, dtype: BasicTypeEnum<'ctx>,
ndims: Option<u64>, ndims: u64,
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
} }
@ -57,26 +62,6 @@ pub struct NDArrayStructFields<'ctx> {
} }
impl<'ctx> NDArrayType<'ctx> { impl<'ctx> NDArrayType<'ctx> {
/// Checks whether `llvm_ty` represents a `ndarray` type, 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 `NDArray` 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. /// Returns an instance of [`StructFields`] containing all field accessors for this type.
#[must_use] #[must_use]
fn fields( fn fields(
@ -86,13 +71,6 @@ impl<'ctx> NDArrayType<'ctx> {
NDArrayStructFields::new(ctx, llvm_usize) NDArrayStructFields::new(ctx, llvm_usize)
} }
/// See [`NDArrayType::fields`].
// TODO: Move this into e.g. StructProxyType
#[must_use]
pub fn get_fields(&self, ctx: impl AsContextRef<'ctx>) -> NDArrayStructFields<'ctx> {
Self::fields(ctx, self.llvm_usize)
}
/// Creates an LLVM type corresponding to the expected structure of an `NDArray`. /// Creates an LLVM type corresponding to the expected structure of an `NDArray`.
#[must_use] #[must_use]
fn llvm_type(ctx: &'ctx Context, llvm_usize: IntType<'ctx>) -> PointerType<'ctx> { fn llvm_type(ctx: &'ctx Context, llvm_usize: IntType<'ctx>) -> PointerType<'ctx> {
@ -102,31 +80,85 @@ impl<'ctx> NDArrayType<'ctx> {
ctx.struct_type(&field_tys, false).ptr_type(AddressSpace::default()) ctx.struct_type(&field_tys, false).ptr_type(AddressSpace::default())
} }
/// Creates an instance of [`NDArrayType`]. fn new_impl(
#[must_use]
pub fn new<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context, ctx: &'ctx Context,
dtype: BasicTypeEnum<'ctx>, dtype: BasicTypeEnum<'ctx>,
ndims: Option<u64>, ndims: u64,
llvm_usize: IntType<'ctx>,
) -> Self { ) -> Self {
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, llvm_usize } NDArrayType { ty: llvm_ndarray, dtype, ndims, llvm_usize }
} }
/// Creates an instance of [`NDArrayType`].
#[must_use]
pub fn new(ctx: &CodeGenContext<'ctx, '_>, dtype: BasicTypeEnum<'ctx>, ndims: u64) -> Self {
Self::new_impl(ctx.ctx, dtype, ndims, ctx.get_size_type())
}
/// Creates an instance of [`NDArrayType`].
#[must_use]
pub fn new_with_generator<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
dtype: BasicTypeEnum<'ctx>,
ndims: u64,
) -> Self {
Self::new_impl(ctx, dtype, ndims, generator.get_size_type(ctx))
}
/// Creates an instance of [`NDArrayType`] as a result of a broadcast operation over one or more
/// `ndarray` operands.
#[must_use]
pub fn new_broadcast(
ctx: &CodeGenContext<'ctx, '_>,
dtype: BasicTypeEnum<'ctx>,
inputs: &[NDArrayType<'ctx>],
) -> Self {
assert!(!inputs.is_empty());
Self::new_impl(
ctx.ctx,
dtype,
inputs.iter().map(NDArrayType::ndims).max().unwrap(),
ctx.get_size_type(),
)
}
/// Creates an instance of [`NDArrayType`] as a result of a broadcast operation over one or more
/// `ndarray` operands.
#[must_use]
pub fn new_broadcast_with_generator<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
dtype: BasicTypeEnum<'ctx>,
inputs: &[NDArrayType<'ctx>],
) -> Self {
assert!(!inputs.is_empty());
Self::new_impl(
ctx,
dtype,
inputs.iter().map(NDArrayType::ndims).max().unwrap(),
generator.get_size_type(ctx),
)
}
/// 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(ctx: &CodeGenContext<'ctx, '_>, dtype: BasicTypeEnum<'ctx>) -> Self {
Self::new_impl(ctx.ctx, dtype, 0, ctx.get_size_type())
}
/// Creates an instance of [`NDArrayType`] with `ndims` of 0.
#[must_use]
pub fn new_unsized_with_generator<G: CodeGenerator + ?Sized>(
generator: &G, generator: &G,
ctx: &'ctx Context, ctx: &'ctx Context,
dtype: BasicTypeEnum<'ctx>, dtype: BasicTypeEnum<'ctx>,
) -> Self { ) -> Self {
let llvm_usize = generator.get_size_type(ctx); Self::new_impl(ctx, dtype, 0, generator.get_size_type(ctx))
let llvm_ndarray = Self::llvm_type(ctx, 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].
@ -139,26 +171,31 @@ impl<'ctx> NDArrayType<'ctx> {
let (dtype, ndims) = unpack_ndarray_var_tys(&mut ctx.unifier, ty); let (dtype, ndims) = unpack_ndarray_var_tys(&mut ctx.unifier, ty);
let llvm_dtype = ctx.get_llvm_type(generator, dtype); let llvm_dtype = ctx.get_llvm_type(generator, dtype);
let llvm_usize = generator.get_size_type(ctx.ctx);
let ndims = extract_ndims(&ctx.unifier, ndims); let ndims = extract_ndims(&ctx.unifier, ndims);
NDArrayType { Self::new_impl(ctx.ctx, llvm_dtype, ndims, ctx.get_size_type())
ty: Self::llvm_type(ctx.ctx, llvm_usize),
dtype: llvm_dtype,
ndims: Some(ndims),
llvm_usize,
} }
/// Creates an [`NDArrayType`] from a [`StructType`] representing an `NDArray`.
#[must_use]
pub fn from_struct_type(
ty: StructType<'ctx>,
dtype: BasicTypeEnum<'ctx>,
ndims: u64,
llvm_usize: IntType<'ctx>,
) -> Self {
Self::from_pointer_type(ty.ptr_type(AddressSpace::default()), dtype, ndims, llvm_usize)
} }
/// Creates an [`NDArrayType`] from a [`PointerType`] representing an `NDArray`. /// Creates an [`NDArrayType`] from a [`PointerType`] representing an `NDArray`.
#[must_use] #[must_use]
pub fn from_type( pub fn from_pointer_type(
ptr_ty: PointerType<'ctx>, ptr_ty: PointerType<'ctx>,
dtype: BasicTypeEnum<'ctx>, dtype: BasicTypeEnum<'ctx>,
ndims: Option<u64>, ndims: 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::has_same_repr(ptr_ty, llvm_usize).is_ok());
NDArrayType { ty: ptr_ty, dtype, ndims, llvm_usize } NDArrayType { ty: ptr_ty, dtype, ndims, llvm_usize }
} }
@ -177,20 +214,40 @@ 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) -> Option<u64> { pub fn ndims(&self) -> 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<G: CodeGenerator + ?Sized>( 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.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(generator, ctx, name), self.raw_alloca_var(generator, ctx, name),
self.dtype, self.dtype,
self.ndims, self.ndims,
self.llvm_usize, self.llvm_usize,
@ -214,15 +271,15 @@ 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(generator, ctx, name); let ndarray = self.alloca_var(generator, ctx, name);
let itemsize = ctx let itemsize = ctx
.builder .builder
.build_int_truncate_or_bit_cast(self.dtype.size_of().unwrap(), self.llvm_usize, "") .build_int_truncate_or_bit_cast(self.dtype.size_of().unwrap(), self.llvm_usize, "")
.unwrap(); .unwrap();
ndarray.store_itemsize(ctx, generator, itemsize); ndarray.store_itemsize(ctx, itemsize);
ndarray.store_ndims(ctx, generator, ndims); ndarray.store_ndims(ctx, ndims);
ndarray.create_shape(ctx, self.llvm_usize, ndims); ndarray.create_shape(ctx, self.llvm_usize, ndims);
ndarray.create_strides(ctx, self.llvm_usize, ndims); ndarray.create_strides(ctx, self.llvm_usize, ndims);
@ -247,35 +304,7 @@ 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 {
assert!(self.ndims.is_some(), "NDArrayType::construct can only be called on an instance with compile-time known ndims (self.ndims = Some(ndims))"); let ndims = self.llvm_usize.const_int(self.ndims, false);
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)
} }
@ -291,12 +320,12 @@ 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!(self.ndims.is_none_or(|ndims| shape.len() as u64 == ndims)); assert_eq!(shape.len() as u64, self.ndims);
let ndarray = Self::new(generator, ctx.ctx, self.dtype, Some(shape.len() as u64)) let ndarray = Self::new(ctx, self.dtype, 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 = ctx.get_size_type();
// Write shape // Write shape
let ndarray_shape = ndarray.shape(); let ndarray_shape = ndarray.shape();
@ -326,12 +355,12 @@ 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!(self.ndims.is_none_or(|ndims| shape.len() as u64 == ndims)); assert_eq!(shape.len() as u64, self.ndims);
let ndarray = Self::new(generator, ctx.ctx, self.dtype, Some(shape.len() as u64)) let ndarray = Self::new(ctx, self.dtype, 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 = ctx.get_size_type();
// Write shape // Write shape
let ndarray_shape = ndarray.shape(); let ndarray_shape = ndarray.shape();
@ -368,7 +397,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!(self.ndims.is_none_or(|ndims| ndims == 0)); assert_eq!(self.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();
@ -378,17 +407,37 @@ impl<'ctx> NDArrayType<'ctx> {
.build_pointer_cast(data, ctx.ctx.i8_type().ptr_type(AddressSpace::default()), "") .build_pointer_cast(data, ctx.ctx.i8_type().ptr_type(AddressSpace::default()), "")
.unwrap(); .unwrap();
let ndarray = Self::new_unsized(generator, ctx.ctx, value.get_type()) let ndarray =
.construct_uninitialized(generator, ctx, name); Self::new_unsized(ctx, value.get_type()).construct_uninitialized(generator, ctx, name);
ctx.builder.build_store(ndarray.ptr_to_data(ctx), data).unwrap(); ctx.builder.build_store(ndarray.ptr_to_data(ctx), data).unwrap();
ndarray ndarray
} }
/// Converts an existing value into a [`NDArrayValue`]. /// Converts an existing value into a [`NDArrayValue`].
#[must_use] #[must_use]
pub fn map_value( pub fn map_struct_value<G: CodeGenerator + ?Sized>(
&self, &self,
value: <<Self as ProxyType<'ctx>>::Value as ProxyValue<'ctx>>::Base, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
value: StructValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_struct_value(
generator,
ctx,
value,
self.dtype,
self.ndims,
self.llvm_usize,
name,
)
}
/// Converts an existing value into a [`NDArrayValue`].
#[must_use]
pub fn map_pointer_value(
&self,
value: PointerValue<'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(
@ -402,64 +451,56 @@ impl<'ctx> NDArrayType<'ctx> {
} }
impl<'ctx> ProxyType<'ctx> for NDArrayType<'ctx> { impl<'ctx> ProxyType<'ctx> for NDArrayType<'ctx> {
type ABI = PointerType<'ctx>;
type Base = PointerType<'ctx>; type Base = PointerType<'ctx>;
type Value = NDArrayValue<'ctx>; type Value = NDArrayValue<'ctx>;
fn is_type<G: CodeGenerator + ?Sized>( fn is_representable(
generator: &G,
ctx: &'ctx Context,
llvm_ty: impl BasicType<'ctx>, llvm_ty: impl BasicType<'ctx>,
llvm_usize: IntType<'ctx>,
) -> Result<(), String> { ) -> Result<(), String> {
if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() { if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() {
<Self as ProxyType<'ctx>>::is_representable(generator, ctx, ty) Self::has_same_repr(ty, llvm_usize)
} else { } else {
Err(format!("Expected pointer type, got {llvm_ty:?}")) Err(format!("Expected pointer type, got {llvm_ty:?}"))
} }
} }
fn is_representable<G: CodeGenerator + ?Sized>( fn has_same_repr(ty: Self::Base, llvm_usize: IntType<'ctx>) -> Result<(), String> {
generator: &G, let ctx = ty.get_context();
ctx: &'ctx Context,
llvm_ty: Self::Base, let llvm_ndarray_ty = ty.get_element_type();
) -> Result<(), String> { let AnyTypeEnum::StructType(llvm_ndarray_ty) = llvm_ndarray_ty else {
Self::is_representable(llvm_ty, generator.get_size_type(ctx)) return Err(format!("Expected struct type for `NDArray` type, got {llvm_ndarray_ty}"));
};
check_struct_type_matches_fields(
Self::fields(ctx, llvm_usize),
llvm_ndarray_ty,
"NDArray",
&[],
)
} }
fn raw_alloca<G: CodeGenerator + ?Sized>( fn alloca_type(&self) -> impl BasicType<'ctx> {
&self, self.as_abi_type().get_element_type().into_struct_type()
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 {
self.ty self.ty
} }
fn as_abi_type(&self) -> Self::ABI {
self.as_base_type()
}
}
impl<'ctx> StructProxyType<'ctx> for NDArrayType<'ctx> {
type StructFields = NDArrayStructFields<'ctx>;
fn get_fields(&self) -> Self::StructFields {
Self::fields(self.ty.get_context(), self.llvm_usize)
}
} }
impl<'ctx> From<NDArrayType<'ctx>> for PointerType<'ctx> { impl<'ctx> From<NDArrayType<'ctx>> for PointerType<'ctx> {

View File

@ -1,7 +1,7 @@
use inkwell::{ use inkwell::{
context::{AsContextRef, Context}, context::{AsContextRef, Context},
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType}, types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType, StructType},
values::{IntValue, PointerValue}, values::{IntValue, PointerValue, StructValue},
AddressSpace, AddressSpace,
}; };
use itertools::Itertools; use itertools::Itertools;
@ -11,10 +11,12 @@ use nac3core_derive::StructFields;
use super::ProxyType; use super::ProxyType;
use crate::codegen::{ use crate::codegen::{
irrt, irrt,
types::structure::{check_struct_type_matches_fields, StructField, StructFields}, types::structure::{
check_struct_type_matches_fields, StructField, StructFields, StructProxyType,
},
values::{ values::{
ndarray::{NDArrayValue, NDIterValue}, ndarray::{NDArrayValue, NDIterValue},
ArraySliceValue, ProxyValue, ArrayLikeValue, ArraySliceValue, ProxyValue, TypedArrayLikeAdapter,
}, },
CodeGenContext, CodeGenerator, CodeGenContext, CodeGenerator,
}; };
@ -44,39 +46,12 @@ pub struct NDIterStructFields<'ctx> {
} }
impl<'ctx> NDIterType<'ctx> { impl<'ctx> NDIterType<'ctx> {
/// Checks whether `llvm_ty` represents a `nditer` type, 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_ty = llvm_ty.get_element_type();
let AnyTypeEnum::StructType(llvm_ndarray_ty) = llvm_ty else {
return Err(format!("Expected struct type for `NDIter` type, got {llvm_ty}"));
};
check_struct_type_matches_fields(
Self::fields(ctx, llvm_usize),
llvm_ndarray_ty,
"NDIter",
&[],
)
}
/// Returns an instance of [`StructFields`] containing all field accessors for this type. /// Returns an instance of [`StructFields`] containing all field accessors for this type.
#[must_use] #[must_use]
fn fields(ctx: impl AsContextRef<'ctx>, llvm_usize: IntType<'ctx>) -> NDIterStructFields<'ctx> { fn fields(ctx: impl AsContextRef<'ctx>, llvm_usize: IntType<'ctx>) -> NDIterStructFields<'ctx> {
NDIterStructFields::new(ctx, llvm_usize) NDIterStructFields::new(ctx, llvm_usize)
} }
/// See [`NDIterType::fields`].
// TODO: Move this into e.g. StructProxyType
#[must_use]
pub fn get_fields(&self, ctx: impl AsContextRef<'ctx>) -> NDIterStructFields<'ctx> {
Self::fields(ctx, self.llvm_usize)
}
/// Creates an LLVM type corresponding to the expected structure of an `NDIter`. /// Creates an LLVM type corresponding to the expected structure of an `NDIter`.
#[must_use] #[must_use]
fn llvm_type(ctx: &'ctx Context, llvm_usize: IntType<'ctx>) -> PointerType<'ctx> { fn llvm_type(ctx: &'ctx Context, llvm_usize: IntType<'ctx>) -> PointerType<'ctx> {
@ -86,19 +61,37 @@ impl<'ctx> NDIterType<'ctx> {
ctx.struct_type(&field_tys, false).ptr_type(AddressSpace::default()) ctx.struct_type(&field_tys, false).ptr_type(AddressSpace::default())
} }
/// Creates an instance of [`NDIter`]. fn new_impl(ctx: &'ctx Context, llvm_usize: IntType<'ctx>) -> Self {
#[must_use]
pub fn new<G: CodeGenerator + ?Sized>(generator: &G, ctx: &'ctx Context) -> Self {
let llvm_usize = generator.get_size_type(ctx);
let llvm_nditer = Self::llvm_type(ctx, llvm_usize); let llvm_nditer = Self::llvm_type(ctx, llvm_usize);
Self { ty: llvm_nditer, llvm_usize } Self { ty: llvm_nditer, llvm_usize }
} }
/// Creates an instance of [`NDIter`].
#[must_use]
pub fn new(ctx: &CodeGenContext<'ctx, '_>) -> Self {
Self::new_impl(ctx.ctx, ctx.get_size_type())
}
/// Creates an instance of [`NDIter`].
#[must_use]
pub fn new_with_generator<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
) -> Self {
Self::new_impl(ctx, generator.get_size_type(ctx))
}
/// Creates an [`NDIterType`] from a [`StructType`] representing an `NDIter`.
#[must_use]
pub fn from_struct_type(ty: StructType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
Self::from_pointer_type(ty.ptr_type(AddressSpace::default()), llvm_usize)
}
/// Creates an [`NDIterType`] from a [`PointerType`] representing an `NDIter`. /// Creates an [`NDIterType`] from a [`PointerType`] representing an `NDIter`.
#[must_use] #[must_use]
pub fn from_type(ptr_ty: PointerType<'ctx>, llvm_usize: IntType<'ctx>) -> Self { pub fn from_pointer_type(ptr_ty: PointerType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
debug_assert!(Self::is_representable(ptr_ty, llvm_usize).is_ok()); debug_assert!(Self::has_same_repr(ptr_ty, llvm_usize).is_ok());
Self { ty: ptr_ty, llvm_usize } Self { ty: ptr_ty, llvm_usize }
} }
@ -109,8 +102,31 @@ 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<G: CodeGenerator + ?Sized>( pub fn alloca(
&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, '_>,
@ -119,7 +135,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(generator, ctx, name), self.raw_alloca_var(generator, ctx, name),
parent, parent,
indices, indices,
self.llvm_usize, self.llvm_usize,
@ -135,30 +151,48 @@ 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(generator, ctx, None); let nditer = self.raw_alloca_var(generator, ctx, None);
let ndims = ndarray.load_ndims(ctx); let ndims = self.llvm_usize.const_int(ndarray.get_type().ndims(), false);
// 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 as ProxyType<'ctx>>::Value::from_pointer_value( let nditer =
nditer, self.map_pointer_value(nditer, ndarray, indices.as_slice_value(ctx, generator), None);
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
} }
#[must_use] #[must_use]
pub fn map_value( pub fn map_struct_value<G: CodeGenerator + ?Sized>(
&self, &self,
value: <<Self as ProxyType<'ctx>>::Value as ProxyValue<'ctx>>::Base, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
value: StructValue<'ctx>,
parent: NDArrayValue<'ctx>,
indices: ArraySliceValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_struct_value(
generator,
ctx,
value,
parent,
indices,
self.llvm_usize,
name,
)
}
#[must_use]
pub fn map_pointer_value(
&self,
value: PointerValue<'ctx>,
parent: NDArrayValue<'ctx>, parent: NDArrayValue<'ctx>,
indices: ArraySliceValue<'ctx>, indices: ArraySliceValue<'ctx>,
name: Option<&'ctx str>, name: Option<&'ctx str>,
@ -174,64 +208,56 @@ impl<'ctx> NDIterType<'ctx> {
} }
impl<'ctx> ProxyType<'ctx> for NDIterType<'ctx> { impl<'ctx> ProxyType<'ctx> for NDIterType<'ctx> {
type ABI = PointerType<'ctx>;
type Base = PointerType<'ctx>; type Base = PointerType<'ctx>;
type Value = NDIterValue<'ctx>; type Value = NDIterValue<'ctx>;
fn is_type<G: CodeGenerator + ?Sized>( fn is_representable(
generator: &G,
ctx: &'ctx Context,
llvm_ty: impl BasicType<'ctx>, llvm_ty: impl BasicType<'ctx>,
llvm_usize: IntType<'ctx>,
) -> Result<(), String> { ) -> Result<(), String> {
if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() { if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() {
<Self as ProxyType<'ctx>>::is_representable(generator, ctx, ty) Self::has_same_repr(ty, llvm_usize)
} else { } else {
Err(format!("Expected pointer type, got {llvm_ty:?}")) Err(format!("Expected pointer type, got {llvm_ty:?}"))
} }
} }
fn is_representable<G: CodeGenerator + ?Sized>( fn has_same_repr(ty: Self::Base, llvm_usize: IntType<'ctx>) -> Result<(), String> {
generator: &G, let ctx = ty.get_context();
ctx: &'ctx Context,
llvm_ty: Self::Base, let llvm_ty = ty.get_element_type();
) -> Result<(), String> { let AnyTypeEnum::StructType(llvm_ndarray_ty) = llvm_ty else {
Self::is_representable(llvm_ty, generator.get_size_type(ctx)) return Err(format!("Expected struct type for `NDIter` type, got {llvm_ty}"));
};
check_struct_type_matches_fields(
Self::fields(ctx, llvm_usize),
llvm_ndarray_ty,
"NDIter",
&[],
)
} }
fn raw_alloca<G: CodeGenerator + ?Sized>( fn alloca_type(&self) -> impl BasicType<'ctx> {
&self, self.as_abi_type().get_element_type().into_struct_type()
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 {
self.ty self.ty
} }
fn as_abi_type(&self) -> Self::ABI {
self.as_base_type()
}
}
impl<'ctx> StructProxyType<'ctx> for NDIterType<'ctx> {
type StructFields = NDIterStructFields<'ctx>;
fn get_fields(&self) -> Self::StructFields {
Self::fields(self.ty.get_context(), self.llvm_usize)
}
} }
impl<'ctx> From<NDIterType<'ctx>> for PointerType<'ctx> { impl<'ctx> From<NDIterType<'ctx>> for PointerType<'ctx> {

View File

@ -0,0 +1,188 @@
use inkwell::{
context::Context,
types::{BasicType, BasicTypeEnum, IntType, PointerType},
values::{BasicValue, BasicValueEnum, PointerValue},
AddressSpace,
};
use super::ProxyType;
use crate::{
codegen::{values::OptionValue, CodeGenContext, CodeGenerator},
typecheck::typedef::{iter_type_vars, Type, TypeEnum},
};
/// Proxy type for an `Option` type in LLVM.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct OptionType<'ctx> {
ty: PointerType<'ctx>,
llvm_usize: IntType<'ctx>,
}
impl<'ctx> OptionType<'ctx> {
/// Creates an LLVM type corresponding to the expected structure of an `Option`.
#[must_use]
fn llvm_type(element_type: &impl BasicType<'ctx>) -> PointerType<'ctx> {
element_type.ptr_type(AddressSpace::default())
}
fn new_impl(element_type: &impl BasicType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
let llvm_option = Self::llvm_type(element_type);
Self { ty: llvm_option, llvm_usize }
}
/// Creates an instance of [`OptionType`].
#[must_use]
pub fn new(ctx: &CodeGenContext<'ctx, '_>, element_type: &impl BasicType<'ctx>) -> Self {
Self::new_impl(element_type, ctx.get_size_type())
}
/// Creates an instance of [`OptionType`].
#[must_use]
pub fn new_with_generator<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
element_type: &impl BasicType<'ctx>,
) -> Self {
Self::new_impl(element_type, generator.get_size_type(ctx))
}
/// Creates an [`OptionType`] 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 `element_type`
let elem_type = match &*ctx.unifier.get_ty_immutable(ty) {
TypeEnum::TObj { obj_id, params, .. }
if *obj_id == ctx.primitives.option.obj_id(&ctx.unifier).unwrap() =>
{
iter_type_vars(params).next().unwrap().ty
}
_ => panic!("Expected `option` type, but got {}", ctx.unifier.stringify(ty)),
};
let llvm_usize = ctx.get_size_type();
let llvm_elem_type = ctx.get_llvm_type(generator, elem_type);
Self::new_impl(&llvm_elem_type, llvm_usize)
}
/// Creates an [`OptionType`] from a [`PointerType`].
#[must_use]
pub fn from_pointer_type(ptr_ty: PointerType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
debug_assert!(Self::has_same_repr(ptr_ty, llvm_usize).is_ok());
Self { ty: ptr_ty, llvm_usize }
}
/// Returns the element type of this `Option` type.
#[must_use]
pub fn element_type(&self) -> BasicTypeEnum<'ctx> {
BasicTypeEnum::try_from(self.ty.get_element_type()).unwrap()
}
/// Allocates an [`OptionValue`] on the stack.
///
/// The returned value will be `Some(v)` if [`value` contains a value][Option::is_some],
/// otherwise `none` will be returned.
#[must_use]
pub fn construct<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
value: Option<BasicValueEnum<'ctx>>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
let ptr = if let Some(v) = value {
let pvar = self.raw_alloca_var(generator, ctx, name);
ctx.builder.build_store(pvar, v).unwrap();
pvar
} else {
self.ty.const_null()
};
self.map_pointer_value(ptr, name)
}
/// Allocates an [`OptionValue`] on the stack.
///
/// The returned value will always be `none`.
#[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 {
self.construct(generator, ctx, None, name)
}
/// Allocates an [`OptionValue`] on the stack.
///
/// The returned value will be set to `Some(value)`.
#[must_use]
pub fn construct_some_value<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
value: &impl BasicValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
self.construct(generator, ctx, Some(value.as_basic_value_enum()), name)
}
/// Converts an existing value into a [`OptionValue`].
#[must_use]
pub fn map_pointer_value(
&self,
value: PointerValue<'ctx>,
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 OptionType<'ctx> {
type ABI = PointerType<'ctx>;
type Base = PointerType<'ctx>;
type Value = OptionValue<'ctx>;
fn is_representable(
llvm_ty: impl BasicType<'ctx>,
llvm_usize: IntType<'ctx>,
) -> Result<(), String> {
if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() {
Self::has_same_repr(ty, llvm_usize)
} else {
Err(format!("Expected pointer type, got {llvm_ty:?}"))
}
}
fn has_same_repr(ty: Self::Base, _: IntType<'ctx>) -> Result<(), String> {
BasicTypeEnum::try_from(ty.get_element_type())
.map_err(|()| format!("Expected `ty` to be a BasicTypeEnum, got {ty}"))?;
Ok(())
}
fn alloca_type(&self) -> impl BasicType<'ctx> {
self.element_type()
}
fn as_base_type(&self) -> Self::Base {
self.ty
}
fn as_abi_type(&self) -> Self::ABI {
self.as_base_type()
}
}
impl<'ctx> From<OptionType<'ctx>> for PointerType<'ctx> {
fn from(value: OptionType<'ctx>) -> Self {
value.as_base_type()
}
}

View File

@ -1,26 +1,167 @@
use inkwell::{ use inkwell::{
context::Context, context::Context,
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType}, types::{AnyTypeEnum, ArrayType, BasicType, BasicTypeEnum, IntType, PointerType},
values::IntValue, values::{ArrayValue, PointerValue},
AddressSpace, AddressSpace,
}; };
use super::ProxyType; use super::ProxyType;
use crate::codegen::{ use crate::{
values::{ArraySliceValue, ProxyValue, RangeValue}, codegen::{
values::RangeValue,
{CodeGenContext, CodeGenerator}, {CodeGenContext, CodeGenerator},
},
typecheck::typedef::{Type, TypeEnum},
}; };
/// Proxy type for a `range` type in LLVM. /// Proxy type for a `range` type in LLVM.
#[derive(Debug, PartialEq, Eq, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct RangeType<'ctx> { pub struct RangeType<'ctx> {
ty: PointerType<'ctx>, ty: PointerType<'ctx>,
llvm_usize: IntType<'ctx>,
} }
impl<'ctx> RangeType<'ctx> { impl<'ctx> RangeType<'ctx> {
/// Checks whether `llvm_ty` represents a `range` type, returning [Err] if it does not. /// Creates an LLVM type corresponding to the expected structure of a `Range`.
pub fn is_representable(llvm_ty: PointerType<'ctx>) -> Result<(), String> { #[must_use]
let llvm_range_ty = llvm_ty.get_element_type(); fn llvm_type(ctx: &'ctx Context) -> PointerType<'ctx> {
// typedef int32_t Range[3];
let llvm_i32 = ctx.i32_type();
llvm_i32.array_type(3).ptr_type(AddressSpace::default())
}
fn new_impl(ctx: &'ctx Context, llvm_usize: IntType<'ctx>) -> Self {
let llvm_range = Self::llvm_type(ctx);
RangeType { ty: llvm_range, llvm_usize }
}
/// Creates an instance of [`RangeType`].
#[must_use]
pub fn new(ctx: &CodeGenContext<'ctx, '_>) -> Self {
Self::new_impl(ctx.ctx, ctx.get_size_type())
}
/// Creates an instance of [`RangeType`].
#[must_use]
pub fn new_with_generator<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
) -> Self {
Self::new_impl(ctx, generator.get_size_type(ctx))
}
/// Creates an [`RangeType`] from a [unifier type][Type].
#[must_use]
pub fn from_unifier_type(ctx: &mut CodeGenContext<'ctx, '_>, ty: Type) -> Self {
// Check unifier type
assert!(
matches!(&*ctx.unifier.get_ty_immutable(ty), TypeEnum::TObj { obj_id, .. } if *obj_id == ctx.primitives.range.obj_id(&ctx.unifier).unwrap())
);
Self::new(ctx)
}
/// Creates an [`RangeType`] from a [`ArrayType`].
#[must_use]
pub fn from_array_type(arr_ty: ArrayType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
Self::from_pointer_type(arr_ty.ptr_type(AddressSpace::default()), llvm_usize)
}
/// Creates an [`RangeType`] from a [`PointerType`].
#[must_use]
pub fn from_pointer_type(ptr_ty: PointerType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
debug_assert!(Self::has_same_repr(ptr_ty, llvm_usize).is_ok());
RangeType { ty: ptr_ty, llvm_usize }
}
/// Returns the type of all fields of this `range` type.
#[must_use]
pub fn value_type(&self) -> IntType<'ctx> {
self.as_abi_type().get_element_type().into_array_type().get_element_type().into_int_type()
}
/// Allocates an instance of [`RangeValue`] as if by calling `alloca` on the base type.
///
/// See [`ProxyType::raw_alloca`].
#[must_use]
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 [`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,
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 [`RangeValue`].
#[must_use]
pub fn map_array_value<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
value: ArrayValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_array_value(
generator,
ctx,
value,
self.llvm_usize,
name,
)
}
/// Converts an existing value into a [`RangeValue`].
#[must_use]
pub fn map_pointer_value(
&self,
value: PointerValue<'ctx>,
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 RangeType<'ctx> {
type ABI = PointerType<'ctx>;
type Base = PointerType<'ctx>;
type Value = RangeValue<'ctx>;
fn is_representable(
llvm_ty: impl BasicType<'ctx>,
llvm_usize: IntType<'ctx>,
) -> Result<(), String> {
if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() {
Self::has_same_repr(ty, llvm_usize)
} else {
Err(format!("Expected pointer type, got {llvm_ty:?}"))
}
}
fn has_same_repr(ty: Self::Base, _: IntType<'ctx>) -> Result<(), String> {
let llvm_range_ty = ty.get_element_type();
let AnyTypeEnum::ArrayType(llvm_range_ty) = llvm_range_ty else { let AnyTypeEnum::ArrayType(llvm_range_ty) = llvm_range_ty else {
return Err(format!("Expected array type for `range` type, got {llvm_range_ty}")); return Err(format!("Expected array type for `range` type, got {llvm_range_ty}"));
}; };
@ -47,120 +188,17 @@ impl<'ctx> RangeType<'ctx> {
Ok(()) Ok(())
} }
/// Creates an LLVM type corresponding to the expected structure of a `Range`. fn alloca_type(&self) -> impl BasicType<'ctx> {
#[must_use] self.as_abi_type().get_element_type().into_struct_type()
fn llvm_type(ctx: &'ctx Context) -> PointerType<'ctx> {
// typedef int32_t Range[3];
let llvm_i32 = ctx.i32_type();
llvm_i32.array_type(3).ptr_type(AddressSpace::default())
}
/// Creates an instance of [`RangeType`].
#[must_use]
pub fn new(ctx: &'ctx Context) -> Self {
let llvm_range = Self::llvm_type(ctx);
RangeType::from_type(llvm_range)
}
/// Creates an [`RangeType`] from a [`PointerType`].
#[must_use]
pub fn from_type(ptr_ty: PointerType<'ctx>) -> Self {
debug_assert!(Self::is_representable(ptr_ty).is_ok());
RangeType { ty: ptr_ty }
}
/// Returns the type of all fields of this `range` type.
#[must_use]
pub fn value_type(&self) -> IntType<'ctx> {
self.as_base_type().get_element_type().into_array_type().get_element_type().into_int_type()
}
/// Allocates an instance of [`RangeValue`] as if by calling `alloca` on the base type.
#[must_use]
pub fn alloca<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(generator, ctx, name),
name,
)
}
/// Converts an existing value into a [`RangeValue`].
#[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, name)
}
}
impl<'ctx> ProxyType<'ctx> for RangeType<'ctx> {
type Base = PointerType<'ctx>;
type Value = RangeValue<'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>(
_: &G,
_: &'ctx Context,
llvm_ty: Self::Base,
) -> Result<(), String> {
Self::is_representable(llvm_ty)
}
fn raw_alloca<G: CodeGenerator + ?Sized>(
&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 {
self.ty self.ty
} }
fn as_abi_type(&self) -> Self::ABI {
self.as_base_type()
}
} }
impl<'ctx> From<RangeType<'ctx>> for PointerType<'ctx> { impl<'ctx> From<RangeType<'ctx>> for PointerType<'ctx> {

View File

@ -0,0 +1,177 @@
use inkwell::{
context::Context,
types::{BasicType, BasicTypeEnum, IntType, PointerType, StructType},
values::{GlobalValue, IntValue, PointerValue, StructValue},
AddressSpace,
};
use itertools::Itertools;
use nac3core_derive::StructFields;
use super::{
structure::{check_struct_type_matches_fields, StructField, StructFields},
ProxyType,
};
use crate::codegen::{values::StringValue, CodeGenContext, CodeGenerator};
/// Proxy type for a `str` type in LLVM.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct StringType<'ctx> {
ty: StructType<'ctx>,
llvm_usize: IntType<'ctx>,
}
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
pub struct StringStructFields<'ctx> {
/// Pointer to the first character of the string.
#[value_type(i8_type().ptr_type(AddressSpace::default()))]
pub ptr: StructField<'ctx, PointerValue<'ctx>>,
/// Length of the string.
#[value_type(usize)]
pub len: StructField<'ctx, IntValue<'ctx>>,
}
impl<'ctx> StringType<'ctx> {
/// Returns an instance of [`StructFields`] containing all field accessors for this type.
#[must_use]
fn fields(llvm_usize: IntType<'ctx>) -> StringStructFields<'ctx> {
StringStructFields::new(llvm_usize.get_context(), llvm_usize)
}
/// Creates an LLVM type corresponding to the expected structure of a `str`.
#[must_use]
fn llvm_type(ctx: &'ctx Context, llvm_usize: IntType<'ctx>) -> StructType<'ctx> {
const NAME: &str = "str";
if let Some(t) = ctx.get_struct_type(NAME) {
t
} else {
let str_ty = ctx.opaque_struct_type(NAME);
let field_tys = Self::fields(llvm_usize).into_iter().map(|field| field.1).collect_vec();
str_ty.set_body(&field_tys, false);
str_ty
}
}
fn new_impl(ctx: &'ctx Context, llvm_usize: IntType<'ctx>) -> Self {
let llvm_str = Self::llvm_type(ctx, llvm_usize);
Self { ty: llvm_str, llvm_usize }
}
/// Creates an instance of [`StringType`].
#[must_use]
pub fn new(ctx: &CodeGenContext<'ctx, '_>) -> Self {
Self::new_impl(ctx.ctx, ctx.get_size_type())
}
/// Creates an instance of [`StringType`].
#[must_use]
pub fn new_with_generator<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
) -> Self {
Self::new_impl(ctx, generator.get_size_type(ctx))
}
/// Creates an [`StringType`] from a [`StructType`] representing a `str`.
#[must_use]
pub fn from_struct_type(ty: StructType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
debug_assert!(Self::has_same_repr(ty, llvm_usize).is_ok());
Self { ty, llvm_usize }
}
/// Creates an [`StringType`] from a [`PointerType`] representing a `str`.
#[must_use]
pub fn from_pointer_type(ptr_ty: PointerType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
Self::from_struct_type(ptr_ty.get_element_type().into_struct_type(), llvm_usize)
}
/// Returns the fields present in this [`StringType`].
#[must_use]
pub fn get_fields(&self) -> StringStructFields<'ctx> {
Self::fields(self.llvm_usize)
}
/// Constructs a global constant string.
#[must_use]
pub fn construct_constant(
&self,
ctx: &CodeGenContext<'ctx, '_>,
v: &str,
name: Option<&'ctx str>,
) -> StringValue<'ctx> {
let str_ptr = ctx
.builder
.build_global_string_ptr(v, "const")
.map(GlobalValue::as_pointer_value)
.unwrap();
let size = ctx.get_size_type().const_int(v.len() as u64, false);
self.map_struct_value(
self.as_abi_type().const_named_struct(&[str_ptr.into(), size.into()]),
name,
)
}
/// Converts an existing value into a [`StringValue`].
#[must_use]
pub fn map_struct_value(
&self,
value: StructValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_struct_value(value, self.llvm_usize, name)
}
/// Converts an existing value into a [`StringValue`].
#[must_use]
pub fn map_pointer_value(
&self,
ctx: &CodeGenContext<'ctx, '_>,
value: PointerValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value(ctx, value, self.llvm_usize, name)
}
}
impl<'ctx> ProxyType<'ctx> for StringType<'ctx> {
type ABI = StructType<'ctx>;
type Base = StructType<'ctx>;
type Value = StringValue<'ctx>;
fn is_representable(
llvm_ty: impl BasicType<'ctx>,
llvm_usize: IntType<'ctx>,
) -> Result<(), String> {
if let BasicTypeEnum::StructType(ty) = llvm_ty.as_basic_type_enum() {
Self::has_same_repr(ty, llvm_usize)
} else {
Err(format!("Expected structure type, got {llvm_ty:?}"))
}
}
fn has_same_repr(ty: Self::Base, llvm_usize: IntType<'ctx>) -> Result<(), String> {
check_struct_type_matches_fields(Self::fields(llvm_usize), ty, "str", &[])
}
fn alloca_type(&self) -> impl BasicType<'ctx> {
self.as_abi_type()
}
fn as_base_type(&self) -> Self::Base {
self.ty
}
fn as_abi_type(&self) -> Self::ABI {
self.as_base_type()
}
}
impl<'ctx> From<StringType<'ctx>> for StructType<'ctx> {
fn from(value: StringType<'ctx>) -> Self {
value.as_base_type()
}
}

View File

@ -2,12 +2,55 @@ use std::marker::PhantomData;
use inkwell::{ use inkwell::{
context::AsContextRef, context::AsContextRef,
types::{BasicTypeEnum, IntType, StructType}, types::{BasicTypeEnum, IntType, PointerType, StructType},
values::{BasicValue, BasicValueEnum, IntValue, PointerValue, StructValue}, values::{AggregateValueEnum, BasicValue, BasicValueEnum, IntValue, PointerValue, StructValue},
AddressSpace,
}; };
use itertools::Itertools;
use super::ProxyType;
use crate::codegen::CodeGenContext; use crate::codegen::CodeGenContext;
/// A LLVM type that is used to represent a corresponding structure-like type in NAC3.
pub trait StructProxyType<'ctx>: ProxyType<'ctx, Base = PointerType<'ctx>> {
/// The concrete type of [`StructFields`].
type StructFields: StructFields<'ctx>;
/// Whether this [`StructProxyType`] has the same LLVM type representation as
/// [`llvm_ty`][StructType].
fn has_same_struct_repr(
llvm_ty: StructType<'ctx>,
llvm_usize: IntType<'ctx>,
) -> Result<(), String> {
Self::has_same_pointer_repr(llvm_ty.ptr_type(AddressSpace::default()), llvm_usize)
}
/// Whether this [`StructProxyType`] has the same LLVM type representation as
/// [`llvm_ty`][PointerType].
fn has_same_pointer_repr(
llvm_ty: PointerType<'ctx>,
llvm_usize: IntType<'ctx>,
) -> Result<(), String> {
Self::has_same_repr(llvm_ty, llvm_usize)
}
/// Returns the fields present in this [`StructProxyType`].
#[must_use]
fn get_fields(&self) -> Self::StructFields;
/// Returns the [`StructType`].
#[must_use]
fn get_struct_type(&self) -> StructType<'ctx> {
self.as_base_type().get_element_type().into_struct_type()
}
/// Returns the [`PointerType`] representing this type.
#[must_use]
fn get_pointer_type(&self) -> PointerType<'ctx> {
self.as_base_type()
}
}
/// Trait indicating that the structure is a field-wise representation of an LLVM structure. /// Trait indicating that the structure is a field-wise representation of an LLVM structure.
/// ///
/// # Usage /// # Usage
@ -55,6 +98,20 @@ 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.
@ -146,17 +203,38 @@ where
/// Gets the value of this field for a given `obj`. /// Gets the value of this field for a given `obj`.
#[must_use] #[must_use]
pub fn get_from_value(&self, obj: StructValue<'ctx>) -> Value { pub fn extract_value(&self, ctx: &CodeGenContext<'ctx, '_>, obj: StructValue<'ctx>) -> Value {
obj.get_field_at_index(self.index).and_then(|value| Value::try_from(value).ok()).unwrap() Value::try_from(
ctx.builder
.build_extract_value(
obj,
self.index,
&format!("{}.{}", obj.get_name().to_str().unwrap(), self.name),
)
.unwrap(),
)
.unwrap()
} }
/// Sets the value of this field for a given `obj`. /// Sets the value of this field for a given `obj`.
pub fn set_for_value(&self, obj: StructValue<'ctx>, value: Value) { #[must_use]
obj.set_field_at_index(self.index, value); pub fn insert_value(
&self,
ctx: &CodeGenContext<'ctx, '_>,
obj: StructValue<'ctx>,
value: Value,
) -> StructValue<'ctx> {
let obj_name = obj.get_name().to_str().unwrap();
let new_obj_name = if obj_name.chars().all(char::is_numeric) { "" } else { obj_name };
ctx.builder
.build_insert_value(obj, value, self.index, new_obj_name)
.map(AggregateValueEnum::into_struct_value)
.unwrap()
} }
/// Gets the value of this field for a pointer-to-structure. /// Loads the value of this field for a pointer-to-structure.
pub fn get( pub fn load(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
pobj: PointerValue<'ctx>, pobj: PointerValue<'ctx>,
@ -172,8 +250,8 @@ where
.unwrap() .unwrap()
} }
/// Sets the value of this field for a pointer-to-structure. /// Stores the value of this field for a pointer-to-structure.
pub fn set( pub fn store(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
pobj: PointerValue<'ctx>, pobj: PointerValue<'ctx>,

View File

@ -0,0 +1,206 @@
use inkwell::{
context::Context,
types::{BasicType, BasicTypeEnum, IntType, PointerType, StructType},
values::{BasicValueEnum, PointerValue, StructValue},
};
use itertools::Itertools;
use super::ProxyType;
use crate::{
codegen::{values::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> {
/// 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)
}
fn new_impl(
ctx: &'ctx Context,
tys: &[BasicTypeEnum<'ctx>],
llvm_usize: IntType<'ctx>,
) -> Self {
let llvm_tuple = Self::llvm_type(ctx, tys);
Self { ty: llvm_tuple, llvm_usize }
}
/// Creates an instance of [`TupleType`].
#[must_use]
pub fn new(ctx: &CodeGenContext<'ctx, '_>, tys: &[impl BasicType<'ctx>]) -> Self {
Self::new_impl(
ctx.ctx,
&tys.iter().map(BasicType::as_basic_type_enum).collect_vec(),
ctx.get_size_type(),
)
}
/// Creates an instance of [`TupleType`].
#[must_use]
pub fn new_with_generator<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
tys: &[BasicTypeEnum<'ctx>],
) -> Self {
Self::new_impl(ctx, tys, generator.get_size_type(ctx))
}
/// 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 = ctx.get_size_type();
// 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_struct_type(struct_ty: StructType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
debug_assert!(Self::has_same_repr(struct_ty, llvm_usize).is_ok());
TupleType { ty: struct_ty, llvm_usize }
}
/// Creates an [`TupleType`] from a [`PointerType`].
#[must_use]
pub fn from_pointer_type(ptr_ty: PointerType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
Self::from_struct_type(ptr_ty.get_element_type().into_struct_type(), 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, name: Option<&'ctx str>) -> <Self as ProxyType<'ctx>>::Value {
self.map_struct_value(self.as_abi_type().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(name);
for (i, val) in values.into_iter().enumerate() {
value.insert_element(ctx, i as u32, val);
}
value
}
/// Converts an existing value into a [`ListValue`].
#[must_use]
pub fn map_struct_value(
&self,
value: StructValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_struct_value(value, self.llvm_usize, name)
}
/// Converts an existing value into a [`TupleValue`].
#[must_use]
pub fn map_pointer_value(
&self,
ctx: &CodeGenContext<'ctx, '_>,
value: PointerValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value(ctx, value, self.llvm_usize, name)
}
}
impl<'ctx> ProxyType<'ctx> for TupleType<'ctx> {
type ABI = StructType<'ctx>;
type Base = StructType<'ctx>;
type Value = TupleValue<'ctx>;
fn is_representable(
llvm_ty: impl BasicType<'ctx>,
llvm_usize: IntType<'ctx>,
) -> Result<(), String> {
if let BasicTypeEnum::StructType(ty) = llvm_ty.as_basic_type_enum() {
Self::has_same_repr(ty, llvm_usize)
} else {
Err(format!("Expected struct type, got {llvm_ty:?}"))
}
}
fn has_same_repr(_: Self::Base, _: IntType<'ctx>) -> Result<(), String> {
Ok(())
}
fn alloca_type(&self) -> impl BasicType<'ctx> {
self.as_base_type()
}
fn as_base_type(&self) -> Self::Base {
self.ty
}
fn as_abi_type(&self) -> Self::ABI {
self.as_base_type()
}
}
impl<'ctx> From<TupleType<'ctx>> for StructType<'ctx> {
fn from(value: TupleType<'ctx>) -> Self {
value.as_base_type()
}
}

View File

@ -1,7 +1,7 @@
use inkwell::{ use inkwell::{
context::{AsContextRef, Context, ContextRef}, context::{AsContextRef, Context, ContextRef},
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType}, types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType, StructType},
values::IntValue, values::{IntValue, PointerValue, StructValue},
AddressSpace, AddressSpace,
}; };
use itertools::Itertools; use itertools::Itertools;
@ -12,10 +12,11 @@ use crate::codegen::{
types::{ types::{
structure::{ structure::{
check_struct_type_matches_fields, FieldIndexCounter, StructField, StructFields, check_struct_type_matches_fields, FieldIndexCounter, StructField, StructFields,
StructProxyType,
}, },
ProxyType, ProxyType,
}, },
values::{utils::SliceValue, ArraySliceValue, ProxyValue}, values::utils::SliceValue,
CodeGenContext, CodeGenerator, CodeGenContext, CodeGenerator,
}; };
@ -27,7 +28,7 @@ pub struct SliceType<'ctx> {
} }
#[derive(PartialEq, Eq, Clone, Copy, StructFields)] #[derive(PartialEq, Eq, Clone, Copy, StructFields)]
pub struct SliceFields<'ctx> { pub struct SliceStructFields<'ctx> {
#[value_type(bool_type())] #[value_type(bool_type())]
pub start_defined: StructField<'ctx, IntValue<'ctx>>, pub start_defined: StructField<'ctx, IntValue<'ctx>>,
#[value_type(usize)] #[value_type(usize)]
@ -42,14 +43,14 @@ pub struct SliceFields<'ctx> {
pub step: StructField<'ctx, IntValue<'ctx>>, pub step: StructField<'ctx, IntValue<'ctx>>,
} }
impl<'ctx> SliceFields<'ctx> { impl<'ctx> SliceStructFields<'ctx> {
/// Creates a new instance of [`SliceFields`] with a custom integer type for its range values. /// Creates a new instance of [`SliceStructFields`] with a custom integer type for its range values.
#[must_use] #[must_use]
pub fn new_sized(ctx: &impl AsContextRef<'ctx>, int_ty: IntType<'ctx>) -> Self { pub fn new_sized(ctx: &impl AsContextRef<'ctx>, int_ty: IntType<'ctx>) -> Self {
let ctx = unsafe { ContextRef::new(ctx.as_ctx_ref()) }; let ctx = unsafe { ContextRef::new(ctx.as_ctx_ref()) };
let mut counter = FieldIndexCounter::default(); let mut counter = FieldIndexCounter::default();
SliceFields { SliceStructFields {
start_defined: StructField::create(&mut counter, "start_defined", ctx.bool_type()), start_defined: StructField::create(&mut counter, "start_defined", ctx.bool_type()),
start: StructField::create(&mut counter, "start", int_ty), start: StructField::create(&mut counter, "start", int_ty),
stop_defined: StructField::create(&mut counter, "stop_defined", ctx.bool_type()), stop_defined: StructField::create(&mut counter, "stop_defined", ctx.bool_type()),
@ -61,16 +62,173 @@ impl<'ctx> SliceFields<'ctx> {
} }
impl<'ctx> SliceType<'ctx> { impl<'ctx> SliceType<'ctx> {
/// Checks whether `llvm_ty` represents a `slice` type, returning [Err] if it does not. /// Creates an LLVM type corresponding to the expected structure of a `Slice`.
pub fn is_representable( #[must_use]
llvm_ty: PointerType<'ctx>, fn llvm_type(ctx: &'ctx Context, int_ty: IntType<'ctx>) -> PointerType<'ctx> {
let field_tys = SliceStructFields::new_sized(&int_ty.get_context(), int_ty)
.into_iter()
.map(|field| field.1)
.collect_vec();
ctx.struct_type(&field_tys, false).ptr_type(AddressSpace::default())
}
fn new_impl(ctx: &'ctx Context, int_ty: IntType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
let llvm_ty = Self::llvm_type(ctx, int_ty);
Self { ty: llvm_ty, int_ty, llvm_usize }
}
/// Creates an instance of [`SliceType`] with `int_ty` as its backing integer type.
#[must_use]
pub fn new(ctx: &CodeGenContext<'ctx, '_>, int_ty: IntType<'ctx>) -> Self {
Self::new_impl(ctx.ctx, int_ty, ctx.get_size_type())
}
/// Creates an instance of [`SliceType`] with `int_ty` as its backing integer type.
#[must_use]
pub fn new_with_generator<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
int_ty: IntType<'ctx>,
) -> Self {
Self::new_impl(ctx, int_ty, generator.get_size_type(ctx))
}
/// Creates an instance of [`SliceType`] with `usize` as its backing integer type.
#[must_use]
pub fn new_usize(ctx: &CodeGenContext<'ctx, '_>) -> Self {
Self::new_impl(ctx.ctx, ctx.get_size_type(), ctx.get_size_type())
}
/// Creates an instance of [`SliceType`] with `usize` as its backing integer type.
#[must_use]
pub fn new_usize_with_generator<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
) -> Self {
Self::new_impl(ctx, generator.get_size_type(ctx), generator.get_size_type(ctx))
}
/// Creates an [`SliceType`] from a [`StructType`] representing a `slice`.
#[must_use]
pub fn from_struct_type(
ty: StructType<'ctx>,
int_ty: IntType<'ctx>,
llvm_usize: IntType<'ctx>,
) -> Self {
Self::from_pointer_type(ty.ptr_type(AddressSpace::default()), int_ty, llvm_usize)
}
/// Creates an [`SliceType`] from a [`PointerType`] representing a `slice`.
#[must_use]
pub fn from_pointer_type(
ptr_ty: PointerType<'ctx>,
int_ty: IntType<'ctx>,
llvm_usize: IntType<'ctx>,
) -> Self {
debug_assert!(Self::has_same_repr(ptr_ty, int_ty).is_ok());
Self { ty: ptr_ty, int_ty, llvm_usize }
}
#[must_use]
pub fn element_type(&self) -> IntType<'ctx> {
self.int_ty
}
/// Allocates an instance of [`SliceValue`] as if by calling `alloca` on the base type.
///
/// See [`ProxyType::raw_alloca`].
#[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.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,
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.int_ty,
self.llvm_usize,
name,
)
}
/// Converts an existing value into a [`SliceValue`].
#[must_use]
pub fn map_struct_value<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
value: StructValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_struct_value(
generator,
ctx,
value,
self.int_ty,
self.llvm_usize,
name,
)
}
/// Converts an existing value into a [`ContiguousNDArrayValue`].
#[must_use]
pub fn map_pointer_value(
&self,
value: PointerValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value(
value,
self.int_ty,
self.llvm_usize,
name,
)
}
}
impl<'ctx> ProxyType<'ctx> for SliceType<'ctx> {
type ABI = PointerType<'ctx>;
type Base = PointerType<'ctx>;
type Value = SliceValue<'ctx>;
fn is_representable(
llvm_ty: impl BasicType<'ctx>,
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
) -> Result<(), String> { ) -> Result<(), String> {
let ctx = llvm_ty.get_context(); if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() {
Self::has_same_repr(ty, llvm_usize)
} else {
Err(format!("Expected pointer type, got {llvm_ty:?}"))
}
}
let fields = SliceFields::new(ctx, llvm_usize); fn has_same_repr(ty: Self::Base, llvm_usize: IntType<'ctx>) -> Result<(), String> {
let ctx = ty.get_context();
let llvm_ty = llvm_ty.get_element_type(); let fields = SliceStructFields::new(ctx, llvm_usize);
let llvm_ty = ty.get_element_type();
let AnyTypeEnum::StructType(llvm_ty) = llvm_ty else { let AnyTypeEnum::StructType(llvm_ty) = llvm_ty else {
return Err(format!("Expected struct type for `Slice` type, got {llvm_ty}")); return Err(format!("Expected struct type for `Slice` type, got {llvm_ty}"));
}; };
@ -105,146 +263,25 @@ impl<'ctx> SliceType<'ctx> {
) )
} }
// TODO: Move this into e.g. StructProxyType fn alloca_type(&self) -> impl BasicType<'ctx> {
#[must_use] self.as_abi_type().get_element_type().into_struct_type()
pub fn get_fields(&self) -> SliceFields<'ctx> {
SliceFields::new_sized(&self.int_ty.get_context(), self.int_ty)
}
/// Creates an LLVM type corresponding to the expected structure of a `Slice`.
#[must_use]
fn llvm_type(ctx: &'ctx Context, int_ty: IntType<'ctx>) -> PointerType<'ctx> {
let field_tys = SliceFields::new_sized(&int_ty.get_context(), int_ty)
.into_iter()
.map(|field| field.1)
.collect_vec();
ctx.struct_type(&field_tys, false).ptr_type(AddressSpace::default())
}
/// Creates an instance of [`SliceType`] with `int_ty` as its backing integer type.
#[must_use]
pub fn new(ctx: &'ctx Context, int_ty: IntType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
let llvm_ty = Self::llvm_type(ctx, int_ty);
Self { ty: llvm_ty, int_ty, llvm_usize }
}
/// Creates an instance of [`SliceType`] with `usize` as its backing integer type.
#[must_use]
pub fn new_usize<G: CodeGenerator + ?Sized>(generator: &G, ctx: &'ctx Context) -> Self {
let llvm_usize = generator.get_size_type(ctx);
Self::new(ctx, llvm_usize, llvm_usize)
}
/// Creates an [`SliceType`] from a [`PointerType`] representing a `slice`.
#[must_use]
pub fn from_type(
ptr_ty: PointerType<'ctx>,
int_ty: IntType<'ctx>,
llvm_usize: IntType<'ctx>,
) -> Self {
debug_assert!(Self::is_representable(ptr_ty, int_ty).is_ok());
Self { ty: ptr_ty, int_ty, llvm_usize }
}
#[must_use]
pub fn element_type(&self) -> IntType<'ctx> {
self.int_ty
}
/// Allocates an instance of [`ContiguousNDArrayValue`] as if by calling `alloca` on the base type.
#[must_use]
pub fn alloca<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(generator, ctx, name),
self.int_ty,
self.llvm_usize,
name,
)
}
/// Converts an existing value into a [`ContiguousNDArrayValue`].
#[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.int_ty,
self.llvm_usize,
name,
)
}
}
impl<'ctx> ProxyType<'ctx> for SliceType<'ctx> {
type Base = PointerType<'ctx>;
type Value = SliceValue<'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 raw_alloca<G: CodeGenerator + ?Sized>(
&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 {
self.ty self.ty
} }
fn as_abi_type(&self) -> Self::ABI {
self.as_base_type()
}
}
impl<'ctx> StructProxyType<'ctx> for SliceType<'ctx> {
type StructFields = SliceStructFields<'ctx>;
fn get_fields(&self) -> Self::StructFields {
SliceStructFields::new_sized(&self.ty.get_context(), self.int_ty)
}
} }
impl<'ctx> From<SliceType<'ctx>> for PointerType<'ctx> { impl<'ctx> From<SliceType<'ctx>> for PointerType<'ctx> {

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: &mut CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
generator: &mut G, generator: &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: &mut CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
generator: &mut G, generator: &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: &mut CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
generator: &mut G, generator: &G,
idx: &Index, idx: &Index,
value: BasicValueEnum<'ctx>, value: BasicValueEnum<'ctx>,
) { ) {
@ -130,32 +130,33 @@ 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, T, Index = IntValue<'ctx>>: pub trait TypedArrayLikeAccessor<'ctx, G: CodeGenerator + ?Sized, 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: &mut CodeGenContext<'ctx, '_>, ctx: &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<G: CodeGenerator + ?Sized>( unsafe fn get_typed_unchecked(
&self, &self,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
generator: &mut G, generator: &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, value) self.downcast_to_type(ctx, generator, value)
} }
/// Returns the data at the `idx`-th index. /// Returns the data at the `idx`-th index.
fn get_typed<G: CodeGenerator + ?Sized>( fn get_typed(
&self, &self,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
generator: &mut G, generator: &mut G,
@ -163,62 +164,63 @@ pub trait TypedArrayLikeAccessor<'ctx, T, Index = IntValue<'ctx>>:
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, value) self.downcast_to_type(ctx, generator, 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, T, Index = IntValue<'ctx>>: pub trait TypedArrayLikeMutator<'ctx, G: CodeGenerator + ?Sized, 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: &mut CodeGenContext<'ctx, '_>, ctx: &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<G: CodeGenerator + ?Sized>( unsafe fn set_typed_unchecked(
&self, &self,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
generator: &mut G, generator: &G,
idx: &Index, idx: &Index,
value: T, value: T,
) { ) {
let value = self.upcast_from_type(ctx, value); let value = self.upcast_from_type(ctx, generator, value);
unsafe { self.set_unchecked(ctx, generator, idx, value) } unsafe { self.set_unchecked(ctx, generator, idx, value) }
} }
/// Sets the data at the `idx`-th index. /// Sets the data at the `idx`-th index.
fn set_typed<G: CodeGenerator + ?Sized>( 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, value); let value = self.upcast_from_type(ctx, generator, 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.
pub struct TypedArrayLikeAdapter<'ctx, T, Adapted: ArrayLikeValue<'ctx> = ArraySliceValue<'ctx>> { #[derive(Copy, Clone)]
pub struct TypedArrayLikeAdapter<
'ctx,
G: CodeGenerator + ?Sized,
T,
Adapted: ArrayLikeValue<'ctx> = ArraySliceValue<'ctx>,
> {
adapted: Adapted, adapted: Adapted,
downcast_fn: ValueDowncastFn<'ctx, T>, downcast_fn: fn(&CodeGenContext<'ctx, '_>, &G, BasicValueEnum<'ctx>) -> T,
upcast_fn: ValueUpcastFn<'ctx, T>, upcast_fn: fn(&CodeGenContext<'ctx, '_>, &G, T) -> BasicValueEnum<'ctx>,
} }
impl<'ctx, T, Adapted> TypedArrayLikeAdapter<'ctx, T, Adapted> impl<'ctx, G: CodeGenerator + ?Sized, T, Adapted> TypedArrayLikeAdapter<'ctx, G, T, Adapted>
where where
Adapted: ArrayLikeValue<'ctx>, Adapted: ArrayLikeValue<'ctx>,
{ {
@ -229,61 +231,70 @@ 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: ValueDowncastFn<'ctx, T>, downcast_fn: fn(&CodeGenContext<'ctx, '_>, &G, BasicValueEnum<'ctx>) -> T,
upcast_fn: ValueUpcastFn<'ctx, T>, upcast_fn: fn(&CodeGenContext<'ctx, '_>, &G, T) -> BasicValueEnum<'ctx>,
) -> Self { ) -> Self {
TypedArrayLikeAdapter { adapted, downcast_fn, upcast_fn } TypedArrayLikeAdapter { adapted, downcast_fn, upcast_fn }
} }
} }
impl<'ctx, T, Adapted> ArrayLikeValue<'ctx> for TypedArrayLikeAdapter<'ctx, T, Adapted> impl<'ctx, G: CodeGenerator + ?Sized, T, Adapted> ArrayLikeValue<'ctx>
for TypedArrayLikeAdapter<'ctx, G, T, Adapted>
where where
Adapted: ArrayLikeValue<'ctx>, Adapted: ArrayLikeValue<'ctx>,
{ {
fn element_type<G: CodeGenerator + ?Sized>( fn element_type<CG: CodeGenerator + ?Sized>(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
generator: &G, generator: &CG,
) -> AnyTypeEnum<'ctx> { ) -> AnyTypeEnum<'ctx> {
self.adapted.element_type(ctx, generator) self.adapted.element_type(ctx, generator)
} }
fn base_ptr<G: CodeGenerator + ?Sized>( fn base_ptr<CG: CodeGenerator + ?Sized>(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
generator: &G, generator: &CG,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
self.adapted.base_ptr(ctx, generator) self.adapted.base_ptr(ctx, generator)
} }
fn size<G: CodeGenerator + ?Sized>( fn size<CG: CodeGenerator + ?Sized>(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
generator: &G, generator: &CG,
) -> 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, T, Index, Adapted> ArrayLikeIndexer<'ctx, Index> impl<'ctx, G: CodeGenerator + ?Sized, T, Index, Adapted> ArrayLikeIndexer<'ctx, Index>
for TypedArrayLikeAdapter<'ctx, T, Adapted> for TypedArrayLikeAdapter<'ctx, G, T, Adapted>
where where
Adapted: ArrayLikeIndexer<'ctx, Index>, Adapted: ArrayLikeIndexer<'ctx, Index>,
{ {
unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>( unsafe fn ptr_offset_unchecked<CG: CodeGenerator + ?Sized>(
&self, &self,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
generator: &mut G, generator: &CG,
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<G: CodeGenerator + ?Sized>( fn ptr_offset<CG: CodeGenerator + ?Sized>(
&self, &self,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
generator: &mut G, generator: &mut CG,
idx: &Index, idx: &Index,
name: Option<&str>, name: Option<&str>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
@ -291,44 +302,46 @@ where
} }
} }
impl<'ctx, T, Index, Adapted> UntypedArrayLikeAccessor<'ctx, Index> impl<'ctx, G: CodeGenerator + ?Sized, T, Index, Adapted> UntypedArrayLikeAccessor<'ctx, Index>
for TypedArrayLikeAdapter<'ctx, T, Adapted> for TypedArrayLikeAdapter<'ctx, G, T, Adapted>
where where
Adapted: UntypedArrayLikeAccessor<'ctx, Index>, Adapted: UntypedArrayLikeAccessor<'ctx, Index>,
{ {
} }
impl<'ctx, T, Index, Adapted> UntypedArrayLikeMutator<'ctx, Index> impl<'ctx, G: CodeGenerator + ?Sized, T, Index, Adapted> UntypedArrayLikeMutator<'ctx, Index>
for TypedArrayLikeAdapter<'ctx, T, Adapted> for TypedArrayLikeAdapter<'ctx, G, T, Adapted>
where where
Adapted: UntypedArrayLikeMutator<'ctx, Index>, Adapted: UntypedArrayLikeMutator<'ctx, Index>,
{ {
} }
impl<'ctx, T, Index, Adapted> TypedArrayLikeAccessor<'ctx, T, Index> impl<'ctx, G: CodeGenerator + ?Sized, T, Index, Adapted> TypedArrayLikeAccessor<'ctx, G, T, Index>
for TypedArrayLikeAdapter<'ctx, T, Adapted> for TypedArrayLikeAdapter<'ctx, G, T, Adapted>
where where
Adapted: UntypedArrayLikeAccessor<'ctx, Index>, Adapted: UntypedArrayLikeAccessor<'ctx, Index>,
{ {
fn downcast_to_type( fn downcast_to_type(
&self, &self,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
generator: &G,
value: BasicValueEnum<'ctx>, value: BasicValueEnum<'ctx>,
) -> T { ) -> T {
(self.downcast_fn)(ctx, value) (self.downcast_fn)(ctx, generator, value)
} }
} }
impl<'ctx, T, Index, Adapted> TypedArrayLikeMutator<'ctx, T, Index> impl<'ctx, G: CodeGenerator + ?Sized, T, Index, Adapted> TypedArrayLikeMutator<'ctx, G, T, Index>
for TypedArrayLikeAdapter<'ctx, T, Adapted> for TypedArrayLikeAdapter<'ctx, G, T, Adapted>
where where
Adapted: UntypedArrayLikeMutator<'ctx, Index>, Adapted: UntypedArrayLikeMutator<'ctx, Index>,
{ {
fn upcast_from_type( fn upcast_from_type(
&self, &self,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
generator: &G,
value: T, value: T,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
(self.upcast_fn)(ctx, value) (self.upcast_fn)(ctx, generator, value)
} }
} }
@ -384,12 +397,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: &mut CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
generator: &mut G, generator: &G,
idx: &IntValue<'ctx>, idx: &IntValue<'ctx>,
name: Option<&str>, name: Option<&str>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
let var_name = name.map(|v| format!("{v}.addr")).unwrap_or_default(); let var_name = name.or(self.2).map(|v| format!("{v}.addr")).unwrap_or_default();
unsafe { unsafe {
ctx.builder ctx.builder
@ -405,7 +418,7 @@ impl<'ctx> ArrayLikeIndexer<'ctx> for ArraySliceValue<'ctx> {
idx: &IntValue<'ctx>, idx: &IntValue<'ctx>,
name: Option<&str>, name: Option<&str>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
debug_assert_eq!(idx.get_type(), generator.get_size_type(ctx.ctx)); debug_assert_eq!(idx.get_type(), ctx.get_size_type());
let size = self.size(ctx, generator); let size = self.size(ctx, generator);
let in_range = ctx.builder.build_int_compare(IntPredicate::ULT, *idx, size, "").unwrap(); let in_range = ctx.builder.build_int_compare(IntPredicate::ULT, *idx, size, "").unwrap();

View File

@ -0,0 +1,188 @@
use inkwell::{
types::IntType,
values::{IntValue, PointerValue, StructValue},
};
use itertools::Itertools;
use nac3parser::ast::Location;
use super::{structure::StructProxyValue, ProxyValue, StringValue};
use crate::codegen::{
types::{
structure::{StructField, StructProxyType},
ExceptionType,
},
CodeGenContext, CodeGenerator,
};
/// Proxy type for accessing an `Exception` value in LLVM.
#[derive(Copy, Clone)]
pub struct ExceptionValue<'ctx> {
value: PointerValue<'ctx>,
llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>,
}
impl<'ctx> ExceptionValue<'ctx> {
/// Creates an [`ExceptionValue`] from a [`StructValue`].
#[must_use]
pub fn from_struct_value<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
val: StructValue<'ctx>,
llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>,
) -> Self {
let pval = generator
.gen_var_alloc(
ctx,
val.get_type().into(),
name.map(|name| format!("{name}.addr")).as_deref(),
)
.unwrap();
ctx.builder.build_store(pval, val).unwrap();
Self::from_pointer_value(pval, llvm_usize, name)
}
/// Creates an [`ExceptionValue`] 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_instance(ptr, llvm_usize).is_ok());
Self { value: ptr, llvm_usize, name }
}
fn name_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
self.get_type().get_fields().name
}
/// Stores the ID of the exception name into this instance.
pub fn store_name(&self, ctx: &CodeGenContext<'ctx, '_>, name: IntValue<'ctx>) {
debug_assert_eq!(name.get_type(), ctx.ctx.i32_type());
self.name_field().store(ctx, self.value, name, self.name);
}
fn file_field(&self) -> StructField<'ctx, StructValue<'ctx>> {
self.get_type().get_fields().file
}
/// Stores the file name of the exception source into this instance.
pub fn store_file(&self, ctx: &CodeGenContext<'ctx, '_>, file: StructValue<'ctx>) {
debug_assert!(StringValue::is_instance(file, self.llvm_usize).is_ok());
self.file_field().store(ctx, self.value, file, self.name);
}
fn line_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
self.get_type().get_fields().line
}
fn col_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
self.get_type().get_fields().col
}
/// Stores the [location][Location] of the exception source into this instance.
pub fn store_location<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
location: Location,
) {
let llvm_i32 = ctx.ctx.i32_type();
let filename = ctx.gen_string(generator, location.file.0);
self.store_file(ctx, filename);
self.line_field().store(
ctx,
self.value,
llvm_i32.const_int(location.row as u64, false),
self.name,
);
self.col_field().store(
ctx,
self.value,
llvm_i32.const_int(location.column as u64, false),
self.name,
);
}
fn func_field(&self) -> StructField<'ctx, StructValue<'ctx>> {
self.get_type().get_fields().func
}
/// Stores the function name of the exception source into this instance.
pub fn store_func(&self, ctx: &CodeGenContext<'ctx, '_>, func: StructValue<'ctx>) {
debug_assert!(StringValue::is_instance(func, self.llvm_usize).is_ok());
self.func_field().store(ctx, self.value, func, self.name);
}
fn message_field(&self) -> StructField<'ctx, StructValue<'ctx>> {
self.get_type().get_fields().message
}
/// Stores the exception message into this instance.
pub fn store_message(&self, ctx: &CodeGenContext<'ctx, '_>, message: StructValue<'ctx>) {
debug_assert!(StringValue::is_instance(message, self.llvm_usize).is_ok());
self.message_field().store(ctx, self.value, message, self.name);
}
fn param0_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
self.get_type().get_fields().param0
}
fn param1_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
self.get_type().get_fields().param1
}
fn param2_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
self.get_type().get_fields().param2
}
/// Stores the parameters of the exception into this instance.
///
/// If the parameter does not exist, pass `i64 0` in the parameter slot.
pub fn store_params(&self, ctx: &CodeGenContext<'ctx, '_>, params: &[IntValue<'ctx>; 3]) {
debug_assert!(params.iter().all(|p| p.get_type() == ctx.ctx.i64_type()));
[self.param0_field(), self.param1_field(), self.param2_field()]
.into_iter()
.zip_eq(params)
.for_each(|(field, param)| {
field.store(ctx, self.value, *param, self.name);
});
}
}
impl<'ctx> ProxyValue<'ctx> for ExceptionValue<'ctx> {
type ABI = PointerValue<'ctx>;
type Base = PointerValue<'ctx>;
type Type = ExceptionType<'ctx>;
fn get_type(&self) -> Self::Type {
Self::Type::from_pointer_type(self.value.get_type(), self.llvm_usize)
}
fn as_base_value(&self) -> Self::Base {
self.value
}
fn as_abi_value(&self, _: &CodeGenContext<'ctx, '_>) -> Self::ABI {
self.as_base_value()
}
}
impl<'ctx> StructProxyValue<'ctx> for ExceptionValue<'ctx> {}
impl<'ctx> From<ExceptionValue<'ctx>> for PointerValue<'ctx> {
fn from(value: ExceptionValue<'ctx>) -> Self {
value.as_base_value()
}
}

View File

@ -1,14 +1,18 @@
use inkwell::{ use inkwell::{
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType}, types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType},
values::{BasicValueEnum, IntValue, PointerValue}, values::{BasicValueEnum, IntValue, PointerValue, StructValue},
AddressSpace, IntPredicate, AddressSpace, IntPredicate,
}; };
use super::{ use super::{
ArrayLikeIndexer, ArrayLikeValue, ProxyValue, UntypedArrayLikeAccessor, UntypedArrayLikeMutator, structure::StructProxyValue, ArrayLikeIndexer, ArrayLikeValue, ProxyValue,
UntypedArrayLikeAccessor, UntypedArrayLikeMutator,
}; };
use crate::codegen::{ use crate::codegen::{
types::ListType, types::{
structure::{StructField, StructProxyType},
ListType, ProxyType,
},
{CodeGenContext, CodeGenerator}, {CodeGenContext, CodeGenerator},
}; };
@ -21,13 +25,24 @@ pub struct ListValue<'ctx> {
} }
impl<'ctx> ListValue<'ctx> { impl<'ctx> ListValue<'ctx> {
/// Checks whether `value` is an instance of `list`, returning [Err] if `value` is not an /// Creates an [`ListValue`] from a [`PointerValue`].
/// instance. #[must_use]
pub fn is_representable( pub fn from_struct_value<G: CodeGenerator + ?Sized>(
value: PointerValue<'ctx>, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
val: StructValue<'ctx>,
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
) -> Result<(), String> { name: Option<&'ctx str>,
ListType::is_representable(value.get_type(), llvm_usize) ) -> Self {
let pval = generator
.gen_var_alloc(
ctx,
val.get_type().into(),
name.map(|name| format!("{name}.addr")).as_deref(),
)
.unwrap();
ctx.builder.build_store(pval, val).unwrap();
Self::from_pointer_value(pval, llvm_usize, name)
} }
/// Creates an [`ListValue`] from a [`PointerValue`]. /// Creates an [`ListValue`] from a [`PointerValue`].
@ -37,53 +52,25 @@ impl<'ctx> ListValue<'ctx> {
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>, name: Option<&'ctx str>,
) -> Self { ) -> Self {
debug_assert!(Self::is_representable(ptr, llvm_usize).is_ok()); debug_assert!(Self::is_instance(ptr, llvm_usize).is_ok());
ListValue { value: ptr, llvm_usize, name } ListValue { value: ptr, llvm_usize, name }
} }
/// Returns the double-indirection pointer to the `data` array, as if by calling `getelementptr` fn items_field(&self) -> StructField<'ctx, PointerValue<'ctx>> {
/// on the field. self.get_type().get_fields().items
fn pptr_to_data(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
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>) {
ctx.builder.build_store(self.pptr_to_data(ctx), data).unwrap(); self.items_field().store(ctx, self.value, data, self.name);
} }
/// 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 `size` is [None], the size stored in the field of this instance is used instead. If
/// `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, '_>,
@ -114,47 +101,60 @@ impl<'ctx> ListValue<'ctx> {
ListDataProxy(self) ListDataProxy(self)
} }
/// Stores the `size` of this `list` into this instance. fn len_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
pub fn store_size<G: CodeGenerator + ?Sized>( self.get_type().get_fields().len
&self, }
ctx: &CodeGenContext<'ctx, '_>,
generator: &G,
size: IntValue<'ctx>,
) {
debug_assert_eq!(size.get_type(), generator.get_size_type(ctx.ctx));
let psize = self.ptr_to_size(ctx); /// Stores the `size` of this `list` into this instance.
ctx.builder.build_store(psize, size).unwrap(); pub fn store_size(&self, ctx: &CodeGenContext<'ctx, '_>, size: IntValue<'ctx>) {
debug_assert_eq!(size.get_type(), ctx.get_size_type());
self.len_field().store(ctx, self.value, size, self.name);
} }
/// Returns the size of this `list` as a value. /// Returns the size of this `list` as a value.
pub fn load_size(&self, ctx: &CodeGenContext<'ctx, '_>, name: Option<&str>) -> IntValue<'ctx> { pub fn load_size(
let psize = self.ptr_to_size(ctx); &self,
let var_name = name ctx: &CodeGenContext<'ctx, '_>,
.map(ToString::to_string) name: Option<&'ctx str>,
.or_else(|| self.name.map(|v| format!("{v}.size"))) ) -> IntValue<'ctx> {
.unwrap_or_default(); self.len_field().load(ctx, self.value, name)
}
ctx.builder /// Returns an instance of [`ListValue`] with the `items` pointer cast to `i8*`.
.build_load(psize, var_name.as_str()) #[must_use]
.map(BasicValueEnum::into_int_value) pub fn as_i8_list(&self, ctx: &CodeGenContext<'ctx, '_>) -> ListValue<'ctx> {
.unwrap() let llvm_i8 = ctx.ctx.i8_type();
let llvm_list_i8 = <Self as ProxyValue>::Type::new(ctx, &llvm_i8);
Self::from_pointer_value(
ctx.builder.build_pointer_cast(self.value, llvm_list_i8.as_abi_type(), "").unwrap(),
self.llvm_usize,
self.name,
)
} }
} }
impl<'ctx> ProxyValue<'ctx> for ListValue<'ctx> { impl<'ctx> ProxyValue<'ctx> for ListValue<'ctx> {
type ABI = PointerValue<'ctx>;
type Base = PointerValue<'ctx>; type Base = PointerValue<'ctx>;
type Type = ListType<'ctx>; type Type = ListType<'ctx>;
fn get_type(&self) -> Self::Type { fn get_type(&self) -> Self::Type {
ListType::from_type(self.as_base_value().get_type(), self.llvm_usize) ListType::from_pointer_type(self.as_base_value().get_type(), self.llvm_usize)
} }
fn as_base_value(&self) -> Self::Base { fn as_base_value(&self) -> Self::Base {
self.value self.value
} }
fn as_abi_value(&self, _: &CodeGenContext<'ctx, '_>) -> Self::ABI {
self.as_base_value()
}
} }
impl<'ctx> StructProxyValue<'ctx> for ListValue<'ctx> {}
impl<'ctx> From<ListValue<'ctx>> for PointerValue<'ctx> { impl<'ctx> From<ListValue<'ctx>> for PointerValue<'ctx> {
fn from(value: ListValue<'ctx>) -> Self { fn from(value: ListValue<'ctx>) -> Self {
value.as_base_value() value.as_base_value()
@ -179,12 +179,7 @@ impl<'ctx> ArrayLikeValue<'ctx> for ListDataProxy<'ctx, '_> {
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
_: &G, _: &G,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
let var_name = self.0.name.map(|v| format!("{v}.data")).unwrap_or_default(); self.0.items_field().load(ctx, self.0.value, self.0.name)
ctx.builder
.build_load(self.0.pptr_to_data(ctx), var_name.as_str())
.map(BasicValueEnum::into_pointer_value)
.unwrap()
} }
fn size<G: CodeGenerator + ?Sized>( fn size<G: CodeGenerator + ?Sized>(
@ -199,8 +194,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: &mut CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
generator: &mut G, generator: &G,
idx: &IntValue<'ctx>, idx: &IntValue<'ctx>,
name: Option<&str>, name: Option<&str>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
@ -220,7 +215,7 @@ impl<'ctx> ArrayLikeIndexer<'ctx> for ListDataProxy<'ctx, '_> {
idx: &IntValue<'ctx>, idx: &IntValue<'ctx>,
name: Option<&str>, name: Option<&str>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
debug_assert_eq!(idx.get_type(), generator.get_size_type(ctx.ctx)); debug_assert_eq!(idx.get_type(), ctx.get_size_type());
let size = self.size(ctx, generator); let size = self.size(ctx, generator);
let in_range = ctx.builder.build_int_compare(IntPredicate::ULT, *idx, size, "").unwrap(); let in_range = ctx.builder.build_int_compare(IntPredicate::ULT, *idx, size, "").unwrap();

View File

@ -1,42 +1,39 @@
use inkwell::{context::Context, values::BasicValue}; use inkwell::{types::IntType, values::BasicValue};
use super::types::ProxyType; use super::{types::ProxyType, CodeGenContext};
use crate::codegen::CodeGenerator;
pub use array::*; pub use array::*;
pub use exception::*;
pub use list::*; pub use list::*;
pub use option::*;
pub use range::*; pub use range::*;
pub use string::*;
pub use tuple::*;
mod array; mod array;
mod exception;
mod list; mod list;
pub mod ndarray; pub mod ndarray;
mod option;
mod range; mod range;
mod string;
pub mod structure;
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.
pub trait ProxyValue<'ctx>: Into<Self::Base> { pub trait ProxyValue<'ctx>: Into<Self::Base> {
/// The type of LLVM values represented by this instance. This is usually the /// The ABI type of LLVM values represented by this instance.
/// [LLVM pointer type][PointerValue]. type ABI: BasicValue<'ctx>;
/// The type of LLVM values represented by this instance.
type Base: BasicValue<'ctx>; type Base: BasicValue<'ctx>;
/// The type of this value. /// The type of this value.
type Type: ProxyType<'ctx, Value = Self>; type Type: ProxyType<'ctx, Value = Self>;
/// Checks whether `value` can be represented by this [`ProxyValue`]. /// Checks whether `value` can be represented by this [`ProxyValue`].
fn is_instance<G: CodeGenerator + ?Sized>( fn is_instance(value: impl BasicValue<'ctx>, llvm_usize: IntType<'ctx>) -> Result<(), String> {
generator: &G, Self::Type::is_representable(value.as_basic_value_enum().get_type(), llvm_usize)
ctx: &'ctx Context,
value: impl BasicValue<'ctx>,
) -> Result<(), String> {
Self::Type::is_type(generator, ctx, value.as_basic_value_enum().get_type())
}
/// Checks whether `value` can be represented by this [`ProxyValue`].
fn is_representable<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
value: Self::Base,
) -> Result<(), String> {
Self::is_instance(generator, ctx, value.as_basic_value_enum())
} }
/// Returns the [type][ProxyType] of this value. /// Returns the [type][ProxyType] of this value.
@ -44,4 +41,10 @@ pub trait ProxyValue<'ctx>: Into<Self::Base> {
/// Returns the [base value][Self::Base] of this proxy. /// Returns the [base value][Self::Base] of this proxy.
fn as_base_value(&self) -> Self::Base; fn as_base_value(&self) -> Self::Base;
/// Returns this proxy as its ABI value, i.e. the expected value representation if a value
/// represented by this [`ProxyValue`] is being passed into or returned from a function.
///
/// See [`CodeGenContext::get_llvm_abi_type`].
fn as_abi_value(&self, ctx: &CodeGenContext<'ctx, '_>) -> Self::ABI;
} }

View File

@ -0,0 +1,262 @@
use inkwell::{
types::IntType,
values::{IntValue, PointerValue, StructValue},
};
use itertools::Itertools;
use crate::codegen::{
irrt,
types::{
ndarray::{NDArrayType, ShapeEntryType},
structure::{StructField, StructProxyType},
ProxyType,
},
values::{
ndarray::NDArrayValue, structure::StructProxyValue, 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> {
/// Creates an [`ShapeEntryValue`] from a [`StructValue`].
#[must_use]
pub fn from_struct_value<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
val: StructValue<'ctx>,
llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>,
) -> Self {
let pval = generator
.gen_var_alloc(
ctx,
val.get_type().into(),
name.map(|name| format!("{name}.addr")).as_deref(),
)
.unwrap();
ctx.builder.build_store(pval, val).unwrap();
Self::from_pointer_value(pval, llvm_usize, name)
}
/// 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_instance(ptr, llvm_usize).is_ok());
Self { value: ptr, llvm_usize, name }
}
fn ndims_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
self.get_type().get_fields().ndims
}
/// Stores the number of dimensions into this value.
pub fn store_ndims(&self, ctx: &CodeGenContext<'ctx, '_>, value: IntValue<'ctx>) {
self.ndims_field().store(ctx, self.value, value, self.name);
}
fn shape_field(&self) -> StructField<'ctx, PointerValue<'ctx>> {
self.get_type().get_fields().shape
}
/// Stores the shape into this value.
pub fn store_shape(&self, ctx: &CodeGenContext<'ctx, '_>, value: PointerValue<'ctx>) {
self.shape_field().store(ctx, self.value, value, self.name);
}
}
impl<'ctx> ProxyValue<'ctx> for ShapeEntryValue<'ctx> {
type ABI = PointerValue<'ctx>;
type Base = PointerValue<'ctx>;
type Type = ShapeEntryType<'ctx>;
fn get_type(&self) -> Self::Type {
Self::Type::from_pointer_type(self.value.get_type(), self.llvm_usize)
}
fn as_base_value(&self) -> Self::Base {
self.value
}
fn as_abi_value(&self, _: &CodeGenContext<'ctx, '_>) -> Self::ABI {
self.as_base_value()
}
}
impl<'ctx> StructProxyValue<'ctx> for ShapeEntryValue<'ctx> {}
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(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(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 = ctx.get_size_type();
let llvm_shape_ty = ShapeEntryType::new(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_pointer_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 = ctx.get_size_type();
// 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

@ -1,16 +1,17 @@
use inkwell::{ use inkwell::{
types::{BasicType, BasicTypeEnum, IntType}, types::{BasicType, BasicTypeEnum, IntType},
values::{IntValue, PointerValue}, values::{IntValue, PointerValue, StructValue},
AddressSpace, AddressSpace,
}; };
use super::{ArrayLikeValue, NDArrayValue, ProxyValue}; use super::NDArrayValue;
use crate::codegen::{ use crate::codegen::{
stmt::gen_if_callback, stmt::gen_if_callback,
types::{ types::{
ndarray::{ContiguousNDArrayType, NDArrayType}, ndarray::{ContiguousNDArrayType, NDArrayType},
structure::StructField, structure::{StructField, StructProxyType},
}, },
values::{structure::StructProxyValue, ArrayLikeValue, ProxyValue},
CodeGenContext, CodeGenerator, CodeGenContext, CodeGenerator,
}; };
@ -23,13 +24,25 @@ pub struct ContiguousNDArrayValue<'ctx> {
} }
impl<'ctx> ContiguousNDArrayValue<'ctx> { impl<'ctx> ContiguousNDArrayValue<'ctx> {
/// Checks whether `value` is an instance of `ContiguousNDArray`, returning [Err] if `value` is /// Creates an [`ContiguousNDArrayValue`] from a [`StructValue`].
/// not an instance. #[must_use]
pub fn is_representable( pub fn from_struct_value<G: CodeGenerator + ?Sized>(
value: PointerValue<'ctx>, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
val: StructValue<'ctx>,
dtype: BasicTypeEnum<'ctx>,
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
) -> Result<(), String> { name: Option<&'ctx str>,
<Self as ProxyValue<'ctx>>::Type::is_representable(value.get_type(), llvm_usize) ) -> Self {
let pval = generator
.gen_var_alloc(
ctx,
val.get_type().into(),
name.map(|name| format!("{name}.addr")).as_deref(),
)
.unwrap();
ctx.builder.build_store(pval, val).unwrap();
Self::from_pointer_value(pval, dtype, llvm_usize, name)
} }
/// Creates an [`ContiguousNDArrayValue`] from a [`PointerValue`]. /// Creates an [`ContiguousNDArrayValue`] from a [`PointerValue`].
@ -40,7 +53,7 @@ impl<'ctx> ContiguousNDArrayValue<'ctx> {
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>, name: Option<&'ctx str>,
) -> Self { ) -> Self {
debug_assert!(Self::is_representable(ptr, llvm_usize).is_ok()); debug_assert!(Self::is_instance(ptr, llvm_usize).is_ok());
Self { value: ptr, item: dtype, llvm_usize, name } Self { value: ptr, item: dtype, llvm_usize, name }
} }
@ -50,7 +63,7 @@ impl<'ctx> ContiguousNDArrayValue<'ctx> {
} }
pub fn store_ndims(&self, ctx: &CodeGenContext<'ctx, '_>, value: IntValue<'ctx>) { pub fn store_ndims(&self, ctx: &CodeGenContext<'ctx, '_>, value: IntValue<'ctx>) {
self.ndims_field().set(ctx, self.as_base_value(), value, self.name); self.ndims_field().store(ctx, self.as_abi_value(ctx), value, self.name);
} }
fn shape_field(&self) -> StructField<'ctx, PointerValue<'ctx>> { fn shape_field(&self) -> StructField<'ctx, PointerValue<'ctx>> {
@ -58,11 +71,11 @@ impl<'ctx> ContiguousNDArrayValue<'ctx> {
} }
pub fn store_shape(&self, ctx: &CodeGenContext<'ctx, '_>, value: PointerValue<'ctx>) { pub fn store_shape(&self, ctx: &CodeGenContext<'ctx, '_>, value: PointerValue<'ctx>) {
self.shape_field().set(ctx, self.as_base_value(), value, self.name); self.shape_field().store(ctx, self.as_abi_value(ctx), value, self.name);
} }
pub fn load_shape(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> { pub fn load_shape(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
self.shape_field().get(ctx, self.value, self.name) self.shape_field().load(ctx, self.value, self.name)
} }
fn data_field(&self) -> StructField<'ctx, PointerValue<'ctx>> { fn data_field(&self) -> StructField<'ctx, PointerValue<'ctx>> {
@ -70,20 +83,21 @@ impl<'ctx> ContiguousNDArrayValue<'ctx> {
} }
pub fn store_data(&self, ctx: &CodeGenContext<'ctx, '_>, value: PointerValue<'ctx>) { pub fn store_data(&self, ctx: &CodeGenContext<'ctx, '_>, value: PointerValue<'ctx>) {
self.data_field().set(ctx, self.as_base_value(), value, self.name); self.data_field().store(ctx, self.as_abi_value(ctx), value, self.name);
} }
pub fn load_data(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> { pub fn load_data(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
self.data_field().get(ctx, self.value, self.name) self.data_field().load(ctx, self.value, self.name)
} }
} }
impl<'ctx> ProxyValue<'ctx> for ContiguousNDArrayValue<'ctx> { impl<'ctx> ProxyValue<'ctx> for ContiguousNDArrayValue<'ctx> {
type ABI = PointerValue<'ctx>;
type Base = PointerValue<'ctx>; type Base = PointerValue<'ctx>;
type Type = ContiguousNDArrayType<'ctx>; type Type = ContiguousNDArrayType<'ctx>;
fn get_type(&self) -> Self::Type { fn get_type(&self) -> Self::Type {
<Self as ProxyValue<'ctx>>::Type::from_type( <Self as ProxyValue<'ctx>>::Type::from_pointer_type(
self.as_base_value().get_type(), self.as_base_value().get_type(),
self.item, self.item,
self.llvm_usize, self.llvm_usize,
@ -93,8 +107,14 @@ impl<'ctx> ProxyValue<'ctx> for ContiguousNDArrayValue<'ctx> {
fn as_base_value(&self) -> Self::Base { fn as_base_value(&self) -> Self::Base {
self.value self.value
} }
fn as_abi_value(&self, _: &CodeGenContext<'ctx, '_>) -> Self::ABI {
self.as_base_value()
}
} }
impl<'ctx> StructProxyValue<'ctx> for ContiguousNDArrayValue<'ctx> {}
impl<'ctx> From<ContiguousNDArrayValue<'ctx>> for PointerValue<'ctx> { impl<'ctx> From<ContiguousNDArrayValue<'ctx>> for PointerValue<'ctx> {
fn from(value: ContiguousNDArrayValue<'ctx>) -> Self { fn from(value: ContiguousNDArrayValue<'ctx>) -> Self {
value.as_base_value() value.as_base_value()
@ -117,13 +137,11 @@ impl<'ctx> NDArrayValue<'ctx> {
generator: &mut G, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
) -> ContiguousNDArrayValue<'ctx> { ) -> ContiguousNDArrayValue<'ctx> {
let result = ContiguousNDArrayType::new(generator, ctx.ctx, self.dtype) let result =
.alloca(generator, ctx, self.name); ContiguousNDArrayType::new(ctx, &self.dtype).alloca_var(generator, ctx, self.name);
// Set ndims and shape. // Set ndims and shape.
let ndims = self let ndims = self.llvm_usize.const_int(self.ndims, false);
.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();
@ -132,10 +150,10 @@ impl<'ctx> NDArrayValue<'ctx> {
gen_if_callback( gen_if_callback(
generator, generator,
ctx, ctx,
|generator, ctx| Ok(self.is_c_contiguous(generator, ctx)), |_, ctx| Ok(self.is_c_contiguous(ctx)),
|_, ctx| { |_, ctx| {
// This ndarray is contiguous. // This ndarray is contiguous.
let data = self.data_field(ctx).get(ctx, self.as_base_value(), self.name); let data = self.data_field().load(ctx, self.as_abi_value(ctx), self.name);
let data = ctx let data = ctx
.builder .builder
.build_pointer_cast(data, result.item.ptr_type(AddressSpace::default()), "") .build_pointer_cast(data, result.item.ptr_type(AddressSpace::default()), "")
@ -180,13 +198,16 @@ 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, Some(ndims)) let ndarray = NDArrayType::new(ctx, carray.item, ndims).construct_uninitialized(
.construct_uninitialized(generator, ctx, carray.name); generator,
ctx,
carray.name,
);
// Copy shape and update strides // Copy shape and update strides
let shape = carray.load_shape(ctx); let shape = carray.load_shape(ctx);
ndarray.copy_shape_from_array(generator, ctx, shape); ndarray.copy_shape_from_array(generator, ctx, shape);
ndarray.set_strides_contiguous(generator, ctx); ndarray.set_strides_contiguous(ctx);
// Share data // Share data
let data = carray.load_data(ctx); let data = carray.load_data(ctx);

View File

@ -0,0 +1,101 @@
use inkwell::values::{BasicValue, BasicValueEnum};
use super::{NDArrayValue, NDIterValue, ScalarOrNDArray};
use crate::codegen::{
stmt::{gen_for_callback, BreakContinueHooks},
types::ndarray::NDIterType,
CodeGenContext, CodeGenerator,
};
impl<'ctx> NDArrayValue<'ctx> {
/// Folds the elements of this ndarray into an accumulator value by applying `f`, returning the
/// final value.
///
/// `f` has access to [`BreakContinueHooks`] to short-circuit the `fold` operation, an instance
/// of `V` representing the current accumulated value, and an [`NDIterValue`] to get the
/// properties of the current iterated element.
pub fn fold<'a, G, V, F>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,
init: V,
f: F,
) -> Result<V, String>
where
G: CodeGenerator + ?Sized,
V: BasicValue<'ctx> + TryFrom<BasicValueEnum<'ctx>>,
<V as TryFrom<BasicValueEnum<'ctx>>>::Error: std::fmt::Debug,
F: FnOnce(
&mut G,
&mut CodeGenContext<'ctx, 'a>,
BreakContinueHooks<'ctx>,
V,
NDIterValue<'ctx>,
) -> Result<V, String>,
{
let acc_ptr =
generator.gen_var_alloc(ctx, init.as_basic_value_enum().get_type(), None).unwrap();
ctx.builder.build_store(acc_ptr, init).unwrap();
gen_for_callback(
generator,
ctx,
Some("ndarray_fold"),
|generator, ctx| Ok(NDIterType::new(ctx).construct(generator, ctx, *self)),
|_, ctx, nditer| Ok(nditer.has_element(ctx)),
|generator, ctx, hooks, nditer| {
let acc = V::try_from(ctx.builder.build_load(acc_ptr, "").unwrap()).unwrap();
let acc = f(generator, ctx, hooks, acc, nditer)?;
ctx.builder.build_store(acc_ptr, acc).unwrap();
Ok(())
},
|_, ctx, nditer| {
nditer.next(ctx);
Ok(())
},
)?;
let acc = ctx.builder.build_load(acc_ptr, "").unwrap();
Ok(V::try_from(acc).unwrap())
}
}
impl<'ctx> ScalarOrNDArray<'ctx> {
/// See [`NDArrayValue::fold`].
///
/// The primary differences between this function and `NDArrayValue::fold` are:
///
/// - The 3rd parameter of `f` is an `Option` of hooks, since `break`/`continue` hooks are not
/// available if this instance represents a scalar value.
/// - The 5th parameter of `f` is a [`BasicValueEnum`], since no [iterator][`NDIterValue`] will
/// be created if this instance represents a scalar value.
pub fn fold<'a, G, V, F>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,
init: V,
f: F,
) -> Result<V, String>
where
G: CodeGenerator + ?Sized,
V: BasicValue<'ctx> + TryFrom<BasicValueEnum<'ctx>>,
<V as TryFrom<BasicValueEnum<'ctx>>>::Error: std::fmt::Debug,
F: FnOnce(
&mut G,
&mut CodeGenContext<'ctx, 'a>,
Option<&BreakContinueHooks<'ctx>>,
V,
BasicValueEnum<'ctx>,
) -> Result<V, String>,
{
match self {
ScalarOrNDArray::Scalar(v) => f(generator, ctx, None, init, *v),
ScalarOrNDArray::NDArray(v) => {
v.fold(generator, ctx, init, |generator, ctx, hooks, acc, nditer| {
let elem = nditer.get_scalar(ctx);
f(generator, ctx, Some(&hooks), acc, elem)
})
}
}
}
}

View File

@ -1,6 +1,6 @@
use inkwell::{ use inkwell::{
types::IntType, types::IntType,
values::{IntValue, PointerValue}, values::{IntValue, PointerValue, StructValue},
AddressSpace, AddressSpace,
}; };
use itertools::Itertools; use itertools::Itertools;
@ -12,10 +12,12 @@ use crate::{
irrt, irrt,
types::{ types::{
ndarray::{NDArrayType, NDIndexType}, ndarray::{NDArrayType, NDIndexType},
structure::StructField, structure::{StructField, StructProxyType},
utils::SliceType, utils::SliceType,
}, },
values::{ndarray::NDArrayValue, utils::RustSlice, ProxyValue}, values::{
ndarray::NDArrayValue, structure::StructProxyValue, utils::RustSlice, ProxyValue,
},
CodeGenContext, CodeGenerator, CodeGenContext, CodeGenerator,
}, },
typecheck::typedef::Type, typecheck::typedef::Type,
@ -30,13 +32,24 @@ pub struct NDIndexValue<'ctx> {
} }
impl<'ctx> NDIndexValue<'ctx> { impl<'ctx> NDIndexValue<'ctx> {
/// Checks whether `value` is an instance of `ndindex`, returning [Err] if `value` is not an /// Creates an [`NDIndexValue`] from a [`StructValue`].
/// instance. #[must_use]
pub fn is_representable( pub fn from_struct_value<G: CodeGenerator + ?Sized>(
value: PointerValue<'ctx>, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
val: StructValue<'ctx>,
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
) -> Result<(), String> { name: Option<&'ctx str>,
<Self as ProxyValue<'ctx>>::Type::is_representable(value.get_type(), llvm_usize) ) -> Self {
let pval = generator
.gen_var_alloc(
ctx,
val.get_type().into(),
name.map(|name| format!("{name}.addr")).as_deref(),
)
.unwrap();
ctx.builder.build_store(pval, val).unwrap();
Self::from_pointer_value(pval, llvm_usize, name)
} }
/// Creates an [`NDIndexValue`] from a [`PointerValue`]. /// Creates an [`NDIndexValue`] from a [`PointerValue`].
@ -46,7 +59,7 @@ impl<'ctx> NDIndexValue<'ctx> {
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>, name: Option<&'ctx str>,
) -> Self { ) -> Self {
debug_assert!(Self::is_representable(ptr, llvm_usize).is_ok()); debug_assert!(Self::is_instance(ptr, llvm_usize).is_ok());
Self { value: ptr, llvm_usize, name } Self { value: ptr, llvm_usize, name }
} }
@ -56,11 +69,11 @@ impl<'ctx> NDIndexValue<'ctx> {
} }
pub fn load_type(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> { pub fn load_type(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
self.type_field().get(ctx, self.value, self.name) self.type_field().load(ctx, self.value, self.name)
} }
pub fn store_type(&self, ctx: &CodeGenContext<'ctx, '_>, value: IntValue<'ctx>) { pub fn store_type(&self, ctx: &CodeGenContext<'ctx, '_>, value: IntValue<'ctx>) {
self.type_field().set(ctx, self.value, value, self.name); self.type_field().store(ctx, self.value, value, self.name);
} }
fn data_field(&self) -> StructField<'ctx, PointerValue<'ctx>> { fn data_field(&self) -> StructField<'ctx, PointerValue<'ctx>> {
@ -68,27 +81,34 @@ impl<'ctx> NDIndexValue<'ctx> {
} }
pub fn load_data(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> { pub fn load_data(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
self.data_field().get(ctx, self.value, self.name) self.data_field().load(ctx, self.value, self.name)
} }
pub fn store_data(&self, ctx: &CodeGenContext<'ctx, '_>, value: PointerValue<'ctx>) { pub fn store_data(&self, ctx: &CodeGenContext<'ctx, '_>, value: PointerValue<'ctx>) {
self.data_field().set(ctx, self.value, value, self.name); self.data_field().store(ctx, self.value, value, self.name);
} }
} }
impl<'ctx> ProxyValue<'ctx> for NDIndexValue<'ctx> { impl<'ctx> ProxyValue<'ctx> for NDIndexValue<'ctx> {
type ABI = PointerValue<'ctx>;
type Base = PointerValue<'ctx>; type Base = PointerValue<'ctx>;
type Type = NDIndexType<'ctx>; type Type = NDIndexType<'ctx>;
fn get_type(&self) -> Self::Type { fn get_type(&self) -> Self::Type {
Self::Type::from_type(self.value.get_type(), self.llvm_usize) Self::Type::from_pointer_type(self.value.get_type(), self.llvm_usize)
} }
fn as_base_value(&self) -> Self::Base { fn as_base_value(&self) -> Self::Base {
self.value self.value
} }
fn as_abi_value(&self, _: &CodeGenContext<'ctx, '_>) -> Self::ABI {
self.as_base_value()
}
} }
impl<'ctx> StructProxyValue<'ctx> for NDIndexValue<'ctx> {}
impl<'ctx> From<NDIndexValue<'ctx>> for PointerValue<'ctx> { impl<'ctx> From<NDIndexValue<'ctx>> for PointerValue<'ctx> {
fn from(value: NDIndexValue<'ctx>) -> Self { fn from(value: NDIndexValue<'ctx>) -> Self {
value.as_base_value() value.as_base_value()
@ -98,8 +118,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>]) -> Option<u64> { fn deduce_ndims_after_indexing_with(&self, indices: &[RustNDIndex<'ctx>]) -> 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 +133,7 @@ impl<'ctx> NDArrayValue<'ctx> {
} }
} }
Some(ndims) 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,14 +147,11 @@ 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(ctx, self.dtype, dst_ndims)
.construct_uninitialized(generator, ctx, None); .construct_uninitialized(generator, ctx, None);
let indices = let indices = NDIndexType::new(ctx).construct_ndindices(generator, ctx, indices);
NDIndexType::new(generator, ctx.ctx).construct_ndindices(generator, ctx, indices);
irrt::ndarray::call_nac3_ndarray_index(generator, ctx, indices, *self, dst_ndarray); irrt::ndarray::call_nac3_ndarray_index(generator, ctx, indices, *self, dst_ndarray);
dst_ndarray dst_ndarray
@ -247,8 +264,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.i32_type()).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

@ -0,0 +1,69 @@
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

@ -0,0 +1,319 @@
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 = ctx.get_size_type();
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(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,
)?;
// 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,
)?;
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(ctx, result);
out_ndarray
}
}
}
}

View File

@ -1,29 +1,45 @@
use std::iter::repeat_n;
use inkwell::{ use inkwell::{
types::{AnyType, AnyTypeEnum, BasicType, BasicTypeEnum, IntType}, types::{AnyType, AnyTypeEnum, BasicType, BasicTypeEnum, IntType},
values::{BasicValueEnum, IntValue, PointerValue}, values::{BasicValue, BasicValueEnum, IntValue, PointerValue, StructValue},
AddressSpace, IntPredicate, AddressSpace, IntPredicate,
}; };
use itertools::Itertools;
use super::{ use super::{
ArrayLikeIndexer, ArrayLikeValue, ProxyValue, TypedArrayLikeAccessor, TypedArrayLikeMutator, structure::StructProxyValue, ArrayLikeIndexer, ArrayLikeValue, ProxyValue, TupleValue,
UntypedArrayLikeAccessor, UntypedArrayLikeMutator, TypedArrayLikeAccessor, TypedArrayLikeAdapter, TypedArrayLikeMutator, UntypedArrayLikeAccessor,
UntypedArrayLikeMutator,
}; };
use crate::codegen::{ use crate::{
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, StructProxyType},
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 fold;
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.
@ -31,19 +47,32 @@ mod view;
pub struct NDArrayValue<'ctx> { pub struct NDArrayValue<'ctx> {
value: PointerValue<'ctx>, value: PointerValue<'ctx>,
dtype: BasicTypeEnum<'ctx>, dtype: BasicTypeEnum<'ctx>,
ndims: Option<u64>, ndims: u64,
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>, name: Option<&'ctx str>,
} }
impl<'ctx> NDArrayValue<'ctx> { impl<'ctx> NDArrayValue<'ctx> {
/// Checks whether `value` is an instance of `NDArray`, returning [Err] if `value` is not an /// Creates an [`NDArrayValue`] from a [`StructValue`].
/// instance. #[must_use]
pub fn is_representable( pub fn from_struct_value<G: CodeGenerator + ?Sized>(
value: PointerValue<'ctx>, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
val: StructValue<'ctx>,
dtype: BasicTypeEnum<'ctx>,
ndims: u64,
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
) -> Result<(), String> { name: Option<&'ctx str>,
NDArrayType::is_representable(value.get_type(), llvm_usize) ) -> Self {
let pval = generator
.gen_var_alloc(
ctx,
val.get_type().into(),
name.map(|name| format!("{name}.addr")).as_deref(),
)
.unwrap();
ctx.builder.build_store(pval, val).unwrap();
Self::from_pointer_value(pval, dtype, ndims, llvm_usize, name)
} }
/// Creates an [`NDArrayValue`] from a [`PointerValue`]. /// Creates an [`NDArrayValue`] from a [`PointerValue`].
@ -51,77 +80,54 @@ 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: Option<u64>, ndims: u64,
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>, name: Option<&'ctx str>,
) -> Self { ) -> Self {
debug_assert!(Self::is_representable(ptr, llvm_usize).is_ok()); debug_assert!(Self::is_instance(ptr, llvm_usize).is_ok());
NDArrayValue { value: ptr, dtype, ndims, llvm_usize, name } NDArrayValue { value: ptr, dtype, ndims, llvm_usize, name }
} }
fn ndims_field(&self, ctx: &CodeGenContext<'ctx, '_>) -> StructField<'ctx, IntValue<'ctx>> { fn ndims_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
self.get_type().get_fields(ctx.ctx).ndims self.get_type().get_fields().ndims
}
/// Returns the pointer to the field storing the number of dimensions of this `NDArray`.
fn ptr_to_ndims(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
self.ndims_field(ctx).ptr_by_gep(ctx, self.value, self.name)
} }
/// Stores the number of dimensions `ndims` into this instance. /// Stores the number of dimensions `ndims` into this instance.
pub fn store_ndims<G: CodeGenerator + ?Sized>( pub fn store_ndims(&self, ctx: &CodeGenContext<'ctx, '_>, ndims: IntValue<'ctx>) {
&self, debug_assert_eq!(ndims.get_type(), ctx.get_size_type());
ctx: &CodeGenContext<'ctx, '_>,
generator: &G,
ndims: IntValue<'ctx>,
) {
debug_assert_eq!(ndims.get_type(), generator.get_size_type(ctx.ctx));
let pndims = self.ptr_to_ndims(ctx); self.ndims_field().store(ctx, self.value, ndims, self.name);
ctx.builder.build_store(pndims, ndims).unwrap();
} }
/// Returns the number of dimensions of this `NDArray` as a value. /// Returns the number of dimensions of this `NDArray` as a value.
pub fn load_ndims(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> { pub fn load_ndims(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
let pndims = self.ptr_to_ndims(ctx); self.ndims_field().load(ctx, self.value, self.name)
ctx.builder.build_load(pndims, "").map(BasicValueEnum::into_int_value).unwrap()
} }
fn itemsize_field(&self, ctx: &CodeGenContext<'ctx, '_>) -> StructField<'ctx, IntValue<'ctx>> { fn itemsize_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
self.get_type().get_fields(ctx.ctx).itemsize self.get_type().get_fields().itemsize
} }
/// Stores the size of each element `itemsize` into this instance. /// Stores the size of each element `itemsize` into this instance.
pub fn store_itemsize<G: CodeGenerator + ?Sized>( pub fn store_itemsize(&self, ctx: &CodeGenContext<'ctx, '_>, itemsize: IntValue<'ctx>) {
&self, debug_assert_eq!(itemsize.get_type(), ctx.get_size_type());
ctx: &CodeGenContext<'ctx, '_>,
generator: &G,
itemsize: IntValue<'ctx>,
) {
debug_assert_eq!(itemsize.get_type(), generator.get_size_type(ctx.ctx));
self.itemsize_field(ctx).set(ctx, self.value, itemsize, self.name); self.itemsize_field().store(ctx, self.value, itemsize, self.name);
} }
/// Returns the size of each element of this `NDArray` as a value. /// Returns the size of each element of this `NDArray` as a value.
pub fn load_itemsize(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> { pub fn load_itemsize(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
self.itemsize_field(ctx).get(ctx, self.value, self.name) self.itemsize_field().load(ctx, self.value, self.name)
} }
fn shape_field(&self, ctx: &CodeGenContext<'ctx, '_>) -> StructField<'ctx, PointerValue<'ctx>> { fn shape_field(&self) -> StructField<'ctx, PointerValue<'ctx>> {
self.get_type().get_fields(ctx.ctx).shape self.get_type().get_fields().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().store(ctx, self.value, dims, self.name);
} }
/// Convenience method for creating a new array storing dimension sizes with the given `size`. /// Convenience method for creating a new array storing dimension sizes with the given `size`.
@ -140,22 +146,13 @@ impl<'ctx> NDArrayValue<'ctx> {
NDArrayShapeProxy(self) NDArrayShapeProxy(self)
} }
fn strides_field( fn strides_field(&self) -> StructField<'ctx, PointerValue<'ctx>> {
&self, self.get_type().get_fields().strides
ctx: &CodeGenContext<'ctx, '_>,
) -> StructField<'ctx, PointerValue<'ctx>> {
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().store(ctx, self.value, strides, self.name);
} }
/// Convenience method for creating a new array storing the stride with the given `size`. /// Convenience method for creating a new array storing the stride with the given `size`.
@ -174,23 +171,23 @@ impl<'ctx> NDArrayValue<'ctx> {
NDArrayStridesProxy(self) NDArrayStridesProxy(self)
} }
fn data_field(&self, ctx: &CodeGenContext<'ctx, '_>) -> StructField<'ctx, PointerValue<'ctx>> { fn data_field(&self) -> StructField<'ctx, PointerValue<'ctx>> {
self.get_type().get_fields(ctx.ctx).data self.get_type().get_fields().data
} }
/// 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.
pub fn ptr_to_data(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> { pub fn ptr_to_data(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
self.data_field(ctx).ptr_by_gep(ctx, self.value, self.name) self.data_field().ptr_by_gep(ctx, self.value, self.name)
} }
/// 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>) { pub 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()), "")
.unwrap(); .unwrap();
self.data_field(ctx).set(ctx, self.as_base_value(), data.into_pointer_value(), self.name); self.data_field().store(ctx, self.value, data.into_pointer_value(), self.name);
} }
/// 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
@ -206,12 +203,12 @@ impl<'ctx> NDArrayValue<'ctx> {
generator: &mut G, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
) { ) {
let nbytes = self.nbytes(generator, ctx); let nbytes = self.nbytes(ctx);
let data = type_aligned_alloca(generator, ctx, self.dtype, nbytes, None); let data = type_aligned_alloca(generator, ctx, self.dtype, nbytes, None);
self.store_data(ctx, data); self.store_data(ctx, data);
self.set_strides_contiguous(generator, ctx); self.set_strides_contiguous(ctx);
} }
/// Returns a proxy object to the field storing the data of this `NDArray`. /// Returns a proxy object to the field storing the data of this `NDArray`.
@ -246,26 +243,7 @@ impl<'ctx> NDArrayValue<'ctx> {
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
src_ndarray: NDArrayValue<'ctx>, src_ndarray: NDArrayValue<'ctx>,
) { ) {
if self.ndims.is_some() && src_ndarray.ndims.is_some() {
assert_eq!(self.ndims, src_ndarray.ndims); 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);
@ -297,96 +275,57 @@ impl<'ctx> NDArrayValue<'ctx> {
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
src_ndarray: NDArrayValue<'ctx>, src_ndarray: NDArrayValue<'ctx>,
) { ) {
if self.ndims.is_some() && src_ndarray.ndims.is_some() {
assert_eq!(self.ndims, src_ndarray.ndims); 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);
} }
/// Get the `np.size()` of this ndarray. /// Get the `np.size()` of this ndarray.
pub fn size<G: CodeGenerator + ?Sized>( pub fn size(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
&self, irrt::ndarray::call_nac3_ndarray_size(ctx, *self)
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
) -> IntValue<'ctx> {
irrt::ndarray::call_nac3_ndarray_size(generator, ctx, *self)
} }
/// Get the `ndarray.nbytes` of this ndarray. /// Get the `ndarray.nbytes` of this ndarray.
pub fn nbytes<G: CodeGenerator + ?Sized>( pub fn nbytes(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
&self, irrt::ndarray::call_nac3_ndarray_nbytes(ctx, *self)
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
) -> IntValue<'ctx> {
irrt::ndarray::call_nac3_ndarray_nbytes(generator, ctx, *self)
} }
/// Get the `len()` of this ndarray. /// Get the `len()` of this ndarray.
pub fn len<G: CodeGenerator + ?Sized>( pub fn len(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
&self, irrt::ndarray::call_nac3_ndarray_len(ctx, *self)
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
) -> IntValue<'ctx> {
irrt::ndarray::call_nac3_ndarray_len(generator, ctx, *self)
} }
/// Check if this ndarray is C-contiguous. /// Check if this ndarray is C-contiguous.
/// ///
/// See NumPy's `flags["C_CONTIGUOUS"]`: <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.flags.html#numpy.ndarray.flags> /// See NumPy's `flags["C_CONTIGUOUS"]`: <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.flags.html#numpy.ndarray.flags>
pub fn is_c_contiguous<G: CodeGenerator + ?Sized>( pub fn is_c_contiguous(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
&self, irrt::ndarray::call_nac3_ndarray_is_c_contiguous(ctx, *self)
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
) -> IntValue<'ctx> {
irrt::ndarray::call_nac3_ndarray_is_c_contiguous(generator, ctx, *self)
} }
/// Call [`call_nac3_ndarray_set_strides_by_shape`] on this ndarray to update `strides`. /// Call [`call_nac3_ndarray_set_strides_by_shape`] on this ndarray to update `strides`.
/// ///
/// Update the ndarray's strides to make the ndarray contiguous. /// Update the ndarray's strides to make the ndarray contiguous.
pub fn set_strides_contiguous<G: CodeGenerator + ?Sized>( pub fn set_strides_contiguous(&self, ctx: &CodeGenContext<'ctx, '_>) {
&self, irrt::ndarray::call_nac3_ndarray_set_strides_by_shape(ctx, *self);
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
) {
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 = if self.ndims.is_some() { let clone = self.get_type().construct_uninitialized(generator, ctx, None);
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));
unsafe { clone.create_data(generator, ctx) }; unsafe { clone.create_data(generator, ctx) };
clone.copy_data_from(generator, ctx, *self); clone.copy_data_from(ctx, *self);
clone clone
} }
@ -396,50 +335,155 @@ impl<'ctx> NDArrayValue<'ctx> {
/// do not matter. The copying order is determined by how their flattened views look. /// do not matter. The copying order is determined by how their flattened views look.
/// ///
/// Panics if the `dtype`s of ndarrays are different. /// Panics if the `dtype`s of ndarrays are different.
pub fn copy_data_from<G: CodeGenerator + ?Sized>( pub fn copy_data_from(&self, ctx: &CodeGenContext<'ctx, '_>, src: NDArrayValue<'ctx>) {
&self,
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
src: NDArrayValue<'ctx>,
) {
assert_eq!(self.dtype, src.dtype, "self and src dtype should match"); assert_eq!(self.dtype, src.dtype, "self and src dtype should match");
irrt::ndarray::call_nac3_ndarray_copy_data(generator, ctx, src, *self); irrt::ndarray::call_nac3_ndarray_copy_data(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(ctx, &repeat_n(llvm_i32, 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(ctx, &repeat_n(llvm_i32, 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) -> Option<bool> { pub fn is_unsized(&self) -> bool {
self.ndims.map(|ndims| ndims == 0) self.ndims == 0
} }
/// If this ndarray is unsized, return its sole value as an [`AnyObject`]. /// Returns the element present in this `ndarray` if this is unsized.
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 = ctx.get_size_type().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> {
let Some(is_unsized) = self.is_unsized() else { todo!() }; if let Some(unsized_elem) = self.get_unsized_element(generator, ctx) {
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> {
type ABI = PointerValue<'ctx>;
type Base = PointerValue<'ctx>; type Base = PointerValue<'ctx>;
type Type = NDArrayType<'ctx>; type Type = NDArrayType<'ctx>;
fn get_type(&self) -> Self::Type { fn get_type(&self) -> Self::Type {
NDArrayType::from_type( NDArrayType::from_pointer_type(
self.as_base_value().get_type(), self.as_base_value().get_type(),
self.dtype, self.dtype,
self.ndims, self.ndims,
@ -450,8 +494,14 @@ impl<'ctx> ProxyValue<'ctx> for NDArrayValue<'ctx> {
fn as_base_value(&self) -> Self::Base { fn as_base_value(&self) -> Self::Base {
self.value self.value
} }
fn as_abi_value(&self, _: &CodeGenContext<'ctx, '_>) -> Self::ABI {
self.as_base_value()
}
} }
impl<'ctx> StructProxyValue<'ctx> for NDArrayValue<'ctx> {}
impl<'ctx> From<NDArrayValue<'ctx>> for PointerValue<'ctx> { impl<'ctx> From<NDArrayValue<'ctx>> for PointerValue<'ctx> {
fn from(value: NDArrayValue<'ctx>) -> Self { fn from(value: NDArrayValue<'ctx>) -> Self {
value.as_base_value() value.as_base_value()
@ -476,7 +526,7 @@ impl<'ctx> ArrayLikeValue<'ctx> for NDArrayShapeProxy<'ctx, '_> {
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
_: &G, _: &G,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
self.0.shape_field(ctx).get(ctx, self.0.as_base_value(), self.0.name) self.0.shape_field().load(ctx, self.0.value, self.0.name)
} }
fn size<G: CodeGenerator + ?Sized>( fn size<G: CodeGenerator + ?Sized>(
@ -491,8 +541,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: &mut CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
generator: &mut G, generator: &G,
idx: &IntValue<'ctx>, idx: &IntValue<'ctx>,
name: Option<&str>, name: Option<&str>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
@ -530,20 +580,26 @@ 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> TypedArrayLikeAccessor<'ctx, IntValue<'ctx>> for NDArrayShapeProxy<'ctx, '_> { impl<'ctx, G: CodeGenerator + ?Sized> TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>
for NDArrayShapeProxy<'ctx, '_>
{
fn downcast_to_type( fn downcast_to_type(
&self, &self,
_: &mut CodeGenContext<'ctx, '_>, _: &CodeGenContext<'ctx, '_>,
_: &G,
value: BasicValueEnum<'ctx>, value: BasicValueEnum<'ctx>,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
value.into_int_value() value.into_int_value()
} }
} }
impl<'ctx> TypedArrayLikeMutator<'ctx, IntValue<'ctx>> for NDArrayShapeProxy<'ctx, '_> { impl<'ctx, G: CodeGenerator + ?Sized> TypedArrayLikeMutator<'ctx, G, IntValue<'ctx>>
for NDArrayShapeProxy<'ctx, '_>
{
fn upcast_from_type( fn upcast_from_type(
&self, &self,
_: &mut CodeGenContext<'ctx, '_>, _: &CodeGenContext<'ctx, '_>,
_: &G,
value: IntValue<'ctx>, value: IntValue<'ctx>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
value.into() value.into()
@ -568,7 +624,7 @@ impl<'ctx> ArrayLikeValue<'ctx> for NDArrayStridesProxy<'ctx, '_> {
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
_: &G, _: &G,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
self.0.strides_field(ctx).get(ctx, self.0.as_base_value(), self.0.name) self.0.strides_field().load(ctx, self.0.value, self.0.name)
} }
fn size<G: CodeGenerator + ?Sized>( fn size<G: CodeGenerator + ?Sized>(
@ -583,8 +639,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: &mut CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
generator: &mut G, generator: &G,
idx: &IntValue<'ctx>, idx: &IntValue<'ctx>,
name: Option<&str>, name: Option<&str>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
@ -622,20 +678,26 @@ 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> TypedArrayLikeAccessor<'ctx, IntValue<'ctx>> for NDArrayStridesProxy<'ctx, '_> { impl<'ctx, G: CodeGenerator + ?Sized> TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>
for NDArrayStridesProxy<'ctx, '_>
{
fn downcast_to_type( fn downcast_to_type(
&self, &self,
_: &mut CodeGenContext<'ctx, '_>, _: &CodeGenContext<'ctx, '_>,
_: &G,
value: BasicValueEnum<'ctx>, value: BasicValueEnum<'ctx>,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
value.into_int_value() value.into_int_value()
} }
} }
impl<'ctx> TypedArrayLikeMutator<'ctx, IntValue<'ctx>> for NDArrayStridesProxy<'ctx, '_> { impl<'ctx, G: CodeGenerator + ?Sized> TypedArrayLikeMutator<'ctx, G, IntValue<'ctx>>
for NDArrayStridesProxy<'ctx, '_>
{
fn upcast_from_type( fn upcast_from_type(
&self, &self,
_: &mut CodeGenContext<'ctx, '_>, _: &CodeGenContext<'ctx, '_>,
_: &G,
value: IntValue<'ctx>, value: IntValue<'ctx>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
value.into() value.into()
@ -660,49 +722,27 @@ impl<'ctx> ArrayLikeValue<'ctx> for NDArrayDataProxy<'ctx, '_> {
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
_: &G, _: &G,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
self.0.data_field(ctx).get(ctx, self.0.as_base_value(), self.0.name) self.0.data_field().load(ctx, self.0.value, self.0.name)
} }
fn size<G: CodeGenerator + ?Sized>( fn size<G: CodeGenerator + ?Sized>(
&self, &self,
ctx: &CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
generator: &G, _: &G,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
irrt::ndarray::call_ndarray_calc_size( irrt::ndarray::call_nac3_ndarray_len(ctx, *self.0)
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: &mut CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
generator: &mut G, generator: &G,
idx: &IntValue<'ctx>, idx: &IntValue<'ctx>,
name: Option<&str>, name: Option<&str>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
let sizeof_elem = ctx let ptr = irrt::ndarray::call_nac3_ndarray_get_nth_pelement(ctx, *self.0, *idx);
.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
@ -713,7 +753,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()
} }
@ -761,55 +801,33 @@ 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: &mut CodeGenContext<'ctx, '_>, ctx: &CodeGenContext<'ctx, '_>,
generator: &mut G, generator: &G,
indices: &Index, indices: &Index,
name: Option<&str>, name: Option<&str>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
let llvm_usize = generator.get_size_type(ctx.ctx); assert_eq!(indices.element_type(ctx, generator), ctx.get_size_type().into());
let indices_elem_ty = indices let indices = TypedArrayLikeAdapter::from(
.ptr_offset(ctx, generator, &llvm_usize.const_zero(), None) indices.as_slice_value(ctx, generator),
.get_type() |_, _, v| v.into_int_value(),
.get_element_type(); |_, _, v| v.into(),
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 index = irrt::ndarray::call_ndarray_flatten_index(generator, ctx, *self.0, indices); let ptr = irrt::ndarray::call_nac3_ndarray_get_pelement_by_indices(
let sizeof_elem = ctx generator, ctx, *self.0, &indices,
.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();
let ptr = unsafe { // Current implementation is transparent - The returned pointer type is
ctx.builder // already cast into the expected type, allowing for immediately
.build_in_bounds_gep( // load/store.
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()
} }
@ -821,7 +839,7 @@ impl<'ctx, Index: UntypedArrayLikeAccessor<'ctx>> ArrayLikeIndexer<'ctx, Index>
indices: &Index, indices: &Index,
name: Option<&str>, name: Option<&str>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
let llvm_usize = generator.get_size_type(ctx.ctx); let llvm_usize = ctx.get_size_type();
let indices_size = indices.size(ctx, generator); let indices_size = indices.size(ctx, generator);
let nidx_leq_ndims = ctx let nidx_leq_ndims = ctx
@ -904,10 +922,9 @@ 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::with_capacity(ndims as usize); let mut strides = vec![0u64; ndims as usize];
let mut stride_product = 1u64; let mut stride_product = 1u64;
for i in 0..ndims { for axis in (0..ndims).rev() {
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];
} }
@ -921,13 +938,109 @@ 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_pointer_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> {
match self { match self {
ScalarOrNDArray::Scalar(scalar) => scalar, ScalarOrNDArray::Scalar(scalar) => scalar,
ScalarOrNDArray::NDArray(ndarray) => ndarray.as_base_value().into(), ScalarOrNDArray::NDArray(ndarray) => ndarray.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(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

@ -1,15 +1,18 @@
use inkwell::{ use inkwell::{
types::{BasicType, IntType}, types::{BasicType, IntType},
values::{BasicValueEnum, IntValue, PointerValue}, values::{BasicValueEnum, IntValue, PointerValue, StructValue},
AddressSpace, AddressSpace,
}; };
use super::{NDArrayValue, ProxyValue, TypedArrayLikeAccessor, TypedArrayLikeMutator}; use super::NDArrayValue;
use crate::codegen::{ use crate::codegen::{
irrt, irrt,
stmt::{gen_for_callback, BreakContinueHooks}, stmt::{gen_for_callback, BreakContinueHooks},
types::{ndarray::NDIterType, structure::StructField}, types::{
values::{ArraySliceValue, TypedArrayLikeAdapter}, ndarray::NDIterType,
structure::{StructField, StructProxyType},
},
values::{structure::StructProxyValue, ArraySliceValue, ProxyValue, TypedArrayLikeAdapter},
CodeGenContext, CodeGenerator, CodeGenContext, CodeGenerator,
}; };
@ -23,13 +26,26 @@ pub struct NDIterValue<'ctx> {
} }
impl<'ctx> NDIterValue<'ctx> { impl<'ctx> NDIterValue<'ctx> {
/// Checks whether `value` is an instance of `NDArray`, returning [Err] if `value` is not an /// Creates an [`NDArrayValue`] from a [`StructValue`].
/// instance. #[must_use]
pub fn is_representable( pub fn from_struct_value<G: CodeGenerator + ?Sized>(
value: PointerValue<'ctx>, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
val: StructValue<'ctx>,
parent: NDArrayValue<'ctx>,
indices: ArraySliceValue<'ctx>,
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
) -> Result<(), String> { name: Option<&'ctx str>,
<Self as ProxyValue>::Type::is_representable(value.get_type(), llvm_usize) ) -> Self {
let pval = generator
.gen_var_alloc(
ctx,
val.get_type().into(),
name.map(|name| format!("{name}.addr")).as_deref(),
)
.unwrap();
ctx.builder.build_store(pval, val).unwrap();
Self::from_pointer_value(pval, parent, indices, llvm_usize, name)
} }
/// Creates an [`NDArrayValue`] from a [`PointerValue`]. /// Creates an [`NDArrayValue`] from a [`PointerValue`].
@ -41,7 +57,7 @@ impl<'ctx> NDIterValue<'ctx> {
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>, name: Option<&'ctx str>,
) -> Self { ) -> Self {
debug_assert!(Self::is_representable(ptr, llvm_usize).is_ok()); debug_assert!(Self::is_instance(ptr, llvm_usize).is_ok());
Self { value: ptr, parent, indices, llvm_usize, name } Self { value: ptr, parent, indices, llvm_usize, name }
} }
@ -53,24 +69,20 @@ impl<'ctx> NDIterValue<'ctx> {
/// If `ndarray` is unsized, this returns true only for the first iteration. /// If `ndarray` is unsized, this returns true only for the first iteration.
/// If `ndarray` is 0-sized, this always returns false. /// If `ndarray` is 0-sized, this always returns false.
#[must_use] #[must_use]
pub fn has_element<G: CodeGenerator + ?Sized>( pub fn has_element(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
&self, irrt::ndarray::call_nac3_nditer_has_element(ctx, *self)
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
) -> IntValue<'ctx> {
irrt::ndarray::call_nac3_nditer_has_element(generator, ctx, *self)
} }
/// Go to the next element. If `has_element()` is false, then this has undefined behavior. /// Go to the next element. If `has_element()` is false, then this has undefined behavior.
/// ///
/// If `ndarray` is unsized, this can only be called once. /// If `ndarray` is unsized, this can only be called once.
/// If `ndarray` is 0-sized, this can never be called. /// If `ndarray` is 0-sized, this can never be called.
pub fn next<G: CodeGenerator + ?Sized>(&self, generator: &G, ctx: &CodeGenContext<'ctx, '_>) { pub fn next(&self, ctx: &CodeGenContext<'ctx, '_>) {
irrt::ndarray::call_nac3_nditer_next(generator, ctx, *self); irrt::ndarray::call_nac3_nditer_next(ctx, *self);
} }
fn element(&self, ctx: &CodeGenContext<'ctx, '_>) -> StructField<'ctx, PointerValue<'ctx>> { fn element_field(&self) -> StructField<'ctx, PointerValue<'ctx>> {
self.get_type().get_fields(ctx.ctx).element self.get_type().get_fields().element
} }
/// Get pointer to the current element. /// Get pointer to the current element.
@ -78,7 +90,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(ctx).get(ctx, self.as_base_value(), None); let p = self.element_field().load(ctx, self.as_abi_value(ctx), self.name);
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()
@ -91,47 +103,49 @@ impl<'ctx> NDIterValue<'ctx> {
ctx.builder.build_load(p, "value").unwrap() ctx.builder.build_load(p, "value").unwrap()
} }
fn nth(&self, ctx: &CodeGenContext<'ctx, '_>) -> StructField<'ctx, IntValue<'ctx>> { fn nth_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
self.get_type().get_fields(ctx.ctx).nth self.get_type().get_fields().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(ctx).get(ctx, self.as_base_value(), None) self.nth_field().load(ctx, self.as_abi_value(ctx), self.name)
} }
/// Get the indices of the current element. /// Get the indices of the current element.
#[must_use] #[must_use]
pub fn get_indices( pub fn get_indices<G: CodeGenerator + ?Sized>(
&'ctx self, &self,
) -> impl TypedArrayLikeAccessor<'ctx, IntValue<'ctx>> + TypedArrayLikeMutator<'ctx, IntValue<'ctx>> ) -> TypedArrayLikeAdapter<'ctx, G, IntValue<'ctx>> {
{
TypedArrayLikeAdapter::from( TypedArrayLikeAdapter::from(
self.indices, self.indices,
Box::new(|ctx, val| { |_, _, val| val.into_int_value(),
ctx.builder |_, _, val| val.into(),
.build_int_z_extend_or_bit_cast(val.into_int_value(), self.llvm_usize, "")
.unwrap()
}),
Box::new(|_, val| val.into()),
) )
} }
} }
impl<'ctx> ProxyValue<'ctx> for NDIterValue<'ctx> { impl<'ctx> ProxyValue<'ctx> for NDIterValue<'ctx> {
type ABI = PointerValue<'ctx>;
type Base = PointerValue<'ctx>; type Base = PointerValue<'ctx>;
type Type = NDIterType<'ctx>; type Type = NDIterType<'ctx>;
fn get_type(&self) -> Self::Type { fn get_type(&self) -> Self::Type {
NDIterType::from_type(self.as_base_value().get_type(), self.llvm_usize) NDIterType::from_pointer_type(self.as_base_value().get_type(), self.llvm_usize)
} }
fn as_base_value(&self) -> Self::Base { fn as_base_value(&self) -> Self::Base {
self.value self.value
} }
fn as_abi_value(&self, _: &CodeGenContext<'ctx, '_>) -> Self::ABI {
self.as_base_value()
}
} }
impl<'ctx> StructProxyValue<'ctx> for NDIterValue<'ctx> {}
impl<'ctx> From<NDIterValue<'ctx>> for PointerValue<'ctx> { impl<'ctx> From<NDIterValue<'ctx>> for PointerValue<'ctx> {
fn from(value: NDIterValue<'ctx>) -> Self { fn from(value: NDIterValue<'ctx>) -> Self {
value.as_base_value() value.as_base_value()
@ -162,13 +176,11 @@ impl<'ctx> NDArrayValue<'ctx> {
generator, generator,
ctx, ctx,
Some("ndarray_foreach"), Some("ndarray_foreach"),
|generator, ctx| { |generator, ctx| Ok(NDIterType::new(ctx).construct(generator, ctx, *self)),
Ok(NDIterType::new(generator, ctx.ctx).construct(generator, ctx, *self)) |_, ctx, nditer| Ok(nditer.has_element(ctx)),
},
|generator, ctx, nditer| Ok(nditer.has_element(generator, ctx)),
|generator, ctx, hooks, nditer| body(generator, ctx, hooks, nditer), |generator, ctx, hooks, nditer| body(generator, ctx, hooks, nditer),
|generator, ctx, nditer| { |_, ctx, nditer| {
nditer.next(generator, ctx); nditer.next(ctx);
Ok(()) Ok(())
}, },
) )

View File

@ -0,0 +1,152 @@
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 = ctx.get_size_type();
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_pointer_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_struct_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.extract_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,9 +1,16 @@
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::{
values::ndarray::{NDArrayValue, RustNDIndex}, irrt,
stmt::gen_if_callback,
types::ndarray::NDArrayType,
values::{
ndarray::{NDArrayValue, RustNDIndex},
ArrayLikeValue, ArraySliceValue, ProxyValue, TypedArrayLikeAccessor, TypedArrayLikeAdapter,
},
CodeGenContext, CodeGenerator, CodeGenContext, CodeGenerator,
}; };
@ -19,9 +26,7 @@ impl<'ctx> NDArrayValue<'ctx> {
ctx: &mut CodeGenContext<'ctx, '_>, ctx: &mut CodeGenContext<'ctx, '_>,
ndmin: u64, ndmin: u64,
) -> Self { ) -> Self {
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;
let ndims = self.ndims.unwrap();
if ndims < ndmin { if ndims < ndmin {
// Extend the dimensions with np.newaxis. // Extend the dimensions with np.newaxis.
@ -33,4 +38,117 @@ 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(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(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,
|_, ctx| Ok(self.is_c_contiguous(ctx)),
|generator, ctx| {
// Reshape is possible without copying
dst_ndarray.set_strides_contiguous(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(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

@ -0,0 +1,75 @@
use inkwell::{
types::IntType,
values::{BasicValueEnum, IntValue, PointerValue},
};
use super::ProxyValue;
use crate::codegen::{types::OptionType, CodeGenContext};
/// Proxy type for accessing a `Option` value in LLVM.
#[derive(Copy, Clone)]
pub struct OptionValue<'ctx> {
value: PointerValue<'ctx>,
llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>,
}
impl<'ctx> OptionValue<'ctx> {
/// Creates an [`OptionValue`] 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_instance(ptr, llvm_usize).is_ok());
Self { value: ptr, llvm_usize, name }
}
/// Returns an `i1` indicating if this `Option` instance does not hold a value.
#[must_use]
pub fn is_none(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
ctx.builder.build_is_null(self.value, "").unwrap()
}
/// Returns an `i1` indicating if this `Option` instance contains a value.
#[must_use]
pub fn is_some(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
ctx.builder.build_is_not_null(self.value, "").unwrap()
}
/// Loads the value present in this `Option` instance.
///
/// # Safety
///
/// The caller must ensure that this `option` value [contains a value][Self::is_some].
#[must_use]
pub unsafe fn load(&self, ctx: &CodeGenContext<'ctx, '_>) -> BasicValueEnum<'ctx> {
ctx.builder.build_load(self.value, "").unwrap()
}
}
impl<'ctx> ProxyValue<'ctx> for OptionValue<'ctx> {
type ABI = PointerValue<'ctx>;
type Base = PointerValue<'ctx>;
type Type = OptionType<'ctx>;
fn get_type(&self) -> Self::Type {
Self::Type::from_pointer_type(self.value.get_type(), self.llvm_usize)
}
fn as_base_value(&self) -> Self::Base {
self.value
}
fn as_abi_value(&self, _: &CodeGenContext<'ctx, '_>) -> Self::ABI {
self.as_base_value()
}
}
impl<'ctx> From<OptionValue<'ctx>> for PointerValue<'ctx> {
fn from(value: OptionValue<'ctx>) -> Self {
value.as_base_value()
}
}

View File

@ -1,27 +1,50 @@
use inkwell::values::{BasicValueEnum, IntValue, PointerValue}; use inkwell::{
types::IntType,
values::{ArrayValue, BasicValueEnum, IntValue, PointerValue},
};
use super::ProxyValue; use super::ProxyValue;
use crate::codegen::{types::RangeType, CodeGenContext}; use crate::codegen::{types::RangeType, CodeGenContext, CodeGenerator};
/// Proxy type for accessing a `range` value in LLVM. /// Proxy type for accessing a `range` value in LLVM.
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct RangeValue<'ctx> { pub struct RangeValue<'ctx> {
value: PointerValue<'ctx>, value: PointerValue<'ctx>,
llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>, name: Option<&'ctx str>,
} }
impl<'ctx> RangeValue<'ctx> { impl<'ctx> RangeValue<'ctx> {
/// Checks whether `value` is an instance of `range`, returning [Err] if `value` is not an instance. /// Creates an [`RangeValue`] from a [`PointerValue`].
pub fn is_representable(value: PointerValue<'ctx>) -> Result<(), String> { #[must_use]
RangeType::is_representable(value.get_type()) pub fn from_array_value<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
val: ArrayValue<'ctx>,
llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>,
) -> Self {
let pval = generator
.gen_var_alloc(
ctx,
val.get_type().into(),
name.map(|name| format!("{name}.addr")).as_deref(),
)
.unwrap();
ctx.builder.build_store(pval, val).unwrap();
Self::from_pointer_value(pval, llvm_usize, name)
} }
/// Creates an [`RangeValue`] from a [`PointerValue`]. /// Creates an [`RangeValue`] from a [`PointerValue`].
#[must_use] #[must_use]
pub fn from_pointer_value(ptr: PointerValue<'ctx>, name: Option<&'ctx str>) -> Self { pub fn from_pointer_value(
debug_assert!(Self::is_representable(ptr).is_ok()); ptr: PointerValue<'ctx>,
llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>,
) -> Self {
debug_assert!(Self::is_instance(ptr, llvm_usize).is_ok());
RangeValue { value: ptr, name } RangeValue { value: ptr, llvm_usize, name }
} }
fn ptr_to_start(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> { fn ptr_to_start(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
@ -31,7 +54,7 @@ impl<'ctx> RangeValue<'ctx> {
unsafe { unsafe {
ctx.builder ctx.builder
.build_in_bounds_gep( .build_in_bounds_gep(
self.as_base_value(), self.as_abi_value(ctx),
&[llvm_i32.const_zero(), llvm_i32.const_int(0, false)], &[llvm_i32.const_zero(), llvm_i32.const_int(0, false)],
var_name.as_str(), var_name.as_str(),
) )
@ -46,7 +69,7 @@ impl<'ctx> RangeValue<'ctx> {
unsafe { unsafe {
ctx.builder ctx.builder
.build_in_bounds_gep( .build_in_bounds_gep(
self.as_base_value(), self.as_abi_value(ctx),
&[llvm_i32.const_zero(), llvm_i32.const_int(1, false)], &[llvm_i32.const_zero(), llvm_i32.const_int(1, false)],
var_name.as_str(), var_name.as_str(),
) )
@ -61,7 +84,7 @@ impl<'ctx> RangeValue<'ctx> {
unsafe { unsafe {
ctx.builder ctx.builder
.build_in_bounds_gep( .build_in_bounds_gep(
self.as_base_value(), self.as_abi_value(ctx),
&[llvm_i32.const_zero(), llvm_i32.const_int(2, false)], &[llvm_i32.const_zero(), llvm_i32.const_int(2, false)],
var_name.as_str(), var_name.as_str(),
) )
@ -134,16 +157,21 @@ impl<'ctx> RangeValue<'ctx> {
} }
impl<'ctx> ProxyValue<'ctx> for RangeValue<'ctx> { impl<'ctx> ProxyValue<'ctx> for RangeValue<'ctx> {
type ABI = PointerValue<'ctx>;
type Base = PointerValue<'ctx>; type Base = PointerValue<'ctx>;
type Type = RangeType<'ctx>; type Type = RangeType<'ctx>;
fn get_type(&self) -> Self::Type { fn get_type(&self) -> Self::Type {
RangeType::from_type(self.value.get_type()) RangeType::from_pointer_type(self.value.get_type(), self.llvm_usize)
} }
fn as_base_value(&self) -> Self::Base { fn as_base_value(&self) -> Self::Base {
self.value self.value
} }
fn as_abi_value(&self, _: &CodeGenContext<'ctx, '_>) -> Self::ABI {
self.as_base_value()
}
} }
impl<'ctx> From<RangeValue<'ctx>> for PointerValue<'ctx> { impl<'ctx> From<RangeValue<'ctx>> for PointerValue<'ctx> {

View File

@ -0,0 +1,87 @@
use inkwell::{
types::IntType,
values::{BasicValueEnum, IntValue, PointerValue, StructValue},
};
use crate::codegen::{
types::{structure::StructField, StringType},
values::ProxyValue,
CodeGenContext,
};
/// Proxy type for accessing a `str` value in LLVM.
#[derive(Copy, Clone)]
pub struct StringValue<'ctx> {
value: StructValue<'ctx>,
llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>,
}
impl<'ctx> StringValue<'ctx> {
/// Creates an [`StringValue`] from a [`StructValue`].
#[must_use]
pub fn from_struct_value(
val: StructValue<'ctx>,
llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>,
) -> Self {
debug_assert!(Self::is_instance(val, llvm_usize).is_ok());
Self { value: val, llvm_usize, name }
}
/// Creates an [`StringValue`] from a [`PointerValue`].
#[must_use]
pub fn from_pointer_value(
ctx: &CodeGenContext<'ctx, '_>,
ptr: PointerValue<'ctx>,
llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>,
) -> Self {
let val = ctx.builder.build_load(ptr, "").map(BasicValueEnum::into_struct_value).unwrap();
Self::from_struct_value(val, llvm_usize, name)
}
fn ptr_field(&self) -> StructField<'ctx, PointerValue<'ctx>> {
self.get_type().get_fields().ptr
}
/// Returns the pointer to the beginning of the string.
pub fn extract_ptr(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
self.ptr_field().extract_value(ctx, self.value)
}
fn len_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
self.get_type().get_fields().len
}
/// Returns the length of the string.
pub fn extract_len(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
self.len_field().extract_value(ctx, self.value)
}
}
impl<'ctx> ProxyValue<'ctx> for StringValue<'ctx> {
type ABI = StructValue<'ctx>;
type Base = StructValue<'ctx>;
type Type = StringType<'ctx>;
fn get_type(&self) -> Self::Type {
Self::Type::from_struct_type(self.value.get_type(), self.llvm_usize)
}
fn as_base_value(&self) -> Self::Base {
self.value
}
fn as_abi_value(&self, _: &CodeGenContext<'ctx, '_>) -> Self::ABI {
self.as_base_value()
}
}
impl<'ctx> From<StringValue<'ctx>> for StructValue<'ctx> {
fn from(value: StringValue<'ctx>) -> Self {
value.as_base_value()
}
}

View File

@ -0,0 +1,24 @@
use inkwell::values::{BasicValueEnum, PointerValue, StructValue};
use super::ProxyValue;
use crate::codegen::{types::structure::StructProxyType, CodeGenContext};
/// An LLVM value that is used to represent a corresponding structure-like value in NAC3.
pub trait StructProxyValue<'ctx>:
ProxyValue<'ctx, Base = PointerValue<'ctx>, Type: StructProxyType<'ctx, Value = Self>>
{
/// Returns this value as a [`StructValue`].
#[must_use]
fn get_struct_value(&self, ctx: &CodeGenContext<'ctx, '_>) -> StructValue<'ctx> {
ctx.builder
.build_load(self.get_pointer_value(ctx), "")
.map(BasicValueEnum::into_struct_value)
.unwrap()
}
/// Returns this value as a [`PointerValue`].
#[must_use]
fn get_pointer_value(&self, _: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
self.as_base_value()
}
}

View File

@ -0,0 +1,103 @@
use inkwell::{
types::IntType,
values::{BasicValue, BasicValueEnum, PointerValue, 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> {
/// 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_instance(value, llvm_usize).is_ok());
Self { value, llvm_usize, name }
}
/// Creates an [`TupleValue`] from a [`PointerValue`].
#[must_use]
pub fn from_pointer_value(
ctx: &CodeGenContext<'ctx, '_>,
ptr: PointerValue<'ctx>,
llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>,
) -> Self {
Self::from_struct_value(
ctx.builder
.build_load(ptr, name.unwrap_or_default())
.map(BasicValueEnum::into_struct_value)
.unwrap(),
llvm_usize,
name,
)
}
/// Stores a value into the tuple element at the given `index`.
pub fn insert_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 extract_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 ABI = StructValue<'ctx>;
type Base = StructValue<'ctx>;
type Type = TupleType<'ctx>;
fn get_type(&self) -> Self::Type {
TupleType::from_struct_type(self.as_base_value().get_type(), self.llvm_usize)
}
fn as_base_value(&self) -> Self::Base {
self.value
}
fn as_abi_value(&self, _: &CodeGenContext<'ctx, '_>) -> Self::ABI {
self.as_base_value()
}
}
impl<'ctx> From<TupleValue<'ctx>> for StructValue<'ctx> {
fn from(value: TupleValue<'ctx>) -> Self {
value.as_base_value()
}
}

View File

@ -1,14 +1,17 @@
use inkwell::{ use inkwell::{
types::IntType, types::IntType,
values::{IntValue, PointerValue}, values::{IntValue, PointerValue, StructValue},
}; };
use nac3parser::ast::Expr; use nac3parser::ast::Expr;
use crate::{ use crate::{
codegen::{ codegen::{
types::{structure::StructField, utils::SliceType}, types::{
values::ProxyValue, structure::{StructField, StructProxyType},
utils::SliceType,
},
values::{structure::StructProxyValue, ProxyValue},
CodeGenContext, CodeGenerator, CodeGenContext, CodeGenerator,
}, },
typecheck::typedef::Type, typecheck::typedef::Type,
@ -24,13 +27,25 @@ pub struct SliceValue<'ctx> {
} }
impl<'ctx> SliceValue<'ctx> { impl<'ctx> SliceValue<'ctx> {
/// Checks whether `value` is an instance of `ContiguousNDArray`, returning [Err] if `value` is /// Creates an [`SliceValue`] from a [`StructValue`].
/// not an instance. #[must_use]
pub fn is_representable( pub fn from_struct_value<G: CodeGenerator + ?Sized>(
value: PointerValue<'ctx>, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
val: StructValue<'ctx>,
int_ty: IntType<'ctx>,
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
) -> Result<(), String> { name: Option<&'ctx str>,
<Self as ProxyValue<'ctx>>::Type::is_representable(value.get_type(), llvm_usize) ) -> Self {
let pval = generator
.gen_var_alloc(
ctx,
val.get_type().into(),
name.map(|name| format!("{name}.addr")).as_deref(),
)
.unwrap();
ctx.builder.build_store(pval, val).unwrap();
Self::from_pointer_value(pval, int_ty, llvm_usize, name)
} }
/// Creates an [`SliceValue`] from a [`PointerValue`]. /// Creates an [`SliceValue`] from a [`PointerValue`].
@ -41,7 +56,7 @@ impl<'ctx> SliceValue<'ctx> {
llvm_usize: IntType<'ctx>, llvm_usize: IntType<'ctx>,
name: Option<&'ctx str>, name: Option<&'ctx str>,
) -> Self { ) -> Self {
debug_assert!(Self::is_representable(ptr, llvm_usize).is_ok()); debug_assert!(Self::is_instance(ptr, llvm_usize).is_ok());
Self { value: ptr, int_ty, llvm_usize, name } Self { value: ptr, int_ty, llvm_usize, name }
} }
@ -51,7 +66,7 @@ impl<'ctx> SliceValue<'ctx> {
} }
pub fn load_start_defined(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> { pub fn load_start_defined(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
self.start_defined_field().get(ctx, self.value, self.name) self.start_defined_field().load(ctx, self.value, self.name)
} }
fn start_field(&self) -> StructField<'ctx, IntValue<'ctx>> { fn start_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
@ -59,22 +74,22 @@ impl<'ctx> SliceValue<'ctx> {
} }
pub fn load_start(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> { pub fn load_start(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
self.start_field().get(ctx, self.value, self.name) self.start_field().load(ctx, self.value, self.name)
} }
pub fn store_start(&self, ctx: &CodeGenContext<'ctx, '_>, value: Option<IntValue<'ctx>>) { pub fn store_start(&self, ctx: &CodeGenContext<'ctx, '_>, value: Option<IntValue<'ctx>>) {
match value { match value {
Some(start) => { Some(start) => {
self.start_defined_field().set( self.start_defined_field().store(
ctx, ctx,
self.value, self.value,
ctx.ctx.bool_type().const_all_ones(), ctx.ctx.bool_type().const_all_ones(),
self.name, self.name,
); );
self.start_field().set(ctx, self.value, start, self.name); self.start_field().store(ctx, self.value, start, self.name);
} }
None => self.start_defined_field().set( None => self.start_defined_field().store(
ctx, ctx,
self.value, self.value,
ctx.ctx.bool_type().const_zero(), ctx.ctx.bool_type().const_zero(),
@ -88,7 +103,7 @@ impl<'ctx> SliceValue<'ctx> {
} }
pub fn load_stop_defined(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> { pub fn load_stop_defined(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
self.stop_defined_field().get(ctx, self.value, self.name) self.stop_defined_field().load(ctx, self.value, self.name)
} }
fn stop_field(&self) -> StructField<'ctx, IntValue<'ctx>> { fn stop_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
@ -96,22 +111,22 @@ impl<'ctx> SliceValue<'ctx> {
} }
pub fn load_stop(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> { pub fn load_stop(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
self.stop_field().get(ctx, self.value, self.name) self.stop_field().load(ctx, self.value, self.name)
} }
pub fn store_stop(&self, ctx: &CodeGenContext<'ctx, '_>, value: Option<IntValue<'ctx>>) { pub fn store_stop(&self, ctx: &CodeGenContext<'ctx, '_>, value: Option<IntValue<'ctx>>) {
match value { match value {
Some(stop) => { Some(stop) => {
self.stop_defined_field().set( self.stop_defined_field().store(
ctx, ctx,
self.value, self.value,
ctx.ctx.bool_type().const_all_ones(), ctx.ctx.bool_type().const_all_ones(),
self.name, self.name,
); );
self.stop_field().set(ctx, self.value, stop, self.name); self.stop_field().store(ctx, self.value, stop, self.name);
} }
None => self.stop_defined_field().set( None => self.stop_defined_field().store(
ctx, ctx,
self.value, self.value,
ctx.ctx.bool_type().const_zero(), ctx.ctx.bool_type().const_zero(),
@ -125,7 +140,7 @@ impl<'ctx> SliceValue<'ctx> {
} }
pub fn load_step_defined(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> { pub fn load_step_defined(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
self.step_defined_field().get(ctx, self.value, self.name) self.step_defined_field().load(ctx, self.value, self.name)
} }
fn step_field(&self) -> StructField<'ctx, IntValue<'ctx>> { fn step_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
@ -133,22 +148,22 @@ impl<'ctx> SliceValue<'ctx> {
} }
pub fn load_step(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> { pub fn load_step(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
self.step_field().get(ctx, self.value, self.name) self.step_field().load(ctx, self.value, self.name)
} }
pub fn store_step(&self, ctx: &CodeGenContext<'ctx, '_>, value: Option<IntValue<'ctx>>) { pub fn store_step(&self, ctx: &CodeGenContext<'ctx, '_>, value: Option<IntValue<'ctx>>) {
match value { match value {
Some(step) => { Some(step) => {
self.step_defined_field().set( self.step_defined_field().store(
ctx, ctx,
self.value, self.value,
ctx.ctx.bool_type().const_all_ones(), ctx.ctx.bool_type().const_all_ones(),
self.name, self.name,
); );
self.step_field().set(ctx, self.value, step, self.name); self.step_field().store(ctx, self.value, step, self.name);
} }
None => self.step_defined_field().set( None => self.step_defined_field().store(
ctx, ctx,
self.value, self.value,
ctx.ctx.bool_type().const_zero(), ctx.ctx.bool_type().const_zero(),
@ -159,18 +174,25 @@ impl<'ctx> SliceValue<'ctx> {
} }
impl<'ctx> ProxyValue<'ctx> for SliceValue<'ctx> { impl<'ctx> ProxyValue<'ctx> for SliceValue<'ctx> {
type ABI = PointerValue<'ctx>;
type Base = PointerValue<'ctx>; type Base = PointerValue<'ctx>;
type Type = SliceType<'ctx>; type Type = SliceType<'ctx>;
fn get_type(&self) -> Self::Type { fn get_type(&self) -> Self::Type {
Self::Type::from_type(self.value.get_type(), self.int_ty, self.llvm_usize) Self::Type::from_pointer_type(self.value.get_type(), self.int_ty, self.llvm_usize)
} }
fn as_base_value(&self) -> Self::Base { fn as_base_value(&self) -> Self::Base {
self.value self.value
} }
fn as_abi_value(&self, _: &CodeGenContext<'ctx, '_>) -> Self::ABI {
self.as_base_value()
}
} }
impl<'ctx> StructProxyValue<'ctx> for SliceValue<'ctx> {}
impl<'ctx> From<SliceValue<'ctx>> for PointerValue<'ctx> { impl<'ctx> From<SliceValue<'ctx>> for PointerValue<'ctx> {
fn from(value: SliceValue<'ctx>) -> Self { fn from(value: SliceValue<'ctx>) -> Self {
value.as_base_value() 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::{chain, izip, Itertools}; use itertools::{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,10 +452,10 @@ pub fn parse_type_annotation<T>(
type_vars.len() type_vars.len()
)])); )]));
} }
let fields = chain( let fields = fields
fields.iter().map(|(k, v, m)| (*k, (*v, *m))), .iter()
methods.iter().map(|(k, v, _)| (*k, (*v, false))), .map(|(k, v, m)| (*k, (*v, *m)))
) .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 {
@ -598,10 +598,12 @@ impl dyn SymbolResolver + Send + Sync {
unifier.internal_stringify( unifier.internal_stringify(
ty, ty,
&mut |id| { &mut |id| {
let TopLevelDef::Class { name, .. } = &*top_level_defs[id].read() else { let top_level_def = &*top_level_defs[id].read();
unreachable!("expected class definition") let (TopLevelDef::Class { name, .. } | TopLevelDef::Module { name, .. }) =
top_level_def
else {
unreachable!("expected class/module definition")
}; };
name.to_string() name.to_string()
}, },
&mut |id| format!("typevar{id}"), &mut |id| format!("typevar{id}"),

View File

@ -1,26 +1,27 @@
use std::iter::once; use std::iter::once;
use indexmap::IndexMap; use indexmap::IndexMap;
use inkwell::{ use inkwell::{values::BasicValue, IntPredicate};
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::{debug_assert_prim_is_allowed, make_exception_fields, PrimDef, PrimDefDetails}, helper::{
numpy::make_ndarray_ty, arraylike_flatten_element_type, debug_assert_prim_is_allowed, extract_ndims,
make_exception_fields, PrimDef, PrimDefDetails,
},
numpy::{make_ndarray_ty, unpack_ndarray_var_tys},
*, *,
}; };
use crate::{ use crate::{
codegen::{ codegen::{
builtin_fns, builtin_fns,
numpy::*, numpy::*,
stmt::exn_constructor, stmt::{exn_constructor, gen_if_callback},
values::{ProxyValue, RangeValue}, types::{ndarray::NDArrayType, RangeType},
values::{
ndarray::{shape::parse_numpy_int_sequence, ScalarOrNDArray},
ProxyValue,
},
}, },
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},
@ -35,9 +36,7 @@ pub fn get_exn_constructor(
unifier: &mut Unifier, unifier: &mut Unifier,
primitives: &PrimitiveStore, primitives: &PrimitiveStore,
) -> (TopLevelDef, TopLevelDef, Type, Type) { ) -> (TopLevelDef, TopLevelDef, Type, Type) {
let int32 = primitives.int32; let PrimitiveStore { int32, int64, str: string, .. } = *primitives;
let int64 = primitives.int64;
let string = primitives.str;
let exception_fields = make_exception_fields(int32, int64, string); let exception_fields = make_exception_fields(int32, int64, string);
let exn_cons_args = vec![ let exn_cons_args = vec![
FuncArg { FuncArg {
@ -148,144 +147,6 @@ 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()
@ -336,7 +197,6 @@ 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,
@ -450,7 +310,6 @@ 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,
@ -512,6 +371,14 @@ 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 => {
@ -540,6 +407,8 @@ impl<'a> BuiltinBuilder<'a> {
PrimDef::FunNpIsNan | PrimDef::FunNpIsInf => self.build_np_float_to_bool_function(prim), PrimDef::FunNpIsNan | PrimDef::FunNpIsInf => self.build_np_float_to_bool_function(prim),
PrimDef::FunNpAny | PrimDef::FunNpAll => self.build_np_any_all_function(prim),
PrimDef::FunNpSin PrimDef::FunNpSin
| PrimDef::FunNpCos | PrimDef::FunNpCos
| PrimDef::FunNpTan | PrimDef::FunNpTan
@ -577,10 +446,6 @@ 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
@ -710,7 +575,7 @@ impl<'a> BuiltinBuilder<'a> {
let (zelf_ty, zelf) = obj.unwrap(); let (zelf_ty, zelf) = obj.unwrap();
let zelf = let zelf =
zelf.to_basic_value_enum(ctx, generator, zelf_ty)?.into_pointer_value(); zelf.to_basic_value_enum(ctx, generator, zelf_ty)?.into_pointer_value();
let zelf = RangeValue::from_pointer_value(zelf, Some("range")); let zelf = RangeType::new(ctx).map_pointer_value(zelf, Some("range"));
let mut start = None; let mut start = None;
let mut stop = None; let mut stop = None;
@ -797,7 +662,7 @@ impl<'a> BuiltinBuilder<'a> {
zelf.store_end(ctx, stop); zelf.store_end(ctx, stop);
zelf.store_step(ctx, step); zelf.store_step(ctx, step);
Ok(Some(zelf.as_base_value().into())) Ok(Some(zelf.as_abi_value(ctx).into()))
}, },
)))), )))),
loc: None, loc: None,
@ -1386,6 +1251,172 @@ 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_pointer_value(ndarray.into_pointer_value(), None);
let size = ctx
.builder
.build_int_truncate_or_bit_cast(ndarray.size(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_pointer_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_abi_value(ctx).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_pointer_value(arg_val.into_pointer_value(), None);
let ndarray = ndarray.transpose(generator, ctx, None); // TODO: Add axes argument
Ok(Some(ndarray.as_abi_value(ctx).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_pointer_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_abi_value(ctx).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;
@ -1693,6 +1724,64 @@ impl<'a> BuiltinBuilder<'a> {
) )
} }
fn build_np_any_all_function(&mut self, prim: PrimDef) -> TopLevelDef {
debug_assert_prim_is_allowed(prim, &[PrimDef::FunNpAny, PrimDef::FunNpAll]);
let param_ty = &[(self.num_or_ndarray_ty.ty, "a")];
let ret_ty = self.primitives.bool;
let var_map = &self.num_or_ndarray_var_map;
let codegen_callback: Box<GenCallCallback> =
Box::new(move |ctx, _, fun, args, generator| {
let llvm_i1 = ctx.ctx.bool_type();
let llvm_i1_k0 = llvm_i1.const_zero();
let llvm_i1_k1 = llvm_i1.const_all_ones();
let a_ty = fun.0.args[0].ty;
let a_val = args[0].1.clone().to_basic_value_enum(ctx, generator, a_ty)?;
let a = ScalarOrNDArray::from_value(generator, ctx, (a_ty, a_val));
let a_elem_ty = arraylike_flatten_element_type(&mut ctx.unifier, a_ty);
let (init, sc_val) = match prim {
PrimDef::FunNpAny => (llvm_i1_k0, llvm_i1_k1),
PrimDef::FunNpAll => (llvm_i1_k1, llvm_i1_k0),
_ => unreachable!(),
};
let acc = a.fold(generator, ctx, init, |generator, ctx, hooks, acc, elem| {
gen_if_callback(
generator,
ctx,
|_, ctx| {
Ok(ctx
.builder
.build_int_compare(IntPredicate::EQ, acc, sc_val, "")
.unwrap())
},
|_, ctx| {
if let Some(hooks) = hooks {
hooks.build_break_branch(&ctx.builder);
}
Ok(())
},
|_, _| Ok(()),
)?;
let is_truthy =
builtin_fns::call_bool(generator, ctx, (a_elem_ty, elem))?.into_int_value();
Ok(match prim {
PrimDef::FunNpAny => ctx.builder.build_or(acc, is_truthy, "").unwrap(),
PrimDef::FunNpAll => ctx.builder.build_and(acc, is_truthy, "").unwrap(),
_ => unreachable!(),
})
})?;
Ok(Some(acc.as_basic_value_enum()))
});
create_fn_by_codegen(self.unifier, var_map, prim.name(), ret_ty, param_ty, codegen_callback)
}
/// Build 1-ary numpy/scipy functions that take in a float or an ndarray and return a value of the same type as the input. /// Build 1-ary numpy/scipy functions that take in a float or an ndarray and return a value of the same type as the input.
fn build_np_sp_float_or_ndarray_1ary_function(&mut self, prim: PrimDef) -> TopLevelDef { fn build_np_sp_float_or_ndarray_1ary_function(&mut self, prim: PrimDef) -> TopLevelDef {
debug_assert_prim_is_allowed( debug_assert_prim_is_allowed(
@ -1873,57 +1962,6 @@ 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`
@ -1955,10 +1993,12 @@ 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)?;
Ok(Some(ndarray_dot(generator, ctx, (x1_ty, x1_val), (x2_ty, x2_val))?)) let result = ndarray_dot(generator, ctx, (x1_ty, x1_val), (x2_ty, x2_val))?;
Ok(Some(result))
}), }),
), ),

View File

@ -101,7 +101,9 @@ impl TopLevelComposer {
let builtin_name_list = definition_ast_list let builtin_name_list = definition_ast_list
.iter() .iter()
.map(|def_ast| match *def_ast.0.read() { .map(|def_ast| match *def_ast.0.read() {
TopLevelDef::Class { name, .. } => name.to_string(), TopLevelDef::Class { name, .. } | TopLevelDef::Module { name, .. } => {
name.to_string()
}
TopLevelDef::Function { simple_name, .. } TopLevelDef::Function { simple_name, .. }
| TopLevelDef::Variable { simple_name, .. } => simple_name.to_string(), | TopLevelDef::Variable { simple_name, .. } => simple_name.to_string(),
}) })
@ -201,6 +203,43 @@ impl TopLevelComposer {
self.definition_ast_list.iter().map(|(def, ..)| def.clone()).collect_vec() self.definition_ast_list.iter().map(|(def, ..)| def.clone()).collect_vec()
} }
/// register top level modules
pub fn register_top_level_module(
&mut self,
module_name: &str,
name_to_pyid: &Rc<HashMap<StrRef, u64>>,
resolver: Arc<dyn SymbolResolver + Send + Sync>,
location: Option<Location>,
) -> Result<DefinitionId, String> {
let mut methods: HashMap<StrRef, DefinitionId> = HashMap::new();
let mut attributes: Vec<(StrRef, DefinitionId)> = Vec::new();
for (name, _) in name_to_pyid.iter() {
if let Ok(def_id) = resolver.get_identifier_def(*name) {
// Avoid repeated attribute instances resulting from multiple imports of same module
if self.defined_names.contains(&format!("{module_name}.{name}")) {
match &*self.definition_ast_list[def_id.0].0.read() {
TopLevelDef::Class { .. } | TopLevelDef::Function { .. } => {
methods.insert(*name, def_id);
}
_ => attributes.push((*name, def_id)),
}
}
};
}
let module_def = TopLevelDef::Module {
name: module_name.to_string().into(),
module_id: DefinitionId(self.definition_ast_list.len()),
methods,
attributes,
resolver: Some(resolver),
loc: location,
};
self.definition_ast_list.push((Arc::new(RwLock::new(module_def)), None));
Ok(DefinitionId(self.definition_ast_list.len() - 1))
}
/// register, just remember the names of top level classes/function /// register, just remember the names of top level classes/function
/// and check duplicate class/method/function definition /// and check duplicate class/method/function definition
pub fn register_top_level( pub fn register_top_level(
@ -469,10 +508,10 @@ impl TopLevelComposer {
self.analyze_top_level_class_definition()?; self.analyze_top_level_class_definition()?;
self.analyze_top_level_class_fields_methods()?; self.analyze_top_level_class_fields_methods()?;
self.analyze_top_level_function()?; self.analyze_top_level_function()?;
self.analyze_top_level_variables()?;
if inference { if inference {
self.analyze_function_instance()?; self.analyze_function_instance()?;
} }
self.analyze_top_level_variables()?;
Ok(()) Ok(())
} }
@ -1052,7 +1091,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 itertools::enumerate(args.args.iter().skip(1)) { for (idx, x) in args.args.iter().skip(1).enumerate() {
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(
@ -1410,7 +1449,7 @@ impl TopLevelComposer {
Ok(()) Ok(())
} }
/// step 4, analyze and call type inferencer to fill the `instance_to_stmt` of /// step 5, analyze and call type inferencer to fill the `instance_to_stmt` of
/// [`TopLevelDef::Function`] /// [`TopLevelDef::Function`]
fn analyze_function_instance(&mut self) -> Result<(), HashSet<String>> { fn analyze_function_instance(&mut self) -> Result<(), HashSet<String>> {
// first get the class constructor type correct for the following type check in function body // first get the class constructor type correct for the following type check in function body
@ -1482,8 +1521,7 @@ impl TopLevelComposer {
.any(|ann| matches!(ann, TypeAnnotation::CustomClass { id, .. } if id.0 == 7)) .any(|ann| matches!(ann, TypeAnnotation::CustomClass { id, .. } if id.0 == 7))
{ {
// create constructor for these classes // create constructor for these classes
let string = primitives_ty.str; let PrimitiveStore { str: string, int64, .. } = *primitives_ty;
let int64 = primitives_ty.int64;
let signature = unifier.add_ty(TypeEnum::TFunc(FunSignature { let signature = unifier.add_ty(TypeEnum::TFunc(FunSignature {
args: vec![ args: vec![
FuncArg { FuncArg {
@ -1941,7 +1979,7 @@ impl TopLevelComposer {
Ok(()) Ok(())
} }
/// Step 5. Analyze and populate the types of global variables. /// Step 4. Analyze and populate the types of global variables.
fn analyze_top_level_variables(&mut self) -> Result<(), HashSet<String>> { fn analyze_top_level_variables(&mut self) -> Result<(), HashSet<String>> {
let def_list = &self.definition_ast_list; let def_list = &self.definition_ast_list;
let temp_def_list = self.extract_def_list(); let temp_def_list = self.extract_def_list();
@ -1959,6 +1997,19 @@ impl TopLevelComposer {
let resolver = &**resolver.as_ref().unwrap(); let resolver = &**resolver.as_ref().unwrap();
if let Some(ty_decl) = ty_decl { if let Some(ty_decl) = ty_decl {
let ty_decl = match &ty_decl.node {
ExprKind::Subscript { value, slice, .. }
if matches!(
&value.node,
ast::ExprKind::Name { id, .. } if self.core_config.kernel_ann.map_or(false, |c| id == &c.into())
) =>
{
slice
}
_ if self.core_config.kernel_ann.is_none() => ty_decl,
_ => unreachable!("Global variables should be annotated with Kernel[]"), // ignore fields annotated otherwise
};
let ty_annotation = parse_ast_to_type_annotation_kinds( let ty_annotation = parse_ast_to_type_annotation_kinds(
resolver, resolver,
&temp_def_list, &temp_def_list,

View File

@ -54,6 +54,16 @@ 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,
@ -101,8 +111,8 @@ pub enum PrimDef {
FunNpLdExp, FunNpLdExp,
FunNpHypot, FunNpHypot,
FunNpNextAfter, FunNpNextAfter,
FunNpTranspose, FunNpAny,
FunNpReshape, FunNpAll,
// Linalg functions // Linalg functions
FunNpDot, FunNpDot,
@ -240,6 +250,16 @@ 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),
@ -287,8 +307,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::FunNpAny => fun("np_any", None),
PrimDef::FunNpReshape => fun("np_reshape", None), PrimDef::FunNpAll => fun("np_all", None),
// Linalg functions // Linalg functions
PrimDef::FunNpDot => fun("np_dot", None), PrimDef::FunNpDot => fun("np_dot", None),
@ -359,21 +379,37 @@ pub fn make_exception_fields(int32: Type, int64: Type, str: Type) -> Vec<(StrRef
impl TopLevelDef { impl TopLevelDef {
pub fn to_string(&self, unifier: &mut Unifier) -> String { pub fn to_string(&self, unifier: &mut Unifier) -> String {
match self { match self {
TopLevelDef::Class { name, ancestors, fields, methods, type_vars, .. } => { TopLevelDef::Module { name, attributes, methods, .. } => {
format!(
"Module {{\nname: {:?},\nattributes: {:?}\nmethods: {:?}\n}}",
name,
attributes.iter().map(|(n, _)| n.to_string()).collect_vec(),
methods.iter().map(|(n, _)| n.to_string()).collect_vec()
)
}
TopLevelDef::Class {
name, ancestors, fields, methods, attributes, type_vars, ..
} => {
let fields_str = fields let fields_str = fields
.iter() .iter()
.map(|(n, ty, _)| (n.to_string(), unifier.stringify(*ty))) .map(|(n, ty, _)| (n.to_string(), unifier.stringify(*ty)))
.collect_vec(); .collect_vec();
let attributes_str = attributes
.iter()
.map(|(n, ty, _)| (n.to_string(), unifier.stringify(*ty)))
.collect_vec();
let methods_str = methods let methods_str = methods
.iter() .iter()
.map(|(n, ty, id)| (n.to_string(), unifier.stringify(*ty), *id)) .map(|(n, ty, id)| (n.to_string(), unifier.stringify(*ty), *id))
.collect_vec(); .collect_vec();
format!( format!(
"Class {{\nname: {:?},\nancestors: {:?},\nfields: {:?},\nmethods: {:?},\ntype_vars: {:?}\n}}", "Class {{\nname: {:?},\nancestors: {:?},\nfields: {:?},\nattributes: {:?},\nmethods: {:?},\ntype_vars: {:?}\n}}",
name, name,
ancestors.iter().map(|ancestor| ancestor.stringify(unifier)).collect_vec(), ancestors.iter().map(|ancestor| ancestor.stringify(unifier)).collect_vec(),
fields_str.iter().map(|(a, _)| a).collect_vec(), fields_str.iter().map(|(a, _)| a).collect_vec(),
attributes_str.iter().map(|(a, _)| a).collect_vec(),
methods_str.iter().map(|(a, b, _)| (a, b)).collect_vec(), methods_str.iter().map(|(a, b, _)| (a, b)).collect_vec(),
type_vars.iter().map(|id| unifier.stringify(*id)).collect_vec(), type_vars.iter().map(|id| unifier.stringify(*id)).collect_vec(),
) )

View File

@ -92,6 +92,20 @@ pub struct FunInstance {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum TopLevelDef { pub enum TopLevelDef {
Module {
/// Name of the module
name: StrRef,
/// Module ID used for [`TypeEnum`]
module_id: DefinitionId,
/// `DefinitionId` of `TopLevelDef::{Class, Function}` within the module
methods: HashMap<StrRef, DefinitionId>,
/// `DefinitionId` of `TopLevelDef::{Variable}` within the module
attributes: Vec<(StrRef, DefinitionId)>,
/// Symbol resolver of the module defined the class.
resolver: Option<Arc<dyn SymbolResolver + Send + Sync>>,
/// Definition location.
loc: Option<Location>,
},
Class { Class {
/// Name for error messages and symbols. /// Name for error messages and symbols.
name: StrRef, name: StrRef,

View File

@ -3,10 +3,10 @@ source: nac3core/src/toplevel/test.rs
expression: res_vec expression: res_vec
--- ---
[ [
"Class {\nname: \"B\",\nancestors: [\"B\"],\nfields: [\"aa\"],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"foo\", \"fn[[b:T], none]\")],\ntype_vars: []\n}\n", "Class {\nname: \"B\",\nancestors: [\"B\"],\nfields: [\"aa\"],\nattributes: [],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"foo\", \"fn[[b:T], 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: \"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\"],\nattributes: [],\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(246)]\n}\n", "Function {\nname: \"Generic_A.fun\",\nsig: \"fn[[a:int32], V]\",\nvar_id: [TypeVarId(261)]\n}\n",
] ]

View File

@ -3,13 +3,13 @@ source: nac3core/src/toplevel/test.rs
expression: res_vec expression: res_vec
--- ---
[ [
"Class {\nname: \"A\",\nancestors: [\"A[T]\"],\nfields: [\"a\", \"b\", \"c\"],\nmethods: [(\"__init__\", \"fn[[t:T], none]\"), (\"fun\", \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\"), (\"foo\", \"fn[[c:C], none]\")],\ntype_vars: [\"T\"]\n}\n", "Class {\nname: \"A\",\nancestors: [\"A[T]\"],\nfields: [\"a\", \"b\", \"c\"],\nattributes: [],\nmethods: [(\"__init__\", \"fn[[t:T], none]\"), (\"fun\", \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\"), (\"foo\", \"fn[[c:C], none]\")],\ntype_vars: [\"T\"]\n}\n",
"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[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", "Class {\nname: \"B\",\nancestors: [\"B[typevar245]\", \"A[float]\"],\nfields: [\"a\", \"b\", \"c\", \"d\"],\nattributes: [],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\"), (\"foo\", \"fn[[c:C], none]\")],\ntype_vars: [\"typevar245\"]\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\"],\nattributes: [],\nmethods: [(\"__init__\", \"fn[[], none]\"), (\"fun\", \"fn[[a:int32, b:T], list[virtual[B[bool]]]]\"), (\"foo\", \"fn[[c:C], 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",
] ]

Some files were not shown because too many files have changed in this diff Show More