Compare commits

...

202 Commits

Author SHA1 Message Date
44f06a88f5 README: nac3ld -> nac3binutils 2026-02-01 12:17:31 +08:00
c3e2ce5576 README: update artiq link 2026-02-01 12:17:31 +08:00
rclovis
bf900922e2 fix: correct argument order in iterable checks 2026-01-30 20:37:47 +01:00
rclovis
19a841a661 Unpacking for target and added tests 2026-01-30 20:37:45 +01:00
rclovis
ce3c24485a refactor: merge gen_for_enumerate_list with gen_for_enumerate_tuple 2026-01-30 16:11:08 +01:00
rclovis
cfee88277d fix: replace hardcoded empty vector with Vec::new() 2026-01-30 12:13:27 +01:00
rclovis
4c5bf5dbc2 Add enumerate() support for lists 2026-01-30 11:47:22 +01:00
rclovis
7cb171ebe4 Replace Located<ExprKind> with Expr for iterable checks 2026-01-30 11:45:38 +01:00
rclovis
bc6ffe66a1 Add enumerate() support for tuples 2026-01-30 11:45:38 +01:00
rclovis
9384447bb7 Use EnumerateType for struct type creation 2026-01-30 11:45:10 +01:00
rclovis
4831ccc230 Replace hardcoded constants with derived values 2026-01-30 11:45:10 +01:00
rclovis
e137cdb22f Add enumerate() builtin support 2026-01-30 11:45:10 +01:00
rclovis
73c9f6ea2b Add tests and comments 2026-01-29 00:15:25 +01:00
rclovis
a25bec7f3e fix: handle unified type variables in InnerResolver to avoid validation issues 2026-01-28 21:11:42 +01:00
rclovis
9584a93494 fix: allow TVar with constraints to pass concrete check 2026-01-28 20:47:15 +01:00
9614ab5f57 Revert "[core] codegen: Remove redundant &mut"
This reverts commit f8bb6311fb.
2026-01-27 19:14:25 +08:00
f722575332 Revert "[core] codegen: Add __nac3_global_begin to LLVM modules"
This reverts commit 696ad06683.
2026-01-27 19:14:20 +08:00
2472f35c6d Revert "[core] WIP - codegen: Implement typeinfo type/value"
This reverts commit 9ddd7272dc.
2026-01-27 19:14:15 +08:00
c6ca355147 fix type variable usage in generic classes (#735)
Fixes type annotation parsing for generic classes that use type variables.

Co-authored-by: rclovis <clovis.rabot@proton.me>
Reviewed-on: M-Labs/nac3#735
Co-authored-by: rclovis <clovis.rabot@gmail.com>
Co-committed-by: rclovis <clovis.rabot@gmail.com>
2026-01-27 19:06:33 +08:00
9ddd7272dc [core] WIP - codegen: Implement typeinfo type/value 2026-01-27 17:56:42 +08:00
696ad06683 [core] codegen: Add __nac3_global_begin to LLVM modules 2026-01-27 17:21:11 +08:00
f8bb6311fb [core] codegen: Remove redundant &mut 2026-01-27 17:01:10 +08:00
fa7313d562 Remove IDE-specific files 2026-01-23 15:16:22 +08:00
2073dd7108 [core] codegen: Remove redundant values/mod.rs 2026-01-23 15:16:22 +08:00
d1afa99127 [meta] Fix clippy warnings 2026-01-23 15:16:22 +08:00
f93e8c76a0 [core] codegen: Remove lifetime specifiers if reference is temporary 2026-01-23 15:16:22 +08:00
722ec433ac [core] Convert VarValue into a struct 2026-01-23 15:16:22 +08:00
72f4dccbde [core] irrt: Add clangd file 2026-01-23 15:16:22 +08:00
a1d74ec321 [meta] Use resolver version 3 2026-01-23 15:16:22 +08:00
9657818cf7 [standalone] Retain debug information in dmeo.c
Useful when demo.c segfaults due to incorrectly shaped objects being
passed across FFI boundaries.
2026-01-23 15:16:22 +08:00
9578729076 nac3symbolizer bugfixes
Fixes the failed test in https://nixbld.m-labs.hk/eval/10054

Reviewed-on: M-Labs/nac3#733
Co-authored-by: occheung <dc@m-labs.hk>
Co-committed-by: occheung <dc@m-labs.hk>
2026-01-22 18:15:18 +08:00
da3dec1c2a apply clippy suggestions 2026-01-21 13:43:12 +08:00
d7c11e588f cargo fmt 2026-01-21 13:43:12 +08:00
1f7ce27466 debug_line: share new row handling through a macro 2026-01-21 13:43:12 +08:00
747990f9df debug_line: fix missing special opcode operations 2026-01-21 13:43:12 +08:00
5b0e348b8a debug_line: remove redundant break statement 2026-01-21 13:43:12 +08:00
65b9873aa4 debug_line: fix offset calculation 2026-01-21 13:43:12 +08:00
f1e7886832 revert additional lib export 2026-01-21 13:43:12 +08:00
310318d37c revert ld renaming in docs 2026-01-21 13:43:12 +08:00
1684949871 return symbolizer python interface to nac3artiq 2026-01-21 13:43:12 +08:00
ca3f99d262 nac3tools -> nac3binutils 2026-01-21 13:43:12 +08:00
90596e429b symbolizer: use simple std error 2026-01-21 13:43:12 +08:00
5e4ecb67d2 apply clippy suggestions 2026-01-21 13:43:12 +08:00
5ecd5b285d nac3ld -> nactools/linker 2026-01-21 13:43:12 +08:00
f63058da06 create nac3tools pymodule 2026-01-21 13:43:12 +08:00
532b47591c review clippy feedback 2026-01-21 13:43:12 +08:00
3cfc618abb cargo fmt 2026-01-21 13:43:12 +08:00
cac6d5c6fb cargo fmt 2026-01-21 13:43:12 +08:00
809f7df6ef replace maybeuninit with a dummy record 2026-01-21 13:43:12 +08:00
a4e6c3ff1f dwarf: actually return &str in read_str 2026-01-21 13:43:12 +08:00
ebdec61396 line program: remove start_addr 2026-01-21 13:43:12 +08:00
8c8067feb4 implement all specified DW_FORM_* skipper 2026-01-21 13:43:12 +08:00
6811ea29a7 cargo fmt 2026-01-21 13:43:12 +08:00
0ee5eaf0b9 allow unrecognized line program opcode
by skipping. Since the intended impact is unknown.
2026-01-21 13:43:12 +08:00
183ddbcc81 remove lifetime
.debug_* sections are intended to live forever
2026-01-21 13:43:12 +08:00
301183eaf4 fix unused vars 2026-01-21 13:43:12 +08:00
1465335692 reorganize nac3 tools 2026-01-21 13:43:12 +08:00
db6336257b minor cleanup 2026-01-21 13:43:12 +08:00
8ab8827ce5 fix extended opcode 2026-01-21 13:43:12 +08:00
c41b19fa4c symbolizer: support multiple addresses as argument 2026-01-21 13:43:12 +08:00
e05f8cdc53 removed unused pymodule declaration 2026-01-21 13:43:12 +08:00
c102fb87ff replace uninit vec with a mutable vec reference 2026-01-21 13:43:12 +08:00
b7d324e501 cargo fmt 2026-01-21 13:43:12 +08:00
7e8fde6443 symbolizer: report file and directories and strings 2026-01-21 13:43:12 +08:00
b6fae43ed3 remove unused bindgens 2026-01-21 13:43:12 +08:00
d4df169c27 init symbolizer 2026-01-21 13:43:12 +08:00
b0d2c5529c msys2: python 3.13 2026-01-20 15:04:24 +08:00
3c9d11e04d revert nixpkgs update 2026-01-19 18:12:07 +08:00
d8bb3aa306 update dependencies 2026-01-19 17:48:47 +08:00
b601bb6051 [core] codegen: Cast exception to i8* before calling raise 2026-01-19 17:13:23 +08:00
b25c760332 [core] codegen: Use i8 for stack-allocated variables, fix assertion
See #321 for rationale.
2026-01-19 15:31:39 +08:00
9051fc78aa Merge remote-tracking branch 'ivan/misc/ptr-independent-code' 2026-01-19 10:08:23 +08:00
9e41f16328 Revert "implement enumerate() builtin"
This reverts commit 5bb16da035.
2026-01-19 09:57:07 +08:00
78c3c8c26f Revert "Iteration through tuples (#688)"
This reverts commit d426fd50a1.
2026-01-19 09:56:57 +08:00
1eaa724f31 [core] simplify ProxyType et al 2026-01-14 19:32:47 +08:00
d426fd50a1 Iteration through tuples (#688)
Co-authored-by: rclovis <clovis.rabot@gmail.com>
Co-committed-by: rclovis <clovis.rabot@gmail.com>
2026-01-14 17:02:57 +08:00
245cd7c246 Revert "implement enumerate() builtin"
This reverts commit 5bb16da035.
2026-01-13 00:47:12 +08:00
6bcc5b3529 [meta] Update dependencies and bump Inkwell to v0.8 2026-01-12 12:07:25 +08:00
5bb16da035 implement enumerate() builtin
Co-authored-by: rclovis <clovis.rabot@gmail.com>
Co-committed-by: rclovis <clovis.rabot@gmail.com>
2026-01-08 19:25:44 +08:00
7f7f15793c flake: update ARTIQ source used for PGO 2026-01-08 19:24:49 +08:00
db31ddce2c cargo: update dependencies 2026-01-08 11:21:20 +08:00
085efc3714 cleanup 2026-01-08 11:18:25 +08:00
8f4e575f8a flake: update ARTIQ source used for PGO 2026-01-07 16:23:41 +08:00
36cb6ec8b4 nac3ld: separate debug sections into a debug object file 2026-01-07 12:53:40 +08:00
1c1f33fe83 codegen: use service_id 0 for attr writeback 2026-01-05 17:44:55 +08:00
7dad4dbb39 fix: enhance error logging for unresolved types (#706)
enhance error logging for unresolved types
allows to have a clearer error log for instances where `try_fold_special_call` doesn't handle builtin functions

from

```
expected concrete type at /home/sb/artiq/artiq/coredevice/ad9912.py:235:23 but got TVar
```

to
```
expected concrete type for call to `round64` at /nix/store/wzispgjcd7lc29va10i6cprkkx9izcbj-source/artiq/coredevice/ad9912.py:235:23, but found unresolved type `TVar`. The type variable `R` must be one of [int64, ndarray[int64, N]], but could not be inferred.
```

Co-authored-by: rclovis <clovis.rabot@epitech.eu>
Co-authored-by: rclovis <clovis.rabot@proton.me>
Reviewed-on: M-Labs/nac3#706
Co-authored-by: rclovis <clovis.rabot@gmail.com>
Co-committed-by: rclovis <clovis.rabot@gmail.com>
2026-01-05 14:36:03 +08:00
rclovis
abaa96b5ec fix: replaced magic number with PrimDef::Exception id 2025-12-30 18:55:19 +01:00
4f19a5e0a7 flake: update copy of ARTIQ used for PGO 2025-12-30 22:48:48 +08:00
rclovis
a63401893c fix: add min, max, and abs functions to built-in registry 2025-12-30 15:34:10 +01:00
b4fc845571 flake: update copy of ARTIQ used for PGO 2025-12-30 22:20:17 +08:00
ef7ed97924 cargo: update dependencies 2025-12-30 22:19:20 +08:00
rclovis
9d6eaa0dff fix: Add Float64 back to ArtiqBuiltinRegistry to resolve as Float 2025-12-30 13:08:58 +01:00
rclovis
49f803307c refactor: Remove Float64 from built-in types 2025-12-30 10:16:39 +01:00
rclovis
1896ea06a6 fix: Apply clippy nursery 2025-12-29 17:58:47 +01:00
rclovis
1b681b2304 fix: removed comment 2025-12-29 17:08:19 +01:00
rclovis
64e7486a63 fix: update tests to match the new PrimDef Ids 2025-12-29 17:08:19 +01:00
rclovis
adc83a2d9b refactor: removed entirely the builtins from the TopLevelComposer for it to rely only on the BuiltinRegistry 2025-12-29 17:08:17 +01:00
rclovis
114d1b26bf refactor: Remove BuiltinKind, use PrimDef for builtin registry 2025-12-29 17:07:53 +01:00
rclovis
837d464242 fix: complete builtin_dict with np and sp functions 2025-12-29 17:04:49 +01:00
7ec429d68f [artiq] Apply clippy nursery 2025-12-29 14:03:46 +08:00
d6aa17cb2a [core] Apply clippy nursery 2025-12-29 14:03:46 +08:00
3e6592ca97 [core_derive] Apply clippy nusery 2025-12-29 14:03:46 +08:00
d6fa96bfab [parser] Apply clippy nursery 2025-12-29 14:03:46 +08:00
844b7eeb8d [ast] Apply clippy nursery 2025-12-29 14:03:46 +08:00
9445c71156 [standalone] Apply clippy nursery 2025-12-29 14:03:46 +08:00
4acb896a18 [ld] Apply clippy nursery 2025-12-29 14:03:46 +08:00
e60bf48bc0 [runkernel] Apply clippy nursery 2025-12-29 14:03:46 +08:00
37be9cd774 Revert "composer: rm typevar fixing"
This reverts commit 96fe87d3cc.
2025-12-29 14:02:29 +08:00
96fe87d3cc composer: rm typevar fixing 2025-12-16 14:27:46 +08:00
84e18b4b17 msys2: update 2025-12-11 20:08:17 +08:00
a9c5e8796c cargo: update dependencies 2025-12-11 20:05:10 +08:00
rclovis
58b90626a7 fix: remove keyword-list entirely 2025-12-11 09:57:00 +01:00
e45a260ac0 codegen/expr: rm unnecessary early returns 2025-12-11 11:37:28 +08:00
f2aff0aee0 codegen/expr: replace if let w match 2025-12-11 11:37:28 +08:00
40df7e1c24 codegen/expr: simplify nested builder stmt 2025-12-11 11:32:50 +08:00
5c83d560cc codegen/expr: refactor complicated map iterator into for loop 2025-12-11 11:32:50 +08:00
b77196fac7 codegen/expr: refactor find_map usage 2025-12-11 11:32:50 +08:00
e4723d91af codegen: gen_expr simplify match statement 2025-12-11 11:32:50 +08:00
16e8dcac6e codegen: factor out gen_subscript_expr 2025-12-11 11:32:50 +08:00
b5d73f0c3e codegen: factor out gen_call_expr 2025-12-11 11:32:50 +08:00
070efdc3ab codegen: factor out gen_ifexp_expr 2025-12-11 11:32:50 +08:00
39e3e65525 codegen: factor out gen_boolop_expr 2025-12-11 11:32:50 +08:00
fd7453fdb3 codegen: factor out gen_attr_expr 2025-12-11 11:32:50 +08:00
a04a699f41 codegen: factor out gen_tuple_expr 2025-12-11 11:32:50 +08:00
c1ddf4fe57 codegen: factor out gen_list_expr 2025-12-11 11:32:50 +08:00
rclovis
418b45b618 fix: rename NDArray to NpNDArray for consistency, add missing PrimDef types to BuiltinKind 2025-12-10 10:46:03 +01:00
rclovis
6ddac5c4b0 fix: improve error message for virtual argument count 2025-12-10 10:37:31 +01:00
rclovis
ed30a83b08 fix: match with PrimDef for fold_special_call resolution 2025-12-10 10:37:31 +01:00
rclovis
793c4ebca3 fix: update BuiltinKind to remove NpNDArray all together 2025-12-10 10:37:31 +01:00
rclovis
b499a2da0f Fix clippy warnings 2025-12-10 10:37:31 +01:00
rclovis
9e8239ff60 feat: extend PrimDef to include additional types 2025-12-10 10:31:43 +01:00
1f2ed83a98 flake: update copy of ARTIQ used for PGO 2025-12-02 15:06:00 +08:00
bf1518747c fix: add NumPy functions and identifiers to ARTIQ builtin registry
Adding all identifiers present in try_fold_special_call to the artiq `BuiltinRegistry`

Co-authored-by: rclovis <clovis.rabot@epitech.eu>
Reviewed-on: M-Labs/nac3#705
Co-authored-by: rclovis <clovis.rabot@gmail.com>
Co-committed-by: rclovis <clovis.rabot@gmail.com>
2025-12-01 19:39:20 +08:00
b4ab037e00 flake: update copy of ARTIQ used for PGO 2025-12-01 09:41:47 +08:00
rclovis
95eea9549e helper is_subscript_builtin, uniformly use match statement when possible 2025-12-01 09:31:56 +08:00
rclovis
41579384e3 refactor: streamline ArtiqBuiltinRegistry by merging decorator IDs into id_to_builtin 2025-12-01 09:31:56 +08:00
rclovis
4d637981af Moved BuiltinRegistry standalone trait to a default implementation 2025-12-01 09:31:56 +08:00
rclovis
46fec4da09 moved from ComposerConfig to BuiltinRegistry for identifying built-in types as a builtin API for artiq and standalone 2025-12-01 09:31:56 +08:00
be4470539f [meta] Update dependencies 2025-11-30 12:57:45 +08:00
e00be95066 flake: format 2025-11-18 18:33:34 +08:00
c95e8273d6 msys2: update 2025-11-18 18:33:25 +08:00
8aa614bd50 make_msys2_packages: output in Alejandra style 2025-11-18 18:33:09 +08:00
2ea9771d3d Run symbol stripping and optimizations in a single pass 2025-11-17 22:05:29 +08:00
77f37cfec9 [core] codegen: Cache primitive store and top level 2025-11-17 12:03:21 +08:00
bd4c21a677 [core] Cache unifier after conversion from shared unifier
Improved performance by ~50%.
2025-11-17 11:51:13 +08:00
c869c7cee4 [core] codegen: fix coercion to dynamic for attr access 2025-11-13 15:47:18 +08:00
edec5b4e73 regenerate cargo lockfile 2025-11-04 21:03:31 +08:00
d8b442aa5c [meta] Bump PyO3 to v0.27 2025-11-04 14:16:06 +08:00
9624890f58 [meta] Update dependency versions 2025-11-04 14:08:18 +08:00
66427b0695 cargo: update dependencies 2025-11-01 22:36:04 +08:00
011fa9c86f [core] Fix clippy warning 2025-10-21 16:28:15 +08:00
25b85ab844 [core] Add missing documentation 2025-10-21 16:27:34 +08:00
7b607666e4 [core] codegen: Add orelse to gen_for*_callback, refactor all usages 2025-10-21 16:21:32 +08:00
e3c0fbee86 [core] codegen: Track var_assignment in gen_while_callback 2025-10-21 16:19:57 +08:00
0c77d6e2a7 [core] codegen: Reimplement gen_for using callbacks 2025-10-21 16:19:56 +08:00
d8cde63987 [core] codegen: Add gen_for_pythonic_callback 2025-10-21 16:15:37 +08:00
b2141103ea [core] codegen: Add gen_while_pythonic_callback 2025-10-21 16:15:35 +08:00
ivan-shrimp
6f72347616 purge unused CodeGenerator parameters 2025-10-17 04:13:08 +08:00
83358d7d7c [core] start introducing RtValue type 2025-10-17 03:33:37 +08:00
96406541d1 revert nixpkgs update (needs llvm>=18) 2025-09-29 17:59:21 +08:00
2e930db6ee update dependencies 2025-09-29 17:49:04 +08:00
b66f97db07 [artiq] Remove unneeded GIL lock 2025-09-29 11:13:27 +08:00
cf8d3af11e [artiq] Refactor use of deprecated APIs 2025-09-29 11:13:27 +08:00
b4e3d67f22 [meta] Update dependency versions in Cargo.toml 2025-09-29 11:13:27 +08:00
ecb7256e42 [artiq] Fix for some modules not having __file__ attribute 2025-09-12 09:37:54 +08:00
5b2bb5b432 [artiq] Add test case 2025-09-11 11:05:28 +08:00
39ba8417b9 [core] toplevel: Rewrite resolution logic for module-qualified class names 2025-09-11 11:05:28 +08:00
91e5a31f72 [artiq] Register imported Python modules 2025-09-11 11:05:28 +08:00
3b6fbba0f7 update dependencies 2025-09-10 09:50:10 +08:00
e2384130ae flake: update copy of sources used for PGO 2025-09-10 09:34:29 +08:00
12e51e95be [core] toplevel/composer: Elaborate error message and give more context 2025-09-08 18:45:18 +08:00
425fdf81a7 [core] Minor formatting fixes
For some reason rustfmt doesn't do anything for this file.
2025-09-08 18:44:33 +08:00
cac98e1009 [meta] Warn on clippy::enum_glob_use 2025-09-08 18:44:33 +08:00
050269b94d [artiq] Extract ID fields by module 2025-09-08 18:44:33 +08:00
11fbbdb173 [artiq] Add missing staticmethod field to artiq_builtins 2025-09-08 18:44:33 +08:00
4a946c5d66 [artiq] Keep Generic[T] in base class declaration 2025-09-08 18:44:18 +08:00
50f35d3073 [core] toplevel: Use has_generic_ann to determine Generic[T] 2025-09-08 18:36:44 +08:00
4e778095ed [core] toplevel: Add has_generic_ann_fn to ComposerConfig 2025-09-08 18:36:38 +08:00
6d48b0899e [standalone] Refactor ndarray output functions
- Use `for ... in ndarray` in output functions - Add test case for
range(len(ndarray))
2025-08-29 12:34:35 +08:00
1fee1194db [core] codegen: Implement support for iterating over ndarrays 2025-08-29 12:34:35 +08:00
22f4ecb3c2 [core] type_inferencer: Implement type inference for ndarray iteration 2025-08-29 12:34:35 +08:00
506fa61eea [core] toplevel/numpy: Slightly optimize unpacking ndarray tvars 2025-08-29 12:34:35 +08:00
91f9f7dc65 [core] remove unnecessary rc 2025-08-22 17:46:04 +08:00
c1f7efbc60 [core] set function signature of call by inference 2025-08-22 17:46:00 +08:00
34554f31b6 update MSYS2 packages 2025-08-21 09:05:35 +08:00
621b8f65b9 typecheck: partly revert TCall simplification
This partly reverts commit 797a2dbbad.
2025-08-20 21:44:24 +08:00
e8a56800f4 [meta] Fix warnings and apply clippy suggestions 2025-08-20 12:31:34 +08:00
7b26dacc6d [meta] Update versions in Cargo.toml 2025-08-20 12:31:34 +08:00
5990bfb50a update dependencies 2025-08-20 12:08:47 +08:00
797a2dbbad typecheck: simplify TCall and fix wrong kwarg type 2025-08-20 12:02:48 +08:00
ac6d7b7496 codegen: remove useless object value 2025-08-20 11:18:47 +08:00
ad668122b3 nac3ld: insert debug sections 2025-08-14 11:38:56 +08:00
98aab1c0d9 nac3ld: verify allocated size for dynamic relocs 2025-08-14 11:38:56 +08:00
cb4df67008 nac3ld: reorder target section index calculations 2025-08-14 11:38:56 +08:00
f70456a805 nac3ld: update symbol address resolution
Use loaded address instead of image offset.
.debug_* sections make use of this mechanism to load zeros, since .debug sections are not allocated space.
Existing symbol resolutions are not affected, since sh_addr == sh_offset for all allocated sections by design.
2025-08-14 11:38:56 +08:00
32cffc8864 further update cargo dependencies 2025-08-11 23:15:54 +08:00
ef113d197a update dependencies 2025-08-11 23:04:13 +08:00
6cc230b3f5 [core] Minor doc clarification on __codegen_context_ref 2025-08-11 14:31:52 +08:00
893e2a1e5b [meta] Run cargo fmt/clippy 2025-08-11 13:02:43 +08:00
768560f893 [ld] Use addend in indirect reloc 2025-08-07 20:59:59 +08:00
16c65dca89 refactor: fixup ModuleContext types 2025-08-07 16:05:37 +08:00
7235012a0d flake: update copy of sources used for PGO 2025-08-06 11:20:40 +08:00
163 changed files with 13807 additions and 16828 deletions

651
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[workspace]
members = [
"nac3ld",
"nac3binutils",
"nac3ast",
"nac3parser",
"nac3core",
@@ -9,7 +9,7 @@ members = [
"nac3artiq",
"runkernel",
]
resolver = "2"
resolver = "3"
[profile.release]
strip = true

View File

@@ -15,7 +15,7 @@ NAC3 has a modular design and its applicability reaches beyond ARTIQ. The ``nac3
NAC3 is packaged using the [Nix](https://nixos.org) Flakes system. Enable flakes by adding ``experimental-features = nix-command flakes`` to ``nix.conf`` (e.g. ``~/.config/nix/nix.conf``).
Use ``nix shell git+https://github.com/m-labs/artiq.git?ref=nac3`` to get a shell with the NAC3 version of ARTIQ. See the ``examples`` directory in ARTIQ (``nac3`` Git branch) for some samples of NAC3 kernel code.
Use ``nix shell git+https://git.m-labs.hk/M-Labs/artiq.git?ref=nac3`` to get a shell with the NAC3 version of ARTIQ. See the ``examples`` directory in ARTIQ (``nac3`` Git branch) for some samples of NAC3 kernel code.
### Windows
@@ -52,7 +52,7 @@ This repository contains:
- ``nac3parser``: Python parser (based on RustPython).
- ``nac3core``: Core compiler library, containing type-checking and code generation.
- ``nac3standalone``: Standalone compiler tool (core language only).
- ``nac3ld``: Minimalist RISC-V and ARM linker.
- ``nac3binutils``: Contains binary tools (linker, symbolizer, etc.)
- ``nac3artiq``: Integration with ARTIQ and implementation of ARTIQ-specific extensions to the core language.
- ``runkernel``: Simple program that runs compiled ARTIQ kernels on the host and displays RTIO operations. Useful for testing without hardware.

6
flake.lock generated
View File

@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1753694789,
"narHash": "sha256-cKgvtz6fKuK1Xr5LQW/zOUiAC0oSQoA9nOISB0pJZqM=",
"lastModified": 1757347588,
"narHash": "sha256-tLdkkC6XnsY9EOZW9TlpesTclELy8W7lL2ClL+nma8o=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "dc9637876d0dcc8c9e5e22986b857632effeb727",
"rev": "b599843bad24621dcaa5ab60dac98f9b0eb1cabe",
"type": "github"
},
"original": {

387
flake.nix
View File

@@ -3,207 +3,208 @@
inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-unstable;
outputs = { self, nixpkgs }:
let
pkgs = import nixpkgs { system = "x86_64-linux"; };
pkgs32 = import nixpkgs { system = "i686-linux"; };
in rec {
packages.x86_64-linux = rec {
llvm-nac3 = pkgs.callPackage ./nix/llvm {};
llvm-tools-irrt = pkgs.runCommandNoCC "llvm-tools-irrt" {}
''
outputs = {
self,
nixpkgs,
}: let
pkgs = import nixpkgs {system = "x86_64-linux";};
pkgs32 = import nixpkgs {system = "i686-linux";};
in rec {
packages.x86_64-linux = rec {
llvm-nac3 = pkgs.callPackage ./nix/llvm {};
llvm-tools-irrt =
pkgs.runCommandNoCC "llvm-tools-irrt" {}
''
mkdir -p $out/bin
ln -s ${pkgs.llvmPackages_16.clang-unwrapped}/bin/clang $out/bin/clang-irrt
ln -s ${pkgs.llvmPackages_16.llvm.out}/bin/llvm-as $out/bin/llvm-as-irrt
'';
demo-linalg-stub = pkgs.rustPlatform.buildRustPackage {
name = "demo-linalg-stub";
src = ./nac3standalone/demo/linalg;
'';
demo-linalg-stub = pkgs.rustPlatform.buildRustPackage {
name = "demo-linalg-stub";
src = ./nac3standalone/demo/linalg;
cargoLock = {
lockFile = ./nac3standalone/demo/linalg/Cargo.lock;
};
doCheck = false;
};
demo-linalg-stub32 = pkgs32.rustPlatform.buildRustPackage {
name = "demo-linalg-stub32";
src = ./nac3standalone/demo/linalg;
cargoLock = {
lockFile = ./nac3standalone/demo/linalg/Cargo.lock;
};
doCheck = false;
};
nac3artiq = pkgs.python3Packages.toPythonModule (
pkgs.rustPlatform.buildRustPackage rec {
name = "nac3artiq";
outputs = ["out" "runkernel" "standalone"];
src = self;
cargoLock = {
lockFile = ./nac3standalone/demo/linalg/Cargo.lock;
lockFile = ./Cargo.lock;
};
doCheck = false;
};
demo-linalg-stub32 = pkgs32.rustPlatform.buildRustPackage {
name = "demo-linalg-stub32";
src = ./nac3standalone/demo/linalg;
cargoLock = {
lockFile = ./nac3standalone/demo/linalg/Cargo.lock;
};
doCheck = false;
};
nac3artiq = pkgs.python3Packages.toPythonModule (
pkgs.rustPlatform.buildRustPackage rec {
name = "nac3artiq";
outputs = [ "out" "runkernel" "standalone" ];
src = self;
cargoLock = {
lockFile = ./Cargo.lock;
};
passthru.cargoLock = cargoLock;
nativeBuildInputs = [ pkgs.python3 (pkgs.wrapClangMulti pkgs.llvmPackages_16.clang) llvm-tools-irrt pkgs.llvmPackages_16.llvm.out pkgs.llvmPackages_16.bintools llvm-nac3 ];
buildInputs = [ pkgs.python3 llvm-nac3 pkgs.stdenv.cc.cc.lib ];
checkInputs = [ (pkgs.python3.withPackages(ps: [ ps.numpy ps.scipy ])) ];
checkPhase =
''
echo "Checking nac3standalone demos..."
pushd nac3standalone/demo
patchShebangs .
export DEMO_LINALG_STUB=${demo-linalg-stub}/lib/liblinalg.a
export DEMO_LINALG_STUB32=${demo-linalg-stub32}/lib/liblinalg.a
./check_demos.sh -i686
popd
echo "Running Cargo tests..."
cargoCheckHook
'';
installPhase =
''
PYTHON_SITEPACKAGES=$out/${pkgs.python3Packages.python.sitePackages}
mkdir -p $PYTHON_SITEPACKAGES
cp target/x86_64-unknown-linux-gnu/release/libnac3artiq.so $PYTHON_SITEPACKAGES/nac3artiq.so
mkdir -p $runkernel/bin
cp target/x86_64-unknown-linux-gnu/release/runkernel $runkernel/bin
mkdir -p $standalone/bin
cp target/x86_64-unknown-linux-gnu/release/nac3standalone $standalone/bin
'';
}
);
python3-mimalloc = pkgs.python3 // rec {
withMimalloc = pkgs.python3.buildEnv.override({ makeWrapperArgs = [ "--set LD_PRELOAD ${pkgs.mimalloc}/lib/libmimalloc.so" ]; });
withPackages = f: let packages = f pkgs.python3.pkgs; in withMimalloc.override { extraLibs = packages; };
};
# LLVM PGO support
llvm-nac3-instrumented = pkgs.callPackage ./nix/llvm {
stdenv = pkgs.llvmPackages_16.stdenv;
extraCmakeFlags = [ "-DLLVM_BUILD_INSTRUMENTED=IR" ];
};
nac3artiq-instrumented = pkgs.python3Packages.toPythonModule (
pkgs.rustPlatform.buildRustPackage {
name = "nac3artiq-instrumented";
src = self;
inherit (nac3artiq) cargoLock;
nativeBuildInputs = [ pkgs.python3 packages.x86_64-linux.llvm-tools-irrt pkgs.llvmPackages_16.bintools llvm-nac3-instrumented ];
buildInputs = [ pkgs.python3 llvm-nac3-instrumented ];
cargoBuildFlags = [ "--package" "nac3artiq" "--features" "init-llvm-profile" ];
doCheck = false;
configurePhase =
''
export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-C link-arg=-L${pkgs.llvmPackages_16.compiler-rt}/lib/linux -C link-arg=-lclang_rt.profile-x86_64"
'';
installPhase =
''
TARGET_DIR=$out/${pkgs.python3Packages.python.sitePackages}
mkdir -p $TARGET_DIR
cp target/x86_64-unknown-linux-gnu/release/libnac3artiq.so $TARGET_DIR/nac3artiq.so
'';
}
);
nac3artiq-profile = pkgs.stdenvNoCC.mkDerivation {
name = "nac3artiq-profile";
srcs = [
(pkgs.fetchFromGitHub {
owner = "m-labs";
repo = "sipyco";
rev = "dd3fc30a5b530279f8d75ae77b4de51f3b9870a3";
sha256 = "sha256-ZOTF/NX7SltbYg+OGSfyjo3uTiaCZJJiITZriKt4YdY=";
})
(pkgs.fetchFromGitHub {
owner = "m-labs";
repo = "artiq";
rev = "9b61fe3e32f3beca6d22975c68eae7adf5c9aa74";
sha256 = "sha256-UETeIBZJyLRvx22F901toSDZibEtke1LA5MPAgPUJRE=";
})
];
buildInputs = [
(python3-mimalloc.withPackages(ps: [ ps.numpy ps.scipy ps.jsonschema ps.lmdb ps.platformdirs nac3artiq-instrumented ]))
pkgs.llvmPackages_16.llvm.out
pkgs.llvmPackages_16.bintools
];
phases = [ "buildPhase" "installPhase" ];
buildPhase =
''
srcs=($srcs)
sipyco=''${srcs[0]}
artiq=''${srcs[1]}
export PYTHONPATH=$sipyco:$artiq
python -m artiq.frontend.artiq_ddb_template $artiq/artiq/examples/nac3devices/nac3devices.json > device_db.py
cp $artiq/artiq/examples/nac3devices/nac3devices.py .
python -m artiq.frontend.artiq_compile nac3devices.py
'';
installPhase =
''
mkdir $out
llvm-profdata merge -o $out/llvm.profdata /build/llvm/build/profiles/*
'';
};
llvm-nac3-pgo = pkgs.callPackage ./nix/llvm {
stdenv = pkgs.llvmPackages_16.stdenv;
extraCmakeFlags = [ "-DLLVM_PROFDATA_FILE=${nac3artiq-profile}/llvm.profdata" ];
};
nac3artiq-pgo = pkgs.python3Packages.toPythonModule (
pkgs.rustPlatform.buildRustPackage {
name = "nac3artiq-pgo";
src = self;
inherit (nac3artiq) cargoLock;
nativeBuildInputs = [ pkgs.python3 packages.x86_64-linux.llvm-tools-irrt pkgs.llvmPackages_16.bintools llvm-nac3-pgo ];
buildInputs = [ pkgs.python3 llvm-nac3-pgo ];
cargoBuildFlags = [ "--package" "nac3artiq" ];
cargoTestFlags = [ "--package" "nac3ast" "--package" "nac3parser" "--package" "nac3core" "--package" "nac3artiq" ];
installPhase =
''
TARGET_DIR=$out/${pkgs.python3Packages.python.sitePackages}
mkdir -p $TARGET_DIR
cp target/x86_64-unknown-linux-gnu/release/libnac3artiq.so $TARGET_DIR/nac3artiq.so
'';
}
);
};
packages.x86_64-w64-mingw32 = import ./nix/windows { inherit pkgs; };
devShells.x86_64-linux.default = pkgs.mkShell {
name = "nac3-dev-shell";
buildInputs = with pkgs; [
# build dependencies
packages.x86_64-linux.llvm-nac3
(pkgs.wrapClangMulti llvmPackages_16.clang) llvmPackages_16.llvm.out llvmPackages_16.bintools # for running nac3standalone demos
packages.x86_64-linux.llvm-tools-irrt
cargo
rustc
# runtime dependencies
lld_16 # for running kernels on the host
(packages.x86_64-linux.python3-mimalloc.withPackages(ps: [ ps.numpy ps.scipy ]))
# development tools
cargo-insta
clippy
pre-commit
rustfmt
];
shellHook =
''
export DEMO_LINALG_STUB=${packages.x86_64-linux.demo-linalg-stub}/lib/liblinalg.a
export DEMO_LINALG_STUB32=${packages.x86_64-linux.demo-linalg-stub32}/lib/liblinalg.a
passthru.cargoLock = cargoLock;
nativeBuildInputs = [pkgs.python3 (pkgs.wrapClangMulti pkgs.llvmPackages_16.clang) llvm-tools-irrt pkgs.llvmPackages_16.llvm.out pkgs.llvmPackages_16.bintools llvm-nac3];
buildInputs = [pkgs.python3 llvm-nac3 pkgs.stdenv.cc.cc.lib];
checkInputs = [(pkgs.python3.withPackages (ps: [ps.numpy ps.scipy]))];
checkPhase = ''
echo "Checking nac3standalone demos..."
pushd nac3standalone/demo
patchShebangs .
export DEMO_LINALG_STUB=${demo-linalg-stub}/lib/liblinalg.a
export DEMO_LINALG_STUB32=${demo-linalg-stub32}/lib/liblinalg.a
./check_demos.sh -i686
popd
echo "Running Cargo tests..."
cargoCheckHook
'';
};
devShells.x86_64-linux.msys2 = pkgs.mkShell {
name = "nac3-dev-shell-msys2";
buildInputs = with pkgs; [
curl
pacman
fakeroot
packages.x86_64-w64-mingw32.wine-msys2
];
};
installPhase = ''
PYTHON_SITEPACKAGES=$out/${pkgs.python3Packages.python.sitePackages}
mkdir -p $PYTHON_SITEPACKAGES
cp target/x86_64-unknown-linux-gnu/release/libnac3artiq.so $PYTHON_SITEPACKAGES/nac3artiq.so
hydraJobs = {
inherit (packages.x86_64-linux) llvm-nac3 nac3artiq nac3artiq-pgo;
llvm-nac3-msys2 = packages.x86_64-w64-mingw32.llvm-nac3;
nac3artiq-msys2 = packages.x86_64-w64-mingw32.nac3artiq;
nac3artiq-msys2-pkg = packages.x86_64-w64-mingw32.nac3artiq-pkg;
mkdir -p $runkernel/bin
cp target/x86_64-unknown-linux-gnu/release/runkernel $runkernel/bin
mkdir -p $standalone/bin
cp target/x86_64-unknown-linux-gnu/release/nac3standalone $standalone/bin
'';
}
);
python3-mimalloc =
pkgs.python3
// rec {
withMimalloc = pkgs.python3.buildEnv.override {makeWrapperArgs = ["--set LD_PRELOAD ${pkgs.mimalloc}/lib/libmimalloc.so"];};
withPackages = f: let packages = f pkgs.python3.pkgs; in withMimalloc.override {extraLibs = packages;};
};
# LLVM PGO support
llvm-nac3-instrumented = pkgs.callPackage ./nix/llvm {
stdenv = pkgs.llvmPackages_16.stdenv;
extraCmakeFlags = ["-DLLVM_BUILD_INSTRUMENTED=IR"];
};
nac3artiq-instrumented = pkgs.python3Packages.toPythonModule (
pkgs.rustPlatform.buildRustPackage {
name = "nac3artiq-instrumented";
src = self;
inherit (nac3artiq) cargoLock;
nativeBuildInputs = [pkgs.python3 packages.x86_64-linux.llvm-tools-irrt pkgs.llvmPackages_16.bintools llvm-nac3-instrumented];
buildInputs = [pkgs.python3 llvm-nac3-instrumented];
cargoBuildFlags = ["--package" "nac3artiq" "--features" "init-llvm-profile"];
doCheck = false;
configurePhase = ''
export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-C link-arg=-L${pkgs.llvmPackages_16.compiler-rt}/lib/linux -C link-arg=-lclang_rt.profile-x86_64"
'';
installPhase = ''
TARGET_DIR=$out/${pkgs.python3Packages.python.sitePackages}
mkdir -p $TARGET_DIR
cp target/x86_64-unknown-linux-gnu/release/libnac3artiq.so $TARGET_DIR/nac3artiq.so
'';
}
);
nac3artiq-profile = pkgs.stdenvNoCC.mkDerivation {
name = "nac3artiq-profile";
srcs = [
(pkgs.fetchFromGitHub {
owner = "m-labs";
repo = "sipyco";
rev = "96fcefbea490a9b42c862393860d2e586b05d744";
sha256 = "sha256-DkcgZ0K6lsxzBWc31GTyufuSOpcorVv5OsZLHphHBtg=";
})
(pkgs.fetchFromGitHub {
owner = "m-labs";
repo = "artiq";
rev = "ee9ccd39afeb5eab60277b745c164f3c26a1f569";
sha256 = "sha256-J8ininS9qHz7hbHzsznpqnU870PdyxYqfLI9itj3Gi8=";
})
];
buildInputs = [
(python3-mimalloc.withPackages (ps: [ps.numpy ps.scipy ps.jsonschema ps.lmdb ps.platformdirs nac3artiq-instrumented]))
pkgs.llvmPackages_16.llvm.out
pkgs.llvmPackages_16.bintools
];
phases = ["buildPhase" "installPhase"];
buildPhase = ''
srcs=($srcs)
sipyco=''${srcs[0]}
artiq=''${srcs[1]}
export PYTHONPATH=$sipyco:$artiq
python -m artiq.frontend.artiq_ddb_template $artiq/artiq/examples/nac3devices/master.json -s 1 $artiq/artiq/examples/nac3devices/satellite.json > device_db.py
cp $artiq/artiq/examples/nac3devices/nac3devices.py .
python -m artiq.frontend.artiq_compile nac3devices.py
'';
installPhase = ''
mkdir $out
llvm-profdata merge -o $out/llvm.profdata /build/llvm/build/profiles/*
'';
};
llvm-nac3-pgo = pkgs.callPackage ./nix/llvm {
stdenv = pkgs.llvmPackages_16.stdenv;
extraCmakeFlags = ["-DLLVM_PROFDATA_FILE=${nac3artiq-profile}/llvm.profdata"];
};
nac3artiq-pgo = pkgs.python3Packages.toPythonModule (
pkgs.rustPlatform.buildRustPackage {
name = "nac3artiq-pgo";
src = self;
inherit (nac3artiq) cargoLock;
nativeBuildInputs = [pkgs.python3 packages.x86_64-linux.llvm-tools-irrt pkgs.llvmPackages_16.bintools llvm-nac3-pgo];
buildInputs = [pkgs.python3 llvm-nac3-pgo];
cargoBuildFlags = ["--package" "nac3artiq"];
cargoTestFlags = ["--package" "nac3ast" "--package" "nac3parser" "--package" "nac3core" "--package" "nac3artiq"];
installPhase = ''
TARGET_DIR=$out/${pkgs.python3Packages.python.sitePackages}
mkdir -p $TARGET_DIR
cp target/x86_64-unknown-linux-gnu/release/libnac3artiq.so $TARGET_DIR/nac3artiq.so
'';
}
);
};
packages.x86_64-w64-mingw32 = import ./nix/windows {inherit pkgs;};
formatter.x86_64-linux = pkgs.alejandra;
devShells.x86_64-linux.default = pkgs.mkShell {
name = "nac3-dev-shell";
buildInputs = with pkgs; [
# build dependencies
packages.x86_64-linux.llvm-nac3
(pkgs.wrapClangMulti llvmPackages_16.clang)
llvmPackages_16.llvm.out
llvmPackages_16.bintools # for running nac3standalone demos
packages.x86_64-linux.llvm-tools-irrt
cargo
rustc
# runtime dependencies
lld_16 # for running kernels on the host
(packages.x86_64-linux.python3-mimalloc.withPackages (ps: [ps.numpy ps.scipy]))
# development tools
cargo-insta
clippy
pre-commit
rustfmt
];
shellHook = ''
export DEMO_LINALG_STUB=${packages.x86_64-linux.demo-linalg-stub}/lib/liblinalg.a
export DEMO_LINALG_STUB32=${packages.x86_64-linux.demo-linalg-stub32}/lib/liblinalg.a
'';
};
devShells.x86_64-linux.msys2 = pkgs.mkShell {
name = "nac3-dev-shell-msys2";
buildInputs = with pkgs; [
curl
pacman
fakeroot
packages.x86_64-w64-mingw32.wine-msys2
];
};
hydraJobs = {
inherit (packages.x86_64-linux) llvm-nac3 nac3artiq nac3artiq-pgo;
llvm-nac3-msys2 = packages.x86_64-w64-mingw32.llvm-nac3;
nac3artiq-msys2 = packages.x86_64-w64-mingw32.nac3artiq;
nac3artiq-msys2-pkg = packages.x86_64-w64-mingw32.nac3artiq-pkg;
};
};
nixConfig = {

View File

@@ -9,13 +9,13 @@ name = "nac3artiq"
crate-type = ["cdylib"]
[dependencies]
indexmap = "2.9"
indexmap = "2.12"
itertools = "0.14"
pyo3 = { version = "0.25", features = ["extension-module"] }
pyo3 = { version = "0.27", features = ["extension-module"] }
parking_lot = "0.12"
tempfile = "3.20"
tempfile = "3.22"
nac3core = { path = "../nac3core" }
nac3ld = { path = "../nac3ld" }
nac3binutils = { path = "../nac3binutils" }
[features]
init-llvm-profile = []

View File

@@ -0,0 +1,102 @@
from __future__ import annotations
from min_artiq import *
from numpy import int32, int64
from typing import Generic, TypeVar
@compile
class ProtoRev8:
"""Has back-reference to CPLD[ProtoRev8] - this is key to triggering the bug."""
cpld: KernelInvariant[CPLD[ProtoRev8]]
cfg_reg: Kernel[int64]
def __init__(self, cpld: CPLD[ProtoRev8]):
self.cpld = cpld
self.cfg_reg = int64(0)
@kernel
def cfg_write(self, cfg: int64):
pass
@kernel
def sta_read(self) -> int32:
return int32(0)
@compile
class ProtoRev9:
"""Has back-reference to CPLD[ProtoRev9] - this is key to triggering the bug."""
cpld: KernelInvariant[CPLD[ProtoRev9]]
cfg_reg: Kernel[int64]
def __init__(self, cpld: CPLD[ProtoRev9]):
self.cpld = cpld
self.cfg_reg = int64(0)
@kernel
def cfg_write(self, cfg: int64):
pass
@kernel
def sta_read(self) -> int32:
return int32(0)
V = TypeVar("V", ProtoRev8, ProtoRev9)
@compile
class CPLD(Generic[V]):
core: KernelInvariant[Core]
version: KernelInvariant[V]
cfg_reg: Kernel[int64]
def __init__(self):
self.core = Core()
# Create version with back-reference to self
self.version = ProtoRev9(self)
self.cfg_reg = int64(0)
@kernel
def cfg_write(self, cfg: int64):
self.version.cfg_write(cfg)
@kernel
def sta_read(self) -> int32:
return self.version.sta_read()
@compile
class AD9910:
core: KernelInvariant[Core]
cpld: KernelInvariant[CPLD[ProtoRev9]]
def __init__(self, cpld: CPLD[ProtoRev9]):
self.core = Core()
self.cpld = cpld
@kernel
def init(self):
self.cpld.cfg_write(self.cpld.cfg_reg)
@compile
class Test:
core: KernelInvariant[Core]
cpld: KernelInvariant[CPLD[ProtoRev9]]
dds: KernelInvariant[AD9910]
def __init__(self):
self.core = Core()
self.cpld = CPLD()
self.dds = AD9910(self.cpld)
@kernel
def run(self):
self.cpld.cfg_write(int64(0))
self.dds.init()
if __name__ == "__main__":
Test().run()

View File

@@ -1,9 +1,12 @@
from inspect import getfullargspec, getmodule
from functools import wraps
from math import floor, ceil
import numpy as np
from scipy import special
from scipy import linalg
from numpy import int32, int64, uint32, uint64, float64, bool_, str_, ndarray
from types import GenericAlias, ModuleType, SimpleNamespace
from typing import _GenericAlias, Generic, TypeVar
from typing import _GenericAlias, Generic, Literal, TypeVar
import nac3artiq
@@ -152,6 +155,20 @@ builtins = {
"list": list,
"tuple": tuple,
"Exception": Exception,
"range": range,
"enumerate": enumerate,
"round": round,
"round64": round64,
"floor": floor,
"floor64": floor64,
"ceil": ceil,
"ceil64": ceil64,
"len": len,
"min": min,
"max": max,
"abs": abs,
"some": Some,
"staticmethod": staticmethod,
"types": {
"GenericAlias": GenericAlias,
@@ -160,7 +177,9 @@ builtins = {
"typing": {
"_GenericAlias": _GenericAlias,
"Generic": Generic,
"TypeVar": TypeVar,
"Literal": Literal,
},
"numpy": {
@@ -172,6 +191,86 @@ builtins = {
"bool_": bool_,
"str_": str_,
"ndarray": ndarray,
"np_empty": np.empty,
"np_zeros": np.zeros,
"np_ones": np.ones,
"np_full": np.full,
"np_array": np.array,
"np_eye": np.eye,
"np_identity": np.identity,
"np_size": np.size,
"np_shape": np.shape,
"np_broadcast_to": np.broadcast_to,
"np_transpose": np.transpose,
"np_reshape": np.reshape,
"np_round": np.round,
"np_floor": np.floor,
"np_ceil": np.ceil,
"np_min": np.min,
"np_minimum": np.minimum,
"np_max": np.max,
"np_maximum": np.maximum,
"np_argmin": np.argmin,
"np_argmax": np.argmax,
"np_isnan": np.isnan,
"np_isinf": np.isinf,
"np_sin": np.sin,
"np_cos": np.cos,
"np_exp": np.exp,
"np_exp2": np.exp2,
"np_log": np.log,
"np_log10": np.log10,
"np_log2": np.log2,
"np_fabs": np.fabs,
"np_sqrt": np.sqrt,
"np_rint": np.rint,
"np_tan": np.tan,
"np_arcsin": np.arcsin,
"np_arccos": np.arccos,
"np_arctan": np.arctan,
"np_sinh": np.sinh,
"np_cosh": np.cosh,
"np_tanh": np.tanh,
"np_arcsinh": np.arcsinh,
"np_arccosh": np.arccosh,
"np_arctanh": np.arctanh,
"np_expm1": np.expm1,
"np_cbrt": np.cbrt,
"sp_spec_erf": special.erf,
"sp_spec_erfc": special.erfc,
"sp_spec_gamma": special.gamma,
"sp_spec_gammaln": special.gammaln,
"sp_spec_j0": special.j0,
"sp_spec_j1": special.j1,
"np_arctan2": np.arctan2,
"np_copysign": np.copysign,
"np_fmax": np.fmax,
"np_fmin": np.fmin,
"np_ldexp": np.ldexp,
"np_hypot": np.hypot,
"np_nextafter": np.nextafter,
"np_any": np.any,
"np_all": np.all,
"np_dot": np.dot,
"np_linalg_cholesky": np.linalg.cholesky,
"np_linalg_qr": np.linalg.qr,
"np_linalg_svd": np.linalg.svd,
"np_linalg_inv": np.linalg.inv,
"np_linalg_pinv": np.linalg.pinv,
"np_linalg_matrix_power": np.linalg.matrix_power,
"np_linalg_det": np.linalg.det,
"sp_linalg_lu": linalg.lu,
"sp_linalg_schur": linalg.schur,
"sp_linalg_hessenberg": linalg.hessenberg,
},
"artiq": {
@@ -298,7 +397,7 @@ class Core:
obj = method
name = ""
compiler.compile_method_to_file(obj, name, args, "module.elf", embedding)
compiler.compile_method_to_file(obj, name, args, "module.elf", "debug.elf", embedding)
@kernel
def reset(self):

View File

@@ -0,0 +1,23 @@
from typing import Literal
from min_artiq import Core, KernelInvariant, compile, kernel
import numpy as np
# Tests the special case where `sys.__file__` is not present
import sys
@compile
class FindTrapResonance():
core: KernelInvariant[Core]
frequencies: KernelInvariant[np.ndarray[float, Literal[1]]]
def __init__(self):
self.core = Core()
self.frequencies = np.zeros((1,))
@kernel
def run(self):
pass
if __name__ == "__main__":
FindTrapResonance().run()

View File

@@ -0,0 +1,60 @@
from min_artiq import *
import numpy as np
enumerated_tuple = enumerate((1, 2, 3))
enumerated_tuple2 = enumerate((1.1, 2.2, 3.3, 4.4))
enumerated_tuple3 = enumerate(())
enumerated_list = enumerate([1, 2, 3])
enumerated_list2 = enumerate(["a", "b", "c", "d"])
enumerated_list3 = enumerate([])
@compile
class Demo:
core: KernelInvariant[Core]
led0: KernelInvariant[TTLOut]
led1: KernelInvariant[TTLOut]
def __init__(self):
self.core = Core()
self.led0 = TTLOut(self.core, 18)
self.led1 = TTLOut(self.core, 19)
@kernel
def test(self):
a = enumerated_tuple
b = enumerated_tuple2
c = enumerated_list
d = enumerated_list2
for x in enumerated_tuple:
x[0]
x[1]
for y in enumerated_tuple2:
y[0]
y[1]
for z in enumerated_tuple3:
z[0]
z[1]
for (h, u) in enumerated_tuple2:
h
u
for p in enumerated_list:
p[0]
p[1]
for q in enumerated_list2:
q[0]
q[1]
for r in enumerated_list3:
r[0]
r[1]
for (m, n) in enumerated_list2:
m
n
def run(self):
self.test()
if __name__ == "__main__":
Demo().run()

49
nac3artiq/demo/typevar.py Normal file
View File

@@ -0,0 +1,49 @@
from min_artiq import *
from typing import Generic, TypeVar
@compile
class TTLInOut:
@kernel
def on(self):
pass
@compile
class TTLOut:
@kernel
def on(self):
pass
T = TypeVar("T", TTLOut, TTLInOut)
@compile
class TurnOn(Generic[T]):
ttl: KernelInvariant[T]
def __init__(self, ttl):
self.ttl = ttl
@kernel
def turn_on(self):
self.ttl.on()
@compile
class TypeVarTest:
core: KernelInvariant[Core]
turn_on: KernelInvariant[TurnOn[TTLOut]]
def __init__(self):
self.core = Core()
self.turn_on = TurnOn(TTLOut())
@kernel
def run(self):
self.turn_on.turn_on()
if __name__ == "__main__":
TypeVarTest().run()

View File

@@ -7,28 +7,30 @@ use std::{
use itertools::Itertools;
use pyo3::{
PyObject, PyResult, Python,
PyResult, Python,
prelude::*,
types::{PyDict, PyList},
};
use nac3core::{
codegen::{
CodeGenContext, CodeGenerator, basic_type_all,
CodeGenContext, CodeGenerator, VarValue, basic_type_all, bool_to_i1,
expr::{call_extern, destructure_range, gen_call},
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::{RangeType, ndarray::NDArrayType},
values::{
ArrayLikeIndexer, ArrayLikeValue, ArraySliceValue, ListValue, ProxyValue,
UntypedArrayLikeAccessor,
stmt::{
gen_array_var, gen_block, gen_dyn_array_var, gen_for_callback_incrementing,
gen_if_callback, gen_var, gen_with,
},
typed_store,
types::{
ArrayLikeIndexer, ArraySliceValue, ExceptionType, ListType, NDArrayType, ProxyTypeExt,
RangeType, field,
},
},
inkwell::{
AddressSpace, IntPredicate,
IntPredicate,
module::Linkage,
types::{BasicType, IntType},
types::IntType,
values::{BasicValueEnum, IntValue, PointerValue, StructValue},
},
nac3parser::ast::{Expr, ExprKind, Located, Stmt, StmtKind, StrRef},
@@ -92,7 +94,7 @@ impl<'a> ArtiqCodeGenerator<'a> {
name: String,
timeline: &'a (dyn TimeFns + Sync),
special_ids: SpecialPythonId,
) -> ArtiqCodeGenerator<'a> {
) -> Self {
ArtiqCodeGenerator {
name,
name_counter: 0,
@@ -112,11 +114,7 @@ impl<'a> ArtiqCodeGenerator<'a> {
/// closest parent `with` statement is a `with parallel` block.
fn timeline_reset_start(&mut self, ctx: &mut CodeGenContext<'_, '_>) -> Result<(), String> {
if let Some(start) = self.start.clone() {
let start_val = self.gen_expr(ctx, &start)?.unwrap().to_basic_value_enum(
ctx,
self,
start.custom.unwrap(),
)?;
let start_val = self.gen_expr(ctx, &start)?.to_basic_value_enum(ctx)?;
self.timeline.emit_at_mu(ctx, start_val);
}
@@ -142,11 +140,7 @@ impl<'a> ArtiqCodeGenerator<'a> {
store_name: Option<&str>,
) -> Result<(), String> {
if let Some(end) = end {
let old_end = self.gen_expr(ctx, &end)?.unwrap().to_basic_value_enum(
ctx,
self,
end.custom.unwrap(),
)?;
let old_end = self.gen_expr(ctx, &end)?.to_basic_value_enum(ctx)?;
let now = self.timeline.emit_now_mu(ctx);
let max =
call_int_smax(ctx, old_end.into_int_value(), now.into_int_value(), Some("smax"));
@@ -157,7 +151,7 @@ impl<'a> ArtiqCodeGenerator<'a> {
store_name.map(|name| format!("{name}.addr")).as_deref(),
)?
.unwrap();
ctx.builder.build_store(end_store, max).unwrap();
typed_store(&ctx.builder, end_store, max);
}
Ok(())
@@ -240,17 +234,15 @@ impl CodeGenerator for ArtiqCodeGenerator<'_> {
// parallel block, and we should update the max end value.
if let ExprKind::Name { id, ctx: name_ctx } = &item.context_expr.node {
let resolver = ctx.resolver.clone();
if let Some(static_value) =
if let Some((_ptr, static_value, _counter)) = ctx.var_assignment.get(id) {
static_value.clone()
} else if let Some(ValueEnum::Static(val)) =
resolver.get_symbol_value(*id, ctx, self)
{
Some(val)
} else {
None
}
if let Some(static_value) = if let Some(VarValue { static_value, .. }) =
ctx.var_assignment.get(id)
{
static_value.clone()
} else if let Some(ValueEnum::Static(val)) = resolver.get_symbol_value(*id, ctx) {
Some(val)
} else {
None
} {
let python_id = static_value.get_unique_identifier();
if python_id == self.special_ids.parallel
|| python_id == self.special_ids.legacy_parallel
@@ -260,11 +252,7 @@ impl CodeGenerator for ArtiqCodeGenerator<'_> {
let old_parallel_mode = self.parallel_mode;
let now = if let Some(old_start) = &old_start {
self.gen_expr(ctx, old_start)?.unwrap().to_basic_value_enum(
ctx,
self,
old_start.custom.unwrap(),
)?
self.gen_expr(ctx, old_start)?.to_basic_value_enum(ctx)?
} else {
self.timeline.emit_now_mu(ctx)
};
@@ -288,7 +276,7 @@ impl CodeGenerator for ArtiqCodeGenerator<'_> {
let start = self
.gen_store_target(ctx, &start_expr, Some("start.addr"))?
.unwrap();
ctx.builder.build_store(start, now).unwrap();
typed_store(&ctx.builder, start, now);
Ok(Some(start_expr)) as Result<_, String>
},
|v| Ok(Some(v)),
@@ -301,7 +289,7 @@ impl CodeGenerator for ArtiqCodeGenerator<'_> {
custom: Some(ctx.primitives.int64),
};
let end = self.gen_store_target(ctx, &end_expr, Some("end.addr"))?.unwrap();
ctx.builder.build_store(end, now).unwrap();
typed_store(&ctx.builder, end, now);
self.end = Some(end_expr);
self.name_counter += 1;
self.parallel_mode = if python_id == self.special_ids.parallel {
@@ -332,11 +320,7 @@ impl CodeGenerator for ArtiqCodeGenerator<'_> {
// set duration
let end_expr = self.end.take().unwrap();
let end_val = self.gen_expr(ctx, &end_expr)?.unwrap().to_basic_value_enum(
ctx,
self,
end_expr.custom.unwrap(),
)?;
let end_val = self.gen_expr(ctx, &end_expr)?.to_basic_value_enum(ctx)?;
// inside a sequential block
if old_start.is_none() {
@@ -387,8 +371,6 @@ fn gen_rpc_tag(
ty: Type,
buffer: &mut Vec<u8>,
) -> Result<(), String> {
use nac3core::typecheck::typedef::TypeEnum::*;
let PrimitiveStore { int32, int64, float, bool, str, none, .. } = ctx.primitives;
if ctx.unifier.unioned(ty, int32) {
@@ -406,22 +388,22 @@ fn gen_rpc_tag(
} else {
let ty_enum = ctx.unifier.get_ty(ty);
match &*ty_enum {
TTuple { ty, is_vararg_ctx: false } => {
TypeEnum::TTuple { ty, is_vararg_ctx: false } => {
buffer.push(b't');
buffer.push(ty.len() as u8);
for ty in ty {
gen_rpc_tag(ctx, *ty, buffer)?;
}
}
TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => {
TypeEnum::TObj { obj_id, params, .. } if *obj_id == PrimDef::List.id() => {
let ty = iter_type_vars(params).next().unwrap().ty;
buffer.push(b'l');
gen_rpc_tag(ctx, ty, buffer)?;
}
TObj { obj_id, .. } if *obj_id == PrimDef::NDArray.id() => {
TypeEnum::TObj { obj_id, .. } if *obj_id == PrimDef::NDArray.id() => {
let (ndarray_dtype, ndarray_ndims) = unpack_ndarray_var_tys(&mut ctx.unifier, ty);
let ndarray_ndims = if let TLiteral { values, .. } =
let ndarray_ndims = if let TypeEnum::TLiteral { values, .. } =
&*ctx.unifier.get_ty_immutable(ndarray_ndims)
{
if values.len() != 1 {
@@ -456,12 +438,11 @@ fn gen_rpc_tag(
///
/// See `artiq/firmware/libproto_artiq/rpc_proto.rs` for the expected format.
fn format_rpc_arg<'ctx>(
generator: &mut dyn CodeGenerator,
ctx: &mut CodeGenContext<'ctx, '_>,
(arg, arg_ty, arg_idx): (BasicValueEnum<'ctx>, Type, usize),
) -> PointerValue<'ctx> {
let llvm_i8 = ctx.ctx.i8_type();
let llvm_pi8 = llvm_i8.ptr_type(AddressSpace::default());
let llvm_i8 = ctx.i8;
let llvm_pi8 = ctx.ptr;
let arg_slot = match &*ctx.unifier.get_ty_immutable(arg_ty) {
TypeEnum::TObj { obj_id, .. } if *obj_id == PrimDef::NDArray.id() => {
@@ -471,53 +452,41 @@ fn format_rpc_arg<'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(elem_ty);
let ndarray = NDArrayType::new(ctx, dtype, ndims)
.map_pointer_value(arg.into_pointer_value(), None);
let ndims = ctx.size_t.const_int(ndims, false);
let ndarray =
NDArrayType::new(ctx, dtype, ndims).map_value(arg.into_pointer_value(), None);
// `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);
let carray = ndarray.make_contiguous_ndarray(ctx);
let sizeof_usize = ctx.size_t.size_of();
let sizeof_usize =
ctx.builder.build_int_truncate_or_bit_cast(sizeof_usize, ctx.size_t, "").unwrap();
let sizeof_pdata = dtype.ptr_type(AddressSpace::default()).size_of();
let sizeof_pdata =
ctx.builder.build_int_truncate_or_bit_cast(sizeof_pdata, ctx.size_t, "").unwrap();
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();
let sizeof_usize = ctx.sizeof(ctx.size_t);
let sizeof_pdata = ctx.sizeof(ctx.ptr);
let sizeof_buf_shape = sizeof_usize * ndims;
let sizeof_buf = sizeof_buf_shape + sizeof_pdata;
// buf = { data: void*, shape: [size_t; ndims]; }
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) };
let buf = gen_array_var(ctx, llvm_i8, sizeof_buf, Some("rpc.arg"));
let buf_data = buf.value.0;
let sizeof_pdata_ = ctx.size_t.const_int(sizeof_pdata, false);
let buf_shape = buf.ptr_offset_unchecked(ctx, &sizeof_pdata_, None);
// Write to `buf->data`
let carray_data = carray.load_data(ctx);
let carray_data = carray.load(ctx, field!(data));
let carray_data = ctx.builder.build_pointer_cast(carray_data, llvm_pi8, "").unwrap();
call_memcpy(ctx, buf_data, carray_data, sizeof_pdata);
call_memcpy(ctx, buf_data, carray_data, sizeof_pdata_);
// Write to `buf->shape`
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);
let carray_shape = ndarray.shape(ctx).value.0;
let sizeof_buf_shape_ = ctx.size_t.const_int(sizeof_buf_shape, false);
call_memcpy(ctx, buf_shape, carray_shape, sizeof_buf_shape_);
buf.base_ptr(ctx, generator)
buf.value.0
}
_ => {
let arg_slot = generator
.gen_var_alloc(ctx, arg.get_type(), Some(&format!("rpc.arg{arg_idx}")))
.unwrap();
ctx.builder.build_store(arg_slot, arg).unwrap();
let arg_slot = gen_var(ctx, arg.get_type(), Some(&format!("rpc.arg{arg_idx}")));
typed_store(&ctx.builder, arg_slot, arg);
ctx.builder
.build_bit_cast(arg_slot, llvm_pi8, "rpc.arg")
@@ -533,7 +502,6 @@ fn format_rpc_arg<'ctx>(
/// Formats an RPC return value to conform to the expected format required by NAC3.
fn format_rpc_ret<'ctx>(
generator: &mut dyn CodeGenerator,
ctx: &mut CodeGenContext<'ctx, '_>,
ret_ty: Type,
) -> Option<BasicValueEnum<'ctx>> {
@@ -547,11 +515,8 @@ fn format_rpc_ret<'ctx>(
// else *(T*)ret_ptr
// }
let llvm_i8 = ctx.ctx.i8_type();
let llvm_i32 = ctx.i32;
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_pusize = ctx.size_t.ptr_type(AddressSpace::default());
let llvm_pi8 = ctx.ptr;
let rpc_recv =
ctx.declare_external("rpc_recv", Some(llvm_i32.into()), &[llvm_pi8.into()], false, &[]);
@@ -602,8 +567,7 @@ fn format_rpc_ret<'ctx>(
let (dtype, ndims) = unpack_ndarray_var_tys(&mut ctx.unifier, ret_ty);
let dtype_llvm = ctx.get_llvm_type(dtype);
let ndims = extract_ndims(&ctx.unifier, ndims);
let ndarray = NDArrayType::new(ctx, dtype_llvm, ndims)
.construct_uninitialized(generator, ctx, None);
let ndarray = NDArrayType::new(ctx, dtype_llvm, ndims).construct(ctx, None);
// NOTE: Current content of `ndarray`:
// - * `data` - **NOT YET** allocated.
@@ -612,34 +576,20 @@ fn format_rpc_ret<'ctx>(
// - * `shape` - allocated; has uninitialized values.
// - * `strides` - allocated; has uninitialized values.
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_usize = ctx.size_t.size_of();
let sizeof_usize =
ctx.builder.build_int_truncate_or_bit_cast(sizeof_usize, ctx.size_t, "").unwrap();
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, ctx.size_t, "").unwrap();
let ndims = ndarray.load_ndims(ctx);
let sizeof_shape = ctx.builder.build_int_mul(ndims, sizeof_usize, "").unwrap();
// Size of the buffer for the initial `rpc_recv()`.
let unaligned_buffer_size =
ctx.builder.build_int_add(sizeof_ptr, sizeof_shape, "").unwrap();
let stackptr = call_stacksave(ctx, None);
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);
let itemsize = ctx.sizeof(ndarray.ty.dtype);
let sizeof_ptr = ctx.sizeof(ctx.ptr);
let sizeof_shape = ndims * ctx.sizeof(ctx.size_t);
// Size of the buffer for the initial `rpc_recv()`.
let unaligned_buffer_size = sizeof_ptr + sizeof_shape;
// Force an aligned allocation.
let chunks = unaligned_buffer_size.div_ceil(8);
let aligned_alloc_ty = ctx.ctx.struct_type(&[ctx.i8.array_type(8).into()], false);
let ptr = gen_array_var(ctx, aligned_alloc_ty, chunks, Some("rpc.buffer")).value.0;
let buffer_bytes = ctx.size_t.const_int(chunks * 8, false);
let buffer = ArraySliceValue::new(ctx.i8.into(), ptr, buffer_bytes, None);
// The first call to `rpc_recv` reads the top-level ndarray object: [pdata, shape]
//
@@ -647,7 +597,7 @@ fn format_rpc_ret<'ctx>(
let ndarray_nbytes = ctx
.build_call_or_invoke(
&rpc_recv,
&[buffer.base_ptr(ctx, generator).into()], // Reads [usize; ndims]
&[buffer.value.0.into()], // Reads [usize; ndims]
"rpc.size.next",
)
.map(BasicValueEnum::into_int_value)
@@ -661,7 +611,6 @@ fn format_rpc_ret<'ctx>(
.unwrap();
ctx.make_assert(
generator,
cmp,
"0:AssertionError",
"Unexpected RPC termination for ndarray - Expected data buffer next",
@@ -672,13 +621,9 @@ 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 =
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);
let sizeof_ptr = ctx.size_t.const_int(sizeof_ptr, false);
let pbuffer_shape = buffer.ptr_offset_unchecked(ctx, &sizeof_ptr, None);
ndarray.shape(ctx).memcpy_from(ctx, pbuffer_shape);
// Restore stack from before allocation of buffer
call_stackrestore(ctx, stackptr);
@@ -686,8 +631,9 @@ 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)
unsafe { ndarray.create_data(generator, ctx) }; // NOTE: the strides of `ndarray` has also been set to contiguous in `create_data`.
ndarray.create_data(ctx); // NOTE: the strides of `ndarray` has also been set to contiguous in `create_data`.
let itemsize = ctx.size_t.const_int(itemsize, false);
// debug_assert(nelems * sizeof(T) >= ndarray_nbytes)
if ctx.registry.codegen_options.debug {
let num_elements = ndarray.size(ctx);
@@ -705,7 +651,6 @@ fn format_rpc_ret<'ctx>(
.unwrap();
ctx.make_assert(
generator,
cmp,
"0:AssertionError",
"Unexpected allocation size request for ndarray data - Expected up to {0} bytes, got {1} bytes",
@@ -714,7 +659,7 @@ fn format_rpc_ret<'ctx>(
);
}
let ndarray_data = ndarray.data().base_ptr(ctx, generator);
let ndarray_data = ndarray.load(ctx, field!(data));
let entry_bb = ctx.builder.get_insert_block().unwrap();
ctx.builder.build_unconditional_branch(head_bb).unwrap();
@@ -739,26 +684,17 @@ 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);
// 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, "").unwrap(),
"rpc.alloc",
)
.unwrap();
let alloc_ptr =
ctx.builder.build_pointer_cast(alloc_ptr, llvm_pi8, "rpc.alloc.ptr").unwrap();
let size = ctx.builder.build_int_unsigned_div(alloc_size, itemsize, "").unwrap();
let alloc_ptr = gen_dyn_array_var(ctx, dtype_llvm, size, Some("rpc.alloc")).value.0;
phi.add_incoming(&[(&alloc_ptr, alloc_bb)]);
ctx.builder.build_unconditional_branch(head_bb).unwrap();
ctx.builder.position_at_end(tail_bb);
ndarray.as_abi_value(ctx).into()
ndarray.value.into()
}
_ => {
let slot = ctx.builder.build_alloca(llvm_ret_ty, "rpc.ret.slot").unwrap();
let slot = gen_var(ctx, llvm_ret_ty, Some("rpc.ret.slot"));
let slotgen = ctx.builder.build_bit_cast(slot, llvm_pi8, "rpc.ret.ptr").unwrap();
ctx.builder.build_unconditional_branch(head_bb).unwrap();
ctx.builder.position_at_end(head_bb);
@@ -777,10 +713,7 @@ fn format_rpc_ret<'ctx>(
ctx.builder.build_conditional_branch(is_done, tail_bb, alloc_bb).unwrap();
ctx.builder.position_at_end(alloc_bb);
let alloc_ptr =
ctx.builder.build_array_alloca(llvm_pi8, alloc_size, "rpc.alloc").unwrap();
let alloc_ptr =
ctx.builder.build_bit_cast(alloc_ptr, llvm_pi8, "rpc.alloc.ptr").unwrap();
let alloc_ptr = gen_dyn_array_var(ctx, llvm_pi8, alloc_size, Some("rpc.alloc")).value.0;
phi.add_incoming(&[(&alloc_ptr, alloc_bb)]);
ctx.builder.build_unconditional_branch(head_bb).unwrap();
@@ -797,13 +730,12 @@ fn rpc_codegen_callback_fn<'ctx>(
obj: Option<(Type, ValueEnum<'ctx>)>,
fun: (&FunSignature, DefinitionId),
args: Vec<(Option<StrRef>, ValueEnum<'ctx>)>,
generator: &mut dyn CodeGenerator,
is_async: bool,
) -> Result<Option<BasicValueEnum<'ctx>>, String> {
let int8 = ctx.ctx.i8_type();
let int8 = ctx.i8;
let int32 = ctx.i32;
let size_type = ctx.size_t;
let ptr_type = int8.ptr_type(AddressSpace::default());
let ptr_type = ctx.ptr;
let tag_ptr_type = ctx.ctx.struct_type(&[ptr_type.into(), size_type.into()], false);
let service_id = int32.const_int(fun.1.0 as u64, false);
@@ -848,13 +780,10 @@ fn rpc_codegen_callback_fn<'ctx>(
})
.as_pointer_value();
let arg_length = args.len() + usize::from(obj.is_some());
let arg_length = args.len() as u64 + u64::from(obj.is_some());
let stackptr = call_stacksave(ctx, Some("rpc.stack"));
let args_ptr = ctx
.builder
.build_array_alloca(ptr_type, ctx.i32.const_int(arg_length as u64, false), "argptr")
.unwrap();
let args_ptr = gen_array_var(ctx, ctx.ptr, arg_length, Some("argptr"));
// -- rpc args handling
let mut keys = fun.0.args.clone();
@@ -864,8 +793,7 @@ fn rpc_codegen_callback_fn<'ctx>(
}
// default value handling
for k in keys {
mapping
.insert(k.name, ctx.gen_symbol_val(generator, &k.default_value.unwrap(), k.ty).into());
mapping.insert(k.name, ctx.gen_symbol_val(&k.default_value.unwrap(), k.ty).into());
}
// reorder the parameters
let mut real_params = fun
@@ -876,13 +804,13 @@ fn rpc_codegen_callback_fn<'ctx>(
mapping
.remove(&arg.name)
.unwrap()
.to_basic_value_enum(ctx, generator, arg.ty)
.to_basic_value_enum(ctx, arg.ty)
.map(|llvm_val| (llvm_val, arg.ty))
})
.collect::<Result<Vec<(_, _)>, _>>()?;
if let Some(obj) = obj {
if let ValueEnum::Static(obj_val) = obj.1 {
real_params.insert(0, (obj_val.get_const_obj(ctx, generator), obj.0));
real_params.insert(0, (obj_val.get_const_obj(ctx), obj.0));
} else {
// should be an error here...
panic!("only host object is allowed");
@@ -890,20 +818,14 @@ fn rpc_codegen_callback_fn<'ctx>(
}
for (i, (arg, arg_ty)) in real_params.iter().enumerate() {
let arg_slot = format_rpc_arg(generator, ctx, (*arg, *arg_ty, i));
let arg_ptr = unsafe {
ctx.builder.build_gep(
args_ptr,
&[int32.const_int(i as u64, false)],
&format!("rpc.arg{i}"),
)
}
.unwrap();
ctx.builder.build_store(arg_ptr, arg_slot).unwrap();
let arg_slot = format_rpc_arg(ctx, (*arg, *arg_ty, i));
let name = format!("rpc.arg{i}");
let i = ctx.size_t.const_int(i as u64, false);
args_ptr.set_unchecked(ctx, &i, arg_slot, Some(&name));
}
call_extern!(ctx: void "rpc.send" =
(if is_async { "rpc_send_async" } else { "rpc_send" })(service_id, tag_ptr, args_ptr));
(if is_async { "rpc_send_async" } else { "rpc_send" })(service_id, tag_ptr, args_ptr.value.0));
// reclaim stack space used by arguments
call_stackrestore(ctx, stackptr);
@@ -912,7 +834,7 @@ fn rpc_codegen_callback_fn<'ctx>(
// async RPCs do not return any values
Ok(None)
} else {
let result = format_rpc_ret(generator, ctx, fun.0.ret);
let result = format_rpc_ret(ctx, fun.0.ret);
// Here we call `basic_type_all` to ensure that the return type is not, nor contains, a
// pointer type which may require further allocation, in which case the stack should not
@@ -927,31 +849,28 @@ fn rpc_codegen_callback_fn<'ctx>(
pub fn attributes_writeback<'ctx>(
ctx: &mut CodeGenContext<'ctx, '_>,
generator: &mut dyn CodeGenerator,
inner_resolver: &InnerResolver,
host_attributes: &PyObject,
host_attributes: &Py<PyAny>,
return_obj: Option<(Type, ValueEnum<'ctx>)>,
) -> Result<(), String> {
Python::with_gil(|py| -> PyResult<Result<(), String>> {
let host_attributes = host_attributes.downcast_bound::<PyList>(py)?;
let top_levels = ctx.top_level.definitions.read();
let globals = inner_resolver.global_value_ids.read();
Python::attach(|py| -> PyResult<Result<(), String>> {
let host_attributes = host_attributes.cast_bound::<PyList>(py)?;
let int32 = ctx.i32;
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()));
values.push((ty, obj.to_basic_value_enum(ctx, ty).unwrap()));
}
for val in (*globals).values() {
for val in (*inner_resolver.global_value_ids.read()).values() {
let val = val.bind(py);
let ty = inner_resolver.get_obj_type(
py,
val,
&mut ctx.unifier,
&top_levels,
&ctx.top_level.definitions.read(),
&ctx.primitives,
)?;
if let Err(ty) = ty {
@@ -966,10 +885,7 @@ pub fn attributes_writeback<'ctx>(
let pydict = PyDict::new(py);
pydict.set_item("obj", val)?;
host_attributes.append(pydict)?;
values.push((
ty,
inner_resolver.get_obj_value(py, val, ctx, generator, ty)?.unwrap(),
));
values.push((ty, inner_resolver.get_obj_value(py, val, ctx, ty)?.unwrap()));
}
}
TypeEnum::TObj { fields, obj_id, .. }
@@ -978,7 +894,7 @@ pub fn attributes_writeback<'ctx>(
// we only care about primitive attributes
// for non-primitive attributes, they should be in another global
let mut attributes = Vec::new();
let obj = inner_resolver.get_obj_value(py, val, ctx, generator, ty)?.unwrap();
let obj = inner_resolver.get_obj_value(py, val, ctx, ty)?.unwrap();
for (name, (field_ty, attr_kind)) in fields {
if !attr_kind.is_mutable() {
continue;
@@ -1022,9 +938,7 @@ pub fn attributes_writeback<'ctx>(
};
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, true)
{
if let Err(e) = rpc_codegen_callback_fn(ctx, None, (&fun, DefinitionId(0)), args, true) {
return Ok(Err(e));
}
Ok(Ok(()))
@@ -1034,8 +948,8 @@ pub fn attributes_writeback<'ctx>(
}
pub fn rpc_codegen_callback(is_async: bool) -> Arc<GenCall> {
Arc::new(GenCall::new(Box::new(move |ctx, obj, fun, args, generator| {
rpc_codegen_callback_fn(ctx, obj, fun, args, generator, is_async)
Arc::new(GenCall::new(Box::new(move |ctx, obj, fun, args| {
rpc_codegen_callback_fn(ctx, obj, fun, args, is_async)
})))
}
@@ -1077,31 +991,28 @@ fn get_fprintf_format_constant<'ctx>(
/// * `as_rtio` - Whether to print to `rtio_log` instead of `core_log`.
fn polymorphic_print<'ctx>(
ctx: &mut CodeGenContext<'ctx, '_>,
generator: &mut dyn CodeGenerator,
values: &[(Type, ValueEnum<'ctx>)],
separator: &str,
suffix: Option<&str>,
as_repr: bool,
as_rtio: bool,
) -> Result<(), String> {
let printf = |ctx: &mut CodeGenContext<'ctx, '_>,
generator: &mut dyn CodeGenerator,
fmt: String,
args: Vec<BasicValueEnum<'ctx>>| {
debug_assert!(!fmt.is_empty());
debug_assert_eq!(fmt.as_bytes().last().unwrap(), &0u8);
let printf =
|ctx: &mut CodeGenContext<'ctx, '_>, fmt: String, args: Vec<BasicValueEnum<'ctx>>| {
debug_assert!(!fmt.is_empty());
debug_assert_eq!(fmt.as_bytes().last().unwrap(), &0u8);
let llvm_i32 = ctx.i32;
let llvm_i32 = ctx.i32;
let fmt = ctx.gen_string(generator, fmt);
let fmt = unsafe { fmt.get_field_at_index_unchecked(0) }.into_pointer_value();
let fmt = ctx.gen_string(fmt);
let fmt = unsafe { fmt.get_field_at_index_unchecked(0) }.into_pointer_value();
if as_rtio {
call_extern!(ctx: void _ = "rtio_log"(fmt; ...args));
} else {
call_extern!(ctx: llvm_i32 _ = "core_log"(fmt; ...args));
}
};
if as_rtio {
call_extern!(ctx: void _ = "rtio_log"(fmt; ...args));
} else {
call_extern!(ctx: llvm_i32 _ = "core_log"(fmt; ...args));
}
};
let llvm_i32 = ctx.i32;
let llvm_i64 = ctx.i64;
@@ -1113,18 +1024,17 @@ fn polymorphic_print<'ctx>(
let mut args = Vec::new();
let flush = |ctx: &mut CodeGenContext<'ctx, '_>,
generator: &mut dyn CodeGenerator,
fmt: &mut String,
args: &mut Vec<BasicValueEnum<'ctx>>| {
if !fmt.is_empty() {
fmt.push('\0');
printf(ctx, generator, mem::take(fmt), mem::take(args));
printf(ctx, mem::take(fmt), mem::take(args));
}
};
for (ty, value) in values {
let ty = *ty;
let value = value.clone().to_basic_value_enum(ctx, generator, ty).unwrap();
let value = value.to_basic_value_enum(ctx, ty).unwrap();
if !fmt.is_empty() {
fmt.push_str(separator);
@@ -1133,13 +1043,13 @@ fn polymorphic_print<'ctx>(
match &*ctx.unifier.get_ty_immutable(ty) {
TypeEnum::TTuple { ty: tys, is_vararg_ctx: false } => {
let pvalue = {
let pvalue = generator.gen_var_alloc(ctx, value.get_type(), None).unwrap();
ctx.builder.build_store(pvalue, value).unwrap();
let pvalue = gen_var(ctx, value.get_type(), None);
typed_store(&ctx.builder, pvalue, value);
pvalue
};
fmt.push('(');
flush(ctx, generator, &mut fmt, &mut args);
flush(ctx, &mut fmt, &mut args);
let tuple_vals = tys
.iter()
@@ -1154,7 +1064,7 @@ fn polymorphic_print<'ctx>(
})
.collect_vec();
polymorphic_print(ctx, generator, &tuple_vals, ", ", None, true, as_rtio)?;
polymorphic_print(ctx, &tuple_vals, ", ", None, true, as_rtio)?;
if tuple_vals.len() == 1 {
fmt.push_str(",)");
@@ -1171,17 +1081,17 @@ fn polymorphic_print<'ctx>(
TypeEnum::TObj { obj_id, .. } if *obj_id == PrimDef::Bool.id() => {
fmt.push_str("%.*s");
let true_str = ctx.gen_string(generator, "True");
let true_str = ctx.gen_string("True");
let true_data =
unsafe { true_str.get_field_at_index_unchecked(0) }.into_pointer_value();
let true_len = unsafe { true_str.get_field_at_index_unchecked(1) }.into_int_value();
let false_str = ctx.gen_string(generator, "False");
let false_str = ctx.gen_string("False");
let false_data =
unsafe { false_str.get_field_at_index_unchecked(0) }.into_pointer_value();
let false_len =
unsafe { false_str.get_field_at_index_unchecked(1) }.into_int_value();
let bool_val = generator.bool_to_i1(ctx, value.into_int_value());
let bool_val = bool_to_i1(ctx, value.into_int_value());
args.extend([
ctx.builder.build_select(bool_val, true_len, false_len, "").unwrap(),
@@ -1234,120 +1144,104 @@ fn polymorphic_print<'ctx>(
let elem_ty = *params.iter().next().unwrap().1;
fmt.push('[');
flush(ctx, generator, &mut fmt, &mut args);
flush(ctx, &mut fmt, &mut args);
let val =
ListValue::from_pointer_value(value.into_pointer_value(), llvm_usize, None);
let len = val.load_size(ctx, None);
let val = ListType::from_unifier_type(ctx, ty)
.map_value(value.into_pointer_value(), None);
let len = val.load(ctx, field!(len));
let last =
ctx.builder.build_int_sub(len, llvm_usize.const_int(1, false), "").unwrap();
gen_for_callback_incrementing(
generator,
&mut (),
ctx,
None,
llvm_usize.const_zero(),
(len, false),
|generator, ctx, _, i| {
let elem = unsafe { val.data().get_unchecked(ctx, generator, &i, None) };
|(), ctx, _, i| {
let elem = val.data(ctx).get_unchecked(ctx, &i, None);
polymorphic_print(
ctx,
generator,
&[(elem_ty, elem.into())],
"",
None,
true,
as_rtio,
)?;
polymorphic_print(ctx, &[(elem_ty, elem)], "", None, true, as_rtio)?;
gen_if_callback(
generator,
&mut (),
ctx,
|_, ctx| {
|(), ctx| {
Ok(ctx
.builder
.build_int_compare(IntPredicate::ULT, i, last, "")
.unwrap())
},
|generator, ctx| {
printf(ctx, generator, ", \0".into(), Vec::default());
|(), ctx| {
printf(ctx, ", \0".into(), Vec::default());
Ok(())
},
|_, _| Ok(()),
|(), _| Ok(()),
)?;
Ok(())
},
llvm_usize.const_int(1, false),
|(), _| Ok(()),
)?;
fmt.push(']');
flush(ctx, generator, &mut fmt, &mut args);
flush(ctx, &mut fmt, &mut args);
}
TypeEnum::TObj { obj_id, .. } if *obj_id == PrimDef::NDArray.id() => {
fmt.push_str("array([");
flush(ctx, generator, &mut fmt, &mut args);
flush(ctx, &mut fmt, &mut args);
let (dtype, _) = unpack_ndarray_var_tys(&mut ctx.unifier, ty);
let ndarray = NDArrayType::from_unifier_type(ctx, ty)
.map_pointer_value(value.into_pointer_value(), None);
.map_value(value.into_pointer_value(), None);
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| {
ndarray.foreach(ctx, |ctx, _, hdl| {
let i = hdl.get_index(ctx);
let scalar = hdl.get_scalar(ctx);
// if (i != 0) puts(", ");
gen_if_callback(
generator,
&mut (),
ctx,
|_, ctx| {
|(), ctx| {
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());
|(), ctx| {
printf(ctx, ", \0".into(), Vec::default());
Ok(())
},
|_, _| Ok(()),
|(), _| Ok(()),
)?;
// Print element
polymorphic_print(
ctx,
generator,
&[(dtype, scalar.into())],
"",
None,
true,
as_rtio,
)?;
polymorphic_print(ctx, &[(dtype, scalar.into())], "", None, true, as_rtio)?;
Ok(())
})?;
fmt.push_str(")]");
flush(ctx, generator, &mut fmt, &mut args);
flush(ctx, &mut fmt, &mut args);
}
TypeEnum::TObj { obj_id, .. } if *obj_id == PrimDef::Range.id() => {
fmt.push_str("range(");
flush(ctx, generator, &mut fmt, &mut args);
flush(ctx, &mut fmt, &mut args);
let val = RangeType::new(ctx).map_pointer_value(value.into_pointer_value(), None);
let val = RangeType::new(ctx).map_value(value.into_pointer_value(), None);
let (start, stop, step) = destructure_range(ctx, val);
polymorphic_print(
ctx,
generator,
&[
(ctx.primitives.int32, start.into()),
(ctx.primitives.int32, stop.into()),
@@ -1369,35 +1263,11 @@ fn polymorphic_print<'ctx>(
get_fprintf_format_constant(llvm_usize, llvm_i64, false),
);
let exn = value.into_pointer_value();
let name = ctx
.build_in_bounds_gep_and_load(
exn,
&[llvm_i32.const_zero(), llvm_i32.const_zero()],
None,
)
.into_int_value();
let param0 = ctx
.build_in_bounds_gep_and_load(
exn,
&[llvm_i32.const_zero(), llvm_i32.const_int(6, false)],
None,
)
.into_int_value();
let param1 = ctx
.build_in_bounds_gep_and_load(
exn,
&[llvm_i32.const_zero(), llvm_i32.const_int(7, false)],
None,
)
.into_int_value();
let param2 = ctx
.build_in_bounds_gep_and_load(
exn,
&[llvm_i32.const_zero(), llvm_i32.const_int(8, false)],
None,
)
.into_int_value();
let exn = ExceptionType::new(ctx).map_value(value.into_pointer_value(), None);
let name = exn.load(ctx, field!(name));
let param0 = exn.load(ctx, field!(param0));
let param1 = exn.load(ctx, field!(param1));
let param2 = exn.load(ctx, field!(param2));
fmt.push_str(fmt_str.as_str());
args.extend_from_slice(&[name.into(), param0.into(), param1.into(), param2.into()]);
@@ -1411,7 +1281,7 @@ fn polymorphic_print<'ctx>(
}
fmt.push_str(suffix);
flush(ctx, generator, &mut fmt, &mut args);
flush(ctx, &mut fmt, &mut args);
Ok(())
}
@@ -1419,12 +1289,11 @@ fn polymorphic_print<'ctx>(
/// Invokes the `core_log` intrinsic function.
pub fn call_core_log_impl<'ctx>(
ctx: &mut CodeGenContext<'ctx, '_>,
generator: &mut dyn CodeGenerator,
arg: (Type, BasicValueEnum<'ctx>),
) -> Result<(), String> {
let (arg_ty, arg_val) = arg;
polymorphic_print(ctx, generator, &[(arg_ty, arg_val.into())], " ", Some("\n"), false, false)?;
polymorphic_print(ctx, &[(arg_ty, arg_val.into())], " ", Some("\n"), false, false)?;
Ok(())
}
@@ -1432,7 +1301,6 @@ pub fn call_core_log_impl<'ctx>(
/// Invokes the `rtio_log` intrinsic function.
pub fn call_rtio_log_impl<'ctx>(
ctx: &mut CodeGenContext<'ctx, '_>,
generator: &mut dyn CodeGenerator,
channel: StructValue<'ctx>,
arg: (Type, BasicValueEnum<'ctx>),
) -> Result<(), String> {
@@ -1440,14 +1308,13 @@ pub fn call_rtio_log_impl<'ctx>(
polymorphic_print(
ctx,
generator,
&[(ctx.primitives.str, channel.into())],
" ",
Some("\x1E"),
false,
true,
)?;
polymorphic_print(ctx, generator, &[(arg_ty, arg_val.into())], " ", Some("\x1D"), false, true)?;
polymorphic_print(ctx, &[(arg_ty, arg_val.into())], " ", Some("\x1D"), false, true)?;
Ok(())
}
@@ -1458,15 +1325,14 @@ pub fn gen_core_log<'ctx>(
obj: Option<&(Type, ValueEnum<'ctx>)>,
fun: (&FunSignature, DefinitionId),
args: &[(Option<StrRef>, ValueEnum<'ctx>)],
generator: &mut dyn CodeGenerator,
) -> Result<(), String> {
assert!(obj.is_none());
assert_eq!(args.len(), 1);
let value_ty = fun.0.args[0].ty;
let value_arg = args[0].1.clone().to_basic_value_enum(ctx, generator, value_ty)?;
let value_arg = args[0].1.clone().to_basic_value_enum(ctx, value_ty)?;
call_core_log_impl(ctx, generator, (value_ty, value_arg))
call_core_log_impl(ctx, (value_ty, value_arg))
}
/// Generates a call to `rtio_log`.
@@ -1475,17 +1341,15 @@ pub fn gen_rtio_log<'ctx>(
obj: Option<&(Type, ValueEnum<'ctx>)>,
fun: (&FunSignature, DefinitionId),
args: &[(Option<StrRef>, ValueEnum<'ctx>)],
generator: &mut dyn CodeGenerator,
) -> Result<(), String> {
assert!(obj.is_none());
assert_eq!(args.len(), 2);
let channel_ty = fun.0.args[0].ty;
assert!(ctx.unifier.unioned(channel_ty, ctx.primitives.str));
let channel_arg =
args[0].1.clone().to_basic_value_enum(ctx, generator, channel_ty)?.into_struct_value();
let channel_arg = args[0].1.clone().to_basic_value_enum(ctx, channel_ty)?.into_struct_value();
let value_ty = fun.0.args[1].ty;
let value_arg = args[1].1.clone().to_basic_value_enum(ctx, generator, value_ty)?;
let value_arg = args[1].1.clone().to_basic_value_enum(ctx, value_ty)?;
call_rtio_log_impl(ctx, generator, channel_arg, (value_ty, value_arg))
call_rtio_log_impl(ctx, channel_arg, (value_ty, value_arg))
}

View File

@@ -29,220 +29,191 @@ impl InnerResolver {
let mut str = String::new();
str.push_str("nac3artiq::InnerResolver {");
{
let id_to_type = self.id_to_type.read();
str.push_str(
format!(
"\n\tid_to_type: {{{}}},",
fmt_elems(
id_to_type
.iter()
.sorted_by_cached_key(|(k, _)| k.to_string())
.map(|(k, v)| {
let ty_str = unifier.as_ref().map_or_else(
|| format!("{v:?}"),
|unifier| unifier.stringify(*v),
);
format!("\t\t{k} -> {ty_str}")
})
.join(",\n")
.as_str()
),
)
.as_str(),
);
}
str.push_str(
format!(
"\n\tid_to_type: {{{}}},",
fmt_elems(
self.id_to_type
.read()
.iter()
.sorted_by_cached_key(|(k, _)| k.to_string())
.map(|(k, v)| {
let ty_str = unifier
.as_ref()
.map_or_else(|| format!("{v:?}"), |unifier| unifier.stringify(*v));
format!("\t\t{k} -> {ty_str}")
})
.join(",\n")
.as_str()
),
)
.as_str(),
);
{
let id_to_def = self.id_to_def.read();
str.push_str(
format!(
"\n\tid_to_def: {{{}}},",
fmt_elems(
id_to_def
.iter()
.sorted_by_cached_key(|(k, _)| k.to_string())
.map(|(k, v)| {
let tld_str = tld.map_or_else(
|| format!("{v:?}"),
|tlds| stringify_tld(&tlds[v.0]),
);
format!("\t\t{k} -> {tld_str}")
})
.join(",\n")
.as_str()
)
str.push_str(
format!(
"\n\tid_to_def: {{{}}},",
fmt_elems(
self.id_to_def
.read()
.iter()
.sorted_by_cached_key(|(k, _)| k.to_string())
.map(|(k, v)| {
let tld_str = tld
.map_or_else(|| format!("{v:?}"), |tlds| stringify_tld(&tlds[v.0]));
format!("\t\t{k} -> {tld_str}")
})
.join(",\n")
.as_str()
)
.as_str(),
);
}
)
.as_str(),
);
{
let id_to_pyval = self.id_to_pyval.read();
str.push_str(
format!(
"\n\tid_to_pyval: {{{}}},",
fmt_elems(
id_to_pyval
.iter()
.sorted_by_cached_key(|(k, _)| k.to_string())
.map(|(k, v)| { format!("\t\t{k} -> {}", stringify_pyvalue_handle(v)) })
.join(",\n")
.as_str()
)
str.push_str(
format!(
"\n\tid_to_pyval: {{{}}},",
fmt_elems(
self.id_to_pyval
.read()
.iter()
.sorted_by_cached_key(|(k, _)| k.to_string())
.map(|(k, v)| { format!("\t\t{k} -> {}", stringify_pyvalue_handle(v)) })
.join(",\n")
.as_str()
)
.as_str(),
);
}
)
.as_str(),
);
{
let id_to_primitive = self.id_to_primitive.read();
str.push_str(
format!(
"\n\tid_to_primitive: {{{}}},",
fmt_elems(
id_to_primitive
.iter()
.sorted_by_key(|(k, _)| *k)
.map(|(k, v)| { format!("\t\t{k} -> {v:?}") })
.join(",\n")
.as_str()
)
str.push_str(
format!(
"\n\tid_to_primitive: {{{}}},",
fmt_elems(
self.id_to_primitive
.read()
.iter()
.sorted_by_key(|(k, _)| *k)
.map(|(k, v)| { format!("\t\t{k} -> {v:?}") })
.join(",\n")
.as_str()
)
.as_str(),
);
}
)
.as_str(),
);
{
let field_to_val = self.field_to_val.read();
str.push_str(
format!(
"\n\tfield_to_val: {{{}}},",
fmt_elems(
field_to_val
.iter()
.sorted_by_key(|((id, _), _)| *id)
.map(|((id, name), pyval)| {
format!(
"\t\t({id}, {name}) -> {}",
pyval.as_ref().map_or_else(
|| String::from("None"),
|pyval| format!(
"Some({})",
stringify_pyvalue_handle(pyval)
)
)
str.push_str(
format!(
"\n\tfield_to_val: {{{}}},",
fmt_elems(
self.field_to_val
.read()
.iter()
.sorted_by_key(|((id, _), _)| *id)
.map(|((id, name), pyval)| {
format!(
"\t\t({id}, {name}) -> {}",
pyval.as_ref().map_or_else(
|| String::from("None"),
|pyval| format!("Some({})", stringify_pyvalue_handle(pyval))
)
})
.join(",\n")
.as_str()
)
)
})
.join(",\n")
.as_str()
)
.as_str(),
);
}
)
.as_str(),
);
{
let global_value_ids = self.global_value_ids.read();
str.push_str(
format!(
"\n\tglobal_value_ids: {{{}}},",
fmt_elems(
global_value_ids
.iter()
.sorted_by_key(|(k, _)| *k)
.map(|(k, v)| format!("\t\t{k} -> {v}"))
.join(",\n")
.as_str()
)
str.push_str(
format!(
"\n\tglobal_value_ids: {{{}}},",
fmt_elems(
self.global_value_ids
.read()
.iter()
.sorted_by_key(|(k, _)| *k)
.map(|(k, v)| format!("\t\t{k} -> {v}"))
.join(",\n")
.as_str()
)
.as_str(),
);
}
)
.as_str(),
);
{
let pyid_to_def = self.pyid_to_def.read();
str.push_str(
format!(
"\n\tpyid_to_def: {{{}}},",
fmt_elems(
pyid_to_def
.iter()
.sorted_by_key(|(k, _)| *k)
.map(|(k, v)| {
let tld_str = tld.map_or_else(
|| format!("{v:?}"),
|tlds| stringify_tld(&tlds[v.0]),
);
format!("\t\t{k} -> {tld_str}")
})
.join(",\n")
.as_str()
)
str.push_str(
format!(
"\n\tpyid_to_def: {{{}}},",
fmt_elems(
self.pyid_to_def
.read()
.iter()
.sorted_by_key(|(k, _)| *k)
.map(|(k, v)| {
let tld_str = tld
.map_or_else(|| format!("{v:?}"), |tlds| stringify_tld(&tlds[v.0]));
format!("\t\t{k} -> {tld_str}")
})
.join(",\n")
.as_str()
)
.as_str(),
);
}
)
.as_str(),
);
{
let pyid_to_type = self.pyid_to_type.read();
str.push_str(
format!(
"\n\tpyid_to_type: {{{}}},",
fmt_elems(
pyid_to_type
.iter()
.sorted_by_key(|(k, _)| *k)
.map(|(k, v)| {
let ty_str = unifier.as_ref().map_or_else(
|| format!("{v:?}"),
|unifier| unifier.stringify(*v),
);
format!("\t\t{k} -> {ty_str}")
})
.join(",\n")
.as_str()
)
str.push_str(
format!(
"\n\tpyid_to_type: {{{}}},",
fmt_elems(
self.pyid_to_type
.read()
.iter()
.sorted_by_key(|(k, _)| *k)
.map(|(k, v)| {
let ty_str = unifier
.as_ref()
.map_or_else(|| format!("{v:?}"), |unifier| unifier.stringify(*v));
format!("\t\t{k} -> {ty_str}")
})
.join(",\n")
.as_str()
)
.as_str(),
);
}
)
.as_str(),
);
{
let string_store = self.string_store.read();
str.push_str(
format!(
"\n\tstring_store: {{{}}},",
fmt_elems(
string_store
.iter()
.sorted_by_key(|(k, _)| *k)
.map(|(k, v)| format!("\t\t{k} -> {v}"))
.join(",\n")
.as_str()
)
str.push_str(
format!(
"\n\tstring_store: {{{}}},",
fmt_elems(
self.string_store
.read()
.iter()
.sorted_by_key(|(k, _)| *k)
.map(|(k, v)| format!("\t\t{k} -> {v}"))
.join(",\n")
.as_str()
)
.as_str(),
);
}
)
.as_str(),
);
{
let exception_ids = self.exception_ids.read();
str.push_str(
format!(
"\n\texception_ids: {{{}}},",
fmt_elems(
exception_ids
.iter()
.sorted_by_key(|(k, _)| *k)
.map(|(k, v)| format!("\t\t{k} -> {v}"))
.join(",\n")
.as_str()
)
str.push_str(
format!(
"\n\texception_ids: {{{}}},",
fmt_elems(
self.exception_ids
.read()
.iter()
.sorted_by_key(|(k, _)| *k)
.map(|(k, v)| format!("\t\t{k} -> {v}"))
.join(",\n")
.as_str()
)
.as_str(),
);
}
)
.as_str(),
);
let name_to_pyid = &self.name_to_pyid;
str.push_str(

File diff suppressed because it is too large Load Diff

View File

@@ -24,13 +24,13 @@ pub use builtins::*;
pub mod builtins {
use pyo3::{
prelude::*,
sync::GILOnceCell,
sync::PyOnceLock,
types::{PyAnyMethods, PyBool, PyCFunction, PyInt, PyModule, PyString, PyType},
};
/// Returns a reference to this module.
pub fn module(py: Python<'_>) -> PyResult<&Bound<'_, PyModule>> {
static MODULE: GILOnceCell<Py<PyModule>> = GILOnceCell::new();
static MODULE: PyOnceLock<Py<PyModule>> = PyOnceLock::new();
MODULE
.get_or_try_init(py, || {
@@ -43,7 +43,7 @@ pub mod builtins {
/// Returns a reference to the
/// [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception) class.
pub fn get_exception_class(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
static EXCEPTION_CLASS: GILOnceCell<Py<PyType>> = GILOnceCell::new();
static EXCEPTION_CLASS: PyOnceLock<Py<PyType>> = PyOnceLock::new();
EXCEPTION_CLASS.import(py, "builtins", "Exception")
}
@@ -51,7 +51,7 @@ pub mod builtins {
/// Returns a reference to the [`id`](https://docs.python.org/3/library/functions.html#id)
/// function.
pub fn id_fn(py: Python<'_>) -> PyResult<&Bound<'_, PyCFunction>> {
static ID_FN: GILOnceCell<Py<PyCFunction>> = GILOnceCell::new();
static ID_FN: PyOnceLock<Py<PyCFunction>> = PyOnceLock::new();
ID_FN.import(py, "builtins", "id")
}
@@ -59,13 +59,13 @@ pub mod builtins {
/// Invokes [`id(object)`][id_fn], extracting its value and returning a [`u64`] representing
/// the result.
pub fn extract_id(object: &Bound<'_, PyAny>) -> PyResult<u64> {
id_fn(object.py())?.call1((object,))?.downcast_into::<PyInt>()?.extract()
id_fn(object.py())?.call1((object,))?.cast_into::<PyInt>()?.extract()
}
/// Returns a reference to the
/// [`issubclass`](https://docs.python.org/3/library/functions.html#issubclass) function.
pub fn issubclass_fn(py: Python<'_>) -> PyResult<&Bound<'_, PyCFunction>> {
static ISSUBCLASS_FN: GILOnceCell<Py<PyCFunction>> = GILOnceCell::new();
static ISSUBCLASS_FN: PyOnceLock<Py<PyCFunction>> = PyOnceLock::new();
ISSUBCLASS_FN.import(py, "builtins", "issubclass")
}
@@ -76,13 +76,13 @@ pub mod builtins {
class: &Bound<'_, PyAny>,
classinfo: &Bound<'_, PyAny>,
) -> PyResult<bool> {
issubclass_fn(class.py())?.call1((class, classinfo))?.downcast_into::<PyBool>()?.extract()
issubclass_fn(class.py())?.call1((class, classinfo))?.cast_into::<PyBool>()?.extract()
}
/// Returns a reference to the [`len`](https://docs.python.org/3/library/functions.html#len)
/// function.
pub fn len_fn(py: Python<'_>) -> PyResult<&Bound<'_, PyCFunction>> {
static LEN_FN: GILOnceCell<Py<PyCFunction>> = GILOnceCell::new();
static LEN_FN: PyOnceLock<Py<PyCFunction>> = PyOnceLock::new();
LEN_FN.import(py, "builtins", "len")
}
@@ -90,13 +90,13 @@ pub mod builtins {
/// Invokes [`len(object)`][len_fn], extracting its value and returning a [`usize`]
/// representing the result.
pub fn extract_len(object: &Bound<'_, PyAny>) -> PyResult<usize> {
len_fn(object.py())?.call1((object,))?.downcast_into::<PyInt>()?.extract()
len_fn(object.py())?.call1((object,))?.cast_into::<PyInt>()?.extract()
}
/// Returns a reference to the
/// [`repr`](https://docs.python.org/3/library/functions.html#repr) function.
pub fn repr_fn(py: Python<'_>) -> PyResult<&Bound<'_, PyCFunction>> {
static REPR_FN: GILOnceCell<Py<PyCFunction>> = GILOnceCell::new();
static REPR_FN: PyOnceLock<Py<PyCFunction>> = PyOnceLock::new();
REPR_FN.import(py, "builtins", "repr")
}
@@ -104,20 +104,20 @@ pub mod builtins {
/// Invokes [`repr(object)`][repr_fn], extracting its value and returning a [`String`]
/// representing the result.
pub fn extract_repr(object: &Bound<'_, PyAny>) -> PyResult<String> {
repr_fn(object.py())?.call1((object,))?.downcast_into::<PyString>()?.extract()
repr_fn(object.py())?.call1((object,))?.cast_into::<PyString>()?.extract()
}
/// Returns a reference to the
/// [`type`](https://docs.python.org/3/library/functions.html#type) class.
pub fn get_type_class(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
static TYPE_FN: GILOnceCell<Py<PyType>> = GILOnceCell::new();
static TYPE_FN: PyOnceLock<Py<PyType>> = PyOnceLock::new();
TYPE_FN.import(py, "builtins", "type")
}
/// Invokes [`type(object)`][type_fn], returning a [`PyType`] representing the result.
pub fn call_type<'py>(object: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyType>> {
Ok(get_type_class(object.py())?.call1((object,))?.downcast_into()?)
Ok(get_type_class(object.py())?.call1((object,))?.cast_into()?)
}
}
@@ -125,14 +125,14 @@ pub mod builtins {
pub mod typing {
use pyo3::{
prelude::*,
sync::GILOnceCell,
sync::PyOnceLock,
types::{PyAnyMethods, PyFunction, PyModule, PyTuple},
};
/// Returns a reference to this module.
#[allow(dead_code, reason = "For API consistency between all `py_interp` modules.")]
pub fn module(py: Python<'_>) -> PyResult<&Bound<'_, PyModule>> {
static MODULE: GILOnceCell<Py<PyModule>> = GILOnceCell::new();
static MODULE: PyOnceLock<Py<PyModule>> = PyOnceLock::new();
MODULE
.get_or_try_init(py, || {
@@ -145,7 +145,7 @@ pub mod typing {
/// Returns a reference to the
/// [`typing.get_args`](https://docs.python.org/3/library/typing.html#typing.get_args) function.
pub fn get_args_fn(py: Python<'_>) -> PyResult<&Bound<'_, PyFunction>> {
static GET_ARGS_FN: GILOnceCell<Py<PyFunction>> = GILOnceCell::new();
static GET_ARGS_FN: PyOnceLock<Py<PyFunction>> = PyOnceLock::new();
GET_ARGS_FN.import(py, "typing", "get_args")
}
@@ -153,14 +153,14 @@ pub mod typing {
/// Invokes [`typing.get_args(tp)`][get_args_fn], returning a [`PyTuple`] representing the
/// result.
pub fn call_get_args<'py>(tp: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyTuple>> {
Ok(get_args_fn(tp.py())?.call1((tp,))?.downcast_into()?)
Ok(get_args_fn(tp.py())?.call1((tp,))?.cast_into()?)
}
/// Returns a reference to the
/// [`typing.get_origin`](https://docs.python.org/3/library/typing.html#typing.get_origin)
/// function.
pub fn get_origin_fn(py: Python<'_>) -> PyResult<&Bound<'_, PyFunction>> {
static GET_ORIGIN_FN: GILOnceCell<Py<PyFunction>> = GILOnceCell::new();
static GET_ORIGIN_FN: PyOnceLock<Py<PyFunction>> = PyOnceLock::new();
GET_ORIGIN_FN.import(py, "typing", "get_origin")
}
@@ -168,6 +168,6 @@ pub mod typing {
/// Invokes [`typing.get_origin(tp)`][get_origin_fn], returning a [`PyAny`] representing the
/// result.
pub fn call_get_origin<'py>(tp: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyAny>> {
Ok(get_origin_fn(tp.py())?.call1((tp,))?.downcast_into()?)
Ok(get_origin_fn(tp.py())?.call1((tp,))?.cast_into()?)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
use nac3core::{
codegen::{CodeGenContext, expr::call_extern},
codegen::{CodeGenContext, expr::call_extern, typed_store},
inkwell::{AddressSpace, AtomicOrdering, values::BasicValueEnum},
};
@@ -86,14 +86,10 @@ impl TimeFns for NowPinningTimeFns64 {
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(2, false)], "now.lo.addr")
}
.unwrap();
ctx.builder
.build_store(now_hiptr, time_hi)
.unwrap()
typed_store(&ctx.builder, now_hiptr, time_hi)
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
.unwrap();
ctx.builder
.build_store(now_loptr, time_lo)
.unwrap()
typed_store(&ctx.builder, now_loptr, time_lo)
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
.unwrap();
}
@@ -147,14 +143,10 @@ impl TimeFns for NowPinningTimeFns64 {
.unwrap();
let time_lo = ctx.builder.build_int_truncate(time, i32_type, "time.lo").unwrap();
ctx.builder
.build_store(now_hiptr, time_hi)
.unwrap()
typed_store(&ctx.builder, now_hiptr, time_hi)
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
.unwrap();
ctx.builder
.build_store(now_loptr, time_lo)
.unwrap()
typed_store(&ctx.builder, now_loptr, time_lo)
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
.unwrap();
}
@@ -213,14 +205,10 @@ impl TimeFns for NowPinningTimeFns {
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(1, false)], "now.lo.addr")
}
.unwrap();
ctx.builder
.build_store(now_hiptr, time_hi)
.unwrap()
typed_store(&ctx.builder, now_hiptr, time_hi)
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
.unwrap();
ctx.builder
.build_store(now_loptr, time_lo)
.unwrap()
typed_store(&ctx.builder, now_loptr, time_lo)
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
.unwrap();
}
@@ -264,14 +252,10 @@ impl TimeFns for NowPinningTimeFns {
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(1, false)], "now.lo.addr")
}
.unwrap();
ctx.builder
.build_store(now_hiptr, time_hi)
.unwrap()
typed_store(&ctx.builder, now_hiptr, time_hi)
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
.unwrap();
ctx.builder
.build_store(now_loptr, time_lo)
.unwrap()
typed_store(&ctx.builder, now_loptr, time_lo)
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
.unwrap();
}

View File

@@ -1,5 +1,7 @@
// File automatically generated by ast/asdl_rs.py.
#![allow(clippy::nursery)]
pub use crate::constant::*;
pub use crate::location::Location;

View File

@@ -5,40 +5,40 @@ pub enum Constant {
Str(String),
Bytes(Vec<u8>),
Int(i128),
Tuple(Vec<Constant>),
Tuple(Vec<Self>),
Float(f64),
Complex { real: f64, imag: f64 },
Ellipsis,
}
impl From<String> for Constant {
fn from(s: String) -> Constant {
fn from(s: String) -> Self {
Self::Str(s)
}
}
impl From<Vec<u8>> for Constant {
fn from(b: Vec<u8>) -> Constant {
fn from(b: Vec<u8>) -> Self {
Self::Bytes(b)
}
}
impl From<bool> for Constant {
fn from(b: bool) -> Constant {
fn from(b: bool) -> Self {
Self::Bool(b)
}
}
impl From<i32> for Constant {
fn from(i: i32) -> Constant {
fn from(i: i32) -> Self {
Self::Int(i128::from(i))
}
}
impl From<i64> for Constant {
fn from(i: i64) -> Constant {
fn from(i: i64) -> Self {
Self::Int(i128::from(i))
}
}
/// Transforms a value prior to formatting it.
#[derive(Copy, Clone, Debug, PartialEq)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum ConversionFlag {
/// Converts by calling `str(<value>)`.
@@ -51,7 +51,7 @@ pub enum ConversionFlag {
impl ConversionFlag {
#[must_use]
pub fn try_from_byte(b: u8) -> Option<Self> {
pub const fn try_from_byte(b: u8) -> Option<Self> {
match b {
b's' => Some(Self::Str),
b'a' => Some(Self::Ascii),
@@ -71,7 +71,7 @@ pub struct ConstantOptimizer {
impl ConstantOptimizer {
#[inline]
#[must_use]
pub fn new() -> Self {
pub const fn new() -> Self {
Self { _priv: () }
}
}

View File

@@ -1,6 +1,6 @@
use crate::{StrRef, constant, fold::Fold};
pub(crate) trait Foldable<T, U> {
pub trait Foldable<T, U> {
type Mapped;
fn fold<F: Fold<T, TargetU = U> + ?Sized>(
self,

View File

@@ -5,16 +5,14 @@ impl<U> ExprKind<U> {
#[must_use]
pub fn name(&self) -> &'static str {
match self {
ExprKind::BoolOp { .. } | ExprKind::BinOp { .. } | ExprKind::UnaryOp { .. } => {
"operator"
}
ExprKind::Subscript { .. } => "subscript",
ExprKind::Await { .. } => "await expression",
ExprKind::Yield { .. } | ExprKind::YieldFrom { .. } => "yield expression",
ExprKind::Compare { .. } => "comparison",
ExprKind::Attribute { .. } => "attribute",
ExprKind::Call { .. } => "function call",
ExprKind::Constant { value, .. } => match value {
Self::BoolOp { .. } | Self::BinOp { .. } | Self::UnaryOp { .. } => "operator",
Self::Subscript { .. } => "subscript",
Self::Await { .. } => "await expression",
Self::Yield { .. } | Self::YieldFrom { .. } => "yield expression",
Self::Compare { .. } => "comparison",
Self::Attribute { .. } => "attribute",
Self::Call { .. } => "function call",
Self::Constant { value, .. } => match value {
Constant::Str(_)
| Constant::Int(_)
| Constant::Float(_)
@@ -24,28 +22,28 @@ impl<U> ExprKind<U> {
Constant::Bool(_) | Constant::None => "keyword",
Constant::Ellipsis => "ellipsis",
},
ExprKind::List { .. } => "list",
ExprKind::Tuple { .. } => "tuple",
ExprKind::Dict { .. } => "dict display",
ExprKind::Set { .. } => "set display",
ExprKind::ListComp { .. } => "list comprehension",
ExprKind::DictComp { .. } => "dict comprehension",
ExprKind::SetComp { .. } => "set comprehension",
ExprKind::GeneratorExp { .. } => "generator expression",
ExprKind::Starred { .. } => "starred",
ExprKind::Slice { .. } => "slice",
ExprKind::JoinedStr { values } => {
if values.iter().any(|e| matches!(e.node, ExprKind::JoinedStr { .. })) {
Self::List { .. } => "list",
Self::Tuple { .. } => "tuple",
Self::Dict { .. } => "dict display",
Self::Set { .. } => "set display",
Self::ListComp { .. } => "list comprehension",
Self::DictComp { .. } => "dict comprehension",
Self::SetComp { .. } => "set comprehension",
Self::GeneratorExp { .. } => "generator expression",
Self::Starred { .. } => "starred",
Self::Slice { .. } => "slice",
Self::JoinedStr { values } => {
if values.iter().any(|e| matches!(e.node, Self::JoinedStr { .. })) {
"f-string expression"
} else {
"literal"
}
}
ExprKind::FormattedValue { .. } => "f-string expression",
ExprKind::Name { .. } => "name",
ExprKind::Lambda { .. } => "lambda",
ExprKind::IfExp { .. } => "conditional expression",
ExprKind::NamedExpr { .. } => "named expression",
Self::FormattedValue { .. } => "f-string expression",
Self::Name { .. } => "name",
Self::Lambda { .. } => "lambda",
Self::IfExp { .. } => "conditional expression",
Self::NamedExpr { .. } => "named expression",
}
}
}

View File

@@ -1,5 +1,5 @@
#![deny(future_incompatible, let_underscore, nonstandard_style, clippy::all)]
#![warn(clippy::pedantic)]
#![warn(clippy::pedantic, clippy::nursery)]
#[allow(
clippy::missing_errors_doc,

View File

@@ -7,13 +7,13 @@ use crate::StrRef;
pub struct FileName(pub StrRef);
impl Default for FileName {
fn default() -> Self {
FileName("unknown".into())
Self("unknown".into())
}
}
impl From<String> for FileName {
fn from(s: String) -> Self {
FileName(s.into())
Self(s.into())
}
}
@@ -82,34 +82,34 @@ impl Location {
impl Location {
#[must_use]
pub fn new(row: usize, column: usize, file: FileName) -> Self {
Location { row, column, file }
pub const fn new(row: usize, column: usize, file: FileName) -> Self {
Self { row, column, file }
}
#[must_use]
pub fn row(&self) -> usize {
pub const fn row(&self) -> usize {
self.row
}
#[must_use]
pub fn column(&self) -> usize {
pub const fn column(&self) -> usize {
self.column
}
pub fn reset(&mut self) {
pub const fn reset(&mut self) {
self.row = 1;
self.column = 1;
}
pub fn go_right(&mut self) {
pub const fn go_right(&mut self) {
self.column += 1;
}
pub fn go_left(&mut self) {
pub const fn go_left(&mut self) {
self.column -= 1;
}
pub fn newline(&mut self) {
pub const fn newline(&mut self) {
self.row += 1;
self.column = 1;
}

View File

@@ -1,5 +1,5 @@
[package]
name = "nac3ld"
name = "nac3binutils"
version = "0.1.0"
authors = ["M-Labs"]
edition = "2024"

View File

@@ -1,29 +1,10 @@
#![allow(non_camel_case_types, non_upper_case_globals)]
#![allow(nonstandard_style, non_upper_case_globals, clippy::wildcard_imports)]
use std::mem;
use crate::include::dwarf::*;
use std::{mem, str};
use byteorder::{ByteOrder, LittleEndian};
pub const DW_EH_PE_omit: u8 = 0xFF;
pub const DW_EH_PE_absptr: u8 = 0x00;
pub const DW_EH_PE_uleb128: u8 = 0x01;
pub const DW_EH_PE_udata2: u8 = 0x02;
pub const DW_EH_PE_udata4: u8 = 0x03;
pub const DW_EH_PE_udata8: u8 = 0x04;
pub const DW_EH_PE_sleb128: u8 = 0x09;
pub const DW_EH_PE_sdata2: u8 = 0x0A;
pub const DW_EH_PE_sdata4: u8 = 0x0B;
pub const DW_EH_PE_sdata8: u8 = 0x0C;
pub const DW_EH_PE_pcrel: u8 = 0x10;
pub const DW_EH_PE_textrel: u8 = 0x20;
pub const DW_EH_PE_datarel: u8 = 0x30;
pub const DW_EH_PE_funcrel: u8 = 0x40;
pub const DW_EH_PE_aligned: u8 = 0x50;
pub const DW_EH_PE_indirect: u8 = 0x80;
#[derive(Clone)]
pub struct DwarfReader<'a> {
pub slice: &'a [u8],
@@ -31,7 +12,7 @@ pub struct DwarfReader<'a> {
}
impl DwarfReader<'_> {
pub fn new(slice: &[u8], virt_addr: u32) -> DwarfReader {
pub const fn new(slice: &[u8], virt_addr: u32) -> DwarfReader<'_> {
DwarfReader { slice, virt_addr }
}
@@ -82,6 +63,31 @@ impl DwarfReader<'_> {
self.virt_addr += 1;
val
}
pub fn read_i8(&mut self) -> i8 {
let val = self.slice[0] as i8;
self.slice = &self.slice[1..];
self.virt_addr += 1;
val
}
pub const fn read_slice(&mut self, len: usize) -> &[u8] {
let (slice, remaining) = self.slice.split_at(len);
self.slice = remaining;
self.virt_addr += len as u32;
slice
}
pub fn read_str(&mut self) -> &str {
let str_len = self
.slice
.iter()
.position(|byte| *byte == 0)
.expect("string should be null-terminated");
unsafe {
str::from_utf8_unchecked(&self.read_slice(str_len + 1)[..str_len]) // null-terminator
}
}
}
macro_rules! impl_read_fn {
@@ -108,13 +114,156 @@ impl_read_fn!(
i64, read_i64
);
macro_rules! read_block_fn {
( $($read_block_fn: ident, $read_length_fn: ident);* ) => {
impl<'a> DwarfReader<'a> {
$(
pub fn $read_block_fn(&mut self) -> &[u8] {
let len = self.$read_length_fn() as usize;
self.read_slice(len)
}
)*
}
}
}
read_block_fn!(
read_form_block, read_uleb128;
read_form_block1, read_u8;
read_form_block2, read_u16;
read_form_block4, read_u32
);
macro_rules! alias_read_fn {
( $($type: ty, $read_form_fn: ident, $aliased_fn: ident);* ) => {
impl<'a> DwarfReader<'a> {
$(
pub fn $read_form_fn(&mut self) -> $type {
self.$aliased_fn()
}
)*
}
}
}
alias_read_fn!(
u32, read_form_addr, read_u32;
u8, read_form_data1, read_u8;
u16, read_form_data2, read_u16;
u32, read_form_data4, read_u32;
u64, read_form_data8, read_u64;
i64, read_form_sdata, read_i64;
u64, read_form_udata, read_u64;
&[u8], read_form_exprloc, read_form_block;
u8, read_form_flag, read_u8;
u32, read_form_sec_offset, read_u32
);
impl DwarfReader<'_> {
// Helper to perform constant read
// Everything is casted as u64 (as the broadest type).
// Use at your own risk.
pub fn read_form_constant(&mut self, attr_form: u64) -> u64 {
match attr_form {
DW_FORM_data1 => u64::from(self.read_form_data1()),
DW_FORM_data2 => u64::from(self.read_form_data2()),
DW_FORM_data4 => u64::from(self.read_form_data4()),
DW_FORM_data8 => self.read_form_data8(),
DW_FORM_sdata => self.read_form_sdata() as u64,
DW_FORM_udata => self.read_form_udata(),
_ => unreachable!("form should be a constant"),
}
}
// Helper to perform reference read
// Everything is casted as u64 (as the broadest type).
// Use at your own risk.
pub fn read_form_reference(&mut self, attr_form: u64) -> u64 {
match attr_form {
DW_FORM_ref1 => u64::from(self.read_form_data1()),
DW_FORM_ref2 => u64::from(self.read_form_data2()),
DW_FORM_ref4 => u64::from(self.read_form_data4()),
DW_FORM_ref8 => self.read_form_data8(),
DW_FORM_ref_addr => u64::from(self.read_form_addr()),
DW_FORM_ref_udata => self.read_form_udata(),
DW_FORM_ref_sig8 => self.read_u64(),
_ => unreachable!("form should be a reference"),
}
}
pub fn skip_form(&mut self, attr_form: u64) {
match attr_form {
DW_FORM_addr | DW_FORM_ref_addr | DW_FORM_strp => {
self.read_form_addr();
}
DW_FORM_data1 | DW_FORM_ref1 => {
self.read_form_data1();
}
DW_FORM_data2 | DW_FORM_ref2 => {
self.read_form_data2();
}
DW_FORM_data4 | DW_FORM_ref4 => {
self.read_form_data4();
}
DW_FORM_data8 | DW_FORM_ref8 => {
self.read_form_data8();
}
DW_FORM_sdata => {
self.read_form_sdata();
}
DW_FORM_udata | DW_FORM_ref_udata => {
self.read_form_udata();
}
DW_FORM_flag => {
self.read_form_flag();
}
DW_FORM_block => {
self.read_form_block();
}
DW_FORM_exprloc => {
self.read_form_exprloc();
}
DW_FORM_block1 => {
self.read_form_block1();
}
DW_FORM_block2 => {
self.read_form_block2();
}
DW_FORM_block4 => {
self.read_form_block4();
}
DW_FORM_string => {
self.read_str();
}
DW_FORM_ref_sig8 => {
self.read_u64();
}
DW_FORM_flag_present => (),
DW_FORM_sec_offset => {
self.read_form_sec_offset();
}
DW_FORM_indirect => {
// Section 7.5.3
// ... the attribute value itself ... begins with an unsigned
// LEB128 number that represents its form.
let inner_form = self.read_uleb128();
self.skip_form(inner_form);
}
_ => unreachable!("unrecognized attribute form"),
}
}
}
pub struct DwarfWriter<'a> {
pub slice: &'a mut [u8],
pub offset: usize,
}
impl DwarfWriter<'_> {
pub fn new(slice: &mut [u8]) -> DwarfWriter {
pub const fn new(slice: &mut [u8]) -> DwarfWriter<'_> {
DwarfWriter { slice, offset: 0 }
}
@@ -191,7 +340,7 @@ fn read_encoded_pointer_with_pc(reader: &mut DwarfReader, encoding: u8) -> Resul
}
#[inline]
fn round_up(unrounded: usize, align: usize) -> Result<usize, ()> {
const fn round_up(unrounded: usize, align: usize) -> Result<usize, ()> {
if align.is_power_of_two() { Ok((unrounded + align - 1) & !(align - 1)) } else { Err(()) }
}
@@ -207,7 +356,7 @@ pub struct EH_Frame<'a> {
impl<'a> EH_Frame<'a> {
/// Creates an [`EH_Frame`] using the bytes in the `.eh_frame` section and its address in the
/// ELF file.
pub fn new(eh_frame_slice: &[u8], eh_frame_addr: u32) -> EH_Frame {
pub const fn new(eh_frame_slice: &[u8], eh_frame_addr: u32) -> EH_Frame<'_> {
EH_Frame { reader: DwarfReader::new(eh_frame_slice, eh_frame_addr) }
}
@@ -233,7 +382,7 @@ pub struct CFI_Record<'a> {
}
impl<'a> CFI_Record<'a> {
pub fn from_reader(cie_reader: &mut DwarfReader<'a>) -> Result<CFI_Record<'a>, ()> {
pub fn from_reader(cie_reader: &mut DwarfReader<'a>) -> Result<Self, ()> {
let length = cie_reader.read_u32();
let fde_reader = match length {
// eh_frame with 0 lengths means the CIE is terminated
@@ -428,7 +577,7 @@ impl EH_Frame_Hdr<'_> {
eh_frame_hdr_slice: &mut [u8],
eh_frame_hdr_addr: u32,
eh_frame_addr: u32,
) -> EH_Frame_Hdr {
) -> EH_Frame_Hdr<'_> {
let mut writer = DwarfWriter::new(eh_frame_hdr_slice);
writer.write_u8(1); // version
@@ -445,7 +594,7 @@ impl EH_Frame_Hdr<'_> {
/// The offset of the `fde_count` value relative to the start of the `.eh_frame_hdr` section in
/// bytes.
fn fde_count_offset() -> usize {
const fn fde_count_offset() -> usize {
8
}

View File

@@ -0,0 +1,374 @@
/* automatically generated by rust-bindgen 0.72.1 */
/* with manual modifications to remove unused specification */
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals, dead_code, clippy::pedantic)]
pub const DW_AT_subscr_data: u32 = 10;
pub const DW_AT_element_list: u32 = 15;
pub const DW_AT_member: u32 = 20;
pub const DW_ADDR_none: u32 = 0;
pub const DW_TAG_array_type: DW_TAG = 1;
pub const DW_TAG_class_type: DW_TAG = 2;
pub const DW_TAG_entry_point: DW_TAG = 3;
pub const DW_TAG_enumeration_type: DW_TAG = 4;
pub const DW_TAG_formal_parameter: DW_TAG = 5;
pub const DW_TAG_imported_declaration: DW_TAG = 8;
pub const DW_TAG_label: DW_TAG = 10;
pub const DW_TAG_lexical_block: DW_TAG = 11;
pub const DW_TAG_member: DW_TAG = 13;
pub const DW_TAG_pointer_type: DW_TAG = 15;
pub const DW_TAG_reference_type: DW_TAG = 16;
pub const DW_TAG_compile_unit: DW_TAG = 17;
pub const DW_TAG_string_type: DW_TAG = 18;
pub const DW_TAG_structure_type: DW_TAG = 19;
pub const DW_TAG_subroutine_type: DW_TAG = 21;
pub const DW_TAG_typedef: DW_TAG = 22;
pub const DW_TAG_union_type: DW_TAG = 23;
pub const DW_TAG_unspecified_parameters: DW_TAG = 24;
pub const DW_TAG_variant: DW_TAG = 25;
pub const DW_TAG_common_block: DW_TAG = 26;
pub const DW_TAG_common_inclusion: DW_TAG = 27;
pub const DW_TAG_inheritance: DW_TAG = 28;
pub const DW_TAG_inlined_subroutine: DW_TAG = 29;
pub const DW_TAG_module: DW_TAG = 30;
pub const DW_TAG_ptr_to_member_type: DW_TAG = 31;
pub const DW_TAG_set_type: DW_TAG = 32;
pub const DW_TAG_subrange_type: DW_TAG = 33;
pub const DW_TAG_with_stmt: DW_TAG = 34;
pub const DW_TAG_access_declaration: DW_TAG = 35;
pub const DW_TAG_base_type: DW_TAG = 36;
pub const DW_TAG_catch_block: DW_TAG = 37;
pub const DW_TAG_const_type: DW_TAG = 38;
pub const DW_TAG_constant: DW_TAG = 39;
pub const DW_TAG_enumerator: DW_TAG = 40;
pub const DW_TAG_file_type: DW_TAG = 41;
pub const DW_TAG_friend: DW_TAG = 42;
pub const DW_TAG_namelist: DW_TAG = 43;
pub const DW_TAG_namelist_item: DW_TAG = 44;
pub const DW_TAG_packed_type: DW_TAG = 45;
pub const DW_TAG_subprogram: DW_TAG = 46;
pub const DW_TAG_template_type_parameter: DW_TAG = 47;
pub const DW_TAG_template_value_parameter: DW_TAG = 48;
pub const DW_TAG_thrown_type: DW_TAG = 49;
pub const DW_TAG_try_block: DW_TAG = 50;
pub const DW_TAG_variant_part: DW_TAG = 51;
pub const DW_TAG_variable: DW_TAG = 52;
pub const DW_TAG_volatile_type: DW_TAG = 53;
pub const DW_TAG_dwarf_procedure: DW_TAG = 54;
pub const DW_TAG_restrict_type: DW_TAG = 55;
pub const DW_TAG_interface_type: DW_TAG = 56;
pub const DW_TAG_namespace: DW_TAG = 57;
pub const DW_TAG_imported_module: DW_TAG = 58;
pub const DW_TAG_unspecified_type: DW_TAG = 59;
pub const DW_TAG_partial_unit: DW_TAG = 60;
pub const DW_TAG_imported_unit: DW_TAG = 61;
pub const DW_TAG_condition: DW_TAG = 63;
pub const DW_TAG_shared_type: DW_TAG = 64;
pub const DW_TAG_type_unit: DW_TAG = 65;
pub const DW_TAG_rvalue_reference_type: DW_TAG = 66;
pub const DW_TAG_template_alias: DW_TAG = 67;
pub const DW_TAG_coarray_type: DW_TAG = 68;
pub const DW_TAG_generic_subrange: DW_TAG = 69;
pub const DW_TAG_dynamic_type: DW_TAG = 70;
pub const DW_TAG_atomic_type: DW_TAG = 71;
pub const DW_TAG_call_site: DW_TAG = 72;
pub const DW_TAG_call_site_parameter: DW_TAG = 73;
pub const DW_TAG_skeleton_unit: DW_TAG = 74;
pub const DW_TAG_immutable_type: DW_TAG = 75;
pub const DW_TAG_lo_user: DW_TAG = 16512;
pub const DW_TAG_MIPS_loop: DW_TAG = 16513;
pub const DW_TAG_format_label: DW_TAG = 16641;
pub const DW_TAG_function_template: DW_TAG = 16642;
pub const DW_TAG_class_template: DW_TAG = 16643;
pub const DW_TAG_GNU_BINCL: DW_TAG = 16644;
pub const DW_TAG_GNU_EINCL: DW_TAG = 16645;
pub const DW_TAG_GNU_template_template_param: DW_TAG = 16646;
pub const DW_TAG_GNU_template_parameter_pack: DW_TAG = 16647;
pub const DW_TAG_GNU_formal_parameter_pack: DW_TAG = 16648;
pub const DW_TAG_GNU_call_site: DW_TAG = 16649;
pub const DW_TAG_GNU_call_site_parameter: DW_TAG = 16650;
pub const DW_TAG_hi_user: DW_TAG = 65535;
pub type DW_TAG = u64; // ULEB128
pub const DW_CHILDREN_no: DW_CHILDREN = 0;
pub const DW_CHILDREN_yes: DW_CHILDREN = 1;
pub type DW_CHILDREN = u8; // 7.5.3 Abbreviations Tables: Encoded as a byte
pub const DW_AT_sibling: DW_AT = 1;
pub const DW_AT_location: DW_AT = 2;
pub const DW_AT_name: DW_AT = 3;
pub const DW_AT_ordering: DW_AT = 9;
pub const DW_AT_byte_size: DW_AT = 11;
pub const DW_AT_bit_offset: DW_AT = 12;
pub const DW_AT_bit_size: DW_AT = 13;
pub const DW_AT_stmt_list: DW_AT = 16;
pub const DW_AT_low_pc: DW_AT = 17;
pub const DW_AT_high_pc: DW_AT = 18;
pub const DW_AT_language: DW_AT = 19;
pub const DW_AT_discr: DW_AT = 21;
pub const DW_AT_discr_value: DW_AT = 22;
pub const DW_AT_visibility: DW_AT = 23;
pub const DW_AT_import: DW_AT = 24;
pub const DW_AT_string_length: DW_AT = 25;
pub const DW_AT_common_reference: DW_AT = 26;
pub const DW_AT_comp_dir: DW_AT = 27;
pub const DW_AT_const_value: DW_AT = 28;
pub const DW_AT_containing_type: DW_AT = 29;
pub const DW_AT_default_value: DW_AT = 30;
pub const DW_AT_inline: DW_AT = 32;
pub const DW_AT_is_optional: DW_AT = 33;
pub const DW_AT_lower_bound: DW_AT = 34;
pub const DW_AT_producer: DW_AT = 37;
pub const DW_AT_prototyped: DW_AT = 39;
pub const DW_AT_return_addr: DW_AT = 42;
pub const DW_AT_start_scope: DW_AT = 44;
pub const DW_AT_bit_stride: DW_AT = 46;
pub const DW_AT_upper_bound: DW_AT = 47;
pub const DW_AT_abstract_origin: DW_AT = 49;
pub const DW_AT_accessibility: DW_AT = 50;
pub const DW_AT_address_class: DW_AT = 51;
pub const DW_AT_artificial: DW_AT = 52;
pub const DW_AT_base_types: DW_AT = 53;
pub const DW_AT_calling_convention: DW_AT = 54;
pub const DW_AT_count: DW_AT = 55;
pub const DW_AT_data_member_location: DW_AT = 56;
pub const DW_AT_decl_column: DW_AT = 57;
pub const DW_AT_decl_file: DW_AT = 58;
pub const DW_AT_decl_line: DW_AT = 59;
pub const DW_AT_declaration: DW_AT = 60;
pub const DW_AT_discr_list: DW_AT = 61;
pub const DW_AT_encoding: DW_AT = 62;
pub const DW_AT_external: DW_AT = 63;
pub const DW_AT_frame_base: DW_AT = 64;
pub const DW_AT_friend: DW_AT = 65;
pub const DW_AT_identifier_case: DW_AT = 66;
pub const DW_AT_macro_info: DW_AT = 67;
pub const DW_AT_namelist_item: DW_AT = 68;
pub const DW_AT_priority: DW_AT = 69;
pub const DW_AT_segment: DW_AT = 70;
pub const DW_AT_specification: DW_AT = 71;
pub const DW_AT_static_link: DW_AT = 72;
pub const DW_AT_type: DW_AT = 73;
pub const DW_AT_use_location: DW_AT = 74;
pub const DW_AT_variable_parameter: DW_AT = 75;
pub const DW_AT_virtuality: DW_AT = 76;
pub const DW_AT_vtable_elem_location: DW_AT = 77;
pub const DW_AT_allocated: DW_AT = 78;
pub const DW_AT_associated: DW_AT = 79;
pub const DW_AT_data_location: DW_AT = 80;
pub const DW_AT_byte_stride: DW_AT = 81;
pub const DW_AT_entry_pc: DW_AT = 82;
pub const DW_AT_use_UTF8: DW_AT = 83;
pub const DW_AT_extension: DW_AT = 84;
pub const DW_AT_ranges: DW_AT = 85;
pub const DW_AT_trampoline: DW_AT = 86;
pub const DW_AT_call_column: DW_AT = 87;
pub const DW_AT_call_file: DW_AT = 88;
pub const DW_AT_call_line: DW_AT = 89;
pub const DW_AT_description: DW_AT = 90;
pub const DW_AT_binary_scale: DW_AT = 91;
pub const DW_AT_decimal_scale: DW_AT = 92;
pub const DW_AT_small: DW_AT = 93;
pub const DW_AT_decimal_sign: DW_AT = 94;
pub const DW_AT_digit_count: DW_AT = 95;
pub const DW_AT_picture_string: DW_AT = 96;
pub const DW_AT_mutable: DW_AT = 97;
pub const DW_AT_threads_scaled: DW_AT = 98;
pub const DW_AT_explicit: DW_AT = 99;
pub const DW_AT_object_pointer: DW_AT = 100;
pub const DW_AT_endianity: DW_AT = 101;
pub const DW_AT_elemental: DW_AT = 102;
pub const DW_AT_pure: DW_AT = 103;
pub const DW_AT_recursive: DW_AT = 104;
pub const DW_AT_signature: DW_AT = 105;
pub const DW_AT_main_subprogram: DW_AT = 106;
pub const DW_AT_data_bit_offset: DW_AT = 107;
pub const DW_AT_const_expr: DW_AT = 108;
pub const DW_AT_enum_class: DW_AT = 109;
pub const DW_AT_linkage_name: DW_AT = 110;
pub const DW_AT_string_length_bit_size: DW_AT = 111;
pub const DW_AT_string_length_byte_size: DW_AT = 112;
pub const DW_AT_rank: DW_AT = 113;
pub const DW_AT_str_offsets_base: DW_AT = 114;
pub const DW_AT_addr_base: DW_AT = 115;
pub const DW_AT_rnglists_base: DW_AT = 116;
pub const DW_AT_dwo_name: DW_AT = 118;
pub const DW_AT_reference: DW_AT = 119;
pub const DW_AT_rvalue_reference: DW_AT = 120;
pub const DW_AT_macros: DW_AT = 121;
pub const DW_AT_call_all_calls: DW_AT = 122;
pub const DW_AT_call_all_source_calls: DW_AT = 123;
pub const DW_AT_call_all_tail_calls: DW_AT = 124;
pub const DW_AT_call_return_pc: DW_AT = 125;
pub const DW_AT_call_value: DW_AT = 126;
pub const DW_AT_call_origin: DW_AT = 127;
pub const DW_AT_call_parameter: DW_AT = 128;
pub const DW_AT_call_pc: DW_AT = 129;
pub const DW_AT_call_tail_call: DW_AT = 130;
pub const DW_AT_call_target: DW_AT = 131;
pub const DW_AT_call_target_clobbered: DW_AT = 132;
pub const DW_AT_call_data_location: DW_AT = 133;
pub const DW_AT_call_data_value: DW_AT = 134;
pub const DW_AT_noreturn: DW_AT = 135;
pub const DW_AT_alignment: DW_AT = 136;
pub const DW_AT_export_symbols: DW_AT = 137;
pub const DW_AT_deleted: DW_AT = 138;
pub const DW_AT_defaulted: DW_AT = 139;
pub const DW_AT_loclists_base: DW_AT = 140;
pub const DW_AT_lo_user: DW_AT = 8192;
pub const DW_AT_MIPS_fde: DW_AT = 8193;
pub const DW_AT_MIPS_loop_begin: DW_AT = 8194;
pub const DW_AT_MIPS_tail_loop_begin: DW_AT = 8195;
pub const DW_AT_MIPS_epilog_begin: DW_AT = 8196;
pub const DW_AT_MIPS_loop_unroll_factor: DW_AT = 8197;
pub const DW_AT_MIPS_software_pipeline_depth: DW_AT = 8198;
pub const DW_AT_MIPS_linkage_name: DW_AT = 8199;
pub const DW_AT_MIPS_stride: DW_AT = 8200;
pub const DW_AT_MIPS_abstract_name: DW_AT = 8201;
pub const DW_AT_MIPS_clone_origin: DW_AT = 8202;
pub const DW_AT_MIPS_has_inlines: DW_AT = 8203;
pub const DW_AT_MIPS_stride_byte: DW_AT = 8204;
pub const DW_AT_MIPS_stride_elem: DW_AT = 8205;
pub const DW_AT_MIPS_ptr_dopetype: DW_AT = 8206;
pub const DW_AT_MIPS_allocatable_dopetype: DW_AT = 8207;
pub const DW_AT_MIPS_assumed_shape_dopetype: DW_AT = 8208;
pub const DW_AT_MIPS_assumed_size: DW_AT = 8209;
pub const DW_AT_sf_names: DW_AT = 8449;
pub const DW_AT_src_info: DW_AT = 8450;
pub const DW_AT_mac_info: DW_AT = 8451;
pub const DW_AT_src_coords: DW_AT = 8452;
pub const DW_AT_body_begin: DW_AT = 8453;
pub const DW_AT_body_end: DW_AT = 8454;
pub const DW_AT_GNU_vector: DW_AT = 8455;
pub const DW_AT_GNU_guarded_by: DW_AT = 8456;
pub const DW_AT_GNU_pt_guarded_by: DW_AT = 8457;
pub const DW_AT_GNU_guarded: DW_AT = 8458;
pub const DW_AT_GNU_pt_guarded: DW_AT = 8459;
pub const DW_AT_GNU_locks_excluded: DW_AT = 8460;
pub const DW_AT_GNU_exclusive_locks_required: DW_AT = 8461;
pub const DW_AT_GNU_shared_locks_required: DW_AT = 8462;
pub const DW_AT_GNU_odr_signature: DW_AT = 8463;
pub const DW_AT_GNU_template_name: DW_AT = 8464;
pub const DW_AT_GNU_call_site_value: DW_AT = 8465;
pub const DW_AT_GNU_call_site_data_value: DW_AT = 8466;
pub const DW_AT_GNU_call_site_target: DW_AT = 8467;
pub const DW_AT_GNU_call_site_target_clobbered: DW_AT = 8468;
pub const DW_AT_GNU_tail_call: DW_AT = 8469;
pub const DW_AT_GNU_all_tail_call_sites: DW_AT = 8470;
pub const DW_AT_GNU_all_call_sites: DW_AT = 8471;
pub const DW_AT_GNU_all_source_call_sites: DW_AT = 8472;
pub const DW_AT_GNU_locviews: DW_AT = 8503;
pub const DW_AT_GNU_entry_view: DW_AT = 8504;
pub const DW_AT_GNU_macros: DW_AT = 8473;
pub const DW_AT_GNU_deleted: DW_AT = 8474;
pub const DW_AT_GNU_dwo_name: DW_AT = 8496;
pub const DW_AT_GNU_dwo_id: DW_AT = 8497;
pub const DW_AT_GNU_ranges_base: DW_AT = 8498;
pub const DW_AT_GNU_addr_base: DW_AT = 8499;
pub const DW_AT_GNU_pubnames: DW_AT = 8500;
pub const DW_AT_GNU_pubtypes: DW_AT = 8501;
pub const DW_AT_GNU_numerator: DW_AT = 8963;
pub const DW_AT_GNU_denominator: DW_AT = 8964;
pub const DW_AT_GNU_bias: DW_AT = 8965;
pub const DW_AT_hi_user: DW_AT = 16383;
pub type DW_AT = u64; // ULEB128
// By attribute specifications in 7.5.3 Abbreviations Tables.
pub const DW_FORM_addr: DW_FORM = 1;
pub const DW_FORM_block2: DW_FORM = 3;
pub const DW_FORM_block4: DW_FORM = 4;
pub const DW_FORM_data2: DW_FORM = 5;
pub const DW_FORM_data4: DW_FORM = 6;
pub const DW_FORM_data8: DW_FORM = 7;
pub const DW_FORM_string: DW_FORM = 8;
pub const DW_FORM_block: DW_FORM = 9;
pub const DW_FORM_block1: DW_FORM = 10;
pub const DW_FORM_data1: DW_FORM = 11;
pub const DW_FORM_flag: DW_FORM = 12;
pub const DW_FORM_sdata: DW_FORM = 13;
pub const DW_FORM_strp: DW_FORM = 14;
pub const DW_FORM_udata: DW_FORM = 15;
pub const DW_FORM_ref_addr: DW_FORM = 16;
pub const DW_FORM_ref1: DW_FORM = 17;
pub const DW_FORM_ref2: DW_FORM = 18;
pub const DW_FORM_ref4: DW_FORM = 19;
pub const DW_FORM_ref8: DW_FORM = 20;
pub const DW_FORM_ref_udata: DW_FORM = 21;
pub const DW_FORM_indirect: DW_FORM = 22;
pub const DW_FORM_sec_offset: DW_FORM = 23;
pub const DW_FORM_exprloc: DW_FORM = 24;
pub const DW_FORM_flag_present: DW_FORM = 25;
pub const DW_FORM_strx: DW_FORM = 26;
pub const DW_FORM_addrx: DW_FORM = 27;
pub const DW_FORM_ref_sup4: DW_FORM = 28;
pub const DW_FORM_strp_sup: DW_FORM = 29;
pub const DW_FORM_data16: DW_FORM = 30;
pub const DW_FORM_line_strp: DW_FORM = 31;
pub const DW_FORM_ref_sig8: DW_FORM = 32;
pub const DW_FORM_implicit_const: DW_FORM = 33;
pub const DW_FORM_loclistx: DW_FORM = 34;
pub const DW_FORM_rnglistx: DW_FORM = 35;
pub const DW_FORM_ref_sup8: DW_FORM = 36;
pub const DW_FORM_strx1: DW_FORM = 37;
pub const DW_FORM_strx2: DW_FORM = 38;
pub const DW_FORM_strx3: DW_FORM = 39;
pub const DW_FORM_strx4: DW_FORM = 40;
pub const DW_FORM_addrx1: DW_FORM = 41;
pub const DW_FORM_addrx2: DW_FORM = 42;
pub const DW_FORM_addrx3: DW_FORM = 43;
pub const DW_FORM_addrx4: DW_FORM = 44;
pub const DW_FORM_GNU_addr_index: DW_FORM = 7937;
pub const DW_FORM_GNU_str_index: DW_FORM = 7938;
pub const DW_FORM_GNU_ref_alt: DW_FORM = 7968;
pub const DW_FORM_GNU_strp_alt: DW_FORM = 7969;
pub type DW_FORM = u64; // ULEB128
// By attribute specifications in 7.5.3 Abbreviations Tables.
pub const DW_LNS_copy: DW_LNS = 1;
pub const DW_LNS_advance_pc: DW_LNS = 2;
pub const DW_LNS_advance_line: DW_LNS = 3;
pub const DW_LNS_set_file: DW_LNS = 4;
pub const DW_LNS_set_column: DW_LNS = 5;
pub const DW_LNS_negate_stmt: DW_LNS = 6;
pub const DW_LNS_set_basic_block: DW_LNS = 7;
pub const DW_LNS_const_add_pc: DW_LNS = 8;
pub const DW_LNS_fixed_advance_pc: DW_LNS = 9;
pub const DW_LNS_set_prologue_end: DW_LNS = 10;
pub const DW_LNS_set_epilogue_begin: DW_LNS = 11;
pub const DW_LNS_set_isa: DW_LNS = 12;
pub type DW_LNS = u8; // ubyte
// 6.2.3 Line Number Program Instructions: standard opcodes
pub const DW_LNE_end_sequence: DW_LNE = 1;
pub const DW_LNE_set_address: DW_LNE = 2;
pub const DW_LNE_define_file: DW_LNE = 3;
pub const DW_LNE_set_discriminator: DW_LNE = 4;
pub const DW_LNE_lo_user: DW_LNE = 128;
pub const DW_LNE_hi_user: DW_LNE = 255;
pub type DW_LNE = u8; // ubyte
// 6.2.3 Line Number Program Instructions: extended opcodes
pub const DW_CIE_ID_32: u32 = 0xffffffff;
pub const DW_CIE_ID_64: u64 = 0xffffffffffffffff;
pub const DW_EH_PE_absptr: DW_EH_PE = 0;
pub const DW_EH_PE_omit: DW_EH_PE = 255;
pub const DW_EH_PE_uleb128: DW_EH_PE = 1;
pub const DW_EH_PE_udata2: DW_EH_PE = 2;
pub const DW_EH_PE_udata4: DW_EH_PE = 3;
pub const DW_EH_PE_udata8: DW_EH_PE = 4;
pub const DW_EH_PE_sleb128: DW_EH_PE = 9;
pub const DW_EH_PE_sdata2: DW_EH_PE = 10;
pub const DW_EH_PE_sdata4: DW_EH_PE = 11;
pub const DW_EH_PE_sdata8: DW_EH_PE = 12;
pub const DW_EH_PE_signed: DW_EH_PE = 8;
pub const DW_EH_PE_pcrel: DW_EH_PE = 16;
pub const DW_EH_PE_textrel: DW_EH_PE = 32;
pub const DW_EH_PE_datarel: DW_EH_PE = 48;
pub const DW_EH_PE_funcrel: DW_EH_PE = 64;
pub const DW_EH_PE_aligned: DW_EH_PE = 80;
pub const DW_EH_PE_indirect: DW_EH_PE = 128;
pub type DW_EH_PE = u8; // A mixture of these flags apply to a ubyte
// which specifies the data types in .eh_frame and .eh_frame_hdr
// See Linux Standard Base Core Specification, Generic Part
// 10.5.1. DWARF Exception Header Encoding

View File

@@ -2872,22 +2872,22 @@ impl Clone for Elf64_Lib {
}
pub type Elf32_Conflict = Elf32_Addr;
pub fn ELF32_R_SYM(info: Elf32_Word) -> Elf32_Word {
pub const fn ELF32_R_SYM(info: Elf32_Word) -> Elf32_Word {
info >> 8
}
pub fn ELF32_R_TYPE(info: Elf32_Word) -> u8 {
pub const fn ELF32_R_TYPE(info: Elf32_Word) -> u8 {
info as u8
}
pub fn ELF32_R_INFO(sym: Elf32_Word, ty: u8) -> Elf32_Word {
pub const fn ELF32_R_INFO(sym: Elf32_Word, ty: u8) -> Elf32_Word {
(sym << 8) | ty as Elf32_Word
}
pub fn ELF32_ST_BIND(info: u8) -> u8 {
pub const fn ELF32_ST_BIND(info: u8) -> u8 {
info >> 4
}
pub fn ELF32_ST_TYPE(info: u8) -> u8 {
pub const fn ELF32_ST_TYPE(info: u8) -> u8 {
info & 0xf
}
pub fn ELF32_ST_INFO(bind: u8, ty: u8) -> u8 {
pub const fn ELF32_ST_INFO(bind: u8, ty: u8) -> u8 {
(bind << 4) | (ty & 0xf)
}

View File

@@ -0,0 +1,2 @@
pub mod dwarf;
pub mod elf;

20
nac3binutils/src/lib.rs Normal file
View File

@@ -0,0 +1,20 @@
#![deny(future_incompatible, let_underscore, nonstandard_style, clippy::all)]
#![warn(clippy::pedantic, clippy::nursery)]
#![allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::cognitive_complexity,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::similar_names,
clippy::too_many_lines,
clippy::wildcard_imports
)]
mod dwarf;
mod include;
mod linker;
pub mod symbolizer;
pub use linker::Linker;

View File

@@ -1,28 +1,12 @@
#![deny(future_incompatible, let_underscore, nonstandard_style, clippy::all)]
#![warn(clippy::pedantic)]
#![allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::enum_glob_use,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::similar_names,
clippy::too_many_lines
)]
use std::{collections::HashMap, mem, ptr, slice, str};
use byteorder::{ByteOrder, LittleEndian};
use dwarf::{EH_Frame, EH_Frame_Hdr};
#[allow(clippy::wildcard_imports)]
use elf::*;
use crate::dwarf::{EH_Frame, EH_Frame_Hdr};
mod dwarf;
mod elf;
use crate::include::elf::*;
#[derive(PartialEq, Clone, Copy)]
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum Isa {
CortexA9,
RiscV32,
@@ -35,8 +19,8 @@ pub enum Error {
}
impl From<&'static str> for Error {
fn from(desc: &'static str) -> Error {
Error::Parsing(desc)
fn from(desc: &'static str) -> Self {
Self::Parsing(desc)
}
}
@@ -83,13 +67,17 @@ impl Relocatable for Elf32_Rela {
// `__aeabi_unwind_cpp_pr0` in exception-throwing code.
const R_TYPE_NONE: u8 = 0;
// Number of program header
const ELF_PHNUM: usize = 5;
const DEBUG_PHNUM: usize = 0; // Debug image is not loadable
struct SectionRecord<'a> {
shdr: Elf32_Shdr,
name: &'a str,
data: Vec<u8>,
}
fn read_unaligned<T: Copy>(data: &[u8], offset: usize) -> Option<T> {
const fn read_unaligned<T: Copy>(data: &[u8], offset: usize) -> Option<T> {
if data.len() < offset + mem::size_of::<T>() {
None
} else {
@@ -99,7 +87,7 @@ fn read_unaligned<T: Copy>(data: &[u8], offset: usize) -> Option<T> {
}
#[must_use]
pub fn get_ref_slice<T: Copy>(data: &[u8], offset: usize, len: usize) -> Option<&[T]> {
pub const fn get_ref_slice<T: Copy>(data: &[u8], offset: usize, len: usize) -> Option<&[T]> {
if data.len() < offset + mem::size_of::<T>() * len {
None
} else {
@@ -113,11 +101,11 @@ fn from_struct_slice<T>(struct_vec: &[T]) -> Vec<u8> {
unsafe { slice::from_raw_parts(ptr.cast(), mem::size_of_val(struct_vec)) }.to_vec()
}
fn to_struct_slice<T>(bytes: &[u8]) -> &[T] {
const fn to_struct_slice<T>(bytes: &[u8]) -> &[T] {
unsafe { slice::from_raw_parts(bytes.as_ptr().cast(), bytes.len() / mem::size_of::<T>()) }
}
fn to_struct_mut_slice<T>(bytes: &mut [u8]) -> &mut [T] {
const fn to_struct_mut_slice<T>(bytes: &mut [u8]) -> &mut [T] {
unsafe {
slice::from_raw_parts_mut(bytes.as_mut_ptr().cast(), bytes.len() / mem::size_of::<T>())
}
@@ -165,29 +153,114 @@ struct SymbolTableReader<'a> {
impl SymbolTableReader<'_> {
pub fn find_index_by_name(&self, sym_name: &[u8]) -> Option<usize> {
self.symtab.iter().position(|sym| {
if let Ok(dynsym_name) = name_starting_at_slice(self.strtab, sym.st_name as usize) {
sym_name == dynsym_name
} else {
false
}
name_starting_at_slice(self.strtab, sym.st_name as usize)
.is_ok_and(|dynsym_name| sym_name == dynsym_name)
})
}
}
struct Image {
data: mem::MaybeUninit<Vec<u8>>,
load_offset: u32,
shdr_offset: u32,
}
impl Image {
pub const fn register<'a>(
&mut self,
shdr: &Elf32_Shdr,
sh_name_str: &'a str,
data: Vec<u8>,
) -> SectionRecord<'a> {
let mut elf_shdr = *shdr;
// Maintain alignment requirement specified in sh_addralign
let align = shdr.sh_addralign;
let load_padding = (align - (self.load_offset % align)) % align;
let image_padding = (align - (self.shdr_offset % align)) % align;
let section_load_offset = if (shdr.sh_flags as usize & SHF_ALLOC) == SHF_ALLOC {
self.load_offset + load_padding
} else {
0
};
let section_image_offset = self.shdr_offset + image_padding;
elf_shdr.sh_addr = section_load_offset;
elf_shdr.sh_offset = section_image_offset;
if (shdr.sh_flags as usize & SHF_ALLOC) == SHF_ALLOC {
self.load_offset = section_load_offset + shdr.sh_size;
}
if shdr.sh_type as usize != SHT_NOBITS {
self.shdr_offset = section_image_offset + shdr.sh_size;
}
SectionRecord { shdr: elf_shdr, name: sh_name_str, data }
}
pub fn finalize(&mut self, shdr_recs: &[SectionRecord]) {
let header_alignment = 4;
let mut final_len = self.shdr_offset;
final_len += (header_alignment - (final_len % header_alignment)) % header_alignment;
self.shdr_offset = final_len;
let final_len = final_len as usize + shdr_recs.len() * mem::size_of::<Elf32_Shdr>();
self.data.write({
// This leaks the memory, but ...
let mut temp_vec: mem::ManuallyDrop<Vec<u32>> =
mem::ManuallyDrop::new(Vec::with_capacity(final_len / header_alignment as usize));
// This new vector picks up the memory.
// The new vector is responsible to free the memory instead.
unsafe { Vec::from_raw_parts(temp_vec.as_mut_ptr().cast(), final_len, final_len) }
});
let owned_buffer = unsafe { self.data.assume_init_mut() };
let mut shdr_ptr =
unsafe { owned_buffer.as_mut_ptr().add(self.shdr_offset as usize).cast() };
for shdr_rec in shdr_recs {
if shdr_rec.shdr.sh_type as usize != SHT_NOBITS {
owned_buffer[shdr_rec.shdr.sh_offset as usize..][..shdr_rec.data.len()]
.clone_from_slice(&shdr_rec.data);
}
unsafe {
*shdr_ptr = shdr_rec.shdr;
shdr_ptr = shdr_ptr.add(1);
}
}
}
pub fn get_mut_ref<T>(&mut self, offset: usize, len: usize) -> &mut [T] {
unsafe {
let borrowed_buf = self.data.assume_init_mut();
assert!(borrowed_buf.len() >= offset + len, "out of bound access to image buffer");
slice::from_raw_parts_mut(borrowed_buf.as_mut_ptr().add(offset).cast(), len)
}
}
pub const fn take(self) -> Vec<u8> {
unsafe { self.data.assume_init() }
}
}
pub struct Linker<'a> {
isa: Isa,
symtab: &'a [Elf32_Sym],
strtab: &'a [u8],
elf_shdrs: Vec<SectionRecord<'a>>,
debug_shdrs: Vec<SectionRecord<'a>>,
section_map: HashMap<usize, usize>,
image: Vec<u8>,
load_offset: u32,
image_offset: u32,
debug_section_map: HashMap<usize, usize>,
dyn_lib_image: Image,
debug_image: Image,
rela_dyn_relas: Vec<Elf32_Rela>,
}
impl<'a> Linker<'a> {
fn get_dynamic_symbol_table(&self) -> Result<SymbolTableReader, Error> {
fn get_dynamic_symbol_table(&self) -> Result<SymbolTableReader<'_>, Error> {
let dynsym_rec = get_section_by_name!(self, ".dynsym")
.ok_or("cannot make SymbolTableReader using .dynsym")?;
Ok(SymbolTableReader {
@@ -197,38 +270,24 @@ impl<'a> Linker<'a> {
}
fn load_section(&mut self, shdr: &Elf32_Shdr, sh_name_str: &'a str, data: Vec<u8>) -> usize {
let mut elf_shdr = *shdr;
// Maintain alignment requirement specified in sh_addralign
let align = shdr.sh_addralign;
let load_padding = (align - (self.load_offset % align)) % align;
let image_padding = (align - (self.image_offset % align)) % align;
let section_load_offset = if (shdr.sh_flags as usize & SHF_ALLOC) == SHF_ALLOC {
self.load_offset + load_padding
} else {
0
};
let section_image_offset = self.image_offset + image_padding;
elf_shdr.sh_addr = section_load_offset;
elf_shdr.sh_offset = section_image_offset;
self.elf_shdrs.push(SectionRecord { shdr: elf_shdr, name: sh_name_str, data });
if (shdr.sh_flags as usize & SHF_ALLOC) == SHF_ALLOC {
self.load_offset = section_load_offset + shdr.sh_size;
}
if shdr.sh_type as usize != SHT_NOBITS {
self.image_offset = section_image_offset + shdr.sh_size;
}
self.elf_shdrs.push(self.dyn_lib_image.register(shdr, sh_name_str, data));
self.elf_shdrs.len() - 1
}
fn load_debug_section(
&mut self,
shdr: &Elf32_Shdr,
sh_name_str: &'a str,
data: Vec<u8>,
) -> usize {
self.debug_shdrs.push(self.debug_image.register(shdr, sh_name_str, data));
self.debug_shdrs.len() - 1
}
// Perform relocation according to the relocation entries
// Only symbols that support relative addressing would be resolved
// This is because the loading address is not known yet
fn resolve_relocatables<R: Relocatable>(
fn resolve_relocatables<R: Relocatable + std::fmt::Debug>(
&mut self,
relocs: &[R],
target_section: Elf32_Word,
@@ -242,6 +301,42 @@ impl<'a> Linker<'a> {
pub relocate: Option<Box<RelocateFn>>,
}
macro_rules! get_referred_section {
($target_section: ident, $return_cb: expr) => {
if let Some(shdr_index) = self.section_map.get(&($target_section as usize)) {
Ok($return_cb(true, &self.elf_shdrs[*shdr_index], *shdr_index))
} else if let Some(debug_index) =
self.debug_section_map.get(&($target_section as usize))
{
Ok($return_cb(false, &self.debug_shdrs[*debug_index], *debug_index))
} else {
Err(Error::Parsing("Cannot find section with matching sh_index"))
}
};
}
let (loaded, target_index) =
get_referred_section!(target_section, |loaded, _, idx| (loaded, idx)).unwrap();
macro_rules! get_shdr_attr {
($index: ident, $attr: ident) => {
if loaded { &self.elf_shdrs[$index].$attr } else { &self.debug_shdrs[$index].$attr }
};
}
macro_rules! get_mut_shdr_attr {
($index: ident, $attr: ident) => {
if loaded {
&mut self.elf_shdrs[$index].$attr
} else {
&mut self.debug_shdrs[$index].$attr
}
};
}
let target_section_alloc =
get_shdr_attr!(target_index, shdr).sh_flags as usize & SHF_ALLOC == SHF_ALLOC;
for reloc in relocs {
if reloc.type_info() == R_TYPE_NONE {
continue;
@@ -261,28 +356,16 @@ impl<'a> Linker<'a> {
match sym.st_shndx {
SHN_UNDEF => Err(Error::Lookup("undefined symbol")),
SHN_ABS => Ok(sym.st_value),
sec_ind => self
.section_map
.get(&(sec_ind as usize))
.map(|&elf_sec_ind: &usize| {
// Unlike the code in artiq libdyld, the image offset value is
// irrelevant in this case.
// The .elf dynamic library can be linked to an arbitrary address
// within the kernel address space
self.elf_shdrs[elf_sec_ind].shdr.sh_offset as Elf32_Word
+ sym.st_value
})
.ok_or(Error::Parsing("section not mapped to the ELF file")),
// Section index may refer to either a debug section or a loaded section
// A debug relocation can still refer to a loaded section for symbol resolution
sec_ind => get_referred_section!(sec_ind, |_, rec: &SectionRecord, _| rec
.shdr
.sh_addr
as Elf32_Word
+ sym.st_value),
}
};
let get_target_section_index = || -> Result<usize, Error> {
self.section_map
.get(&(target_section as usize))
.copied()
.ok_or(Error::Parsing("Cannot find section with matching sh_index"))
};
let classify = |reloc: &R, sym_option: Option<&Elf32_Sym>| -> Option<RelocInfo<R>> {
let defined_val = sym_option.is_none_or(|sym| {
sym.st_shndx != SHN_UNDEF || ELF32_ST_BIND(sym.st_info) == STB_LOCAL
@@ -315,7 +398,13 @@ impl<'a> Linker<'a> {
defined_val,
indirect_reloc: None,
pc_relative: false,
relocate: None,
relocate: if target_section_alloc {
None
} else {
Some(Box::new(|target_word, value| {
LittleEndian::write_u32(target_word, value);
}))
},
}),
_ => None,
},
@@ -412,7 +501,13 @@ impl<'a> Linker<'a> {
defined_val,
indirect_reloc: None,
pc_relative: false,
relocate: None,
relocate: if target_section_alloc {
None
} else {
Some(Box::new(|target_word, value| {
LittleEndian::write_u32(target_word, value);
}))
},
}),
R_RISCV_SET32 => Some(RelocInfo {
@@ -532,28 +627,24 @@ impl<'a> Linker<'a> {
let reloc_info =
classify(reloc, sym).ok_or(Error::Parsing("unsupported relocation"))?;
let target_index = get_target_section_index()?;
let target_sec_off = self.elf_shdrs[target_index].shdr.sh_offset;
let target_sec_off = get_shdr_attr!(target_index, shdr).sh_offset;
if reloc_info.defined_val {
let (sym_addr, rela_off) = {
let (refed_sym, refed_reloc) =
if let Some(indirect_reloc) = reloc_info.indirect_reloc {
(Some(&self.symtab[indirect_reloc.sym_info() as usize]), indirect_reloc)
} else {
(sym, reloc)
};
(resolve_symbol_addr(refed_sym)?, target_sec_off + refed_reloc.offset())
};
let (refed_sym, refed_reloc) =
if let Some(indirect_reloc) = reloc_info.indirect_reloc {
(Some(&self.symtab[indirect_reloc.sym_info() as usize]), indirect_reloc)
} else {
(sym, reloc)
};
let sym_addr = resolve_symbol_addr(refed_sym)?;
let rela_off = target_sec_off + refed_reloc.offset();
let target_sec_image = &mut self.elf_shdrs[target_index].data;
let value = if reloc_info.pc_relative {
sym_addr
.wrapping_sub(rela_off)
.wrapping_add(reloc.addend(target_sec_image) as Elf32_Word)
} else {
sym_addr.wrapping_add(reloc.addend(target_sec_image) as Elf32_Word)
};
let target_sec_image = get_mut_shdr_attr!(target_index, data);
let mut value =
sym_addr.wrapping_add(refed_reloc.addend(target_sec_image) as Elf32_Word);
if reloc_info.pc_relative {
value = value.wrapping_sub(rela_off);
}
if let Some(relocate) = reloc_info.relocate {
let target_word = &mut target_sec_image[reloc.offset() as usize..];
@@ -572,7 +663,7 @@ impl<'a> Linker<'a> {
});
}
} else {
let target_sec_image = &self.elf_shdrs[target_index].data;
let target_sec_image = &get_shdr_attr!(target_index, data);
let sym_name = name_starting_at_slice(self.strtab, sym.unwrap().st_name as usize)
.map_err(|_| "cannot read symbol name from original .strtab")?;
@@ -588,6 +679,7 @@ impl<'a> Linker<'a> {
});
}
}
Ok(())
}
@@ -629,7 +721,7 @@ impl<'a> Linker<'a> {
Ok(())
}
pub fn ld(data: &'a [u8]) -> Result<Vec<u8>, Error> {
pub fn ld(data: &'a [u8]) -> Result<(Vec<u8>, Vec<u8>), Error> {
fn allocate_rela_dyn<R: Relocatable>(
linker: &Linker,
relocs: &[R],
@@ -756,16 +848,52 @@ impl<'a> Linker<'a> {
name: "",
data: vec![0; 0],
}];
let elf_sh_data_off = mem::size_of::<Elf32_Ehdr>() + mem::size_of::<Elf32_Phdr>() * 5;
// Debug object also needs a starting NULL record
let debug_shdrs = vec![SectionRecord {
shdr: Elf32_Shdr {
sh_name: 0,
sh_type: 0,
sh_flags: 0,
sh_addr: 0,
sh_offset: 0,
sh_size: 0,
sh_link: 0,
sh_info: 0,
sh_addralign: 0,
sh_entsize: 0,
},
name: "",
data: vec![0; 0],
}];
let elf_sh_data_off =
mem::size_of::<Elf32_Ehdr>() + mem::size_of::<Elf32_Phdr>() * ELF_PHNUM;
let debug_sh_data_off =
mem::size_of::<Elf32_Ehdr>() + mem::size_of::<Elf32_Phdr>() * DEBUG_PHNUM;
// Image of the linked dynamic library, to be formalized incrementally
// just as the section table eventually does
let image: Vec<u8> = vec![0; elf_sh_data_off];
let dyn_lib_image = Image {
data: mem::MaybeUninit::uninit(),
load_offset: elf_sh_data_off as u32,
shdr_offset: elf_sh_data_off as u32,
};
// Debug image
// Only to the symbolizer for traceback generation
let debug_image = Image {
data: mem::MaybeUninit::uninit(),
load_offset: debug_sh_data_off as u32,
shdr_offset: debug_sh_data_off as u32,
};
// Section relocation table
// A map of the original index of copied sections to the new sections
let section_map = HashMap::new();
// Section relocation table, but for debug sections
let debug_section_map = HashMap::new();
// Vector of relocation entries in .rela.dyn
let rela_dyn_relas = Vec::new();
@@ -774,10 +902,11 @@ impl<'a> Linker<'a> {
symtab,
strtab,
elf_shdrs,
debug_shdrs,
section_map,
image,
load_offset: elf_sh_data_off as u32,
image_offset: elf_sh_data_off as u32,
debug_section_map,
dyn_lib_image,
debug_image,
rela_dyn_relas,
};
@@ -1362,19 +1491,28 @@ impl<'a> Linker<'a> {
last_elf_shdr_index as Elf32_Section
);
// Load unallocated PROGBITS sections
// Mainly for debugging symbols
for (i, shdr) in shdrs.iter().enumerate() {
if (shdr.sh_type as usize != SHT_PROGBITS)
|| (shdr.sh_flags as usize & SHF_ALLOC == SHF_ALLOC)
{
continue;
}
let section_name = name_starting_at_slice(strtab, shdr.sh_name as usize)
.map_err(|_| "cannot read section name")?;
let elf_shdrs_index = linker.load_debug_section(
shdr,
str::from_utf8(section_name).unwrap(),
data[shdr.sh_offset as usize..(shdr.sh_offset + shdr.sh_size) as usize].to_vec(),
);
linker.debug_section_map.insert(i, elf_shdrs_index);
}
for shdr in shdrs
.iter()
.filter(|shdr| shdr.sh_type as usize == SHT_RELA || shdr.sh_type as usize == SHT_REL)
{
// If the reloction refers to a section that will not be loaded,
// do not process the relocations. The section will not be loaded
let referred_shdr = shdrs
.get(shdr.sh_info as usize)
.ok_or("relocation is not specified to a valid section number")?;
if (referred_shdr.sh_flags as usize & SHF_ALLOC) != SHF_ALLOC {
continue;
}
reloc_invariant!(shdr, |relocs| linker.resolve_relocatables(relocs, shdr.sh_info))?;
}
@@ -1385,6 +1523,7 @@ impl<'a> Linker<'a> {
let rela_dyn_slice =
to_struct_mut_slice::<Elf32_Rela>(rela_dyn_rec.data.as_mut_slice());
assert_eq!(linker.rela_dyn_relas.iter().len(), rela_dyn_slice.len());
for (i, &rela) in linker.rela_dyn_relas.iter().enumerate() {
rela_dyn_slice[i] = rela;
}
@@ -1417,140 +1556,171 @@ impl<'a> Linker<'a> {
sh_entsize: 0,
};
// Same for the debug sections
let mut debug_shstrtab = Vec::new();
for shdr_rec in &mut linker.debug_shdrs {
let shstrtab_index = debug_shstrtab.len();
debug_shstrtab.extend(shdr_rec.name.as_bytes());
debug_shstrtab.push(0);
shdr_rec.shdr.sh_name = shstrtab_index as Elf32_Word;
}
// Add en entry for .shstrtab
let debug_shstrtab_shdr_sh_name = debug_shstrtab.len();
debug_shstrtab.extend(b".shstrtab");
debug_shstrtab.push(0);
let debug_shstrtab_shdr = Elf32_Shdr {
sh_name: debug_shstrtab_shdr_sh_name as Elf32_Word,
sh_type: SHT_STRTAB as Elf32_Word,
sh_flags: 0,
sh_addr: 0,
sh_offset: 0,
sh_size: debug_shstrtab.len() as Elf32_Word,
sh_link: 0,
sh_info: 0,
sh_addralign: 1,
sh_entsize: 0,
};
let shstrtab_elf_index = linker.load_section(&shstrtab_shdr, ".shstrtab", shstrtab);
let debug_shstrtab_elf_index =
linker.load_debug_section(&debug_shstrtab_shdr, ".shstrtab", debug_shstrtab);
// Edit .eh_frame_hdr content
if linker.isa == Isa::RiscV32 {
linker.implement_eh_frame_hdr()?;
}
// Load all non-NOBITS section data into the image
for rec in &linker.elf_shdrs[1..] {
if rec.shdr.sh_type as usize != SHT_NOBITS {
linker.image.extend(vec![0; (rec.shdr.sh_offset as usize) - linker.image.len()]);
linker.image.extend(&rec.data);
}
}
// Load all section headers to the image
let alignment = (4 - (linker.image.len() % 4)) % 4;
let sec_headers_offset = linker.image.len() + alignment;
linker.image.extend(vec![0; alignment]);
for rec in &linker.elf_shdrs {
let shdr = rec.shdr;
linker.image.extend(unsafe {
slice::from_raw_parts(ptr::addr_of!(shdr).cast(), mem::size_of::<Elf32_Shdr>())
});
}
linker.dyn_lib_image.finalize(&linker.elf_shdrs);
linker.debug_image.finalize(&linker.debug_shdrs);
// Update the PHDRs
let phdr_offset = mem::size_of::<Elf32_Ehdr>();
unsafe {
let phdr_ptr = linker.image.as_mut_ptr().add(phdr_offset).cast();
let phdr_slice = slice::from_raw_parts_mut(phdr_ptr, 5);
// List of program headers:
// 1. ELF headers & program headers
// 2. Read-only sections
// 3. All other A-flag sections
// 4. Dynamic
// 5. EH frame & its header
let header_size = mem::size_of::<Elf32_Ehdr>() + mem::size_of::<Elf32_Phdr>() * 5;
phdr_slice[0] = Elf32_Phdr {
p_type: PT_LOAD,
p_offset: 0,
p_vaddr: 0,
p_paddr: 0,
p_filesz: header_size as Elf32_Word,
p_memsz: header_size as Elf32_Word,
p_flags: PF_R as Elf32_Word,
p_align: 0x1000,
};
let last_ro_shdr = linker.elf_shdrs[first_writable_sec_elf_index - 1].shdr;
let last_ro_addr = last_ro_shdr.sh_offset + last_ro_shdr.sh_size;
let ro_load_size = last_ro_addr - header_size as Elf32_Word;
phdr_slice[1] = Elf32_Phdr {
p_type: PT_LOAD,
p_offset: header_size as Elf32_Off,
p_vaddr: header_size as Elf32_Addr,
p_paddr: header_size as Elf32_Addr,
p_filesz: ro_load_size,
p_memsz: ro_load_size,
p_flags: (PF_R | PF_X) as Elf32_Word,
p_align: 0x1000,
};
let first_w_shdr = linker.elf_shdrs[first_writable_sec_elf_index].shdr;
let first_w_addr = first_w_shdr.sh_offset;
let last_w_shdr = linker.elf_shdrs[last_w_sec_elf_index].shdr;
// According to the specification, regarding PT_LOAD program header when filesz < memsz:
// The ``extra`` bytes are defined to hold the value 0 and to follow the segment's initialized area.
//
// We use this specified behavior to handle NOBITS.
let w_fsize = last_w_shdr.sh_offset + last_w_shdr.sh_size - first_w_addr;
let w_msize = end_load_addr - first_w_addr;
phdr_slice[2] = Elf32_Phdr {
p_type: PT_LOAD,
p_offset: first_w_addr as Elf32_Off,
p_vaddr: first_w_addr as Elf32_Addr,
p_paddr: first_w_addr as Elf32_Addr,
p_filesz: w_fsize,
p_memsz: w_msize,
p_flags: (PF_R | PF_W) as Elf32_Word,
p_align: 0x1000,
};
let dynamic_shdr = linker.elf_shdrs[dynamic_elf_index].shdr;
phdr_slice[3] = Elf32_Phdr {
p_type: PT_DYNAMIC,
p_offset: dynamic_shdr.sh_offset,
p_vaddr: dynamic_shdr.sh_offset,
p_paddr: dynamic_shdr.sh_offset,
p_filesz: dynamic_shdr.sh_size,
p_memsz: dynamic_shdr.sh_size,
p_flags: (PF_R | PF_W) as Elf32_Word,
p_align: 4,
};
let (eh_type, eh_shdr_name) = match linker.isa {
Isa::CortexA9 => (PT_ARM_EXIDX, ".ARM.exidx"),
Isa::RiscV32 => (PT_GNU_EH_FRAME, ".eh_frame_hdr"),
};
let eh_shdr = get_section_by_name!(linker, eh_shdr_name)
.ok_or("cannot read error handling section when finalizing phdrs")?
.shdr;
phdr_slice[4] = Elf32_Phdr {
p_type: eh_type,
p_offset: eh_shdr.sh_offset,
p_vaddr: eh_shdr.sh_offset,
p_paddr: eh_shdr.sh_offset,
p_filesz: eh_shdr.sh_size,
p_memsz: eh_shdr.sh_size,
p_flags: PF_R as Elf32_Word,
p_align: 4,
};
}
let phdr_slice: &mut [Elf32_Phdr] =
linker.dyn_lib_image.get_mut_ref(phdr_offset, ELF_PHNUM);
// List of program headers:
// 1. ELF headers & program headers
// 2. Read-only sections
// 3. All other A-flag sections
// 4. Dynamic
// 5. EH frame & its header
let header_size = mem::size_of::<Elf32_Ehdr>() + mem::size_of::<Elf32_Phdr>() * 5;
phdr_slice[0] = Elf32_Phdr {
p_type: PT_LOAD,
p_offset: 0,
p_vaddr: 0,
p_paddr: 0,
p_filesz: header_size as Elf32_Word,
p_memsz: header_size as Elf32_Word,
p_flags: PF_R as Elf32_Word,
p_align: 0x1000,
};
let last_ro_shdr = linker.elf_shdrs[first_writable_sec_elf_index - 1].shdr;
let last_ro_addr = last_ro_shdr.sh_offset + last_ro_shdr.sh_size;
let ro_load_size = last_ro_addr - header_size as Elf32_Word;
phdr_slice[1] = Elf32_Phdr {
p_type: PT_LOAD,
p_offset: header_size as Elf32_Off,
p_vaddr: header_size as Elf32_Addr,
p_paddr: header_size as Elf32_Addr,
p_filesz: ro_load_size,
p_memsz: ro_load_size,
p_flags: (PF_R | PF_X) as Elf32_Word,
p_align: 0x1000,
};
let first_w_shdr = linker.elf_shdrs[first_writable_sec_elf_index].shdr;
let first_w_addr = first_w_shdr.sh_offset;
let last_w_shdr = linker.elf_shdrs[last_w_sec_elf_index].shdr;
// According to the specification, regarding PT_LOAD program header when filesz < memsz:
// The ``extra`` bytes are defined to hold the value 0 and to follow the segment's initialized area.
//
// We use this specified behavior to handle NOBITS.
let w_fsize = last_w_shdr.sh_offset + last_w_shdr.sh_size - first_w_addr;
let w_msize = end_load_addr - first_w_addr;
phdr_slice[2] = Elf32_Phdr {
p_type: PT_LOAD,
p_offset: first_w_addr as Elf32_Off,
p_vaddr: first_w_addr as Elf32_Addr,
p_paddr: first_w_addr as Elf32_Addr,
p_filesz: w_fsize,
p_memsz: w_msize,
p_flags: (PF_R | PF_W) as Elf32_Word,
p_align: 0x1000,
};
let dynamic_shdr = linker.elf_shdrs[dynamic_elf_index].shdr;
phdr_slice[3] = Elf32_Phdr {
p_type: PT_DYNAMIC,
p_offset: dynamic_shdr.sh_offset,
p_vaddr: dynamic_shdr.sh_offset,
p_paddr: dynamic_shdr.sh_offset,
p_filesz: dynamic_shdr.sh_size,
p_memsz: dynamic_shdr.sh_size,
p_flags: (PF_R | PF_W) as Elf32_Word,
p_align: 4,
};
let (eh_type, eh_shdr_name) = match linker.isa {
Isa::CortexA9 => (PT_ARM_EXIDX, ".ARM.exidx"),
Isa::RiscV32 => (PT_GNU_EH_FRAME, ".eh_frame_hdr"),
};
let eh_shdr = get_section_by_name!(linker, eh_shdr_name)
.ok_or("cannot read error handling section when finalizing phdrs")?
.shdr;
phdr_slice[4] = Elf32_Phdr {
p_type: eh_type,
p_offset: eh_shdr.sh_offset,
p_vaddr: eh_shdr.sh_offset,
p_paddr: eh_shdr.sh_offset,
p_filesz: eh_shdr.sh_size,
p_memsz: eh_shdr.sh_size,
p_flags: PF_R as Elf32_Word,
p_align: 4,
};
// Update the EHDR
let ehdr_ptr = linker.image.as_mut_ptr().cast();
unsafe {
*ehdr_ptr = Elf32_Ehdr {
e_ident: ehdr.e_ident,
e_type: ET_DYN,
e_machine: ehdr.e_machine,
e_version: ehdr.e_version,
e_entry: elf_sh_data_off as Elf32_Addr,
e_phoff: phdr_offset as Elf32_Off,
e_shoff: sec_headers_offset as Elf32_Off,
e_flags: match linker.isa {
Isa::RiscV32 => ehdr.e_flags,
Isa::CortexA9 => ehdr.e_flags | EF_ARM_ABI_FLOAT_HARD as Elf32_Word,
},
e_ehsize: mem::size_of::<Elf32_Ehdr>() as Elf32_Half,
e_phentsize: mem::size_of::<Elf32_Phdr>() as Elf32_Half,
e_phnum: 5,
e_shentsize: mem::size_of::<Elf32_Shdr>() as Elf32_Half,
e_shnum: linker.elf_shdrs.len() as Elf32_Half,
e_shstrndx: shstrtab_elf_index as Elf32_Half,
}
}
let dyn_lib_e_shoff = linker.dyn_lib_image.shdr_offset;
let ehdr_ptr: &mut [Elf32_Ehdr] = linker.dyn_lib_image.get_mut_ref(0, 1);
ehdr_ptr[0] = Elf32_Ehdr {
e_ident: ehdr.e_ident,
e_type: ET_DYN,
e_machine: ehdr.e_machine,
e_version: ehdr.e_version,
e_entry: elf_sh_data_off as Elf32_Addr,
e_phoff: phdr_offset as Elf32_Off,
e_shoff: dyn_lib_e_shoff,
e_flags: match linker.isa {
Isa::RiscV32 => ehdr.e_flags,
Isa::CortexA9 => ehdr.e_flags | EF_ARM_ABI_FLOAT_HARD as Elf32_Word,
},
e_ehsize: mem::size_of::<Elf32_Ehdr>() as Elf32_Half,
e_phentsize: mem::size_of::<Elf32_Phdr>() as Elf32_Half,
e_phnum: ELF_PHNUM as Elf32_Half,
e_shentsize: mem::size_of::<Elf32_Shdr>() as Elf32_Half,
e_shnum: linker.elf_shdrs.len() as Elf32_Half,
e_shstrndx: shstrtab_elf_index as Elf32_Half,
};
Ok(linker.image)
let debug_e_shoff = linker.debug_image.shdr_offset;
let ehdr_ptr: &mut [Elf32_Ehdr] = linker.debug_image.get_mut_ref(0, 1);
ehdr_ptr[0] = Elf32_Ehdr {
e_ident: ehdr.e_ident,
e_type: ET_DYN,
e_machine: ehdr.e_machine,
e_version: ehdr.e_version,
e_entry: debug_sh_data_off as Elf32_Addr,
e_phoff: phdr_offset as Elf32_Off,
e_shoff: debug_e_shoff,
e_flags: match linker.isa {
Isa::RiscV32 => ehdr.e_flags,
Isa::CortexA9 => ehdr.e_flags | EF_ARM_ABI_FLOAT_HARD as Elf32_Word,
},
e_ehsize: mem::size_of::<Elf32_Ehdr>() as Elf32_Half,
e_phentsize: mem::size_of::<Elf32_Phdr>() as Elf32_Half,
e_phnum: DEBUG_PHNUM as Elf32_Half,
e_shentsize: mem::size_of::<Elf32_Shdr>() as Elf32_Half,
e_shnum: linker.debug_shdrs.len() as Elf32_Half,
e_shstrndx: debug_shstrtab_elf_index as Elf32_Half,
};
Ok((linker.dyn_lib_image.take(), linker.debug_image.take()))
}
}

View File

@@ -0,0 +1,832 @@
#![allow(nonstandard_style)]
use std::{cmp, collections::HashMap, fmt::Error, mem, ptr, slice};
use crate::dwarf::DwarfReader;
use crate::include::dwarf::*;
use crate::include::elf::*;
#[derive(Debug, PartialEq, Clone)]
enum NameRef {
Concrete(&'static str),
Abstract(usize),
Unknown,
}
#[derive(Debug, Clone)]
pub struct CallRecord {
name: NameRef,
pub address: Option<u32>,
pub line: u32,
pub column: u32,
pub file: &'static str,
pub dir: Option<&'static str>,
}
impl CallRecord {
#[must_use]
pub fn get_name(&self) -> &'static str {
if let NameRef::Concrete(name) = self.name {
name
} else {
panic!("name reference should always be finalzied when read")
}
}
}
#[derive(Clone, Debug)]
struct AbbreviationEntry {
tag: DW_TAG,
has_child: DW_CHILDREN,
attribute_specs: Vec<(DW_AT, DW_FORM)>,
}
impl AbbreviationEntry {
// Returns (abbreviation code, entry)
pub fn new(reader: &mut DwarfReader) -> Option<(u64, Self)> {
let abbrev_code = reader.read_uleb128();
if abbrev_code != 0 {
let mut entry = Self { tag: 0, has_child: 0, attribute_specs: Vec::new() };
entry.tag = reader.read_uleb128();
entry.has_child = reader.read_u8();
let mut attr = (reader.read_uleb128(), reader.read_uleb128());
while attr != (0, 0) {
entry.attribute_specs.push(attr);
attr = (reader.read_uleb128(), reader.read_uleb128());
}
Some((abbrev_code, entry))
} else {
None
}
}
}
// Special section names
#[allow(clippy::struct_field_names)]
struct DebugInfoReader {
debug_info: &'static [u8],
debug_abbrev: &'static [u8],
debug_line: &'static [u8],
debug_ranges: &'static [u8],
debug_str: &'static [u8],
}
impl DebugInfoReader {
// -debug_infos are centralized into a section
// We should not have partial compile unit
pub fn search(&self, pc: u32) -> Vec<CallRecord> {
self.search_compilation_units(DwarfReader::new(self.debug_info, 0), pc)
}
fn parse_die_attributes(
&self,
reader: &mut DwarfReader,
attr_specs: &Vec<(DW_AT, DW_FORM)>,
file_ptrs: &[(&'static str, Option<&'static str>)],
pc: u32,
start_addr: u32,
cu_origin: Option<u32>,
) -> (bool, u32, NameRef, Option<u32>, CallRecord) {
// Compute PC range
let mut in_range = false;
let mut low_pc: Option<u32> = None;
let mut high_pc_relative = false;
let mut high_pc: Option<u32> = None;
let mut stmt_list_offset: u32 = 0;
let mut name_ref: NameRef = NameRef::Unknown;
let mut call_record = CallRecord {
// This is different from the `name_ref` variable
// The call site's name is only resolvable by parent DIE.
name: NameRef::Unknown,
address: None,
line: 0,
column: 0,
file: "",
dir: None,
};
for (attr_name, attr_form) in attr_specs {
match *attr_name {
DW_AT_low_pc => {
assert_eq!(
*attr_form, DW_FORM_addr,
"DW_AT_low_pc should be specified by an address"
);
low_pc = Some(reader.read_form_addr());
}
DW_AT_high_pc => match *attr_form {
DW_FORM_addr => {
high_pc = Some(reader.read_form_addr());
}
DW_FORM_data1 | DW_FORM_data2 | DW_FORM_data4 | DW_FORM_data8
| DW_FORM_sdata | DW_FORM_udata => {
high_pc_relative = true;
high_pc = Some(reader.read_form_constant(*attr_form) as u32);
}
_ => panic!("DW_AT_high_pc value should either be an address or a constant"),
},
DW_AT_ranges => {
assert_eq!(
*attr_form, DW_FORM_sec_offset,
"DW_AT_ranges should be specified by an address"
);
let debug_ranges_offset = reader.read_form_addr() as usize;
let mut range_reader =
DwarfReader::new(&self.debug_ranges[debug_ranges_offset..], 0);
let mut begin_offset = range_reader.read_u32();
let mut end_offset = range_reader.read_u32();
while (begin_offset, end_offset) != (0, 0) {
if (begin_offset..end_offset).contains(&(pc - start_addr)) {
in_range = true;
break;
}
begin_offset = range_reader.read_u32();
end_offset = range_reader.read_u32();
}
}
DW_AT_stmt_list => {
assert_eq!(
*attr_form, DW_FORM_sec_offset,
"DW_AT_ranges should be specified by an address"
);
stmt_list_offset = reader.read_form_addr();
}
DW_AT_name => match *attr_form {
DW_FORM_string => {
name_ref = unsafe {
NameRef::Concrete(mem::transmute::<&'_ str, &'static str>(
reader.read_str(),
))
};
}
DW_FORM_strp => {
let debug_str_offset = reader.read_form_addr() as usize;
let str_head = &self.debug_str[debug_str_offset..];
let str_len = str_head
.iter()
.position(|byte| *byte == 0)
.expect("string should be null terminated");
name_ref = unsafe {
NameRef::Concrete(str::from_utf8_unchecked(&str_head[..str_len]))
};
}
_ => panic!("name should be a string"),
},
// Inlined procedure may only include an abstract reference to another DIE
// See Section 7.5.4 Attribute Encodings for reference type specifications
// This replaces DW_AT_name, so we fetch DW_AT_name from that referred entry
DW_AT_abstract_origin => {
let referred_die_addr = match *attr_form {
DW_FORM_ref1 | DW_FORM_ref2 | DW_FORM_ref4 | DW_FORM_ref8
| DW_FORM_ref_udata
if cu_origin.is_some() =>
{
let cu_relative_addr = reader.read_form_reference(*attr_form) as usize;
cu_origin.unwrap() as usize + cu_relative_addr
}
DW_FORM_ref_addr => reader.read_form_addr() as usize,
_ => unreachable!(
"DW_AT_abstract_origin expects an apparopriate pointer type to a DIE"
),
};
name_ref = NameRef::Abstract(referred_die_addr);
}
DW_AT_call_file => {
// call_record.file_idx = reader.read_form_constant(*attr_form) as u32;
let file_idx = reader.read_form_constant(*attr_form) as usize;
(call_record.file, call_record.dir) = file_ptrs[file_idx - 1];
}
DW_AT_call_line => {
call_record.line = reader.read_form_constant(*attr_form) as u32;
}
DW_AT_call_column => {
call_record.column = reader.read_form_constant(*attr_form) as u32;
}
_ => {
// Unrecognized attributes
// They are valid, but we cannot process them
reader.skip_form(*attr_form);
}
}
}
// Determine if pc is within range
let die_relevant = in_range || {
low_pc.is_some_and(|low_pc| {
let high_pc = high_pc.map_or_else(
|| low_pc + 4,
|high_pc| if high_pc_relative { low_pc + high_pc } else { high_pc },
);
(low_pc..high_pc).contains(&pc)
})
};
(die_relevant, stmt_list_offset, name_ref, low_pc, call_record)
}
fn skip_die_attributes(reader: &mut DwarfReader, attr_specs: &Vec<(DW_AT, DW_FORM)>) {
for (_attr_name, attr_form) in attr_specs {
reader.skip_form(*attr_form);
}
}
fn search_compilation_units(&self, mut reader: DwarfReader, pc: u32) -> Vec<CallRecord> {
while !reader.slice.is_empty() {
let cu_origin = reader.virt_addr;
// 7.5.1.1 Compilation Unit Header
let unit_length = reader.read_u32();
let mut next_reader = reader.clone();
next_reader.offset(unit_length);
assert_eq!(reader.read_u16(), 4, "expected DWARF version 4");
let debug_abbrev_offset = reader.read_u32() as usize;
assert_eq!(reader.read_u8(), 4, "only 32-bit system is supported");
let abbrev_table = self.parse_abbrev(debug_abbrev_offset);
// Parse the actual DW_TAG_compile_unit, skip the partially parsed unit if irrelevant
let compile_unit_abbrev_code = reader.read_uleb128();
let abbrev_entry = abbrev_table.get(&compile_unit_abbrev_code).expect(
"all non-zero abbreviation code should be resolvable by the abbreviation table",
);
assert_eq!(
abbrev_entry.tag, DW_TAG_compile_unit,
"a normal compile unit should start with DW_TAG_compile_unit"
);
// FIXME: The start address is called base address in the docs
//
// The base address of a compilation unit is defined as the value of the DW_AT_low_pc attribute,
// if present; otherwise, it is undefined
let (cu_die_relevant, cu_stmt_list_offset, _cu_name_ref, start_addr, _cu_call_record) =
self.parse_die_attributes(
&mut reader,
&abbrev_entry.attribute_specs,
&[],
pc,
0,
Some(cu_origin),
);
if cu_die_relevant {
let (immediate_call_record, file_ptrs) =
self.parse_line_info(cu_stmt_list_offset as usize, pc);
let mut call_sites = vec![immediate_call_record];
assert!(
self.search_dies(
&mut reader,
&abbrev_table,
&file_ptrs,
pc,
start_addr.unwrap(),
&mut call_sites,
cu_origin,
),
"exhausted all debugging information entries (DIE)"
);
// Resolve name references
for rec in &mut call_sites {
while let NameRef::Abstract(die_offset) = rec.name {
let referred_die = &self.debug_info[die_offset..];
let mut referred_reader = DwarfReader::new(referred_die, 0);
let abbrev_code = referred_reader.read_uleb128();
let abbrev_entry = abbrev_table.get(&abbrev_code).expect("all non-zero abbreviation code should be resolvable by the abbreviation table");
let (_die_relevant, _stmt_list_offset, name_ref, _low_pc, _call_record) =
self.parse_die_attributes(
&mut referred_reader,
&abbrev_entry.attribute_specs,
&file_ptrs,
pc,
start_addr.unwrap(),
None,
);
rec.name = name_ref;
}
assert!(
!(rec.name == NameRef::Unknown),
"found a call site with an unknown subroutine name"
);
}
return call_sites;
}
reader = next_reader;
}
unreachable!("no relevant debugging info to pc: {}", pc);
}
#[allow(clippy::too_many_arguments)]
#[must_use]
fn search_dies(
&self,
reader: &mut DwarfReader,
abbrev_table: &HashMap<u64, AbbreviationEntry>,
file_ptrs: &Vec<(&'static str, Option<&'static str>)>,
pc: u32,
start_addr: u32,
call_sites: &mut Vec<CallRecord>,
cu_origin: u32,
) -> bool {
let mut abbrev_code = reader.read_uleb128();
while abbrev_code != 0 {
let abbrev_entry = abbrev_table.get(&abbrev_code).expect(
"all non-zero abbreviation code should be resolvable by the abbreviation table",
);
let (die_relevant, _stmt_list_offset, name_ref, _start_addr, call_record) = self
.parse_die_attributes(
reader,
&abbrev_entry.attribute_specs,
file_ptrs,
pc,
start_addr,
Some(cu_origin),
);
if die_relevant {
if abbrev_entry.has_child != 0 {
// It is possible to not find more appropriate entires in any subtrees
// e.g. exception is raised in a function that is also a caller to some other functions
let _ = self.search_dies(
reader,
abbrev_table,
file_ptrs,
pc,
start_addr,
call_sites,
cu_origin,
);
}
let last_name_ref = call_sites.last_mut().unwrap();
if last_name_ref.name == NameRef::Unknown {
last_name_ref.name = name_ref;
}
// Only inlined subprogram has a call record
if abbrev_entry.tag == DW_TAG_inlined_subroutine {
call_sites.push(call_record);
}
return true;
}
if abbrev_entry.has_child != 0 {
// Moving directly to its sibling is impossible if there are children
// The entries are arranged with prefix ordering
Self::skip_dies(reader, abbrev_table);
}
abbrev_code = reader.read_uleb128();
}
false
}
fn skip_dies(reader: &mut DwarfReader, abbrev_table: &HashMap<u64, AbbreviationEntry>) {
let mut abbrev_code = reader.read_uleb128();
while abbrev_code != 0 {
let abbrev_entry = abbrev_table.get(&abbrev_code).expect(
"all non-zero abbreviation code should be resolvable by the abbreviation table",
);
Self::skip_die_attributes(reader, &abbrev_entry.attribute_specs);
if abbrev_entry.has_child != 0 {
// Moving directly to its sibling is impossible if there are children
// The entries are arranged with prefix ordering
Self::skip_dies(reader, abbrev_table);
}
abbrev_code = reader.read_uleb128();
}
}
fn parse_abbrev(&self, abbrev_offset: usize) -> HashMap<u64, AbbreviationEntry> {
let mut reader = DwarfReader::new(&self.debug_abbrev[abbrev_offset..], 0);
let mut table: HashMap<u64, AbbreviationEntry> = HashMap::new();
while let Some((code, entry)) = AbbreviationEntry::new(&mut reader) {
table.insert(code, entry);
}
table
}
fn parse_line_info(
&self,
stmt_list_offset: usize,
pc: u32,
) -> (CallRecord, Vec<(&'static str, Option<&'static str>)>) {
let mut header_reader = DwarfReader::new(&self.debug_line[stmt_list_offset..], 0);
// header begins
let _unit_length = header_reader.read_u32(); // eventually consume all
assert_eq!(header_reader.read_u16(), 4, "expected DWARF version 4 for .debug_line");
let header_length = header_reader.read_u32();
// Create a line program reader
// So we can find the encoded call site first, then decode it directly
let mut program_reader = header_reader.clone();
program_reader.offset(header_length);
let minimum_instruction_length = u32::from(header_reader.read_u8());
let maximum_operations_per_instruction = u32::from(header_reader.read_u8());
let default_is_stmt = header_reader.read_u8();
let line_base = header_reader.read_i8();
let line_range = header_reader.read_u8();
let opcode_base = header_reader.read_u8();
// var-len array
// We simply take the reference to the array and index it as necessary
// However, the array starts with index = 1.
// There are only opcode_base - 1 elements since opcode 0 is reserved
// for the preamble of extended opcode.
let standard_opcode_lengths = unsafe {
mem::transmute::<&'_ [u8], &'static [u8]>(
header_reader.read_slice(opcode_base as usize - 1),
)
};
{
// Standard opcodes, if defined, must match the standard arities
const MAX_STANDARD_OPCODES: usize = 12;
const EXPECTED_ARITIES: [u8; MAX_STANDARD_OPCODES] =
[0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1];
let standard_opcode_num = cmp::min(MAX_STANDARD_OPCODES, opcode_base as usize - 1);
assert_eq!(
standard_opcode_lengths[standard_opcode_num..],
EXPECTED_ARITIES[standard_opcode_num..]
);
}
let mut include_directories: Vec<&'static str> = vec![];
let mut dir_str = header_reader.read_str();
while !dir_str.is_empty() {
include_directories.push(unsafe { mem::transmute::<&'_ str, &'static str>(dir_str) });
dir_str = header_reader.read_str();
}
let mut file_ptrs: Vec<(&'static str, Option<&'static str>)> = vec![];
loop {
let path_name =
unsafe { mem::transmute::<&'_ str, &'static str>(header_reader.read_str()) };
if path_name.is_empty() {
break;
}
let dir_index = header_reader.read_uleb128();
let dir_name = if dir_index == 0 {
None
} else {
Some(include_directories[dir_index as usize - 1])
};
let _last_modified = header_reader.read_uleb128();
let _file_len = header_reader.read_uleb128();
file_ptrs.push((path_name, dir_name));
}
// We execute the line program first
// TODO: Register state can be trimmed for non-VLIW targets
// To clippy: This struct only makes sense here.
// Why would I care about line program register when not parsing it?
// Starting a new scope does not sound clearer in my opinion.
#[allow(clippy::items_after_statements, clippy::struct_excessive_bools)]
#[derive(Copy, Clone)]
struct Register {
address: u32,
op_index: u32,
file: u32,
line: u32,
column: u32,
is_stmt: bool,
basic_block: bool,
end_sequence: bool,
prologue_end: bool,
epilogue_begin: bool,
isa: u32,
discriminator: u32,
}
// The provisionally accepted entry
// last_entry.address < (pc - start_addr)
let mut last_entry = Register {
address: 0,
op_index: 0,
file: 1,
line: 1,
column: 0,
is_stmt: default_is_stmt != 0,
basic_block: false,
end_sequence: false,
prologue_end: false,
epilogue_begin: false,
isa: 0,
discriminator: 0,
};
// From spec: the address of each entries in the matrix is strictly increasing
// It means that we don't need to maintain a var-len matrix, keeping
// adjacent entries is sufficient.
let mut curr_entry = last_entry;
// Macro is avoidable by passing in the predetermined constants
// But I would not like to pollute the arg list
macro_rules! advance_address {
($operation_advance: ident) => {
let unadjusted_op_index = curr_entry.op_index + $operation_advance;
curr_entry.address += minimum_instruction_length
* (unadjusted_op_index / maximum_operations_per_instruction);
curr_entry.op_index += unadjusted_op_index % maximum_operations_per_instruction;
};
}
macro_rules! handle_special_opcode {
($opcode: expr, $update_line: literal) => {
let adjusted_opcode = $opcode - opcode_base;
let operation_advance = u32::from(adjusted_opcode / line_range);
advance_address!(operation_advance);
if $update_line {
curr_entry.line = curr_entry
.line
.wrapping_add_signed(i32::from(line_base))
.wrapping_add(u32::from(adjusted_opcode % line_range));
}
};
}
macro_rules! push_row_or_break {
($last_entry: expr, $curr_entry: expr, $pc: expr) => {
// This is the moment that we know if we have found the entry
// Indicated by the address overtaking pc
if (last_entry.address..curr_entry.address).contains($pc) {
break;
}
// Update last entry otherwise
last_entry = curr_entry;
};
}
while !curr_entry.end_sequence {
// Decode opcode
let opcode = program_reader.read_u8();
match opcode {
// Extended opcode
0 => {
let insn_len = program_reader.read_uleb128();
let mut extended_opcode_reader = program_reader.clone();
extended_opcode_reader.slice =
&extended_opcode_reader.slice[..insn_len as usize];
program_reader.offset(insn_len as u32);
let extended_opcode = extended_opcode_reader.read_u8();
match extended_opcode {
DW_LNE_end_sequence => {
// No operands
curr_entry.end_sequence = true;
push_row_or_break!(last_entry, curr_entry, &pc);
}
DW_LNE_set_address => {
let address = extended_opcode_reader.read_u32();
curr_entry.address = address;
curr_entry.op_index = 0;
}
DW_LNE_define_file => {
let path_name = unsafe {
mem::transmute::<&'_ str, &'static str>(
extended_opcode_reader.read_str(),
)
};
let dir_index = extended_opcode_reader.read_uleb128();
let dir_name = if dir_index == 0 {
None
} else {
Some(include_directories[dir_index as usize - 1])
};
let _last_modified = extended_opcode_reader.read_uleb128();
let _file_len = extended_opcode_reader.read_uleb128();
file_ptrs.push((path_name, dir_name));
}
DW_LNE_set_discriminator => {
let discriminator = extended_opcode_reader.read_uleb128() as u32;
curr_entry.discriminator = discriminator;
}
// It is posslbe that other user defined instruction appears
// But, we do not support them, nor do we know what to do about them
// Hence we simply skip these instructions
_ => (),
}
}
// Standard opcode
DW_LNS_copy if DW_LNS_copy < opcode_base => {
// No operands
push_row_or_break!(last_entry, curr_entry, &pc);
}
DW_LNS_advance_pc if DW_LNS_advance_pc < opcode_base => {
let operation_advance = program_reader.read_uleb128() as u32;
advance_address!(operation_advance);
}
DW_LNS_advance_line if DW_LNS_advance_line < opcode_base => {
let line_advance = program_reader.read_sleb128();
curr_entry.line = curr_entry.line.wrapping_add_signed(line_advance as i32);
}
DW_LNS_set_file if DW_LNS_set_file < opcode_base => {
let file = program_reader.read_uleb128();
curr_entry.file = file as u32;
}
DW_LNS_set_column if DW_LNS_set_column < opcode_base => {
let column = program_reader.read_uleb128();
curr_entry.column = column as u32;
}
DW_LNS_negate_stmt if DW_LNS_negate_stmt < opcode_base => {
// No operands
curr_entry.is_stmt = !curr_entry.is_stmt;
}
DW_LNS_set_basic_block if DW_LNS_set_basic_block < opcode_base => {
// No operands
curr_entry.basic_block = true;
}
DW_LNS_const_add_pc if DW_LNS_const_add_pc < opcode_base => {
// No operands
handle_special_opcode!(0xff, false);
}
DW_LNS_fixed_advance_pc if DW_LNS_fixed_advance_pc < opcode_base => {
let address_advance = u32::from(program_reader.read_u16());
curr_entry.address += address_advance;
curr_entry.op_index = 0;
}
DW_LNS_set_prologue_end if DW_LNS_set_prologue_end < opcode_base => {
// No operands
curr_entry.prologue_end = true;
}
DW_LNS_set_epilogue_begin if DW_LNS_set_epilogue_begin < opcode_base => {
// No operands
curr_entry.epilogue_begin = true;
}
DW_LNS_set_isa if DW_LNS_set_isa < opcode_base => {
let isa = program_reader.read_uleb128() as u32;
curr_entry.isa = isa;
}
// vendor specific extensions
_ if (0..opcode_base).contains(&opcode) => {
// Skip an appropriate amount of operands
for _ in 0..standard_opcode_lengths[opcode as usize] {
program_reader.read_uleb128();
}
}
// Special opcode
_ if (opcode_base..=0xff).contains(&opcode) => {
handle_special_opcode!(opcode, true);
curr_entry.basic_block = false;
curr_entry.prologue_end = false;
curr_entry.epilogue_begin = false;
curr_entry.discriminator = 0;
push_row_or_break!(last_entry, curr_entry, &pc);
}
_ => unreachable!(),
}
}
(
CallRecord {
name: NameRef::Unknown,
address: Some(pc),
line: last_entry.line,
file: file_ptrs[last_entry.file as usize - 1].0,
dir: file_ptrs[last_entry.file as usize - 1].1,
column: last_entry.column,
},
file_ptrs,
)
}
}
#[must_use]
pub fn symbolize(elf_byte: &[u8], pc_list: Vec<u32>) -> Vec<CallRecord> {
let elf_ptr = elf_byte.as_ptr();
let ehdr = unsafe { ptr::read::<Elf32_Ehdr>(elf_ptr.cast()) };
let shdrs = unsafe {
slice::from_raw_parts::<Elf32_Shdr>(
elf_ptr.add(ehdr.e_shoff as usize).cast(),
ehdr.e_shnum as usize,
)
};
// Read .strtab
let strtab = {
let strtab_shdr = shdrs[ehdr.e_shstrndx as usize];
unsafe {
slice::from_raw_parts::<u8>(
elf_ptr.add(strtab_shdr.sh_offset as usize).cast(),
strtab_shdr.sh_size as usize,
)
}
};
let get_str = |str_offset: usize| -> Result<&[u8], Error> {
let strtab_trimmed = &strtab[str_offset..];
let str_len = strtab_trimmed
.iter()
.position(|&x| x == 0)
.expect("string in string table should be null-terminated");
Ok(&strtab_trimmed[..str_len])
};
// Retrieve these debug sections from the .elf file
// .debug_info
// .debug_line
// .debug_abbrev
// .debug_ranges (may)
// .debug_str
let debug_info = {
let debug_info_shdr = shdrs
.iter()
.find(|shdr| get_str(shdr.sh_name as usize).unwrap() == b".debug_info")
.expect("missing .debug_info section");
unsafe {
slice::from_raw_parts::<u8>(
elf_ptr.add(debug_info_shdr.sh_offset as usize),
debug_info_shdr.sh_size as usize,
)
}
};
let debug_line = {
let debug_line_shdr = shdrs
.iter()
.find(|shdr| get_str(shdr.sh_name as usize).unwrap() == b".debug_line")
.expect("missing .debug_line section");
unsafe {
slice::from_raw_parts::<u8>(
elf_ptr.add(debug_line_shdr.sh_offset as usize),
debug_line_shdr.sh_size as usize,
)
}
};
let debug_abbrev = {
let debug_abbrev_shdr = shdrs
.iter()
.find(|shdr| get_str(shdr.sh_name as usize).unwrap() == b".debug_abbrev")
.expect("missing .debug_abbrev section");
unsafe {
slice::from_raw_parts::<u8>(
elf_ptr.add(debug_abbrev_shdr.sh_offset as usize),
debug_abbrev_shdr.sh_size as usize,
)
}
};
let debug_ranges = {
// It is tempting to just cast as slice of u32, but
// .debug_* sections do not have the concept of alignment
//
// TODO: Coerce nac3ld to align the debug sections.
shdrs
.iter()
.find(|shdr| get_str(shdr.sh_name as usize).unwrap() == b".debug_ranges")
.map_or_else(
|| unsafe { slice::from_raw_parts::<u8>(elf_ptr.cast(), 0) },
|debug_ranges_shdr| unsafe {
slice::from_raw_parts::<u8>(
elf_ptr.add(debug_ranges_shdr.sh_offset as usize).cast(),
debug_ranges_shdr.sh_size as usize,
)
},
)
};
let debug_str = {
let debug_str_shdr = shdrs
.iter()
.find(|shdr| get_str(shdr.sh_name as usize).unwrap() == b".debug_str")
.expect("missing .debug_str section");
unsafe {
slice::from_raw_parts::<u8>(
elf_ptr.add(debug_str_shdr.sh_offset as usize).cast(),
debug_str_shdr.sh_size as usize,
)
}
};
let info_reader =
DebugInfoReader { debug_info, debug_abbrev, debug_line, debug_ranges, debug_str };
let mut call_sites: Vec<CallRecord> = vec![];
for pc in pc_list {
call_sites.append(&mut info_reader.search(pc));
}
call_sites
}

View File

@@ -12,7 +12,7 @@ no-escape-analysis = []
[dependencies]
itertools = "0.14"
crossbeam = "0.8"
indexmap = "2.9"
indexmap = "2.12"
parking_lot = "0.12"
nac3core_derive = { path = "nac3core_derive", optional = true }
nac3parser = { path = "../nac3parser" }
@@ -20,15 +20,15 @@ strum = "0.27"
strum_macros = "0.27"
[dependencies.inkwell]
version = "0.6.0"
version = "0.8"
default-features = false
features = ["llvm16-0-prefer-dynamic", "target-x86", "target-arm", "target-riscv", "no-libffi-linking", "typed-pointers"]
[dev-dependencies]
test-case = "3.3"
indoc = "2.0"
insta = "1.43"
insta = "1.44"
function_name = "0.3"
[build-dependencies]
regex = "1.11"
regex = "1.12"

View File

@@ -1,3 +1,7 @@
#![deny(clippy::all)]
#![warn(clippy::cargo, clippy::pedantic, clippy::nursery)]
#![allow(clippy::cargo_common_metadata)]
use std::{
env,
fs::File,
@@ -9,6 +13,10 @@ use std::{
use regex::Regex;
fn main() {
// For debugging
// Doing `DEBUG_DUMP_IRRT=1 cargo build -p nac3core` dumps the LLVM IR generated
const DEBUG_DUMP_IRRT: &str = "DEBUG_DUMP_IRRT";
let out_dir = env::var("OUT_DIR").unwrap();
let out_dir = Path::new(&out_dir);
let irrt_dir = Path::new("irrt");
@@ -88,9 +96,6 @@ fn main() {
.unwrap()
.replace_all(&filtered_output, "");
// For debugging
// Doing `DEBUG_DUMP_IRRT=1 cargo build -p nac3core` dumps the LLVM IR generated
const DEBUG_DUMP_IRRT: &str = "DEBUG_DUMP_IRRT";
println!("cargo:rerun-if-env-changed={DEBUG_DUMP_IRRT}");
if env::var(DEBUG_DUMP_IRRT).is_ok() {
let mut file = File::create(out_dir.join("irrt.ll")).unwrap();

View File

@@ -1,6 +1,7 @@
#pragma once
#include "irrt/debug.hpp"
#include "irrt/exception.hpp"
#include "irrt/int_types.hpp"
namespace {
@@ -44,4 +45,4 @@ using namespace range;
SliceIndex __nac3_range_slice_len(const SliceIndex start, const SliceIndex end, const SliceIndex step) {
return len(start, end, step);
}
}
}

View File

@@ -6,13 +6,8 @@ edition = "2024"
[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"

View File

@@ -1,165 +1,10 @@
#![deny(future_incompatible, let_underscore, nonstandard_style, clippy::all)]
#![warn(clippy::pedantic)]
#![warn(clippy::pedantic, clippy::nursery)]
use proc_macro::TokenStream;
use proc_macro_error::{abort, proc_macro_error};
use quote::quote;
use syn::{
Data, DataStruct, Expr, ExprField, ExprMethodCall, ExprPath, GenericArgument, Ident, LitStr,
Path, PathArguments, Type, TypePath, parse_macro_input, spanned::Spanned,
};
/// 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;
}