Compare commits

..

113 Commits

Author SHA1 Message Date
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
1531b6cc98 cargo: update dependencies 2024-12-13 19:42:01 +08:00
9bbc40bbfa flake: update dependencies 2024-12-13 19:41:52 +08:00
790e56d106 msys2: update 2024-12-13 19:39:39 +08:00
a00eb7969e [core] codegen: Implement matrix_power
Last of the functions that need to be ported over to strided-ndarray.
2024-12-13 15:23:31 +08:00
27a6f47330 [core] codegen: Implement construction of unsized ndarrays
Partially based on f731e604: core/ndstrides: add more ScalarOrNDArray
and NDArrayObject utils.
2024-12-13 15:23:31 +08:00
061747c67b [core] codegen: Implement NDArrayValue::atleast_nd
Based on 9cfa2622: core/ndstrides: add NDArrayObject::atleast_nd.
2024-12-13 15:23:31 +08:00
dc91d9e35a [core] codegen: Implement ScalarOrNDArray and use it in indexing
Based on 8f9d2d82: core/ndstrides: implement ndarray indexing.
2024-12-13 15:23:31 +08:00
438943ac6f [core] codegen: Implement indexing for NDArray
Based on 8f9d2d82: core/ndstrides: implement ndarray indexing

The functionality for `...` and `np.newaxis` is there in IRRT, but there
is no implementation of them for @kernel Python expressions because of
M-Labs/nac3#486.
2024-12-13 15:23:31 +08:00
678e56c95d [core] irrt: rename NDIndex to NDIndexInt
Unfortunately the name `NDIndex` is used in later commits. Renaming this
typedef to `NDIndexInt` to avoid amending. `NDIndexInt` will be removed
anyway when ndarray strides is completed.
2024-12-13 15:23:31 +08:00
fdfc80ca5f [core] codegen: Implement Slice{Type,Value}, RustSlice
Based on 01c96396: core/irrt: add Slice and Range and part of
8f9d2d82: core/ndstrides: implement ndarray indexing.

Needed for implementing general ndarray indexing.

Currently IRRT slice and range have nothing to do with NAC3's slice
and range. The IRRT slice and range are currently there to implement
ndarray specific features. However, in the future their definitions may
be used to replace that of NAC3's. (NAC3's range is a [i32 x 3], IRRT's
range is a proper struct. NAC3 does not have a slice struct).
2024-12-13 15:23:31 +08:00
8b3429d62a [artiq] Reimplement get_obj_value for strided ndarray
Based on 7ef93472: artiq: reimplement get_obj_value to use ndarray with
strides
2024-12-13 15:23:31 +08:00
f4c5038b95 [artiq] codegen: Reimplement polymorphic_print for strided ndarray
Based on 2a6ee503: artiq: reimplement polymorphic_print for ndarray
2024-12-13 15:23:31 +08:00
ddd16738a6 [core] codegen: implement ndarray iterator NDIter
Based on 50f960ab: core/ndstrides: implement ndarray iterator NDIter

A necessary utility to iterate through all elements in a possibly
strided ndarray.
2024-12-13 15:23:31 +08:00
44c49dc102 [artiq] codegen: Reimplement polymorphic_print for strided ndarray
Based on 2a6ee503: artiq: reimplement polymorphic_print for ndarray
2024-12-13 15:23:31 +08:00
e4bd376587 [core] codegen: Implement ContiguousNDArray
Fixes compatibility with linalg algorithms. matrix_power is missing due
to the need for indexing support.
2024-12-13 15:23:29 +08:00
44498f22f6 [core] codegen: Implement NDArray functions from a0a1f35b 2024-12-13 15:22:11 +08:00
110416d07a [core] codegen/irrt: Add IRRT functions for strided-ndarray 2024-12-13 15:22:11 +08:00
08a7d01a13 [core] Add itemsize and strides to NDArray struct
Temporarily disable linalg ndarray tests as they are not ported to work
with strided-ndarray.
2024-12-13 15:22:09 +08:00
3cd36fddc3 [core] codegen/types: Add check_struct_type_matches_fields
Shorthand for checking if a type is representable by a StructFields
instance.
2024-12-12 11:40:44 +08:00
56a7a9e03d [core] codegen: Add helper functions for create+call functions
Replacement for various FnCall methods from legacy ndstrides
implementation.
2024-12-12 11:30:36 +08:00
574ae40f97 [core] codegen: Add call_memcpy_generic_array
Replacement for Instance<Ptr>::copy_from from legacy ndstrides
implementation.
2024-12-12 11:30:36 +08:00
aa293b6bea [core] codegen: Add type_aligned_alloca 2024-12-12 11:30:35 +08:00
eb4b881690 [core] Expose {types,values}::ndarray modules
Allows better encapsulation of members in these modules rather than
allowing them to leak into types/values mod.
2024-12-12 11:30:14 +08:00
3d0a1d281c [core] Expose irrt::ndarray 2024-12-10 12:49:49 +08:00
ad67a99c8f [core] codegen: Cleanup builtin_fns.rs
- Unpack tuples directly in function argument
- Replace Vec parameters with slices
- Replace unwrap-transform with map-unwrap
2024-12-10 12:49:49 +08:00
8e2b50df21 [core] codegen/ndarray: Cleanup
- Remove redundant size param
- Add *_fields functions and docs
2024-12-09 13:01:08 +08:00
06092ad29b [core] Move alloca and map_value of ProxyType to implementations
These functions may not be invokable by the same set of parameters as
some classes has associated states.
2024-12-09 12:51:50 +08:00
d62c6b95fd [core] codegen/types: Rename StructField::set_from_value 2024-12-09 12:51:50 +08:00
95e29d9997 [core] codegen: Move ndarray type/value as a separate module 2024-12-09 12:51:46 +08:00
536ed2146c [meta] Remove all mentions of build_int_cast
build_int_cast performs signed extension or truncation depending on the
source and target int lengths. This is usually not what we want - We
want zero-extension instead.

Replace all instances of build_int_cast with
build_int_z_extend_or_bit_cast to fix this issue.
2024-12-09 12:51:39 +08:00
d484d44d95 [standalone] linalg: Fix function name in error message 2024-12-09 12:09:57 +08:00
ac978864f2 [meta] Apply clippy suggestions 2024-12-09 12:08:41 +08:00
95254f8464 [meta] Update Cargo dependencies 2024-12-09 12:08:41 +08:00
964945d244 string_store: update embedding map after compilation 2024-12-03 16:45:46 +08:00
ae09a0d444 exceptions: preallocate in NAC3 instead 2024-12-03 16:45:05 +08:00
01edd5af67 [meta] Apply rustfmt changes 2024-11-29 15:43:34 +08:00
015714eee1 copy constructor -> clone 2024-11-28 18:52:53 +08:00
71dec251e3 ld/dwarf: remove reader resets
DWARF reader never had to reverse. Readers are already copied to achieve this effect.
Plus the position that it reverses to might be questionable.
2024-11-28 18:52:53 +08:00
fce61f7b8c ld: fix dwarf sections offset calculations 2024-11-28 18:52:53 +08:00
babc081dbd core/toplevel: update tests 2024-11-27 14:31:57 +08:00
5337dbe23b core/toplevel: add python-like error messages for class definition 2024-11-27 14:31:57 +08:00
f862c01412 core/toplevel: refactor composer 2024-11-27 14:31:53 +08:00
0c9705f5f1 [meta] Apply clippy changes 2024-11-25 16:05:12 +08:00
5f940f86d9 [artiq] Fix obtaining ndarray struct from NDArrayType 2024-11-25 15:01:39 +08:00
5651e00688 flake: add platformdirs artiq dependency 2024-11-22 20:30:30 +08:00
f6745b987f bump sipyco and artiq used for profiling 2024-11-22 19:43:03 +08:00
e0dedc6580 nac3artiq: support kernels sent by content 2024-11-22 19:38:52 +08:00
28f574282c [core_derive] Ignore doctest in example
Causes linker errors for unknown reasons.
2024-11-22 00:00:05 +08:00
144f0922db [core] coregen/types: Implement StructFields for NDArray
Also rename some fields to better align with their naming in numpy.
2024-11-21 14:27:00 +08:00
c58ce9c3a9 [core] codegen/types: Implement NDArray in terms of i8*
Better aligns with the future implementation of ndstrides.
2024-11-21 14:27:00 +08:00
f7e296da53 [core] irrt: Break IRRT into several impl files
Each IRRT file is now mapped to one Rust file.
2024-11-21 14:27:00 +08:00
b58c99369e [core] irrt: Update some IRRT implementation
- Change CSlice to use `void*` for better pointer compatibility
- Only include impl *.hpp files in irrt.cpp
- Refactor typedef to using declaration
- Add missing ``// namespace`
2024-11-21 14:26:58 +08:00
1a535db558 [core] codegen: Add dtype to NDArrayType
We won't have this once NDArray is refactored to strided impl.
2024-11-20 15:35:57 +08:00
1ba2e287a6 [core] codegen: Add Self::llvm_type to all type abstractions 2024-11-20 15:35:57 +08:00
f95f979ad3 core/irrt: fix exception.hpp C++ castings 2024-11-20 15:35:57 +08:00
48e2148c0f core/toplevel/helper: add {extract,create}_ndims 2024-11-20 15:35:57 +08:00
88e57f7120 [core_derive] Initial implementation 2024-11-20 15:35:55 +08:00
d7633c42bc [core] codegen/types: Implement StructField{,s}
Loosely based on FieldTraversal by lyken.
2024-11-19 13:46:25 +08:00
a4f53b6e6b [core] codegen: Refactor ProxyType and ProxyValue
Accepts generator+context object for generic type checking. Also
implements more default trait impl for easier delegation.
2024-11-19 13:46:25 +08:00
9d9ead211e [core] Move Proxies to their own modules 2024-11-19 13:46:23 +08:00
26a1b85206 [core] codegen/classes: Remove Underlying type
This is confusing and we want a better abstraction than this.
2024-11-19 13:45:55 +08:00
2822074b2d [meta] Cleanup from upgrading Rust version
- Remove rust_2024_edition warnings, since it wouldn't be released for
another 3 months
- Fix new clippy warnings
2024-11-19 13:43:57 +08:00
fe67ed076c [meta] Update pre-commit configuration 2024-11-19 13:20:27 +08:00
94e2414df0 [meta] Update cargo dependencies 2024-11-19 13:20:26 +08:00
2cee760404 turn rust_2024_compatibility lints into warnings 2024-11-16 13:41:49 +08:00
230982dc84 update dependencies 2024-11-16 12:40:11 +08:00
2bd3f63991 boolop: terminate both branches with *_end_bb 2024-11-16 12:06:20 +08:00
b53266e9e6 artiq: use async RPC for attributes writeback 2024-11-12 12:04:01 +08:00
86eb22bbf3 artiq: main is always the last module 2024-11-12 12:03:38 +08:00
beaa38047d artiq: suppress main module debug warning 2024-11-12 12:03:08 +08:00
705dc4ff1c artiq: lump return value into attributes writeback RPC 2024-11-12 12:02:35 +08:00
979209a526 binop: expand not operator as loglcal not 2024-11-08 17:12:01 +08:00
c3927d0ef6 [ast] Refactor lazy_static to LazyLock
It is available in Rust 1.80 and reduces a dependency.
2024-10-30 12:29:51 +08:00
202a902cd0 [meta] Update dependencies 2024-10-30 12:29:51 +08:00
147 changed files with 11449 additions and 8607 deletions

View File

@ -1,7 +1,7 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
default_stages: [commit]
default_stages: [pre-commit]
repos:
- repo: local

348
Cargo.lock generated
View File

@ -26,9 +26,9 @@ dependencies = [
[[package]]
name = "anstream"
version = "0.6.15"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [
"anstyle",
"anstyle-parse",
@ -41,36 +41,36 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.8"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
version = "0.2.5"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.1"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.4"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -126,9 +126,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
version = "1.1.30"
version = "1.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945"
checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7"
dependencies = [
"shlex",
]
@ -141,9 +141,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.20"
version = "4.5.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
dependencies = [
"clap_builder",
"clap_derive",
@ -151,9 +151,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.20"
version = "4.5.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
dependencies = [
"anstream",
"anstyle",
@ -170,38 +170,38 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.94",
]
[[package]]
name = "clap_lex"
version = "0.7.2"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "colorchoice"
version = "1.0.2"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "console"
version = "0.15.8"
version = "0.15.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b"
dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"windows-sys 0.52.0",
"once_cell",
"windows-sys 0.59.0",
]
[[package]]
name = "cpufeatures"
version = "0.2.14"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
dependencies = [
"libc",
]
@ -221,18 +221,18 @@ dependencies = [
[[package]]
name = "crossbeam-channel"
version = "0.5.13"
version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2"
checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.5"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
@ -249,18 +249,18 @@ dependencies = [
[[package]]
name = "crossbeam-queue"
version = "0.3.11"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35"
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.20"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crypto-common"
@ -282,6 +282,12 @@ dependencies = [
"crypto-common",
]
[[package]]
name = "dissimilar"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59f8e79d1fbf76bdfbde321e902714bf6c49df88a7dda6fc682fc2979226962d"
[[package]]
name = "either"
version = "1.13.0"
@ -299,9 +305,9 @@ dependencies = [
[[package]]
name = "encode_unicode"
version = "0.3.6"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "equivalent"
@ -311,19 +317,19 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.9"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
name = "fastrand"
version = "2.1.1"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fixedbitset"
@ -370,6 +376,12 @@ dependencies = [
"wasi",
]
[[package]]
name = "glob"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "hashbrown"
version = "0.12.3"
@ -387,9 +399,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.15.0"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "heck"
@ -405,11 +417,11 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "home"
version = "0.5.9"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
dependencies = [
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -424,12 +436,12 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.6.0"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
dependencies = [
"equivalent",
"hashbrown 0.15.0",
"hashbrown 0.15.2",
]
[[package]]
@ -460,7 +472,7 @@ checksum = "9dd28cfd4cfba665d47d31c08a6ba637eed16770abca2eccbbc3ca831fef1e44"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.94",
]
[[package]]
@ -494,9 +506,9 @@ dependencies = [
[[package]]
name = "itoa"
version = "1.0.11"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "keccak"
@ -547,15 +559,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.161"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "libloading"
version = "0.8.5"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [
"cfg-if",
"windows-targets",
@ -634,7 +646,6 @@ name = "nac3ast"
version = "0.1.0"
dependencies = [
"fxhash",
"lazy_static",
"parking_lot",
"string-interner",
]
@ -644,11 +655,12 @@ name = "nac3core"
version = "0.1.0"
dependencies = [
"crossbeam",
"indexmap 2.6.0",
"indexmap 2.7.0",
"indoc",
"inkwell",
"insta",
"itertools",
"nac3core_derive",
"nac3parser",
"parking_lot",
"rayon",
@ -658,6 +670,18 @@ dependencies = [
"test-case",
]
[[package]]
name = "nac3core_derive"
version = "0.1.0"
dependencies = [
"nac3core",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.94",
"trybuild",
]
[[package]]
name = "nac3ld"
version = "0.1.0"
@ -732,7 +756,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
dependencies = [
"fixedbitset",
"indexmap 2.6.0",
"indexmap 2.7.0",
]
[[package]]
@ -775,7 +799,7 @@ dependencies = [
"phf_shared 0.11.2",
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.94",
]
[[package]]
@ -804,9 +828,9 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
[[package]]
name = "portable-atomic"
version = "1.9.0"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2"
checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6"
[[package]]
name = "ppv-lite86"
@ -824,10 +848,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "proc-macro2"
version = "1.0.88"
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
@ -879,7 +927,7 @@ dependencies = [
"proc-macro2",
"pyo3-macros-backend",
"quote",
"syn 2.0.79",
"syn 2.0.94",
]
[[package]]
@ -892,14 +940,14 @@ dependencies = [
"proc-macro2",
"pyo3-build-config",
"quote",
"syn 2.0.79",
"syn 2.0.94",
]
[[package]]
name = "quote"
version = "1.0.37"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
@ -956,18 +1004,18 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.5.7"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.11.0"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
@ -977,9 +1025,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.8"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
@ -1001,22 +1049,22 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.37"
version = "0.38.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
name = "rustversion"
version = "1.0.18"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
[[package]]
name = "ryu"
@ -1041,35 +1089,35 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.23"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba"
[[package]]
name = "serde"
version = "1.0.210"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.210"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.94",
]
[[package]]
name = "serde_json"
version = "1.0.129"
version = "1.0.134"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbcf9b78a125ee667ae19388837dd12294b858d101fdd393cb9d5501ef09eb2"
checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d"
dependencies = [
"itoa",
"memchr",
@ -1077,6 +1125,15 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
dependencies = [
"serde",
]
[[package]]
name = "serde_yaml"
version = "0.8.26"
@ -1169,7 +1226,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.79",
"syn 2.0.94",
]
[[package]]
@ -1185,9 +1242,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.79"
version = "2.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3"
dependencies = [
"proc-macro2",
"quote",
@ -1201,13 +1258,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tempfile"
version = "3.13.0"
name = "target-triple"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b"
checksum = "42a4d50cdb458045afc8131fd91b64904da29548bcb63c7236e0844936c13078"
[[package]]
name = "tempfile"
version = "3.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
dependencies = [
"cfg-if",
"fastrand",
"getrandom",
"once_cell",
"rustix",
"windows-sys 0.59.0",
@ -1215,14 +1279,23 @@ dependencies = [
[[package]]
name = "term"
version = "1.0.0"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4df4175de05129f31b80458c6df371a15e7fc3fd367272e6bf938e5c351c7ea0"
checksum = "a3bb6001afcea98122260987f8b7b5da969ecad46dbf0b5453702f776b491a41"
dependencies = [
"home",
"windows-sys 0.52.0",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "test-case"
version = "1.2.3"
@ -1238,22 +1311,72 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.64"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.64"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.94",
]
[[package]]
name = "toml"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
dependencies = [
"indexmap 2.7.0",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]]
name = "trybuild"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dcd332a5496c026f1e14b7f3d2b7bd98e509660c04239c58b0ba38a12daded4"
dependencies = [
"dissimilar",
"glob",
"serde",
"serde_derive",
"serde_json",
"target-triple",
"termcolor",
"toml",
]
[[package]]
@ -1316,9 +1439,9 @@ dependencies = [
[[package]]
name = "unicode-ident"
version = "1.0.13"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "unicode-width"
@ -1479,6 +1602,15 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.6.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980"
dependencies = [
"memchr",
]
[[package]]
name = "yaml-rust"
version = "0.4.5"
@ -1506,5 +1638,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.79",
"syn 2.0.94",
]

View File

@ -4,6 +4,7 @@ members = [
"nac3ast",
"nac3parser",
"nac3core",
"nac3core/nac3core_derive",
"nac3standalone",
"nac3artiq",
"runkernel",

6
flake.lock generated
View File

@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1727348695,
"narHash": "sha256-J+PeFKSDV+pHL7ukkfpVzCOO7mBSrrpJ3svwBFABbhI=",
"lastModified": 1735834308,
"narHash": "sha256-dklw3AXr3OGO4/XT1Tu3Xz9n/we8GctZZ75ZWVqAVhk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "1925c603f17fc89f4c8f6bf6f631a802ad85d784",
"rev": "6df24922a1400241dae323af55f30e4318a6ca65",
"type": "github"
},
"original": {

View File

@ -107,18 +107,18 @@
(pkgs.fetchFromGitHub {
owner = "m-labs";
repo = "sipyco";
rev = "939f84f9b5eef7efbf7423c735d1834783b6140e";
sha256 = "sha256-15Nun4EY35j+6SPZkjzZtyH/ncxLS60KuGJjFh5kSTc=";
rev = "094a6cd63ffa980ef63698920170e50dc9ba77fd";
sha256 = "sha256-PPnAyDedUQ7Og/Cby9x5OT9wMkNGTP8GS53V6N/dk4w=";
})
(pkgs.fetchFromGitHub {
owner = "m-labs";
repo = "artiq";
rev = "923ca3377d42c815f979983134ec549dc39d3ca0";
sha256 = "sha256-oJoEeNEeNFSUyh6jXG8Tzp6qHVikeHS0CzfE+mODPgw=";
rev = "28c9de3e251daa89a8c9fd79d5ab64a3ec03bac6";
sha256 = "sha256-vAvpbHc5B+1wtG8zqN7j9dQE1ON+i22v+uqA+tw6Gak=";
})
];
buildInputs = [
(python3-mimalloc.withPackages(ps: [ ps.numpy ps.scipy ps.jsonschema ps.lmdb nac3artiq-instrumented ]))
(python3-mimalloc.withPackages(ps: [ ps.numpy ps.scipy ps.jsonschema ps.lmdb ps.platformdirs nac3artiq-instrumented ]))
pkgs.llvmPackages_14.llvm.out
];
phases = [ "buildPhase" "installPhase" ];

View File

@ -7,33 +7,6 @@ class EmbeddingMap:
self.function_map = {}
self.attributes_writeback = []
# preallocate exception names
self.preallocate_runtime_exception_names(["RuntimeError",
"RTIOUnderflow",
"RTIOOverflow",
"RTIODestinationUnreachable",
"DMAError",
"I2CError",
"CacheError",
"SPIError",
"0:ZeroDivisionError",
"0:IndexError",
"0:ValueError",
"0:RuntimeError",
"0:AssertionError",
"0:KeyError",
"0:NotImplementedError",
"0:OverflowError",
"0:IOError",
"0:UnwrapNoneError"])
def preallocate_runtime_exception_names(self, names):
for i, name in enumerate(names):
if ":" not in name:
name = "0:artiq.coredevice.exceptions." + name
exn_id = self.store_str(name)
assert exn_id == i
def store_function(self, key, fun):
self.function_map[key] = fun
return key

View File

@ -206,7 +206,7 @@ class Core:
embedding = EmbeddingMap()
if allow_registration:
compiler.analyze(registered_functions, registered_classes)
compiler.analyze(registered_functions, registered_classes, set())
allow_registration = False
if hasattr(method, "__self__"):

View File

@ -12,21 +12,25 @@ use pyo3::{
PyObject, PyResult, Python,
};
use super::{symbol_resolver::InnerResolver, timeline::TimeFns};
use nac3core::{
codegen::{
classes::{ListValue, RangeValue, UntypedArrayLikeAccessor},
expr::{destructure_range, gen_call},
llvm_intrinsics::{call_int_smax, call_stackrestore, call_stacksave},
model::*,
object::{any::AnyObject, ndarray::NDArrayObject},
llvm_intrinsics::{call_int_smax, call_memcpy, call_stackrestore, call_stacksave},
stmt::{gen_block, gen_for_callback_incrementing, gen_if_callback, gen_with},
type_aligned_alloca,
types::ndarray::NDArrayType,
values::{
ArrayLikeIndexer, ArrayLikeValue, ArraySliceValue, ListValue, ProxyValue, RangeValue,
UntypedArrayLikeAccessor,
},
CodeGenContext, CodeGenerator,
},
inkwell::{
context::Context,
module::Linkage,
types::IntType,
values::{BasicValue, BasicValueEnum, IntValue, PointerValue, StructValue},
types::{BasicType, IntType},
values::{BasicValueEnum, IntValue, PointerValue, StructValue},
AddressSpace, IntPredicate, OptimizationLevel,
},
nac3parser::ast::{Expr, ExprKind, Located, Stmt, StmtKind, StrRef},
@ -39,8 +43,6 @@ use nac3core::{
typecheck::typedef::{iter_type_vars, FunSignature, FuncArg, Type, TypeEnum, VarMap},
};
use super::{symbol_resolver::InnerResolver, timeline::TimeFns};
/// The parallelism mode within a block.
#[derive(Copy, Clone, Eq, PartialEq)]
enum ParallelMode {
@ -456,42 +458,52 @@ fn format_rpc_arg<'ctx>(
// NAC3: NDArray = { usize, usize*, T* }
// libproto_artiq: NDArray = [data[..], dim_sz[..]]
let ndarray = AnyObject { ty: arg_ty, value: arg };
let ndarray = NDArrayObject::from_object(generator, ctx, ndarray);
let llvm_i1 = ctx.ctx.bool_type();
let llvm_usize = generator.get_size_type(ctx.ctx);
let dtype = ctx.get_llvm_type(generator, ndarray.dtype);
let ndims = ndarray.ndims_llvm(generator, ctx.ctx);
let (elem_ty, ndims) = unpack_ndarray_var_tys(&mut ctx.unifier, arg_ty);
let ndims = extract_ndims(&ctx.unifier, ndims);
let dtype = ctx.get_llvm_type(generator, elem_ty);
let ndarray = NDArrayType::new(generator, ctx.ctx, dtype, ndims)
.map_value(arg.into_pointer_value(), None);
let ndims = llvm_usize.const_int(ndims, false);
// `ndarray.data` is possibly not contiguous, and we need it to be contiguous for
// the reader.
// Turning it into a ContiguousNDArray to get a `data` that is contiguous.
let carray = ndarray.make_contiguous_ndarray(generator, ctx, Any(dtype));
let carray = ndarray.make_contiguous_ndarray(generator, ctx);
let sizeof_sizet = Int(SizeT).size_of(generator, ctx.ctx);
let sizeof_sizet = Int(SizeT).truncate_or_bit_cast(generator, ctx, sizeof_sizet);
let sizeof_usize = llvm_usize.size_of();
let sizeof_usize =
ctx.builder.build_int_z_extend_or_bit_cast(sizeof_usize, llvm_usize, "").unwrap();
let sizeof_pdata = Ptr(Any(dtype)).size_of(generator, ctx.ctx);
let sizeof_pdata = Int(SizeT).truncate_or_bit_cast(generator, ctx, sizeof_pdata);
let sizeof_pdata = dtype.ptr_type(AddressSpace::default()).size_of();
let sizeof_pdata =
ctx.builder.build_int_z_extend_or_bit_cast(sizeof_pdata, llvm_usize, "").unwrap();
let sizeof_buf_shape = sizeof_sizet.mul(ctx, ndims);
let sizeof_buf = sizeof_buf_shape.add(ctx, sizeof_pdata);
let sizeof_buf_shape = ctx.builder.build_int_mul(sizeof_usize, ndims, "").unwrap();
let sizeof_buf = ctx.builder.build_int_add(sizeof_buf_shape, sizeof_pdata, "").unwrap();
// buf = { data: void*, shape: [size_t; ndims]; }
let buf = Int(Byte).array_alloca(generator, ctx, sizeof_buf.value);
let buf_data = buf;
let buf_shape = buf_data.offset(ctx, sizeof_pdata.value);
let buf = ctx.builder.build_array_alloca(llvm_i8, sizeof_buf, "rpc.arg").unwrap();
let buf = ArraySliceValue::from_ptr_val(buf, sizeof_buf, Some("rpc.arg"));
let buf_data = buf.base_ptr(ctx, generator);
let buf_shape =
unsafe { buf.ptr_offset_unchecked(ctx, generator, &sizeof_pdata, None) };
// Write to `buf->data`
let carray_data = carray.get(generator, ctx, |f| f.data); // has type Ptr<Any>
let carray_data = carray_data.pointer_cast(generator, ctx, Int(Byte));
buf_data.copy_from(generator, ctx, carray_data, sizeof_pdata.value);
let carray_data = carray.load_data(ctx);
let carray_data = ctx.builder.build_pointer_cast(carray_data, llvm_pi8, "").unwrap();
call_memcpy(ctx, buf_data, carray_data, sizeof_pdata, llvm_i1.const_zero());
// Write to `buf->shape`
let carray_shape = ndarray.instance.get(generator, ctx, |f| f.shape);
let carray_shape_i8 = carray_shape.pointer_cast(generator, ctx, Int(Byte));
buf_shape.copy_from(generator, ctx, carray_shape_i8, sizeof_buf_shape.value);
let carray_shape = ndarray.shape().base_ptr(ctx, generator);
let carray_shape_i8 =
ctx.builder.build_pointer_cast(carray_shape, llvm_pi8, "").unwrap();
call_memcpy(ctx, buf_shape, carray_shape_i8, sizeof_buf_shape, llvm_i1.const_zero());
buf.value
buf.base_ptr(ctx, generator)
}
_ => {
@ -532,6 +544,8 @@ fn format_rpc_ret<'ctx>(
let llvm_i32 = ctx.ctx.i32_type();
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_usize = generator.get_size_type(ctx.ctx);
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
let rpc_recv = ctx.module.get_function("rpc_recv").unwrap_or_else(|| {
ctx.module.add_function("rpc_recv", llvm_i32.fn_type(&[llvm_pi8.into()], false), None)
@ -552,10 +566,7 @@ fn format_rpc_ret<'ctx>(
let result = match &*ctx.unifier.get_ty_immutable(ret_ty) {
TypeEnum::TObj { obj_id, .. } if *obj_id == PrimDef::NDArray.id() => {
// FIXME: It is possible to rewrite everything more neatly with `Model<'ctx>`, but this is not too important.
let num_0 = Int(SizeT).const_0(generator, ctx.ctx);
let num_8 = Int(SizeT).const_int(generator, ctx.ctx, 8, false);
let num_0 = llvm_usize.const_zero();
// Round `val` up to its modulo `power_of_two`
let round_up = |ctx: &mut CodeGenContext<'ctx, '_>,
@ -584,8 +595,10 @@ fn format_rpc_ret<'ctx>(
// Allocate the resulting ndarray
// A condition after format_rpc_ret ensures this will not be popped this off.
let (dtype, ndims) = unpack_ndarray_var_tys(&mut ctx.unifier, ret_ty);
let dtype_llvm = ctx.get_llvm_type(generator, dtype);
let ndims = extract_ndims(&ctx.unifier, ndims);
let ndarray = NDArrayObject::alloca(generator, ctx, dtype, ndims);
let ndarray = NDArrayType::new(generator, ctx.ctx, dtype_llvm, ndims)
.construct_uninitialized(generator, ctx, None);
// NOTE: Current content of `ndarray`:
// - * `data` - **NOT YET** allocated.
@ -594,40 +607,34 @@ fn format_rpc_ret<'ctx>(
// - * `shape` - allocated; has uninitialized values.
// - * `strides` - allocated; has uninitialized values.
let itemsize = ndarray.instance.get(generator, ctx, |f| f.itemsize); // Same as doing a `ctx.get_llvm_type` on `dtype` and get its `size_of()`.
let dtype_llvm = ctx.get_llvm_type(generator, dtype);
let itemsize = ndarray.load_itemsize(ctx); // Same as doing a `ctx.get_llvm_type` on `dtype` and get its `size_of()`.
// Allocates a buffer for the initial RPC'ed object, which is guaranteed to be
// (4 + 4 * ndims) bytes with 8-byte alignment
let sizeof_size_t = Int(SizeT).size_of(generator, ctx.ctx);
let sizeof_size_t = Int(SizeT).z_extend_or_truncate(generator, ctx, sizeof_size_t); // sizeof(size_t)
let sizeof_usize = llvm_usize.size_of();
let sizeof_usize =
ctx.builder.build_int_truncate_or_bit_cast(sizeof_usize, llvm_usize, "").unwrap();
let sizeof_ptr = Ptr(Int(Byte)).size_of(generator, ctx.ctx);
let sizeof_ptr = Int(SizeT).z_extend_or_truncate(generator, ctx, sizeof_ptr); // sizeof(uint8_t*)
let sizeof_ptr = llvm_i8.ptr_type(AddressSpace::default()).size_of();
let sizeof_ptr =
ctx.builder.build_int_z_extend_or_bit_cast(sizeof_ptr, llvm_usize, "").unwrap();
let sizeof_shape = ndarray.ndims_llvm(generator, ctx.ctx).mul(ctx, sizeof_size_t); // sizeof([size_t; ndims]); same as the # of bytes of `ndarray.shape`.
let sizeof_shape =
ctx.builder.build_int_mul(ndarray.load_ndims(ctx), sizeof_usize, "").unwrap();
// Size of the buffer for the initial `rpc_recv()`.
let unaligned_buffer_size = sizeof_ptr.add(ctx, sizeof_shape); // sizeof(uint8_t*) + sizeof([size_t; ndims]).
let buffer_size = round_up(ctx, unaligned_buffer_size.value, num_8.value);
let buffer_size = unsafe { Int(SizeT).believe_value(buffer_size) };
let unaligned_buffer_size =
ctx.builder.build_int_add(sizeof_ptr, sizeof_shape, "").unwrap();
let stackptr = call_stacksave(ctx, None);
// Just to be absolutely sure, alloca in [i8 x 8] slices to force 8-byte alignment
let buffer = ctx
.builder
.build_array_alloca(
llvm_i8_8,
ctx.builder.build_int_unsigned_div(buffer_size.value, num_8.value, "").unwrap(),
"rpc.buffer",
)
.unwrap();
let buffer = ctx
.builder
.build_bit_cast(buffer, llvm_pi8, "")
.map(BasicValueEnum::into_pointer_value)
.unwrap();
let buffer = unsafe { Ptr(Int(Byte)).believe_value(buffer) };
let buffer = type_aligned_alloca(
generator,
ctx,
llvm_i8_8,
unaligned_buffer_size,
Some("rpc.buffer"),
);
let buffer = ArraySliceValue::from_ptr_val(buffer, unaligned_buffer_size, None);
// The first call to `rpc_recv` reads the top-level ndarray object: [pdata, shape]
//
@ -635,20 +642,22 @@ fn format_rpc_ret<'ctx>(
let ndarray_nbytes = ctx
.build_call_or_invoke(
rpc_recv,
&[buffer.value.into()], // Reads [usize; ndims]
&[buffer.base_ptr(ctx, generator).into()], // Reads [usize; ndims]
"rpc.size.next",
)
.map(BasicValueEnum::into_int_value)
.unwrap();
let ndarray_nbytes = unsafe { Int(SizeT).believe_value(ndarray_nbytes) };
// debug_assert(ndarray_nbytes > 0)
if ctx.registry.llvm_options.opt_level == OptimizationLevel::None {
let cmp = ndarray_nbytes.compare(ctx, IntPredicate::UGT, num_0);
let cmp = ctx
.builder
.build_int_compare(IntPredicate::UGT, ndarray_nbytes, num_0, "")
.unwrap();
ctx.make_assert(
generator,
cmp.value,
cmp,
"0:AssertionError",
"Unexpected RPC termination for ndarray - Expected data buffer next",
[None, None, None],
@ -658,8 +667,10 @@ fn format_rpc_ret<'ctx>(
// Copy shape from the buffer to `ndarray.shape`.
// We need to skip the first `sizeof(uint8_t*)` bytes to skip the `pdata` in `[pdata, shape]`.
let pbuffer_shape = buffer.offset(ctx, sizeof_ptr.value);
let pbuffer_shape = pbuffer_shape.pointer_cast(generator, ctx, Int(SizeT));
let pbuffer_shape =
unsafe { buffer.ptr_offset_unchecked(ctx, generator, &sizeof_ptr, None) };
let pbuffer_shape =
ctx.builder.build_pointer_cast(pbuffer_shape, llvm_pusize, "").unwrap();
// Copy shape from buffer to `ndarray.shape`
ndarray.copy_shape_from_array(generator, ctx, pbuffer_shape);
@ -670,26 +681,35 @@ fn format_rpc_ret<'ctx>(
// Allocate `ndarray.data`.
// `ndarray.shape` must be initialized beforehand in this implementation
// (for ndarray.create_data() to know how many elements to allocate)
ndarray.create_data(generator, ctx); // NOTE: the strides of `ndarray` has also been set to contiguous in `::create_data()`.
unsafe { ndarray.create_data(generator, ctx) }; // NOTE: the strides of `ndarray` has also been set to contiguous in `create_data`.
// debug_assert(nelems * sizeof(T) >= ndarray_nbytes)
if ctx.registry.llvm_options.opt_level == OptimizationLevel::None {
let num_elements = ndarray.size(generator, ctx);
let expected_ndarray_nbytes = num_elements.mul(ctx, itemsize);
let cmp = expected_ndarray_nbytes.compare(ctx, IntPredicate::UGE, ndarray_nbytes);
let expected_ndarray_nbytes =
ctx.builder.build_int_mul(num_elements, itemsize, "").unwrap();
let cmp = ctx
.builder
.build_int_compare(
IntPredicate::UGE,
expected_ndarray_nbytes,
ndarray_nbytes,
"",
)
.unwrap();
ctx.make_assert(
generator,
cmp.value,
cmp,
"0:AssertionError",
"Unexpected allocation size request for ndarray data - Expected up to {0} bytes, got {1} bytes",
[Some(expected_ndarray_nbytes.value), Some(ndarray_nbytes.value), None],
[Some(expected_ndarray_nbytes), Some(ndarray_nbytes), None],
ctx.current_loc,
);
}
let ndarray_data = ndarray.instance.get(generator, ctx, |f| f.data);
let ndarray_data = ndarray.data().base_ptr(ctx, generator);
// NOTE: Currently on `prehead_bb`
ctx.builder.build_unconditional_branch(head_bb).unwrap();
@ -698,7 +718,7 @@ fn format_rpc_ret<'ctx>(
ctx.builder.position_at_end(head_bb);
let phi = ctx.builder.build_phi(llvm_pi8, "rpc.ptr").unwrap();
phi.add_incoming(&[(&ndarray_data.value, prehead_bb)]);
phi.add_incoming(&[(&ndarray_data, prehead_bb)]);
let alloc_size = ctx
.build_call_or_invoke(rpc_recv, &[phi.as_basic_value()], "rpc.size.next")
@ -713,12 +733,13 @@ fn format_rpc_ret<'ctx>(
ctx.builder.position_at_end(alloc_bb);
// Align the allocation to sizeof(T)
let alloc_size = round_up(ctx, alloc_size, itemsize.value);
let alloc_size = round_up(ctx, alloc_size, itemsize);
// TODO(Derppening): Candidate for refactor into type_aligned_alloca
let alloc_ptr = ctx
.builder
.build_array_alloca(
dtype_llvm,
ctx.builder.build_int_unsigned_div(alloc_size, itemsize.value, "").unwrap(),
ctx.builder.build_int_unsigned_div(alloc_size, itemsize, "").unwrap(),
"rpc.alloc",
)
.unwrap();
@ -728,7 +749,7 @@ fn format_rpc_ret<'ctx>(
ctx.builder.build_unconditional_branch(head_bb).unwrap();
ctx.builder.position_at_end(tail_bb);
ndarray.instance.value.as_basic_value_enum()
ndarray.as_base_value().into()
}
_ => {
@ -941,11 +962,12 @@ fn rpc_codegen_callback_fn<'ctx>(
}
}
pub fn attributes_writeback(
ctx: &mut CodeGenContext<'_, '_>,
pub fn attributes_writeback<'ctx>(
ctx: &mut CodeGenContext<'ctx, '_>,
generator: &mut dyn CodeGenerator,
inner_resolver: &InnerResolver,
host_attributes: &PyObject,
return_obj: Option<(Type, ValueEnum<'ctx>)>,
) -> Result<(), String> {
Python::with_gil(|py| -> PyResult<Result<(), String>> {
let host_attributes: &PyList = host_attributes.downcast(py)?;
@ -955,6 +977,11 @@ pub fn attributes_writeback(
let zero = int32.const_zero();
let mut values = Vec::new();
let mut scratch_buffer = Vec::new();
if let Some((ty, obj)) = return_obj {
values.push((ty, obj.to_basic_value_enum(ctx, generator, ty).unwrap()));
}
for val in (*globals).values() {
let val = val.as_ref(py);
let ty = inner_resolver.get_obj_type(
@ -1033,7 +1060,7 @@ pub fn attributes_writeback(
let args: Vec<_> =
values.into_iter().map(|(_, val)| (None, ValueEnum::Dynamic(val))).collect();
if let Err(e) =
rpc_codegen_callback_fn(ctx, None, (&fun, PrimDef::Int32.id()), args, generator, false)
rpc_codegen_callback_fn(ctx, None, (&fun, PrimDef::Int32.id()), args, generator, true)
{
return Ok(Err(e));
}
@ -1259,7 +1286,8 @@ fn polymorphic_print<'ctx>(
fmt.push('[');
flush(ctx, generator, &mut fmt, &mut args);
let val = ListValue::from_ptr_val(value.into_pointer_value(), llvm_usize, None);
let val =
ListValue::from_pointer_value(value.into_pointer_value(), llvm_usize, None);
let len = val.load_size(ctx, None);
let last =
ctx.builder.build_int_sub(len, llvm_usize.const_int(1, false), "").unwrap();
@ -1313,23 +1341,27 @@ fn polymorphic_print<'ctx>(
fmt.push_str("array([");
flush(ctx, generator, &mut fmt, &mut args);
let ndarray = AnyObject { ty, value };
let ndarray = NDArrayObject::from_object(generator, ctx, ndarray);
let (dtype, _) = unpack_ndarray_var_tys(&mut ctx.unifier, ty);
let ndarray = NDArrayType::from_unifier_type(generator, ctx, ty)
.map_value(value.into_pointer_value(), None);
let num_0 = Int(SizeT).const_0(generator, ctx.ctx);
let num_0 = llvm_usize.const_zero();
// Print `ndarray` as a flat list delimited by interspersed with ", \0"
ndarray.foreach(generator, ctx, |generator, ctx, _, hdl| {
let i = hdl.get_index(generator, ctx);
let scalar = hdl.get_scalar(generator, ctx);
let i = hdl.get_index(ctx);
let scalar = hdl.get_scalar(ctx);
// if (i != 0) { puts(", "); }
// if (i != 0) puts(", ");
gen_if_callback(
generator,
ctx,
|_, ctx| {
let not_first = i.compare(ctx, IntPredicate::NE, num_0);
Ok(not_first.value)
let not_first = ctx
.builder
.build_int_compare(IntPredicate::NE, i, num_0, "")
.unwrap();
Ok(not_first)
},
|generator, ctx| {
printf(ctx, generator, ", \0".into(), Vec::default());
@ -1342,7 +1374,7 @@ fn polymorphic_print<'ctx>(
polymorphic_print(
ctx,
generator,
&[(scalar.ty, scalar.value.into())],
&[(dtype, scalar.into())],
"",
None,
true,
@ -1359,7 +1391,7 @@ fn polymorphic_print<'ctx>(
fmt.push_str("range(");
flush(ctx, generator, &mut fmt, &mut args);
let val = RangeValue::from_ptr_val(value.into_pointer_value(), None);
let val = RangeValue::from_pointer_value(value.into_pointer_value(), None);
let (start, stop, step) = destructure_range(ctx, val);

View File

@ -1,10 +1,4 @@
#![deny(
future_incompatible,
let_underscore,
nonstandard_style,
rust_2024_compatibility,
clippy::all
)]
#![deny(future_incompatible, let_underscore, nonstandard_style, clippy::all)]
#![warn(clippy::pedantic)]
#![allow(
unsafe_op_in_unsafe_fn,
@ -30,19 +24,19 @@ use parking_lot::{Mutex, RwLock};
use pyo3::{
create_exception, exceptions,
prelude::*,
types::{PyBytes, PyDict, PySet},
types::{PyBytes, PyDict, PyNone, PySet},
};
use tempfile::{self, TempDir};
use nac3core::{
codegen::{
concrete_type::ConcreteTypeStore, gen_func_impl, irrt::load_irrt, CodeGenLLVMOptions,
CodeGenTargetMachineOptions, CodeGenTask, WithCall, WorkerRegistry,
CodeGenTargetMachineOptions, CodeGenTask, CodeGenerator, WithCall, WorkerRegistry,
},
inkwell::{
context::Context,
memory_buffer::MemoryBuffer,
module::{Linkage, Module},
module::{FlagBehavior, Linkage, Module},
passes::PassBuilderOptions,
support::is_multithreaded,
targets::*,
@ -148,14 +142,32 @@ impl Nac3 {
module: &PyObject,
registered_class_ids: &HashSet<u64>,
) -> PyResult<()> {
let (module_name, source_file) = Python::with_gil(|py| -> PyResult<(String, String)> {
let module: &PyAny = module.extract(py)?;
Ok((module.getattr("__name__")?.extract()?, module.getattr("__file__")?.extract()?))
})?;
let (module_name, source_file, source) =
Python::with_gil(|py| -> PyResult<(String, String, String)> {
let module: &PyAny = module.extract(py)?;
let source_file = module.getattr("__file__");
let (source_file, source) = if let Ok(source_file) = source_file {
let source_file = source_file.extract()?;
(
source_file,
fs::read_to_string(source_file).map_err(|e| {
exceptions::PyIOError::new_err(format!(
"failed to read input file: {e}"
))
})?,
)
} else {
// kernels submitted by content have no file
// but still can provide source by StringLoader
let get_src_fn = module
.getattr("__loader__")?
.extract::<PyObject>()?
.getattr(py, "get_source")?;
("<expcontent>", get_src_fn.call1(py, (PyNone::get(py),))?.extract(py)?)
};
Ok((module.getattr("__name__")?.extract()?, source_file.to_string(), source))
})?;
let source = fs::read_to_string(&source_file).map_err(|e| {
exceptions::PyIOError::new_err(format!("failed to read input file: {e}"))
})?;
let parser_result = parse_program(&source, source_file.into())
.map_err(|e| exceptions::PySyntaxError::new_err(format!("parse error: {e}")))?;
@ -565,7 +577,7 @@ impl Nac3 {
field_to_val: RwLock::default(),
name_to_pyid,
module: module.to_object(py),
helper,
helper: helper.clone(),
string_store: self.string_store.clone(),
exception_ids: self.exception_ids.clone(),
deferred_eval_store: self.deferred_eval_store.clone(),
@ -673,33 +685,12 @@ impl Nac3 {
let task = CodeGenTask {
subst: Vec::default(),
symbol_name: "__modinit__".to_string(),
body: instance.body,
signature,
resolver: resolver.clone(),
store,
unifier_index: instance.unifier_id,
calls: instance.calls,
id: 0,
};
let mut store = ConcreteTypeStore::new();
let mut cache = HashMap::new();
let signature = store.from_signature(
&mut composer.unifier,
&self.primitive,
&fun_signature,
&mut cache,
);
let signature = store.add_cty(signature);
let attributes_writeback_task = CodeGenTask {
subst: Vec::default(),
symbol_name: "attributes_writeback".to_string(),
body: Arc::new(Vec::default()),
signature,
resolver,
store,
unifier_index: instance.unifier_id,
calls: Arc::new(HashMap::default()),
calls: instance.calls,
id: 0,
};
@ -723,19 +714,27 @@ impl Nac3 {
.collect();
let membuffer = membuffers.clone();
let mut has_return = false;
py.allow_threads(|| {
let (registry, handles) =
WorkerRegistry::create_workers(threads, top_level.clone(), &self.llvm_options, &f);
registry.add_task(task);
registry.wait_tasks_complete(handles);
let mut generator =
ArtiqCodeGenerator::new("attributes_writeback".to_string(), size_t, self.time_fns);
let mut generator = ArtiqCodeGenerator::new("main".to_string(), size_t, self.time_fns);
let context = Context::create();
let module = context.create_module("attributes_writeback");
let module = context.create_module("main");
let target_machine = self.llvm_options.create_target_machine().unwrap();
module.set_data_layout(&target_machine.get_target_data().get_data_layout());
module.set_triple(&target_machine.get_triple());
module.add_basic_value_flag(
"Debug Info Version",
FlagBehavior::Warning,
context.i32_type().const_int(3, false),
);
module.add_basic_value_flag(
"Dwarf Version",
FlagBehavior::Warning,
context.i32_type().const_int(4, false),
);
let builder = context.create_builder();
let (_, module, _) = gen_func_impl(
&context,
@ -743,9 +742,27 @@ impl Nac3 {
&registry,
builder,
module,
attributes_writeback_task,
task,
|generator, ctx| {
attributes_writeback(ctx, generator, inner_resolver.as_ref(), &host_attributes)
assert_eq!(instance.body.len(), 1, "toplevel module should have 1 statement");
let StmtKind::Expr { value: ref expr, .. } = instance.body[0].node else {
unreachable!("toplevel statement must be an expression")
};
let ExprKind::Call { .. } = expr.node else {
unreachable!("toplevel expression must be a function call")
};
let return_obj =
generator.gen_expr(ctx, expr)?.map(|value| (expr.custom.unwrap(), value));
has_return = return_obj.is_some();
registry.wait_tasks_complete(handles);
attributes_writeback(
ctx,
generator,
inner_resolver.as_ref(),
&host_attributes,
return_obj,
)
},
)
.unwrap();
@ -754,35 +771,23 @@ impl Nac3 {
membuffer.lock().push(buffer);
});
embedding_map.setattr("expects_return", has_return).unwrap();
// Link all modules into `main`.
let buffers = membuffers.lock();
let main = context
.create_module_from_ir(MemoryBuffer::create_from_memory_range(&buffers[0], "main"))
.create_module_from_ir(MemoryBuffer::create_from_memory_range(
buffers.last().unwrap(),
"main",
))
.unwrap();
for buffer in buffers.iter().skip(1) {
for buffer in buffers.iter().rev().skip(1) {
let other = context
.create_module_from_ir(MemoryBuffer::create_from_memory_range(buffer, "main"))
.unwrap();
main.link_in_module(other).map_err(|err| CompileError::new_err(err.to_string()))?;
}
let builder = context.create_builder();
let modinit_return = main
.get_function("__modinit__")
.unwrap()
.get_last_basic_block()
.unwrap()
.get_terminator()
.unwrap();
builder.position_before(&modinit_return);
builder
.build_call(
main.get_function("attributes_writeback").unwrap(),
&[],
"attributes_writeback",
)
.unwrap();
main.link_in_module(irrt).map_err(|err| CompileError::new_err(err.to_string()))?;
let mut function_iter = main.get_first_function();
@ -817,6 +822,20 @@ impl Nac3 {
panic!("Failed to run optimization for module `main`: {}", err.to_string());
}
Python::with_gil(|py| {
let string_store = self.string_store.read();
let mut string_store_vec = string_store.iter().collect::<Vec<_>>();
string_store_vec.sort_by(|(_s1, key1), (_s2, key2)| key1.cmp(key2));
for (s, key) in string_store_vec {
let embed_key: i32 = helper.store_str.call1(py, (s,)).unwrap().extract(py).unwrap();
assert_eq!(
embed_key, *key,
"string {s} is out of sync between embedding map (key={embed_key}) and \
the internal string store (key={key})"
);
}
});
link_fn(&main)
}
@ -1066,6 +1085,48 @@ impl Nac3 {
let working_directory = tempfile::Builder::new().prefix("nac3-").tempdir().unwrap();
fs::write(working_directory.path().join("kernel.ld"), include_bytes!("kernel.ld")).unwrap();
let mut string_store: HashMap<String, i32> = HashMap::default();
// Keep this list of exceptions in sync with `EXCEPTION_ID_LOOKUP` in `artiq::firmware::ksupport::eh_artiq`
// The exceptions declared here must be defined in `artiq.coredevice.exceptions`
// Verify synchronization by running the test cases in `artiq.test.coredevice.test_exceptions`
let runtime_exception_names = [
"RTIOUnderflow",
"RTIOOverflow",
"RTIODestinationUnreachable",
"DMAError",
"I2CError",
"CacheError",
"SPIError",
"SubkernelError",
"0:AssertionError",
"0:AttributeError",
"0:IndexError",
"0:IOError",
"0:KeyError",
"0:NotImplementedError",
"0:OverflowError",
"0:RuntimeError",
"0:TimeoutError",
"0:TypeError",
"0:ValueError",
"0:ZeroDivisionError",
"0:LinAlgError",
"UnwrapNoneError",
];
// Preallocate runtime exception names
for (i, name) in runtime_exception_names.iter().enumerate() {
let exn_name = if name.find(':').is_none() {
format!("0:artiq.coredevice.exceptions.{name}")
} else {
(*name).to_string()
};
let id = i32::try_from(i).unwrap();
string_store.insert(exn_name, id);
}
Ok(Nac3 {
isa,
time_fns,
@ -1075,7 +1136,7 @@ impl Nac3 {
top_levels: Vec::default(),
pyid_to_def: Arc::default(),
working_directory,
string_store: Arc::default(),
string_store: Arc::new(string_store.into()),
exception_ids: Arc::default(),
deferred_eval_store: DeferredEvaluationStore::new(),
llvm_options: CodeGenLLVMOptions {
@ -1085,7 +1146,12 @@ impl Nac3 {
})
}
fn analyze(&mut self, functions: &PySet, classes: &PySet) -> PyResult<()> {
fn analyze(
&mut self,
functions: &PySet,
classes: &PySet,
content_modules: &PySet,
) -> PyResult<()> {
let (modules, class_ids) =
Python::with_gil(|py| -> PyResult<(HashMap<u64, PyObject>, HashSet<u64>)> {
let mut modules: HashMap<u64, PyObject> = HashMap::new();
@ -1095,14 +1161,22 @@ impl Nac3 {
let getmodule_fn = PyModule::import(py, "inspect")?.getattr("getmodule")?;
for function in functions {
let module = getmodule_fn.call1((function,))?.extract()?;
modules.insert(id_fn.call1((&module,))?.extract()?, module);
let module: PyObject = getmodule_fn.call1((function,))?.extract()?;
if !module.is_none(py) {
modules.insert(id_fn.call1((&module,))?.extract()?, module);
}
}
for class in classes {
let module = getmodule_fn.call1((class,))?.extract()?;
modules.insert(id_fn.call1((&module,))?.extract()?, module);
let module: PyObject = getmodule_fn.call1((class,))?.extract()?;
if !module.is_none(py) {
modules.insert(id_fn.call1((&module,))?.extract()?, module);
}
class_ids.insert(id_fn.call1((class,))?.extract()?);
}
for module in content_modules {
let module: PyObject = module.extract()?;
modules.insert(id_fn.call1((&module,))?.extract()?, module);
}
Ok((modules, class_ids))
})?;

View File

@ -13,16 +13,17 @@ use pyo3::{
PyAny, PyErr, PyObject, PyResult, Python,
};
use super::PrimitivePythonId;
use nac3core::{
codegen::{
model::*,
object::ndarray::{make_contiguous_strides, NDArray},
types::{ndarray::NDArrayType, ProxyType},
values::ndarray::make_contiguous_strides,
CodeGenContext, CodeGenerator,
},
inkwell::{
module::Linkage,
types::BasicType,
values::{BasicValue, BasicValueEnum},
types::{BasicType, BasicTypeEnum},
values::BasicValueEnum,
AddressSpace,
},
nac3parser::ast::{self, StrRef},
@ -38,8 +39,6 @@ use nac3core::{
},
};
use super::PrimitivePythonId;
pub enum PrimitiveValue {
I32(i32),
I64(i64),
@ -1086,15 +1085,19 @@ impl InnerResolver {
} else {
unreachable!("must be ndarray")
};
let (ndarray_dtype, ndarray_ndims) =
unpack_ndarray_var_tys(&mut ctx.unifier, ndarray_ty);
let (ndarray_dtype, _) = unpack_ndarray_var_tys(&mut ctx.unifier, ndarray_ty);
let llvm_i8 = ctx.ctx.i8_type();
let llvm_pi8 = llvm_i8.ptr_type(AddressSpace::default());
let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_ndarray = NDArrayType::from_unifier_type(generator, ctx, ndarray_ty);
let dtype = llvm_ndarray.element_type();
let dtype = Any(ctx.get_llvm_type(generator, ndarray_dtype));
{
if self.global_value_ids.read().contains_key(&id) {
let global = ctx.module.get_global(&id_str).unwrap_or_else(|| {
ctx.module.add_global(
Struct(NDArray).llvm_type(generator, ctx.ctx),
llvm_ndarray.as_base_type().get_element_type().into_struct_type(),
Some(AddressSpace::default()),
&id_str,
)
@ -1104,26 +1107,14 @@ impl InnerResolver {
self.global_value_ids.write().insert(id, obj.into());
}
let TypeEnum::TLiteral { values, .. } = &*ctx.unifier.get_ty_immutable(ndarray_ndims)
else {
unreachable!("Expected Literal for ndarray_ndims")
};
let ndarray_ndims = if values.len() == 1 {
values[0].clone()
} else {
todo!("Unpacking literal of more than one element unimplemented")
};
let Ok(ndims) = u64::try_from(ndarray_ndims) else {
unreachable!("Expected u64 value for ndarray_ndims")
};
let ndims = llvm_ndarray.ndims();
// Obtain the shape of the ndarray
let shape_tuple: &PyTuple = obj.getattr("shape")?.downcast()?;
assert_eq!(shape_tuple.len(), ndims as usize);
// The Rust type inferencer cannot figure this out
let shape_values: Result<Vec<Instance<'ctx, Int<SizeT>>>, PyErr> = shape_tuple
let shape_values = shape_tuple
.iter()
.enumerate()
.map(|(i, elem)| {
@ -1133,33 +1124,32 @@ impl InnerResolver {
super::CompileError::new_err(format!("Error getting element {i}: {e}"))
})?
.unwrap();
let value = Int(SizeT).check_value(generator, ctx.ctx, value).unwrap();
let value = value.into_int_value();
Ok(value)
})
.collect();
let shape_values = shape_values?;
.collect::<Result<Vec<_>, PyErr>>()?;
// Also use this opportunity to get the constant values of `shape_values` for calculating strides.
let shape_u64s = shape_values
.iter()
.map(|dim| {
assert!(dim.value.is_const());
dim.value.get_zero_extended_constant().unwrap()
assert!(dim.is_const());
dim.get_zero_extended_constant().unwrap()
})
.collect_vec();
let shape_values = Int(SizeT).const_array(generator, ctx.ctx, &shape_values);
let shape_values = llvm_usize.const_array(&shape_values);
// create a global for ndarray.shape and initialize it using the shape
let shape_global = ctx.module.add_global(
Array { len: AnyLen(ndims as u32), item: Int(SizeT) }.llvm_type(generator, ctx.ctx),
llvm_usize.array_type(ndims as u32),
Some(AddressSpace::default()),
&(id_str.clone() + ".shape"),
);
shape_global.set_initializer(&shape_values.value);
shape_global.set_initializer(&shape_values);
// Obtain the (flattened) elements of the ndarray
let sz: usize = obj.getattr("size")?.extract()?;
let data_values: Vec<Instance<'ctx, Any>> = (0..sz)
let data: Vec<_> = (0..sz)
.map(|i| {
obj.getattr("flat")?.get_item(i).and_then(|elem| {
let value = self
@ -1171,79 +1161,97 @@ impl InnerResolver {
})?
.unwrap();
let value = dtype.check_value(generator, ctx.ctx, value).unwrap();
assert_eq!(value.get_type(), dtype);
Ok(value)
})
})
.try_collect()?;
let data = dtype.const_array(generator, ctx.ctx, &data_values);
let data = data.into_iter();
let data = match dtype {
BasicTypeEnum::ArrayType(ty) => {
ty.const_array(&data.map(BasicValueEnum::into_array_value).collect_vec())
}
BasicTypeEnum::FloatType(ty) => {
ty.const_array(&data.map(BasicValueEnum::into_float_value).collect_vec())
}
BasicTypeEnum::IntType(ty) => {
ty.const_array(&data.map(BasicValueEnum::into_int_value).collect_vec())
}
BasicTypeEnum::PointerType(ty) => {
ty.const_array(&data.map(BasicValueEnum::into_pointer_value).collect_vec())
}
BasicTypeEnum::StructType(ty) => {
ty.const_array(&data.map(BasicValueEnum::into_struct_value).collect_vec())
}
BasicTypeEnum::VectorType(_) => unreachable!(),
};
// create a global for ndarray.data and initialize it using the elements
//
// NOTE: NDArray's `data` is `u8*`. Here, `data_global` is an array of `dtype`.
// We will have to cast it to an `u8*` later.
let data_global = ctx.module.add_global(
Array { len: AnyLen(sz as u32), item: dtype }.llvm_type(generator, ctx.ctx),
dtype.array_type(sz as u32),
Some(AddressSpace::default()),
&(id_str.clone() + ".data"),
);
data_global.set_initializer(&data.value);
data_global.set_initializer(&data);
// Get the constant itemsize.
let itemsize = dtype.llvm_type(generator, ctx.ctx).size_of().unwrap();
let itemsize = dtype.size_of().unwrap();
let itemsize = itemsize.get_zero_extended_constant().unwrap();
// Create the strides needed for ndarray.strides
let strides = make_contiguous_strides(itemsize, ndims, &shape_u64s);
let strides = strides
.into_iter()
.map(|stride| Int(SizeT).const_int(generator, ctx.ctx, stride, false))
.collect_vec();
let strides = Int(SizeT).const_array(generator, ctx.ctx, &strides);
let strides =
strides.into_iter().map(|stride| llvm_usize.const_int(stride, false)).collect_vec();
let strides = llvm_usize.const_array(&strides);
// create a global for ndarray.strides and initialize it
let strides_global = ctx.module.add_global(
Array { len: AnyLen(ndims as u32), item: Int(Byte) }.llvm_type(generator, ctx.ctx),
llvm_i8.array_type(ndims as u32),
Some(AddressSpace::default()),
&(id_str.clone() + ".strides"),
&format!("${id_str}.strides"),
);
strides_global.set_initializer(&strides.value);
strides_global.set_initializer(&strides);
// create a global for the ndarray object and initialize it
// We are also doing [`Model::check_value`] instead of [`Model::believe_value`] to catch bugs.
// NOTE: data_global is an array of dtype, we want a `u8*`.
let ndarray_data = Ptr(dtype).check_value(generator, ctx.ctx, data_global).unwrap();
let ndarray_data = Ptr(Int(Byte)).pointer_cast(generator, ctx, ndarray_data.value);
let ndarray_data = data_global.as_pointer_value();
let ndarray_data = ctx.builder.build_pointer_cast(ndarray_data, llvm_pi8, "").unwrap();
let ndarray_itemsize = Int(SizeT).const_int(generator, ctx.ctx, itemsize, false);
let ndarray_itemsize = llvm_usize.const_int(itemsize, false);
let ndarray_ndims = Int(SizeT).const_int(generator, ctx.ctx, ndims, false);
let ndarray_ndims = llvm_usize.const_int(ndims, false);
let ndarray_shape =
Ptr(Int(SizeT)).check_value(generator, ctx.ctx, shape_global).unwrap();
let ndarray_shape = shape_global.as_pointer_value();
let ndarray_strides =
Ptr(Int(SizeT)).check_value(generator, ctx.ctx, strides_global).unwrap();
let ndarray_strides = strides_global.as_pointer_value();
let ndarray = Struct(NDArray).const_struct(
generator,
ctx.ctx,
&[
ndarray_data.value.as_basic_value_enum(),
ndarray_itemsize.value.as_basic_value_enum(),
ndarray_ndims.value.as_basic_value_enum(),
ndarray_shape.value.as_basic_value_enum(),
ndarray_strides.value.as_basic_value_enum(),
],
);
let ndarray = llvm_ndarray
.as_base_type()
.get_element_type()
.into_struct_type()
.const_named_struct(&[
ndarray_itemsize.into(),
ndarray_ndims.into(),
ndarray_shape.into(),
ndarray_strides.into(),
ndarray_data.into(),
]);
let ndarray_global = ctx.module.add_global(
Struct(NDArray).llvm_type(generator, ctx.ctx),
llvm_ndarray.as_base_type().get_element_type().into_struct_type(),
Some(AddressSpace::default()),
&id_str,
);
ndarray_global.set_initializer(&ndarray.value);
ndarray_global.set_initializer(&ndarray);
Ok(Some(ndarray_global.as_pointer_value().into()))
} else if ty_id == self.primitive_ids.tuple {
@ -1568,10 +1576,7 @@ impl SymbolResolver for Resolver {
if let Some(id) = string_store.get(s) {
*id
} else {
let id = Python::with_gil(|py| -> PyResult<i32> {
self.0.helper.store_str.call1(py, (s,))?.extract(py)
})
.unwrap();
let id = i32::try_from(string_store.len()).unwrap();
string_store.insert(s.into(), id);
id
}

View File

@ -10,7 +10,6 @@ constant-optimization = ["fold"]
fold = []
[dependencies]
lazy_static = "1.5"
parking_lot = "0.12"
string-interner = "0.17"
fxhash = "0.2"

View File

@ -5,14 +5,12 @@ pub use crate::location::Location;
use fxhash::FxBuildHasher;
use parking_lot::{Mutex, MutexGuard};
use std::{cell::RefCell, collections::HashMap, fmt};
use std::{cell::RefCell, collections::HashMap, fmt, sync::LazyLock};
use string_interner::{symbol::SymbolU32, DefaultBackend, StringInterner};
pub type Interner = StringInterner<DefaultBackend, FxBuildHasher>;
lazy_static! {
static ref INTERNER: Mutex<Interner> =
Mutex::new(StringInterner::with_hasher(FxBuildHasher::default()));
}
static INTERNER: LazyLock<Mutex<Interner>> =
LazyLock::new(|| Mutex::new(StringInterner::with_hasher(FxBuildHasher::default())));
thread_local! {
static LOCAL_INTERNER: RefCell<HashMap<String, StrRef>> = RefCell::default();

View File

@ -1,10 +1,4 @@
#![deny(
future_incompatible,
let_underscore,
nonstandard_style,
rust_2024_compatibility,
clippy::all
)]
#![deny(future_incompatible, let_underscore, nonstandard_style, clippy::all)]
#![warn(clippy::pedantic)]
#![allow(
clippy::missing_errors_doc,
@ -14,9 +8,6 @@
clippy::wildcard_imports
)]
#[macro_use]
extern crate lazy_static;
mod ast_gen;
mod constant;
#[cfg(feature = "fold")]

View File

@ -5,6 +5,8 @@ authors = ["M-Labs"]
edition = "2021"
[features]
default = ["derive"]
derive = ["dep:nac3core_derive"]
no-escape-analysis = []
[dependencies]
@ -13,6 +15,7 @@ crossbeam = "0.8"
indexmap = "2.6"
parking_lot = "0.12"
rayon = "1.10"
nac3core_derive = { path = "nac3core_derive", optional = true }
nac3parser = { path = "../nac3parser" }
strum = "0.26"
strum_macros = "0.26"

View File

@ -56,9 +56,8 @@ fn main() {
let output = Command::new("clang-irrt")
.args(flags)
.output()
.map(|o| {
.inspect(|o| {
assert!(o.status.success(), "{}", std::str::from_utf8(&o.stderr).unwrap());
o
})
.unwrap();

View File

@ -1,9 +1,9 @@
#include "irrt/exception.hpp"
#include "irrt/int_types.hpp"
#include "irrt/list.hpp"
#include "irrt/math.hpp"
#include "irrt/range.hpp"
#include "irrt/slice.hpp"
#include "irrt/string.hpp"
#include "irrt/ndarray/basic.hpp"
#include "irrt/ndarray/def.hpp"
#include "irrt/ndarray/iter.hpp"

View File

@ -4,6 +4,6 @@
template<typename SizeT>
struct CSlice {
uint8_t* base;
void* base;
SizeT len;
};

View File

@ -6,7 +6,7 @@
/**
* @brief The int type of ARTIQ exception IDs.
*/
typedef int32_t ExceptionId;
using ExceptionId = int32_t;
/*
* Set of exceptions C++ IRRT can use.
@ -55,14 +55,14 @@ void _raise_exception_helper(ExceptionId id,
int64_t param2) {
Exception<SizeT> e = {
.id = id,
.filename = {.base = reinterpret_cast<uint8_t*>(const_cast<char*>(filename)),
.len = static_cast<int32_t>(__builtin_strlen(filename))},
.filename = {.base = reinterpret_cast<void*>(const_cast<char*>(filename)),
.len = static_cast<SizeT>(__builtin_strlen(filename))},
.line = line,
.column = 0,
.function = {.base = reinterpret_cast<uint8_t*>(const_cast<char*>(function)),
.len = static_cast<int32_t>(__builtin_strlen(function))},
.msg = {.base = reinterpret_cast<uint8_t*>(const_cast<char*>(msg)),
.len = static_cast<int32_t>(__builtin_strlen(msg))},
.function = {.base = reinterpret_cast<void*>(const_cast<char*>(function)),
.len = static_cast<SizeT>(__builtin_strlen(function))},
.msg = {.base = reinterpret_cast<void*>(const_cast<char*>(msg)),
.len = static_cast<SizeT>(__builtin_strlen(msg))},
};
e.params[0] = param0;
e.params[1] = param1;
@ -70,6 +70,7 @@ void _raise_exception_helper(ExceptionId id,
__nac3_raise(reinterpret_cast<void*>(&e));
__builtin_unreachable();
}
} // namespace
/**
* @brief Raise an exception with location details (location in the IRRT source files).
@ -82,4 +83,3 @@ void _raise_exception_helper(ExceptionId id,
*/
#define raise_exception(SizeT, id, msg, param0, param1, param2) \
_raise_exception_helper<SizeT>(id, __FILE__, __LINE__, __FUNCTION__, msg, param0, param1, param2)
} // namespace

View File

@ -8,15 +8,18 @@ using uint32_t = unsigned _BitInt(32);
using int64_t = _BitInt(64);
using uint64_t = unsigned _BitInt(64);
#else
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-type"
using int8_t = _ExtInt(8);
using uint8_t = unsigned _ExtInt(8);
using int32_t = _ExtInt(32);
using uint32_t = unsigned _ExtInt(32);
using int64_t = _ExtInt(64);
using uint64_t = unsigned _ExtInt(64);
#pragma clang diagnostic pop
#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`.
using SliceIndex = int32_t;

View File

@ -28,12 +28,12 @@ extern "C" {
SliceIndex __nac3_list_slice_assign_var_size(SliceIndex dest_start,
SliceIndex dest_end,
SliceIndex dest_step,
uint8_t* dest_arr,
void* dest_arr,
SliceIndex dest_arr_len,
SliceIndex src_start,
SliceIndex src_end,
SliceIndex src_step,
uint8_t* src_arr,
void* src_arr,
SliceIndex src_arr_len,
const SliceIndex size) {
/* if dest_arr_len == 0, do nothing since we do not support extending list */
@ -44,11 +44,13 @@ SliceIndex __nac3_list_slice_assign_var_size(SliceIndex dest_start,
const SliceIndex src_len = (src_end >= src_start) ? (src_end - src_start + 1) : 0;
const SliceIndex dest_len = (dest_end >= dest_start) ? (dest_end - dest_start + 1) : 0;
if (src_len > 0) {
__builtin_memmove(dest_arr + dest_start * size, src_arr + src_start * size, src_len * size);
__builtin_memmove(static_cast<uint8_t*>(dest_arr) + dest_start * size,
static_cast<uint8_t*>(src_arr) + src_start * size, src_len * size);
}
if (dest_len > 0) {
/* dropping */
__builtin_memmove(dest_arr + (dest_start + src_len) * size, dest_arr + (dest_end + 1) * size,
__builtin_memmove(static_cast<uint8_t*>(dest_arr) + (dest_start + src_len) * size,
static_cast<uint8_t*>(dest_arr) + (dest_end + 1) * size,
(dest_arr_len - dest_end - 1) * size);
}
/* shrink size */
@ -59,7 +61,7 @@ SliceIndex __nac3_list_slice_assign_var_size(SliceIndex dest_start,
&& !(max(dest_start, dest_end) < min(src_start, src_end)
|| max(src_start, src_end) < min(dest_start, dest_end));
if (need_alloca) {
uint8_t* tmp = reinterpret_cast<uint8_t*>(__builtin_alloca(src_arr_len * size));
void* tmp = __builtin_alloca(src_arr_len * size);
__builtin_memcpy(tmp, src_arr, src_arr_len * size);
src_arr = tmp;
}
@ -68,20 +70,24 @@ SliceIndex __nac3_list_slice_assign_var_size(SliceIndex dest_start,
for (; (src_step > 0) ? (src_ind <= src_end) : (src_ind >= src_end); src_ind += src_step, dest_ind += dest_step) {
/* for constant optimization */
if (size == 1) {
__builtin_memcpy(dest_arr + dest_ind, src_arr + src_ind, 1);
__builtin_memcpy(static_cast<uint8_t*>(dest_arr) + dest_ind, static_cast<uint8_t*>(src_arr) + src_ind, 1);
} else if (size == 4) {
__builtin_memcpy(dest_arr + dest_ind * 4, src_arr + src_ind * 4, 4);
__builtin_memcpy(static_cast<uint8_t*>(dest_arr) + dest_ind * 4,
static_cast<uint8_t*>(src_arr) + src_ind * 4, 4);
} else if (size == 8) {
__builtin_memcpy(dest_arr + dest_ind * 8, src_arr + src_ind * 8, 8);
__builtin_memcpy(static_cast<uint8_t*>(dest_arr) + dest_ind * 8,
static_cast<uint8_t*>(src_arr) + src_ind * 8, 8);
} else {
/* memcpy for var size, cannot overlap after previous alloca */
__builtin_memcpy(dest_arr + dest_ind * size, src_arr + src_ind * size, size);
__builtin_memcpy(static_cast<uint8_t*>(dest_arr) + dest_ind * size,
static_cast<uint8_t*>(src_arr) + src_ind * size, size);
}
}
/* only dest_step == 1 can we shrink the dest list. */
/* size should be ensured prior to calling this function */
if (dest_step == 1 && dest_end >= dest_start) {
__builtin_memmove(dest_arr + dest_ind * size, dest_arr + (dest_end + 1) * size,
__builtin_memmove(static_cast<uint8_t*>(dest_arr) + dest_ind * size,
static_cast<uint8_t*>(dest_arr) + (dest_end + 1) * size,
(dest_arr_len - dest_end - 1) * size);
return dest_arr_len - (dest_end - dest_ind) - 1;
}

View File

@ -1,5 +1,7 @@
#pragma once
#include "irrt/int_types.hpp"
namespace {
// 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
@ -90,4 +92,4 @@ double __nac3_j0(double x) {
return j0(x);
}
}
} // namespace

View File

@ -8,8 +8,7 @@
#include "irrt/ndarray/def.hpp"
namespace {
namespace ndarray {
namespace array {
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],
@ -88,7 +87,7 @@ void write_list_to_array_helper(SizeT axis, SizeT* index, List<SizeT>* list, NDA
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 = ndarray->data + (ndarray->itemsize * (*index));
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 {
@ -109,8 +108,7 @@ 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 array
} // namespace ndarray
} // namespace ndarray::array
} // namespace
extern "C" {

View File

@ -6,8 +6,7 @@
#include "irrt/ndarray/def.hpp"
namespace {
namespace ndarray {
namespace basic {
namespace ndarray::basic {
/**
* @brief Assert that `shape` does not contain negative dimensions.
*
@ -113,12 +112,13 @@ SizeT nbytes(const NDArray<SizeT>* ndarray) {
*/
template<typename SizeT>
SizeT len(const NDArray<SizeT>* ndarray) {
// numpy prohibits `__len__` on unsized objects
if (ndarray->ndims == 0) {
raise_exception(SizeT, EXN_TYPE_ERROR, "len() of unsized object", NO_PARAM, NO_PARAM, NO_PARAM);
} else {
if (ndarray->ndims != 0) {
return ndarray->shape[0];
}
// numpy prohibits `__len__` on unsized objects
raise_exception(SizeT, EXN_TYPE_ERROR, "len() of unsized object", NO_PARAM, NO_PARAM, NO_PARAM);
__builtin_unreachable();
}
/**
@ -178,10 +178,10 @@ bool is_c_contiguous(const NDArray<SizeT>* ndarray) {
* This function does no bound check.
*/
template<typename SizeT>
uint8_t* get_pelement_by_indices(const NDArray<SizeT>* ndarray, const SizeT* indices) {
uint8_t* element = ndarray->data;
void* get_pelement_by_indices(const NDArray<SizeT>* ndarray, const SizeT* indices) {
void* element = ndarray->data;
for (SizeT dim_i = 0; dim_i < ndarray->ndims; dim_i++)
element += indices[dim_i] * ndarray->strides[dim_i];
element = static_cast<uint8_t*>(element) + indices[dim_i] * ndarray->strides[dim_i];
return element;
}
@ -191,12 +191,12 @@ uint8_t* get_pelement_by_indices(const NDArray<SizeT>* ndarray, const SizeT* ind
* This function does no bound check.
*/
template<typename SizeT>
uint8_t* get_nth_pelement(const NDArray<SizeT>* ndarray, SizeT nth) {
uint8_t* element = ndarray->data;
void* get_nth_pelement(const NDArray<SizeT>* ndarray, SizeT nth) {
void* element = ndarray->data;
for (SizeT i = 0; i < ndarray->ndims; i++) {
SizeT axis = ndarray->ndims - i - 1;
SizeT dim = ndarray->shape[axis];
element += ndarray->strides[axis] * (nth % dim);
element = static_cast<uint8_t*>(element) + ndarray->strides[axis] * (nth % dim);
nth /= dim;
}
return element;
@ -224,7 +224,7 @@ void set_strides_by_shape(NDArray<SizeT>* ndarray) {
* @param pvalue Pointer to the value `pelement` will be set to.
*/
template<typename SizeT>
void set_pelement_value(NDArray<SizeT>* ndarray, uint8_t* pelement, const uint8_t* pvalue) {
void set_pelement_value(NDArray<SizeT>* ndarray, void* pelement, const void* pvalue) {
__builtin_memcpy(pelement, pvalue, ndarray->itemsize);
}
@ -246,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);
}
}
} // namespace basic
} // namespace ndarray
} // namespace ndarray::basic
} // namespace
extern "C" {
@ -307,19 +306,19 @@ bool __nac3_ndarray_is_c_contiguous64(NDArray<int64_t>* ndarray) {
return is_c_contiguous(ndarray);
}
uint8_t* __nac3_ndarray_get_nth_pelement(const NDArray<int32_t>* ndarray, int32_t nth) {
void* __nac3_ndarray_get_nth_pelement(const NDArray<int32_t>* ndarray, int32_t nth) {
return get_nth_pelement(ndarray, nth);
}
uint8_t* __nac3_ndarray_get_nth_pelement64(const NDArray<int64_t>* ndarray, int64_t nth) {
void* __nac3_ndarray_get_nth_pelement64(const NDArray<int64_t>* ndarray, int64_t nth) {
return get_nth_pelement(ndarray, nth);
}
uint8_t* __nac3_ndarray_get_pelement_by_indices(const NDArray<int32_t>* ndarray, int32_t* indices) {
void* __nac3_ndarray_get_pelement_by_indices(const NDArray<int32_t>* ndarray, int32_t* indices) {
return get_pelement_by_indices(ndarray, indices);
}
uint8_t* __nac3_ndarray_get_pelement_by_indices64(const NDArray<int64_t>* ndarray, int64_t* indices) {
void* __nac3_ndarray_get_pelement_by_indices64(const NDArray<int64_t>* ndarray, int64_t* indices) {
return get_pelement_by_indices(ndarray, indices);
}

View File

@ -13,8 +13,7 @@ struct ShapeEntry {
} // namespace
namespace {
namespace ndarray {
namespace broadcast {
namespace ndarray::broadcast {
/**
* @brief Return true if `src_shape` can broadcast to `dst_shape`.
*
@ -87,8 +86,10 @@ void broadcast_shapes(SizeT num_shapes, const ShapeEntry<SizeT>* shapes, SizeT d
}
}
#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
}
/**
@ -134,8 +135,7 @@ void broadcast_to(const NDArray<SizeT>* src_ndarray, NDArray<SizeT>* dst_ndarray
}
}
}
} // namespace broadcast
} // namespace ndarray
} // namespace ndarray::broadcast
} // namespace
extern "C" {

View File

@ -7,15 +7,16 @@ namespace {
* @brief The NDArray object
*
* Official numpy implementation:
* https://github.com/numpy/numpy/blob/735a477f0bc2b5b84d0e72d92f224bde78d4e069/doc/source/reference/c-api/types-and-structures.rst
* https://github.com/numpy/numpy/blob/735a477f0bc2b5b84d0e72d92f224bde78d4e069/doc/source/reference/c-api/types-and-structures.rst#pyarrayinterface
*
* Note that this implementation is based on `PyArrayInterface` rather of `PyArrayObject`. The
* difference between `PyArrayInterface` and `PyArrayObject` (relevant to our implementation) is
* that `PyArrayInterface` *has* `itemsize` and uses `void*` for its `data`, whereas `PyArrayObject`
* does not require `itemsize` (probably using `strides[-1]` instead) and uses `char*` for its
* `data`. There are also minor differences in the struct layout.
*/
template<typename SizeT>
struct NDArray {
/**
* @brief The underlying data this `ndarray` is pointing to.
*/
uint8_t* data;
/**
* @brief The number of bytes of a single element in `data`.
*/
@ -41,5 +42,10 @@ struct NDArray {
* Note that `strides` can have negative values or contain 0.
*/
SizeT* strides;
/**
* @brief The underlying data this `ndarray` is pointing to.
*/
void* data;
};
} // namespace

View File

@ -65,8 +65,7 @@ struct NDIndex {
} // namespace
namespace {
namespace ndarray {
namespace indexing {
namespace ndarray::indexing {
/**
* @brief Perform ndarray "basic indexing" (https://numpy.org/doc/stable/user/basics.indexing.html#basic-indexing)
*
@ -154,7 +153,7 @@ void index(SizeT num_indices, const NDIndex* indices, const NDArray<SizeT>* src_
input, src_axis, src_ndarray->shape[src_axis]);
}
dst_ndarray->data += k * src_ndarray->strides[src_axis];
dst_ndarray->data = static_cast<uint8_t*>(dst_ndarray->data) + k * src_ndarray->strides[src_axis];
src_axis++;
} else if (index->type == ND_INDEX_TYPE_SLICE) {
@ -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]);
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->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, dst_ndarray->ndims, dst_axis);
}
} // namespace indexing
} // namespace ndarray
} // namespace ndarray::indexing
} // namespace
extern "C" {

View File

@ -58,7 +58,7 @@ struct NDIter {
*
* Initially this points to first element of the ndarray.
*/
uint8_t* element;
void* element;
/**
* @brief Cache for the product of shape.
@ -67,7 +67,7 @@ struct NDIter {
*/
SizeT size;
void initialize(SizeT ndims, SizeT* shape, SizeT* strides, uint8_t* element, SizeT* indices) {
void initialize(SizeT ndims, SizeT* shape, SizeT* strides, void* element, SizeT* indices) {
this->ndims = ndims;
this->shape = shape;
this->strides = strides;
@ -108,9 +108,9 @@ struct NDIter {
// TODO: There is something called backstrides to speedup iteration.
// See https://ajcr.net/stride-guide-part-1/, and
// https://docs.scipy.org/doc/numpy-1.13.0/reference/c-api.types-and-structures.html#c.PyArrayIterObject.PyArrayIterObject.backstrides.
element -= strides[axis] * (shape[axis] - 1);
element = static_cast<void*>(reinterpret_cast<uint8_t*>(element) - strides[axis] * (shape[axis] - 1));
} else {
element += strides[axis];
element = static_cast<void*>(reinterpret_cast<uint8_t*>(element) + strides[axis]);
break;
}
}

View File

@ -10,8 +10,7 @@
// NOTE: Everything would be much easier and elegant if einsum is implemented.
namespace {
namespace ndarray {
namespace matmul {
namespace ndarray::matmul {
/**
* @brief Perform the broadcast in `np.einsum("...ij,...jk->...ik", a, b)`.
@ -69,8 +68,7 @@ void calculate_shapes(SizeT a_ndims,
dst_shape[final_ndims - 2] = a_shape[a_ndims - 2];
dst_shape[final_ndims - 1] = b_shape[b_ndims - 1];
}
} // namespace matmul
} // namespace ndarray
} // namespace ndarray::matmul
} // namespace
extern "C" {

View File

@ -5,8 +5,7 @@
#include "irrt/ndarray/def.hpp"
namespace {
namespace ndarray {
namespace reshape {
namespace ndarray::reshape {
/**
* @brief Perform assertions on and resolve unknown dimensions in `new_shape` in `np.reshape(<ndarray>, new_shape)`
*
@ -84,8 +83,7 @@ void resolve_and_check_new_shape(SizeT size, SizeT new_ndims, SizeT* new_shape)
NO_PARAM);
}
}
} // namespace reshape
} // namespace ndarray
} // namespace ndarray::reshape
} // namespace
extern "C" {

View File

@ -16,8 +16,7 @@
*/
namespace {
namespace ndarray {
namespace transpose {
namespace ndarray::transpose {
/**
* @brief Do assertions on `<axes>` in `np.transpose(<array>, <axes>)`.
*
@ -123,8 +122,7 @@ void transpose(const NDArray<SizeT>* src_ndarray, NDArray<SizeT>* dst_ndarray, S
}
}
}
} // namespace transpose
} // namespace ndarray
} // namespace ndarray::transpose
} // namespace
extern "C" {

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);
}
}

View File

@ -0,0 +1,21 @@
[package]
name = "nac3core_derive"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
[[test]]
name = "structfields_tests"
path = "tests/structfields_test.rs"
[dev-dependencies]
nac3core = { path = ".." }
trybuild = { version = "1.0", features = ["diff"] }
[dependencies]
proc-macro2 = "1.0"
proc-macro-error = "1.0"
syn = "2.0"
quote = "1.0"

View File

@ -0,0 +1,320 @@
use proc_macro::TokenStream;
use proc_macro_error::{abort, proc_macro_error};
use quote::quote;
use syn::{
parse_macro_input, spanned::Spanned, Data, DataStruct, Expr, ExprField, ExprMethodCall,
ExprPath, GenericArgument, Ident, LitStr, Path, PathArguments, Type, TypePath,
};
/// Extracts all generic arguments of a [`Type`] into a [`Vec`].
///
/// Returns [`Some`] of a possibly-empty [`Vec`] if the path of `ty` matches with
/// `expected_ty_name`, otherwise returns [`None`].
fn extract_generic_args(expected_ty_name: &'static str, ty: &Type) -> Option<Vec<GenericArgument>> {
let Type::Path(TypePath { qself: None, path, .. }) = ty else {
return None;
};
let segments = &path.segments;
if segments.len() != 1 {
return None;
};
let segment = segments.iter().next().unwrap();
if segment.ident != expected_ty_name {
return None;
}
let PathArguments::AngleBracketed(path_args) = &segment.arguments else {
return Some(Vec::new());
};
let args = &path_args.args;
Some(args.iter().cloned().collect::<Vec<_>>())
}
/// Maps a `path` matching one of the `target_idents` into the `replacement` [`Ident`].
fn map_path_to_ident(path: &Path, target_idents: &[&str], replacement: &str) -> Option<Ident> {
path.require_ident()
.ok()
.filter(|ident| target_idents.iter().any(|target| ident == target))
.map(|ident| Ident::new(replacement, ident.span()))
}
/// Extracts the left-hand side of a dot-expression.
fn extract_dot_operand(expr: &Expr) -> Option<&Expr> {
match expr {
Expr::MethodCall(ExprMethodCall { receiver: operand, .. })
| Expr::Field(ExprField { base: operand, .. }) => Some(operand),
_ => None,
}
}
/// Replaces the top-level receiver of a dot-expression with an [`Ident`], returning `Some(&mut expr)` if the
/// replacement is performed.
///
/// The top-level receiver is the left-most receiver expression, e.g. the top-level receiver of `a.b.c.foo()` is `a`.
fn replace_top_level_receiver(expr: &mut Expr, ident: Ident) -> Option<&mut Expr> {
if let Expr::MethodCall(ExprMethodCall { receiver: operand, .. })
| Expr::Field(ExprField { base: operand, .. }) = expr
{
return if extract_dot_operand(operand).is_some() {
if replace_top_level_receiver(operand, ident).is_some() {
Some(expr)
} else {
None
}
} else {
*operand = Box::new(Expr::Path(ExprPath {
attrs: Vec::default(),
qself: None,
path: ident.into(),
}));
Some(expr)
};
}
None
}
/// Iterates all operands to the left-hand side of the `.` of an [expression][`Expr`], i.e. the container operand of all
/// [`Expr::Field`] and the receiver operand of all [`Expr::MethodCall`].
///
/// The iterator will return the operand expressions in reverse order of appearance. For example, `a.b.c.func()` will
/// return `vec![c, b, a]`.
fn iter_dot_operands(expr: &Expr) -> impl Iterator<Item = &Expr> {
let mut o = extract_dot_operand(expr);
std::iter::from_fn(move || {
let this = o;
o = o.as_ref().and_then(|o| extract_dot_operand(o));
this
})
}
/// Normalizes a value expression for use when creating an instance of this structure, returning a
/// [`proc_macro2::TokenStream`] of tokens representing the normalized expression.
fn normalize_value_expr(expr: &Expr) -> proc_macro2::TokenStream {
match &expr {
Expr::Path(ExprPath { qself: None, path, .. }) => {
if let Some(ident) = map_path_to_ident(path, &["usize", "size_t"], "llvm_usize") {
quote! { #ident }
} else {
abort!(
path,
format!(
"Expected one of `size_t`, `usize`, or an implicit call expression in #[value_type(...)], found {}",
quote!(#expr).to_string(),
)
)
}
}
Expr::Call(_) => {
quote! { ctx.#expr }
}
Expr::MethodCall(_) => {
let base_receiver = iter_dot_operands(expr).last();
match base_receiver {
// `usize.{...}`, `size_t.{...}` -> Rewrite the identifiers to `llvm_usize`
Some(Expr::Path(ExprPath { qself: None, path, .. }))
if map_path_to_ident(path, &["usize", "size_t"], "llvm_usize").is_some() =>
{
let ident =
map_path_to_ident(path, &["usize", "size_t"], "llvm_usize").unwrap();
let mut expr = expr.clone();
let expr = replace_top_level_receiver(&mut expr, ident).unwrap();
quote!(#expr)
}
// `ctx.{...}`, `context.{...}` -> Rewrite the identifiers to `ctx`
Some(Expr::Path(ExprPath { qself: None, path, .. }))
if map_path_to_ident(path, &["ctx", "context"], "ctx").is_some() =>
{
let ident = map_path_to_ident(path, &["ctx", "context"], "ctx").unwrap();
let mut expr = expr.clone();
let expr = replace_top_level_receiver(&mut expr, ident).unwrap();
quote!(#expr)
}
// No reserved identifier prefix -> Prepend `ctx.` to the entire expression
_ => quote! { ctx.#expr },
}
}
_ => {
abort!(
expr,
format!(
"Expected one of `size_t`, `usize`, or an implicit call expression in #[value_type(...)], found {}",
quote!(#expr).to_string(),
)
)
}
}
}
/// Derives an implementation of `codegen::types::structure::StructFields`.
///
/// The benefit of using `#[derive(StructFields)]` is that all index- or order-dependent logic required by
/// `impl StructFields` is automatically generated by this implementation, including the field index as required by
/// `StructField::new` and the fields as returned by `StructFields::to_vec`.
///
/// # Prerequisites
///
/// In order to derive from [`StructFields`], you must implement (or derive) [`Eq`] and [`Copy`] as required by
/// `StructFields`.
///
/// Moreover, `#[derive(StructFields)]` can only be used for `struct`s with named fields, and may only contain fields
/// with either `StructField` or [`PhantomData`] types.
///
/// # Attributes for [`StructFields`]
///
/// Each `StructField` field must be declared with the `#[value_type(...)]` attribute. The argument of `value_type`
/// accepts one of the following:
///
/// - An expression returning an instance of `inkwell::types::BasicType` (with or without the receiver `ctx`/`context`).
/// For example, `context.i8_type()`, `ctx.i8_type()`, and `i8_type()` all refer to `i8`.
/// - The reserved identifiers `usize` and `size_t` referring to an `inkwell::types::IntType` of the platform-dependent
/// integer size. `usize` and `size_t` can also be used as the receiver to other method calls, e.g.
/// `usize.array_type(3)`.
///
/// # Example
///
/// The following is an example of an LLVM slice implemented using `#[derive(StructFields)]`.
///
/// ```rust,ignore
/// use nac3core::{
/// codegen::types::structure::StructField,
/// inkwell::{
/// values::{IntValue, PointerValue},
/// AddressSpace,
/// },
/// };
/// use nac3core_derive::StructFields;
///
/// // All classes that implement StructFields must also implement Eq and Copy
/// #[derive(PartialEq, Eq, Clone, Copy, StructFields)]
/// pub struct SliceValue<'ctx> {
/// // Declares ptr have a value type of i8*
/// //
/// // Can also be written as `ctx.i8_type().ptr_type(...)` or `context.i8_type().ptr_type(...)`
/// #[value_type(i8_type().ptr_type(AddressSpace::default()))]
/// ptr: StructField<'ctx, PointerValue<'ctx>>,
///
/// // Declares len have a value type of usize, depending on the target compilation platform
/// #[value_type(usize)]
/// len: StructField<'ctx, IntValue<'ctx>>,
/// }
/// ```
#[proc_macro_derive(StructFields, attributes(value_type))]
#[proc_macro_error]
pub fn derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as syn::DeriveInput);
let ident = &input.ident;
let Data::Struct(DataStruct { fields, .. }) = &input.data else {
abort!(input, "Only structs with named fields are supported");
};
if let Err(err_span) =
fields
.iter()
.try_for_each(|field| if field.ident.is_some() { Ok(()) } else { Err(field.span()) })
{
abort!(err_span, "Only structs with named fields are supported");
};
// Check if struct<'ctx>
if input.generics.params.len() != 1 {
abort!(input.generics, "Expected exactly 1 generic parameter")
}
let phantom_info = fields
.iter()
.filter(|field| extract_generic_args("PhantomData", &field.ty).is_some())
.map(|field| field.ident.as_ref().unwrap())
.cloned()
.collect::<Vec<_>>();
let field_info = fields
.iter()
.filter(|field| extract_generic_args("PhantomData", &field.ty).is_none())
.map(|field| {
let ident = field.ident.as_ref().unwrap();
let ty = &field.ty;
let Some(_) = extract_generic_args("StructField", ty) else {
abort!(field, "Only StructField and PhantomData are allowed")
};
let attrs = &field.attrs;
let Some(value_type_attr) =
attrs.iter().find(|attr| attr.path().is_ident("value_type"))
else {
abort!(field, "Expected #[value_type(...)] attribute for field");
};
let Ok(value_type_expr) = value_type_attr.parse_args::<Expr>() else {
abort!(value_type_attr, "Expected expression in #[value_type(...)]");
};
let value_expr_toks = normalize_value_expr(&value_type_expr);
(ident.clone(), value_expr_toks)
})
.collect::<Vec<_>>();
// `<*>::new` impl of `StructField` and `PhantomData` for `StructFields::new`
let phantoms_create = phantom_info
.iter()
.map(|id| quote! { #id: ::std::marker::PhantomData })
.collect::<Vec<_>>();
let fields_create = field_info
.iter()
.map(|(id, ty)| {
let id_lit = LitStr::new(&id.to_string(), id.span());
quote! {
#id: ::nac3core::codegen::types::structure::StructField::create(
&mut counter,
#id_lit,
#ty,
)
}
})
.collect::<Vec<_>>();
// `.into()` impl of `StructField` for `StructFields::to_vec`
let fields_into =
field_info.iter().map(|(id, _)| quote! { self.#id.into() }).collect::<Vec<_>>();
let impl_block = quote! {
impl<'ctx> ::nac3core::codegen::types::structure::StructFields<'ctx> for #ident<'ctx> {
fn new(ctx: impl ::nac3core::inkwell::context::AsContextRef<'ctx>, llvm_usize: ::nac3core::inkwell::types::IntType<'ctx>) -> Self {
let ctx = unsafe { ::nac3core::inkwell::context::ContextRef::new(ctx.as_ctx_ref()) };
let mut counter = ::nac3core::codegen::types::structure::FieldIndexCounter::default();
#ident {
#(#fields_create),*
#(#phantoms_create),*
}
}
fn to_vec(&self) -> ::std::vec::Vec<(&'static str, ::nac3core::inkwell::types::BasicTypeEnum<'ctx>)> {
vec![
#(#fields_into),*
]
}
}
};
impl_block.into()
}

View File

@ -0,0 +1,9 @@
use nac3core_derive::StructFields;
use std::marker::PhantomData;
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
pub struct EmptyValue<'ctx> {
_phantom: PhantomData<&'ctx ()>,
}
fn main() {}

View File

@ -0,0 +1,20 @@
use nac3core::{
codegen::types::structure::StructField,
inkwell::{
values::{IntValue, PointerValue},
AddressSpace,
},
};
use nac3core_derive::StructFields;
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
pub struct NDArrayValue<'ctx> {
#[value_type(usize)]
ndims: StructField<'ctx, IntValue<'ctx>>,
#[value_type(usize.ptr_type(AddressSpace::default()))]
shape: StructField<'ctx, PointerValue<'ctx>>,
#[value_type(i8_type().ptr_type(AddressSpace::default()))]
data: StructField<'ctx, PointerValue<'ctx>>,
}
fn main() {}

View File

@ -0,0 +1,18 @@
use nac3core::{
codegen::types::structure::StructField,
inkwell::{
values::{IntValue, PointerValue},
AddressSpace,
},
};
use nac3core_derive::StructFields;
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
pub struct SliceValue<'ctx> {
#[value_type(i8_type().ptr_type(AddressSpace::default()))]
ptr: StructField<'ctx, PointerValue<'ctx>>,
#[value_type(usize)]
len: StructField<'ctx, IntValue<'ctx>>,
}
fn main() {}

View File

@ -0,0 +1,18 @@
use nac3core::{
codegen::types::structure::StructField,
inkwell::{
values::{IntValue, PointerValue},
AddressSpace,
},
};
use nac3core_derive::StructFields;
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
pub struct SliceValue<'ctx> {
#[value_type(context.i8_type().ptr_type(AddressSpace::default()))]
ptr: StructField<'ctx, PointerValue<'ctx>>,
#[value_type(usize)]
len: StructField<'ctx, IntValue<'ctx>>,
}
fn main() {}

View File

@ -0,0 +1,18 @@
use nac3core::{
codegen::types::structure::StructField,
inkwell::{
values::{IntValue, PointerValue},
AddressSpace,
},
};
use nac3core_derive::StructFields;
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
pub struct SliceValue<'ctx> {
#[value_type(ctx.i8_type().ptr_type(AddressSpace::default()))]
ptr: StructField<'ctx, PointerValue<'ctx>>,
#[value_type(usize)]
len: StructField<'ctx, IntValue<'ctx>>,
}
fn main() {}

View File

@ -0,0 +1,18 @@
use nac3core::{
codegen::types::structure::StructField,
inkwell::{
values::{IntValue, PointerValue},
AddressSpace,
},
};
use nac3core_derive::StructFields;
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
pub struct SliceValue<'ctx> {
#[value_type(i8_type().ptr_type(AddressSpace::default()))]
ptr: StructField<'ctx, PointerValue<'ctx>>,
#[value_type(size_t)]
len: StructField<'ctx, IntValue<'ctx>>,
}
fn main() {}

View File

@ -0,0 +1,10 @@
#[test]
fn test_parse_empty() {
let t = trybuild::TestCases::new();
t.pass("tests/structfields_empty.rs");
t.pass("tests/structfields_slice.rs");
t.pass("tests/structfields_slice_ctx.rs");
t.pass("tests/structfields_slice_context.rs");
t.pass("tests/structfields_slice_sizet.rs");
t.pass("tests/structfields_ndarray.rs");
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -8,13 +8,10 @@ use std::{
use inkwell::{
attributes::{Attribute, AttributeLoc},
types::{AnyType, BasicType, BasicTypeEnum},
values::{
BasicValue, BasicValueEnum, CallSiteValue, FunctionValue, IntValue, PointerValue,
StructValue,
},
values::{BasicValueEnum, CallSiteValue, FunctionValue, IntValue, PointerValue, StructValue},
AddressSpace, IntPredicate, OptimizationLevel,
};
use itertools::{chain, izip, Either, Itertools};
use itertools::{izip, Either, Itertools};
use nac3parser::ast::{
self, Boolop, Cmpop, Comprehension, Constant, Expr, ExprKind, Location, Operator, StrRef,
@ -22,35 +19,34 @@ use nac3parser::ast::{
};
use super::{
classes::{
ArrayLikeIndexer, ArrayLikeValue, ListType, ListValue, ProxyType, ProxyValue, RangeValue,
UntypedArrayLikeAccessor,
},
concrete_type::{ConcreteFuncArg, ConcreteTypeEnum, ConcreteTypeStore},
gen_in_range_check, get_llvm_abi_type, get_llvm_type, get_va_count_arg_name,
irrt::*,
llvm_intrinsics::{
call_expect, call_float_floor, call_float_pow, call_float_powi, call_int_smax,
call_int_umin, call_memcpy_generic,
call_memcpy_generic,
},
macros::codegen_unreachable,
need_sret,
object::{
any::AnyObject,
ndarray::{
indexing::util::gen_ndarray_subscript_ndindices, NDArrayObject, NDArrayOut,
ScalarOrNDArray,
},
},
stmt::{
gen_for_callback_incrementing, gen_if_callback, gen_if_else_expr_callback, gen_raise,
gen_var,
},
types::{ndarray::NDArrayType, ListType},
values::{
ndarray::{NDArrayOut, RustNDIndex, ScalarOrNDArray},
ArrayLikeIndexer, ArrayLikeValue, ListValue, ProxyValue, RangeValue,
UntypedArrayLikeAccessor,
},
CodeGenContext, CodeGenTask, CodeGenerator,
};
use crate::{
symbol_resolver::{SymbolValue, ValueEnum},
toplevel::{helper::PrimDef, DefinitionId, TopLevelDef},
toplevel::{
helper::{arraylike_flatten_element_type, PrimDef},
numpy::unpack_ndarray_var_tys,
DefinitionId, TopLevelDef,
},
typecheck::{
magic_methods::{Binop, BinopVariant, HasOpInfo},
typedef::{FunSignature, FuncArg, Type, TypeEnum, TypeVarId, Unifier, VarMap},
@ -1099,33 +1095,6 @@ pub fn destructure_range<'ctx>(
(start, end, step)
}
/// Allocates a List structure with the given [type][ty] and [length]. The name of the resulting
/// LLVM value is `{name}.addr`, or `list.addr` if [name] is not specified.
///
/// Setting `ty` to [`None`] implies that the list is empty **and** does not have a known element
/// type, and will therefore set the `list.data` type as `size_t*`. It is undefined behavior to
/// generate a sized list with an unknown element type.
pub fn allocate_list<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
ty: Option<BasicTypeEnum<'ctx>>,
length: IntValue<'ctx>,
name: Option<&'ctx str>,
) -> ListValue<'ctx> {
let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_elem_ty = ty.unwrap_or(llvm_usize.into());
// List structure; type { ty*, size_t }
let arr_ty = ListType::new(generator, ctx.ctx, llvm_elem_ty);
let list = arr_ty.new_value(generator, ctx, name);
let length = ctx.builder.build_int_z_extend(length, llvm_usize, "").unwrap();
list.store_size(ctx, generator, length);
list.create_data(ctx, llvm_elem_ty, None);
list
}
/// Generates LLVM IR for a [list comprehension expression][expr].
pub fn gen_comprehension<'ctx, G: CodeGenerator>(
generator: &mut G,
@ -1173,7 +1142,8 @@ pub fn gen_comprehension<'ctx, G: CodeGenerator>(
TypeEnum::TObj { obj_id, .. }
if *obj_id == ctx.primitives.range.obj_id(&ctx.unifier).unwrap() =>
{
let iter_val = RangeValue::from_ptr_val(iter_val.into_pointer_value(), Some("range"));
let iter_val =
RangeValue::from_pointer_value(iter_val.into_pointer_value(), Some("range"));
let (start, stop, step) = destructure_range(ctx, iter_val);
let diff = ctx.builder.build_int_sub(stop, start, "diff").unwrap();
// add 1 to the length as the value is rounded to zero
@ -1197,12 +1167,11 @@ pub fn gen_comprehension<'ctx, G: CodeGenerator>(
"listcomp.alloc_size",
)
.unwrap();
list = allocate_list(
list = ListType::new(generator, ctx.ctx, elem_ty).construct(
generator,
ctx,
Some(elem_ty),
list_alloc_size.into_int_value(),
Some("listcomp.addr"),
Some("listcomp"),
);
let i = generator.gen_store_target(ctx, target, Some("i.addr"))?.unwrap();
@ -1249,7 +1218,12 @@ pub fn gen_comprehension<'ctx, G: CodeGenerator>(
Some("length"),
)
.into_int_value();
list = allocate_list(generator, ctx, Some(elem_ty), length, Some("listcomp"));
list = ListType::new(generator, ctx.ctx, elem_ty).construct(
generator,
ctx,
length,
Some("listcomp"),
);
let counter = generator.gen_var_alloc(ctx, size_t.into(), Some("counter.addr"))?;
// counter = -1
@ -1404,15 +1378,18 @@ pub fn gen_binop_expr_with_values<'ctx, G: CodeGenerator>(
let llvm_elem_ty = ctx.get_llvm_type(generator, elem_ty1);
let sizeof_elem = llvm_elem_ty.size_of().unwrap();
let lhs = ListValue::from_ptr_val(left_val.into_pointer_value(), llvm_usize, None);
let rhs = ListValue::from_ptr_val(right_val.into_pointer_value(), llvm_usize, None);
let lhs =
ListValue::from_pointer_value(left_val.into_pointer_value(), llvm_usize, None);
let rhs =
ListValue::from_pointer_value(right_val.into_pointer_value(), llvm_usize, None);
let size = ctx
.builder
.build_int_add(lhs.load_size(ctx, None), rhs.load_size(ctx, None), "")
.unwrap();
let new_list = allocate_list(generator, ctx, Some(llvm_elem_ty), size, None);
let new_list = ListType::new(generator, ctx.ctx, llvm_elem_ty)
.construct(generator, ctx, size, None);
let lhs_size = ctx
.builder
@ -1488,7 +1465,7 @@ pub fn gen_binop_expr_with_values<'ctx, G: CodeGenerator>(
codegen_unreachable!(ctx)
};
let list_val =
ListValue::from_ptr_val(list_val.into_pointer_value(), llvm_usize, None);
ListValue::from_pointer_value(list_val.into_pointer_value(), llvm_usize, None);
let int_val = ctx
.builder
.build_int_s_extend(int_val.into_int_value(), llvm_usize, "")
@ -1499,10 +1476,9 @@ pub fn gen_binop_expr_with_values<'ctx, G: CodeGenerator>(
let elem_llvm_ty = ctx.get_llvm_type(generator, elem_ty);
let sizeof_elem = elem_llvm_ty.size_of().unwrap();
let new_list = allocate_list(
let new_list = ListType::new(generator, ctx.ctx, elem_llvm_ty).construct(
generator,
ctx,
Some(elem_llvm_ty),
ctx.builder.build_int_mul(list_val.load_size(ctx, None), int_val, "").unwrap(),
None,
);
@ -1555,21 +1531,23 @@ pub fn gen_binop_expr_with_values<'ctx, G: CodeGenerator>(
} else if ty1.obj_id(&ctx.unifier).is_some_and(|id| id == PrimDef::NDArray.id())
|| ty2.obj_id(&ctx.unifier).is_some_and(|id| id == PrimDef::NDArray.id())
{
let left =
ScalarOrNDArray::split_object(generator, ctx, AnyObject { ty: ty1, value: left_val });
let right =
ScalarOrNDArray::split_object(generator, ctx, AnyObject { ty: ty2, value: right_val });
let left = ScalarOrNDArray::from_value(generator, ctx, (ty1, left_val));
let right = ScalarOrNDArray::from_value(generator, ctx, (ty2, right_val));
let ty1_dtype = arraylike_flatten_element_type(&mut ctx.unifier, ty1);
let ty2_dtype = arraylike_flatten_element_type(&mut ctx.unifier, ty2);
// Inhomogeneous binary operations are not supported.
assert!(ctx.unifier.unioned(left.get_dtype(), right.get_dtype()));
assert!(ctx.unifier.unioned(ty1_dtype, ty2_dtype));
let common_dtype = left.get_dtype();
let common_dtype = ty1_dtype;
let llvm_common_dtype = left.get_dtype();
let out = match op.variant {
BinopVariant::Normal => NDArrayOut::NewNDArray { dtype: common_dtype },
BinopVariant::Normal => NDArrayOut::NewNDArray { dtype: llvm_common_dtype },
BinopVariant::AugAssign => {
// If this is an augmented assignment.
// `left` has to be an ndarray. If it were a scalar then NAC3 simply doesn't support it.
// Augmented assignment - `left` has to be an ndarray. If it were a scalar then NAC3
// simply doesn't support it.
if let ScalarOrNDArray::NDArray(out_ndarray) = left {
NDArrayOut::WriteToNDArray { ndarray: out_ndarray }
} else {
@ -1579,12 +1557,12 @@ pub fn gen_binop_expr_with_values<'ctx, G: CodeGenerator>(
};
if op.base == Operator::MatMult {
// Handle matrix multiplication.
let left = left.to_ndarray(generator, ctx);
let right = right.to_ndarray(generator, ctx);
let result = NDArrayObject::matmul(generator, ctx, left, right, out)
let result = left
.matmul(generator, ctx, ty1, (ty2, right), (common_dtype, out))
.split_unsized(generator, ctx);
Ok(Some(ValueEnum::Dynamic(result.to_basic_value_enum())))
Ok(Some(result.to_basic_value_enum().into()))
} else {
// For other operations, they are all elementwise operations.
@ -1599,31 +1577,31 @@ pub fn gen_binop_expr_with_values<'ctx, G: CodeGenerator>(
let left = left.to_ndarray(generator, ctx);
let right = right.to_ndarray(generator, ctx);
let result = NDArrayObject::broadcast_starmap(
let result = NDArrayType::new_broadcast(
generator,
ctx,
&[left, right],
out,
|generator, ctx, scalars| {
let left_value = scalars[0];
let right_value = scalars[1];
let result = gen_binop_expr_with_values(
generator,
ctx,
(&Some(left.dtype), left_value),
op,
(&Some(right.dtype), right_value),
ctx.current_loc,
)?
.unwrap()
.to_basic_value_enum(ctx, generator, common_dtype)?;
Ok(result)
},
ctx.ctx,
llvm_common_dtype,
&[left.get_type(), right.get_type()],
)
.broadcast_starmap(generator, ctx, &[left, right], out, |generator, ctx, scalars| {
let left_value = scalars[0];
let right_value = scalars[1];
let result = gen_binop_expr_with_values(
generator,
ctx,
(&Some(ty1_dtype), left_value),
op,
(&Some(ty2_dtype), right_value),
ctx.current_loc,
)?
.unwrap()
.to_basic_value_enum(ctx, generator, common_dtype)?;
Ok(result)
})
.unwrap();
Ok(Some(ValueEnum::Dynamic(result.instance.value.as_basic_value_enum())))
Ok(Some(result.as_base_value().into()))
}
} else {
let left_ty_enum = ctx.unifier.get_ty_immutable(left_ty.unwrap());
@ -1759,7 +1737,12 @@ pub fn gen_unaryop_expr_with_values<'ctx, G: CodeGenerator>(
ast::Unaryop::Invert => ctx.builder.build_not(val, "not").map(Into::into).unwrap(),
ast::Unaryop::Not => ctx
.builder
.build_xor(val, val.get_type().const_all_ones(), "not")
.build_int_compare(
inkwell::IntPredicate::EQ,
val,
val.get_type().const_zero(),
"not",
)
.map(Into::into)
.unwrap(),
ast::Unaryop::UAdd => val.into(),
@ -1781,12 +1764,14 @@ pub fn gen_unaryop_expr_with_values<'ctx, G: CodeGenerator>(
_ => val.into(),
}
} else if ty.obj_id(&ctx.unifier).is_some_and(|id| id == PrimDef::NDArray.id()) {
let ndarray = AnyObject { value: val, ty };
let ndarray = NDArrayObject::from_object(generator, ctx, ndarray);
let (ndarray_dtype, _) = unpack_ndarray_var_tys(&mut ctx.unifier, ty);
let ndarray = NDArrayType::from_unifier_type(generator, ctx, ty)
.map_value(val.into_pointer_value(), None);
// ndarray uses `~` rather than `not` to perform elementwise inversion, convert it before
// passing it to the elementwise codegen function
let op = if ndarray.dtype.obj_id(&ctx.unifier).is_some_and(|id| id == PrimDef::Bool.id()) {
let op = if ndarray_dtype.obj_id(&ctx.unifier).is_some_and(|id| id == PrimDef::Bool.id()) {
if op == ast::Unaryop::Invert {
ast::Unaryop::Not
} else {
@ -1803,15 +1788,15 @@ pub fn gen_unaryop_expr_with_values<'ctx, G: CodeGenerator>(
let mapped_ndarray = ndarray.map(
generator,
ctx,
NDArrayOut::NewNDArray { dtype: ndarray.dtype },
NDArrayOut::NewNDArray { dtype: ndarray.get_type().element_type() },
|generator, ctx, scalar| {
gen_unaryop_expr_with_values(generator, ctx, op, (&Some(ndarray.dtype), scalar))?
gen_unaryop_expr_with_values(generator, ctx, op, (&Some(ndarray_dtype), scalar))?
.map(|val| val.to_basic_value_enum(ctx, generator, ndarray_dtype))
.unwrap()
.to_basic_value_enum(ctx, generator, ndarray.dtype)
},
)?;
ValueEnum::Dynamic(mapped_ndarray.instance.value.as_basic_value_enum())
mapped_ndarray.as_base_value().into()
} else {
unimplemented!()
}))
@ -1858,19 +1843,25 @@ pub fn gen_cmpop_expr_with_values<'ctx, G: CodeGenerator>(
let (Some(right_ty), right) = comparators[0] else { codegen_unreachable!(ctx) };
let op = ops[0];
let left = AnyObject { value: left, ty: left_ty };
let left =
ScalarOrNDArray::split_object(generator, ctx, left).to_ndarray(generator, ctx);
let left_ty_dtype = arraylike_flatten_element_type(&mut ctx.unifier, left_ty);
let right_ty_dtype = arraylike_flatten_element_type(&mut ctx.unifier, right_ty);
let right = AnyObject { value: right, ty: right_ty };
let right =
ScalarOrNDArray::split_object(generator, ctx, right).to_ndarray(generator, ctx);
let left = ScalarOrNDArray::from_value(generator, ctx, (left_ty, left))
.to_ndarray(generator, ctx);
let right = ScalarOrNDArray::from_value(generator, ctx, (right_ty, right))
.to_ndarray(generator, ctx);
let result_ndarray = NDArrayObject::broadcast_starmap(
let result_ndarray = NDArrayType::new_broadcast(
generator,
ctx.ctx,
ctx.ctx.i8_type().into(),
&[left.get_type(), right.get_type()],
)
.broadcast_starmap(
generator,
ctx,
&[left, right],
NDArrayOut::NewNDArray { dtype: ctx.primitives.bool },
NDArrayOut::NewNDArray { dtype: ctx.ctx.i8_type().into() },
|generator, ctx, scalars| {
let left_scalar = scalars[0];
let right_scalar = scalars[1];
@ -1878,9 +1869,9 @@ pub fn gen_cmpop_expr_with_values<'ctx, G: CodeGenerator>(
let val = gen_cmpop_expr_with_values(
generator,
ctx,
(Some(left.dtype), left_scalar),
(Some(left_ty_dtype), left_scalar),
&[op],
&[(Some(right.dtype), right_scalar)],
&[(Some(right_ty_dtype), right_scalar)],
)?
.unwrap()
.to_basic_value_enum(
@ -1893,11 +1884,11 @@ pub fn gen_cmpop_expr_with_values<'ctx, G: CodeGenerator>(
},
)?;
return Ok(Some(result_ndarray.instance.value.into()));
return Ok(Some(result_ndarray.as_base_value().into()));
}
}
let cmp_val = izip!(chain(once(&left), comparators.iter()), comparators.iter(), ops.iter(),)
let cmp_val = izip!(once(&left).chain(comparators.iter()), comparators.iter(), ops.iter(),)
.fold(Ok(None), |prev: Result<Option<_>, String>, (lhs, rhs, op)| {
let (left_ty, lhs) = lhs;
let (right_ty, rhs) = rhs;
@ -1977,111 +1968,43 @@ pub fn gen_cmpop_expr_with_values<'ctx, G: CodeGenerator>(
} else if left_ty == ctx.primitives.str {
assert!(ctx.unifier.unioned(left_ty, right_ty));
let llvm_i1 = ctx.ctx.bool_type();
let llvm_i32 = ctx.ctx.i32_type();
let llvm_usize = generator.get_size_type(ctx.ctx);
let lhs = lhs.into_struct_value();
let rhs = rhs.into_struct_value();
let llvm_i32 = ctx.ctx.i32_type();
let llvm_usize = generator.get_size_type(ctx.ctx);
let plhs = generator.gen_var_alloc(ctx, lhs.get_type().into(), None).unwrap();
ctx.builder.build_store(plhs, lhs).unwrap();
let prhs = generator.gen_var_alloc(ctx, lhs.get_type().into(), None).unwrap();
ctx.builder.build_store(prhs, rhs).unwrap();
let lhs_ptr = ctx.build_in_bounds_gep_and_load(
plhs,
&[llvm_usize.const_zero(), llvm_i32.const_zero()],
None,
).into_pointer_value();
let lhs_len = ctx.build_in_bounds_gep_and_load(
plhs,
&[llvm_i32.const_zero(), llvm_i32.const_int(1, false)],
&[llvm_usize.const_zero(), llvm_i32.const_int(1, false)],
None,
).into_int_value();
let rhs_ptr = ctx.build_in_bounds_gep_and_load(
prhs,
&[llvm_usize.const_zero(), llvm_i32.const_zero()],
None,
).into_pointer_value();
let rhs_len = ctx.build_in_bounds_gep_and_load(
prhs,
&[llvm_i32.const_zero(), llvm_i32.const_int(1, false)],
&[llvm_usize.const_zero(), llvm_i32.const_int(1, false)],
None,
).into_int_value();
let len = call_int_umin(ctx, lhs_len, rhs_len, None);
let current_bb = ctx.builder.get_insert_block().unwrap();
let post_foreach_cmp = ctx.ctx.insert_basic_block_after(current_bb, "foreach.cmp.end");
ctx.builder.position_at_end(post_foreach_cmp);
let cmp_phi = ctx.builder.build_phi(llvm_i1, "").unwrap();
ctx.builder.position_at_end(current_bb);
gen_for_callback_incrementing(
generator,
ctx,
None,
llvm_usize.const_zero(),
(len, false),
|generator, ctx, _, i| {
let lhs_char = {
let plhs_data = ctx.build_in_bounds_gep_and_load(
plhs,
&[llvm_i32.const_zero(), llvm_i32.const_zero()],
None,
).into_pointer_value();
ctx.build_in_bounds_gep_and_load(
plhs_data,
&[i],
None
).into_int_value()
};
let rhs_char = {
let prhs_data = ctx.build_in_bounds_gep_and_load(
prhs,
&[llvm_i32.const_zero(), llvm_i32.const_zero()],
None,
).into_pointer_value();
ctx.build_in_bounds_gep_and_load(
prhs_data,
&[i],
None
).into_int_value()
};
gen_if_callback(
generator,
ctx,
|_, ctx| {
Ok(ctx.builder.build_int_compare(IntPredicate::NE, lhs_char, rhs_char, "").unwrap())
},
|_, ctx| {
let bb = ctx.builder.get_insert_block().unwrap();
cmp_phi.add_incoming(&[(&llvm_i1.const_zero(), bb)]);
ctx.builder.build_unconditional_branch(post_foreach_cmp).unwrap();
Ok(())
},
|_, _| Ok(()),
)?;
Ok(())
},
llvm_usize.const_int(1, false),
)?;
let bb = ctx.builder.get_insert_block().unwrap();
let is_len_eq = ctx.builder.build_int_compare(
IntPredicate::EQ,
lhs_len,
rhs_len,
"",
).unwrap();
cmp_phi.add_incoming(&[(&is_len_eq, bb)]);
ctx.builder.build_unconditional_branch(post_foreach_cmp).unwrap();
ctx.builder.position_at_end(post_foreach_cmp);
let cmp_phi = cmp_phi.as_basic_value().into_int_value();
// Invert the final value if __ne__
let result = call_string_eq(generator, ctx, lhs_ptr, lhs_len, rhs_ptr, rhs_len);
if *op == Cmpop::NotEq {
ctx.builder.build_not(cmp_phi, "").unwrap()
ctx.builder.build_not(result, "").unwrap()
} else {
cmp_phi
result
}
} else if [left_ty, right_ty]
.iter()
@ -2135,9 +2058,9 @@ pub fn gen_cmpop_expr_with_values<'ctx, G: CodeGenerator>(
}
let left_val =
ListValue::from_ptr_val(lhs.into_pointer_value(), llvm_usize, None);
ListValue::from_pointer_value(lhs.into_pointer_value(), llvm_usize, None);
let right_val =
ListValue::from_ptr_val(rhs.into_pointer_value(), llvm_usize, None);
ListValue::from_pointer_value(rhs.into_pointer_value(), llvm_usize, None);
Ok(gen_if_else_expr_callback(
generator,
@ -2558,7 +2481,20 @@ pub fn gen_expr<'ctx, G: CodeGenerator>(
Some(elements[0].get_type())
};
let length = generator.get_size_type(ctx.ctx).const_int(elements.len() as u64, false);
let arr_str_ptr = allocate_list(generator, ctx, ty, length, Some("list"));
let arr_str_ptr = if let Some(ty) = ty {
ListType::new(generator, ctx.ctx, ty).construct(
generator,
ctx,
length,
Some("list"),
)
} else {
ListType::new_untyped(generator, ctx.ctx).construct_empty(
generator,
ctx,
Some("list"),
)
};
let arr_ptr = arr_str_ptr.data();
for (i, v) in elements.iter().enumerate() {
let elem_ptr = arr_ptr.ptr_offset(
@ -2710,48 +2646,53 @@ pub fn gen_expr<'ctx, G: CodeGenerator>(
};
let left = generator.bool_to_i1(ctx, left);
let current = ctx.builder.get_insert_block().unwrap().get_parent().unwrap();
let a_bb = ctx.ctx.append_basic_block(current, "a");
let b_bb = ctx.ctx.append_basic_block(current, "b");
let a_begin_bb = ctx.ctx.append_basic_block(current, "a_begin");
let a_end_bb = ctx.ctx.append_basic_block(current, "a_end");
let b_begin_bb = ctx.ctx.append_basic_block(current, "b_begin");
let b_end_bb = ctx.ctx.append_basic_block(current, "b_end");
let cont_bb = ctx.ctx.append_basic_block(current, "cont");
ctx.builder.build_conditional_branch(left, a_bb, b_bb).unwrap();
ctx.builder.build_conditional_branch(left, a_begin_bb, b_begin_bb).unwrap();
ctx.builder.position_at_end(a_end_bb);
ctx.builder.build_unconditional_branch(cont_bb).unwrap();
ctx.builder.position_at_end(b_end_bb);
ctx.builder.build_unconditional_branch(cont_bb).unwrap();
let (a, b) = match op {
Boolop::Or => {
ctx.builder.position_at_end(a_bb);
ctx.builder.position_at_end(a_begin_bb);
let a = ctx.ctx.i8_type().const_int(1, false);
ctx.builder.build_unconditional_branch(cont_bb).unwrap();
ctx.builder.build_unconditional_branch(a_end_bb).unwrap();
ctx.builder.position_at_end(b_bb);
ctx.builder.position_at_end(b_begin_bb);
let b = if let Some(v) = generator.gen_expr(ctx, &values[1])? {
let b = v
.to_basic_value_enum(ctx, generator, values[1].custom.unwrap())?
.into_int_value();
let b = generator.bool_to_i8(ctx, b);
ctx.builder.build_unconditional_branch(cont_bb).unwrap();
Some(b)
} else {
None
};
ctx.builder.build_unconditional_branch(b_end_bb).unwrap();
(Some(a), b)
}
Boolop::And => {
ctx.builder.position_at_end(a_bb);
ctx.builder.position_at_end(a_begin_bb);
let a = if let Some(v) = generator.gen_expr(ctx, &values[1])? {
let a = v
.to_basic_value_enum(ctx, generator, values[1].custom.unwrap())?
.into_int_value();
let a = generator.bool_to_i8(ctx, a);
ctx.builder.build_unconditional_branch(cont_bb).unwrap();
Some(a)
} else {
None
};
ctx.builder.build_unconditional_branch(a_end_bb).unwrap();
ctx.builder.position_at_end(b_bb);
ctx.builder.position_at_end(b_begin_bb);
let b = ctx.ctx.i8_type().const_zero();
ctx.builder.build_unconditional_branch(cont_bb).unwrap();
ctx.builder.build_unconditional_branch(b_end_bb).unwrap();
(a, Some(b))
}
@ -2761,7 +2702,7 @@ pub fn gen_expr<'ctx, G: CodeGenerator>(
match (a, b) {
(Some(a), Some(b)) => {
let phi = ctx.builder.build_phi(ctx.ctx.i8_type(), "").unwrap();
phi.add_incoming(&[(&a, a_bb), (&b, b_bb)]);
phi.add_incoming(&[(&a, a_end_bb), (&b, b_end_bb)]);
phi.as_basic_value().into()
}
(Some(a), None) => a.into(),
@ -2999,7 +2940,7 @@ pub fn gen_expr<'ctx, G: CodeGenerator>(
} else {
return Ok(None);
};
let v = ListValue::from_ptr_val(v, usize, Some("arr"));
let v = ListValue::from_pointer_value(v, usize, Some("arr"));
let ty = ctx.get_llvm_type(generator, *ty);
if let ExprKind::Slice { lower, upper, step } = &slice.node {
let one = int32.const_int(1, false);
@ -3031,8 +2972,12 @@ pub fn gen_expr<'ctx, G: CodeGenerator>(
.unwrap(),
step,
);
let res_array_ret =
allocate_list(generator, ctx, Some(ty), length, Some("ret"));
let res_array_ret = ListType::new(generator, ctx.ctx, ty).construct(
generator,
ctx,
length,
Some("ret"),
);
let Some(res_ind) = handle_slice_indices(
&None,
&None,
@ -3107,14 +3052,10 @@ pub fn gen_expr<'ctx, G: CodeGenerator>(
let ndarray_ty = value.custom.unwrap();
let ndarray = ndarray.to_basic_value_enum(ctx, generator, ndarray_ty)?;
let ndarray = NDArrayType::from_unifier_type(generator, ctx, ndarray_ty)
.map_value(ndarray.into_pointer_value(), None);
let ndarray = NDArrayObject::from_object(
generator,
ctx,
AnyObject { ty: ndarray_ty, value: ndarray },
);
let indices = gen_ndarray_subscript_ndindices(generator, ctx, slice)?;
let indices = RustNDIndex::from_subscript_expr(generator, ctx, slice)?;
let result = ndarray
.index(generator, ctx, &indices)
.split_unsized(generator, ctx)
@ -3168,3 +3109,97 @@ pub fn gen_expr<'ctx, G: CodeGenerator>(
_ => unimplemented!(),
}))
}
/// Creates a function in the current module and inserts a `call` instruction into the LLVM IR.
#[allow(clippy::too_many_arguments)]
pub fn create_fn_and_call<'ctx>(
ctx: &CodeGenContext<'ctx, '_>,
fn_name: &str,
ret_type: Option<BasicTypeEnum<'ctx>>,
params: &[BasicTypeEnum<'ctx>],
args: &[BasicValueEnum<'ctx>],
is_var_args: bool,
call_value_name: Option<&str>,
configure: Option<&dyn Fn(&FunctionValue<'ctx>)>,
) -> Option<BasicValueEnum<'ctx>> {
let intrinsic_fn = ctx.module.get_function(fn_name).unwrap_or_else(|| {
let params = params.iter().copied().map(BasicTypeEnum::into).collect_vec();
let fn_type = if let Some(ret_type) = ret_type {
ret_type.fn_type(params.as_slice(), is_var_args)
} else {
ctx.ctx.void_type().fn_type(params.as_slice(), is_var_args)
};
ctx.module.add_function(fn_name, fn_type, None)
});
if let Some(configure) = configure {
configure(&intrinsic_fn);
}
let args = args.iter().copied().map(BasicValueEnum::into).collect_vec();
ctx.builder
.build_call(intrinsic_fn, args.as_slice(), call_value_name.unwrap_or_default())
.map(CallSiteValue::try_as_basic_value)
.map(Either::left)
.unwrap()
}
/// Creates a function in the current module and inserts a `call` instruction into the LLVM IR.
///
/// This is a wrapper around [`create_fn_and_call`] for non-vararg function. This function allows
/// parameters and arguments to be specified as tuples to better indicate the expected type and
/// actual value of each parameter-argument pair of the call.
pub fn create_and_call_function<'ctx>(
ctx: &CodeGenContext<'ctx, '_>,
fn_name: &str,
ret_type: Option<BasicTypeEnum<'ctx>>,
params: &[(BasicTypeEnum<'ctx>, BasicValueEnum<'ctx>)],
value_name: Option<&str>,
configure: Option<&dyn Fn(&FunctionValue<'ctx>)>,
) -> Option<BasicValueEnum<'ctx>> {
let param_tys = params.iter().map(|(ty, _)| ty).copied().map(BasicTypeEnum::into).collect_vec();
let arg_values =
params.iter().map(|(_, value)| value).copied().map(BasicValueEnum::into).collect_vec();
create_fn_and_call(
ctx,
fn_name,
ret_type,
param_tys.as_slice(),
arg_values.as_slice(),
false,
value_name,
configure,
)
}
/// Creates a function in the current module and inserts a `call` instruction into the LLVM IR.
///
/// This is a wrapper around [`create_fn_and_call`] for non-vararg function. This function allows
/// only arguments to be specified and performs inference for the parameter types of the function
/// using [`BasicValueEnum::get_type`] on the arguments.
///
/// This function is recommended if it is known that all function arguments match the parameter
/// types of the invoked function.
pub fn infer_and_call_function<'ctx>(
ctx: &CodeGenContext<'ctx, '_>,
fn_name: &str,
ret_type: Option<BasicTypeEnum<'ctx>>,
args: &[BasicValueEnum<'ctx>],
value_name: Option<&str>,
configure: Option<&dyn Fn(&FunctionValue<'ctx>)>,
) -> Option<BasicValueEnum<'ctx>> {
let param_tys = args.iter().map(BasicValueEnum::get_type).collect_vec();
create_fn_and_call(
ctx,
fn_name,
ret_type,
param_tys.as_slice(),
args,
false,
value_name,
configure,
)
}

View File

@ -6,7 +6,7 @@ use inkwell::{
use nac3parser::ast::{Expr, Stmt, StrRef};
use super::{bool_to_i1, bool_to_i8, classes::ArraySliceValue, expr::*, stmt::*, CodeGenContext};
use super::{bool_to_i1, bool_to_i8, expr::*, stmt::*, values::ArraySliceValue, CodeGenContext};
use crate::{
symbol_resolver::ValueEnum,
toplevel::{DefinitionId, TopLevelDef},
@ -17,6 +17,7 @@ pub trait CodeGenerator {
/// Return the module name for the code generator.
fn get_name(&self) -> &str;
/// Return an instance of [`IntType`] corresponding to the type of `size_t` for this instance.
fn get_size_type<'ctx>(&self, ctx: &'ctx Context) -> IntType<'ctx>;
/// Generate function call and returns the function return value.

View File

@ -0,0 +1,174 @@
use inkwell::{
types::BasicTypeEnum,
values::{BasicValueEnum, CallSiteValue, IntValue},
AddressSpace, IntPredicate,
};
use itertools::Either;
use super::calculate_len_for_slice_range;
use crate::codegen::{
macros::codegen_unreachable,
values::{ArrayLikeValue, ListValue},
CodeGenContext, CodeGenerator,
};
/// This function handles 'end' **inclusively**.
/// Order of tuples `assign_idx` and `value_idx` is ('start', 'end', 'step').
/// Negative index should be handled before entering this function
pub fn list_slice_assignment<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
ty: BasicTypeEnum<'ctx>,
dest_arr: ListValue<'ctx>,
dest_idx: (IntValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>),
src_arr: ListValue<'ctx>,
src_idx: (IntValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>),
) {
let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_pi8 = ctx.ctx.i8_type().ptr_type(AddressSpace::default());
let llvm_i32 = ctx.ctx.i32_type();
assert_eq!(dest_idx.0.get_type(), llvm_i32);
assert_eq!(dest_idx.1.get_type(), llvm_i32);
assert_eq!(dest_idx.2.get_type(), llvm_i32);
assert_eq!(src_idx.0.get_type(), llvm_i32);
assert_eq!(src_idx.1.get_type(), llvm_i32);
assert_eq!(src_idx.2.get_type(), llvm_i32);
let (fun_symbol, elem_ptr_type) = ("__nac3_list_slice_assign_var_size", llvm_pi8);
let slice_assign_fun = {
let ty_vec = vec![
llvm_i32.into(), // dest start idx
llvm_i32.into(), // dest end idx
llvm_i32.into(), // dest step
elem_ptr_type.into(), // dest arr ptr
llvm_i32.into(), // dest arr len
llvm_i32.into(), // src start idx
llvm_i32.into(), // src end idx
llvm_i32.into(), // src step
elem_ptr_type.into(), // src arr ptr
llvm_i32.into(), // src arr len
llvm_i32.into(), // size
];
ctx.module.get_function(fun_symbol).unwrap_or_else(|| {
let fn_t = llvm_i32.fn_type(ty_vec.as_slice(), false);
ctx.module.add_function(fun_symbol, fn_t, None)
})
};
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 =
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 =
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 =
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 =
ctx.builder.build_int_truncate_or_bit_cast(src_len, llvm_i32, "srclen32").unwrap();
// index in bound and positive should be done
// assert if dest.step == 1 then len(src) <= len(dest) else len(src) == len(dest), and
// throw exception if not satisfied
let src_end = ctx
.builder
.build_select(
ctx.builder.build_int_compare(IntPredicate::SLT, src_idx.2, zero, "is_neg").unwrap(),
ctx.builder.build_int_sub(src_idx.1, one, "e_min_one").unwrap(),
ctx.builder.build_int_add(src_idx.1, one, "e_add_one").unwrap(),
"final_e",
)
.map(BasicValueEnum::into_int_value)
.unwrap();
let dest_end = ctx
.builder
.build_select(
ctx.builder.build_int_compare(IntPredicate::SLT, dest_idx.2, zero, "is_neg").unwrap(),
ctx.builder.build_int_sub(dest_idx.1, one, "e_min_one").unwrap(),
ctx.builder.build_int_add(dest_idx.1, one, "e_add_one").unwrap(),
"final_e",
)
.map(BasicValueEnum::into_int_value)
.unwrap();
let src_slice_len =
calculate_len_for_slice_range(generator, ctx, src_idx.0, src_end, src_idx.2);
let dest_slice_len =
calculate_len_for_slice_range(generator, ctx, dest_idx.0, dest_end, dest_idx.2);
let src_eq_dest = ctx
.builder
.build_int_compare(IntPredicate::EQ, src_slice_len, dest_slice_len, "slice_src_eq_dest")
.unwrap();
let src_slt_dest = ctx
.builder
.build_int_compare(IntPredicate::SLT, src_slice_len, dest_slice_len, "slice_src_slt_dest")
.unwrap();
let dest_step_eq_one = ctx
.builder
.build_int_compare(
IntPredicate::EQ,
dest_idx.2,
dest_idx.2.get_type().const_int(1, false),
"slice_dest_step_eq_one",
)
.unwrap();
let cond_1 = ctx.builder.build_and(dest_step_eq_one, src_slt_dest, "slice_cond_1").unwrap();
let cond = ctx.builder.build_or(src_eq_dest, cond_1, "slice_cond").unwrap();
ctx.make_assert(
generator,
cond,
"0:ValueError",
"attempt to assign sequence of size {0} to slice of size {1} with step size {2}",
[Some(src_slice_len), Some(dest_slice_len), Some(dest_idx.2)],
ctx.current_loc,
);
let new_len = {
let args = vec![
dest_idx.0.into(), // dest start idx
dest_idx.1.into(), // dest end idx
dest_idx.2.into(), // dest step
dest_arr_ptr.into(), // dest arr ptr
dest_len.into(), // dest arr len
src_idx.0.into(), // src start idx
src_idx.1.into(), // src end idx
src_idx.2.into(), // src step
src_arr_ptr.into(), // src arr ptr
src_len.into(), // src arr len
{
let s = match ty {
BasicTypeEnum::FloatType(t) => t.size_of(),
BasicTypeEnum::IntType(t) => t.size_of(),
BasicTypeEnum::PointerType(t) => t.size_of(),
BasicTypeEnum::StructType(t) => t.size_of().unwrap(),
_ => codegen_unreachable!(ctx),
};
ctx.builder.build_int_truncate_or_bit_cast(s, llvm_i32, "size").unwrap()
}
.into(),
];
ctx.builder
.build_call(slice_assign_fun, args.as_slice(), "slice_assign")
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_int_value))
.map(Either::unwrap_left)
.unwrap()
};
// update length
let need_update =
ctx.builder.build_int_compare(IntPredicate::NE, new_len, dest_len, "need_update").unwrap();
let current = ctx.builder.get_insert_block().unwrap().get_parent().unwrap();
let update_bb = ctx.ctx.append_basic_block(current, "update");
let cont_bb = ctx.ctx.append_basic_block(current, "cont");
ctx.builder.build_conditional_branch(need_update, update_bb, cont_bb).unwrap();
ctx.builder.position_at_end(update_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, generator, new_len);
ctx.builder.build_unconditional_branch(cont_bb).unwrap();
ctx.builder.position_at_end(cont_bb);
}

View File

@ -0,0 +1,168 @@
use inkwell::{
values::{BasicValueEnum, CallSiteValue, FloatValue, IntValue},
IntPredicate,
};
use itertools::Either;
use crate::codegen::{
macros::codegen_unreachable,
{CodeGenContext, CodeGenerator},
};
// repeated squaring method adapted from GNU Scientific Library:
// https://git.savannah.gnu.org/cgit/gsl.git/tree/sys/pow_int.c
pub fn integer_power<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
base: IntValue<'ctx>,
exp: IntValue<'ctx>,
signed: bool,
) -> IntValue<'ctx> {
let symbol = match (base.get_type().get_bit_width(), exp.get_type().get_bit_width(), signed) {
(32, 32, true) => "__nac3_int_exp_int32_t",
(64, 64, true) => "__nac3_int_exp_int64_t",
(32, 32, false) => "__nac3_int_exp_uint32_t",
(64, 64, false) => "__nac3_int_exp_uint64_t",
_ => 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
let ge_zero = ctx
.builder
.build_int_compare(
IntPredicate::SGE,
exp,
exp.get_type().const_zero(),
"assert_int_pow_ge_0",
)
.unwrap();
ctx.make_assert(
generator,
ge_zero,
"0:ValueError",
"integer power must be positive or zero",
[None, None, None],
ctx.current_loc,
);
ctx.builder
.build_call(pow_fun, &[base.into(), exp.into()], "call_int_pow")
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_int_value))
.map(Either::unwrap_left)
.unwrap()
}
/// Generates a call to `isinf` in IR. Returns an `i1` representing the result.
pub fn call_isinf<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
v: FloatValue<'ctx>,
) -> IntValue<'ctx> {
let llvm_i32 = ctx.ctx.i32_type();
let llvm_f64 = ctx.ctx.f64_type();
assert_eq!(v.get_type(), llvm_f64);
let intrinsic_fn = ctx.module.get_function("__nac3_isinf").unwrap_or_else(|| {
let fn_type = llvm_i32.fn_type(&[llvm_f64.into()], false);
ctx.module.add_function("__nac3_isinf", fn_type, None)
});
let ret = ctx
.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)
}
/// Generates a call to `isnan` in IR. Returns an `i1` representing the result.
pub fn call_isnan<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
v: FloatValue<'ctx>,
) -> IntValue<'ctx> {
let llvm_i32 = ctx.ctx.i32_type();
let llvm_f64 = ctx.ctx.f64_type();
assert_eq!(v.get_type(), llvm_f64);
let intrinsic_fn = ctx.module.get_function("__nac3_isnan").unwrap_or_else(|| {
let fn_type = llvm_i32.fn_type(&[llvm_f64.into()], false);
ctx.module.add_function("__nac3_isnan", fn_type, None)
});
let ret = ctx
.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)
}
/// 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> {
let llvm_f64 = ctx.ctx.f64_type();
assert_eq!(v.get_type(), llvm_f64);
let intrinsic_fn = ctx.module.get_function("__nac3_gamma").unwrap_or_else(|| {
let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false);
ctx.module.add_function("__nac3_gamma", fn_type, None)
});
ctx.builder
.build_call(intrinsic_fn, &[v.into()], "gamma")
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_float_value))
.map(Either::unwrap_left)
.unwrap()
}
/// Generates a call to `gammaln` in IR. Returns an `f64` representing the result.
pub fn call_gammaln<'ctx>(ctx: &CodeGenContext<'ctx, '_>, v: FloatValue<'ctx>) -> FloatValue<'ctx> {
let llvm_f64 = ctx.ctx.f64_type();
assert_eq!(v.get_type(), llvm_f64);
let intrinsic_fn = ctx.module.get_function("__nac3_gammaln").unwrap_or_else(|| {
let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false);
ctx.module.add_function("__nac3_gammaln", fn_type, None)
});
ctx.builder
.build_call(intrinsic_fn, &[v.into()], "gammaln")
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_float_value))
.map(Either::unwrap_left)
.unwrap()
}
/// Generates a call to `j0` in IR. Returns an `f64` representing the result.
pub fn call_j0<'ctx>(ctx: &CodeGenContext<'ctx, '_>, v: FloatValue<'ctx>) -> FloatValue<'ctx> {
let llvm_f64 = ctx.ctx.f64_type();
assert_eq!(v.get_type(), llvm_f64);
let intrinsic_fn = ctx.module.get_function("__nac3_j0").unwrap_or_else(|| {
let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false);
ctx.module.add_function("__nac3_j0", fn_type, None)
});
ctx.builder
.build_call(intrinsic_fn, &[v.into()], "j0")
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_float_value))
.map(Either::unwrap_left)
.unwrap()
}

View File

@ -3,25 +3,26 @@ use inkwell::{
context::Context,
memory_buffer::MemoryBuffer,
module::Module,
types::BasicTypeEnum,
values::{BasicValue, BasicValueEnum, CallSiteValue, FloatValue, IntValue},
AddressSpace, IntPredicate,
values::{BasicValue, BasicValueEnum, IntValue},
IntPredicate,
};
use itertools::Either;
use nac3parser::ast::Expr;
use super::{
classes::{ArrayLikeValue, ListValue},
macros::codegen_unreachable,
model::{function::FnCall, *},
object::{
list::List,
ndarray::{broadcast::ShapeEntry, indexing::NDIndex, nditer::NDIter, NDArray},
},
CodeGenContext, CodeGenerator,
};
use super::{CodeGenContext, CodeGenerator};
use crate::{symbol_resolver::SymbolResolver, typecheck::typedef::Type};
pub use list::*;
pub use math::*;
pub use range::*;
pub use slice::*;
pub use string::*;
mod list;
mod math;
pub mod ndarray;
mod range;
mod slice;
mod string;
#[must_use]
pub fn load_irrt<'ctx>(ctx: &'ctx Context, symbol_resolver: &dyn SymbolResolver) -> Module<'ctx> {
@ -62,86 +63,25 @@ pub fn load_irrt<'ctx>(ctx: &'ctx Context, symbol_resolver: &dyn SymbolResolver)
irrt_mod
}
// repeated squaring method adapted from GNU Scientific Library:
// https://git.savannah.gnu.org/cgit/gsl.git/tree/sys/pow_int.c
pub fn integer_power<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
base: IntValue<'ctx>,
exp: IntValue<'ctx>,
signed: bool,
) -> IntValue<'ctx> {
let symbol = match (base.get_type().get_bit_width(), exp.get_type().get_bit_width(), signed) {
(32, 32, true) => "__nac3_int_exp_int32_t",
(64, 64, true) => "__nac3_int_exp_int64_t",
(32, 32, false) => "__nac3_int_exp_uint32_t",
(64, 64, false) => "__nac3_int_exp_uint64_t",
_ => 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
let ge_zero = ctx
.builder
.build_int_compare(
IntPredicate::SGE,
exp,
exp.get_type().const_zero(),
"assert_int_pow_ge_0",
)
.unwrap();
ctx.make_assert(
generator,
ge_zero,
"0:ValueError",
"integer power must be positive or zero",
[None, None, None],
ctx.current_loc,
);
ctx.builder
.build_call(pow_fun, &[base.into(), exp.into()], "call_int_pow")
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_int_value))
.map(Either::unwrap_left)
.unwrap()
}
pub fn calculate_len_for_slice_range<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
start: IntValue<'ctx>,
end: IntValue<'ctx>,
step: IntValue<'ctx>,
) -> IntValue<'ctx> {
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 fn_t = i32_t.fn_type(&[i32_t.into(), i32_t.into(), i32_t.into()], false);
ctx.module.add_function(SYMBOL, fn_t, None)
});
// assert step != 0, throw exception if not
let not_zero = ctx
.builder
.build_int_compare(IntPredicate::NE, step, step.get_type().const_zero(), "range_step_ne")
.unwrap();
ctx.make_assert(
generator,
not_zero,
"0:ValueError",
"step must not be zero",
[None, None, None],
ctx.current_loc,
);
ctx.builder
.build_call(len_func, &[start.into(), end.into(), step.into()], "calc_len")
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_int_value))
.map(Either::unwrap_left)
.unwrap()
/// Returns the name of a function which contains variants for 32-bit and 64-bit `size_t`.
///
/// - 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`.
#[must_use]
pub fn get_usize_dependent_function_name<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &CodeGenContext<'_, '_>,
name: &str,
) -> String {
let mut name = name.to_owned();
match generator.get_size_type(ctx.ctx).get_bit_width() {
32 => {}
64 => name.push_str("64"),
bit_width => {
panic!("Unsupported int type bit width {bit_width}, must be either 32-bits or 64-bits")
}
}
name
}
/// NOTE: the output value of the end index of this function should be compared ***inclusively***,
@ -192,10 +132,11 @@ pub fn handle_slice_indices<'ctx, G: CodeGenerator>(
generator: &mut G,
length: IntValue<'ctx>,
) -> Result<Option<(IntValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>)>, String> {
let int32 = ctx.ctx.i32_type();
let zero = int32.const_zero();
let one = int32.const_int(1, false);
let length = ctx.builder.build_int_truncate_or_bit_cast(length, int32, "leni32").unwrap();
let llvm_i32 = ctx.ctx.i32_type();
let zero = llvm_i32.const_zero();
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) {
(s, e, None) => (
if let Some(s) = s.as_ref() {
@ -204,7 +145,7 @@ pub fn handle_slice_indices<'ctx, G: CodeGenerator>(
None => return Ok(None),
}
} else {
int32.const_zero()
llvm_i32.const_zero()
},
{
let e = if let Some(s) = e.as_ref() {
@ -309,569 +250,3 @@ pub fn handle_slice_indices<'ctx, G: CodeGenerator>(
}
}))
}
/// this function allows index out of range, since python
/// allows index out of range in slice (`a = [1,2,3]; a[1:10] == [2,3]`).
pub fn handle_slice_index_bound<'ctx, G: CodeGenerator>(
i: &Expr<Option<Type>>,
ctx: &mut CodeGenContext<'ctx, '_>,
generator: &mut G,
length: IntValue<'ctx>,
) -> Result<Option<IntValue<'ctx>>, String> {
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 fn_t = i32_t.fn_type(&[i32_t.into(), i32_t.into()], false);
ctx.module.add_function(SYMBOL, fn_t, None)
});
let i = if let Some(v) = generator.gen_expr(ctx, i)? {
v.to_basic_value_enum(ctx, generator, i.custom.unwrap())?
} else {
return Ok(None);
};
Ok(Some(
ctx.builder
.build_call(func, &[i.into(), length.into()], "bounded_ind")
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_int_value))
.map(Either::unwrap_left)
.unwrap(),
))
}
/// This function handles 'end' **inclusively**.
/// Order of tuples `assign_idx` and `value_idx` is ('start', 'end', 'step').
/// Negative index should be handled before entering this function
pub fn list_slice_assignment<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
ty: BasicTypeEnum<'ctx>,
dest_arr: ListValue<'ctx>,
dest_idx: (IntValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>),
src_arr: ListValue<'ctx>,
src_idx: (IntValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>),
) {
let size_ty = generator.get_size_type(ctx.ctx);
let int8_ptr = ctx.ctx.i8_type().ptr_type(AddressSpace::default());
let int32 = 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();
let one = int32.const_int(1, false);
let dest_arr_ptr = dest_arr.data().base_ptr(ctx, generator);
let dest_arr_ptr =
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 = ctx.builder.build_int_truncate_or_bit_cast(dest_len, int32, "srclen32").unwrap();
let src_arr_ptr = src_arr.data().base_ptr(ctx, generator);
let src_arr_ptr =
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 = ctx.builder.build_int_truncate_or_bit_cast(src_len, int32, "srclen32").unwrap();
// index in bound and positive should be done
// assert if dest.step == 1 then len(src) <= len(dest) else len(src) == len(dest), and
// throw exception if not satisfied
let src_end = ctx
.builder
.build_select(
ctx.builder.build_int_compare(IntPredicate::SLT, src_idx.2, zero, "is_neg").unwrap(),
ctx.builder.build_int_sub(src_idx.1, one, "e_min_one").unwrap(),
ctx.builder.build_int_add(src_idx.1, one, "e_add_one").unwrap(),
"final_e",
)
.map(BasicValueEnum::into_int_value)
.unwrap();
let dest_end = ctx
.builder
.build_select(
ctx.builder.build_int_compare(IntPredicate::SLT, dest_idx.2, zero, "is_neg").unwrap(),
ctx.builder.build_int_sub(dest_idx.1, one, "e_min_one").unwrap(),
ctx.builder.build_int_add(dest_idx.1, one, "e_add_one").unwrap(),
"final_e",
)
.map(BasicValueEnum::into_int_value)
.unwrap();
let src_slice_len =
calculate_len_for_slice_range(generator, ctx, src_idx.0, src_end, src_idx.2);
let dest_slice_len =
calculate_len_for_slice_range(generator, ctx, dest_idx.0, dest_end, dest_idx.2);
let src_eq_dest = ctx
.builder
.build_int_compare(IntPredicate::EQ, src_slice_len, dest_slice_len, "slice_src_eq_dest")
.unwrap();
let src_slt_dest = ctx
.builder
.build_int_compare(IntPredicate::SLT, src_slice_len, dest_slice_len, "slice_src_slt_dest")
.unwrap();
let dest_step_eq_one = ctx
.builder
.build_int_compare(
IntPredicate::EQ,
dest_idx.2,
dest_idx.2.get_type().const_int(1, false),
"slice_dest_step_eq_one",
)
.unwrap();
let cond_1 = ctx.builder.build_and(dest_step_eq_one, src_slt_dest, "slice_cond_1").unwrap();
let cond = ctx.builder.build_or(src_eq_dest, cond_1, "slice_cond").unwrap();
ctx.make_assert(
generator,
cond,
"0:ValueError",
"attempt to assign sequence of size {0} to slice of size {1} with step size {2}",
[Some(src_slice_len), Some(dest_slice_len), Some(dest_idx.2)],
ctx.current_loc,
);
let new_len = {
let args = vec![
dest_idx.0.into(), // dest start idx
dest_idx.1.into(), // dest end idx
dest_idx.2.into(), // dest step
dest_arr_ptr.into(), // dest arr ptr
dest_len.into(), // dest arr len
src_idx.0.into(), // src start idx
src_idx.1.into(), // src end idx
src_idx.2.into(), // src step
src_arr_ptr.into(), // src arr ptr
src_len.into(), // src arr len
{
let s = match ty {
BasicTypeEnum::FloatType(t) => t.size_of(),
BasicTypeEnum::IntType(t) => t.size_of(),
BasicTypeEnum::PointerType(t) => t.size_of(),
BasicTypeEnum::StructType(t) => t.size_of().unwrap(),
_ => codegen_unreachable!(ctx),
};
ctx.builder.build_int_truncate_or_bit_cast(s, int32, "size").unwrap()
}
.into(),
];
ctx.builder
.build_call(slice_assign_fun, args.as_slice(), "slice_assign")
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_int_value))
.map(Either::unwrap_left)
.unwrap()
};
// update length
let need_update =
ctx.builder.build_int_compare(IntPredicate::NE, new_len, dest_len, "need_update").unwrap();
let current = ctx.builder.get_insert_block().unwrap().get_parent().unwrap();
let update_bb = ctx.ctx.append_basic_block(current, "update");
let cont_bb = ctx.ctx.append_basic_block(current, "cont");
ctx.builder.build_conditional_branch(need_update, update_bb, cont_bb).unwrap();
ctx.builder.position_at_end(update_bb);
let new_len = ctx.builder.build_int_z_extend_or_bit_cast(new_len, size_ty, "new_len").unwrap();
dest_arr.store_size(ctx, generator, new_len);
ctx.builder.build_unconditional_branch(cont_bb).unwrap();
ctx.builder.position_at_end(cont_bb);
}
/// Generates a call to `isinf` in IR. Returns an `i1` representing the result.
pub fn call_isinf<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
v: FloatValue<'ctx>,
) -> IntValue<'ctx> {
let intrinsic_fn = ctx.module.get_function("__nac3_isinf").unwrap_or_else(|| {
let fn_type = ctx.ctx.i32_type().fn_type(&[ctx.ctx.f64_type().into()], false);
ctx.module.add_function("__nac3_isinf", fn_type, None)
});
let ret = ctx
.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)
}
/// Generates a call to `isnan` in IR. Returns an `i1` representing the result.
pub fn call_isnan<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
v: FloatValue<'ctx>,
) -> IntValue<'ctx> {
let intrinsic_fn = ctx.module.get_function("__nac3_isnan").unwrap_or_else(|| {
let fn_type = ctx.ctx.i32_type().fn_type(&[ctx.ctx.f64_type().into()], false);
ctx.module.add_function("__nac3_isnan", fn_type, None)
});
let ret = ctx
.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)
}
/// 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> {
let llvm_f64 = ctx.ctx.f64_type();
let intrinsic_fn = ctx.module.get_function("__nac3_gamma").unwrap_or_else(|| {
let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false);
ctx.module.add_function("__nac3_gamma", fn_type, None)
});
ctx.builder
.build_call(intrinsic_fn, &[v.into()], "gamma")
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_float_value))
.map(Either::unwrap_left)
.unwrap()
}
/// Generates a call to `gammaln` in IR. Returns an `f64` representing the result.
pub fn call_gammaln<'ctx>(ctx: &CodeGenContext<'ctx, '_>, v: FloatValue<'ctx>) -> FloatValue<'ctx> {
let llvm_f64 = ctx.ctx.f64_type();
let intrinsic_fn = ctx.module.get_function("__nac3_gammaln").unwrap_or_else(|| {
let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false);
ctx.module.add_function("__nac3_gammaln", fn_type, None)
});
ctx.builder
.build_call(intrinsic_fn, &[v.into()], "gammaln")
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_float_value))
.map(Either::unwrap_left)
.unwrap()
}
/// Generates a call to `j0` in IR. Returns an `f64` representing the result.
pub fn call_j0<'ctx>(ctx: &CodeGenContext<'ctx, '_>, v: FloatValue<'ctx>) -> FloatValue<'ctx> {
let llvm_f64 = ctx.ctx.f64_type();
let intrinsic_fn = ctx.module.get_function("__nac3_j0").unwrap_or_else(|| {
let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false);
ctx.module.add_function("__nac3_j0", fn_type, None)
});
ctx.builder
.build_call(intrinsic_fn, &[v.into()], "j0")
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_float_value))
.map(Either::unwrap_left)
.unwrap()
}
// 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".
#[must_use]
pub fn get_sizet_dependent_function_name<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &CodeGenContext<'_, '_>,
name: &str,
) -> String {
let mut name = name.to_owned();
match generator.get_size_type(ctx.ctx).get_bit_width() {
32 => {}
64 => name.push_str("64"),
bit_width => {
panic!("Unsupported int type bit width {bit_width}, must be either 32-bits or 64-bits")
}
}
name
}
pub fn call_nac3_ndarray_util_assert_shape_no_negative<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
ndims: Instance<'ctx, Int<SizeT>>,
shape: Instance<'ctx, Ptr<Int<SizeT>>>,
) {
let name = get_sizet_dependent_function_name(
generator,
ctx,
"__nac3_ndarray_util_assert_shape_no_negative",
);
FnCall::builder(generator, ctx, &name).arg(ndims).arg(shape).returning_void();
}
pub fn call_nac3_ndarray_util_assert_output_shape_same<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
ndarray_ndims: Instance<'ctx, Int<SizeT>>,
ndarray_shape: Instance<'ctx, Ptr<Int<SizeT>>>,
output_ndims: Instance<'ctx, Int<SizeT>>,
output_shape: Instance<'ctx, Ptr<Int<SizeT>>>,
) {
let name = get_sizet_dependent_function_name(
generator,
ctx,
"__nac3_ndarray_util_assert_output_shape_same",
);
FnCall::builder(generator, ctx, &name)
.arg(ndarray_ndims)
.arg(ndarray_shape)
.arg(output_ndims)
.arg(output_shape)
.returning_void();
}
pub fn call_nac3_ndarray_size<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
ndarray: Instance<'ctx, Ptr<Struct<NDArray>>>,
) -> Instance<'ctx, Int<SizeT>> {
let name = get_sizet_dependent_function_name(generator, ctx, "__nac3_ndarray_size");
FnCall::builder(generator, ctx, &name).arg(ndarray).returning_auto("size")
}
pub fn call_nac3_ndarray_nbytes<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
ndarray: Instance<'ctx, Ptr<Struct<NDArray>>>,
) -> Instance<'ctx, Int<SizeT>> {
let name = get_sizet_dependent_function_name(generator, ctx, "__nac3_ndarray_nbytes");
FnCall::builder(generator, ctx, &name).arg(ndarray).returning_auto("nbytes")
}
pub fn call_nac3_ndarray_len<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
ndarray: Instance<'ctx, Ptr<Struct<NDArray>>>,
) -> Instance<'ctx, Int<SizeT>> {
let name = get_sizet_dependent_function_name(generator, ctx, "__nac3_ndarray_len");
FnCall::builder(generator, ctx, &name).arg(ndarray).returning_auto("len")
}
pub fn call_nac3_ndarray_is_c_contiguous<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
ndarray: Instance<'ctx, Ptr<Struct<NDArray>>>,
) -> Instance<'ctx, Int<Bool>> {
let name = get_sizet_dependent_function_name(generator, ctx, "__nac3_ndarray_is_c_contiguous");
FnCall::builder(generator, ctx, &name).arg(ndarray).returning_auto("is_c_contiguous")
}
pub fn call_nac3_ndarray_get_nth_pelement<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
ndarray: Instance<'ctx, Ptr<Struct<NDArray>>>,
index: Instance<'ctx, Int<SizeT>>,
) -> Instance<'ctx, Ptr<Int<Byte>>> {
let name = get_sizet_dependent_function_name(generator, ctx, "__nac3_ndarray_get_nth_pelement");
FnCall::builder(generator, ctx, &name).arg(ndarray).arg(index).returning_auto("pelement")
}
pub fn call_nac3_ndarray_get_pelement_by_indices<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
ndarray: Instance<'ctx, Ptr<Struct<NDArray>>>,
indices: Instance<'ctx, Ptr<Int<SizeT>>>,
) -> Instance<'ctx, Ptr<Int<Byte>>> {
let name =
get_sizet_dependent_function_name(generator, ctx, "__nac3_ndarray_get_pelement_by_indices");
FnCall::builder(generator, ctx, &name).arg(ndarray).arg(indices).returning_auto("pelement")
}
pub fn call_nac3_ndarray_set_strides_by_shape<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
ndarray: Instance<'ctx, Ptr<Struct<NDArray>>>,
) {
let name =
get_sizet_dependent_function_name(generator, ctx, "__nac3_ndarray_set_strides_by_shape");
FnCall::builder(generator, ctx, &name).arg(ndarray).returning_void();
}
pub fn call_nac3_ndarray_copy_data<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
src_ndarray: Instance<'ctx, Ptr<Struct<NDArray>>>,
dst_ndarray: Instance<'ctx, Ptr<Struct<NDArray>>>,
) {
let name = get_sizet_dependent_function_name(generator, ctx, "__nac3_ndarray_copy_data");
FnCall::builder(generator, ctx, &name).arg(src_ndarray).arg(dst_ndarray).returning_void();
}
pub fn call_nac3_nditer_initialize<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
iter: Instance<'ctx, Ptr<Struct<NDIter>>>,
ndarray: Instance<'ctx, Ptr<Struct<NDArray>>>,
indices: Instance<'ctx, Ptr<Int<SizeT>>>,
) {
let name = get_sizet_dependent_function_name(generator, ctx, "__nac3_nditer_initialize");
FnCall::builder(generator, ctx, &name).arg(iter).arg(ndarray).arg(indices).returning_void();
}
pub fn call_nac3_nditer_has_element<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
iter: Instance<'ctx, Ptr<Struct<NDIter>>>,
) -> Instance<'ctx, Int<Bool>> {
let name = get_sizet_dependent_function_name(generator, ctx, "__nac3_nditer_has_element");
FnCall::builder(generator, ctx, &name).arg(iter).returning_auto("has_element")
}
pub fn call_nac3_nditer_next<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
iter: Instance<'ctx, Ptr<Struct<NDIter>>>,
) {
let name = get_sizet_dependent_function_name(generator, ctx, "__nac3_nditer_next");
FnCall::builder(generator, ctx, &name).arg(iter).returning_void();
}
pub fn call_nac3_ndarray_index<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
num_indices: Instance<'ctx, Int<SizeT>>,
indices: Instance<'ctx, Ptr<Struct<NDIndex>>>,
src_ndarray: Instance<'ctx, Ptr<Struct<NDArray>>>,
dst_ndarray: Instance<'ctx, Ptr<Struct<NDArray>>>,
) {
let name = get_sizet_dependent_function_name(generator, ctx, "__nac3_ndarray_index");
FnCall::builder(generator, ctx, &name)
.arg(num_indices)
.arg(indices)
.arg(src_ndarray)
.arg(dst_ndarray)
.returning_void();
}
pub fn call_nac3_ndarray_array_set_and_validate_list_shape<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
list: Instance<'ctx, Ptr<Struct<List<Int<Byte>>>>>,
ndims: Instance<'ctx, Int<SizeT>>,
shape: Instance<'ctx, Ptr<Int<SizeT>>>,
) {
let name = get_sizet_dependent_function_name(
generator,
ctx,
"__nac3_ndarray_array_set_and_validate_list_shape",
);
FnCall::builder(generator, ctx, &name).arg(list).arg(ndims).arg(shape).returning_void();
}
pub fn call_nac3_ndarray_array_write_list_to_array<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
list: Instance<'ctx, Ptr<Struct<List<Int<Byte>>>>>,
ndarray: Instance<'ctx, Ptr<Struct<NDArray>>>,
) {
let name = get_sizet_dependent_function_name(
generator,
ctx,
"__nac3_ndarray_array_write_list_to_array",
);
FnCall::builder(generator, ctx, &name).arg(list).arg(ndarray).returning_void();
}
pub fn call_nac3_ndarray_reshape_resolve_and_check_new_shape<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
size: Instance<'ctx, Int<SizeT>>,
new_ndims: Instance<'ctx, Int<SizeT>>,
new_shape: Instance<'ctx, Ptr<Int<SizeT>>>,
) {
let name = get_sizet_dependent_function_name(
generator,
ctx,
"__nac3_ndarray_reshape_resolve_and_check_new_shape",
);
FnCall::builder(generator, ctx, &name).arg(size).arg(new_ndims).arg(new_shape).returning_void();
}
pub fn call_nac3_ndarray_broadcast_to<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
src_ndarray: Instance<'ctx, Ptr<Struct<NDArray>>>,
dst_ndarray: Instance<'ctx, Ptr<Struct<NDArray>>>,
) {
let name = get_sizet_dependent_function_name(generator, ctx, "__nac3_ndarray_broadcast_to");
FnCall::builder(generator, ctx, &name).arg(src_ndarray).arg(dst_ndarray).returning_void();
}
pub fn call_nac3_ndarray_broadcast_shapes<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
num_shape_entries: Instance<'ctx, Int<SizeT>>,
shape_entries: Instance<'ctx, Ptr<Struct<ShapeEntry>>>,
dst_ndims: Instance<'ctx, Int<SizeT>>,
dst_shape: Instance<'ctx, Ptr<Int<SizeT>>>,
) {
let name = get_sizet_dependent_function_name(generator, ctx, "__nac3_ndarray_broadcast_shapes");
FnCall::builder(generator, ctx, &name)
.arg(num_shape_entries)
.arg(shape_entries)
.arg(dst_ndims)
.arg(dst_shape)
.returning_void();
}
pub fn call_nac3_ndarray_transpose<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
src_ndarray: Instance<'ctx, Ptr<Struct<NDArray>>>,
dst_ndarray: Instance<'ctx, Ptr<Struct<NDArray>>>,
num_axes: Instance<'ctx, Int<SizeT>>,
axes: Instance<'ctx, Ptr<Int<SizeT>>>,
) {
let name = get_sizet_dependent_function_name(generator, ctx, "__nac3_ndarray_transpose");
FnCall::builder(generator, ctx, &name)
.arg(src_ndarray)
.arg(dst_ndarray)
.arg(num_axes)
.arg(axes)
.returning_void();
}
#[allow(clippy::too_many_arguments)]
pub fn call_nac3_ndarray_matmul_calculate_shapes<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
a_ndims: Instance<'ctx, Int<SizeT>>,
a_shape: Instance<'ctx, Ptr<Int<SizeT>>>,
b_ndims: Instance<'ctx, Int<SizeT>>,
b_shape: Instance<'ctx, Ptr<Int<SizeT>>>,
final_ndims: Instance<'ctx, Int<SizeT>>,
new_a_shape: Instance<'ctx, Ptr<Int<SizeT>>>,
new_b_shape: Instance<'ctx, Ptr<Int<SizeT>>>,
dst_shape: Instance<'ctx, Ptr<Int<SizeT>>>,
) {
let name =
get_sizet_dependent_function_name(generator, ctx, "__nac3_ndarray_matmul_calculate_shapes");
FnCall::builder(generator, ctx, &name)
.arg(a_ndims)
.arg(a_shape)
.arg(b_ndims)
.arg(b_shape)
.arg(final_ndims)
.arg(new_a_shape)
.arg(new_b_shape)
.arg(dst_shape)
.returning_void();
}

View File

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

View File

@ -0,0 +1,310 @@
use inkwell::{
types::BasicTypeEnum,
values::{BasicValueEnum, IntValue, PointerValue},
AddressSpace,
};
use crate::codegen::{
expr::{create_and_call_function, infer_and_call_function},
irrt::get_usize_dependent_function_name,
types::ProxyType,
values::{ndarray::NDArrayValue, ProxyValue, TypedArrayLikeAccessor},
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>(
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
shape: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>,
) {
let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
assert_eq!(
BasicTypeEnum::try_from(shape.element_type(ctx, generator)).unwrap(),
llvm_usize.into()
);
let name = get_usize_dependent_function_name(
generator,
ctx,
"__nac3_ndarray_util_assert_shape_no_negative",
);
create_and_call_function(
ctx,
&name,
Some(llvm_usize.into()),
&[
(llvm_usize.into(), shape.size(ctx, generator).into()),
(llvm_pusize.into(), shape.base_ptr(ctx, generator).into()),
],
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>(
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
ndarray_shape: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>,
output_shape: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>,
) {
let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
assert_eq!(
BasicTypeEnum::try_from(ndarray_shape.element_type(ctx, generator)).unwrap(),
llvm_usize.into()
);
assert_eq!(
BasicTypeEnum::try_from(output_shape.element_type(ctx, generator)).unwrap(),
llvm_usize.into()
);
let name = get_usize_dependent_function_name(
generator,
ctx,
"__nac3_ndarray_util_assert_output_shape_same",
);
create_and_call_function(
ctx,
&name,
Some(llvm_usize.into()),
&[
(llvm_usize.into(), ndarray_shape.size(ctx, generator).into()),
(llvm_pusize.into(), ndarray_shape.base_ptr(ctx, generator).into()),
(llvm_usize.into(), output_shape.size(ctx, generator).into()),
(llvm_pusize.into(), output_shape.base_ptr(ctx, generator).into()),
],
None,
None,
);
}
/// Generates a call to `__nac3_ndarray_size`.
///
/// Returns a [`usize`][CodeGenerator::get_size_type] value of the number of elements of an
/// `ndarray`, corresponding to the value of `ndarray.size`.
pub fn call_nac3_ndarray_size<'ctx, G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
ndarray: NDArrayValue<'ctx>,
) -> IntValue<'ctx> {
let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_ndarray = ndarray.get_type().as_base_type();
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_size");
create_and_call_function(
ctx,
&name,
Some(llvm_usize.into()),
&[(llvm_ndarray.into(), ndarray.as_base_value().into())],
Some("size"),
None,
)
.map(BasicValueEnum::into_int_value)
.unwrap()
}
/// Generates a call to `__nac3_ndarray_nbytes`.
///
/// Returns a [`usize`][CodeGenerator::get_size_type] value of the number of bytes consumed by the
/// data of the `ndarray`, corresponding to the value of `ndarray.nbytes`.
pub fn call_nac3_ndarray_nbytes<'ctx, G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
ndarray: NDArrayValue<'ctx>,
) -> IntValue<'ctx> {
let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_ndarray = ndarray.get_type().as_base_type();
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_nbytes");
create_and_call_function(
ctx,
&name,
Some(llvm_usize.into()),
&[(llvm_ndarray.into(), ndarray.as_base_value().into())],
Some("nbytes"),
None,
)
.map(BasicValueEnum::into_int_value)
.unwrap()
}
/// Generates a call to `__nac3_ndarray_len`.
///
/// Returns a [`usize`][CodeGenerator::get_size_type] value of the size of the topmost dimension of
/// the `ndarray`, corresponding to the value of `ndarray.__len__`.
pub fn call_nac3_ndarray_len<'ctx, G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
ndarray: NDArrayValue<'ctx>,
) -> IntValue<'ctx> {
let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_ndarray = ndarray.get_type().as_base_type();
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_len");
create_and_call_function(
ctx,
&name,
Some(llvm_usize.into()),
&[(llvm_ndarray.into(), ndarray.as_base_value().into())],
Some("len"),
None,
)
.map(BasicValueEnum::into_int_value)
.unwrap()
}
/// Generates a call to `__nac3_ndarray_is_c_contiguous`.
///
/// Returns an `i1` value indicating whether the `ndarray` is C-contiguous.
pub fn call_nac3_ndarray_is_c_contiguous<'ctx, G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
ndarray: NDArrayValue<'ctx>,
) -> IntValue<'ctx> {
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");
create_and_call_function(
ctx,
&name,
Some(llvm_i1.into()),
&[(llvm_ndarray.into(), ndarray.as_base_value().into())],
Some("is_c_contiguous"),
None,
)
.map(BasicValueEnum::into_int_value)
.unwrap()
}
/// Generates a call to `__nac3_ndarray_get_nth_pelement`.
///
/// Returns a [`PointerValue`] to the `index`-th flattened element of the `ndarray`.
pub fn call_nac3_ndarray_get_nth_pelement<'ctx, G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
ndarray: NDArrayValue<'ctx>,
index: IntValue<'ctx>,
) -> PointerValue<'ctx> {
let llvm_i8 = ctx.ctx.i8_type();
let llvm_pi8 = llvm_i8.ptr_type(AddressSpace::default());
let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_ndarray = ndarray.get_type().as_base_type();
assert_eq!(index.get_type(), llvm_usize);
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_get_nth_pelement");
create_and_call_function(
ctx,
&name,
Some(llvm_pi8.into()),
&[(llvm_ndarray.into(), ndarray.as_base_value().into()), (llvm_usize.into(), index.into())],
Some("pelement"),
None,
)
.map(BasicValueEnum::into_pointer_value)
.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>(
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
ndarray: NDArrayValue<'ctx>,
indices: &impl TypedArrayLikeAccessor<'ctx, G, IntValue<'ctx>>,
) -> PointerValue<'ctx> {
let llvm_i8 = ctx.ctx.i8_type();
let llvm_pi8 = llvm_i8.ptr_type(AddressSpace::default());
let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
let llvm_ndarray = ndarray.get_type().as_base_type();
assert_eq!(
BasicTypeEnum::try_from(indices.element_type(ctx, generator)).unwrap(),
llvm_usize.into()
);
let name =
get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_get_pelement_by_indices");
create_and_call_function(
ctx,
&name,
Some(llvm_pi8.into()),
&[
(llvm_ndarray.into(), ndarray.as_base_value().into()),
(llvm_pusize.into(), indices.base_ptr(ctx, generator).into()),
],
Some("pelement"),
None,
)
.map(BasicValueEnum::into_pointer_value)
.unwrap()
}
/// Generates a call to `__nac3_ndarray_set_strides_by_shape`.
///
/// Sets `ndarray.strides` assuming that `ndarray.shape` is C-contiguous.
pub fn call_nac3_ndarray_set_strides_by_shape<'ctx, G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
ndarray: NDArrayValue<'ctx>,
) {
let llvm_ndarray = ndarray.get_type().as_base_type();
let name =
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,
);
}
/// Generates a call to `__nac3_ndarray_copy_data`.
///
/// Copies all elements from `src_ndarray` to `dst_ndarray` using their flattened views. The number
/// of elements in `src_ndarray` must be greater than or equal to the number of elements in
/// `dst_ndarray`.
pub fn call_nac3_ndarray_copy_data<'ctx, G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
src_ndarray: NDArrayValue<'ctx>,
dst_ndarray: NDArrayValue<'ctx>,
) {
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_copy_data");
infer_and_call_function(
ctx,
&name,
None,
&[src_ndarray.as_base_value().into(), dst_ndarray.as_base_value().into()],
None,
None,
);
}

View File

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

View File

@ -0,0 +1,34 @@
use crate::codegen::{
expr::infer_and_call_function,
irrt::get_usize_dependent_function_name,
values::{ndarray::NDArrayValue, ArrayLikeValue, ArraySliceValue, ProxyValue},
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>(
generator: &G,
ctx: &CodeGenContext<'ctx, '_>,
indices: ArraySliceValue<'ctx>,
src_ndarray: NDArrayValue<'ctx>,
dst_ndarray: NDArrayValue<'ctx>,
) {
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_index");
infer_and_call_function(
ctx,
&name,
None,
&[
indices.size(ctx, generator).into(),
indices.base_ptr(ctx, generator).into(),
src_ndarray.as_base_value().into(),
dst_ndarray.as_base_value().into(),
],
None,
None,
);
}

View File

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

View File

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

View File

@ -0,0 +1,17 @@
pub use array::*;
pub use basic::*;
pub use broadcast::*;
pub use indexing::*;
pub use iter::*;
pub use matmul::*;
pub use reshape::*;
pub use transpose::*;
mod array;
mod basic;
mod broadcast;
mod indexing;
mod iter;
mod matmul;
mod reshape;
mod transpose;

View File

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

View File

@ -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 = generator.get_size_type(ctx.ctx);
assert!(axes.is_none_or(|axes| axes.size(ctx, generator).get_type() == llvm_usize));
assert!(axes.is_none_or(|axes| axes.element_type(ctx, generator) == llvm_usize.into()));
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_transpose");
infer_and_call_function(
ctx,
&name,
None,
&[
src_ndarray.as_base_value().into(),
dst_ndarray.as_base_value().into(),
axes.map_or(llvm_usize.const_zero(), |axes| axes.size(ctx, generator)).into(),
axes.map_or(llvm_usize.ptr_type(AddressSpace::default()).const_null(), |axes| {
axes.base_ptr(ctx, generator)
})
.into(),
],
None,
None,
);
}

View File

@ -0,0 +1,56 @@
use inkwell::{
values::{BasicValueEnum, CallSiteValue, IntValue},
IntPredicate,
};
use itertools::Either;
use crate::codegen::{CodeGenContext, CodeGenerator};
/// Invokes the `__nac3_range_slice_len` in IRRT.
///
/// - `start`: The `i32` start value for the slice.
/// - `end`: The `i32` end value for the slice.
/// - `step`: The `i32` step value for the slice.
///
/// Returns an `i32` value of the length of the slice.
pub fn calculate_len_for_slice_range<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
start: IntValue<'ctx>,
end: IntValue<'ctx>,
step: IntValue<'ctx>,
) -> IntValue<'ctx> {
const SYMBOL: &str = "__nac3_range_slice_len";
let llvm_i32 = ctx.ctx.i32_type();
assert_eq!(start.get_type(), llvm_i32);
assert_eq!(end.get_type(), llvm_i32);
assert_eq!(step.get_type(), llvm_i32);
let len_func = ctx.module.get_function(SYMBOL).unwrap_or_else(|| {
let fn_t = llvm_i32.fn_type(&[llvm_i32.into(), llvm_i32.into(), llvm_i32.into()], false);
ctx.module.add_function(SYMBOL, fn_t, None)
});
// assert step != 0, throw exception if not
let not_zero = ctx
.builder
.build_int_compare(IntPredicate::NE, step, step.get_type().const_zero(), "range_step_ne")
.unwrap();
ctx.make_assert(
generator,
not_zero,
"0:ValueError",
"step must not be zero",
[None, None, None],
ctx.current_loc,
);
ctx.builder
.build_call(len_func, &[start.into(), end.into(), step.into()], "calc_len")
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_int_value))
.map(Either::unwrap_left)
.unwrap()
}

View File

@ -0,0 +1,39 @@
use inkwell::values::{BasicValueEnum, CallSiteValue, IntValue};
use itertools::Either;
use nac3parser::ast::Expr;
use crate::{
codegen::{CodeGenContext, CodeGenerator},
typecheck::typedef::Type,
};
/// this function allows index out of range, since python
/// allows index out of range in slice (`a = [1,2,3]; a[1:10] == [2,3]`).
pub fn handle_slice_index_bound<'ctx, G: CodeGenerator>(
i: &Expr<Option<Type>>,
ctx: &mut CodeGenContext<'ctx, '_>,
generator: &mut G,
length: IntValue<'ctx>,
) -> Result<Option<IntValue<'ctx>>, String> {
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 fn_t = i32_t.fn_type(&[i32_t.into(), i32_t.into()], false);
ctx.module.add_function(SYMBOL, fn_t, None)
});
let i = if let Some(v) = generator.gen_expr(ctx, i)? {
v.to_basic_value_enum(ctx, generator, i.custom.unwrap())?
} else {
return Ok(None);
};
Ok(Some(
ctx.builder
.build_call(func, &[i.into(), length.into()], "bounded_ind")
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_int_value))
.map(Either::unwrap_left)
.unwrap(),
))
}

View File

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

View File

@ -1,7 +1,6 @@
use inkwell::{
context::Context,
intrinsics::Intrinsic,
types::{AnyTypeEnum::IntType, FloatType},
types::AnyTypeEnum::IntType,
values::{BasicValueEnum, CallSiteValue, FloatValue, IntValue, PointerValue},
AddressSpace,
};
@ -9,34 +8,6 @@ use itertools::Either;
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)
/// intrinsic.
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();
}
/// 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.
pub fn call_va_end<'ctx>(ctx: &CodeGenContext<'ctx, '_>, arglist: PointerValue<'ctx>) {
const FN_NAME: &str = "llvm.va_end";
@ -201,6 +172,49 @@ pub fn call_memcpy_generic<'ctx>(
call_memcpy(ctx, dest, src, len, is_volatile);
}
/// Invokes the `llvm.memcpy` intrinsic.
///
/// Unlike [`call_memcpy`], this function accepts any type of pointer value. If `dest` or `src` is
/// not a pointer to an integer, the pointer(s) will be cast to `i8*` before invoking `memcpy`.
/// Moreover, `len` now refers to the number of elements to copy (rather than number of bytes to
/// copy).
pub fn call_memcpy_generic_array<'ctx>(
ctx: &CodeGenContext<'ctx, '_>,
dest: PointerValue<'ctx>,
src: PointerValue<'ctx>,
len: IntValue<'ctx>,
is_volatile: IntValue<'ctx>,
) {
let llvm_i8 = ctx.ctx.i8_type();
let llvm_p0i8 = llvm_i8.ptr_type(AddressSpace::default());
let llvm_sizeof_expr_t = llvm_i8.size_of().get_type();
let dest_elem_t = dest.get_type().get_element_type();
let src_elem_t = src.get_type().get_element_type();
let dest = if matches!(dest_elem_t, IntType(t) if t.get_bit_width() == 8) {
dest
} else {
ctx.builder
.build_bit_cast(dest, llvm_p0i8, "")
.map(BasicValueEnum::into_pointer_value)
.unwrap()
};
let src = if matches!(src_elem_t, IntType(t) if t.get_bit_width() == 8) {
src
} else {
ctx.builder
.build_bit_cast(src, llvm_p0i8, "")
.map(BasicValueEnum::into_pointer_value)
.unwrap()
};
let len = ctx.builder.build_int_z_extend_or_bit_cast(len, llvm_sizeof_expr_t, "").unwrap();
let len = ctx.builder.build_int_mul(len, src_elem_t.size_of().unwrap(), "").unwrap();
call_memcpy(ctx, dest, src, len, is_volatile);
}
/// Macro to find and generate build call for llvm intrinsic (body of llvm intrinsic function)
///
/// Arguments:
@ -343,3 +357,25 @@ pub fn call_float_powi<'ctx>(
.map(Either::unwrap_left)
.unwrap()
}
/// Invokes the [`llvm.ctpop`](https://llvm.org/docs/LangRef.html#llvm-ctpop-intrinsic) intrinsic.
pub fn call_int_ctpop<'ctx>(
ctx: &CodeGenContext<'ctx, '_>,
src: IntValue<'ctx>,
name: Option<&str>,
) -> IntValue<'ctx> {
const FN_NAME: &str = "llvm.ctpop";
let llvm_src_t = src.get_type();
let intrinsic_fn = Intrinsic::find(FN_NAME)
.and_then(|intrinsic| intrinsic.get_declaration(&ctx.module, &[llvm_src_t.into()]))
.unwrap();
ctx.builder
.build_call(intrinsic_fn, &[src.into()], name.unwrap_or_default())
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_int_value))
.map(Either::unwrap_left)
.unwrap()
}

View File

@ -30,30 +30,31 @@ use nac3parser::ast::{Location, Stmt, StrRef};
use crate::{
symbol_resolver::{StaticValue, SymbolResolver},
toplevel::{helper::PrimDef, TopLevelContext, TopLevelDef},
toplevel::{
helper::{extract_ndims, PrimDef},
numpy::unpack_ndarray_var_tys,
TopLevelContext, TopLevelDef,
},
typecheck::{
type_inferencer::{CodeLocation, PrimitiveStore},
typedef::{CallId, FuncArg, Type, TypeEnum, Unifier},
},
};
use classes::{ListType, ProxyType, RangeType};
use concrete_type::{ConcreteType, ConcreteTypeEnum, ConcreteTypeStore};
pub use generator::{CodeGenerator, DefaultCodeGenerator};
use model::*;
use object::ndarray::NDArray;
use types::{ndarray::NDArrayType, ListType, ProxyType, RangeType, TupleType};
pub mod builtin_fns;
pub mod classes;
pub mod concrete_type;
pub mod expr;
pub mod extern_fns;
mod generator;
pub mod irrt;
pub mod llvm_intrinsics;
pub mod model;
pub mod numpy;
pub mod object;
pub mod stmt;
pub mod types;
pub mod values;
#[cfg(test)]
mod test;
@ -513,7 +514,13 @@ fn get_llvm_type<'ctx, G: CodeGenerator + ?Sized>(
}
TObj { obj_id, .. } if *obj_id == PrimDef::NDArray.id() => {
Ptr(Struct(NDArray)).llvm_type(generator, ctx).as_basic_type_enum()
let (dtype, ndims) = unpack_ndarray_var_tys(unifier, ty);
let ndims = extract_ndims(unifier, ndims);
let element_type = get_llvm_type(
ctx, module, generator, unifier, top_level, type_cache, dtype,
);
NDArrayType::new(generator, ctx, element_type, ndims).as_base_type().into()
}
_ => unreachable!(
@ -567,7 +574,7 @@ fn get_llvm_type<'ctx, G: CodeGenerator + ?Sized>(
get_llvm_type(ctx, module, generator, unifier, top_level, type_cache, *ty)
})
.collect_vec();
ctx.struct_type(&fields, false).into()
TupleType::new(generator, ctx, &fields).as_base_type().into()
}
TVirtual { .. } => unimplemented!(),
_ => unreachable!("{}", ty_enum.get_type_name()),
@ -1117,3 +1124,106 @@ fn gen_in_range_check<'ctx>(
fn get_va_count_arg_name(arg_name: StrRef) -> StrRef {
format!("__{}_va_count", &arg_name).into()
}
/// Returns the alignment of the type.
///
/// This is necessary as `get_alignment` is not implemented as part of [`BasicType`].
pub fn get_type_alignment<'ctx>(ty: impl Into<BasicTypeEnum<'ctx>>) -> IntValue<'ctx> {
match ty.into() {
BasicTypeEnum::ArrayType(ty) => ty.get_alignment(),
BasicTypeEnum::FloatType(ty) => ty.get_alignment(),
BasicTypeEnum::IntType(ty) => ty.get_alignment(),
BasicTypeEnum::PointerType(ty) => ty.get_alignment(),
BasicTypeEnum::StructType(ty) => ty.get_alignment(),
BasicTypeEnum::VectorType(ty) => ty.get_alignment(),
}
}
/// Inserts an `alloca` instruction with allocation `size` given in bytes and the alignment of the
/// given type.
///
/// The returned [`PointerValue`] will have a type of `i8*`, a size of at least `size`, and will be
/// aligned with the alignment of `align_ty`.
pub fn type_aligned_alloca<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
align_ty: impl Into<BasicTypeEnum<'ctx>>,
size: IntValue<'ctx>,
name: Option<&str>,
) -> PointerValue<'ctx> {
/// Round `val` up to its modulo `power_of_two`.
fn round_up<'ctx>(
ctx: &CodeGenContext<'ctx, '_>,
val: IntValue<'ctx>,
power_of_two: IntValue<'ctx>,
) -> IntValue<'ctx> {
debug_assert_eq!(
val.get_type().get_bit_width(),
power_of_two.get_type().get_bit_width(),
"`val` ({}) and `power_of_two` ({}) must be the same type",
val.get_type(),
power_of_two.get_type(),
);
let llvm_val_t = val.get_type();
let max_rem =
ctx.builder.build_int_sub(power_of_two, llvm_val_t.const_int(1, false), "").unwrap();
ctx.builder
.build_and(
ctx.builder.build_int_add(val, max_rem, "").unwrap(),
ctx.builder.build_not(max_rem, "").unwrap(),
"",
)
.unwrap()
}
let llvm_i8 = ctx.ctx.i8_type();
let llvm_pi8 = llvm_i8.ptr_type(AddressSpace::default());
let llvm_usize = generator.get_size_type(ctx.ctx);
let align_ty = align_ty.into();
let size = ctx.builder.build_int_truncate_or_bit_cast(size, llvm_usize, "").unwrap();
debug_assert_eq!(
size.get_type().get_bit_width(),
llvm_usize.get_bit_width(),
"Expected size_t ({}) for parameter `size` of `aligned_alloca`, got {}",
llvm_usize,
size.get_type(),
);
let alignment = get_type_alignment(align_ty);
let alignment = ctx.builder.build_int_truncate_or_bit_cast(alignment, llvm_usize, "").unwrap();
if ctx.registry.llvm_options.opt_level == OptimizationLevel::None {
let alignment_bitcount = llvm_intrinsics::call_int_ctpop(ctx, alignment, None);
ctx.make_assert(
generator,
ctx.builder
.build_int_compare(
IntPredicate::EQ,
alignment_bitcount,
alignment_bitcount.get_type().const_int(1, false),
"",
)
.unwrap(),
"0:AssertionError",
"Expected power-of-two alignment for aligned_alloca, got {0}",
[Some(alignment), None, None],
ctx.current_loc,
);
}
let buffer_size = round_up(ctx, size, alignment);
let aligned_slices = ctx.builder.build_int_unsigned_div(buffer_size, alignment, "").unwrap();
// Just to be absolutely sure, alloca in [i8 x alignment] slices
let buffer = ctx.builder.build_array_alloca(align_ty, aligned_slices, "").unwrap();
ctx.builder
.build_bit_cast(buffer, llvm_pi8, name.unwrap_or_default())
.map(BasicValueEnum::into_pointer_value)
.unwrap()
}

View File

@ -1,41 +0,0 @@
use inkwell::{
context::Context,
types::{BasicType, BasicTypeEnum},
values::BasicValueEnum,
};
use super::*;
use crate::codegen::CodeGenerator;
/// A [`Model`] of any [`BasicTypeEnum`].
///
/// Use this when it is infeasible to use model abstractions.
#[derive(Debug, Clone, Copy)]
pub struct Any<'ctx>(pub BasicTypeEnum<'ctx>);
impl<'ctx> Model<'ctx> for Any<'ctx> {
type Value = BasicValueEnum<'ctx>;
type Type = BasicTypeEnum<'ctx>;
fn llvm_type<G: CodeGenerator + ?Sized>(
&self,
_generator: &G,
_ctx: &'ctx Context,
) -> Self::Type {
self.0
}
fn check_type<T: BasicType<'ctx>, G: CodeGenerator + ?Sized>(
&self,
_generator: &mut G,
_ctx: &'ctx Context,
ty: T,
) -> Result<(), ModelError> {
let ty = ty.as_basic_type_enum();
if ty == self.0 {
Ok(())
} else {
Err(ModelError(format!("Expecting {}, but got {}", self.0, ty)))
}
}
}

View File

@ -1,146 +0,0 @@
use std::fmt;
use inkwell::{
context::Context,
types::{ArrayType, BasicType, BasicTypeEnum},
values::{ArrayValue, IntValue},
};
use super::*;
use crate::codegen::{CodeGenContext, CodeGenerator};
/// Trait for Rust structs identifying length values for [`Array`].
pub trait ArrayLen: fmt::Debug + Clone + Copy {
fn length(&self) -> u32;
}
/// A statically known length.
#[derive(Debug, Clone, Copy, Default)]
pub struct Len<const N: u32>;
/// A dynamically known length.
#[derive(Debug, Clone, Copy)]
pub struct AnyLen(pub u32);
impl<const N: u32> ArrayLen for Len<N> {
fn length(&self) -> u32 {
N
}
}
impl ArrayLen for AnyLen {
fn length(&self) -> u32 {
self.0
}
}
/// A Model for an [`ArrayType`].
///
/// `Len` should be of a [`LenKind`] and `Item` should be a of [`Model`].
#[derive(Debug, Clone, Copy, Default)]
pub struct Array<Len, Item> {
/// Length of this array.
pub len: Len,
/// [`Model`] of the array items.
pub item: Item,
}
impl<'ctx, Len: ArrayLen, Item: Model<'ctx>> Model<'ctx> for Array<Len, Item> {
type Value = ArrayValue<'ctx>;
type Type = ArrayType<'ctx>;
fn llvm_type<G: CodeGenerator + ?Sized>(
&self,
generator: &G,
ctx: &'ctx Context,
) -> Self::Type {
self.item.llvm_type(generator, ctx).array_type(self.len.length())
}
fn check_type<T: BasicType<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
ty: T,
) -> Result<(), ModelError> {
let ty = ty.as_basic_type_enum();
let BasicTypeEnum::ArrayType(ty) = ty else {
return Err(ModelError(format!("Expecting ArrayType, but got {ty:?}")));
};
if ty.len() != self.len.length() {
return Err(ModelError(format!(
"Expecting ArrayType with size {}, but got an ArrayType with size {}",
ty.len(),
self.len.length()
)));
}
self.item
.check_type(generator, ctx, ty.get_element_type())
.map_err(|err| err.under_context("an ArrayType"))?;
Ok(())
}
}
impl<'ctx, Len: ArrayLen, Item: Model<'ctx>> Instance<'ctx, Ptr<Array<Len, Item>>> {
/// Get the pointer to the `i`-th (0-based) array element.
pub fn gep(
&self,
ctx: &CodeGenContext<'ctx, '_>,
i: IntValue<'ctx>,
) -> Instance<'ctx, Ptr<Item>> {
let zero = ctx.ctx.i32_type().const_zero();
let ptr = unsafe { ctx.builder.build_in_bounds_gep(self.value, &[zero, i], "").unwrap() };
unsafe { Ptr(self.model.0.item).believe_value(ptr) }
}
/// Like `gep` but `i` is a constant.
pub fn gep_const(&self, ctx: &CodeGenContext<'ctx, '_>, i: u64) -> Instance<'ctx, Ptr<Item>> {
assert!(
i < u64::from(self.model.0.len.length()),
"Index {i} is out of bounds. Array length = {}",
self.model.0.len.length()
);
let i = ctx.ctx.i32_type().const_int(i, false);
self.gep(ctx, i)
}
/// Convenience function equivalent to `.gep(...).load(...)`.
pub fn get<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
i: IntValue<'ctx>,
) -> Instance<'ctx, Item> {
self.gep(ctx, i).load(generator, ctx)
}
/// Like `get` but `i` is a constant.
pub fn get_const<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
i: u64,
) -> Instance<'ctx, Item> {
self.gep_const(ctx, i).load(generator, ctx)
}
/// Convenience function equivalent to `.gep(...).store(...)`.
pub fn set(
&self,
ctx: &CodeGenContext<'ctx, '_>,
i: IntValue<'ctx>,
value: Instance<'ctx, Item>,
) {
self.gep(ctx, i).store(ctx, value);
}
/// Like `set` but `i` is a constant.
pub fn set_const(&self, ctx: &CodeGenContext<'ctx, '_>, i: u64, value: Instance<'ctx, Item>) {
self.gep_const(ctx, i).store(ctx, value);
}
}

View File

@ -1,207 +0,0 @@
use std::fmt;
use inkwell::{context::Context, types::*, values::*};
use itertools::Itertools;
use super::*;
use crate::codegen::{CodeGenContext, CodeGenerator};
/// A error type for reporting any [`Model`]-related error (e.g., a [`BasicType`] mismatch).
#[derive(Debug, Clone)]
pub struct ModelError(pub String);
impl ModelError {
/// Append a context message to the error.
pub(super) fn under_context(mut self, context: &str) -> Self {
self.0.push_str(" ... in ");
self.0.push_str(context);
self
}
}
/// Trait for Rust structs identifying [`BasicType`]s in the context of a known [`CodeGenerator`] and [`CodeGenContext`].
///
/// For instance,
/// - [`Int<Int32>`] identifies an [`IntType`] with 32-bits.
/// - [`Int<SizeT>`] identifies an [`IntType`] with bit-width [`CodeGenerator::get_size_type`].
/// - [`Ptr<Int<SizeT>>`] identifies a [`PointerType`] that points to an [`IntType`] with bit-width [`CodeGenerator::get_size_type`].
/// - [`Int<AnyInt>`] identifies an [`IntType`] with bit-width of whatever is set in the [`AnyInt`] object.
/// - [`Any`] identifies a [`BasicType`] set in the [`Any`] object itself.
///
/// You can get the [`BasicType`] out of a model with [`Model::get_type`].
///
/// Furthermore, [`Instance<'ctx, M>`] is a simple structure that carries a [`BasicValue`] with [`BasicType`] identified by model `M`.
///
/// The main purpose of this abstraction is to have a more Rust type-safe way to use Inkwell and give type-hints for programmers.
///
/// ### Notes on `Default` trait
///
/// For some models like [`Int<Int32>`] or [`Int<SizeT>`], they have a [`Default`] trait since just by looking at their types, it is possible
/// to tell the [`BasicType`]s they are identifying.
///
/// This can be used to create strongly-typed interfaces accepting only values of a specific [`BasicType`] without having to worry about
/// writing debug assertions to check, for example, if the programmer has passed in an [`IntValue`] with the wrong bit-width.
/// ```ignore
/// fn give_me_i32_and_get_a_size_t_back<'ctx>(i32: Instance<'ctx, Int<Int32>>) -> Instance<'ctx, Int<SizeT>> {
/// // code...
/// }
/// ```
///
/// ### Notes on converting between Inkwell and model/ge.
///
/// Suppose you have an [`IntValue`], and you want to pass it into a function that takes a [`Instance<'ctx, Int<Int32>>`]. You can do use
/// [`Model::check_value`] or [`Model::believe_value`].
/// ```ignore
/// let my_value: IntValue<'ctx>;
///
/// let my_value = Int(Int32).check_value(my_value).unwrap(); // Panics if `my_value` is not 32-bit with a descriptive error message.
///
/// // or, if you are absolutely certain that `my_value` is 32-bit and doing extra checks is a waste of time:
/// let my_value = Int(Int32).believe_value(my_value);
/// ```
pub trait Model<'ctx>: fmt::Debug + Clone + Copy {
/// The [`BasicType`] *variant* this model is identifying.
type Type: BasicType<'ctx>;
/// The [`BasicValue`] type of the [`BasicType`] of this model.
type Value: BasicValue<'ctx> + TryFrom<BasicValueEnum<'ctx>>;
/// Return the [`BasicType`] of this model.
#[must_use]
fn llvm_type<G: CodeGenerator + ?Sized>(&self, generator: &G, ctx: &'ctx Context)
-> Self::Type;
/// Get the number of bytes of the [`BasicType`] of this model.
fn size_of<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
) -> IntValue<'ctx> {
self.llvm_type(generator, ctx).size_of().unwrap()
}
/// Check if a [`BasicType`] matches the [`BasicType`] of this model.
fn check_type<T: BasicType<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
ty: T,
) -> Result<(), ModelError>;
/// Create an instance from a value.
///
/// # Safety
///
/// Caller must make sure the type of `value` and the type of this `model` are equivalent.
#[must_use]
unsafe fn believe_value(&self, value: Self::Value) -> Instance<'ctx, Self> {
Instance { model: *self, value }
}
/// Check if a [`BasicValue`]'s type is equivalent to the type of this model.
/// Wrap the [`BasicValue`] into an [`Instance`] if it is.
fn check_value<V: BasicValue<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
value: V,
) -> Result<Instance<'ctx, Self>, ModelError> {
let value = value.as_basic_value_enum();
self.check_type(generator, ctx, value.get_type())
.map_err(|err| err.under_context(format!("the value {value:?}").as_str()))?;
let Ok(value) = Self::Value::try_from(value) else {
unreachable!("check_type() has bad implementation")
};
unsafe { Ok(self.believe_value(value)) }
}
// Allocate a value on the stack and return its pointer.
fn alloca<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
) -> Instance<'ctx, Ptr<Self>> {
let p = ctx.builder.build_alloca(self.llvm_type(generator, ctx.ctx), "").unwrap();
unsafe { Ptr(*self).believe_value(p) }
}
// Allocate an array on the stack and return its pointer.
fn array_alloca<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
len: IntValue<'ctx>,
) -> Instance<'ctx, Ptr<Self>> {
let p =
ctx.builder.build_array_alloca(self.llvm_type(generator, ctx.ctx), len, "").unwrap();
unsafe { Ptr(*self).believe_value(p) }
}
fn var_alloca<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&str>,
) -> Result<Instance<'ctx, Ptr<Self>>, String> {
let ty = self.llvm_type(generator, ctx.ctx).as_basic_type_enum();
let p = generator.gen_var_alloc(ctx, ty, name)?;
unsafe { Ok(Ptr(*self).believe_value(p)) }
}
fn array_var_alloca<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
len: IntValue<'ctx>,
name: Option<&'ctx str>,
) -> Result<Instance<'ctx, Ptr<Self>>, String> {
// TODO: Remove ArraySliceValue
let ty = self.llvm_type(generator, ctx.ctx).as_basic_type_enum();
let p = generator.gen_array_var_alloc(ctx, ty, len, name)?;
unsafe { Ok(Ptr(*self).believe_value(PointerValue::from(p))) }
}
/// Allocate a constant array.
fn const_array<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
values: &[Instance<'ctx, Self>],
) -> Instance<'ctx, Array<AnyLen, Self>> {
macro_rules! make {
($t:expr, $into_value:expr) => {
$t.const_array(
&values
.iter()
.map(|x| $into_value(x.value.as_basic_value_enum()))
.collect_vec(),
)
};
}
let value = match self.llvm_type(generator, ctx).as_basic_type_enum() {
BasicTypeEnum::ArrayType(t) => make!(t, BasicValueEnum::into_array_value),
BasicTypeEnum::IntType(t) => make!(t, BasicValueEnum::into_int_value),
BasicTypeEnum::FloatType(t) => make!(t, BasicValueEnum::into_float_value),
BasicTypeEnum::PointerType(t) => make!(t, BasicValueEnum::into_pointer_value),
BasicTypeEnum::StructType(t) => make!(t, BasicValueEnum::into_struct_value),
BasicTypeEnum::VectorType(t) => make!(t, BasicValueEnum::into_vector_value),
};
Array { len: AnyLen(values.len() as u32), item: *self }
.check_value(generator, ctx, value)
.unwrap()
}
}
#[derive(Debug, Clone, Copy)]
pub struct Instance<'ctx, M: Model<'ctx>> {
/// The model of this instance.
pub model: M,
/// The value of this instance.
///
/// It is guaranteed the [`BasicType`] of `value` is consistent with that of `model`.
pub value: M::Value,
}

View File

@ -1,93 +0,0 @@
use std::fmt;
use inkwell::{
context::Context,
types::{BasicType, FloatType},
values::FloatValue,
};
use super::*;
use crate::codegen::CodeGenerator;
pub trait FloatKind<'ctx>: fmt::Debug + Clone + Copy {
fn get_float_type<G: CodeGenerator + ?Sized>(
&self,
generator: &G,
ctx: &'ctx Context,
) -> FloatType<'ctx>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Float32;
#[derive(Debug, Clone, Copy, Default)]
pub struct Float64;
impl<'ctx> FloatKind<'ctx> for Float32 {
fn get_float_type<G: CodeGenerator + ?Sized>(
&self,
_generator: &G,
ctx: &'ctx Context,
) -> FloatType<'ctx> {
ctx.f32_type()
}
}
impl<'ctx> FloatKind<'ctx> for Float64 {
fn get_float_type<G: CodeGenerator + ?Sized>(
&self,
_generator: &G,
ctx: &'ctx Context,
) -> FloatType<'ctx> {
ctx.f64_type()
}
}
#[derive(Debug, Clone, Copy)]
pub struct AnyFloat<'ctx>(FloatType<'ctx>);
impl<'ctx> FloatKind<'ctx> for AnyFloat<'ctx> {
fn get_float_type<G: CodeGenerator + ?Sized>(
&self,
_generator: &G,
_ctx: &'ctx Context,
) -> FloatType<'ctx> {
self.0
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Float<N>(pub N);
impl<'ctx, N: FloatKind<'ctx>> Model<'ctx> for Float<N> {
type Value = FloatValue<'ctx>;
type Type = FloatType<'ctx>;
fn llvm_type<G: CodeGenerator + ?Sized>(
&self,
generator: &G,
ctx: &'ctx Context,
) -> Self::Type {
self.0.get_float_type(generator, ctx)
}
fn check_type<T: BasicType<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
ty: T,
) -> Result<(), ModelError> {
let ty = ty.as_basic_type_enum();
let Ok(ty) = FloatType::try_from(ty) else {
return Err(ModelError(format!("Expecting FloatType, but got {ty:?}")));
};
let exp_ty = self.0.get_float_type(generator, ctx);
// TODO: Inkwell does not have get_bit_width for FloatType?
if ty != exp_ty {
return Err(ModelError(format!("Expecting {exp_ty:?}, but got {ty:?}")));
}
Ok(())
}
}

View File

@ -1,121 +0,0 @@
use inkwell::{
attributes::{Attribute, AttributeLoc},
types::{BasicMetadataTypeEnum, BasicType, FunctionType},
values::{AnyValue, BasicMetadataValueEnum, BasicValue, BasicValueEnum, CallSiteValue},
};
use itertools::Itertools;
use super::*;
use crate::codegen::{CodeGenContext, CodeGenerator};
#[derive(Debug, Clone, Copy)]
struct Arg<'ctx> {
ty: BasicMetadataTypeEnum<'ctx>,
val: BasicMetadataValueEnum<'ctx>,
}
/// A convenience structure to construct & call an LLVM function.
///
/// ### Usage
///
/// The syntax is like this:
/// ```ignore
/// let result = CallFunction::begin("my_function_name")
/// .attrs(...)
/// .arg(arg1)
/// .arg(arg2)
/// .arg(arg3)
/// .returning("my_function_result", Int32);
/// ```
///
/// The function `my_function_name` is called when `.returning()` (or its variants) is called, returning
/// the result as an `Instance<'ctx, Int<Int32>>`.
///
/// If `my_function_name` has not been declared in `ctx.module`, once `.returning()` is called, a function
/// declaration of `my_function_name` is added to `ctx.module`, where the [`FunctionType`] is deduced from
/// the argument types and returning type.
pub struct FnCall<'ctx, 'a, 'b, 'c, 'd, G: CodeGenerator + ?Sized> {
generator: &'d mut G,
ctx: &'b CodeGenContext<'ctx, 'a>,
/// Function name
name: &'c str,
/// Call arguments
args: Vec<Arg<'ctx>>,
/// LLVM function Attributes
attrs: Vec<&'static str>,
}
impl<'ctx, 'a, 'b, 'c, 'd, G: CodeGenerator + ?Sized> FnCall<'ctx, 'a, 'b, 'c, 'd, G> {
pub fn builder(generator: &'d mut G, ctx: &'b CodeGenContext<'ctx, 'a>, name: &'c str) -> Self {
FnCall { generator, ctx, name, args: Vec::new(), attrs: Vec::new() }
}
/// Push a list of LLVM function attributes to the function declaration.
#[must_use]
pub fn attrs(mut self, attrs: Vec<&'static str>) -> Self {
self.attrs = attrs;
self
}
/// Push a call argument to the function call.
#[allow(clippy::needless_pass_by_value)]
#[must_use]
pub fn arg<M: Model<'ctx>>(mut self, arg: Instance<'ctx, M>) -> Self {
let arg = Arg {
ty: arg.model.llvm_type(self.generator, self.ctx.ctx).as_basic_type_enum().into(),
val: arg.value.as_basic_value_enum().into(),
};
self.args.push(arg);
self
}
/// Call the function and expect the function to return a value of type of `return_model`.
#[must_use]
pub fn returning<M: Model<'ctx>>(self, name: &str, return_model: M) -> Instance<'ctx, M> {
let ret_ty = return_model.llvm_type(self.generator, self.ctx.ctx);
let ret = self.call(|tys| ret_ty.fn_type(tys, false), name);
let ret = BasicValueEnum::try_from(ret.as_any_value_enum()).unwrap(); // Must work
let ret = return_model.check_value(self.generator, self.ctx.ctx, ret).unwrap(); // Must work
ret
}
/// Like [`CallFunction::returning_`] but `return_model` is automatically inferred.
#[must_use]
pub fn returning_auto<M: Model<'ctx> + Default>(self, name: &str) -> Instance<'ctx, M> {
self.returning(name, M::default())
}
/// Call the function and expect the function to return a void-type.
pub fn returning_void(self) {
let ret_ty = self.ctx.ctx.void_type();
let _ = self.call(|tys| ret_ty.fn_type(tys, false), "");
}
fn call<F>(&self, make_fn_type: F, return_value_name: &str) -> CallSiteValue<'ctx>
where
F: FnOnce(&[BasicMetadataTypeEnum<'ctx>]) -> FunctionType<'ctx>,
{
// Get the LLVM function.
let func = self.ctx.module.get_function(self.name).unwrap_or_else(|| {
// Declare the function if it doesn't exist.
let tys = self.args.iter().map(|arg| arg.ty).collect_vec();
let func_type = make_fn_type(&tys);
let func = self.ctx.module.add_function(self.name, func_type, None);
for attr in &self.attrs {
func.add_attribute(
AttributeLoc::Function,
self.ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0),
);
}
func
});
let vals = self.args.iter().map(|arg| arg.val).collect_vec();
self.ctx.builder.build_call(func, &vals, return_value_name).unwrap()
}
}

View File

@ -1,421 +0,0 @@
use std::{cmp::Ordering, fmt};
use inkwell::{
context::Context,
types::{BasicType, IntType},
values::IntValue,
IntPredicate,
};
use super::*;
use crate::codegen::{CodeGenContext, CodeGenerator};
pub trait IntKind<'ctx>: fmt::Debug + Clone + Copy {
fn get_int_type<G: CodeGenerator + ?Sized>(
&self,
generator: &G,
ctx: &'ctx Context,
) -> IntType<'ctx>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Bool;
#[derive(Debug, Clone, Copy, Default)]
pub struct Byte;
#[derive(Debug, Clone, Copy, Default)]
pub struct Int32;
#[derive(Debug, Clone, Copy, Default)]
pub struct Int64;
#[derive(Debug, Clone, Copy, Default)]
pub struct SizeT;
impl<'ctx> IntKind<'ctx> for Bool {
fn get_int_type<G: CodeGenerator + ?Sized>(
&self,
_generator: &G,
ctx: &'ctx Context,
) -> IntType<'ctx> {
ctx.bool_type()
}
}
impl<'ctx> IntKind<'ctx> for Byte {
fn get_int_type<G: CodeGenerator + ?Sized>(
&self,
_generator: &G,
ctx: &'ctx Context,
) -> IntType<'ctx> {
ctx.i8_type()
}
}
impl<'ctx> IntKind<'ctx> for Int32 {
fn get_int_type<G: CodeGenerator + ?Sized>(
&self,
_generator: &G,
ctx: &'ctx Context,
) -> IntType<'ctx> {
ctx.i32_type()
}
}
impl<'ctx> IntKind<'ctx> for Int64 {
fn get_int_type<G: CodeGenerator + ?Sized>(
&self,
_generator: &G,
ctx: &'ctx Context,
) -> IntType<'ctx> {
ctx.i64_type()
}
}
impl<'ctx> IntKind<'ctx> for SizeT {
fn get_int_type<G: CodeGenerator + ?Sized>(
&self,
generator: &G,
ctx: &'ctx Context,
) -> IntType<'ctx> {
generator.get_size_type(ctx)
}
}
#[derive(Debug, Clone, Copy)]
pub struct AnyInt<'ctx>(pub IntType<'ctx>);
impl<'ctx> IntKind<'ctx> for AnyInt<'ctx> {
fn get_int_type<G: CodeGenerator + ?Sized>(
&self,
_generator: &G,
_ctx: &'ctx Context,
) -> IntType<'ctx> {
self.0
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Int<N>(pub N);
impl<'ctx, N: IntKind<'ctx>> Model<'ctx> for Int<N> {
type Value = IntValue<'ctx>;
type Type = IntType<'ctx>;
fn llvm_type<G: CodeGenerator + ?Sized>(
&self,
generator: &G,
ctx: &'ctx Context,
) -> Self::Type {
self.0.get_int_type(generator, ctx)
}
fn check_type<T: BasicType<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
ty: T,
) -> Result<(), ModelError> {
let ty = ty.as_basic_type_enum();
let Ok(ty) = IntType::try_from(ty) else {
return Err(ModelError(format!("Expecting IntType, but got {ty:?}")));
};
let exp_ty = self.0.get_int_type(generator, ctx);
if ty.get_bit_width() != exp_ty.get_bit_width() {
return Err(ModelError(format!(
"Expecting IntType to have {} bit(s), but got {} bit(s)",
exp_ty.get_bit_width(),
ty.get_bit_width()
)));
}
Ok(())
}
}
impl<'ctx, N: IntKind<'ctx>> Int<N> {
pub fn const_int<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
value: u64,
sign_extend: bool,
) -> Instance<'ctx, Self> {
let value = self.llvm_type(generator, ctx).const_int(value, sign_extend);
unsafe { self.believe_value(value) }
}
pub fn const_0<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
) -> Instance<'ctx, Self> {
let value = self.llvm_type(generator, ctx).const_zero();
unsafe { self.believe_value(value) }
}
pub fn const_1<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
) -> Instance<'ctx, Self> {
self.const_int(generator, ctx, 1, false)
}
pub fn const_all_ones<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
) -> Instance<'ctx, Self> {
let value = self.llvm_type(generator, ctx).const_all_ones();
unsafe { self.believe_value(value) }
}
pub fn s_extend_or_bit_cast<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
value: IntValue<'ctx>,
) -> Instance<'ctx, Self> {
assert!(
value.get_type().get_bit_width()
<= self.0.get_int_type(generator, ctx.ctx).get_bit_width()
);
let value = ctx
.builder
.build_int_s_extend_or_bit_cast(value, self.llvm_type(generator, ctx.ctx), "")
.unwrap();
unsafe { self.believe_value(value) }
}
pub fn s_extend<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
value: IntValue<'ctx>,
) -> Instance<'ctx, Self> {
assert!(
value.get_type().get_bit_width()
< self.0.get_int_type(generator, ctx.ctx).get_bit_width()
);
let value =
ctx.builder.build_int_s_extend(value, self.llvm_type(generator, ctx.ctx), "").unwrap();
unsafe { self.believe_value(value) }
}
pub fn z_extend_or_bit_cast<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
value: IntValue<'ctx>,
) -> Instance<'ctx, Self> {
assert!(
value.get_type().get_bit_width()
<= self.0.get_int_type(generator, ctx.ctx).get_bit_width()
);
let value = ctx
.builder
.build_int_z_extend_or_bit_cast(value, self.llvm_type(generator, ctx.ctx), "")
.unwrap();
unsafe { self.believe_value(value) }
}
pub fn z_extend<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
value: IntValue<'ctx>,
) -> Instance<'ctx, Self> {
assert!(
value.get_type().get_bit_width()
< self.0.get_int_type(generator, ctx.ctx).get_bit_width()
);
let value =
ctx.builder.build_int_z_extend(value, self.llvm_type(generator, ctx.ctx), "").unwrap();
unsafe { self.believe_value(value) }
}
pub fn truncate_or_bit_cast<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
value: IntValue<'ctx>,
) -> Instance<'ctx, Self> {
assert!(
value.get_type().get_bit_width()
>= self.0.get_int_type(generator, ctx.ctx).get_bit_width()
);
let value = ctx
.builder
.build_int_truncate_or_bit_cast(value, self.llvm_type(generator, ctx.ctx), "")
.unwrap();
unsafe { self.believe_value(value) }
}
pub fn truncate<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
value: IntValue<'ctx>,
) -> Instance<'ctx, Self> {
assert!(
value.get_type().get_bit_width()
> self.0.get_int_type(generator, ctx.ctx).get_bit_width()
);
let value =
ctx.builder.build_int_truncate(value, self.llvm_type(generator, ctx.ctx), "").unwrap();
unsafe { self.believe_value(value) }
}
/// `sext` or `trunc` an int to this model's int type. Does nothing if equal bit-widths.
pub fn s_extend_or_truncate<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
value: IntValue<'ctx>,
) -> Instance<'ctx, Self> {
let their_width = value.get_type().get_bit_width();
let our_width = self.0.get_int_type(generator, ctx.ctx).get_bit_width();
match their_width.cmp(&our_width) {
Ordering::Less => self.s_extend(generator, ctx, value),
Ordering::Equal => unsafe { self.believe_value(value) },
Ordering::Greater => self.truncate(generator, ctx, value),
}
}
/// `zext` or `trunc` an int to this model's int type. Does nothing if equal bit-widths.
pub fn z_extend_or_truncate<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
value: IntValue<'ctx>,
) -> Instance<'ctx, Self> {
let their_width = value.get_type().get_bit_width();
let our_width = self.0.get_int_type(generator, ctx.ctx).get_bit_width();
match their_width.cmp(&our_width) {
Ordering::Less => self.z_extend(generator, ctx, value),
Ordering::Equal => unsafe { self.believe_value(value) },
Ordering::Greater => self.truncate(generator, ctx, value),
}
}
}
impl Int<Bool> {
#[must_use]
pub fn const_false<'ctx, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
) -> Instance<'ctx, Self> {
self.const_int(generator, ctx, 0, false)
}
#[must_use]
pub fn const_true<'ctx, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
) -> Instance<'ctx, Self> {
self.const_int(generator, ctx, 1, false)
}
}
impl<'ctx, N: IntKind<'ctx>> Instance<'ctx, Int<N>> {
pub fn s_extend_or_bit_cast<NewN: IntKind<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
to_int_kind: NewN,
) -> Instance<'ctx, Int<NewN>> {
Int(to_int_kind).s_extend_or_bit_cast(generator, ctx, self.value)
}
pub fn s_extend<NewN: IntKind<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
to_int_kind: NewN,
) -> Instance<'ctx, Int<NewN>> {
Int(to_int_kind).s_extend(generator, ctx, self.value)
}
pub fn z_extend_or_bit_cast<NewN: IntKind<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
to_int_kind: NewN,
) -> Instance<'ctx, Int<NewN>> {
Int(to_int_kind).z_extend_or_bit_cast(generator, ctx, self.value)
}
pub fn z_extend<NewN: IntKind<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
to_int_kind: NewN,
) -> Instance<'ctx, Int<NewN>> {
Int(to_int_kind).z_extend(generator, ctx, self.value)
}
pub fn truncate_or_bit_cast<NewN: IntKind<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
to_int_kind: NewN,
) -> Instance<'ctx, Int<NewN>> {
Int(to_int_kind).truncate_or_bit_cast(generator, ctx, self.value)
}
pub fn truncate<NewN: IntKind<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
to_int_kind: NewN,
) -> Instance<'ctx, Int<NewN>> {
Int(to_int_kind).truncate(generator, ctx, self.value)
}
pub fn s_extend_or_truncate<NewN: IntKind<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
to_int_kind: NewN,
) -> Instance<'ctx, Int<NewN>> {
Int(to_int_kind).s_extend_or_truncate(generator, ctx, self.value)
}
pub fn z_extend_or_truncate<NewN: IntKind<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
to_int_kind: NewN,
) -> Instance<'ctx, Int<NewN>> {
Int(to_int_kind).z_extend_or_truncate(generator, ctx, self.value)
}
#[must_use]
pub fn add(&self, ctx: &CodeGenContext<'ctx, '_>, other: Self) -> Self {
let value = ctx.builder.build_int_add(self.value, other.value, "").unwrap();
unsafe { self.model.believe_value(value) }
}
#[must_use]
pub fn sub(&self, ctx: &CodeGenContext<'ctx, '_>, other: Self) -> Self {
let value = ctx.builder.build_int_sub(self.value, other.value, "").unwrap();
unsafe { self.model.believe_value(value) }
}
#[must_use]
pub fn mul(&self, ctx: &CodeGenContext<'ctx, '_>, other: Self) -> Self {
let value = ctx.builder.build_int_mul(self.value, other.value, "").unwrap();
unsafe { self.model.believe_value(value) }
}
pub fn compare(
&self,
ctx: &CodeGenContext<'ctx, '_>,
op: IntPredicate,
other: Self,
) -> Instance<'ctx, Int<Bool>> {
let value = ctx.builder.build_int_compare(op, self.value, other.value, "").unwrap();
unsafe { Int(Bool).believe_value(value) }
}
}

View File

@ -1,17 +0,0 @@
mod any;
mod array;
mod core;
mod float;
pub mod function;
mod int;
mod ptr;
mod structure;
pub mod util;
pub use any::*;
pub use array::*;
pub use core::*;
pub use float::*;
pub use int::*;
pub use ptr::*;
pub use structure::*;

View File

@ -1,222 +0,0 @@
use inkwell::{
context::Context,
types::{BasicType, BasicTypeEnum, PointerType},
values::{IntValue, PointerValue},
AddressSpace,
};
use super::*;
use crate::codegen::{llvm_intrinsics::call_memcpy_generic, CodeGenContext, CodeGenerator};
/// A model for [`PointerType`].
///
/// `Item` is the element type this pointer is pointing to, and should be of a [`Model`].
///
// TODO: LLVM 15: `Item` is a Rust type-hint for the LLVM type of value the `.store()/.load()` family
// of functions return. If a truly opaque pointer is needed, tell the programmer to use `OpaquePtr`.
#[derive(Debug, Clone, Copy, Default)]
pub struct Ptr<Item>(pub Item);
/// An opaque pointer. Like [`Ptr`] but without any Rust type-hints about its element type.
///
/// `.load()/.store()` is not available for [`Instance`]s of opaque pointers.
pub type OpaquePtr = Ptr<()>;
// TODO: LLVM 15: `Item: Model<'ctx>` don't even need to be a model anymore. It will only be
// a type-hint for the `.load()/.store()` functions for the `pointee_ty`.
//
// See https://thedan64.github.io/inkwell/inkwell/builder/struct.Builder.html#method.build_load.
impl<'ctx, Item: Model<'ctx>> Model<'ctx> for Ptr<Item> {
type Value = PointerValue<'ctx>;
type Type = PointerType<'ctx>;
fn llvm_type<G: CodeGenerator + ?Sized>(
&self,
generator: &G,
ctx: &'ctx Context,
) -> Self::Type {
// TODO: LLVM 15: ctx.ptr_type(AddressSpace::default())
self.0.llvm_type(generator, ctx).ptr_type(AddressSpace::default())
}
fn check_type<T: BasicType<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
ty: T,
) -> Result<(), ModelError> {
let ty = ty.as_basic_type_enum();
let Ok(ty) = PointerType::try_from(ty) else {
return Err(ModelError(format!("Expecting PointerType, but got {ty:?}")));
};
let elem_ty = ty.get_element_type();
let Ok(elem_ty) = BasicTypeEnum::try_from(elem_ty) else {
return Err(ModelError(format!(
"Expecting pointer element type to be a BasicTypeEnum, but got {elem_ty:?}"
)));
};
// TODO: inkwell `get_element_type()` will be deprecated.
// Remove the check for `get_element_type()` when the time comes.
self.0
.check_type(generator, ctx, elem_ty)
.map_err(|err| err.under_context("a PointerType"))?;
Ok(())
}
}
impl<'ctx, Item: Model<'ctx>> Ptr<Item> {
/// Return a ***constant*** nullptr.
pub fn nullptr<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
) -> Instance<'ctx, Ptr<Item>> {
let ptr = self.llvm_type(generator, ctx).const_null();
unsafe { self.believe_value(ptr) }
}
/// Cast a pointer into this model with [`inkwell::builder::Builder::build_pointer_cast`]
pub fn pointer_cast<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
ptr: PointerValue<'ctx>,
) -> Instance<'ctx, Ptr<Item>> {
// TODO: LLVM 15: Write in an impl where `Item` does not have to be `Model<'ctx>`.
// TODO: LLVM 15: This function will only have to be:
// ```
// return self.believe_value(ptr);
// ```
let t = self.llvm_type(generator, ctx.ctx);
let ptr = ctx.builder.build_pointer_cast(ptr, t, "").unwrap();
unsafe { self.believe_value(ptr) }
}
}
impl<'ctx, Item: Model<'ctx>> Instance<'ctx, Ptr<Item>> {
/// Offset the pointer by [`inkwell::builder::Builder::build_in_bounds_gep`].
#[must_use]
pub fn offset(
&self,
ctx: &CodeGenContext<'ctx, '_>,
offset: IntValue<'ctx>,
) -> Instance<'ctx, Ptr<Item>> {
let p = unsafe { ctx.builder.build_in_bounds_gep(self.value, &[offset], "").unwrap() };
unsafe { self.model.believe_value(p) }
}
/// Offset the pointer by [`inkwell::builder::Builder::build_in_bounds_gep`] by a constant offset.
#[must_use]
pub fn offset_const(
&self,
ctx: &CodeGenContext<'ctx, '_>,
offset: i64,
) -> Instance<'ctx, Ptr<Item>> {
let offset = ctx.ctx.i32_type().const_int(offset as u64, true);
self.offset(ctx, offset)
}
pub fn set_index(
&self,
ctx: &CodeGenContext<'ctx, '_>,
index: IntValue<'ctx>,
value: Instance<'ctx, Item>,
) {
self.offset(ctx, index).store(ctx, value);
}
pub fn set_index_const(
&self,
ctx: &CodeGenContext<'ctx, '_>,
index: i64,
value: Instance<'ctx, Item>,
) {
self.offset_const(ctx, index).store(ctx, value);
}
pub fn get_index<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
index: IntValue<'ctx>,
) -> Instance<'ctx, Item> {
self.offset(ctx, index).load(generator, ctx)
}
pub fn get_index_const<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
index: i64,
) -> Instance<'ctx, Item> {
self.offset_const(ctx, index).load(generator, ctx)
}
/// Load the value with [`inkwell::builder::Builder::build_load`].
pub fn load<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
) -> Instance<'ctx, Item> {
let value = ctx.builder.build_load(self.value, "").unwrap();
self.model.0.check_value(generator, ctx.ctx, value).unwrap() // If unwrap() panics, there is a logic error.
}
/// Store a value with [`inkwell::builder::Builder::build_store`].
pub fn store(&self, ctx: &CodeGenContext<'ctx, '_>, value: Instance<'ctx, Item>) {
ctx.builder.build_store(self.value, value.value).unwrap();
}
/// Return a casted pointer of element type `NewElement` with [`inkwell::builder::Builder::build_pointer_cast`].
pub fn pointer_cast<NewItem: Model<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
new_item: NewItem,
) -> Instance<'ctx, Ptr<NewItem>> {
// TODO: LLVM 15: Write in an impl where `Item` does not have to be `Model<'ctx>`.
Ptr(new_item).pointer_cast(generator, ctx, self.value)
}
/// Cast this pointer to `uint8_t*`
pub fn cast_to_pi8<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
) -> Instance<'ctx, Ptr<Int<Byte>>> {
Ptr(Int(Byte)).pointer_cast(generator, ctx, self.value)
}
/// Check if the pointer is null with [`inkwell::builder::Builder::build_is_null`].
pub fn is_null(&self, ctx: &CodeGenContext<'ctx, '_>) -> Instance<'ctx, Int<Bool>> {
let value = ctx.builder.build_is_null(self.value, "").unwrap();
unsafe { Int(Bool).believe_value(value) }
}
/// Check if the pointer is not null with [`inkwell::builder::Builder::build_is_not_null`].
pub fn is_not_null(&self, ctx: &CodeGenContext<'ctx, '_>) -> Instance<'ctx, Int<Bool>> {
let value = ctx.builder.build_is_not_null(self.value, "").unwrap();
unsafe { Int(Bool).believe_value(value) }
}
/// `memcpy` from another pointer.
pub fn copy_from<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
source: Self,
num_items: IntValue<'ctx>,
) {
// Force extend `num_items` and `itemsize` to `i64` so their types would match.
let itemsize = self.model.size_of(generator, ctx.ctx);
let itemsize = Int(SizeT).z_extend_or_truncate(generator, ctx, itemsize);
let num_items = Int(SizeT).z_extend_or_truncate(generator, ctx, num_items);
let totalsize = itemsize.mul(ctx, num_items);
let is_volatile = ctx.ctx.bool_type().const_zero(); // is_volatile = false
call_memcpy_generic(ctx, self.value, source.value, totalsize.value, is_volatile);
}
}

View File

@ -1,363 +0,0 @@
use std::fmt;
use inkwell::{
context::Context,
types::{BasicType, BasicTypeEnum, StructType},
values::{BasicValueEnum, StructValue},
};
use super::*;
use crate::codegen::{CodeGenContext, CodeGenerator};
/// A traveral that traverses a Rust `struct` that is used to declare an LLVM's struct's field types.
pub trait FieldTraversal<'ctx> {
/// Output type of [`FieldTraversal::add`].
type Output<M>;
/// Traverse through the type of a declared field and do something with it.
///
/// * `name` - The cosmetic name of the LLVM field. Used for debugging.
/// * `model` - The [`Model`] representing the LLVM type of this field.
fn add<M: Model<'ctx>>(&mut self, name: &'static str, model: M) -> Self::Output<M>;
/// Like [`FieldTraversal::add`] but [`Model`] is automatically inferred from its [`Default`] trait.
fn add_auto<M: Model<'ctx> + Default>(&mut self, name: &'static str) -> Self::Output<M> {
self.add(name, M::default())
}
}
/// Descriptor of an LLVM struct field.
#[derive(Debug, Clone, Copy)]
pub struct GepField<M> {
/// The GEP index of this field. This is the index to use with `build_gep`.
pub gep_index: u32,
/// The cosmetic name of this field.
pub name: &'static str,
/// The [`Model`] of this field's type.
pub model: M,
}
/// A traversal to calculate the GEP index of fields.
pub struct GepFieldTraversal {
/// The current GEP index.
gep_index_counter: u32,
}
impl<'ctx> FieldTraversal<'ctx> for GepFieldTraversal {
type Output<M> = GepField<M>;
fn add<M: Model<'ctx>>(&mut self, name: &'static str, model: M) -> Self::Output<M> {
let gep_index = self.gep_index_counter;
self.gep_index_counter += 1;
Self::Output { gep_index, name, model }
}
}
/// A traversal to collect the field types of a struct.
///
/// This is used to collect field types and construct the LLVM struct type with [`Context::struct_type`].
struct TypeFieldTraversal<'ctx, 'a, G: CodeGenerator + ?Sized> {
generator: &'a G,
ctx: &'ctx Context,
/// The collected field types so far in exact order.
field_types: Vec<BasicTypeEnum<'ctx>>,
}
impl<'ctx, 'a, G: CodeGenerator + ?Sized> FieldTraversal<'ctx> for TypeFieldTraversal<'ctx, 'a, G> {
type Output<M> = (); // Checking types return nothing.
fn add<M: Model<'ctx>>(&mut self, _name: &'static str, model: M) -> Self::Output<M> {
let t = model.llvm_type(self.generator, self.ctx).as_basic_type_enum();
self.field_types.push(t);
}
}
/// A traversal to check the types of fields.
struct CheckTypeFieldTraversal<'ctx, 'a, G: CodeGenerator + ?Sized> {
generator: &'a mut G,
ctx: &'ctx Context,
/// The current GEP index, so we can tell the index of the field we are checking
/// and report the GEP index.
gep_index_counter: u32,
/// The [`StructType`] to check.
scrutinee: StructType<'ctx>,
/// The list of collected errors so far.
errors: Vec<ModelError>,
}
impl<'ctx, 'a, G: CodeGenerator + ?Sized> FieldTraversal<'ctx>
for CheckTypeFieldTraversal<'ctx, 'a, G>
{
type Output<M> = (); // Checking types return nothing.
fn add<M: Model<'ctx>>(&mut self, name: &'static str, model: M) -> Self::Output<M> {
let gep_index = self.gep_index_counter;
self.gep_index_counter += 1;
if let Some(t) = self.scrutinee.get_field_type_at_index(gep_index) {
if let Err(err) = model.check_type(self.generator, self.ctx, t) {
self.errors
.push(err.under_context(format!("field #{gep_index} '{name}'").as_str()));
}
}
// Otherwise, it will be caught by Struct's `check_type`.
}
}
/// A trait for Rust structs identifying LLVM structures.
///
/// ### Example
///
/// Suppose you want to define this structure:
/// ```c
/// template <typename T>
/// struct ContiguousNDArray {
/// size_t ndims;
/// size_t* shape;
/// T* data;
/// }
/// ```
///
/// This is how it should be done:
/// ```ignore
/// pub struct ContiguousNDArrayFields<'ctx, F: FieldTraversal<'ctx>, Item: Model<'ctx>> {
/// pub ndims: F::Out<Int<SizeT>>,
/// pub shape: F::Out<Ptr<Int<SizeT>>>,
/// pub data: F::Out<Ptr<Item>>,
/// }
///
/// /// An ndarray without strides and non-opaque `data` field in NAC3.
/// #[derive(Debug, Clone, Copy)]
/// pub struct ContiguousNDArray<M> {
/// /// [`Model`] of the items.
/// pub item: M,
/// }
///
/// impl<'ctx, Item: Model<'ctx>> StructKind<'ctx> for ContiguousNDArray<Item> {
/// type Fields<F: FieldTraversal<'ctx>> = ContiguousNDArrayFields<'ctx, F, Item>;
///
/// fn traverse_fields<F: FieldTraversal<'ctx>>(&self, traversal: &mut F) -> Self::Fields<F> {
/// // The order of `traversal.add*` is important
/// Self::Fields {
/// ndims: traversal.add_auto("ndims"),
/// shape: traversal.add_auto("shape"),
/// data: traversal.add("data", Ptr(self.item)),
/// }
/// }
/// }
/// ```
///
/// The [`FieldTraversal`] here is a mechanism to allow the fields of `ContiguousNDArrayFields` to be
/// traversed to do useful work such as:
///
/// - To create the [`StructType`] of `ContiguousNDArray` by collecting [`BasicType`]s of the fields.
/// - To enable the `.gep(ctx, |f| f.ndims).store(ctx, ...)` syntax.
///
/// Suppose now that you have defined `ContiguousNDArray` and you want to allocate a `ContiguousNDArray`
/// with dtype `float64` in LLVM, this is how you do it:
/// ```ignore
/// type F64NDArray = Struct<ContiguousNDArray<Float<Float64>>>; // Type alias for leaner documentation
/// let model: F64NDArray = Struct(ContigousNDArray { item: Float(Float64) });
/// let ndarray: Instance<'ctx, Ptr<F64NDArray>> = model.alloca(generator, ctx);
/// ```
///
/// ...and here is how you may manipulate/access `ndarray`:
///
/// (NOTE: some arguments have been omitted)
///
/// ```ignore
/// // Get `&ndarray->data`
/// ndarray.gep(|f| f.data); // type: Instance<'ctx, Ptr<Float<Float64>>>
///
/// // Get `ndarray->ndims`
/// ndarray.get(|f| f.ndims); // type: Instance<'ctx, Int<SizeT>>
///
/// // Get `&ndarray->ndims`
/// ndarray.gep(|f| f.ndims); // type: Instance<'ctx, Ptr<Int<SizeT>>>
///
/// // Get `ndarray->shape[0]`
/// ndarray.get(|f| f.shape).get_index_const(0); // Instance<'ctx, Int<SizeT>>
///
/// // Get `&ndarray->shape[2]`
/// ndarray.get(|f| f.shape).offset_const(2); // Instance<'ctx, Ptr<Int<SizeT>>>
///
/// // Do `ndarray->ndims = 3;`
/// let num_3 = Int(SizeT).const_int(3);
/// ndarray.set(|f| f.ndims, num_3);
/// ```
pub trait StructKind<'ctx>: fmt::Debug + Clone + Copy {
/// The associated fields of this struct.
type Fields<F: FieldTraversal<'ctx>>;
/// Traverse through all fields of this [`StructKind`].
///
/// Only used internally in this module for implementing other components.
fn iter_fields<F: FieldTraversal<'ctx>>(&self, traversal: &mut F) -> Self::Fields<F>;
/// Get a convenience structure to get a struct field's GEP index through its corresponding Rust field.
///
/// Only used internally in this module for implementing other components.
fn fields(&self) -> Self::Fields<GepFieldTraversal> {
self.iter_fields(&mut GepFieldTraversal { gep_index_counter: 0 })
}
/// Get the LLVM [`StructType`] of this [`StructKind`].
fn get_struct_type<G: CodeGenerator + ?Sized>(
&self,
generator: &G,
ctx: &'ctx Context,
) -> StructType<'ctx> {
let mut traversal = TypeFieldTraversal { generator, ctx, field_types: Vec::new() };
self.iter_fields(&mut traversal);
ctx.struct_type(&traversal.field_types, false)
}
}
/// A model for LLVM struct.
///
/// `S` should be of a [`StructKind`].
#[derive(Debug, Clone, Copy, Default)]
pub struct Struct<S>(pub S);
impl<'ctx, S: StructKind<'ctx>> Struct<S> {
/// Create a constant struct value from its fields.
///
/// This function also validates `fields` and panic when there is something wrong.
pub fn const_struct<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
fields: &[BasicValueEnum<'ctx>],
) -> Instance<'ctx, Self> {
// NOTE: There *could* have been a functor `F<M> = Instance<'ctx, M>` for `S::Fields<F>`
// to create a more user-friendly interface, but Rust's type system is not sophisticated enough
// and if you try doing that Rust would force you put lifetimes everywhere.
let val = ctx.const_struct(fields, false);
self.check_value(generator, ctx, val).unwrap()
}
}
impl<'ctx, S: StructKind<'ctx>> Model<'ctx> for Struct<S> {
type Value = StructValue<'ctx>;
type Type = StructType<'ctx>;
fn llvm_type<G: CodeGenerator + ?Sized>(
&self,
generator: &G,
ctx: &'ctx Context,
) -> Self::Type {
self.0.get_struct_type(generator, ctx)
}
fn check_type<T: BasicType<'ctx>, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
ty: T,
) -> Result<(), ModelError> {
let ty = ty.as_basic_type_enum();
let Ok(ty) = StructType::try_from(ty) else {
return Err(ModelError(format!("Expecting StructType, but got {ty:?}")));
};
// Check each field individually.
let mut traversal = CheckTypeFieldTraversal {
generator,
ctx,
gep_index_counter: 0,
errors: Vec::new(),
scrutinee: ty,
};
self.0.iter_fields(&mut traversal);
// Check the number of fields.
let exp_num_fields = traversal.gep_index_counter;
let got_num_fields = u32::try_from(ty.get_field_types().len()).unwrap();
if exp_num_fields != got_num_fields {
return Err(ModelError(format!(
"Expecting StructType with {exp_num_fields} field(s), but got {got_num_fields}"
)));
}
if !traversal.errors.is_empty() {
// Currently, only the first error is reported.
return Err(traversal.errors[0].clone());
}
Ok(())
}
}
impl<'ctx, S: StructKind<'ctx>> Instance<'ctx, Struct<S>> {
/// Get a field with [`StructValue::get_field_at_index`].
pub fn get_field<G: CodeGenerator + ?Sized, M, GetField>(
&self,
generator: &mut G,
ctx: &'ctx Context,
get_field: GetField,
) -> Instance<'ctx, M>
where
M: Model<'ctx>,
GetField: FnOnce(S::Fields<GepFieldTraversal>) -> GepField<M>,
{
let field = get_field(self.model.0.fields());
let val = self.value.get_field_at_index(field.gep_index).unwrap();
field.model.check_value(generator, ctx, val).unwrap()
}
}
impl<'ctx, S: StructKind<'ctx>> Instance<'ctx, Ptr<Struct<S>>> {
/// Get a pointer to a field with [`Builder::build_in_bounds_gep`].
pub fn gep<M, GetField>(
&self,
ctx: &CodeGenContext<'ctx, '_>,
get_field: GetField,
) -> Instance<'ctx, Ptr<M>>
where
M: Model<'ctx>,
GetField: FnOnce(S::Fields<GepFieldTraversal>) -> GepField<M>,
{
let field = get_field(self.model.0 .0.fields());
let llvm_i32 = ctx.ctx.i32_type();
let ptr = unsafe {
ctx.builder
.build_in_bounds_gep(
self.value,
&[llvm_i32.const_zero(), llvm_i32.const_int(u64::from(field.gep_index), false)],
field.name,
)
.unwrap()
};
unsafe { Ptr(field.model).believe_value(ptr) }
}
/// Convenience function equivalent to `.gep(...).load(...)`.
pub fn get<M, GetField, G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
get_field: GetField,
) -> Instance<'ctx, M>
where
M: Model<'ctx>,
GetField: FnOnce(S::Fields<GepFieldTraversal>) -> GepField<M>,
{
self.gep(ctx, get_field).load(generator, ctx)
}
/// Convenience function equivalent to `.gep(...).store(...)`.
pub fn set<M, GetField>(
&self,
ctx: &CodeGenContext<'ctx, '_>,
get_field: GetField,
value: Instance<'ctx, M>,
) where
M: Model<'ctx>,
GetField: FnOnce(S::Fields<GepFieldTraversal>) -> GepField<M>,
{
self.gep(ctx, get_field).store(ctx, value);
}
}

View File

@ -1,41 +0,0 @@
use super::*;
use crate::codegen::{
stmt::{gen_for_callback_incrementing, BreakContinueHooks},
CodeGenContext, CodeGenerator,
};
/// Like [`gen_for_callback_incrementing`] with [`Model`] abstractions.
///
/// The value for `stop` is exclusive.
pub fn gen_for_model<'ctx, 'a, G, F, N>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,
start: Instance<'ctx, Int<N>>,
stop: Instance<'ctx, Int<N>>,
step: Instance<'ctx, Int<N>>,
body: F,
) -> Result<(), String>
where
G: CodeGenerator + ?Sized,
F: FnOnce(
&mut G,
&mut CodeGenContext<'ctx, 'a>,
BreakContinueHooks<'ctx>,
Instance<'ctx, Int<N>>,
) -> Result<(), String>,
N: IntKind<'ctx> + Default,
{
let int_model = Int(N::default());
gen_for_callback_incrementing(
generator,
ctx,
None,
start.value,
(stop.value, false),
|g, ctx, hooks, i| {
let i = unsafe { int_model.believe_value(i) };
body(g, ctx, hooks, i)
},
step.value,
)
}

View File

@ -7,17 +7,18 @@ use nac3parser::ast::StrRef;
use super::{
macros::codegen_unreachable,
model::*,
object::{
any::AnyObject,
ndarray::{nditer::NDIterHandle, shape_util::parse_numpy_int_sequence, NDArrayObject},
},
stmt::gen_for_callback,
types::ndarray::{NDArrayType, NDIterType},
values::{ndarray::shape::parse_numpy_int_sequence, ProxyValue},
CodeGenContext, CodeGenerator,
};
use crate::{
symbol_resolver::ValueEnum,
toplevel::{helper::extract_ndims, numpy::unpack_ndarray_var_tys, DefinitionId},
toplevel::{
helper::{arraylike_flatten_element_type, extract_ndims},
numpy::unpack_ndarray_var_tys,
DefinitionId,
},
typecheck::typedef::{FunSignature, Type},
};
@ -36,12 +37,14 @@ pub fn gen_ndarray_empty<'ctx>(
let shape_arg = args[0].1.clone().to_basic_value_enum(context, generator, shape_ty)?;
let (dtype, ndims) = unpack_ndarray_var_tys(&mut context.unifier, fun.0.ret);
let llvm_dtype = context.get_llvm_type(generator, dtype);
let ndims = extract_ndims(&context.unifier, ndims);
let shape = AnyObject { value: shape_arg, ty: shape_ty };
let (_, shape) = parse_numpy_int_sequence(generator, context, shape);
let ndarray = NDArrayObject::make_np_empty(generator, context, dtype, ndims, shape);
Ok(ndarray.instance.value)
let shape = parse_numpy_int_sequence(generator, context, (shape_ty, shape_arg));
let ndarray = NDArrayType::new(generator, context.ctx, llvm_dtype, ndims)
.construct_numpy_empty(generator, context, &shape, None);
Ok(ndarray.as_base_value())
}
/// Generates LLVM IR for `ndarray.zeros`.
@ -59,12 +62,14 @@ pub fn gen_ndarray_zeros<'ctx>(
let shape_arg = args[0].1.clone().to_basic_value_enum(context, generator, shape_ty)?;
let (dtype, ndims) = unpack_ndarray_var_tys(&mut context.unifier, fun.0.ret);
let llvm_dtype = context.get_llvm_type(generator, dtype);
let ndims = extract_ndims(&context.unifier, ndims);
let shape = AnyObject { value: shape_arg, ty: shape_ty };
let (_, shape) = parse_numpy_int_sequence(generator, context, shape);
let ndarray = NDArrayObject::make_np_zeros(generator, context, dtype, ndims, shape);
Ok(ndarray.instance.value)
let shape = parse_numpy_int_sequence(generator, context, (shape_ty, shape_arg));
let ndarray = NDArrayType::new(generator, context.ctx, llvm_dtype, ndims)
.construct_numpy_zeros(generator, context, dtype, &shape, None);
Ok(ndarray.as_base_value())
}
/// Generates LLVM IR for `ndarray.ones`.
@ -82,12 +87,14 @@ pub fn gen_ndarray_ones<'ctx>(
let shape_arg = args[0].1.clone().to_basic_value_enum(context, generator, shape_ty)?;
let (dtype, ndims) = unpack_ndarray_var_tys(&mut context.unifier, fun.0.ret);
let llvm_dtype = context.get_llvm_type(generator, dtype);
let ndims = extract_ndims(&context.unifier, ndims);
let shape = AnyObject { value: shape_arg, ty: shape_ty };
let (_, shape) = parse_numpy_int_sequence(generator, context, shape);
let ndarray = NDArrayObject::make_np_ones(generator, context, dtype, ndims, shape);
Ok(ndarray.instance.value)
let shape = parse_numpy_int_sequence(generator, context, (shape_ty, shape_arg));
let ndarray = NDArrayType::new(generator, context.ctx, llvm_dtype, ndims)
.construct_numpy_ones(generator, context, dtype, &shape, None);
Ok(ndarray.as_base_value())
}
/// Generates LLVM IR for `ndarray.full`.
@ -108,13 +115,19 @@ pub fn gen_ndarray_full<'ctx>(
args[1].1.clone().to_basic_value_enum(context, generator, fill_value_ty)?;
let (dtype, ndims) = unpack_ndarray_var_tys(&mut context.unifier, fun.0.ret);
let llvm_dtype = context.get_llvm_type(generator, dtype);
let ndims = extract_ndims(&context.unifier, ndims);
let shape = AnyObject { value: shape_arg, ty: shape_ty };
let (_, shape) = parse_numpy_int_sequence(generator, context, shape);
let ndarray =
NDArrayObject::make_np_full(generator, context, dtype, ndims, shape, fill_value_arg);
Ok(ndarray.instance.value)
let shape = parse_numpy_int_sequence(generator, context, (shape_ty, shape_arg));
let ndarray = NDArrayType::new(generator, context.ctx, llvm_dtype, ndims).construct_numpy_full(
generator,
context,
&shape,
fill_value_arg,
None,
);
Ok(ndarray.as_base_value())
}
pub fn gen_ndarray_array<'ctx>(
@ -148,13 +161,12 @@ pub fn gen_ndarray_array<'ctx>(
let (_, ndims) = unpack_ndarray_var_tys(&mut context.unifier, fun.0.ret);
let ndims = extract_ndims(&context.unifier, ndims);
let object = AnyObject { value: obj_arg, ty: obj_ty };
// NAC3 booleans are i8.
let copy = Int(Bool).truncate(generator, context, copy_arg.into_int_value());
let ndarray = NDArrayObject::make_np_array(generator, context, object, copy)
let copy = generator.bool_to_i1(context, copy_arg.into_int_value());
let ndarray = NDArrayType::from_unifier_type(generator, context, fun.0.ret)
.construct_numpy_array(generator, context, (obj_ty, obj_arg), copy, None)
.atleast_nd(generator, context, ndims);
Ok(ndarray.instance.value)
Ok(ndarray.as_base_value())
}
/// Generates LLVM IR for `ndarray.eye`.
@ -195,21 +207,25 @@ pub fn gen_ndarray_eye<'ctx>(
let (dtype, _) = unpack_ndarray_var_tys(&mut context.unifier, fun.0.ret);
let nrows = Int(Int32)
.check_value(generator, context.ctx, nrows_arg)
.unwrap()
.s_extend_or_bit_cast(generator, context, SizeT);
let ncols = Int(Int32)
.check_value(generator, context.ctx, ncols_arg)
.unwrap()
.s_extend_or_bit_cast(generator, context, SizeT);
let offset = Int(Int32)
.check_value(generator, context.ctx, offset_arg)
.unwrap()
.s_extend_or_bit_cast(generator, context, SizeT);
let llvm_usize = generator.get_size_type(context.ctx);
let llvm_dtype = context.get_llvm_type(generator, dtype);
let ndarray = NDArrayObject::make_np_eye(generator, context, dtype, nrows, ncols, offset);
Ok(ndarray.instance.value)
let nrows = context
.builder
.build_int_s_extend_or_bit_cast(nrows_arg.into_int_value(), llvm_usize, "")
.unwrap();
let ncols = context
.builder
.build_int_s_extend_or_bit_cast(ncols_arg.into_int_value(), llvm_usize, "")
.unwrap();
let offset = context
.builder
.build_int_s_extend_or_bit_cast(offset_arg.into_int_value(), llvm_usize, "")
.unwrap();
let ndarray = NDArrayType::new(generator, context.ctx, llvm_dtype, 2)
.construct_numpy_eye(generator, context, dtype, nrows, ncols, offset, None);
Ok(ndarray.as_base_value())
}
/// Generates LLVM IR for `ndarray.identity`.
@ -223,15 +239,21 @@ pub fn gen_ndarray_identity<'ctx>(
assert!(obj.is_none());
assert_eq!(args.len(), 1);
let (dtype, _) = unpack_ndarray_var_tys(&mut context.unifier, fun.0.ret);
let n_ty = fun.0.args[0].ty;
let n_arg = args[0].1.clone().to_basic_value_enum(context, generator, n_ty)?;
let n = Int(Int32).check_value(generator, context.ctx, n_arg).unwrap();
let n = n.s_extend_or_bit_cast(generator, context, SizeT);
let ndarray = NDArrayObject::make_np_identity(generator, context, dtype, n);
Ok(ndarray.instance.value)
let (dtype, _) = unpack_ndarray_var_tys(&mut context.unifier, fun.0.ret);
let llvm_usize = generator.get_size_type(context.ctx);
let llvm_dtype = context.get_llvm_type(generator, dtype);
let n = context
.builder
.build_int_s_extend_or_bit_cast(n_arg.into_int_value(), llvm_usize, "")
.unwrap();
let ndarray = NDArrayType::new(generator, context.ctx, llvm_dtype, 2)
.construct_numpy_identity(generator, context, dtype, n, None);
Ok(ndarray.as_base_value())
}
/// Generates LLVM IR for `ndarray.copy`.
@ -249,10 +271,10 @@ pub fn gen_ndarray_copy<'ctx>(
let this_arg =
obj.as_ref().unwrap().1.clone().to_basic_value_enum(context, generator, this_ty)?;
let this = AnyObject { value: this_arg, ty: this_ty };
let this = NDArrayObject::from_object(generator, context, this);
let this = NDArrayType::from_unifier_type(generator, context, this_ty)
.map_value(this_arg.into_pointer_value(), None);
let ndarray = this.make_copy(generator, context);
Ok(ndarray.instance.value)
Ok(ndarray.as_base_value())
}
/// Generates LLVM IR for `ndarray.fill`.
@ -272,8 +294,8 @@ pub fn gen_ndarray_fill<'ctx>(
let value_ty = fun.0.args[0].ty;
let value_arg = args[0].1.clone().to_basic_value_enum(context, generator, value_ty)?;
let this = AnyObject { value: this_arg, ty: this_ty };
let this = NDArrayObject::from_object(generator, context, this);
let this = NDArrayType::from_unifier_type(generator, context, this_ty)
.map_value(this_arg.into_pointer_value(), None);
this.fill(generator, context, value_arg);
Ok(())
}
@ -287,36 +309,32 @@ pub fn gen_ndarray_fill<'ctx>(
pub fn ndarray_dot<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
x1: (Type, BasicValueEnum<'ctx>),
x2: (Type, BasicValueEnum<'ctx>),
(x1_ty, x1): (Type, BasicValueEnum<'ctx>),
(x2_ty, x2): (Type, BasicValueEnum<'ctx>),
) -> Result<BasicValueEnum<'ctx>, String> {
const FN_NAME: &str = "ndarray_dot";
let (x1_ty, x1) = x1;
let (x2_ty, x2) = x2;
match (x1, x2) {
(BasicValueEnum::PointerValue(_), BasicValueEnum::PointerValue(_)) => {
let a = AnyObject { ty: x1_ty, value: x1 };
let b = AnyObject { ty: x2_ty, value: x2 };
let a = NDArrayObject::from_object(generator, ctx, a);
let b = NDArrayObject::from_object(generator, ctx, b);
(BasicValueEnum::PointerValue(n1), BasicValueEnum::PointerValue(n2)) => {
let a = NDArrayType::from_unifier_type(generator, ctx, x1_ty).map_value(n1, None);
let b = NDArrayType::from_unifier_type(generator, ctx, x2_ty).map_value(n2, None);
// TODO: General `np.dot()` https://numpy.org/doc/stable/reference/generated/numpy.dot.html.
assert_eq!(a.ndims, 1);
assert_eq!(b.ndims, 1);
let common_dtype = a.dtype;
assert_eq!(a.get_type().ndims(), 1);
assert_eq!(b.get_type().ndims(), 1);
let common_dtype = arraylike_flatten_element_type(&mut ctx.unifier, x1_ty);
// Check shapes.
let a_size = a.size(generator, ctx);
let b_size = b.size(generator, ctx);
let same_shape = a_size.compare(ctx, IntPredicate::EQ, b_size);
let same_shape =
ctx.builder.build_int_compare(IntPredicate::EQ, a_size, b_size, "").unwrap();
ctx.make_assert(
generator,
same_shape.value,
same_shape,
"0:ValueError",
"shapes ({0},) and ({1},) not aligned: {0} (dim 0) != {1} (dim 1)",
[Some(a_size.value), Some(b_size.value), None],
[Some(a_size), Some(b_size), None],
ctx.current_loc,
);
@ -331,17 +349,17 @@ pub fn ndarray_dot<'ctx, G: CodeGenerator + ?Sized>(
ctx,
Some("np_dot"),
|generator, ctx| {
let a_iter = NDIterHandle::new(generator, ctx, a);
let b_iter = NDIterHandle::new(generator, ctx, b);
let a_iter = NDIterType::new(generator, ctx.ctx).construct(generator, ctx, a);
let b_iter = NDIterType::new(generator, ctx.ctx).construct(generator, ctx, b);
Ok((a_iter, b_iter))
},
|generator, ctx, (a_iter, _b_iter)| {
// Only a_iter drives the condition, b_iter should have the same status.
Ok(a_iter.has_element(generator, ctx).value)
Ok(a_iter.has_element(generator, ctx))
},
|generator, ctx, _hooks, (a_iter, b_iter)| {
let a_scalar = a_iter.get_scalar(generator, ctx).value;
let b_scalar = b_iter.get_scalar(generator, ctx).value;
|_, ctx, _hooks, (a_iter, b_iter)| {
let a_scalar = a_iter.get_scalar(ctx);
let b_scalar = b_iter.get_scalar(ctx);
let old_result = ctx.builder.build_load(result, "").unwrap();
let new_result: BasicValueEnum<'ctx> = match old_result {
@ -351,12 +369,14 @@ pub fn ndarray_dot<'ctx, G: CodeGenerator + ?Sized>(
let x = ctx.builder.build_int_mul(a_scalar, b_scalar, "").unwrap();
ctx.builder.build_int_add(old_result, x, "").unwrap().into()
}
BasicValueEnum::FloatValue(old_result) => {
let a_scalar = a_scalar.into_float_value();
let b_scalar = b_scalar.into_float_value();
let x = ctx.builder.build_float_mul(a_scalar, b_scalar, "").unwrap();
ctx.builder.build_float_add(old_result, x, "").unwrap().into()
}
_ => {
panic!("Unrecognized dtype: {}", ctx.unifier.stringify(common_dtype));
}
@ -375,12 +395,15 @@ pub fn ndarray_dot<'ctx, G: CodeGenerator + ?Sized>(
Ok(ctx.builder.build_load(result, "").unwrap())
}
(BasicValueEnum::IntValue(e1), BasicValueEnum::IntValue(e2)) => {
Ok(ctx.builder.build_int_mul(e1, e2, "").unwrap().as_basic_value_enum())
}
(BasicValueEnum::FloatValue(e1), BasicValueEnum::FloatValue(e2)) => {
Ok(ctx.builder.build_float_mul(e1, e2, "").unwrap().as_basic_value_enum())
}
_ => codegen_unreachable!(
ctx,
"{FN_NAME}() not supported for '{}'",

View File

@ -1,12 +0,0 @@
use inkwell::values::BasicValueEnum;
use crate::typecheck::typedef::Type;
/// A NAC3 LLVM Python object of any type.
#[derive(Debug, Clone, Copy)]
pub struct AnyObject<'ctx> {
/// Typechecker type of the object.
pub ty: Type,
/// LLVM value of the object.
pub value: BasicValueEnum<'ctx>,
}

View File

@ -1,86 +0,0 @@
use super::any::AnyObject;
use crate::{
codegen::{model::*, CodeGenContext, CodeGenerator},
typecheck::typedef::{iter_type_vars, Type, TypeEnum},
};
/// Fields of [`List`]
pub struct ListFields<'ctx, F: FieldTraversal<'ctx>, Item: Model<'ctx>> {
/// Array pointer to content
pub items: F::Output<Ptr<Item>>,
/// Number of items in the array
pub len: F::Output<Int<SizeT>>,
}
/// A list in NAC3.
#[derive(Debug, Clone, Copy, Default)]
pub struct List<Item> {
/// Model of the list items
pub item: Item,
}
impl<'ctx, Item: Model<'ctx>> StructKind<'ctx> for List<Item> {
type Fields<F: FieldTraversal<'ctx>> = ListFields<'ctx, F, Item>;
fn iter_fields<F: FieldTraversal<'ctx>>(&self, traversal: &mut F) -> Self::Fields<F> {
Self::Fields {
items: traversal.add("items", Ptr(self.item)),
len: traversal.add_auto("len"),
}
}
}
impl<'ctx, Item: Model<'ctx>> Instance<'ctx, Ptr<Struct<List<Item>>>> {
/// Cast the items pointer to `uint8_t*`.
pub fn with_pi8_items<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) -> Instance<'ctx, Ptr<Struct<List<Int<Byte>>>>> {
self.pointer_cast(generator, ctx, Struct(List { item: Int(Byte) }))
}
}
/// A NAC3 Python List object.
#[derive(Debug, Clone, Copy)]
pub struct ListObject<'ctx> {
/// Typechecker type of the list items
pub item_type: Type,
pub instance: Instance<'ctx, Ptr<Struct<List<Any<'ctx>>>>>,
}
impl<'ctx> ListObject<'ctx> {
/// Create a [`ListObject`] from an LLVM value and its typechecker [`Type`].
pub fn from_object<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
object: AnyObject<'ctx>,
) -> Self {
// Check typechecker type and extract `item_type`
let item_type = match &*ctx.unifier.get_ty(object.ty) {
TypeEnum::TObj { obj_id, params, .. }
if *obj_id == ctx.primitives.list.obj_id(&ctx.unifier).unwrap() =>
{
iter_type_vars(params).next().unwrap().ty // Extract `item_type`
}
_ => {
panic!("Expecting type to be a list, but got {}", ctx.unifier.stringify(object.ty))
}
};
let plist = Ptr(Struct(List { item: Any(ctx.get_llvm_type(generator, item_type)) }));
// Create object
let value = plist.check_value(generator, ctx.ctx, object.value).unwrap();
ListObject { item_type, instance: value }
}
/// Get the `len()` of this list.
pub fn len<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) -> Instance<'ctx, Int<SizeT>> {
self.instance.get(generator, ctx, |f| f.len)
}
}

View File

@ -1,5 +0,0 @@
pub mod any;
pub mod list;
pub mod ndarray;
pub mod tuple;
pub mod utils;

View File

@ -1,184 +0,0 @@
use super::NDArrayObject;
use crate::{
codegen::{
irrt::{
call_nac3_ndarray_array_set_and_validate_list_shape,
call_nac3_ndarray_array_write_list_to_array,
},
model::*,
object::{any::AnyObject, list::ListObject},
stmt::gen_if_else_expr_callback,
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>(
ctx: &mut CodeGenContext<'ctx, '_>,
list: ListObject<'ctx>,
) -> (Type, u64) {
let dtype = arraylike_flatten_element_type(&mut ctx.unifier, list.item_type);
let ndims = arraylike_get_ndims(&mut ctx.unifier, list.item_type);
let ndims = ndims + 1; // To count `list` itself.
(dtype, ndims)
}
impl<'ctx> NDArrayObject<'ctx> {
/// Implementation of `np_array(<list>, copy=True)`
fn make_np_array_list_copy_true_impl<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
list: ListObject<'ctx>,
) -> Self {
let (dtype, ndims_int) = get_list_object_dtype_and_ndims(ctx, list);
let list_value = list.instance.with_pi8_items(generator, ctx);
// Validate `list` has a consistent shape.
// Raise an exception if `list` is something abnormal like `[[1, 2], [3]]`.
// If `list` has a consistent shape, deduce the shape and write it to `shape`.
let ndims = Int(SizeT).const_int(generator, ctx.ctx, ndims_int, false);
let shape = Int(SizeT).array_alloca(generator, ctx, ndims.value);
call_nac3_ndarray_array_set_and_validate_list_shape(
generator, ctx, list_value, ndims, shape,
);
let ndarray = NDArrayObject::alloca(generator, ctx, dtype, ndims_int);
ndarray.copy_shape_from_array(generator, ctx, shape);
ndarray.create_data(generator, ctx);
// Copy all contents from the list.
call_nac3_ndarray_array_write_list_to_array(generator, ctx, list_value, ndarray.instance);
ndarray
}
/// Implementation of `np_array(<list>, copy=None)`
fn make_np_array_list_copy_none_impl<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
list: ListObject<'ctx>,
) -> Self {
// 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(ctx, list);
if ndims == 1 {
// `list` is not nested
let ndarray = NDArrayObject::alloca(generator, ctx, dtype, 1);
// Set data
let data = list.instance.get(generator, ctx, |f| f.items).cast_to_pi8(generator, ctx);
ndarray.instance.set(ctx, |f| f.data, data);
// ndarray->shape[0] = list->len;
let shape = ndarray.instance.get(generator, ctx, |f| f.shape);
let list_len = list.instance.get(generator, ctx, |f| f.len);
shape.set_index_const(ctx, 0, list_len);
// Set strides, the `data` is contiguous
ndarray.set_strides_contiguous(generator, ctx);
ndarray
} else {
// `list` is nested, copy
NDArrayObject::make_np_array_list_copy_true_impl(generator, ctx, list)
}
}
/// Implementation of `np_array(<list>, copy=copy)`
fn make_np_array_list_impl<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
list: ListObject<'ctx>,
copy: Instance<'ctx, Int<Bool>>,
) -> Self {
let (dtype, ndims) = get_list_object_dtype_and_ndims(ctx, list);
let ndarray = gen_if_else_expr_callback(
generator,
ctx,
|_generator, _ctx| Ok(copy.value),
|generator, ctx| {
let ndarray =
NDArrayObject::make_np_array_list_copy_true_impl(generator, ctx, list);
Ok(Some(ndarray.instance.value))
},
|generator, ctx| {
let ndarray =
NDArrayObject::make_np_array_list_copy_none_impl(generator, ctx, list);
Ok(Some(ndarray.instance.value))
},
)
.unwrap()
.unwrap();
NDArrayObject::from_value_and_unpacked_types(generator, ctx, ndarray, dtype, ndims)
}
/// Implementation of `np_array(<ndarray>, copy=copy)`.
pub fn make_np_array_ndarray_impl<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
ndarray: NDArrayObject<'ctx>,
copy: Instance<'ctx, Int<Bool>>,
) -> Self {
let ndarray_val = gen_if_else_expr_callback(
generator,
ctx,
|_generator, _ctx| Ok(copy.value),
|generator, ctx| {
let ndarray = ndarray.make_copy(generator, ctx); // Force copy
Ok(Some(ndarray.instance.value))
},
|_generator, _ctx| {
// No need to copy. Return `ndarray` itself.
Ok(Some(ndarray.instance.value))
},
)
.unwrap()
.unwrap();
NDArrayObject::from_value_and_unpacked_types(
generator,
ctx,
ndarray_val,
ndarray.dtype,
ndarray.ndims,
)
}
/// Create a new ndarray like `np.array()`.
///
/// NOTE: The `ndmin` argument is not here. You may want to
/// do [`NDArrayObject::atleast_nd`] to achieve that.
pub fn make_np_array<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
object: AnyObject<'ctx>,
copy: Instance<'ctx, Int<Bool>>,
) -> Self {
match &*ctx.unifier.get_ty(object.ty) {
TypeEnum::TObj { obj_id, .. }
if *obj_id == ctx.primitives.list.obj_id(&ctx.unifier).unwrap() =>
{
let list = ListObject::from_object(generator, ctx, object);
NDArrayObject::make_np_array_list_impl(generator, ctx, list, copy)
}
TypeEnum::TObj { obj_id, .. }
if *obj_id == ctx.primitives.ndarray.obj_id(&ctx.unifier).unwrap() =>
{
let ndarray = NDArrayObject::from_object(generator, ctx, object);
NDArrayObject::make_np_array_ndarray_impl(generator, ctx, ndarray, copy)
}
_ => panic!("Unrecognized object type: {}", ctx.unifier.stringify(object.ty)), // Typechecker ensures this
}
}
}

View File

@ -1,138 +0,0 @@
use itertools::Itertools;
use super::NDArrayObject;
use crate::codegen::{
irrt::{call_nac3_ndarray_broadcast_shapes, call_nac3_ndarray_broadcast_to},
model::*,
CodeGenContext, CodeGenerator,
};
/// Fields of [`ShapeEntry`]
pub struct ShapeEntryFields<'ctx, F: FieldTraversal<'ctx>> {
pub ndims: F::Output<Int<SizeT>>,
pub shape: F::Output<Ptr<Int<SizeT>>>,
}
/// An IRRT structure used in broadcasting.
#[derive(Debug, Clone, Copy, Default)]
pub struct ShapeEntry;
impl<'ctx> StructKind<'ctx> for ShapeEntry {
type Fields<F: FieldTraversal<'ctx>> = ShapeEntryFields<'ctx, F>;
fn iter_fields<F: FieldTraversal<'ctx>>(&self, traversal: &mut F) -> Self::Fields<F> {
Self::Fields { ndims: traversal.add_auto("ndims"), shape: traversal.add_auto("shape") }
}
}
impl<'ctx> NDArrayObject<'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: Instance<'ctx, Ptr<Int<SizeT>>>,
) -> Self {
let broadcast_ndarray = NDArrayObject::alloca(generator, ctx, self.dtype, target_ndims);
broadcast_ndarray.copy_shape_from_array(generator, ctx, target_shape);
call_nac3_ndarray_broadcast_to(generator, ctx, self.instance, broadcast_ndarray.instance);
broadcast_ndarray
}
}
/// A result produced by [`broadcast_all_ndarrays`]
#[derive(Debug, Clone)]
pub struct BroadcastAllResult<'ctx> {
/// The statically known `ndims` of the broadcast result.
pub ndims: u64,
/// The broadcasting shape.
pub shape: Instance<'ctx, Ptr<Int<SizeT>>>,
/// 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<NDArrayObject<'ctx>>,
}
/// Helper function to call `call_nac3_ndarray_broadcast_shapes`
fn broadcast_shapes<'ctx, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
in_shape_entries: &[(Instance<'ctx, Ptr<Int<SizeT>>>, u64)], // (shape, shape's length/ndims)
broadcast_ndims: u64,
broadcast_shape: Instance<'ctx, Ptr<Int<SizeT>>>,
) {
// Prepare input shape entries to be passed to `call_nac3_ndarray_broadcast_shapes`.
let num_shape_entries = Int(SizeT).const_int(
generator,
ctx.ctx,
u64::try_from(in_shape_entries.len()).unwrap(),
false,
);
let shape_entries = Struct(ShapeEntry).array_alloca(generator, ctx, num_shape_entries.value);
for (i, (in_shape, in_ndims)) in in_shape_entries.iter().enumerate() {
let pshape_entry = shape_entries.offset_const(ctx, i64::try_from(i).unwrap());
let in_ndims = Int(SizeT).const_int(generator, ctx.ctx, *in_ndims, false);
pshape_entry.set(ctx, |f| f.ndims, in_ndims);
pshape_entry.set(ctx, |f| f.shape, *in_shape);
}
let broadcast_ndims = Int(SizeT).const_int(generator, ctx.ctx, broadcast_ndims, false);
call_nac3_ndarray_broadcast_shapes(
generator,
ctx,
num_shape_entries,
shape_entries,
broadcast_ndims,
broadcast_shape,
);
}
impl<'ctx> NDArrayObject<'ctx> {
/// Broadcast all ndarrays according to `np.broadcast()` and return a [`BroadcastAllResult`]
/// containing all the information of the result of the broadcast operation.
pub fn broadcast<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
ndarrays: &[Self],
) -> BroadcastAllResult<'ctx> {
assert!(!ndarrays.is_empty());
// Infer the broadcast output ndims.
let broadcast_ndims_int = ndarrays.iter().map(|ndarray| ndarray.ndims).max().unwrap();
let broadcast_ndims = Int(SizeT).const_int(generator, ctx.ctx, broadcast_ndims_int, false);
let broadcast_shape = Int(SizeT).array_alloca(generator, ctx, broadcast_ndims.value);
let shape_entries = ndarrays
.iter()
.map(|ndarray| (ndarray.instance.get(generator, ctx, |f| f.shape), ndarray.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: Vec<_> = 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,133 +0,0 @@
use super::NDArrayObject;
use crate::{
codegen::{model::*, CodeGenContext, CodeGenerator},
typecheck::typedef::Type,
};
/// Fields of [`ContiguousNDArray`]
pub struct ContiguousNDArrayFields<'ctx, F: FieldTraversal<'ctx>, Item: Model<'ctx>> {
pub ndims: F::Output<Int<SizeT>>,
pub shape: F::Output<Ptr<Int<SizeT>>>,
pub data: F::Output<Ptr<Item>>,
}
/// An ndarray without strides and non-opaque `data` field in NAC3.
#[derive(Debug, Clone, Copy)]
pub struct ContiguousNDArray<M> {
/// [`Model`] of the items.
pub item: M,
}
impl<'ctx, Item: Model<'ctx>> StructKind<'ctx> for ContiguousNDArray<Item> {
type Fields<F: FieldTraversal<'ctx>> = ContiguousNDArrayFields<'ctx, F, Item>;
fn iter_fields<F: FieldTraversal<'ctx>>(&self, traversal: &mut F) -> Self::Fields<F> {
Self::Fields {
ndims: traversal.add_auto("ndims"),
shape: traversal.add_auto("shape"),
data: traversal.add("data", Ptr(self.item)),
}
}
}
impl<'ctx> NDArrayObject<'ctx> {
/// Create a [`ContiguousNDArray`] from the contents of this ndarray.
///
/// This function may or may not be expensive depending on if this ndarray has contiguous data.
///
/// If this ndarray is not C-contiguous, this function will allocate memory on the stack for the `data` field of
/// the returned [`ContiguousNDArray`] and copy contents of this ndarray to there.
///
/// If this ndarray is C-contiguous, contents of this ndarray will not be copied. The created [`ContiguousNDArray`]
/// will share memory with this ndarray.
///
/// The `item_model` sets the [`Model`] of the returned [`ContiguousNDArray`]'s `Item` model for type-safety, and
/// should match the `ctx.get_llvm_type()` of this ndarray's `dtype`. Otherwise this function panics. Use model [`Any`]
/// if you don't care/cannot know the [`Model`] in advance.
pub fn make_contiguous_ndarray<G: CodeGenerator + ?Sized, Item: Model<'ctx>>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
item_model: Item,
) -> Instance<'ctx, Ptr<Struct<ContiguousNDArray<Item>>>> {
// Sanity check on `self.dtype` and `item_model`.
let dtype_llvm = ctx.get_llvm_type(generator, self.dtype);
item_model.check_type(generator, ctx.ctx, dtype_llvm).unwrap();
let cdarray_model = Struct(ContiguousNDArray { item: item_model });
let current_bb = ctx.builder.get_insert_block().unwrap();
let then_bb = ctx.ctx.insert_basic_block_after(current_bb, "then_bb");
let else_bb = ctx.ctx.insert_basic_block_after(then_bb, "else_bb");
let end_bb = ctx.ctx.insert_basic_block_after(else_bb, "end_bb");
// Allocate and setup the resulting [`ContiguousNDArray`].
let result = cdarray_model.alloca(generator, ctx);
// Set ndims and shape.
let ndims = self.ndims_llvm(generator, ctx.ctx);
result.set(ctx, |f| f.ndims, ndims);
let shape = self.instance.get(generator, ctx, |f| f.shape);
result.set(ctx, |f| f.shape, shape);
let is_contiguous = self.is_c_contiguous(generator, ctx);
ctx.builder.build_conditional_branch(is_contiguous.value, then_bb, else_bb).unwrap();
// Inserting into then_bb; This ndarray is contiguous.
ctx.builder.position_at_end(then_bb);
let data = self.instance.get(generator, ctx, |f| f.data);
let data = data.pointer_cast(generator, ctx, item_model);
result.set(ctx, |f| f.data, data);
ctx.builder.build_unconditional_branch(end_bb).unwrap();
// Inserting into else_bb; This ndarray is not contiguous. Do a full-copy on `data`.
// `make_copy` produces an ndarray with contiguous `data`.
ctx.builder.position_at_end(else_bb);
let copied_ndarray = self.make_copy(generator, ctx);
let data = copied_ndarray.instance.get(generator, ctx, |f| f.data);
let data = data.pointer_cast(generator, ctx, item_model);
result.set(ctx, |f| f.data, data);
ctx.builder.build_unconditional_branch(end_bb).unwrap();
// Reposition to end_bb for continuation
ctx.builder.position_at_end(end_bb);
result
}
/// Create an [`NDArrayObject`] from a [`ContiguousNDArray`].
///
/// The operation is super cheap. The newly created [`NDArrayObject`] will share the
/// same memory as the [`ContiguousNDArray`].
///
/// `ndims` has to be provided as [`NDArrayObject`] requires a statically known `ndims` value, despite
/// the fact that the information should be contained within the [`ContiguousNDArray`].
pub fn from_contiguous_ndarray<G: CodeGenerator + ?Sized, Item: Model<'ctx>>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
carray: Instance<'ctx, Ptr<Struct<ContiguousNDArray<Item>>>>,
dtype: Type,
ndims: u64,
) -> Self {
// Sanity check on `dtype` and `contiguous_array`'s `Item` model.
let dtype_llvm = ctx.get_llvm_type(generator, dtype);
carray.model.0 .0.item.check_type(generator, ctx.ctx, dtype_llvm).unwrap();
// TODO: Debug assert `ndims == carray.ndims` to catch bugs.
// Allocate the resulting ndarray.
let ndarray = NDArrayObject::alloca(generator, ctx, dtype, ndims);
// Copy shape and update strides
let shape = carray.get(generator, ctx, |f| f.shape);
ndarray.copy_shape_from_array(generator, ctx, shape);
ndarray.set_strides_contiguous(generator, ctx);
// Share data
let data = carray.get(generator, ctx, |f| f.data).pointer_cast(generator, ctx, Int(Byte));
ndarray.instance.set(ctx, |f| f.data, data);
ndarray
}
}

View File

@ -1,175 +0,0 @@
use inkwell::{values::BasicValueEnum, IntPredicate};
use super::NDArrayObject;
use crate::{
codegen::{
irrt::call_nac3_ndarray_util_assert_shape_no_negative, model::*, 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> NDArrayObject<'ctx> {
/// Create an ndarray like `np.empty`.
pub fn make_np_empty<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
dtype: Type,
ndims: u64,
shape: Instance<'ctx, Ptr<Int<SizeT>>>,
) -> Self {
// Validate `shape`
let ndims_llvm = Int(SizeT).const_int(generator, ctx.ctx, ndims, false);
call_nac3_ndarray_util_assert_shape_no_negative(generator, ctx, ndims_llvm, shape);
let ndarray = NDArrayObject::alloca(generator, ctx, dtype, ndims);
ndarray.copy_shape_from_array(generator, ctx, shape);
ndarray.create_data(generator, ctx);
ndarray
}
/// Create an ndarray like `np.full`.
pub fn make_np_full<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
dtype: Type,
ndims: u64,
shape: Instance<'ctx, Ptr<Int<SizeT>>>,
fill_value: BasicValueEnum<'ctx>,
) -> Self {
let ndarray = NDArrayObject::make_np_empty(generator, ctx, dtype, ndims, shape);
ndarray.fill(generator, ctx, fill_value);
ndarray
}
/// Create an ndarray like `np.zero`.
pub fn make_np_zeros<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
dtype: Type,
ndims: u64,
shape: Instance<'ctx, Ptr<Int<SizeT>>>,
) -> Self {
let fill_value = ndarray_zero_value(generator, ctx, dtype);
NDArrayObject::make_np_full(generator, ctx, dtype, ndims, shape, fill_value)
}
/// Create an ndarray like `np.ones`.
pub fn make_np_ones<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
dtype: Type,
ndims: u64,
shape: Instance<'ctx, Ptr<Int<SizeT>>>,
) -> Self {
let fill_value = ndarray_one_value(generator, ctx, dtype);
NDArrayObject::make_np_full(generator, ctx, dtype, ndims, shape, fill_value)
}
/// Create an ndarray like `np.eye`.
pub fn make_np_eye<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
dtype: Type,
nrows: Instance<'ctx, Int<SizeT>>,
ncols: Instance<'ctx, Int<SizeT>>,
offset: Instance<'ctx, Int<SizeT>>,
) -> Self {
let ndzero = ndarray_zero_value(generator, ctx, dtype);
let ndone = ndarray_one_value(generator, ctx, dtype);
let ndarray = NDArrayObject::alloca_dynamic_shape(generator, ctx, dtype, &[nrows, ncols]);
// Create data and make the matrix like look np.eye()
ndarray.create_data(generator, ctx);
ndarray
.foreach(generator, ctx, |generator, ctx, _hooks, 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.
// Load up `row_i` and `col_i` from indices.
let row_i = nditer.get_indices().get_index_const(generator, ctx, 0);
let col_i = nditer.get_indices().get_index_const(generator, ctx, 1);
let be_one = row_i.add(ctx, offset).compare(ctx, IntPredicate::EQ, col_i);
let value = ctx.builder.build_select(be_one.value, ndone, ndzero, "value").unwrap();
let p = nditer.get_pointer(generator, ctx);
ctx.builder.build_store(p, value).unwrap();
Ok(())
})
.unwrap();
ndarray
}
/// Create an ndarray like `np.identity`.
pub fn make_np_identity<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
dtype: Type,
size: Instance<'ctx, Int<SizeT>>,
) -> Self {
// Convenient implementation
let offset = Int(SizeT).const_0(generator, ctx.ctx);
NDArrayObject::make_np_eye(generator, ctx, dtype, size, size, offset)
}
}

View File

@ -1,226 +0,0 @@
use super::NDArrayObject;
use crate::codegen::{
irrt::call_nac3_ndarray_index,
model::*,
object::utils::slice::{RustSlice, Slice},
CodeGenContext, CodeGenerator,
};
pub type NDIndexType = Byte;
/// Fields of [`NDIndex`]
#[derive(Debug, Clone, Copy)]
pub struct NDIndexFields<'ctx, F: FieldTraversal<'ctx>> {
pub type_: F::Output<Int<NDIndexType>>,
pub data: F::Output<Ptr<Int<Byte>>>,
}
/// An IRRT representation of an ndarray subscript index.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct NDIndex;
impl<'ctx> StructKind<'ctx> for NDIndex {
type Fields<F: FieldTraversal<'ctx>> = NDIndexFields<'ctx, F>;
fn iter_fields<F: FieldTraversal<'ctx>>(&self, traversal: &mut F) -> Self::Fields<F> {
Self::Fields { type_: traversal.add_auto("type"), data: traversal.add_auto("data") }
}
}
// A convenience enum representing a [`NDIndex`].
#[derive(Debug, Clone)]
pub enum RustNDIndex<'ctx> {
SingleElement(Instance<'ctx, Int<Int32>>),
Slice(RustSlice<'ctx, Int32>),
NewAxis,
Ellipsis,
}
impl<'ctx> RustNDIndex<'ctx> {
/// Get the value to set `NDIndex::type` for this variant.
fn get_type_id(&self) -> u64 {
// Defined in IRRT, must be in sync
match self {
RustNDIndex::SingleElement(_) => 0,
RustNDIndex::Slice(_) => 1,
RustNDIndex::NewAxis => 2,
RustNDIndex::Ellipsis => 3,
}
}
/// Serialize this [`RustNDIndex`] by writing it into an LLVM [`NDIndex`].
fn write_to_ndindex<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
dst_ndindex_ptr: Instance<'ctx, Ptr<Struct<NDIndex>>>,
) {
// Set `dst_ndindex_ptr->type`
dst_ndindex_ptr.gep(ctx, |f| f.type_).store(
ctx,
Int(NDIndexType::default()).const_int(generator, ctx.ctx, self.get_type_id(), false),
);
// Set `dst_ndindex_ptr->data`
match self {
RustNDIndex::SingleElement(in_index) => {
let index_ptr = Int(Int32).alloca(generator, ctx);
index_ptr.store(ctx, *in_index);
dst_ndindex_ptr
.gep(ctx, |f| f.data)
.store(ctx, index_ptr.pointer_cast(generator, ctx, Int(Byte)));
}
RustNDIndex::Slice(in_rust_slice) => {
let user_slice_ptr = Struct(Slice(Int32)).alloca(generator, ctx);
in_rust_slice.write_to_slice(generator, ctx, user_slice_ptr);
dst_ndindex_ptr
.gep(ctx, |f| f.data)
.store(ctx, user_slice_ptr.pointer_cast(generator, ctx, Int(Byte)));
}
RustNDIndex::NewAxis | RustNDIndex::Ellipsis => {}
}
}
/// Serialize a list of `RustNDIndex` as a newly allocated LLVM array of `NDIndex`.
pub fn make_ndindices<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
in_ndindices: &[RustNDIndex<'ctx>],
) -> (Instance<'ctx, Int<SizeT>>, Instance<'ctx, Ptr<Struct<NDIndex>>>) {
let ndindex_model = Struct(NDIndex);
// Allocate the LLVM ndindices.
let num_ndindices =
Int(SizeT).const_int(generator, ctx.ctx, in_ndindices.len() as u64, false);
let ndindices = ndindex_model.array_alloca(generator, ctx, num_ndindices.value);
// Initialize all of them.
for (i, in_ndindex) in in_ndindices.iter().enumerate() {
let pndindex = ndindices.offset_const(ctx, i64::try_from(i).unwrap());
in_ndindex.write_to_ndindex(generator, ctx, pndindex);
}
(num_ndindices, ndindices)
}
}
impl<'ctx> NDArrayObject<'ctx> {
/// Get the expected `ndims` after indexing with `indices`.
#[must_use]
fn deduce_ndims_after_indexing_with(&self, indices: &[RustNDIndex<'ctx>]) -> u64 {
let mut ndims = self.ndims;
for index in indices {
match index {
RustNDIndex::SingleElement(_) => {
ndims -= 1; // Single elements decrements ndims
}
RustNDIndex::NewAxis => {
ndims += 1; // `np.newaxis` / `none` adds a new axis
}
RustNDIndex::Ellipsis | RustNDIndex::Slice(_) => {}
}
}
ndims
}
/// Index into the ndarray, and return a newly-allocated view on this ndarray.
///
/// This function behaves like NumPy's ndarray indexing, but if the indices index
/// into a single element, an unsized ndarray is returned.
#[must_use]
pub fn index<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
indices: &[RustNDIndex<'ctx>],
) -> Self {
let dst_ndims = self.deduce_ndims_after_indexing_with(indices);
let dst_ndarray = NDArrayObject::alloca(generator, ctx, self.dtype, dst_ndims);
let (num_indices, indices) = RustNDIndex::make_ndindices(generator, ctx, indices);
call_nac3_ndarray_index(
generator,
ctx,
num_indices,
indices,
self.instance,
dst_ndarray.instance,
);
dst_ndarray
}
}
pub mod util {
use itertools::Itertools;
use nac3parser::ast::{Expr, ExprKind};
use crate::{
codegen::{model::*, object::utils::slice::util::gen_slice, CodeGenContext, CodeGenerator},
typecheck::typedef::Type,
};
use super::RustNDIndex;
/// Generate LLVM code to transform an ndarray subscript expression to
/// its list of [`RustNDIndex`]
///
/// i.e.,
/// ```python
/// my_ndarray[::3, 1, :2:]
/// ^^^^^^^^^^^ Then these into a three `RustNDIndex`es
/// ```
pub fn gen_ndarray_subscript_ndindices<'ctx, G: CodeGenerator>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
subscript: &Expr<Option<Type>>,
) -> Result<Vec<RustNDIndex<'ctx>>, String> {
// TODO: Support https://numpy.org/doc/stable/user/basics.indexing.html#dimensional-indexing-tools
// Annoying notes about `slice`
// - `my_array[5]`
// - slice is a `Constant`
// - `my_array[:5]`
// - slice is a `Slice`
// - `my_array[:]`
// - slice is a `Slice`, but lower upper step would all be `Option::None`
// - `my_array[:, :]`
// - slice is now a `Tuple` of two `Slice`-s
//
// In summary:
// - when there is a comma "," within [], `slice` will be a `Tuple` of the entries.
// - when there is not comma "," within [] (i.e., just a single entry), `slice` will be that entry itself.
//
// So we first "flatten" out the slice expression
let index_exprs = match &subscript.node {
ExprKind::Tuple { elts, .. } => elts.iter().collect_vec(),
_ => vec![subscript],
};
// Process all index expressions
let mut rust_ndindices: Vec<RustNDIndex> = Vec::with_capacity(index_exprs.len()); // Not using iterators here because `?` is used here.
for index_expr in index_exprs {
// NOTE: Currently nac3core's slices do not have an object representation,
// so the code/implementation looks awkward - we have to do pattern matching on the expression
let ndindex = if let ExprKind::Slice { lower, upper, step } = &index_expr.node {
// Handle slices
let slice = gen_slice(generator, ctx, lower, upper, step)?;
RustNDIndex::Slice(slice)
} else {
// Treat and handle everything else as a single element index.
let index = generator.gen_expr(ctx, index_expr)?.unwrap().to_basic_value_enum(
ctx,
generator,
ctx.primitives.int32, // Must be int32, this checks for illegal values
)?;
let index = Int(Int32).check_value(generator, ctx.ctx, index).unwrap();
RustNDIndex::SingleElement(index)
};
rust_ndindices.push(ndindex);
}
Ok(rust_ndindices)
}
}

View File

@ -1,219 +0,0 @@
use inkwell::values::BasicValueEnum;
use itertools::Itertools;
use crate::{
codegen::{
object::ndarray::{AnyObject, NDArrayObject},
stmt::gen_for_callback,
CodeGenContext, CodeGenerator,
},
typecheck::typedef::Type,
};
use super::{nditer::NDIterHandle, NDArrayOut, ScalarOrNDArray};
impl<'ctx> NDArrayObject<'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>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,
ndarrays: &[Self],
out: NDArrayOut<'ctx>,
mapping: MappingFn,
) -> Result<Self, String>
where
G: CodeGenerator + ?Sized,
MappingFn: FnOnce(
&mut G,
&mut CodeGenContext<'ctx, 'a>,
&[BasicValueEnum<'ctx>],
) -> Result<BasicValueEnum<'ctx>, String>,
{
// Broadcast inputs
let broadcast_result = NDArrayObject::broadcast(generator, ctx, ndarrays);
let out_ndarray = match out {
NDArrayOut::NewNDArray { dtype } => {
// Create a new ndarray based on the broadcast shape.
let result_ndarray =
NDArrayObject::alloca(generator, ctx, dtype, broadcast_result.ndims);
result_ndarray.copy_shape_from_array(generator, ctx, broadcast_result.shape);
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.ndims,
broadcast_result.shape,
);
result_ndarray
}
};
// Map element-wise and store results into `mapped_ndarray`.
let nditer = NDIterHandle::new(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| NDIterHandle::new(generator, ctx, *ndarray))
.collect_vec();
Ok((nditer, other_nditers))
},
|generator, ctx, (out_nditer, _in_nditers)| {
// We can simply use `out_nditer`'s `has_element()`.
// `in_nditers`' `has_element()`s should return the same value.
Ok(out_nditer.has_element(generator, ctx).value)
},
|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(generator, ctx).value)
.collect_vec();
let result = mapping(generator, ctx, &in_scalars)?;
let p = out_nditer.get_pointer(generator, ctx);
ctx.builder.build_store(p, result).unwrap();
Ok(())
},
|generator, ctx, (out_nditer, in_nditers)| {
// Advance all iterators
out_nditer.next(generator, ctx);
in_nditers.iter().for_each(|nditer| nditer.next(generator, ctx));
Ok(())
},
)?;
Ok(out_ndarray)
}
/// 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>,
{
NDArrayObject::broadcast_starmap(
generator,
ctx,
&[*self],
out,
|generator, ctx, scalars| mapping(generator, ctx, scalars[0]),
)
}
}
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 [`NDArrayObject::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: Type,
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(AnyObject::try_from).try_collect().ok();
if let Some(scalars) = all_scalars {
let scalars = scalars.iter().map(|scalar| scalar.value).collect_vec();
let value = mapping(generator, ctx, &scalars)?;
Ok(ScalarOrNDArray::Scalar(AnyObject { ty: ret_dtype, 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 = NDArrayObject::broadcast_starmap(
generator,
ctx,
&inputs,
NDArrayOut::NewNDArray { dtype: ret_dtype },
mapping,
)?;
Ok(ScalarOrNDArray::NDArray(ndarray))
}
}
/// 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: Type,
mapping: Mapping,
) -> Result<ScalarOrNDArray<'ctx>, String>
where
G: CodeGenerator + ?Sized,
Mapping: FnOnce(
&mut G,
&mut CodeGenContext<'ctx, 'a>,
BasicValueEnum<'ctx>,
) -> Result<BasicValueEnum<'ctx>, String>,
{
ScalarOrNDArray::broadcasting_starmap(
generator,
ctx,
&[*self],
ret_dtype,
|generator, ctx, scalars| mapping(generator, ctx, scalars[0]),
)
}
}

View File

@ -1,216 +0,0 @@
use std::cmp::max;
use nac3parser::ast::Operator;
use super::{util::gen_for_model, NDArrayObject, NDArrayOut};
use crate::{
codegen::{
expr::gen_binop_expr_with_values, irrt::call_nac3_ndarray_matmul_calculate_shapes,
model::*, object::ndarray::indexing::RustNDIndex, CodeGenContext, CodeGenerator,
},
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: NDArrayObject<'ctx>,
in_b: NDArrayObject<'ctx>,
) -> NDArrayObject<'ctx> {
assert!(in_a.ndims >= 2);
assert!(in_b.ndims >= 2);
// Deduce ndims of the result of matmul.
let ndims_int = max(in_a.ndims, in_b.ndims);
let ndims = Int(SizeT).const_int(generator, ctx.ctx, 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 = in_a.ndims_llvm(generator, ctx.ctx);
let in_lhs_shape = in_a.instance.get(generator, ctx, |f| f.shape);
let in_rhs_ndims = in_b.ndims_llvm(generator, ctx.ctx);
let in_rhs_shape = in_b.instance.get(generator, ctx, |f| f.shape);
let lhs_shape = Int(SizeT).array_alloca(generator, ctx, ndims.value);
let rhs_shape = Int(SizeT).array_alloca(generator, ctx, ndims.value);
let dst_shape = Int(SizeT).array_alloca(generator, ctx, ndims.value);
// Matmul dimension compatibility is checked here.
call_nac3_ndarray_matmul_calculate_shapes(
generator,
ctx,
in_lhs_ndims,
in_lhs_shape,
in_rhs_ndims,
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 = NDArrayObject::alloca(generator, ctx, dst_dtype, ndims_int);
dst.copy_shape_from_array(generator, ctx, dst_shape);
dst.create_data(generator, ctx);
(lhs, rhs, dst)
};
let len = lhs.instance.get(generator, ctx, |f| f.shape).get_index_const(
generator,
ctx,
i64::try_from(ndims_int - 1).unwrap(),
);
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(generator, ctx);
ctx.builder.build_store(pdst_ij, dst_zero).unwrap();
let indices = hdl.get_indices();
let i = indices.get_index_const(generator, ctx, at_row);
let j = indices.get_index_const(generator, ctx, at_col);
let num_0 = Int(SizeT).const_int(generator, ctx.ctx, 0, false);
let num_1 = Int(SizeT).const_int(generator, ctx.ctx, 1, false);
gen_for_model(generator, ctx, num_0, len, num_1, |generator, ctx, _, k| {
// `indices` is modified to index into `a` and `b`, and restored.
indices.set_index_const(ctx, at_row, i);
indices.set_index_const(ctx, at_col, k);
let a_ik = lhs.get_scalar_by_indices(generator, ctx, indices);
indices.set_index_const(ctx, at_row, k);
indices.set_index_const(ctx, at_col, j);
let b_kj = rhs.get_scalar_by_indices(generator, ctx, indices);
// Restore `indices`.
indices.set_index_const(ctx, at_row, i);
indices.set_index_const(ctx, at_col, j);
// x = a_[...]ik * b_[...]kj
let x = gen_binop_expr_with_values(
generator,
ctx,
(&Some(lhs.dtype), a_ik.value),
Binop::normal(Operator::Mult),
(&Some(rhs.dtype), b_kj.value),
ctx.current_loc,
)?
.unwrap()
.to_basic_value_enum(ctx, generator, dst_dtype)?;
// dst_[...]ij += x
let dst_ij = ctx.builder.build_load(pdst_ij, "").unwrap();
let dst_ij = gen_binop_expr_with_values(
generator,
ctx,
(&Some(dst_dtype), dst_ij),
Binop::normal(Operator::Add),
(&Some(dst_dtype), x),
ctx.current_loc,
)?
.unwrap()
.to_basic_value_enum(ctx, generator, dst_dtype)?;
ctx.builder.build_store(pdst_ij, dst_ij).unwrap();
Ok(())
})
})
.unwrap();
dst
}
impl<'ctx> NDArrayObject<'ctx> {
/// Perform `np.matmul` according to the rules in
/// <https://numpy.org/doc/stable/reference/generated/numpy.matmul.html>.
///
/// This function always return an [`NDArrayObject`]. You may want to use [`NDArrayObject::split_unsized`]
/// to handle when the output could be a scalar.
///
/// `dst_dtype` defines the dtype of the returned ndarray.
pub fn matmul<G: CodeGenerator>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
a: Self,
b: Self,
out: NDArrayOut<'ctx>,
) -> Self {
// Sanity check, but type inference should prevent this.
assert!(a.ndims > 0 && b.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 a.ndims == 1 {
// Prepend 1 to its dimensions
a.index(generator, ctx, &[RustNDIndex::NewAxis, RustNDIndex::Ellipsis])
} else {
a
};
let new_b = if b.ndims == 1 {
// Append 1 to its dimensions
b.index(generator, ctx, &[RustNDIndex::Ellipsis, RustNDIndex::NewAxis])
} else {
b
};
// 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.get_dtype(), new_a, new_b);
// Postprocessing on the result to remove prepended/appended axes.
let mut postindices = vec![];
let zero = Int(Int32).const_0(generator, ctx.ctx);
if a.ndims == 1 {
// Remove the prepended 1
postindices.push(RustNDIndex::SingleElement(zero));
}
if b.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.instance.get(generator, ctx, |f| f.shape);
out_ndarray.assert_can_be_written_by_out(
generator,
ctx,
result.ndims,
result_shape,
);
out_ndarray.copy_data_from(generator, ctx, result);
out_ndarray
}
}
}
}

View File

@ -1,669 +0,0 @@
use inkwell::{
context::Context,
types::BasicType,
values::{BasicValue, BasicValueEnum, PointerValue},
AddressSpace,
};
use super::{any::AnyObject, tuple::TupleObject};
use crate::{
codegen::{
irrt::{
call_nac3_ndarray_copy_data, call_nac3_ndarray_get_nth_pelement,
call_nac3_ndarray_get_pelement_by_indices, call_nac3_ndarray_is_c_contiguous,
call_nac3_ndarray_len, call_nac3_ndarray_nbytes,
call_nac3_ndarray_set_strides_by_shape, call_nac3_ndarray_size,
call_nac3_ndarray_util_assert_output_shape_same,
},
model::*,
CodeGenContext, CodeGenerator,
},
toplevel::{
helper::{create_ndims, extract_ndims},
numpy::{make_ndarray_ty, unpack_ndarray_var_tys},
},
typecheck::typedef::{Type, TypeEnum},
};
pub mod array;
pub mod broadcast;
pub mod contiguous;
pub mod factory;
pub mod indexing;
pub mod map;
pub mod matmul;
pub mod nditer;
pub mod shape_util;
pub mod view;
/// Fields of [`NDArray`]
pub struct NDArrayFields<'ctx, F: FieldTraversal<'ctx>> {
pub data: F::Output<Ptr<Int<Byte>>>,
pub itemsize: F::Output<Int<SizeT>>,
pub ndims: F::Output<Int<SizeT>>,
pub shape: F::Output<Ptr<Int<SizeT>>>,
pub strides: F::Output<Ptr<Int<SizeT>>>,
}
/// A strided ndarray in NAC3.
///
/// See IRRT implementation for details about its fields.
#[derive(Debug, Clone, Copy, Default)]
pub struct NDArray;
impl<'ctx> StructKind<'ctx> for NDArray {
type Fields<F: FieldTraversal<'ctx>> = NDArrayFields<'ctx, F>;
fn iter_fields<F: FieldTraversal<'ctx>>(&self, traversal: &mut F) -> Self::Fields<F> {
Self::Fields {
data: traversal.add_auto("data"),
itemsize: traversal.add_auto("itemsize"),
ndims: traversal.add_auto("ndims"),
shape: traversal.add_auto("shape"),
strides: traversal.add_auto("strides"),
}
}
}
/// A NAC3 Python ndarray object.
#[derive(Debug, Clone, Copy)]
pub struct NDArrayObject<'ctx> {
pub dtype: Type,
pub ndims: u64,
pub instance: Instance<'ctx, Ptr<Struct<NDArray>>>,
}
impl<'ctx> NDArrayObject<'ctx> {
/// Attempt to convert an [`AnyObject`] into an [`NDArrayObject`].
pub fn from_object<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
object: AnyObject<'ctx>,
) -> NDArrayObject<'ctx> {
let (dtype, ndims) = unpack_ndarray_var_tys(&mut ctx.unifier, object.ty);
let ndims = extract_ndims(&ctx.unifier, ndims);
Self::from_value_and_unpacked_types(generator, ctx, object.value, dtype, ndims)
}
/// Like [`NDArrayObject::from_object`] but you directly supply the ndarray's
/// `dtype` and `ndims`.
pub fn from_value_and_unpacked_types<V: BasicValue<'ctx>, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
value: V,
dtype: Type,
ndims: u64,
) -> Self {
let value = Ptr(Struct(NDArray)).check_value(generator, ctx.ctx, value).unwrap();
NDArrayObject { dtype, ndims, instance: value }
}
/// Get this ndarray's `ndims` as an LLVM constant.
pub fn ndims_llvm<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &'ctx Context,
) -> Instance<'ctx, Int<SizeT>> {
Int(SizeT).const_int(generator, ctx, self.ndims, false)
}
/// Get the typechecker ndarray type of this [`NDArrayObject`].
pub fn get_type(&self, ctx: &mut CodeGenContext<'ctx, '_>) -> Type {
let ndims = create_ndims(&mut ctx.unifier, self.ndims);
make_ndarray_ty(&mut ctx.unifier, &ctx.primitives, Some(self.dtype), Some(ndims))
}
/// Forget that this is an ndarray and convert into an [`AnyObject`].
pub fn to_any(&self, ctx: &mut CodeGenContext<'ctx, '_>) -> AnyObject<'ctx> {
let ty = self.get_type(ctx);
AnyObject { value: self.instance.value.as_basic_value_enum(), ty }
}
/// Allocate an ndarray 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 `sizeof()` 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.
pub fn alloca<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
dtype: Type,
ndims: u64,
) -> Self {
let ndarray = Struct(NDArray).alloca(generator, ctx);
let itemsize = ctx.get_llvm_type(generator, dtype).size_of().unwrap();
let itemsize = Int(SizeT).z_extend_or_truncate(generator, ctx, itemsize);
ndarray.set(ctx, |f| f.itemsize, itemsize);
let ndims_val = Int(SizeT).const_int(generator, ctx.ctx, ndims, false);
ndarray.set(ctx, |f| f.ndims, ndims_val);
let shape = Int(SizeT).array_alloca(generator, ctx, ndims_val.value);
ndarray.set(ctx, |f| f.shape, shape);
let strides = Int(SizeT).array_alloca(generator, ctx, ndims_val.value);
ndarray.set(ctx, |f| f.strides, strides);
NDArrayObject { dtype, ndims, instance: ndarray }
}
/// Convenience function. Allocate an [`NDArrayObject`] with a statically known shape.
///
/// The returned [`NDArrayObject`]'s `data` and `strides` are uninitialized.
pub fn alloca_constant_shape<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
dtype: Type,
shape: &[u64],
) -> Self {
let ndarray = NDArrayObject::alloca(generator, ctx, dtype, shape.len() as u64);
// Write shape
let dst_shape = ndarray.instance.get(generator, ctx, |f| f.shape);
for (i, dim) in shape.iter().enumerate() {
let dim = Int(SizeT).const_int(generator, ctx.ctx, *dim, false);
dst_shape.offset_const(ctx, i64::try_from(i).unwrap()).store(ctx, dim);
}
ndarray
}
/// Convenience function. Allocate an [`NDArrayObject`] with a dynamically known shape.
///
/// The returned [`NDArrayObject`]'s `data` and `strides` are uninitialized.
pub fn alloca_dynamic_shape<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
dtype: Type,
shape: &[Instance<'ctx, Int<SizeT>>],
) -> Self {
let ndarray = NDArrayObject::alloca(generator, ctx, dtype, shape.len() as u64);
// Write shape
let dst_shape = ndarray.instance.get(generator, ctx, |f| f.shape);
for (i, dim) in shape.iter().enumerate() {
dst_shape.offset_const(ctx, i64::try_from(i).unwrap()).store(ctx, *dim);
}
ndarray
}
/// Initialize an ndarray's `data` by allocating a buffer on the stack.
/// The allocated data buffer is considered to be *owned* by the ndarray.
///
/// `strides` of the ndarray will also be updated with `set_strides_by_shape`.
///
/// `shape` and `itemsize` of the ndarray ***must*** be initialized first.
pub fn create_data<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) {
let nbytes = self.nbytes(generator, ctx);
let data = Int(Byte).array_alloca(generator, ctx, nbytes.value);
self.instance.set(ctx, |f| f.data, data);
self.set_strides_contiguous(generator, ctx);
}
/// Copy shape dimensions from an array.
pub fn copy_shape_from_array<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
shape: Instance<'ctx, Ptr<Int<SizeT>>>,
) {
let num_items = self.ndims_llvm(generator, ctx.ctx).value;
self.instance.get(generator, ctx, |f| f.shape).copy_from(generator, ctx, shape, num_items);
}
/// Copy shape dimensions from an ndarray.
/// Panics if `ndims` mismatches.
pub fn copy_shape_from_ndarray<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
src_ndarray: NDArrayObject<'ctx>,
) {
assert_eq!(self.ndims, src_ndarray.ndims);
let src_shape = src_ndarray.instance.get(generator, ctx, |f| f.shape);
self.copy_shape_from_array(generator, ctx, src_shape);
}
/// Copy strides dimensions from an array.
pub fn copy_strides_from_array<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
strides: Instance<'ctx, Ptr<Int<SizeT>>>,
) {
let num_items = self.ndims_llvm(generator, ctx.ctx).value;
self.instance
.get(generator, ctx, |f| f.strides)
.copy_from(generator, ctx, strides, num_items);
}
/// Copy strides dimensions from an ndarray.
/// Panics if `ndims` mismatches.
pub fn copy_strides_from_ndarray<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
src_ndarray: NDArrayObject<'ctx>,
) {
assert_eq!(self.ndims, src_ndarray.ndims);
let src_strides = src_ndarray.instance.get(generator, ctx, |f| f.strides);
self.copy_strides_from_array(generator, ctx, src_strides);
}
/// Get the `np.size()` of this ndarray.
pub fn size<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) -> Instance<'ctx, Int<SizeT>> {
call_nac3_ndarray_size(generator, ctx, self.instance)
}
/// Get the `ndarray.nbytes` of this ndarray.
pub fn nbytes<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) -> Instance<'ctx, Int<SizeT>> {
call_nac3_ndarray_nbytes(generator, ctx, self.instance)
}
/// Get the `len()` of this ndarray.
pub fn len<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) -> Instance<'ctx, Int<SizeT>> {
call_nac3_ndarray_len(generator, ctx, self.instance)
}
/// 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>
pub fn is_c_contiguous<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) -> Instance<'ctx, Int<Bool>> {
call_nac3_ndarray_is_c_contiguous(generator, ctx, self.instance)
}
/// Get the pointer to the n-th (0-based) element.
///
/// The returned pointer has the element type of the LLVM type of this ndarray's `dtype`.
pub fn get_nth_pelement<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
nth: Instance<'ctx, Int<SizeT>>,
) -> PointerValue<'ctx> {
let elem_ty = ctx.get_llvm_type(generator, self.dtype);
let p = call_nac3_ndarray_get_nth_pelement(generator, ctx, self.instance, nth);
ctx.builder
.build_pointer_cast(p.value, elem_ty.ptr_type(AddressSpace::default()), "")
.unwrap()
}
/// Get the n-th (0-based) scalar.
pub fn get_nth_scalar<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
nth: Instance<'ctx, Int<SizeT>>,
) -> AnyObject<'ctx> {
let ptr = self.get_nth_pelement(generator, ctx, nth);
let value = ctx.builder.build_load(ptr, "").unwrap();
AnyObject { ty: self.dtype, value }
}
/// Get the pointer to the element indexed by `indices`.
///
/// The returned pointer has the element type of the LLVM type of this ndarray's `dtype`.
pub fn get_pelement_by_indices<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
indices: Instance<'ctx, Ptr<Int<SizeT>>>,
) -> PointerValue<'ctx> {
let elem_ty = ctx.get_llvm_type(generator, self.dtype);
let p = call_nac3_ndarray_get_pelement_by_indices(generator, ctx, self.instance, indices);
ctx.builder
.build_pointer_cast(p.value, elem_ty.ptr_type(AddressSpace::default()), "")
.unwrap()
}
/// Get the scalar indexed by `indices`.
pub fn get_scalar_by_indices<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
indices: Instance<'ctx, Ptr<Int<SizeT>>>,
) -> AnyObject<'ctx> {
let ptr = self.get_pelement_by_indices(generator, ctx, indices);
let value = ctx.builder.build_load(ptr, "").unwrap();
AnyObject { ty: self.dtype, value }
}
/// Call [`call_nac3_ndarray_set_strides_by_shape`] on this ndarray to update `strides`.
///
/// Update the ndarray's strides to make the ndarray contiguous.
pub fn set_strides_contiguous<G: CodeGenerator + ?Sized>(
self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) {
call_nac3_ndarray_set_strides_by_shape(generator, ctx, self.instance);
}
/// 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]
pub fn make_copy<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) -> Self {
let clone = NDArrayObject::alloca(generator, ctx, self.dtype, self.ndims);
let shape = self.instance.gep(ctx, |f| f.shape).load(generator, ctx);
clone.copy_shape_from_array(generator, ctx, shape);
clone.create_data(generator, ctx);
clone.copy_data_from(generator, ctx, *self);
clone
}
/// Copy data from another ndarray.
///
/// This ndarray and `src` is that their `np.size()` should be the same. Their shapes
/// do not matter. The copying order is determined by how their flattened views look.
///
/// Panics if the `dtype`s of ndarrays are different.
pub fn copy_data_from<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
src: NDArrayObject<'ctx>,
) {
assert!(ctx.unifier.unioned(self.dtype, src.dtype), "self and src dtype should match");
call_nac3_ndarray_copy_data(generator, ctx, src.instance, self.instance);
}
/// Returns true if this ndarray is unsized - `ndims == 0` and only contains a scalar.
#[must_use]
pub fn is_unsized(&self) -> bool {
self.ndims == 0
}
/// If this ndarray is unsized, return its sole value as an [`AnyObject`].
/// Otherwise, do nothing and return the ndarray itself.
pub fn split_unsized<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) -> ScalarOrNDArray<'ctx> {
if self.is_unsized() {
// NOTE: `np.size(self) == 0` here is never possible.
let zero = Int(SizeT).const_0(generator, ctx.ctx);
let value = self.get_nth_scalar(generator, ctx, zero).value;
ScalarOrNDArray::Scalar(AnyObject { ty: self.dtype, value })
} else {
ScalarOrNDArray::NDArray(*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, |generator, ctx, _hooks, nditer| {
let p = nditer.get_pointer(generator, ctx);
ctx.builder.build_store(p, value).unwrap();
Ok(())
})
.unwrap();
}
/// Create the shape tuple of this ndarray like `np.shape(<ndarray>)`.
///
/// The returned integers in the tuple are in int32.
pub fn make_shape_tuple<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) -> TupleObject<'ctx> {
// TODO: Return a tuple of SizeT
let mut objects = Vec::with_capacity(self.ndims as usize);
for i in 0..self.ndims {
let dim = self
.instance
.get(generator, ctx, |f| f.shape)
.get_index_const(generator, ctx, i64::try_from(i).unwrap())
.truncate_or_bit_cast(generator, ctx, Int32);
objects.push(AnyObject {
ty: ctx.primitives.int32,
value: dim.value.as_basic_value_enum(),
});
}
TupleObject::from_objects(generator, ctx, objects)
}
/// Create the strides tuple of this ndarray like `<ndarray>.strides`.
///
/// The returned integers in the tuple are in int32.
pub fn make_strides_tuple<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) -> TupleObject<'ctx> {
// TODO: Return a tuple of SizeT.
let mut objects = Vec::with_capacity(self.ndims as usize);
for i in 0..self.ndims {
let dim = self
.instance
.get(generator, ctx, |f| f.strides)
.get_index_const(generator, ctx, i64::try_from(i).unwrap())
.truncate_or_bit_cast(generator, ctx, Int32);
objects.push(AnyObject {
ty: ctx.primitives.int32,
value: dim.value.as_basic_value_enum(),
});
}
TupleObject::from_objects(generator, ctx, objects)
}
/// Create an unsized ndarray to contain `object`.
pub fn make_unsized<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
object: AnyObject<'ctx>,
) -> NDArrayObject<'ctx> {
// We have to put the value on the stack to get a data pointer.
let data = ctx.builder.build_alloca(object.value.get_type(), "make_unsized").unwrap();
ctx.builder.build_store(data, object.value).unwrap();
let data = Ptr(Int(Byte)).pointer_cast(generator, ctx, data);
let ndarray = NDArrayObject::alloca(generator, ctx, object.ty, 0);
ndarray.instance.set(ctx, |f| f.data, data);
ndarray
}
/// 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_ndims: u64,
out_shape: Instance<'ctx, Ptr<Int<SizeT>>>,
) {
let ndarray_ndims = self.ndims_llvm(generator, ctx.ctx);
let ndarray_shape = self.instance.get(generator, ctx, |f| f.shape);
let output_ndims = Int(SizeT).const_int(generator, ctx.ctx, out_ndims, false);
let output_shape = out_shape;
call_nac3_ndarray_util_assert_output_shape_same(
generator,
ctx,
ndarray_ndims,
ndarray_shape,
output_ndims,
output_shape,
);
}
}
/// A convenience enum for implementing functions that acts on scalars or ndarrays or both.
#[derive(Debug, Clone, Copy)]
pub enum ScalarOrNDArray<'ctx> {
Scalar(AnyObject<'ctx>),
NDArray(NDArrayObject<'ctx>),
}
impl<'ctx> TryFrom<&ScalarOrNDArray<'ctx>> for AnyObject<'ctx> {
type Error = ();
fn try_from(value: &ScalarOrNDArray<'ctx>) -> Result<Self, Self::Error> {
match value {
ScalarOrNDArray::Scalar(scalar) => Ok(*scalar),
ScalarOrNDArray::NDArray(_ndarray) => Err(()),
}
}
}
impl<'ctx> TryFrom<&ScalarOrNDArray<'ctx>> for NDArrayObject<'ctx> {
type Error = ();
fn try_from(value: &ScalarOrNDArray<'ctx>) -> Result<Self, Self::Error> {
match value {
ScalarOrNDArray::Scalar(_scalar) => Err(()),
ScalarOrNDArray::NDArray(ndarray) => Ok(*ndarray),
}
}
}
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 split_object<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
object: AnyObject<'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 = NDArrayObject::from_object(generator, ctx, object);
ScalarOrNDArray::NDArray(ndarray)
}
_ => ScalarOrNDArray::Scalar(object),
}
}
/// Get the underlying [`BasicValueEnum<'ctx>`] of this [`ScalarOrNDArray`].
#[must_use]
pub fn to_basic_value_enum(self) -> BasicValueEnum<'ctx> {
match self {
ScalarOrNDArray::Scalar(scalar) => scalar.value,
ScalarOrNDArray::NDArray(ndarray) => ndarray.instance.value.as_basic_value_enum(),
}
}
/// 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 [`NDArrayObject::make_unsized`].
pub fn to_ndarray<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) -> NDArrayObject<'ctx> {
match self {
ScalarOrNDArray::NDArray(ndarray) => *ndarray,
ScalarOrNDArray::Scalar(scalar) => NDArrayObject::make_unsized(generator, ctx, *scalar),
}
}
/// Get the dtype of the ndarray created if this were called with [`ScalarOrNDArray::to_ndarray`].
#[must_use]
pub fn get_dtype(&self) -> Type {
match self {
ScalarOrNDArray::NDArray(ndarray) => ndarray.dtype,
ScalarOrNDArray::Scalar(scalar) => scalar.ty,
}
}
}
/// 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(Debug, Clone, Copy)]
pub enum NDArrayOut<'ctx> {
/// Tell a function should create a new ndarray with the expected element type `dtype`.
NewNDArray { dtype: Type },
/// Tell a function to write the result to `ndarray`.
WriteToNDArray { ndarray: NDArrayObject<'ctx> },
}
impl<'ctx> NDArrayOut<'ctx> {
/// Get the dtype of this output.
#[must_use]
pub fn get_dtype(&self) -> Type {
match self {
NDArrayOut::NewNDArray { dtype } => *dtype,
NDArrayOut::WriteToNDArray { ndarray } => ndarray.dtype,
}
}
}
/// A version of [`call_nac3_ndarray_set_strides_by_shape`] in Rust.
///
/// This function is used generating strides for globally defined contiguous ndarrays.
#[must_use]
pub fn make_contiguous_strides(itemsize: u64, ndims: u64, shape: &[u64]) -> Vec<u64> {
let mut strides = Vec::with_capacity(ndims as usize);
let mut stride_product = 1u64;
for i in 0..ndims {
let axis = ndims - i - 1;
strides[axis as usize] = stride_product * itemsize;
stride_product *= shape[axis as usize];
}
strides
}

View File

@ -1,178 +0,0 @@
use inkwell::{types::BasicType, values::PointerValue, AddressSpace};
use super::NDArrayObject;
use crate::codegen::{
irrt::{call_nac3_nditer_has_element, call_nac3_nditer_initialize, call_nac3_nditer_next},
model::*,
object::any::AnyObject,
stmt::{gen_for_callback, BreakContinueHooks},
CodeGenContext, CodeGenerator,
};
/// Fields of [`NDIter`]
pub struct NDIterFields<'ctx, F: FieldTraversal<'ctx>> {
pub ndims: F::Output<Int<SizeT>>,
pub shape: F::Output<Ptr<Int<SizeT>>>,
pub strides: F::Output<Ptr<Int<SizeT>>>,
pub indices: F::Output<Ptr<Int<SizeT>>>,
pub nth: F::Output<Int<SizeT>>,
pub element: F::Output<Ptr<Int<Byte>>>,
pub size: F::Output<Int<SizeT>>,
}
/// An IRRT helper structure used to iterate through an ndarray.
#[derive(Debug, Clone, Copy, Default)]
pub struct NDIter;
impl<'ctx> StructKind<'ctx> for NDIter {
type Fields<F: FieldTraversal<'ctx>> = NDIterFields<'ctx, F>;
fn iter_fields<F: FieldTraversal<'ctx>>(&self, traversal: &mut F) -> Self::Fields<F> {
Self::Fields {
ndims: traversal.add_auto("ndims"),
shape: traversal.add_auto("shape"),
strides: traversal.add_auto("strides"),
indices: traversal.add_auto("indices"),
nth: traversal.add_auto("nth"),
element: traversal.add_auto("element"),
size: traversal.add_auto("size"),
}
}
}
/// A helper structure with a convenient interface to interact with [`NDIter`].
#[derive(Debug, Clone)]
pub struct NDIterHandle<'ctx> {
instance: Instance<'ctx, Ptr<Struct<NDIter>>>,
/// The ndarray this [`NDIter`] to iterating over.
ndarray: NDArrayObject<'ctx>,
/// The current indices of [`NDIter`].
indices: Instance<'ctx, Ptr<Int<SizeT>>>,
}
impl<'ctx> NDIterHandle<'ctx> {
/// Allocate an [`NDIter`] that iterates through an ndarray.
pub fn new<G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
ndarray: NDArrayObject<'ctx>,
) -> Self {
let nditer = Struct(NDIter).alloca(generator, ctx);
let ndims = ndarray.ndims_llvm(generator, ctx.ctx);
// The caller has the responsibility to allocate 'indices' for `NDIter`.
let indices = Int(SizeT).array_alloca(generator, ctx, ndims.value);
call_nac3_nditer_initialize(generator, ctx, nditer, ndarray.instance, indices);
NDIterHandle { ndarray, instance: nditer, indices }
}
/// Is the current iteration valid?
///
/// If true, then `element`, `indices` and `nth` contain details about the current element.
///
/// If `ndarray` is unsized, this returns true only for the first iteration.
/// If `ndarray` is 0-sized, this always returns false.
#[must_use]
pub fn has_element<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) -> Instance<'ctx, Int<Bool>> {
call_nac3_nditer_has_element(generator, ctx, self.instance)
}
/// 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 0-sized, this can never be called.
pub fn next<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) {
call_nac3_nditer_next(generator, ctx, self.instance);
}
/// Get pointer to the current element.
#[must_use]
pub fn get_pointer<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) -> PointerValue<'ctx> {
let elem_ty = ctx.get_llvm_type(generator, self.ndarray.dtype);
let p = self.instance.get(generator, ctx, |f| f.element);
ctx.builder
.build_pointer_cast(p.value, elem_ty.ptr_type(AddressSpace::default()), "element")
.unwrap()
}
/// Get the value of the current element.
#[must_use]
pub fn get_scalar<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) -> AnyObject<'ctx> {
let p = self.get_pointer(generator, ctx);
let value = ctx.builder.build_load(p, "value").unwrap();
AnyObject { ty: self.ndarray.dtype, value }
}
/// Get the index of the current element if this ndarray were a flat ndarray.
#[must_use]
pub fn get_index<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) -> Instance<'ctx, Int<SizeT>> {
self.instance.get(generator, ctx, |f| f.nth)
}
/// Get the indices of the current element.
#[must_use]
pub fn get_indices(&self) -> Instance<'ctx, Ptr<Int<SizeT>>> {
self.indices
}
}
impl<'ctx> NDArrayObject<'ctx> {
/// Iterate through every element in the ndarray.
///
/// `body` has access to [`BreakContinueHooks`] to short-circuit and [`NDIterHandle`] to
/// get properties of the current iteration (e.g., the current element, indices, etc.)
pub fn foreach<'a, G, F>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,
body: F,
) -> Result<(), String>
where
G: CodeGenerator + ?Sized,
F: FnOnce(
&mut G,
&mut CodeGenContext<'ctx, 'a>,
BreakContinueHooks<'ctx>,
NDIterHandle<'ctx>,
) -> Result<(), String>,
{
gen_for_callback(
generator,
ctx,
Some("ndarray_foreach"),
|generator, ctx| Ok(NDIterHandle::new(generator, ctx, *self)),
|generator, ctx, nditer| Ok(nditer.has_element(generator, ctx).value),
|generator, ctx, hooks, nditer| body(generator, ctx, hooks, nditer),
|generator, ctx, nditer| {
nditer.next(generator, ctx);
Ok(())
},
)
}
}

View File

@ -1,104 +0,0 @@
use crate::{
codegen::{
model::*,
object::{any::AnyObject, list::ListObject, tuple::TupleObject},
CodeGenContext, CodeGenerator,
},
typecheck::typedef::TypeEnum,
};
use util::gen_for_model;
/// 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_sequence: AnyObject<'ctx>,
) -> (Instance<'ctx, Int<SizeT>>, Instance<'ctx, Ptr<Int<SizeT>>>) {
let zero = Int(SizeT).const_0(generator, ctx.ctx);
let one = Int(SizeT).const_1(generator, ctx.ctx);
// The result `list` to return.
match &*ctx.unifier.get_ty(input_sequence.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])`
// Check `input_sequence`
let input_sequence = ListObject::from_object(generator, ctx, input_sequence);
let len = input_sequence.instance.get(generator, ctx, |f| f.len);
let result = Int(SizeT).array_alloca(generator, ctx, len.value);
// Load all the `int32`s from the input_sequence, cast them to `SizeT`, and store them into `result`
gen_for_model(generator, ctx, zero, len, one, |generator, ctx, _hooks, i| {
// Load the i-th int32 in the input sequence
let int = input_sequence
.instance
.get(generator, ctx, |f| f.items)
.get_index(generator, ctx, i.value)
.value
.into_int_value();
// Cast to SizeT
let int = Int(SizeT).s_extend_or_bit_cast(generator, ctx, int);
// Store
result.set_index(ctx, i.value, int);
Ok(())
})
.unwrap();
(len, result)
}
TypeEnum::TTuple { .. } => {
// 2. A tuple of ints; e.g., `np.empty((600, 800, 3))`
let input_sequence = TupleObject::from_object(ctx, input_sequence);
let len = input_sequence.len(generator, ctx);
let result = Int(SizeT).array_alloca(generator, ctx, len.value);
for i in 0..input_sequence.num_elements() {
// Get the i-th element off of the tuple and load it into `result`.
let int = input_sequence.index(ctx, i).value.into_int_value();
let int = Int(SizeT).s_extend_or_bit_cast(generator, ctx, int);
result.set_index_const(ctx, i64::try_from(i).unwrap(), int);
}
(len, 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_sequence.value.into_int_value();
let len = Int(SizeT).const_1(generator, ctx.ctx);
let result = Int(SizeT).array_alloca(generator, ctx, len.value);
let int = Int(SizeT).s_extend_or_bit_cast(generator, ctx, input_int);
// Storing into result[0]
result.store(ctx, int);
(len, result)
}
_ => panic!(
"encountered unknown sequence type: {}",
ctx.unifier.stringify(input_sequence.ty)
),
}
}

View File

@ -1,118 +0,0 @@
use super::{indexing::RustNDIndex, NDArrayObject};
use crate::codegen::{
irrt::{call_nac3_ndarray_reshape_resolve_and_check_new_shape, call_nac3_ndarray_transpose},
model::*,
CodeGenContext, CodeGenerator,
};
impl<'ctx> NDArrayObject<'ctx> {
/// Make sure the ndarray is at least `ndmin`-dimensional.
///
/// If this ndarray's `ndims` is less than `ndmin`, a view is created on this with 1s prepended to the shape.
/// If this ndarray's `ndims` is not less than `ndmin`, this function does nothing and return this ndarray.
#[must_use]
pub fn atleast_nd<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
ndmin: u64,
) -> Self {
if self.ndims < ndmin {
// Extend the dimensions with np.newaxis.
let mut indices = vec![];
for _ in self.ndims..ndmin {
indices.push(RustNDIndex::NewAxis);
}
indices.push(RustNDIndex::Ellipsis);
self.index(generator, ctx, &indices)
} else {
*self
}
}
/// Create a reshaped view on this ndarray like `np.reshape()`.
///
/// 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: Instance<'ctx, Ptr<Int<SizeT>>>,
) -> Self {
// 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 current_bb = ctx.builder.get_insert_block().unwrap();
let then_bb = ctx.ctx.insert_basic_block_after(current_bb, "then_bb");
let else_bb = ctx.ctx.insert_basic_block_after(then_bb, "else_bb");
let end_bb = ctx.ctx.insert_basic_block_after(else_bb, "end_bb");
let dst_ndarray = NDArrayObject::alloca(generator, ctx, self.dtype, new_ndims);
dst_ndarray.copy_shape_from_array(generator, ctx, new_shape);
// Reolsve negative indices
let size = self.size(generator, ctx);
let dst_ndims = dst_ndarray.ndims_llvm(generator, ctx.ctx);
let dst_shape = dst_ndarray.instance.get(generator, ctx, |f| f.shape);
call_nac3_ndarray_reshape_resolve_and_check_new_shape(
generator, ctx, size, dst_ndims, dst_shape,
);
let is_c_contiguous = self.is_c_contiguous(generator, ctx);
ctx.builder.build_conditional_branch(is_c_contiguous.value, then_bb, else_bb).unwrap();
// Inserting into then_bb: reshape is possible without copying
ctx.builder.position_at_end(then_bb);
dst_ndarray.set_strides_contiguous(generator, ctx);
dst_ndarray.instance.set(ctx, |f| f.data, self.instance.get(generator, ctx, |f| f.data));
ctx.builder.build_unconditional_branch(end_bb).unwrap();
// Inserting into else_bb: reshape is impossible without copying
ctx.builder.position_at_end(else_bb);
dst_ndarray.create_data(generator, ctx);
dst_ndarray.copy_data_from(generator, ctx, *self);
ctx.builder.build_unconditional_branch(end_bb).unwrap();
// Reposition for continuation
ctx.builder.position_at_end(end_bb);
dst_ndarray
}
/// Create a transposed view on this ndarray like `np.transpose(<ndarray>, <axes> = None)`.
/// * `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<Instance<'ctx, Ptr<Int<SizeT>>>>,
) -> Self {
// Define models
let transposed_ndarray = NDArrayObject::alloca(generator, ctx, self.dtype, self.ndims);
let num_axes = self.ndims_llvm(generator, ctx.ctx);
// `axes = nullptr` if `axes` is unspecified.
let axes = axes.unwrap_or_else(|| Ptr(Int(SizeT)).nullptr(generator, ctx.ctx));
call_nac3_ndarray_transpose(
generator,
ctx,
self.instance,
transposed_ndarray.instance,
num_axes,
axes,
);
transposed_ndarray
}
}

View File

@ -1,98 +0,0 @@
use inkwell::values::StructValue;
use itertools::Itertools;
use super::any::AnyObject;
use crate::{
codegen::{model::*, CodeGenContext, CodeGenerator},
typecheck::typedef::{Type, TypeEnum},
};
/// A NAC3 tuple object.
///
/// NOTE: This struct has no copy trait.
#[derive(Debug, Clone)]
pub struct TupleObject<'ctx> {
/// The type of the tuple.
pub tys: Vec<Type>,
/// The underlying LLVM struct value of this tuple.
pub value: StructValue<'ctx>,
}
impl<'ctx> TupleObject<'ctx> {
pub fn from_object(ctx: &mut CodeGenContext<'ctx, '_>, object: AnyObject<'ctx>) -> Self {
// TODO: Keep `is_vararg_ctx` from TTuple?
// Sanity check on object type.
let TypeEnum::TTuple { ty: tys, .. } = &*ctx.unifier.get_ty(object.ty) else {
panic!(
"Expected type to be a TypeEnum::TTuple, got {}",
ctx.unifier.stringify(object.ty)
);
};
// Check number of fields
let value = object.value.into_struct_value();
let value_num_fields = value.get_type().count_fields() as usize;
assert!(
value_num_fields == tys.len(),
"Tuple type has {} item(s), but the LLVM struct value has {} field(s)",
tys.len(),
value_num_fields
);
TupleObject { tys: tys.clone(), value }
}
/// Convenience function. Create a [`TupleObject`] from an iterator of objects.
pub fn from_objects<I, G: CodeGenerator + ?Sized>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
objects: I,
) -> Self
where
I: IntoIterator<Item = AnyObject<'ctx>>,
{
let (values, tys): (Vec<_>, Vec<_>) =
objects.into_iter().map(|object| (object.value, object.ty)).unzip();
let llvm_tys = tys.iter().map(|ty| ctx.get_llvm_type(generator, *ty)).collect_vec();
let llvm_tuple_ty = ctx.ctx.struct_type(&llvm_tys, false);
let pllvm_tuple = ctx.builder.build_alloca(llvm_tuple_ty, "tuple").unwrap();
for (i, val) in values.into_iter().enumerate() {
let pval = ctx.builder.build_struct_gep(pllvm_tuple, i as u32, "value").unwrap();
ctx.builder.build_store(pval, val).unwrap();
}
let value = ctx.builder.build_load(pllvm_tuple, "").unwrap().into_struct_value();
TupleObject { tys, value }
}
#[must_use]
pub fn num_elements(&self) -> usize {
self.tys.len()
}
/// Get the `len()` of this tuple.
#[must_use]
pub fn len<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
) -> Instance<'ctx, Int<SizeT>> {
Int(SizeT).const_int(generator, ctx.ctx, self.num_elements() as u64, false)
}
/// Get the `i`-th (0-based) object in this tuple.
pub fn index(&self, ctx: &mut CodeGenContext<'ctx, '_>, i: usize) -> AnyObject<'ctx> {
assert!(
i < self.num_elements(),
"Tuple object with length {} have index {i}",
self.num_elements()
);
let value = ctx.builder.build_extract_value(self.value, i as u32, "tuple[{i}]").unwrap();
let ty = self.tys[i];
AnyObject { ty, value }
}
}

View File

@ -1 +0,0 @@
pub mod slice;

View File

@ -1,125 +0,0 @@
use crate::codegen::{model::*, CodeGenContext, CodeGenerator};
/// Fields of [`Slice`]
#[derive(Debug, Clone)]
pub struct SliceFields<'ctx, F: FieldTraversal<'ctx>, N: IntKind<'ctx>> {
pub start_defined: F::Output<Int<Bool>>,
pub start: F::Output<Int<N>>,
pub stop_defined: F::Output<Int<Bool>>,
pub stop: F::Output<Int<N>>,
pub step_defined: F::Output<Int<Bool>>,
pub step: F::Output<Int<N>>,
}
/// An IRRT representation of an (unresolved) slice.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Slice<N>(pub N);
impl<'ctx, N: IntKind<'ctx>> StructKind<'ctx> for Slice<N> {
type Fields<F: FieldTraversal<'ctx>> = SliceFields<'ctx, F, N>;
fn iter_fields<F: FieldTraversal<'ctx>>(&self, traversal: &mut F) -> Self::Fields<F> {
Self::Fields {
start_defined: traversal.add_auto("start_defined"),
start: traversal.add("start", Int(self.0)),
stop_defined: traversal.add_auto("stop_defined"),
stop: traversal.add("stop", Int(self.0)),
step_defined: traversal.add_auto("step_defined"),
step: traversal.add("step", Int(self.0)),
}
}
}
/// A Rust structure that has [`Slice`] utilities and looks like a [`Slice`] but
/// `start`, `stop` and `step` are held by LLVM registers only and possibly
/// [`Option::None`] if unspecified.
#[derive(Debug, Clone)]
pub struct RustSlice<'ctx, N: IntKind<'ctx>> {
// It is possible that `start`, `stop`, and `step` are all `None`.
// We need to know the `int_kind` even when that is the case.
pub int_kind: N,
pub start: Option<Instance<'ctx, Int<N>>>,
pub stop: Option<Instance<'ctx, Int<N>>>,
pub step: Option<Instance<'ctx, Int<N>>>,
}
impl<'ctx, N: IntKind<'ctx>> RustSlice<'ctx, N> {
/// Write the contents to an LLVM [`Slice`].
pub fn write_to_slice<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &CodeGenContext<'ctx, '_>,
dst_slice_ptr: Instance<'ctx, Ptr<Struct<Slice<N>>>>,
) {
let false_ = Int(Bool).const_false(generator, ctx.ctx);
let true_ = Int(Bool).const_true(generator, ctx.ctx);
match self.start {
Some(start) => {
dst_slice_ptr.gep(ctx, |f| f.start_defined).store(ctx, true_);
dst_slice_ptr.gep(ctx, |f| f.start).store(ctx, start);
}
None => dst_slice_ptr.gep(ctx, |f| f.start_defined).store(ctx, false_),
}
match self.stop {
Some(stop) => {
dst_slice_ptr.gep(ctx, |f| f.stop_defined).store(ctx, true_);
dst_slice_ptr.gep(ctx, |f| f.stop).store(ctx, stop);
}
None => dst_slice_ptr.gep(ctx, |f| f.stop_defined).store(ctx, false_),
}
match self.step {
Some(step) => {
dst_slice_ptr.gep(ctx, |f| f.step_defined).store(ctx, true_);
dst_slice_ptr.gep(ctx, |f| f.step).store(ctx, step);
}
None => dst_slice_ptr.gep(ctx, |f| f.step_defined).store(ctx, false_),
}
}
}
pub mod util {
use nac3parser::ast::Expr;
use crate::{
codegen::{model::*, CodeGenContext, CodeGenerator},
typecheck::typedef::Type,
};
use super::RustSlice;
/// Generate LLVM IR for an [`ExprKind::Slice`] and convert it into a [`RustSlice`].
#[allow(clippy::type_complexity)]
pub fn gen_slice<'ctx, G: CodeGenerator>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
lower: &Option<Box<Expr<Option<Type>>>>,
upper: &Option<Box<Expr<Option<Type>>>>,
step: &Option<Box<Expr<Option<Type>>>>,
) -> Result<RustSlice<'ctx, Int32>, String> {
let mut help = |value_expr: &Option<Box<Expr<Option<Type>>>>| -> Result<_, String> {
Ok(match value_expr {
None => None,
Some(value_expr) => {
let value_expr = generator
.gen_expr(ctx, value_expr)?
.unwrap()
.to_basic_value_enum(ctx, generator, ctx.primitives.int32)?;
let value_expr =
Int(Int32).check_value(generator, ctx.ctx, value_expr).unwrap();
Some(value_expr)
}
})
};
let start = help(lower)?;
let stop = help(upper)?;
let step = help(step)?;
Ok(RustSlice { int_kind: Int32, start, stop, step })
}
}

View File

@ -12,16 +12,14 @@ use nac3parser::ast::{
};
use super::{
classes::{ArrayLikeIndexer, ArraySliceValue, ListValue, RangeValue},
expr::{destructure_range, gen_binop_expr},
gen_in_range_check,
irrt::{handle_slice_indices, list_slice_assignment},
macros::codegen_unreachable,
object::{
any::AnyObject,
ndarray::{
indexing::util::gen_ndarray_subscript_ndindices, NDArrayObject, ScalarOrNDArray,
},
types::ndarray::NDArrayType,
values::{
ndarray::{RustNDIndex, ScalarOrNDArray},
ArrayLikeIndexer, ArraySliceValue, ListValue, ProxyValue, RangeValue,
},
CodeGenContext, CodeGenerator,
};
@ -316,7 +314,7 @@ pub fn gen_setitem<'ctx, G: CodeGenerator>(
.unwrap()
.to_basic_value_enum(ctx, generator, target_ty)?
.into_pointer_value();
let target = ListValue::from_ptr_val(target, llvm_usize, None);
let target = ListValue::from_pointer_value(target, llvm_usize, None);
if let ExprKind::Slice { .. } = &key.node {
// Handle assigning to a slice
@ -337,7 +335,7 @@ pub fn gen_setitem<'ctx, G: CodeGenerator>(
let value =
value.to_basic_value_enum(ctx, generator, value_ty)?.into_pointer_value();
let value = ListValue::from_ptr_val(value, llvm_usize, None);
let value = ListValue::from_pointer_value(value, llvm_usize, None);
let target_item_ty = ctx.get_llvm_type(generator, target_item_ty);
let Some(src_ind) = handle_slice_indices(
@ -422,37 +420,42 @@ pub fn gen_setitem<'ctx, G: CodeGenerator>(
.gen_expr(ctx, target)?
.unwrap()
.to_basic_value_enum(ctx, generator, target_ty)?;
let target = AnyObject { value: target, ty: target_ty };
// Process key
let key = gen_ndarray_subscript_ndindices(generator, ctx, key)?;
let key = RustNDIndex::from_subscript_expr(generator, ctx, key)?;
// Process value
let value = value.to_basic_value_enum(ctx, generator, value_ty)?;
let value = AnyObject { value, ty: value_ty };
/*
Reference code:
```python
target = target[key]
value = np.asarray(value)
// 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.
// ```
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 = NDArrayObject::from_object(generator, ctx, target);
let target = NDArrayType::from_unifier_type(generator, ctx, target_ty)
.map_value(target.into_pointer_value(), None);
let target = target.index(generator, ctx, &key);
let value =
ScalarOrNDArray::split_object(generator, ctx, value).to_ndarray(generator, ctx);
let value = ScalarOrNDArray::from_value(generator, ctx, (value_ty, value))
.to_ndarray(generator, ctx);
let broadcast_result = NDArrayObject::broadcast(generator, ctx, &[target, value]);
let broadcast_ndims =
[target.get_type().ndims(), value.get_type().ndims()].into_iter().max().unwrap();
let broadcast_result = NDArrayType::new(
generator,
ctx.ctx,
value.get_type().element_type(),
broadcast_ndims,
)
.broadcast(generator, ctx, &[target, value]);
let target = broadcast_result.ndarrays[0];
let value = broadcast_result.ndarrays[1];
@ -509,7 +512,8 @@ pub fn gen_for<G: CodeGenerator>(
TypeEnum::TObj { obj_id, .. }
if *obj_id == ctx.primitives.range.obj_id(&ctx.unifier).unwrap() =>
{
let iter_val = RangeValue::from_ptr_val(iter_val.into_pointer_value(), Some("range"));
let iter_val =
RangeValue::from_pointer_value(iter_val.into_pointer_value(), Some("range"));
// Internal variable for loop; Cannot be assigned
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

View File

@ -16,8 +16,8 @@ use nac3parser::{
use parking_lot::RwLock;
use super::{
classes::{ListType, ProxyType, RangeType},
concrete_type::ConcreteTypeStore,
types::{ndarray::NDArrayType, ListType, ProxyType, RangeType},
CodeGenContext, CodeGenLLVMOptions, CodeGenTargetMachineOptions, CodeGenTask, CodeGenerator,
DefaultCodeGenerator, WithCall, WorkerRegistry,
};
@ -36,7 +36,6 @@ use crate::{
struct Resolver {
id_to_type: HashMap<StrRef, Type>,
id_to_def: RwLock<HashMap<StrRef, DefinitionId>>,
class_names: HashMap<StrRef, Type>,
}
impl Resolver {
@ -104,11 +103,9 @@ fn test_primitives() {
let top_level = Arc::new(composer.make_top_level_context());
unifier.top_level = Some(top_level.clone());
let resolver = Arc::new(Resolver {
id_to_type: HashMap::new(),
id_to_def: RwLock::new(HashMap::new()),
class_names: HashMap::default(),
}) as Arc<dyn SymbolResolver + Send + Sync>;
let resolver =
Arc::new(Resolver { id_to_type: HashMap::new(), id_to_def: RwLock::new(HashMap::new()) })
as Arc<dyn SymbolResolver + Send + Sync>;
let threads = vec![DefaultCodeGenerator::new("test".into(), 32).into()];
let signature = FunSignature {
@ -298,11 +295,7 @@ fn test_simple_call() {
loc: None,
})));
let resolver = Resolver {
id_to_type: HashMap::new(),
id_to_def: RwLock::new(HashMap::new()),
class_names: HashMap::default(),
};
let resolver = Resolver { id_to_type: HashMap::new(), id_to_def: RwLock::new(HashMap::new()) };
resolver.add_id_def("foo".into(), DefinitionId(foo_id));
let resolver = Arc::new(resolver) as Arc<dyn SymbolResolver + Send + Sync>;
@ -452,7 +445,7 @@ fn test_classes_list_type_new() {
let llvm_usize = generator.get_size_type(&ctx);
let llvm_list = ListType::new(&generator, &ctx, llvm_i32.into());
assert!(ListType::is_type(llvm_list.as_base_type(), llvm_usize).is_ok());
assert!(ListType::is_representable(llvm_list.as_base_type(), llvm_usize).is_ok());
}
#[test]
@ -460,5 +453,17 @@ fn test_classes_range_type_new() {
let ctx = inkwell::context::Context::create();
let llvm_range = RangeType::new(&ctx);
assert!(RangeType::is_type(llvm_range.as_base_type()).is_ok());
assert!(RangeType::is_representable(llvm_range.as_base_type()).is_ok());
}
#[test]
fn test_classes_ndarray_type_new() {
let ctx = inkwell::context::Context::create();
let generator = DefaultCodeGenerator::new(String::new(), 64);
let llvm_i32 = ctx.i32_type();
let llvm_usize = generator.get_size_type(&ctx);
let llvm_ndarray = NDArrayType::new(&generator, &ctx, llvm_i32.into(), 2);
assert!(NDArrayType::is_representable(llvm_ndarray.as_base_type(), llvm_usize).is_ok());
}

View File

@ -0,0 +1,357 @@
use inkwell::{
context::{AsContextRef, Context},
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType},
values::{IntValue, PointerValue},
AddressSpace, IntPredicate, OptimizationLevel,
};
use itertools::Itertools;
use nac3core_derive::StructFields;
use super::ProxyType;
use crate::{
codegen::{
types::structure::{
check_struct_type_matches_fields, FieldIndexCounter, StructField, StructFields,
},
values::{ListValue, ProxyValue},
CodeGenContext, CodeGenerator,
},
typecheck::typedef::{iter_type_vars, Type, TypeEnum},
};
/// Proxy type for a `list` type in LLVM.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct ListType<'ctx> {
ty: PointerType<'ctx>,
item: Option<BasicTypeEnum<'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> {
/// Checks whether `llvm_ty` represents a `list` 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 `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}"))
}
})],
)
}
/// Returns an instance of [`StructFields`] containing all field accessors for this type.
#[must_use]
fn fields(item: BasicTypeEnum<'ctx>, llvm_usize: IntType<'ctx>) -> ListStructFields<'ctx> {
ListStructFields::new_typed(item, llvm_usize)
}
/// See [`ListType::fields`].
// TODO: Move this into e.g. StructProxyType
#[must_use]
pub fn get_fields(&self, _ctx: &impl AsContextRef<'ctx>) -> ListStructFields<'ctx> {
Self::fields(self.item.unwrap_or(self.llvm_usize.into()), self.llvm_usize)
}
/// Creates an LLVM type corresponding to the expected structure of a `List`.
#[must_use]
fn llvm_type(
ctx: &'ctx Context,
element_type: Option<BasicTypeEnum<'ctx>>,
llvm_usize: IntType<'ctx>,
) -> PointerType<'ctx> {
let element_type = element_type.unwrap_or(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())
}
/// Creates an instance of [`ListType`].
#[must_use]
pub fn new<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
element_type: BasicTypeEnum<'ctx>,
) -> Self {
let llvm_usize = generator.get_size_type(ctx);
let llvm_list = Self::llvm_type(ctx, Some(element_type), llvm_usize);
Self { ty: llvm_list, item: Some(element_type), llvm_usize }
}
/// Creates an instance of [`ListType`] with an unknown element type.
#[must_use]
pub fn new_untyped<G: CodeGenerator + ?Sized>(generator: &G, ctx: &'ctx Context) -> Self {
let llvm_usize = generator.get_size_type(ctx);
let llvm_list = Self::llvm_type(ctx, None, llvm_usize);
Self { ty: llvm_list, item: None, llvm_usize }
}
/// Creates an [`ListType`] from a [unifier type][Type].
#[must_use]
pub fn from_unifier_type<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &mut CodeGenContext<'ctx, '_>,
ty: Type,
) -> Self {
// Check unifier type and extract `item_type`
let elem_type = match &*ctx.unifier.get_ty_immutable(ty) {
TypeEnum::TObj { obj_id, params, .. }
if *obj_id == ctx.primitives.list.obj_id(&ctx.unifier).unwrap() =>
{
iter_type_vars(params).next().unwrap().ty
}
_ => panic!("Expected `list` type, but got {}", ctx.unifier.stringify(ty)),
};
let llvm_usize = generator.get_size_type(ctx.ctx);
let llvm_elem_type = if let TypeEnum::TVar { .. } = &*ctx.unifier.get_ty_immutable(ty) {
None
} else {
Some(ctx.get_llvm_type(generator, elem_type))
};
Self {
ty: Self::llvm_type(ctx.ctx, llvm_elem_type, llvm_usize),
item: llvm_elem_type,
llvm_usize,
}
}
/// Creates an [`ListType`] from a [`PointerType`].
#[must_use]
pub fn from_type(ptr_ty: PointerType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
debug_assert!(Self::is_representable(ptr_ty, llvm_usize).is_ok());
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.
#[must_use]
pub fn size_type(&self) -> IntType<'ctx> {
self.llvm_usize
}
/// Returns the element type of this `list` type.
#[must_use]
pub fn element_type(&self) -> Option<BasicTypeEnum<'ctx>> {
self.item
}
/// Allocates an instance of [`ListValue`] 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 [`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,
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,
)
}
/// Allocates a [`ListValue`] on the stack using `item` of this [`ListType`] instance.
///
/// The returned list will contain:
///
/// - `data`: Allocated with `len` number of elements.
/// - `len`: Initialized to the value of `len` passed to this function.
#[must_use]
pub fn construct<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
len: IntValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
let len = ctx.builder.build_int_z_extend(len, self.llvm_usize, "").unwrap();
// Generate a runtime assertion if allocating a non-empty list with unknown element type
if ctx.registry.llvm_options.opt_level == OptimizationLevel::None && self.item.is_none() {
let len_eqz = ctx
.builder
.build_int_compare(IntPredicate::EQ, len, self.llvm_usize.const_zero(), "")
.unwrap();
ctx.make_assert(
generator,
len_eqz,
"0:AssertionError",
"Cannot allocate a non-empty list with unknown element type",
[None, None, None],
ctx.current_loc,
);
}
let plist = self.alloca_var(generator, ctx, name);
plist.store_size(ctx, generator, len);
let item = self.item.unwrap_or(self.llvm_usize.into());
plist.create_data(ctx, item, None);
plist
}
/// Convenience function for creating a list with zero elements.
///
/// This function is preferred over [`ListType::construct`] if the length is known to always be
/// 0, as this function avoids injecting an IR assertion for checking if a non-empty untyped
/// list is being allocated.
///
/// The returned list will contain:
///
/// - `data`: Initialized to `(T*) 0`.
/// - `len`: Initialized to `0`.
#[must_use]
pub fn construct_empty<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
let plist = self.alloca_var(generator, ctx, name);
plist.store_size(ctx, generator, self.llvm_usize.const_zero());
plist.create_data(ctx, self.item.unwrap_or(self.llvm_usize.into()), None);
plist
}
/// Converts an existing value into a [`ListValue`].
#[must_use]
pub fn map_value(
&self,
value: <<Self as ProxyType<'ctx>>::Value as ProxyValue<'ctx>>::Base,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value(value, self.llvm_usize, name)
}
}
impl<'ctx> ProxyType<'ctx> for ListType<'ctx> {
type Base = PointerType<'ctx>;
type Value = ListValue<'ctx>;
fn is_type<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
llvm_ty: impl BasicType<'ctx>,
) -> Result<(), String> {
if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() {
<Self as ProxyType<'ctx>>::is_representable(generator, ctx, ty)
} else {
Err(format!("Expected pointer type, got {llvm_ty:?}"))
}
}
fn is_representable<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
llvm_ty: Self::Base,
) -> Result<(), String> {
Self::is_representable(llvm_ty, generator.get_size_type(ctx))
}
fn alloca_type(&self) -> impl BasicType<'ctx> {
self.as_base_type().get_element_type().into_struct_type()
}
fn as_base_type(&self) -> Self::Base {
self.ty
}
}
impl<'ctx> From<ListType<'ctx>> for PointerType<'ctx> {
fn from(value: ListType<'ctx>) -> Self {
value.as_base_type()
}
}

View File

@ -0,0 +1,125 @@
//! This module contains abstraction over all intrinsic composite types of NAC3.
//!
//! # `raw_alloca` vs `alloca` vs `construct`
//!
//! There are three ways of creating a new object instance using the abstractions provided by this
//! module.
//!
//! - `raw_alloca`: Allocates the object on the stack, returning an instance of
//! [`impl BasicValue`][inkwell::values::BasicValue]. This is similar to a `malloc` expression in
//! C++ but the object is allocated on the stack.
//! - `alloca`: Similar to `raw_alloca`, but also wraps the allocated object with
//! [`<Self as ProxyType<'ctx>>::Value`][ProxyValue], and returns the wrapped object. The returned
//! object will not initialize any value or fields. This is similar to a type-safe `malloc`
//! expression in C++ but the object is allocated on the stack.
//! - `construct`: Similar to `alloca`, but performs some initialization on the value or fields of
//! the returned object. This is similar to a `new` expression in C++ but the object is allocated
//! on the stack.
use inkwell::{
context::Context,
types::BasicType,
values::{IntValue, PointerValue},
};
use super::{
values::{ArraySliceValue, ProxyValue},
{CodeGenContext, CodeGenerator},
};
pub use list::*;
pub use range::*;
pub use tuple::*;
mod list;
pub mod ndarray;
mod range;
pub mod structure;
mod tuple;
pub mod utils;
/// A LLVM type that is used to represent a corresponding type in NAC3.
pub trait ProxyType<'ctx>: Into<Self::Base> {
/// The LLVM type of which values of this type possess. This is usually a
/// [LLVM pointer type][PointerType] for any non-primitive types.
type Base: BasicType<'ctx>;
/// The type of values represented by this type.
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`].
fn is_representable<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
llvm_ty: Self::Base,
) -> 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,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>,
) -> PointerValue<'ctx> {
generator.gen_var_alloc(ctx, self.alloca_type().as_basic_type_enum(), name).unwrap()
}
/// Creates a new array value of this type by invoking `alloca` at the current builder location,
/// returning an [`ArraySliceValue`] encapsulating the resulting array.
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,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
size: IntValue<'ctx>,
name: Option<&'ctx str>,
) -> 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.
fn as_base_type(&self) -> Self::Base;
}

View File

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

View File

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

View File

@ -0,0 +1,250 @@
use inkwell::{
context::Context,
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType},
values::{IntValue, PointerValue},
AddressSpace,
};
use itertools::Itertools;
use nac3core_derive::StructFields;
use crate::{
codegen::{
types::{
structure::{
check_struct_type_matches_fields, FieldIndexCounter, StructField, StructFields,
},
ProxyType,
},
values::{ndarray::ContiguousNDArrayValue, ProxyValue},
CodeGenContext, CodeGenerator,
},
toplevel::numpy::unpack_ndarray_var_tys,
typecheck::typedef::Type,
};
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct ContiguousNDArrayType<'ctx> {
ty: PointerType<'ctx>,
item: BasicTypeEnum<'ctx>,
llvm_usize: IntType<'ctx>,
}
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
pub struct ContiguousNDArrayStructFields<'ctx> {
#[value_type(usize)]
pub ndims: StructField<'ctx, IntValue<'ctx>>,
#[value_type(usize.ptr_type(AddressSpace::default()))]
pub shape: StructField<'ctx, PointerValue<'ctx>>,
#[value_type(i8_type().ptr_type(AddressSpace::default()))]
pub data: StructField<'ctx, PointerValue<'ctx>>,
}
impl<'ctx> ContiguousNDArrayStructFields<'ctx> {
#[must_use]
pub fn new_typed(item: BasicTypeEnum<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
let mut counter = FieldIndexCounter::default();
ContiguousNDArrayStructFields {
ndims: StructField::create(&mut counter, "ndims", llvm_usize),
shape: StructField::create(
&mut counter,
"shape",
llvm_usize.ptr_type(AddressSpace::default()),
),
data: StructField::create(&mut counter, "data", item.ptr_type(AddressSpace::default())),
}
}
}
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 = 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}"))
}
})],
)
}
/// Returns an instance of [`StructFields`] containing all field accessors for this type.
#[must_use]
fn fields(
item: BasicTypeEnum<'ctx>,
llvm_usize: IntType<'ctx>,
) -> ContiguousNDArrayStructFields<'ctx> {
ContiguousNDArrayStructFields::new_typed(item, llvm_usize)
}
/// See [`NDArrayType::fields`].
// TODO: Move this into e.g. StructProxyType
#[must_use]
pub fn get_fields(&self) -> ContiguousNDArrayStructFields<'ctx> {
Self::fields(self.item, self.llvm_usize)
}
/// Creates an LLVM type corresponding to the expected structure of an `NDArray`.
#[must_use]
fn llvm_type(
ctx: &'ctx Context,
item: BasicTypeEnum<'ctx>,
llvm_usize: IntType<'ctx>,
) -> PointerType<'ctx> {
let field_tys =
Self::fields(item, llvm_usize).into_iter().map(|field| field.1).collect_vec();
ctx.struct_type(&field_tys, false).ptr_type(AddressSpace::default())
}
/// Creates an instance of [`ContiguousNDArrayType`].
#[must_use]
pub fn new<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
item: BasicTypeEnum<'ctx>,
) -> Self {
let llvm_usize = 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].
#[must_use]
pub fn from_unifier_type<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &mut CodeGenContext<'ctx, '_>,
ty: Type,
) -> Self {
let (dtype, _) = unpack_ndarray_var_tys(&mut ctx.unifier, ty);
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 }
}
/// Creates an [`ContiguousNDArrayType`] from a [`PointerType`] representing an `NDArray`.
#[must_use]
pub fn from_type(
ptr_ty: PointerType<'ctx>,
item: BasicTypeEnum<'ctx>,
llvm_usize: IntType<'ctx>,
) -> Self {
debug_assert!(Self::is_representable(ptr_ty, llvm_usize).is_ok());
Self { ty: ptr_ty, item, llvm_usize }
}
/// Allocates an instance of [`ContiguousNDArrayValue`] 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.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,
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.item,
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.item,
self.llvm_usize,
name,
)
}
}
impl<'ctx> ProxyType<'ctx> for ContiguousNDArrayType<'ctx> {
type Base = PointerType<'ctx>;
type Value = ContiguousNDArrayValue<'ctx>;
fn is_type<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
llvm_ty: impl BasicType<'ctx>,
) -> Result<(), String> {
if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() {
<Self as ProxyType<'ctx>>::is_representable(generator, ctx, ty)
} else {
Err(format!("Expected pointer type, got {llvm_ty:?}"))
}
}
fn is_representable<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
llvm_ty: Self::Base,
) -> Result<(), String> {
Self::is_representable(llvm_ty, generator.get_size_type(ctx))
}
fn alloca_type(&self) -> impl BasicType<'ctx> {
self.as_base_type().get_element_type().into_struct_type()
}
fn as_base_type(&self) -> Self::Base {
self.ty
}
}
impl<'ctx> From<ContiguousNDArrayType<'ctx>> for PointerType<'ctx> {
fn from(value: ContiguousNDArrayType<'ctx>) -> Self {
value.as_base_type()
}
}

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

@ -0,0 +1,205 @@
use inkwell::{
context::{AsContextRef, Context},
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType},
values::{IntValue, PointerValue},
AddressSpace,
};
use itertools::Itertools;
use nac3core_derive::StructFields;
use crate::codegen::{
types::{
structure::{check_struct_type_matches_fields, StructField, StructFields},
ProxyType,
},
values::{
ndarray::{NDIndexValue, RustNDIndex},
ArrayLikeIndexer, ArraySliceValue, ProxyValue,
},
CodeGenContext, CodeGenerator,
};
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct NDIndexType<'ctx> {
ty: PointerType<'ctx>,
llvm_usize: IntType<'ctx>,
}
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
pub struct NDIndexStructFields<'ctx> {
#[value_type(i8_type())]
pub type_: StructField<'ctx, IntValue<'ctx>>,
#[value_type(i8_type().ptr_type(AddressSpace::default()))]
pub data: StructField<'ctx, PointerValue<'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]
fn fields(
ctx: impl AsContextRef<'ctx>,
llvm_usize: IntType<'ctx>,
) -> NDIndexStructFields<'ctx> {
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]
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())
}
#[must_use]
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);
Self { ty: llvm_ndindex, llvm_usize }
}
#[must_use]
pub fn from_type(ptr_ty: PointerType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
debug_assert!(Self::is_representable(ptr_ty, llvm_usize).is_ok());
Self { ty: ptr_ty, llvm_usize }
}
/// Allocates an instance of [`NDIndexValue`] 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 [`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,
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,
)
}
/// Serialize a list of [`RustNDIndex`] as a newly allocated LLVM array of [`NDIndexValue`].
#[must_use]
pub fn construct_ndindices<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
in_ndindices: &[RustNDIndex<'ctx>],
) -> ArraySliceValue<'ctx> {
// Allocate the LLVM ndindices.
let num_ndindices = self.llvm_usize.const_int(in_ndindices.len() as u64, false);
let ndindices = self.array_alloca_var(generator, ctx, num_ndindices, None);
// Initialize all of them.
for (i, in_ndindex) in in_ndindices.iter().enumerate() {
let pndindex = unsafe {
ndindices.ptr_offset_unchecked(
ctx,
generator,
&ctx.ctx.i64_type().const_int(u64::try_from(i).unwrap(), false),
None,
)
};
in_ndindex.write_to_ndindex(
generator,
ctx,
NDIndexValue::from_pointer_value(pndindex, self.llvm_usize, None),
);
}
ndindices
}
#[must_use]
pub fn map_value(
&self,
value: <<Self as ProxyType<'ctx>>::Value as ProxyValue<'ctx>>::Base,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
<Self as ProxyType<'ctx>>::Value::from_pointer_value(value, self.llvm_usize, name)
}
}
impl<'ctx> ProxyType<'ctx> for NDIndexType<'ctx> {
type Base = PointerType<'ctx>;
type Value = NDIndexValue<'ctx>;
fn is_type<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
llvm_ty: impl BasicType<'ctx>,
) -> Result<(), String> {
if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() {
<Self as ProxyType<'ctx>>::is_representable(generator, ctx, ty)
} else {
Err(format!("Expected pointer type, got {llvm_ty:?}"))
}
}
fn is_representable<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
llvm_ty: Self::Base,
) -> Result<(), String> {
Self::is_representable(llvm_ty, generator.get_size_type(ctx))
}
fn alloca_type(&self) -> impl BasicType<'ctx> {
self.as_base_type().get_element_type().into_struct_type()
}
fn as_base_type(&self) -> Self::Base {
self.ty
}
}
impl<'ctx> From<NDIndexType<'ctx>> for PointerType<'ctx> {
fn from(value: NDIndexType<'ctx>) -> Self {
value.as_base_type()
}
}

View File

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

View File

@ -0,0 +1,452 @@
use inkwell::{
context::{AsContextRef, Context},
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType},
values::{BasicValue, IntValue, PointerValue},
AddressSpace,
};
use itertools::Itertools;
use nac3core_derive::StructFields;
use super::{
structure::{check_struct_type_matches_fields, StructField, StructFields},
ProxyType,
};
use crate::{
codegen::{
values::{ndarray::NDArrayValue, ProxyValue, TypedArrayLikeMutator},
{CodeGenContext, CodeGenerator},
},
toplevel::{helper::extract_ndims, numpy::unpack_ndarray_var_tys},
typecheck::typedef::Type,
};
pub use broadcast::*;
pub use contiguous::*;
pub use indexing::*;
pub use nditer::*;
mod array;
mod broadcast;
mod contiguous;
pub mod factory;
mod indexing;
mod map;
mod nditer;
/// Proxy type for a `ndarray` type in LLVM.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct NDArrayType<'ctx> {
ty: PointerType<'ctx>,
dtype: BasicTypeEnum<'ctx>,
ndims: u64,
llvm_usize: IntType<'ctx>,
}
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
pub struct NDArrayStructFields<'ctx> {
/// The size of each `NDArray` element in bytes.
#[value_type(usize)]
pub itemsize: StructField<'ctx, IntValue<'ctx>>,
/// Number of dimensions in the array.
#[value_type(usize)]
pub ndims: StructField<'ctx, IntValue<'ctx>>,
/// Pointer to an array containing the shape of the `NDArray`.
#[value_type(usize.ptr_type(AddressSpace::default()))]
pub shape: StructField<'ctx, PointerValue<'ctx>>,
/// Pointer to an array indicating the number of bytes between each element at a dimension
#[value_type(usize.ptr_type(AddressSpace::default()))]
pub strides: StructField<'ctx, PointerValue<'ctx>>,
/// Pointer to an array containing the array data
#[value_type(i8_type().ptr_type(AddressSpace::default()))]
pub data: StructField<'ctx, PointerValue<'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.
#[must_use]
fn fields(
ctx: impl AsContextRef<'ctx>,
llvm_usize: IntType<'ctx>,
) -> NDArrayStructFields<'ctx> {
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`.
#[must_use]
fn llvm_type(ctx: &'ctx Context, llvm_usize: IntType<'ctx>) -> PointerType<'ctx> {
let field_tys =
Self::fields(ctx, llvm_usize).into_iter().map(|field| field.1).collect_vec();
ctx.struct_type(&field_tys, false).ptr_type(AddressSpace::default())
}
/// Creates an instance of [`NDArrayType`].
#[must_use]
pub fn new<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
dtype: BasicTypeEnum<'ctx>,
ndims: u64,
) -> Self {
let llvm_usize = generator.get_size_type(ctx);
let llvm_ndarray = Self::llvm_type(ctx, llvm_usize);
NDArrayType { ty: llvm_ndarray, dtype, ndims, llvm_usize }
}
/// Creates an instance of [`NDArrayType`] as a result of a broadcast operation over one or more
/// `ndarray` operands.
#[must_use]
pub fn new_broadcast<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
dtype: BasicTypeEnum<'ctx>,
inputs: &[NDArrayType<'ctx>],
) -> Self {
assert!(!inputs.is_empty());
Self::new(generator, ctx, dtype, inputs.iter().map(NDArrayType::ndims).max().unwrap())
}
/// Creates an instance of [`NDArrayType`] with `ndims` of 0.
#[must_use]
pub fn new_unsized<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
dtype: BasicTypeEnum<'ctx>,
) -> Self {
let llvm_usize = generator.get_size_type(ctx);
let llvm_ndarray = Self::llvm_type(ctx, llvm_usize);
NDArrayType { ty: llvm_ndarray, dtype, ndims: 0, llvm_usize }
}
/// Creates an [`NDArrayType`] 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 (dtype, ndims) = unpack_ndarray_var_tys(&mut ctx.unifier, ty);
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);
NDArrayType {
ty: Self::llvm_type(ctx.ctx, llvm_usize),
dtype: llvm_dtype,
ndims,
llvm_usize,
}
}
/// Creates an [`NDArrayType`] from a [`PointerType`] representing an `NDArray`.
#[must_use]
pub fn from_type(
ptr_ty: PointerType<'ctx>,
dtype: BasicTypeEnum<'ctx>,
ndims: u64,
llvm_usize: IntType<'ctx>,
) -> Self {
debug_assert!(Self::is_representable(ptr_ty, llvm_usize).is_ok());
NDArrayType { ty: ptr_ty, dtype, ndims, llvm_usize }
}
/// Returns the type of the `size` field of this `ndarray` type.
#[must_use]
pub fn size_type(&self) -> IntType<'ctx> {
self.llvm_usize
}
/// Returns the element type of this `ndarray` type.
#[must_use]
pub fn element_type(&self) -> BasicTypeEnum<'ctx> {
self.dtype
}
/// Returns the number of dimensions of this `ndarray` type.
#[must_use]
pub fn ndims(&self) -> u64 {
self.ndims
}
/// Allocates an instance of [`NDArrayValue`] 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.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,
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.dtype,
self.ndims,
self.llvm_usize,
name,
)
}
/// Allocates an [`NDArrayValue`] on the stack and initializes all fields as follows:
///
/// - `data`: uninitialized.
/// - `itemsize`: set to the size of `self.dtype`.
/// - `ndims`: set to the value of `ndims`.
/// - `shape`: allocated on the stack with an array of length `ndims` with uninitialized values.
/// - `strides`: allocated on the stack with an array of length `ndims` with uninitialized
/// values.
#[must_use]
fn construct_impl<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
ndims: IntValue<'ctx>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
let ndarray = self.alloca_var(generator, ctx, name);
let itemsize = ctx
.builder
.build_int_truncate_or_bit_cast(self.dtype.size_of().unwrap(), self.llvm_usize, "")
.unwrap();
ndarray.store_itemsize(ctx, generator, itemsize);
ndarray.store_ndims(ctx, generator, ndims);
ndarray.create_shape(ctx, self.llvm_usize, ndims);
ndarray.create_strides(ctx, self.llvm_usize, ndims);
ndarray
}
/// Allocate an [`NDArrayValue`] on the stack using `dtype` and `ndims` of this [`NDArrayType`]
/// instance.
///
/// The returned ndarray's content will be:
/// - `data`: uninitialized.
/// - `itemsize`: set to the size of `dtype`.
/// - `ndims`: set to the value of `self.ndims`.
/// - `shape`: allocated on the stack with an array of length `ndims` with uninitialized values.
/// - `strides`: allocated on the stack with an array of length `ndims` with uninitialized
/// values.
#[must_use]
pub fn construct_uninitialized<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
let ndims = self.llvm_usize.const_int(self.ndims, false);
self.construct_impl(generator, ctx, ndims, name)
}
/// Convenience function. Allocate an [`NDArrayValue`] with a statically known shape.
///
/// The returned [`NDArrayValue`]'s `data` and `strides` are uninitialized.
#[must_use]
pub fn construct_const_shape<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
shape: &[u64],
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
assert_eq!(shape.len() as u64, self.ndims);
let ndarray = Self::new(generator, ctx.ctx, self.dtype, shape.len() as u64)
.construct_uninitialized(generator, ctx, name);
let llvm_usize = generator.get_size_type(ctx.ctx);
// Write shape
let ndarray_shape = ndarray.shape();
for (i, dim) in shape.iter().enumerate() {
let dim = llvm_usize.const_int(*dim, false);
unsafe {
ndarray_shape.set_typed_unchecked(
ctx,
generator,
&llvm_usize.const_int(i as u64, false),
dim,
);
}
}
ndarray
}
/// Convenience function. Allocate an [`NDArrayValue`] with a dynamically known shape.
///
/// The returned [`NDArrayValue`]'s `data` and `strides` are uninitialized.
#[must_use]
pub fn construct_dyn_shape<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
shape: &[IntValue<'ctx>],
name: Option<&'ctx str>,
) -> <Self as ProxyType<'ctx>>::Value {
assert_eq!(shape.len() as u64, self.ndims);
let ndarray = Self::new(generator, ctx.ctx, self.dtype, shape.len() as u64)
.construct_uninitialized(generator, ctx, name);
let llvm_usize = generator.get_size_type(ctx.ctx);
// Write shape
let ndarray_shape = ndarray.shape();
for (i, dim) in shape.iter().enumerate() {
assert_eq!(
dim.get_type(),
llvm_usize,
"Expected {} but got {}",
llvm_usize.print_to_string(),
dim.get_type().print_to_string()
);
unsafe {
ndarray_shape.set_typed_unchecked(
ctx,
generator,
&llvm_usize.const_int(i as u64, false),
*dim,
);
}
}
ndarray
}
/// Create an unsized ndarray to contain `value`.
#[must_use]
pub fn construct_unsized<G: CodeGenerator + ?Sized>(
&self,
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, '_>,
value: &impl BasicValue<'ctx>,
name: Option<&'ctx str>,
) -> NDArrayValue<'ctx> {
let value = value.as_basic_value_enum();
assert_eq!(value.get_type(), self.dtype);
assert_eq!(self.ndims, 0);
// 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();
ctx.builder.build_store(data, value).unwrap();
let data = ctx
.builder
.build_pointer_cast(data, ctx.ctx.i8_type().ptr_type(AddressSpace::default()), "")
.unwrap();
let ndarray = Self::new_unsized(generator, ctx.ctx, value.get_type())
.construct_uninitialized(generator, ctx, name);
ctx.builder.build_store(ndarray.ptr_to_data(ctx), data).unwrap();
ndarray
}
/// Converts an existing value into a [`NDArrayValue`].
#[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.dtype,
self.ndims,
self.llvm_usize,
name,
)
}
}
impl<'ctx> ProxyType<'ctx> for NDArrayType<'ctx> {
type Base = PointerType<'ctx>;
type Value = NDArrayValue<'ctx>;
fn is_type<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
llvm_ty: impl BasicType<'ctx>,
) -> Result<(), String> {
if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() {
<Self as ProxyType<'ctx>>::is_representable(generator, ctx, ty)
} else {
Err(format!("Expected pointer type, got {llvm_ty:?}"))
}
}
fn is_representable<G: CodeGenerator + ?Sized>(
generator: &G,
ctx: &'ctx Context,
llvm_ty: Self::Base,
) -> Result<(), String> {
Self::is_representable(llvm_ty, generator.get_size_type(ctx))
}
fn alloca_type(&self) -> impl BasicType<'ctx> {
self.as_base_type().get_element_type().into_struct_type()
}
fn as_base_type(&self) -> Self::Base {
self.ty
}
}
impl<'ctx> From<NDArrayType<'ctx>> for PointerType<'ctx> {
fn from(value: NDArrayType<'ctx>) -> Self {
value.as_base_type()
}
}

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