kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 64afd07c476c8aaca38a511fdc9177b830390630
parent 0f9b477324a56ef9ee19c54b592b703c5d5796e6
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 15 Jun 2026 12:56:01 -0700

cg: clean cutover of the type and type-id system

Replace the segmented KitCgTypeId scheme and NONE-as-void convention with a
direct-index type table per doc/plan/CG-TYPES.md.

Core (src/cg/type.c, include/kit/cg.h):
- Builtin ids are 1..KIT_CG_BUILTIN_COUNT; user ids index directly after them;
  no segment/bias decode.
- Void is always the void builtin; KIT_CG_TYPE_NONE means invalid/absent only.
  Function results are always valid type ids (kit_cg_func_result_has_value /
  kit_cg_type_is_void replace result.type == NONE checks).
- Two-phase nominal records: kit_cg_type_record_decl + _record_complete (once);
  pointers to incomplete records are legal; by-value fields must be sized.
- Layout/size/align come from the ABI path and cache on the type entry.
- ptr/array/func interning via id-keyed maps; exact vs storage alias queries.

C frontend (lang/c):
- Records lower via declare-at-tag / complete-at-definition; the old
  record-context pointer->void* erasure and incomplete->void lowering are gone.
- A pointer carries its pointee's nominal (decl) id and never forces
  completion, so a forward-declared struct used only through pointers is never
  completed; completion is triggered only by by-value use (local, by-value
  field/param/result, array element, sizeof, member access).
- In-flight-record guard breaks any by-value re-entry cycle (invalid C) with a
  clean error instead of a stack overflow.
- Diagnose "array has incomplete element type" at array-declarator formation
  (6.7.6.2p1), matching clang across member/param/extern/local/typedef; VLAs
  are not affected.

Toy and wasm frontends use the void builtin for void results.

Backend/library fixes uncovered by the void = builtin invariant:
- c_target c_emit_ret: detect non-void via cg_type_is_void, not != NONE, so a
  void return emits `return;` rather than __builtin_unreachable().
- cg/atomic.c: the void __atomic_store_8 libcall result is the void builtin,
  not NONE (kit_cg_type_func now rejects a NONE result; reachable on 32-bit
  targets doing 8-byte atomics).

Validation: test-parse (DREJC + R + err), test-toy, test-cg-api, test-opt,
test-aa64-inline, test-debug, test-dwarf, smoke x64/rv64 all green.

Diffstat:
Adoc/plan/CG-TYPES.md | 586+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdoc/plan/README.md | 1+
Minclude/kit/cg.h | 237+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Mlang/c/parse/cg_adapter.c | 42++++++++++++++++++++----------------------
Mlang/c/parse/parse_stmt.c | 4++--
Mlang/c/parse/parse_type.c | 18++++++++++++++++++
Mlang/c/type/type.c | 154+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Mlang/toy/builtins.c | 4++--
Mlang/toy/decls.c | 63++++++++++++++++++++++++++++++++++++++-------------------------
Mlang/toy/internal.h | 7++++++-
Mlang/toy/parser.c | 6+++---
Mlang/toy/parser_core.c | 30+++++++++++++++++++++++++++---
Mlang/toy/types.c | 34++++++++++++++--------------------
Mlang/wasm/cg.c | 149++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Mmk/test.mk | 3+++
Mmk/test_unit.mk | 3++-
Msrc/abi/abi.c | 8++++++--
Msrc/arch/c_target/c_emit.c | 7+++++--
Msrc/arch/riscv/emu.c | 7+++----
Msrc/cg/arith.c | 225++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Msrc/cg/asm.c | 24+++++++++++++++++++++---
Msrc/cg/atomic.c | 35++++++++++++++++++++++++++++++++++-
Msrc/cg/call.c | 54++++++++++++++++++++++++++++++++++++++++++++++++------
Asrc/cg/const.c | 552+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/cg/control.c | 392+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Msrc/cg/fold.c | 108++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/cg/internal.h | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Msrc/cg/local.c | 7+++++--
Msrc/cg/memory.c | 294+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Msrc/cg/session.c | 10++++++++--
Msrc/cg/type.c | 538+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Msrc/cg/type.h | 36+++++++++++++++---------------------
Msrc/cg/value.c | 141+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Msrc/cg/wide.c | 36+++++++++++++++++++++++++++++++-----
Msrc/emu/cpu.c | 6++----
Msrc/opt/pass_combine.c | 11+++--------
Msrc/opt/pass_lower.c | 18+++++++++++-------
Mtest/api/abi_classify_test.c | 129++++++++++++++++++++++++++++++++++++++++---------------------------------------
Atest/api/cg_const_test.c | 223+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/api/cg_control_test.c | 39+++++++++++++++++++++------------------
Mtest/api/cg_fp_cmp_test.c | 7+++----
Mtest/api/cg_switch_test.c | 324++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mtest/api/cg_type_test.c | 292+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mtest/arch/inline_public_test.h | 9++++++---
Mtest/cg/ir_recorder_test.c | 7+++----
Mtest/cg/native_direct_target_test.c | 13+++++++++----
Mtest/cg/strength_reduce_test.c | 4+---
Mtest/interp/interp_smoke_test.c | 8+++-----
Mtest/opt/cg_ir_lower_test.c | 4+---
Mtest/opt/tiny_inline_test.c | 7+++----
50 files changed, 4205 insertions(+), 864 deletions(-)

diff --git a/doc/plan/CG-TYPES.md b/doc/plan/CG-TYPES.md @@ -0,0 +1,586 @@ +# CG type and type-id system clean cutover + +**Goal.** Replace the current `KitCgTypeId` implementation and public type API +with a simpler, explicit, and ABI-correct type table. Keep the good part: a +small compiler-owned integer handle that can be stored cheaply throughout IR, +ABI records, frontends, and backends. Change what that handle means, how void +and aliases are represented, where layout facts are computed, and how nominal +records are completed. + +This is a **clean-break rewrite, no backcompat**. The old constructors, query +semantics, `KIT_CG_TYPE_NONE`-as-void convention, segmented id encoding, and +frontend workarounds are removed as the cutover lands. There is no compatibility +adapter and no byte-identity gate. + +--- + +## 1. Current State + +The public API uses an opaque `uint32_t KitCgTypeId`: + +- `0` is `KIT_CG_TYPE_NONE`. +- Builtins are encoded in a reserved id segment. +- User types are stored in a segmented vector with a biased segment id. +- Pointers, arrays, and functions are structurally interned. +- Aliases, records, and enums allocate fresh ids. + +Internally, `src/cg/type.c` owns `CgApiState`: + +- `CgType builtins[KIT_CG_BUILTIN_COUNT]` +- `CgApiTypes types` +- structural indexes for pointer, array, and function types +- per-entry caches for alias resolution, predicates, type class, and ABI layout + +The C frontend lowers `Type*` to `KitCgTypeId` lazily and caches ids on C type +nodes and parser stack slots. Incomplete records are handled outside CG: a +temporarily incomplete record lowers to `void`, and record-field/function-param +pointer lowering collapses pointees to `void*` so layout can proceed without a +true incomplete nominal record id. + +The ABI layer reads `CgType` entries and caches target layout/classification +facts by type id. Some storage facts live eagerly on `CgType` (`size`, `align`); +some target facts live in `TargetABI`; record layout is currently computed by +the CG type constructor and re-exposed by the ABI cache. + +--- + +## 2. Problems + +### 2.1 `NONE` Has Too Many Meanings + +`KIT_CG_TYPE_NONE` currently means: + +- invalid type id +- absent function result, i.e. a void return +- statement scope or no value in some control APIs + +There is also a real void builtin. Every caller must remember which API treats +void as a real type and which treats `NONE` as absence. That is a correctness +hazard and a permanent source of branchy special cases. + +### 2.2 Alias Semantics Are Implicit + +Some queries inspect the exact id. Others recurse through aliases. Examples: + +- integer and float width follow aliases +- predicates follow aliases through a cached descriptor +- pointer/array/function field queries are mostly exact-kind queries +- many internal call sites remember to call `api_unalias_type` + +The result is not one type model; it is a collection of local conventions. + +### 2.3 ID Decode Complexity Does Not Buy Enough + +The segmented builtin/user encoding makes every id lookup decode segment bits. +That helped keep builtin ids stable and user pointers stable, but the hot path +now contains extra decode logic and special builtin handling. A direct table +index keeps the same public handle shape with less machinery. + +### 2.4 Layout Authority Is Split + +`CgType` eagerly stores `size` and `align`, while `TargetABI` is supposed to be +the single authority for target-dependent layout and calling convention facts. +This is especially fragile for: + +- 32-bit ABIs and non-default pointer alignment +- Windows and other ABI-specific scalar/aggregate rules +- bit-field layout differences +- `va_list` and other target-shaped builtins + +### 2.5 Incomplete Nominal Records Are Not Modeled + +The C frontend needs true identities for incomplete records so these are legal: + +```c +struct S; +struct T { struct S *p; }; +struct S { int x; }; +``` + +Today CG has no incomplete record declaration/completion API, so the frontend +works around it by erasing some pointee identity to `void*` while lowering +record fields and function parameters. That preserves layout but loses type +identity and pushes a CG responsibility into the frontend. + +### 2.6 Queries Are Too Fragmented + +The public API exposes many one-off queries (`kind`, `size`, `align`, +`ptr_pointee`, `array_elem`, `func_param`, `record_field`, etc.). That is easy +to call but expensive and ambiguous at scale: each query chooses exact vs +resolved behavior separately, and hot call sites decode the same id repeatedly. + +--- + +## 3. Design Decisions + +| # | Decision | Choice | +|---|---|---| +| D1 | Public handle shape | Keep `typedef uint32_t KitCgTypeId`; `0` is invalid/none only | +| D2 | Void representation | Void is always the real void builtin type id; `NONE` never means void | +| D3 | ID layout | Builtin ids are `1..KIT_CG_BUILTIN_COUNT`; user ids are `KIT_CG_BUILTIN_COUNT + 1 + index` | +| D4 | Cross-compiler stability | Type ids are stable only within one `KitCompiler`; never serialized as semantic identities | +| D5 | Structural interning | Pointer, array, and function constructors intern by exact constructor shape | +| D6 | Nominal identity | Records, enums, and aliases keep fresh source-facing ids | +| D7 | Alias behavior | Exact queries inspect aliases; storage/semantic queries resolve aliases explicitly | +| D8 | Function void result | Function result type is always a valid type id; use the void builtin for no value | +| D9 | Layout authority | `TargetABI` computes all size/align/scalar layout facts; type entries only cache ABI results | +| D10 | Records | Records are nominal and two-phase: declare first, complete once | +| D11 | Incomplete use | Pointers to incomplete records are legal; sizing, arrays, fields, by-value params/results, locals, and memory accesses require complete/sized types | +| D12 | Caches | Caches live on the owning type entry or ABI context and are filled only after the facts they depend on are final | +| D13 | Implementation style | C11, no VLAs, no global type state; everything hangs off `KitCompiler`/`TargetABI` | + +--- + +## 4. New Public Shape + +### 4.1 IDs and Builtins + +The id remains opaque, but implementation decode is direct: + +```c +#define KIT_CG_TYPE_NONE 0u + +/* implementation rule, not a public promise beyond nonzero/stable per compiler: + * builtin id = 1 + KitCgBuiltinType + * user id = 1 + KIT_CG_BUILTIN_COUNT + user_index + */ +``` + +The public builtin query becomes singular. The whole-table query is removed +unless a real caller needs it after the cutover. + +```c +KIT_API KitCgTypeId kit_cg_type_builtin(KitCompiler*, KitCgBuiltinType); +``` + +### 4.2 Function Signatures + +`KitCgFuncResult.type` is always valid. A void function uses the void builtin. + +```c +typedef struct KitCgFuncResult { + KitCgTypeId type; /* valid; void builtin means no value */ + KitCgAbiAttrs attrs; +} KitCgFuncResult; + +typedef struct KitCgFuncSig { + KitCgFuncResult result; + const KitCgFuncParam* params; + uint32_t nparams; + KitCgCallConv call_conv; + bool abi_variadic; +} KitCgFuncSig; +``` + +The helper predicate is explicit: + +```c +KIT_API int kit_cg_type_is_void(KitCompiler*, KitCgTypeId); +KIT_API int kit_cg_func_result_has_value(KitCompiler*, KitCgFuncResult); +``` + +No caller tests `result.type == KIT_CG_TYPE_NONE` after this cutover. + +### 4.3 Exact and Storage Queries + +Exact identity and storage identity are separate operations. + +```c +KIT_API KitCgTypeId kit_cg_type_resolve_alias(KitCompiler*, KitCgTypeId); +KIT_API int kit_cg_type_same_storage(KitCompiler*, KitCgTypeId, + KitCgTypeId); +``` + +Rules: + +- `kit_cg_type_kind` is exact. An alias id reports `KIT_CG_TYPE_ALIAS`. +- `kit_cg_type_storage_info` resolves aliases before reporting layout/scalar + facts. +- `kit_cg_type_same_storage` recursively compares alias-resolved storage + structure where needed, so `ptr(alias(i32))` and `ptr(i32)` can be storage + equivalent even if their exact ids differ. + +### 4.4 Descriptor Query + +Add one structured query for hot callers and keep narrow helpers only where they +remain clearly useful. + +```c +typedef enum KitCgTypeFlag { + KIT_CG_TYPEF_COMPLETE = 1u << 0, + KIT_CG_TYPEF_SIZED = 1u << 1, + KIT_CG_TYPEF_BUILTIN = 1u << 2, + KIT_CG_TYPEF_NOMINAL = 1u << 3, +} KitCgTypeFlag; + +typedef enum KitCgStorageKind { + KIT_CG_STORAGE_VOID, + KIT_CG_STORAGE_BOOL, + KIT_CG_STORAGE_INT, + KIT_CG_STORAGE_FLOAT, + KIT_CG_STORAGE_PTR, + KIT_CG_STORAGE_AGGREGATE, +} KitCgStorageKind; + +typedef struct KitCgTypeLayout { + uint64_t size; + uint32_t align; + uint16_t scalar_width; + uint8_t storage_kind; /* KitCgStorageKind */ + uint8_t valid; +} KitCgTypeLayout; + +typedef struct KitCgTypeInfo { + KitCgTypeId id; + KitCgTypeId storage_id; /* alias-resolved id, or id */ + KitCgTypeKind kind; /* exact kind */ + uint32_t flags; /* KitCgTypeFlag */ + KitCgTypeLayout layout; /* valid only for sized/storage queries */ +} KitCgTypeInfo; + +KIT_API KitStatus kit_cg_type_info(KitCompiler*, KitCgTypeId, + KitCgTypeInfo*); +``` + +This descriptor is intentionally shallow. Shape-specific data still comes from +shape-specific APIs or internal entry access: + +- pointer: pointee + address space +- array: element + count +- function: result/params/call convention +- record: field count and field descriptors +- enum: base and values +- alias: name and base + +### 4.5 Record Declaration and Completion + +Records are true nominal objects. + +```c +KIT_API KitCgTypeId kit_cg_type_record_decl(KitCompiler*, KitSym tag, + int is_union); + +KIT_API KitStatus kit_cg_type_record_complete(KitCompiler*, KitCgTypeId record, + const KitCgRecordDesc*); + +KIT_API int kit_cg_type_is_complete(KitCompiler*, KitCgTypeId); +``` + +Convenience construction stays possible, but it is just declare + complete: + +```c +KIT_API KitCgTypeId kit_cg_type_record(KitCompiler*, const KitCgRecordDesc*); +``` + +Completion rules: + +- A record can be completed exactly once. +- Field types must be valid. +- A by-value field must be sized and complete. +- A pointer field may point to an incomplete record. +- A record cannot contain itself by value. +- Layout caches for the record are invalid until completion succeeds. + +The C frontend no longer lowers record-context pointers to `void*`. + +### 4.6 Array Counts + +The public array count remains `uint64_t`. The structural key must use the full +64-bit count or reject out-of-range counts with a documented status. Do not +truncate the key. Prefer full-width support unless an object/backend limit +requires a specific diagnostic. + +```c +KIT_API KitCgTypeId kit_cg_type_array(KitCompiler*, KitCgTypeId elem, + uint64_t count); +``` + +--- + +## 5. Internal Architecture + +### 5.1 Type Table + +Use one compiler-owned table. Builtins occupy the first nonzero ids; user entries +append after them. + +```c +typedef struct CgTypeEntry { + KitCgTypeKind kind; + uint32_t flags; + + KitCgTypeId id; + KitCgTypeId storage_id; /* alias terminal once known */ + + uint8_t pred_bits; + uint8_t pred_valid; + uint8_t class_bits; + uint8_t class_valid; + uint8_t layout_valid; + + KitCgTypeLayout layout; + + union { + CgBuiltinType builtin; + CgPtrType ptr; + CgArrayType array; + CgFuncType func; + CgRecordType record; + CgEnumType enum_; + CgAliasType alias; + }; +} CgTypeEntry; + +typedef struct CgTypeTable { + Heap* heap; + CgTypeEntries entries; + CgPtrMap ptr_index; + CgArrayMap array_index; + CgFuncMap func_index; +} CgTypeTable; +``` + +The table can still use a segmented vector if stable entry pointers are useful, +but indexes are direct: `id - 1` maps to an entry. Hash indexes store ids, not +entry pointers, so the implementation is free to move entries later if that +becomes desirable. + +### 5.2 Structural Indexes + +Pointer key: + +```c +{ pointee_id, address_space } +``` + +Array key: + +```c +{ elem_id, uint64_t count } +``` + +Function key: + +```c +{ result type+attrs, params type+attrs[], call_conv, abi_variadic } +``` + +The key is exact-id based. Alias preservation is therefore possible for source +facing APIs and generated C/debug output. Storage equivalence is a separate +query and must not depend on exact id equality. + +### 5.3 Layout Cache + +`CgTypeEntry.layout` is a cache of the ABI-computed layout, not an independent +source of truth. + +Flow: + +```text +kit_cg_type_size/align/info + -> cg_type_require_layout + -> TargetABI layout hook / shared ABI layout + -> cache on CgTypeEntry after success +``` + +Builtins can be initialized with target facts, but they still follow the same +layout contract. Target-shaped builtins such as `vararg_state` are computed +through the ABI layer. + +Record completion asks the ABI/layout authority to compute source-facing record +layout before marking the record complete. If a future ABI has different +bit-field layout rules, it plugs in at this boundary rather than after the +record is already committed. + +### 5.4 Predicates and Classes + +Predicate bits and codegen class bits remain valuable, but they should be +computed from one storage descriptor: + +```text +exact id -> resolve alias -> storage/layout descriptor -> pred/class bits +``` + +That avoids one-off predicate functions each re-decoding the id and each +choosing its own alias behavior. + +--- + +## 6. Frontend Impact + +### 6.1 C Frontend + +The C frontend should map source types to CG types as follows: + +- C scalar type -> builtin storage id +- C pointer type -> pointer to the exact lowered pointee id +- C function type -> function id with a real void result type when appropriate +- C record declaration -> `kit_cg_type_record_decl` +- C record definition -> `kit_cg_type_record_complete` +- C enum -> enum nominal id with integer base +- C typedef -> source alias id only if CG/debug/C-backend output needs the + source-facing name; storage semantics must use alias resolution + +The current incomplete-record workaround is deleted. The C type pool can cache +the declared record id as soon as the tag exists, then complete the same id when +the definition is parsed. + +### 6.2 Toy Frontend + +Toy mostly uses CG storage types directly. It should switch to: + +- real void result type in function signatures +- explicit storage/equivalence queries where it currently relies on exact + `kit_cg_type_kind` +- record construction through the new declare/complete convenience API + +### 6.3 C Backend and Debug Info + +The C backend and debug producer are the main consumers that care about exact +source-facing ids. They should use exact queries for names and aliases, and +storage queries for codegen legality. + +--- + +## 7. Implementation Plan + +### Phase 1: Lock the New Contract in Tests + +Add focused `test/api/cg_type_test.c` cases that fail under the old model: + +- void function result uses the void builtin, never `NONE` +- invalid ids stay invalid and never masquerade as void +- alias exact kind is `KIT_CG_TYPE_ALIAS` +- alias storage info reports the base storage kind/layout +- `kit_cg_type_same_storage` succeeds for alias-equivalent storage shapes +- record declaration creates an incomplete nominal id +- pointer to incomplete record is legal +- array of incomplete record is rejected +- record completion fills fields/layout and cannot run twice +- recursive by-pointer records are legal +- recursive by-value records are rejected +- full-width array count keys do not collide + +### Phase 2: Replace the Type Table + +Introduce the new direct-index table behind `src/cg/type.c`: + +- builtins are entries `1..KIT_CG_BUILTIN_COUNT` +- user entries append after builtins +- `cg_type_get` becomes direct index validation +- structural maps store ids +- remove segment/bias decode helpers + +Do this before converting all callers, accepting compile failures in the +working tree during the phase. There is no compatibility shim. + +### Phase 3: Cut Over Public API and Call Sites + +Update `include/kit/cg.h` and every caller: + +- replace `kit_cg_builtin_type_id` with `kit_cg_type_builtin` +- replace `result.type == KIT_CG_TYPE_NONE` void checks with void-builtin checks +- update scope/block APIs that currently use `NONE` for statement/no-result so + absence remains explicit and cannot be confused with the void type +- replace ambiguous one-off queries with exact or storage queries +- update function, intrinsic, call, local, memory, and data APIs to require real + type ids where a type is required + +### Phase 4: Move Layout Authority to ABI + +Make `TargetABI` the only layout authority: + +- scalar/builtin layout through ABI helpers +- pointer layout through target spec read by ABI +- array layout from element ABI layout +- record layout during completion through an ABI record-layout hook +- `kit_cg_type_size/align/info` read cached ABI layout, computing on demand + +Delete independent eager `CgType.size` / `CgType.align` ownership. A type entry +may cache layout but does not define it. + +### Phase 5: Add Two-Phase Records and Convert C Lowering + +Convert the C frontend record path: + +- create/get CG record id at tag declaration time +- complete that same id at record definition time +- preserve pointer-to-incomplete-record identity +- delete `TYPE_CG_RECORD_FIELD` pointee erasure to `void*` +- delete incomplete-record temporary lowering to `void` +- keep source diagnostics in the C frontend for illegal incomplete uses + +### Phase 6: Delete Old Caches and Helpers + +Remove now-redundant pieces: + +- segment id constants and decode helpers +- recursive alias helper conventions in random query functions +- `api_unalias_type` call-site scatter where replaced by descriptor queries +- duplicated eager layout caches in ABI/CG if the entry layout cache covers the + use case +- C frontend record memo workarounds that exist only because CG lacked + incomplete nominal records + +--- + +## 8. Validation + +Use targeted runs during the cutover: + +- `make test-cg-api` +- `make test-parse` +- `make test-toy` +- `make test-opt` +- `make test-aa64-inline` +- targeted smoke tests for one native 64-bit target and one non-aa64 backend + touched by layout or call classification + +Broaden only after the API and frontend are compiling cleanly. + +Important scenario coverage: + +- C recursive structs by pointer +- incomplete record misuse diagnostics +- function pointers involving records/enums/aliases +- varargs and `va_list` +- bit-fields, packed records, explicit/max alignment +- enum integer bases +- generated C backend output for aliases/records +- debug info typedef/record naming + +--- + +## 9. Acceptance Criteria + +- `KIT_CG_TYPE_NONE` means invalid/absent only; no function result or value type + uses it to mean void. +- Builtin and user ids decode by direct table index, with no segment/bias scheme. +- Pointer, array, and function interning remains O(1)-average and collision-safe. +- Aliases have explicit exact-vs-storage semantics. +- Records can be declared incomplete and completed once. +- The C frontend no longer erases record-context pointer pointees to `void*`. +- Size/align/scalar facts come from the ABI layout path. +- Existing type hot paths still have cached predicate/class/layout bytes, but + those caches derive from the new descriptor model. +- No global state, no VLAs, and all state hangs off `KitCompiler` or + `TargetABI`. + +--- + +## 10. Open Questions + +1. Should `KIT_CG_TYPE_ALIAS` remain a true public kind, or should source aliases + move entirely into debug/C-backend metadata after the cutover? The plan above + keeps alias ids because they already serve source-facing consumers, but the + storage semantics do not require them. +2. Should function types reject incomplete by-value records at construction, or + permit declaration-like signatures and reject only when defining/calling? + Prefer rejection unless a frontend has a concrete declaration use case. +3. Should `kit_cg_type_info` include shallow shape facts, or stay layout-only + with shape-specific accessors? Prefer shallow layout-only initially to keep + the public struct stable. +4. Should array counts larger than target addressable object size be rejected by + the type constructor or by object/data/local users? Prefer constructor + rejection if every backend agrees on the same maximum; otherwise diagnose at + the first sized-object use. diff --git a/doc/plan/README.md b/doc/plan/README.md @@ -11,6 +11,7 @@ shrinks to whatever remains open. | [RELEASE.md](RELEASE.md) | Cross-cutting initial-release punchlist: release scope, deferred features, and per-subsystem completion/validation items. | — | | [OPTIMIZER.md](OPTIMIZER.md) | Completing the O2 SSA mid-end, expanded inlining, -O0/-O1 performance work, machine register-constraint improvements. | [../OPT.md](../OPT.md) | | [PERF.md](PERF.md) | Making kit the fastest `-O0` C compiler with code as dense as tcc: current compile-speed + code-size standings, how to reproduce them (macOS instruction counts, Linux callgrind, the `make bench-cc` scaling guard), and the ranked forward-looking levers on both axes. | [../ARCH.md](../ARCH.md) | +| [CG-TYPES.md](CG-TYPES.md) | Clean-break redesign of the public CG type/type-id system: direct-index handles, real void types, explicit alias/storage semantics, ABI-owned layout, and two-phase nominal records. | [../CODEGEN.md](../CODEGEN.md), [../INTERFACES.md](../INTERFACES.md) | | [CG-STACK-API.md](CG-STACK-API.md) | Refactoring the parser-to-CG expression seam: keep stack-based liveness, move the C frontend's duplicated `pcg` slot state onto CG-owned stack slots, and add parser-shaped place/load/store operations without backend fusion. | [../FRONTENDS.md](../FRONTENDS.md) | | [LEX-PP-API.md](LEX-PP-API.md) | Redesigning the lexer -> preprocessor -> parser boundary around lean tokens, lazy spelling/location materialization, and slot-based handoffs for a high-performance lexer/preprocessor rewrite without frontend layer fusion. | [../FRONTENDS.md](../FRONTENDS.md) | | [LINKER.md](LINKER.md) | Incremental linking: the file-based object-link redesign and remaining non-ELF format coverage. | [../LINK.md](../LINK.md) | diff --git a/include/kit/cg.h b/include/kit/cg.h @@ -20,6 +20,8 @@ typedef uint32_t KitCgTypeId; #define KIT_CG_LOCAL_NONE 0u #define KIT_CG_SCOPE_NONE 0u #define KIT_CG_SYM_NONE 0u +/* Invalid or absent type sentinel. Void is represented by the real void + * builtin type id, never by KIT_CG_TYPE_NONE. */ #define KIT_CG_TYPE_NONE 0u /* ============================================================ @@ -41,10 +43,6 @@ typedef enum KitCgBuiltinType { KIT_CG_BUILTIN_COUNT, } KitCgBuiltinType; -typedef struct KitCgBuiltinTypes { - KitCgTypeId id[KIT_CG_BUILTIN_COUNT]; -} KitCgBuiltinTypes; - typedef enum KitCgTypeKind { KIT_CG_TYPE_VOID, KIT_CG_TYPE_BOOL, @@ -98,15 +96,16 @@ typedef struct KitCgFuncParam { KitCgAbiAttrs attrs; } KitCgFuncParam; -/* Symmetric with KitCgFuncParam. A function returns a single value or nothing; - * result.type == KIT_CG_TYPE_NONE means void. */ +/* Symmetric with KitCgFuncParam. result.type is always a valid type id; use + * kit_cg_type_builtin(c, KIT_CG_BUILTIN_VOID) for a function that returns no + * value. */ typedef struct KitCgFuncResult { KitCgTypeId type; KitCgAbiAttrs attrs; } KitCgFuncResult; typedef struct KitCgFuncSig { - KitCgFuncResult result; /* result.type == KIT_CG_TYPE_NONE == void */ + KitCgFuncResult result; /* void builtin means no value */ const KitCgFuncParam* params; uint32_t nparams; KitCgCallConv call_conv; @@ -143,39 +142,99 @@ typedef struct KitCgRecordDesc { uint32_t align_override; /* 0 = natural, >0 explicit record alignment */ } KitCgRecordDesc; -/* Builtin ids are stable for the compiler. Pointer, array, and function - * constructors return a stable id for the same shape within one compiler; - * aliases, records, and enums allocate fresh user-facing identities. +typedef enum KitCgTypeFlag { + KIT_CG_TYPEF_COMPLETE = 1u << 0, + KIT_CG_TYPEF_SIZED = 1u << 1, + KIT_CG_TYPEF_BUILTIN = 1u << 2, + KIT_CG_TYPEF_NOMINAL = 1u << 3, +} KitCgTypeFlag; + +typedef enum KitCgStorageKind { + KIT_CG_STORAGE_VOID, + KIT_CG_STORAGE_BOOL, + KIT_CG_STORAGE_INT, + KIT_CG_STORAGE_FLOAT, + KIT_CG_STORAGE_PTR, + KIT_CG_STORAGE_AGGREGATE, +} KitCgStorageKind; + +typedef struct KitCgTypeLayout { + uint64_t size; + uint32_t align; + uint16_t scalar_width; /* bit width for scalar int/float/bool, else 0 */ + uint8_t storage_kind; /* KitCgStorageKind */ + uint8_t valid; /* 0 for invalid, incomplete, or unsized types */ +} KitCgTypeLayout; + +typedef struct KitCgTypeInfo { + KitCgTypeId id; + KitCgTypeId storage_id; /* alias-resolved terminal id, or id */ + KitCgTypeKind kind; /* exact kind */ + uint32_t flags; /* KitCgTypeFlag */ + KitCgTypeLayout layout; /* ABI/storage layout; valid only when sized */ +} KitCgTypeInfo; + +/* Type ids are stable only within one compiler. Pointer, array, and function + * constructors return a stable id for the same exact shape within one compiler; + * aliases, records, and enums allocate fresh user-facing identities. Void is a + * builtin type id; KIT_CG_TYPE_NONE is invalid/absent only. * * Integer types are width-only storage types. Signedness is carried by * operations, comparisons, conversions, and ABI extension attributes. */ -KIT_API KitCgBuiltinTypes kit_cg_builtin_types(KitCompiler*); - -/* Single builtin id. Equivalent to kit_cg_builtin_types(c).id[which] but - * without materializing (and copying by value) the whole table -- the hot - * type-lowering path queries one builtin at a time. */ -KIT_API KitCgTypeId kit_cg_builtin_type_id(KitCompiler*, KitCgBuiltinType which); +KIT_API KitCgTypeId kit_cg_type_builtin(KitCompiler*, KitCgBuiltinType which); /* Interned structural types. Address space 0 is the normal target data - * address space. */ + * address space. Function signatures must use the void builtin type id as the + * result type for a no-value return. */ KIT_API KitCgTypeId kit_cg_type_func(KitCompiler*, KitCgFuncSig sig); KIT_API KitCgTypeId kit_cg_type_ptr(KitCompiler*, KitCgTypeId pointee, uint32_t address_space); KIT_API KitCgTypeId kit_cg_type_array(KitCompiler*, KitCgTypeId elem, uint64_t count); -/* Fresh nominal/source-facing types. Enums use a width-only integer base. */ -KIT_API KitCgTypeId kit_cg_type_alias(KitCompiler*, KitSym name, - KitCgTypeId base); -KIT_API KitCgTypeId kit_cg_type_record(KitCompiler*, KitSym tag, - const KitCgField* fields, - uint32_t nfields); -KIT_API KitCgTypeId kit_cg_type_record_ex(KitCompiler*, const KitCgRecordDesc*); +/* Enums use a width-only integer base. */ KIT_API KitCgTypeId kit_cg_type_enum(KitCompiler*, KitSym tag, KitCgTypeId base, const KitCgEnumValue* values, uint32_t nvalues); -/* Type queries. These report codegen storage, ABI, and target layout facts. */ +/* Records are nominal and may be declared before they are complete. Pointers + * to incomplete records are valid; sizing, arrays, fields, by-value + * params/results, locals, and memory accesses require complete/sized types. + * Completion is one-shot and computes ABI/source layout for the record. */ +KIT_API KitCgTypeId kit_cg_type_record_decl(KitCompiler*, KitSym tag, + int is_union); +KIT_API KitStatus kit_cg_type_record_complete(KitCompiler*, KitCgTypeId record, + const KitCgRecordDesc*); +/* Convenience constructor: declare a fresh nominal record, complete it, and + * return its id, or KIT_CG_TYPE_NONE on failure. */ +static inline KitCgTypeId kit_cg_type_record(KitCompiler* c, + const KitCgRecordDesc* desc) { + KitCgTypeId record; + if (!desc) return KIT_CG_TYPE_NONE; + record = kit_cg_type_record_decl(c, desc->tag, desc->is_union); + if (record == KIT_CG_TYPE_NONE) return KIT_CG_TYPE_NONE; + if (kit_cg_type_record_complete(c, record, desc) != KIT_OK) + return KIT_CG_TYPE_NONE; + return record; +} + +/* Fresh nominal/source-facing types. */ +KIT_API KitCgTypeId kit_cg_type_alias(KitCompiler*, KitSym name, + KitCgTypeId base); + +/* Type queries. + * + * kit_cg_type_kind is exact: alias ids report KIT_CG_TYPE_ALIAS. Layout and + * width queries report ABI/storage facts and resolve aliases. Shape-specific + * queries are exact: call kit_cg_type_resolve_alias first when storage shape is + * desired. */ +KIT_API KitStatus kit_cg_type_info(KitCompiler*, KitCgTypeId, KitCgTypeInfo*); +KIT_API KitCgTypeId kit_cg_type_resolve_alias(KitCompiler*, KitCgTypeId); +KIT_API int kit_cg_type_same_storage(KitCompiler*, KitCgTypeId, KitCgTypeId); +KIT_API int kit_cg_type_is_void(KitCompiler*, KitCgTypeId); +KIT_API int kit_cg_type_is_complete(KitCompiler*, KitCgTypeId); +KIT_API int kit_cg_type_is_sized(KitCompiler*, KitCgTypeId); +KIT_API int kit_cg_func_result_has_value(KitCompiler*, KitCgFuncResult); KIT_API KitCgTypeKind kit_cg_type_kind(KitCompiler*, KitCgTypeId); KIT_API uint64_t kit_cg_type_size(KitCompiler*, KitCgTypeId); KIT_API uint32_t kit_cg_type_align(KitCompiler*, KitCgTypeId); @@ -187,8 +246,7 @@ KIT_API uint32_t kit_cg_type_ptr_address_space(KitCompiler*, KitCgTypeId); KIT_API KitCgTypeId kit_cg_type_array_elem(KitCompiler*, KitCgTypeId); KIT_API uint64_t kit_cg_type_array_count(KitCompiler*, KitCgTypeId); -/* The function's result; result.type == KIT_CG_TYPE_NONE for a void function. - */ +/* The function's result. A no-value result uses the void builtin type id. */ KIT_API KitCgFuncResult kit_cg_type_func_result(KitCompiler*, KitCgTypeId); KIT_API uint32_t kit_cg_type_func_nparams(KitCompiler*, KitCgTypeId); KIT_API KitCgFuncParam kit_cg_type_func_param(KitCompiler*, KitCgTypeId, @@ -196,12 +254,23 @@ KIT_API KitCgFuncParam kit_cg_type_func_param(KitCompiler*, KitCgTypeId, KIT_API KitCgCallConv kit_cg_type_func_call_conv(KitCompiler*, KitCgTypeId); KIT_API int kit_cg_type_func_is_variadic(KitCompiler*, KitCgTypeId); +KIT_API KitSym kit_cg_type_record_tag(KitCompiler*, KitCgTypeId); +KIT_API int kit_cg_type_record_is_union(KitCompiler*, KitCgTypeId); KIT_API uint32_t kit_cg_type_record_nfields(KitCompiler*, KitCgTypeId); /* Returns KIT_OK and fills any non-NULL out parameters on success. */ KIT_API KitStatus kit_cg_type_record_field(KitCompiler*, KitCgTypeId, uint32_t index, KitCgField* out, uint64_t* offset_out); +KIT_API KitSym kit_cg_type_enum_tag(KitCompiler*, KitCgTypeId); +KIT_API KitCgTypeId kit_cg_type_enum_base(KitCompiler*, KitCgTypeId); +KIT_API uint32_t kit_cg_type_enum_nvalues(KitCompiler*, KitCgTypeId); +KIT_API KitStatus kit_cg_type_enum_value(KitCompiler*, KitCgTypeId, + uint32_t index, KitCgEnumValue* out); + +KIT_API KitSym kit_cg_type_alias_name(KitCompiler*, KitCgTypeId); +KIT_API KitCgTypeId kit_cg_type_alias_base(KitCompiler*, KitCgTypeId); + typedef enum KitCgSymbolFeature { KIT_CG_SYMFEAT_WEAK, KIT_CG_SYMFEAT_PROTECTED_VISIBILITY, @@ -295,12 +364,16 @@ typedef enum KitCgMemAccessFlag { /* Access is an externally observable side effect and must not be merged, * removed, or reordered across other volatile accesses. */ KIT_CG_MEM_VOLATILE = 1u << 0, - /* Codegen hint (loads only): the loaded value is a signed narrow integer that - * the frontend will only ever widen by sign-extension, so the backend is free - * to emit a single sign-extending load and the -O0 path may then elide the - * following sign-extend convert. A backend that does not exploit it just emits - * a plain load; semantics are unchanged. Ignored on stores and bit-fields. */ + /* Backend hint (loads only): emit a sign-extending narrow load when + * supported. Frontends normally set KIT_CG_MEM_SOURCE_SIGNED and let + * kit_cg_load derive this for byte/half integer loads; direct callers may + * still set it when they already know the following use is sign-extension. + * Ignored on stores and bit-fields. */ KIT_CG_MEM_SEXT_LOAD = 1u << 1, + /* The source-language object being accessed is a signed integer. KitCgTypeId + * intentionally carries width/category, not C signedness, so this keeps the + * source semantic fact separate from the backend SEXT-load hint above. */ + KIT_CG_MEM_SOURCE_SIGNED = 1u << 2, } KitCgMemAccessFlag; typedef struct KitCgMemAccess { @@ -541,11 +614,11 @@ KIT_API void kit_cg_alloca(KitCg*, uint32_t align, KitCgTypeId result_ptr_type); /* Structured control flow scopes. * - * result_type determines whether the scope carries a value: - * KIT_CG_TYPE_NONE - statement scope, no result. - * otherwise - expression scope; break carries a result. + * kit_cg_scope_begin / kit_cg_block_begin create statement scopes with no + * result values. The *_value variants create single-result expression scopes; + * result_type must be a valid non-void type id. * - * Stack effects when result_type != NONE: + * Stack effects for a value scope: * break(scope) -> pop result, exit scope * break_true(scope) -> stack is [result, bool]; pop bool; if true, pop * result and exit; otherwise pop result @@ -554,14 +627,16 @@ KIT_API void kit_cg_alloca(KitCg*, uint32_t align, KitCgTypeId result_ptr_type); * * continue/continue_true/continue_false never carry a result; they jump to * the loop header, not the scope exit. */ -KIT_API KitCgScope kit_cg_scope_begin(KitCg*, KitCgTypeId result_type); +KIT_API KitCgScope kit_cg_scope_begin(KitCg*); +KIT_API KitCgScope kit_cg_scope_begin_value(KitCg*, KitCgTypeId result_type); /* Like kit_cg_scope_begin but opens a forward-only block: break exits past * the scope; continue is not legal. This is the natural shape for `if`/ * `if-else` (a wrapping block whose break is "skip the rest"), and lets * backends with structured control flow (Wasm) emit `block`/`end` directly * instead of recognizing arbitrary forward-only label-and-jump patterns. */ -KIT_API KitCgScope kit_cg_block_begin(KitCg*, KitCgTypeId result_type); +KIT_API KitCgScope kit_cg_block_begin(KitCg*); +KIT_API KitCgScope kit_cg_block_begin_value(KitCg*, KitCgTypeId result_type); /* Multi-value scopes: a block/loop that carries N params and/or N results * (the value-stack analogue of a Wasm multi-value block). params/results are @@ -575,16 +650,18 @@ KIT_API KitCgScope kit_cg_block_begin(KitCg*, KitCgTypeId result_type); * kit_cg_scope_store_params (which pops `nparams` into those locals, no * jump) followed by a jump to kit_cg_scope_continue_label. * - * kit_cg_scope_begin / kit_cg_block_begin are the nparams==0, nresults<=1 - * special cases and emit an identical op sequence. break, break_true, - * break_false, the continue ops and scope_end all generalize to N values. */ + * kit_cg_scope_begin / kit_cg_block_begin are the nparams==0, nresults==0 + * special cases; the *_value variants are nparams==0, nresults==1. + * Results must be valid non-void type ids. break, break_true, break_false, the + * continue ops and scope_end all generalize to N values. */ typedef struct KitCgScopeSig { const KitCgTypeId* params; /* value-stack bottom -> top */ uint32_t nparams; const KitCgTypeId* results; /* value-stack bottom -> top */ uint32_t nresults; } KitCgScopeSig; -KIT_API KitCgScope kit_cg_scope_begin_sig(KitCg*, const KitCgScopeSig*); /*loop*/ +KIT_API KitCgScope kit_cg_scope_begin_sig(KitCg*, + const KitCgScopeSig*); /*loop*/ KIT_API KitCgScope kit_cg_block_begin_sig(KitCg*, const KitCgScopeSig*); /* Pop `nparams` values into the loop's param locals (bottom->top), without * jumping. The caller then jumps kit_cg_scope_continue_label to take the back @@ -700,13 +777,57 @@ KIT_API void kit_cg_dup2(KitCg*); /* duplicates the top two slots */ KIT_API void kit_cg_swap(KitCg*); KIT_API void kit_cg_drop(KitCg*); KIT_API void kit_cg_rot3(KitCg*); /* [..., a, b, c] -> [..., b, c, a] */ + +typedef struct KitCgSlotInfo { + KitCgTypeId cg_type; + const void* lang_type; + uint16_t lang_flags; +} KitCgSlotInfo; + +/* Inspect and update frontend-owned facts on value-stack slots. CG treats + * lang_type as opaque and lang_flags as frontend-defined bits; stack-producing + * operations clear them, while structural stack operations copy/move them with + * the slot. depth_from_top 0 names TOS. */ +KIT_API KitCgSlotInfo kit_cg_slot_info(KitCg*, uint32_t depth_from_top); +KIT_API void kit_cg_retag_top(KitCg*, const void* lang_type, + uint16_t lang_flags); +KIT_API void kit_cg_retag_at(KitCg*, uint32_t depth_from_top, + const void* lang_type, uint16_t lang_flags); +KIT_API void kit_cg_set_top_flags(KitCg*, uint16_t set, uint16_t clear); + /* The current value-stack depth. Lets a frontend record a base depth and later * drop back to it (e.g. discarding the operands a Wasm polymorphic/unreachable * region left behind). */ KIT_API uint32_t kit_cg_stack_depth(KitCg*); -/* Returns nonzero when the current top-of-stack value is a compile-time known - * integer-like immediate VALUE. The value is not popped. */ +typedef struct KitCgConstInt { + uint64_t lo; /* low bits, always truncated to width */ + uint64_t hi; /* high bits for width > 64 */ + uint16_t width; /* 1..128 for integer constants */ + uint8_t is_signed; /* interpretation hint for convenience queries */ + uint8_t known; /* 0 => not a compile-time integer constant */ +} KitCgConstInt; + +/* Suppress target emission while preserving value-stack shape and stack-owned + * constant facts. Nested pushes are allowed; pop diagnoses underflow. */ +KIT_API void kit_cg_unevaluated_push(KitCg*); +KIT_API void kit_cg_unevaluated_pop(KitCg*); + +/* Width-complete integer-constant query. Reads the stack-side constant payload, + * not the emitted operand shape, and does not pop the value. */ +KIT_API int kit_cg_top_const_int_ex(KitCg*, KitCgConstInt* out_value); + +/* Convenience query for constants representable as one 64-bit lane. */ +KIT_API int kit_cg_top_const_i64(KitCg*, int64_t* out_value); + +/* Push a scalar integer VALUE with an explicit width-complete constant payload. + * The value is truncated to the destination type width when that width is + * integer-like and known. */ +KIT_API void kit_cg_push_const_int(KitCg*, KitCgTypeId type, + const KitCgConstInt* value); + +/* Compatibility wrapper for older callers that only need a signed 64-bit + * result. */ KIT_API int kit_cg_top_const_int(KitCg*, int64_t* out_value); /* Push a scalar VALUE. */ @@ -761,6 +882,14 @@ KIT_API void kit_cg_deref(KitCg*, int64_t offset); * the frontend rejects `&` / `sizeof` on a bit-field before reaching CG. */ KIT_API void kit_cg_field(KitCg*, uint32_t field_index); +/* PLACE -> PLACE: project to a byte-offset subobject with a frontend-supplied + * field type. This is the layout-known counterpart to kit_cg_field: CG does no + * record lookup, but still folds byte_offset into the place operand when the + * target addressing mode can carry it. Bit-field geometry, if any, is attached + * separately with kit_cg_field_bits. */ +KIT_API void kit_cg_field_at(KitCg*, int64_t byte_offset, + KitCgTypeId field_type); + /* PLACE -> PLACE(bit-field): tag the TOS place as a bit-field place carrying * the given bit geometry. This is the manual-addressing counterpart to the * implicit bit-field projection `field` performs: a frontend that builds the @@ -802,9 +931,11 @@ KIT_API void kit_cg_elem_scaled(KitCg*, uint32_t elem_size, int64_t offset); * * Stack effects: * load: [place] -> [value] - * store: [place, value] -> [] */ + * store: [place, value] -> [] + * store_keep: [place, value] -> [value] */ KIT_API void kit_cg_load(KitCg*, KitCgMemAccess access); KIT_API void kit_cg_store(KitCg*, KitCgMemAccess access); +KIT_API void kit_cg_store_keep(KitCg*, KitCgMemAccess access); /* ============================================================ * ABI variadic argument access @@ -1092,12 +1223,12 @@ typedef enum KitCgBarrierScope { KIT_CG_BARRIER_NON_SHARE, } KitCgBarrierScope; -/* Pops nargs operands. Pushes result_type unless result_type is - * KIT_CG_TYPE_NONE or void. Overflow intrinsics push two values: - * result, overflow_bool regardless of result_type. Syscall uses nargs = argc + - * 1: the syscall number plus 0..6 long arguments. Runtime-extension intrinsics - * mirror rt/include/kit/{syscall,baremetal,coro}.h and must diagnose targets - * where the primitive has no legal lowering. */ +/* Pops nargs operands. result_type must be a valid type id; the void builtin + * means no result is pushed. Overflow intrinsics push two values: result, + * overflow_bool regardless of result_type. Syscall uses nargs = argc + 1: the + * syscall number plus 0..6 long arguments. Runtime-extension intrinsics mirror + * rt/include/kit/{syscall,baremetal,coro}.h and must diagnose targets where + * the primitive has no legal lowering. */ KIT_API void kit_cg_intrinsic(KitCg*, KitCgIntrinsic, uint32_t nargs, KitCgTypeId result_type); @@ -1424,8 +1555,8 @@ typedef struct KitCgIf { static inline KitCgIf kit_cg_if_begin(KitCg* cg) { KitCgIf it; - it.outer = kit_cg_block_begin(cg, KIT_CG_TYPE_NONE); - it.inner = kit_cg_block_begin(cg, KIT_CG_TYPE_NONE); + it.outer = kit_cg_block_begin(cg); + it.inner = kit_cg_block_begin(cg); kit_cg_break_false(cg, it.inner); return it; } diff --git a/lang/c/parse/cg_adapter.c b/lang/c/parse/cg_adapter.c @@ -66,6 +66,8 @@ static KitCgMemAccess pcg_mem_id(Parser* p, KitCgTypeId id, const Type* ty) { m.type = id; m.align = (u32)kit_cg_type_align(p->c, id); if (ty && (ty->qual & Q_VOLATILE)) m.flags |= KIT_CG_MEM_VOLATILE; + if (type_is_int(ty) && pcg_type_is_signed(ty)) + m.flags |= KIT_CG_MEM_SOURCE_SIGNED; return m; } @@ -123,7 +125,8 @@ void pcg_dup_type(Parser* p) { pcg_aux_clear(&top.aux); } /* Copy the slot out before push: pcg_stack_grow may reallocate. The whole - * slot (cg_id included) is copied back at the end, so dup preserves the id. */ + * slot (cg_id included) is copied back at the end, so dup preserves the id. + */ pcg_push_type(p, top.type); if (p->cg_type_sp) p->cg_slot_stack[p->cg_type_sp - 1u] = top; } @@ -166,8 +169,9 @@ void pcg_drop(Parser* p) { /* Reclaim dead compiler-temp slots at a statement boundary. The authoritative * "no value is live" witness is the CG value stack depth, which * kit_cg_reclaim_temps checks (g->sp == 0): a stray non-empty call is a safe - * no-op rather than a miscompile. (The parser's typed shadow stack cg_type_sp is - * NOT a reliable witness — it is not drained in lockstep with the value stack.) */ + * no-op rather than a miscompile. (The parser's typed shadow stack cg_type_sp + * is NOT a reliable witness — it is not drained in lockstep with the value + * stack.) */ void pcg_reclaim_temps(Parser* p) { if (!pcg_emit_enabled(p)) return; kit_cg_reclaim_temps(p->cg); @@ -532,15 +536,6 @@ void pcg_load(Parser* p) { KitCgMemAccess access = pcg_mem_id(p, cg_id, ty); /* Snapshot bit-field geometry before materialize clears the aux. */ PcgLvAux bf = lv ? *lv : (PcgLvAux){0}; - /* Widening signed load (Lever 4): a non-bit-field signed byte/short load is - * only ever widened by sign-extension (the integer-promotion CV_SEXT), so - * mark the access so the backend can fold the load + sign-extend into one - * ldrsb/ldrsh and the -O0 path can drop the redundant CV_SEXT. Plain hint; - * a backend that ignores it stays correct. */ - if (bf.bit_width == 0 && type_is_int(ty) && pcg_type_is_signed(ty) && - (u32)kit_cg_type_size(p->c, cg_id) < 4u) { - access.flags |= KIT_CG_MEM_SEXT_LOAD; - } /* Build the PLACE the strict load requires. A trivial local already has its * PLACE on the CG stack (push_local) and loads directly; anything else is * reduced to a single pointer (materialize, or a pointer-rvalue base) and @@ -637,9 +632,9 @@ static void pcg_materialize_lv_to_ptr(Parser* p, const Type* result_ptr_ty) { /* Build the PLACE a load/store reads, from the TOS lvalue, keeping a constant * field/element offset as the deref displacement (so the backend folds it into * the load/store: `ldr w, [base, #ofs]`) rather than baking it into an explicit - * `base + ofs` pointer the way pcg_materialize_lv_to_ptr + kit_cg_deref(0) does. - * Mirrors that pair's postcondition (CG TOS is the place; parser slot is the - * field pointer type with aux cleared). The pointer is reinterpreted to the + * `base + ofs` pointer the way pcg_materialize_lv_to_ptr + kit_cg_deref(0) + * does. Mirrors that pair's postcondition (CG TOS is the place; parser slot is + * the field pointer type with aux cleared). The pointer is reinterpreted to the * field type with a bitcast — free on the native backend (a no-op same-width * cast), an explicit cast on the C-source backend. Indexed (scale != 0) and * out-of-displacement-range offsets fall back to the explicit-pointer fold. @@ -666,11 +661,12 @@ static void pcg_lv_to_memop_place(Parser* p, const Type* field_ty) { * pointer value, then fuse. The index stride is the containing array's * element size (scale) while the access type is field_ty, so `a[i].f` * strides by sizeof(elem) but reads `f`. */ - kit_cg_swap(p->cg); /* [index, base] */ - if (base_kind == PCG_LV_BASE_LOCAL) kit_cg_addr(p->cg); /* [index, base_ptr] */ - kit_cg_bitcast(p->cg, pcg_tid(p, fptr)); /* [index, (T*)base] */ - kit_cg_swap(p->cg); /* [base, index] */ - kit_cg_elem_scaled(p->cg, scale, ofs); /* [place] */ + kit_cg_swap(p->cg); /* [index, base] */ + if (base_kind == PCG_LV_BASE_LOCAL) + kit_cg_addr(p->cg); /* [index, base_ptr] */ + kit_cg_bitcast(p->cg, pcg_tid(p, fptr)); /* [index, (T*)base] */ + kit_cg_swap(p->cg); /* [base, index] */ + kit_cg_elem_scaled(p->cg, scale, ofs); /* [place] */ } else { if (base_kind == PCG_LV_BASE_LOCAL) kit_cg_addr(p->cg); kit_cg_bitcast(p->cg, pcg_tid(p, fptr)); @@ -743,7 +739,8 @@ static void pcg_store_impl(Parser* p, int keep_result) { const Type* lv_ty = pcg_top2_type(p); const Type* rv_ty = pcg_top_type(p); const Type* mem_ty = lv_ty; - /* Cached CG ids of the two slots (lowered once, not re-crossed by pcg_mem). */ + /* Cached CG ids of the two slots (lowered once, not re-crossed by pcg_mem). + */ KitCgTypeId rv_cg = pcg_top_cg_id(p); KitCgTypeId mem_cg = pcg_top2_cg_id(p); /* tracks mem_ty (= lv_ty) */ int emit = pcg_emit_enabled(p); @@ -1278,7 +1275,8 @@ void pcg_intrinsic_void(Parser* p, IntrinKind k) { if (pcg_emit_enabled(p)) kit_cg_unreachable(p->cg); } else { if (pcg_emit_enabled(p)) { - kit_cg_intrinsic(p->cg, KIT_CG_INTRIN_TRAP, 0, KIT_CG_TYPE_NONE); + kit_cg_intrinsic(p->cg, KIT_CG_INTRIN_TRAP, 0, + kit_cg_type_builtin(p->c, KIT_CG_BUILTIN_VOID)); } } } diff --git a/lang/c/parse/parse_stmt.c b/lang/c/parse/parse_stmt.c @@ -86,7 +86,7 @@ static void parse_while_stmt(Parser* p) { CGLabel L_end; int emit = pcg_emit_enabled(p); if (emit) { - scope = kit_cg_scope_begin(p->cg, KIT_CG_TYPE_NONE); + scope = kit_cg_scope_begin(p->cg); L_top = kit_cg_scope_continue_label(p->cg, scope); L_end = kit_cg_scope_break_label(p->cg, scope); } else { @@ -416,7 +416,7 @@ static void parse_switch_stmt(Parser* p) { CaseEntry* head; if (emit) { - scope = kit_cg_scope_begin(p->cg, KIT_CG_TYPE_NONE); + scope = kit_cg_scope_begin(p->cg); L_dispatch = pcg_label_new(p); L_end = kit_cg_scope_break_label(p->cg, scope); } else { diff --git a/lang/c/parse/parse_type.c b/lang/c/parse/parse_type.c @@ -1499,6 +1499,24 @@ const Type* apply_decl_suffix(Parser* p, const Type* base, if (base && base->kind == TY_VOID) { perr(p, "array of void type is invalid"); } + /* C11 6.7.6.2p1: an array's element type must be complete. Completeness is + * fixed where the array declarator is formed, so an incomplete record stays + * an error here even if its tag is completed later (or never) -- this is + * the constraint that rejects `struct N { struct N a[10]; }` while a + * pointer element (`struct N *a[10]`) stays legal because a pointer is + * complete. Element incompleteness is checked only for records: VLAs share + * the array `incomplete` flag, so an incomplete-array element cannot be + * told apart from a valid VLA element here and is intentionally not + * diagnosed. */ + if (base && (base->kind == TY_STRUCT || base->kind == TY_UNION) && + base->rec.incomplete) { + if (base->rec.tag) { + perr(p, "array has incomplete element type '%s %.*s'", + base->kind == TY_UNION ? "union" : "struct", + KIT_SLICE_ARG(kit_sym_str(p->pool->c, base->rec.tag))); + } + perr(p, "array has incomplete element type"); + } return type_array(p->pool, base, s->count, s->incomplete || s->vla); } { diff --git a/lang/c/type/type.c b/lang/c/type/type.c @@ -97,7 +97,8 @@ static int type_struct_eq(const Type* a, const Type* b) { * declaration identity on `derived` and never enter this set. Replaces the * former O(types) linear scan of one flat derived list (which made each * derivation O(types) → type construction O(types^2)). */ -KIT_HASHSET_DEFINE(TypeInternSet, const Type*, type_struct_hash, type_struct_eq); +KIT_HASHSET_DEFINE(TypeInternSet, const Type*, type_struct_hash, + type_struct_eq); /* Completed record layout id, keyed by record identity. A record's identity is * its tag id (same_record_type is tag-id equality), so a plain u32->id map @@ -113,9 +114,10 @@ static inline u32 type_ptr_hash(const Type* t) { /* Canonical-unqualified Type* memo, keyed by the qualified Type*. Collapses the * O(derived) linear scan type_unqual runs for a complete tagged type (the hot - * const-record path) to one lookup. Only stable answers are inserted (a complete - * record's unqualified node never changes); incomplete/enum results are left - * uncached because they can still change as a forward record completes. */ + * const-record path) to one lookup. Only stable answers are inserted (a + * complete record's unqualified node never changes); incomplete/enum results + * are left uncached because they can still change as a forward record + * completes. */ KIT_HASHMAP_DEFINE(CgUnqualMemo, const Type*, const Type*, type_ptr_hash); typedef struct PoolTypeCache { @@ -129,6 +131,12 @@ typedef struct PoolTypeCache { TypeListNode* derived; /* Completed record layout ids, keyed by tag id. */ CgRecordMap cg_records; + /* Records whose CG completion is currently in flight, keyed by tag id -> + * decl id. Self-/mutually-recursive records reach themselves through a + * pointer field mid-completion; this lets type_cg_record_layout hand back the + * still-incomplete decl id (a pointer to an incomplete record is legal) + * instead of recursing into a second completion of the same record. */ + CgRecordMap cg_records_completing; /* Canonical-unqualified node per qualified Type* (stable answers only). */ CgUnqualMemo unqual; /* Tag id allocator (1-based; TAG_NONE = 0). */ @@ -147,6 +155,7 @@ static PoolTypeCache* cache_get(Pool* p) { * needed. Lazy (cap 0): the first insert allocates. */ TypeInternSet_init_cap(&c->structural, &p->arena_heap, 0); CgRecordMap_init_cap(&c->cg_records, &p->arena_heap, 0); + CgRecordMap_init_cap(&c->cg_records_completing, &p->arena_heap, 0); CgUnqualMemo_init_cap(&c->unqual, &p->arena_heap, 0); p->type_cache = c; return c; @@ -292,8 +301,8 @@ static int type_tagged_eq(const Type* n, const Type* base, u16 qual) { n->rec.max_align == base->rec.max_align && n->rec.align_override == base->rec.align_override; case TY_ENUM: - return n->enm.tag_id == base->enm.tag_id && - n->enm.tag == base->enm.tag && n->enm.base == base->enm.base; + return n->enm.tag_id == base->enm.tag_id && n->enm.tag == base->enm.tag && + n->enm.base == base->enm.base; default: return 0; } @@ -691,40 +700,40 @@ static KitCgTypeId type_cg_builtin(KitCompiler* c, TypeKind kind) { * widths, so it is fetched lazily inside those cases rather than per call. */ switch (kind) { case TY_VOID: - return kit_cg_builtin_type_id(c, KIT_CG_BUILTIN_VOID); + return kit_cg_type_builtin(c, KIT_CG_BUILTIN_VOID); case TY_BOOL: - return kit_cg_builtin_type_id(c, KIT_CG_BUILTIN_BOOL); + return kit_cg_type_builtin(c, KIT_CG_BUILTIN_BOOL); case TY_CHAR: case TY_SCHAR: case TY_UCHAR: - return kit_cg_builtin_type_id(c, KIT_CG_BUILTIN_I8); + return kit_cg_type_builtin(c, KIT_CG_BUILTIN_I8); case TY_SHORT: case TY_USHORT: - return kit_cg_builtin_type_id(c, KIT_CG_BUILTIN_I16); + return kit_cg_type_builtin(c, KIT_CG_BUILTIN_I16); case TY_INT: case TY_UINT: - return kit_cg_builtin_type_id(c, KIT_CG_BUILTIN_I32); + return kit_cg_type_builtin(c, KIT_CG_BUILTIN_I32); case TY_LONG: case TY_ULONG: - return kit_cg_builtin_type_id( + return kit_cg_type_builtin( c, kit_target_uses_lp64(kit_compiler_target_spec(c)) ? KIT_CG_BUILTIN_I64 : KIT_CG_BUILTIN_I32); case TY_LLONG: case TY_ULLONG: - return kit_cg_builtin_type_id(c, KIT_CG_BUILTIN_I64); + return kit_cg_type_builtin(c, KIT_CG_BUILTIN_I64); case TY_INT128: case TY_UINT128: - return kit_cg_builtin_type_id(c, KIT_CG_BUILTIN_I128); + return kit_cg_type_builtin(c, KIT_CG_BUILTIN_I128); case TY_FLOAT: - return kit_cg_builtin_type_id(c, KIT_CG_BUILTIN_F32); + return kit_cg_type_builtin(c, KIT_CG_BUILTIN_F32); case TY_DOUBLE: - return kit_cg_builtin_type_id(c, KIT_CG_BUILTIN_F64); + return kit_cg_type_builtin(c, KIT_CG_BUILTIN_F64); case TY_LDOUBLE: /* `long double` is IEEE-754 binary128 on targets that follow the quad * psABI (RISC-V, aarch64-linux, wasm32); elsewhere it aliases `double`. * See kit_target_long_double_is_binary128. */ - return kit_cg_builtin_type_id( + return kit_cg_type_builtin( c, kit_target_long_double_is_binary128(kit_compiler_target_spec(c)) ? KIT_CG_BUILTIN_F128 : KIT_CG_BUILTIN_F64); @@ -736,7 +745,6 @@ static KitCgTypeId type_cg_builtin(KitCompiler* c, TypeKind kind) { typedef enum TypeCgMode { TYPE_CG_VALUE, - TYPE_CG_RECORD_FIELD, } TypeCgMode; typedef struct TypeCgLower { @@ -772,33 +780,65 @@ static void type_cg_record_memo_put(Pool* p, PoolTypeCache* cache, (void)c; if (!cache || !t || id == KIT_CG_TYPE_NONE) return; if (t->kind != TY_STRUCT && t->kind != TY_UNION) return; - if (t->rec.incomplete || t->rec.tag_id == TAG_NONE) return; + if (t->rec.tag_id == TAG_NONE) return; CgRecordMap_set(&cache->cg_records, t->rec.tag_id, id); } +static KitCgTypeId type_cg_record_decl_id(TypeCgLower* l, const Type* t) { + KitCgTypeId id; + if (!l || !t || (t->kind != TY_STRUCT && t->kind != TY_UNION)) { + return KIT_CG_TYPE_NONE; + } + id = type_cg_record_memo_get(l->cache, l->c, t); + if (id != KIT_CG_TYPE_NONE) return id; + id = kit_cg_type_record_decl(l->c, t->rec.tag, t->kind == TY_UNION); + type_cg_record_memo_put(l->p, l->cache, l->c, t, id); + return id; +} + static KitCgTypeId type_cg_record_layout(TypeCgLower* l, const Type* t) { KitCgField* fields = NULL; KitCgRecordDesc desc; KitCgTypeId id; + int tracked = 0; if (!l || !t || (t->kind != TY_STRUCT && t->kind != TY_UNION)) { return KIT_CG_TYPE_NONE; } + id = type_cg_record_decl_id(l, t); + if (id == KIT_CG_TYPE_NONE) return KIT_CG_TYPE_NONE; if (t->rec.incomplete) { - /* Lowers to void for now but to its real layout once the record is - * completed in place (type_record_install). Mark the lowering unstable so - * no enclosing node caches this transient result. */ l->saw_incomplete = 1; - return type_cg_builtin(l->c, TY_VOID); + return id; + } + if (kit_cg_type_is_complete(l->c, id)) return id; + /* Cycle safety net. Recursion *through a pointer* is already broken in the + * TY_PTR case (the pointee takes the decl id, never re-entering here), so the + * valid self-/mutual-reference shapes never reach this guard. What can still + * re-enter mid-completion is a by-value path back to an in-flight record -- + * e.g. the invalid `struct N { struct N arr[10]; }`, where the array forces + * its incomplete element to lay out. Hand back the still-incomplete decl id + * (the array then fails the sized-element check, surfacing a clean error) + * rather than recursing into a second completion and overflowing the stack. + * Track every in-flight record so mutual by-value cycles are covered too. + * Anonymous records carry no tag but cannot name themselves, so they never + * reach this guard. */ + if (t->rec.tag_id != TAG_NONE) { + if (CgRecordMap_get(&l->cache->cg_records_completing, t->rec.tag_id)) { + l->saw_incomplete = 1; + return id; + } + CgRecordMap_set(&l->cache->cg_records_completing, t->rec.tag_id, id); + tracked = 1; } - id = type_cg_record_memo_get(l->cache, l->c, t); - if (id != KIT_CG_TYPE_NONE) return id; if (t->rec.nfields) { fields = arena_zarray(l->p->arena, KitCgField, t->rec.nfields); - if (!fields) return KIT_CG_TYPE_NONE; + if (!fields) { + id = KIT_CG_TYPE_NONE; + goto done; + } for (u32 i = 0; i < t->rec.nfields; ++i) { fields[i].name = t->rec.fields[i].name; - fields[i].type = - type_cg_lower(l, t->rec.fields[i].type, TYPE_CG_RECORD_FIELD); + fields[i].type = type_cg_lower(l, t->rec.fields[i].type, TYPE_CG_VALUE); fields[i].align_override = t->rec.fields[i].align_override; fields[i].max_align = t->rec.fields[i].max_align; if (t->rec.max_align && @@ -826,8 +866,13 @@ static KitCgTypeId type_cg_record_layout(TypeCgLower* l, const Type* t) { desc.nfields = t->rec.nfields; desc.is_union = t->kind == TY_UNION; desc.align_override = t->rec.align_override; - id = kit_cg_type_record_ex(l->c, &desc); - type_cg_record_memo_put(l->p, l->cache, l->c, t, id); + if (kit_cg_type_record_complete(l->c, id, &desc) != KIT_OK) + id = KIT_CG_TYPE_NONE; +done: + /* Clear the in-flight marker so a later, independent lowering of the same tag + * (now CG-complete) takes the fast is_complete path rather than this guard. + */ + if (tracked) CgRecordMap_del(&l->cache->cg_records_completing, t->rec.tag_id); return id; } @@ -837,22 +882,19 @@ static KitCgTypeId type_cg_lower(TypeCgLower* l, const Type* t, int cacheable; int outer_incomplete; if (!l || !t) return KIT_CG_TYPE_NONE; - /* Only VALUE-mode lowering is memoized on the node: TYPE_CG_RECORD_FIELD - * collapses pointers to void*, so the same Type* lowers differently there - * (and that mode is used only transiently for record fields / func params). - * Check the cache BEFORE the builtin switch: a cached cg_id (whether a builtin - * id stamped below or a non-builtin lowering) always equals what the switch - * would recompute, so this hoist only short-circuits already-lowered types — - * byte-identical, but skips the recompute. */ + /* Check the cache BEFORE the builtin switch: a cached cg_id (whether a + * builtin id stamped below or a non-builtin lowering) always equals what the + * switch would recompute, so this hoist only short-circuits already-lowered + * types -- byte-identical, but skips the recompute. */ cacheable = (mode == TYPE_CG_VALUE); if (cacheable && t->cg_id != KIT_CG_TYPE_NONE) return t->cg_id; /* cached => was incomplete-free => still stable */ id = type_cg_builtin(l->c, (TypeKind)t->kind); if (id != KIT_CG_TYPE_NONE) { /* type_cg_builtin is a pure function of t->kind (and the fixed target), so - * a builtin id is mode-independent and incomplete-free: stamp it on the node - * so the next VALUE-mode crossing short-circuits above. A builtin id is - * never 0 (builtin_id(VOID)=64), so it can't alias the NONE sentinel. */ + * a builtin id is mode-independent and incomplete-free: stamp it on the + * node so the next VALUE-mode crossing short-circuits above. Builtins are + * never 0, so they can't alias the NONE sentinel. */ if (cacheable) ((Type*)t)->cg_id = id; return id; } @@ -861,14 +903,29 @@ static KitCgTypeId type_cg_lower(TypeCgLower* l, const Type* t, outer_incomplete = l->saw_incomplete; l->saw_incomplete = 0; switch ((TypeKind)t->kind) { - case TY_PTR: - if (mode == TYPE_CG_RECORD_FIELD) { - id = kit_cg_type_ptr(l->c, type_cg_builtin(l->c, TY_VOID), 0); + case TY_PTR: { + /* A pointer needs only its pointee's nominal identity, never its layout, + * so a record/union pointee lowers to its (possibly incomplete) decl id + * instead of being completed here. This is what makes a forward-declared + * record used only through pointers legal without ever completing it, and + * it breaks self-/mutual-reference cycles (struct N { struct N* next; }, + * A<->B) at the pointer rather than recursing into completion. A record + * is completed only when something needs its layout -- a by-value local, + * field, param/result, array element, sizeof, or member access -- all of + * which lower the record itself, not a pointer to it. */ + const Type* pointee = t->ptr.pointee; + KitCgTypeId pid; + if (pointee->kind == TY_STRUCT || pointee->kind == TY_UNION) { + pid = type_cg_record_decl_id(l, pointee); + if (pid != KIT_CG_TYPE_NONE && !kit_cg_type_is_complete(l->c, pid)) + l->saw_incomplete = 1; } else { - id = kit_cg_type_ptr(l->c, - type_cg_lower(l, t->ptr.pointee, TYPE_CG_VALUE), 0); + pid = type_cg_lower(l, pointee, TYPE_CG_VALUE); } + id = pid == KIT_CG_TYPE_NONE ? KIT_CG_TYPE_NONE + : kit_cg_type_ptr(l->c, pid, 0); break; + } case TY_ARRAY: id = kit_cg_type_array(l->c, type_cg_lower(l, t->arr.elem, mode), t->arr.count); @@ -877,8 +934,7 @@ static KitCgTypeId type_cg_lower(TypeCgLower* l, const Type* t, KitCgFuncParam* params = NULL; KitCgFuncSig sig; memset(&sig, 0, sizeof sig); - if (t->fn.ret->kind != TY_VOID) - sig.result.type = type_cg_lower(l, t->fn.ret, TYPE_CG_VALUE); + sig.result.type = type_cg_lower(l, t->fn.ret, TYPE_CG_VALUE); sig.nparams = t->fn.nparams; sig.abi_variadic = t->fn.variadic; sig.call_conv = KIT_CG_CC_TARGET_C; @@ -889,8 +945,7 @@ static KitCgTypeId type_cg_lower(TypeCgLower* l, const Type* t, break; } for (u32 i = 0; i < t->fn.nparams; ++i) { - params[i].type = - type_cg_lower(l, t->fn.params[i], TYPE_CG_RECORD_FIELD); + params[i].type = type_cg_lower(l, t->fn.params[i], TYPE_CG_VALUE); } } sig.params = params; @@ -931,4 +986,3 @@ KitCgTypeId type_cg_id_in_pool(KitCompiler* c, Pool* p, const Type* t) { l.saw_incomplete = 0; return type_cg_lower(&l, t, TYPE_CG_VALUE); } - diff --git a/lang/toy/builtins.c b/lang/toy/builtins.c @@ -1060,7 +1060,7 @@ KitCgTypeId toy_parse_generic_builtin(ToyParser* p, KitSym name, fields[0].type = ty; fields[1].name = kit_sym_intern(p->c, KIT_SLICE_LIT("overflow")); fields[1].type = toy_builtin_type(p, KIT_CG_BUILTIN_BOOL); - rec_ty = kit_cg_type_record(p->c, 0, fields, 2); + rec_ty = toy_cg_record_type(p, 0, fields, 2, 0, 0); rec_slot = kit_cg_local(p->cg, rec_ty, toy_slot_attrs(0)); { uint64_t f0_off = 0, f1_off = 0; @@ -1367,7 +1367,7 @@ KitCgTypeId toy_parse_atomic_generic_builtin(ToyParser* p, KitSym name, fields[0].type = ty; fields[1].name = kit_sym_intern(p->c, KIT_SLICE_LIT("ok")); fields[1].type = toy_builtin_type(p, KIT_CG_BUILTIN_BOOL); - rec_ty = kit_cg_type_record(p->c, 0, fields, 2); + rec_ty = toy_cg_record_type(p, 0, fields, 2, 0, 0); rec_slot = kit_cg_local(p->cg, rec_ty, toy_slot_attrs(0)); { uint64_t f0_off = 0, f1_off = 0; diff --git a/lang/toy/decls.c b/lang/toy/decls.c @@ -33,7 +33,7 @@ int toy_parse_record_decl(ToyParser* p) { size_t nfields = 0; size_t cap_fields = 0; size_t cap_field_infos = 0; - KitCgTypeId type; + KitCgTypeId type = KIT_CG_TYPE_NONE; ToyNamedType* named; int packed = 0; uint32_t record_align = 0; @@ -47,15 +47,42 @@ int toy_parse_record_decl(ToyParser* p) { } name = toy_tok_sym(p, p->cur); toy_parser_advance(p); + named = toy_find_named_type(p, name); if (toy_parser_match(p, TOK_SEMI)) { - return toy_add_named_type(p, name, KIT_CG_TYPE_NONE, TOY_NAMED_RECORD, - KIT_CG_TYPE_NONE); + if (named) return 1; + type = kit_cg_type_record_decl(p->c, name, 0); + if (type == KIT_CG_TYPE_NONE) { + toy_error(p, p->cur.loc, "failed to declare record type"); + return 0; + } + return toy_add_named_type(p, name, type, TOY_NAMED_RECORD, type); } - if (!toy_find_named_type(p, name)) { - if (!toy_add_named_type(p, name, KIT_CG_TYPE_NONE, TOY_NAMED_RECORD, - KIT_CG_TYPE_NONE)) { + if (named) { + if (named->kind != TOY_NAMED_RECORD) { + toy_error(p, p->cur.loc, "record name already used for another type"); return 0; } + type = named->type; + if (type == KIT_CG_TYPE_NONE) { + type = kit_cg_type_record_decl(p->c, name, 0); + if (type == KIT_CG_TYPE_NONE || + !toy_add_named_type(p, name, type, TOY_NAMED_RECORD, type)) { + toy_error(p, p->cur.loc, "failed to declare record type"); + return 0; + } + named = toy_find_named_type(p, name); + } else if (kit_cg_type_is_complete(p->c, type)) { + toy_error(p, p->cur.loc, "record type redefinition"); + return 0; + } + } else { + type = kit_cg_type_record_decl(p->c, name, 0); + if (type == KIT_CG_TYPE_NONE || + !toy_add_named_type(p, name, type, TOY_NAMED_RECORD, type)) { + toy_error(p, p->cur.loc, "failed to declare record type"); + return 0; + } + named = toy_find_named_type(p, name); } if (!toy_parser_expect(p, TOK_LBRACE)) { toy_error(p, p->cur.loc, "expected '{' after record name"); @@ -98,23 +125,11 @@ int toy_parse_record_decl(ToyParser* p) { if (!fields[i].align_override) fields[i].align_override = 1; } } - if (record_align) { - KitCgRecordDesc desc; - memset(&desc, 0, sizeof desc); - desc.tag = name; - desc.fields = fields; - desc.nfields = (uint32_t)nfields; - desc.align_override = record_align; - type = kit_cg_type_record_ex(p->c, &desc); - } else { - type = kit_cg_type_record(p->c, name, fields, (uint32_t)nfields); - } - if (type == KIT_CG_TYPE_NONE) { + if (!toy_cg_record_complete(p, type, name, fields, (uint32_t)nfields, 0, + record_align)) { toy_error(p, p->cur.loc, "failed to create record type"); goto done; } - if (!toy_add_named_type(p, name, type, TOY_NAMED_RECORD, type)) goto done; - named = toy_find_named_type(p, name); ok = named && toy_set_named_type_fields(p, named, field_infos, nfields); done: @@ -169,7 +184,7 @@ int toy_parse_tuple_decl(ToyParser* p) { toy_error(p, p->cur.loc, "expected '}' after tuple declaration"); goto done; } - type = kit_cg_type_record(p->c, name, fields, (uint32_t)nfields); + type = toy_cg_record_type(p, name, fields, (uint32_t)nfields, 0, 0); if (type == KIT_CG_TYPE_NONE) { toy_error(p, p->cur.loc, "failed to create tuple type"); goto done; @@ -426,10 +441,8 @@ int toy_parse_fn(ToyParser* p, int is_extern, int is_pub) { sig_params[i].attrs = param_attrs[i]; } memset(&sig, 0, sizeof sig); - if (kit_cg_type_kind(p->c, ret_type) != KIT_CG_TYPE_VOID) { - sig.result.type = ret_type; - sig.result.attrs = ret_attrs; - } + sig.result.type = ret_type; + sig.result.attrs = ret_attrs; sig.params = sig_params; sig.nparams = (uint32_t)nparams; sig.call_conv = attrs.has_call_conv ? attrs.call_conv : KIT_CG_CC_TARGET_C; diff --git a/lang/toy/internal.h b/lang/toy/internal.h @@ -233,7 +233,6 @@ typedef struct ToyParser { ToyToken cur; KitCompiler* c; KitCg* cg; - KitCgBuiltinTypes types; KitCgTypeId int_type; KitCgTypeId size_type; KitCgTypeId int_ptr_type; @@ -285,6 +284,12 @@ typedef struct ToyParser { } ToyParser; KitCgTypeId toy_builtin_type(ToyParser* p, KitCgBuiltinType ty); +KitCgTypeId toy_cg_record_type(ToyParser* p, KitSym tag, + const KitCgField* fields, uint32_t nfields, + int is_union, uint32_t align_override); +int toy_cg_record_complete(ToyParser* p, KitCgTypeId record, KitSym tag, + const KitCgField* fields, uint32_t nfields, + int is_union, uint32_t align_override); /* The toy frontend models a single return value: the function's first result * type, or void when it has no results. */ KitCgTypeId toy_cg_func_ret(ToyParser* p, KitCgTypeId fn_ty); diff --git a/lang/toy/parser.c b/lang/toy/parser.c @@ -602,7 +602,7 @@ static int toy_parse_while_initializer_named(ToyParser* p, KitCgLocal slot, p->nscopes + 1u, sizeof *p->scopes, "scopes")) { return 0; } - scope = kit_cg_scope_begin(p->cg, result_ty); + scope = kit_cg_scope_begin_value(p->cg, result_ty); p->scopes[p->nscopes].name = label_name; p->scopes[p->nscopes].kind = TOY_SCOPE_LOOP; p->scopes[p->nscopes].cg_scope = scope; @@ -1017,7 +1017,7 @@ static int toy_parse_while_stmt_named(ToyParser* p, KitSym label_name) { p->nscopes + 1u, sizeof *p->scopes, "scopes")) { return 0; } - scope = kit_cg_scope_begin(p->cg, KIT_CG_TYPE_NONE); + scope = kit_cg_scope_begin(p->cg); p->scopes[p->nscopes].name = label_name; p->scopes[p->nscopes].kind = TOY_SCOPE_LOOP; p->scopes[p->nscopes].cg_scope = scope; @@ -1086,7 +1086,7 @@ static int toy_parse_switch_stmt_named(ToyParser* p, KitSym label_name) { * block is reachable from the straight-line predecessor. With the * jump before scope_begin, opt's CFG sees the scope_begin block as * unreachable and prunes it; scope_end then can't find its handle. */ - scope = kit_cg_scope_begin(p->cg, KIT_CG_TYPE_NONE); + scope = kit_cg_scope_begin(p->cg); p->scopes[p->nscopes].name = label_name; p->scopes[p->nscopes].kind = TOY_SCOPE_SWITCH; p->scopes[p->nscopes].cg_scope = scope; diff --git a/lang/toy/parser_core.c b/lang/toy/parser_core.c @@ -5,7 +5,33 @@ #include "internal.h" KitCgTypeId toy_builtin_type(ToyParser* p, KitCgBuiltinType ty) { - return p->types.id[ty]; + return kit_cg_type_builtin(p->c, ty); +} + +KitCgTypeId toy_cg_record_type(ToyParser* p, KitSym tag, + const KitCgField* fields, uint32_t nfields, + int is_union, uint32_t align_override) { + KitCgRecordDesc desc; + memset(&desc, 0, sizeof desc); + desc.tag = tag; + desc.fields = fields; + desc.nfields = nfields; + desc.is_union = is_union; + desc.align_override = align_override; + return kit_cg_type_record(p->c, &desc); +} + +int toy_cg_record_complete(ToyParser* p, KitCgTypeId record, KitSym tag, + const KitCgField* fields, uint32_t nfields, + int is_union, uint32_t align_override) { + KitCgRecordDesc desc; + memset(&desc, 0, sizeof desc); + desc.tag = tag; + desc.fields = fields; + desc.nfields = nfields; + desc.is_union = is_union; + desc.align_override = align_override; + return kit_cg_type_record_complete(p->c, record, &desc) == KIT_OK; } KitCgTypeId toy_cg_func_ret(ToyParser* p, KitCgTypeId fn_ty) { @@ -57,7 +83,6 @@ void toy_parser_init(ToyParser* p, KitCompiler* c, KitCg* cg, ToyModule* module, p->cur = toy_lexer_next(&p->lex); p->c = c; p->cg = cg; - p->types = kit_cg_builtin_types(c); p->target = kit_compiler_target_spec(c); p->int_type = toy_builtin_type(p, KIT_CG_BUILTIN_I64); p->size_type = toy_builtin_type( @@ -100,7 +125,6 @@ void toy_parser_reinit(ToyParser* p, KitCompiler* c, KitCg* cg, p->cur = toy_lexer_next(&p->lex); p->c = c; p->cg = cg; - p->types = kit_cg_builtin_types(c); p->target = kit_compiler_target_spec(c); p->int_type = toy_builtin_type(p, KIT_CG_BUILTIN_I64); p->size_type = toy_builtin_type( diff --git a/lang/toy/types.c b/lang/toy/types.c @@ -159,8 +159,7 @@ KitCgTypeId toy_parse_type(ToyParser* p) { for (i = 0; i < nparams; i++) sig_params[i].type = param_types[i]; memset(&sig, 0, sizeof sig); - if (kit_cg_type_kind(p->c, ret_type) != KIT_CG_TYPE_VOID) - sig.result.type = ret_type; + sig.result.type = ret_type; sig.params = sig_params; sig.nparams = (uint32_t)nparams; sig.call_conv = KIT_CG_CC_TARGET_C; @@ -255,20 +254,9 @@ KitCgTypeId toy_parse_type(ToyParser* p) { if (!fields[i].align_override) fields[i].align_override = 1; } } - if (record_align) { - KitCgRecordDesc desc; - KitCgTypeId record_ty; - memset(&desc, 0, sizeof desc); - desc.fields = fields; - desc.nfields = (uint32_t)nfields; - desc.align_override = record_align; - record_ty = kit_cg_type_record_ex(p->c, &desc); - result = toy_type_finish(p, record_ty, toy_type_from_cg(p, record_ty)); - goto record_done; - } { KitCgTypeId record_ty = - kit_cg_type_record(p->c, 0, fields, (uint32_t)nfields); + toy_cg_record_type(p, 0, fields, (uint32_t)nfields, 0, record_align); result = toy_type_finish(p, record_ty, toy_type_from_cg(p, record_ty)); } record_done: @@ -331,7 +319,10 @@ KitCgTypeId toy_parse_type(ToyParser* p) { else { ToyNamedType* named = toy_find_named_type(p, toy_tok_sym(p, p->cur)); if (named) { - if (named->type == KIT_CG_TYPE_NONE) { + if (named->type == KIT_CG_TYPE_NONE || + ((named->kind == TOY_NAMED_RECORD || + named->kind == TOY_NAMED_TUPLE) && + !kit_cg_type_is_complete(p->c, named->type))) { toy_error(p, p->cur.loc, "incomplete record type requires pointer"); return KIT_CG_TYPE_NONE; } @@ -366,11 +357,14 @@ KitCgTypeId toy_parse_type(ToyParser* p) { } if (p->cur.kind == TOK_IDENT) { incomplete_pointee = toy_find_named_type(p, toy_tok_sym(p, p->cur)); - if (incomplete_pointee && incomplete_pointee->type == KIT_CG_TYPE_NONE) { + if (incomplete_pointee && + (incomplete_pointee->kind == TOY_NAMED_RECORD || + incomplete_pointee->kind == TOY_NAMED_TUPLE) && + incomplete_pointee->type != KIT_CG_TYPE_NONE && + !kit_cg_type_is_complete(p->c, incomplete_pointee->type)) { KitCgTypeId ptr_ty; toy_parser_advance(p); - ptr_ty = kit_cg_type_ptr(p->c, toy_builtin_type(p, KIT_CG_BUILTIN_VOID), - address_space); + ptr_ty = kit_cg_type_ptr(p->c, incomplete_pointee->type, address_space); return toy_type_finish( p, ptr_ty, toy_type_register_ptr(p, ptr_ty, incomplete_pointee->toy_type, @@ -428,7 +422,7 @@ KitCgTypeId toy_ptr_pointee_func_type(ToyParser* p, KitCgTypeId ptr_ty) { void toy_type_register_builtins(ToyParser* p) { size_t i; for (i = 0; i < KIT_CG_BUILTIN_COUNT; ++i) - (void)toy_type_from_cg(p, p->types.id[i]); + (void)toy_type_from_cg(p, toy_builtin_type(p, (KitCgBuiltinType)i)); (void)toy_type_from_cg(p, p->int_ptr_type); } @@ -584,7 +578,7 @@ ToyTypeId toy_type_register_slice(ToyParser* p, KitCgTypeId elem_cg, fields[1].type = p->size_type; memset(&type, 0, sizeof type); type.kind = TOY_TYPE_SLICE; - type.cg = kit_cg_type_record(p->c, 0, fields, 2); + type.cg = toy_cg_record_type(p, 0, fields, 2, 0, 0); type.elem = elem; return toy_type_add(p, &type); } diff --git a/lang/wasm/cg.c b/lang/wasm/cg.c @@ -3,7 +3,30 @@ #include "runtime_abi.h" #include "wasm/wasm.h" -static KitCgTypeId wasm_cg_type(KitCompiler* c, KitCgBuiltinTypes b, +typedef struct WasmCgBuiltinTypes { + KitCgTypeId id[KIT_CG_BUILTIN_COUNT]; +} WasmCgBuiltinTypes; + +static WasmCgBuiltinTypes wasm_cg_builtin_types(KitCompiler* c) { + WasmCgBuiltinTypes b; + memset(&b, 0, sizeof b); + for (uint32_t i = 0; i < KIT_CG_BUILTIN_COUNT; ++i) + b.id[i] = kit_cg_type_builtin(c, (KitCgBuiltinType)i); + return b; +} + +static KitCgTypeId wasm_cg_record_type(KitCompiler* c, KitSym tag, + const KitCgField* fields, + uint32_t nfields) { + KitCgRecordDesc desc; + memset(&desc, 0, sizeof desc); + desc.tag = tag; + desc.fields = fields; + desc.nfields = nfields; + return kit_cg_type_record(c, &desc); +} + +static KitCgTypeId wasm_cg_type(KitCompiler* c, WasmCgBuiltinTypes b, WasmValType vt) { switch (vt) { case WASM_VAL_I32: @@ -22,7 +45,7 @@ static KitCgTypeId wasm_cg_type(KitCompiler* c, KitCgBuiltinTypes b, return b.id[KIT_CG_BUILTIN_I32]; } -static KitCgMemAccess wasm_cg_mem(KitCompiler* c, KitCgBuiltinTypes b, +static KitCgMemAccess wasm_cg_mem(KitCompiler* c, WasmCgBuiltinTypes b, WasmValType vt) { KitCgMemAccess mem; memset(&mem, 0, sizeof mem); @@ -37,7 +60,7 @@ static KitCgMemAccess wasm_cg_mem_type(KitCgTypeId ty) { return mem; } -static void wasm_cg_push_zero(KitCompiler* c, KitCg* cg, KitCgBuiltinTypes b, +static void wasm_cg_push_zero(KitCompiler* c, KitCg* cg, WasmCgBuiltinTypes b, WasmValType vt) { KitCgTypeId ty = wasm_cg_type(c, b, vt); if (wasm_is_ref_type(vt)) @@ -48,7 +71,7 @@ static void wasm_cg_push_zero(KitCompiler* c, KitCg* cg, KitCgBuiltinTypes b, kit_cg_push_int(cg, 0, ty); } -static KitCgTypeId wasm_load_storage_type(KitCgBuiltinTypes b, uint8_t kind) { +static KitCgTypeId wasm_load_storage_type(WasmCgBuiltinTypes b, uint8_t kind) { switch (kind) { case WASM_INSN_I32_LOAD8_S: case WASM_INSN_I32_LOAD8_U: @@ -73,7 +96,7 @@ static KitCgTypeId wasm_load_storage_type(KitCgBuiltinTypes b, uint8_t kind) { } } -static KitCgTypeId wasm_store_storage_type(KitCgBuiltinTypes b, uint8_t kind) { +static KitCgTypeId wasm_store_storage_type(WasmCgBuiltinTypes b, uint8_t kind) { switch (kind) { case WASM_INSN_I32_STORE8: case WASM_INSN_I64_STORE8: @@ -168,6 +191,7 @@ typedef struct WasmCgRuntime { uint32_t* passive_elem_storage_field; uint32_t passive_elem_entries_field; uint32_t passive_elem_length_field; + KitCgTypeId void_ty; KitCgTypeId trap_func_ty; KitCgSym trap_syms[WASM_TRAP_COUNT]; } WasmCgRuntime; @@ -191,8 +215,9 @@ static const char* wasm_trap_name(WasmTrapKind kind) { } } -static void wasm_cg_emit_raw_trap(KitCg* cg) { - kit_cg_intrinsic(cg, KIT_CG_INTRIN_TRAP, 0, KIT_CG_TYPE_NONE); +static void wasm_cg_emit_raw_trap(KitCompiler* c, KitCg* cg) { + kit_cg_intrinsic(cg, KIT_CG_INTRIN_TRAP, 0, + kit_cg_type_builtin(c, KIT_CG_BUILTIN_VOID)); kit_cg_unreachable(cg); } @@ -201,8 +226,8 @@ static void wasm_cg_trap(KitCg* cg, const WasmCgRuntime* rt, if (rt && kind < WASM_TRAP_COUNT && rt->trap_syms[kind]) kit_cg_call_symbol(cg, rt->trap_syms[kind], 0, (KitCgCallAttrs){.flags = KIT_CG_CALL_COLD}); - else - kit_cg_intrinsic(cg, KIT_CG_INTRIN_TRAP, 0, KIT_CG_TYPE_NONE); + else if (rt) + kit_cg_intrinsic(cg, KIT_CG_INTRIN_TRAP, 0, rt->void_ty); kit_cg_unreachable(cg); } @@ -255,7 +280,7 @@ static uint32_t wasm_cg_checked_add_u32(KitCompiler* c, uint32_t a, uint32_t b, return a + b; } -static void wasm_cg_build_runtime(KitCompiler* c, KitCgBuiltinTypes b, +static void wasm_cg_build_runtime(KitCompiler* c, WasmCgBuiltinTypes b, const WasmModule* m, WasmCgRuntime* rt, KitArena* arena) { KitCgField memory_fields[4]; @@ -344,6 +369,7 @@ static void wasm_cg_build_runtime(KitCompiler* c, KitCgBuiltinTypes b, m->ntables ? kit_arena_zarray(arena, uint32_t, m->ntables) : NULL; table_entries_field_idx = m->ntables ? kit_arena_zarray(arena, uint32_t, m->ntables) : NULL; + rt->void_ty = b.id[KIT_CG_BUILTIN_VOID]; rt->i8_ptr_ty = kit_cg_type_ptr(c, b.id[KIT_CG_BUILTIN_I8], 0); rt->void_ptr_ty = rt->i8_ptr_ty; memset(memory_fields, 0, sizeof memory_fields); @@ -355,7 +381,7 @@ static void wasm_cg_build_runtime(KitCompiler* c, KitCgBuiltinTypes b, memory_fields[2].type = b.id[KIT_CG_BUILTIN_I64]; memory_fields[3].name = kit_sym_intern(c, KIT_SLICE_LIT("flags")); memory_fields[3].type = b.id[KIT_CG_BUILTIN_I32]; - rt->memory_ty = kit_cg_type_record( + rt->memory_ty = wasm_cg_record_type( c, kit_sym_intern(c, KIT_SLICE_LIT("KitWasmMemory")), memory_fields, 4); rt->memory_data_offset = wasm_cg_field_offset(c, rt->memory_ty, 0); rt->memory_pages_offset = wasm_cg_field_offset(c, rt->memory_ty, 1); @@ -368,14 +394,14 @@ static void wasm_cg_build_runtime(KitCompiler* c, KitCgBuiltinTypes b, memset(func_import_fields, 0, sizeof func_import_fields); func_import_fields[0].name = kit_sym_intern(c, KIT_SLICE_LIT("fn")); func_import_fields[0].type = rt->void_ptr_ty; - rt->func_import_ty = kit_cg_type_record( + rt->func_import_ty = wasm_cg_record_type( c, kit_sym_intern(c, KIT_SLICE_LIT("KitWasmFuncImport")), func_import_fields, 1); rt->func_import_fn_offset = wasm_cg_field_offset(c, rt->func_import_ty, 0); memset(global_import_fields, 0, sizeof global_import_fields); global_import_fields[0].name = kit_sym_intern(c, KIT_SLICE_LIT("addr")); global_import_fields[0].type = rt->void_ptr_ty; - rt->global_import_ty = kit_cg_type_record( + rt->global_import_ty = wasm_cg_record_type( c, kit_sym_intern(c, KIT_SLICE_LIT("KitWasmGlobalImport")), global_import_fields, 1); rt->global_import_addr_offset = @@ -385,7 +411,7 @@ static void wasm_cg_build_runtime(KitCompiler* c, KitCgBuiltinTypes b, table_entry_fields[0].type = rt->void_ptr_ty; table_entry_fields[1].name = kit_sym_intern(c, KIT_SLICE_LIT("typeidx")); table_entry_fields[1].type = b.id[KIT_CG_BUILTIN_I32]; - rt->table_entry_ty = kit_cg_type_record( + rt->table_entry_ty = wasm_cg_record_type( c, kit_sym_intern(c, KIT_SLICE_LIT("KitWasmTableEntry")), table_entry_fields, 2); rt->table_entry_ptr_ty = kit_cg_type_ptr(c, rt->table_entry_ty, 0); @@ -402,7 +428,7 @@ static void wasm_cg_build_runtime(KitCompiler* c, KitCgBuiltinTypes b, table_fields[1].type = b.id[KIT_CG_BUILTIN_I32]; table_fields[2].name = kit_sym_intern(c, KIT_SLICE_LIT("max")); table_fields[2].type = b.id[KIT_CG_BUILTIN_I32]; - rt->table_ty = kit_cg_type_record( + rt->table_ty = wasm_cg_record_type( c, kit_sym_intern(c, KIT_SLICE_LIT("KitWasmTable")), table_fields, 3); rt->table_entries_ptr_offset = wasm_cg_field_offset(c, rt->table_ty, 0); rt->table_len_offset = wasm_cg_field_offset(c, rt->table_ty, 1); @@ -415,7 +441,7 @@ static void wasm_cg_build_runtime(KitCompiler* c, KitCgBuiltinTypes b, passive_data_fields[0].type = rt->i8_ptr_ty; passive_data_fields[1].name = kit_sym_intern(c, KIT_SLICE_LIT("len")); passive_data_fields[1].type = b.id[KIT_CG_BUILTIN_I64]; - rt->passive_data_ty = kit_cg_type_record( + rt->passive_data_ty = wasm_cg_record_type( c, kit_sym_intern(c, KIT_SLICE_LIT("KitWasmPassiveDataSegment")), passive_data_fields, 2); rt->passive_data_base_field = 0; @@ -425,7 +451,7 @@ static void wasm_cg_build_runtime(KitCompiler* c, KitCgBuiltinTypes b, passive_elem_fields[0].type = rt->table_entry_ptr_ty; passive_elem_fields[1].name = kit_sym_intern(c, KIT_SLICE_LIT("length")); passive_elem_fields[1].type = b.id[KIT_CG_BUILTIN_I32]; - rt->passive_elem_ty = kit_cg_type_record( + rt->passive_elem_ty = wasm_cg_record_type( c, kit_sym_intern(c, KIT_SLICE_LIT("KitWasmPassiveElemSegment")), passive_elem_fields, 2); rt->passive_elem_entries_field = 0; @@ -514,9 +540,9 @@ static void wasm_cg_build_runtime(KitCompiler* c, KitCgBuiltinTypes b, kit_cg_type_array(c, rt->table_entry_ty, nfuncs ? nfuncs : 1u); nfields++; } - rt->instance_ty = - kit_cg_type_record(c, kit_sym_intern(c, KIT_SLICE_LIT("KitWasmInstance")), - instance_fields, nfields); + rt->instance_ty = wasm_cg_record_type( + c, kit_sym_intern(c, KIT_SLICE_LIT("KitWasmInstance")), instance_fields, + nfields); rt->instance_ptr_ty = kit_cg_type_ptr(c, rt->instance_ty, 0); for (uint32_t i = 0; i < m->nmemories; ++i) rt->memory_offset[i] = @@ -558,7 +584,7 @@ static void wasm_cg_push_instance_lvalue(KitCg* cg, const WasmCgRuntime* rt, /* Add a constant byte offset to the pointer rvalue on TOS, retyping to * `result_ptr_ty`. No-op when offset == 0 (and the type is not retyped). */ -static void wasm_cg_ptr_add_offset(KitCg* cg, KitCgBuiltinTypes b, +static void wasm_cg_ptr_add_offset(KitCg* cg, WasmCgBuiltinTypes b, uint64_t offset, KitCgTypeId result_ptr_ty) { KitCgTypeId i64_ty = b.id[KIT_CG_BUILTIN_I64]; if (offset == 0) { @@ -603,7 +629,7 @@ static void wasm_cg_push_import_func_ptr(KitCg* cg, const WasmCgRuntime* rt, * dereferencing the import indirection if needed. Result type is T* where T is * the global's value type. */ static void wasm_cg_push_global_value_ptr( - KitCompiler* c, KitCg* cg, KitCgBuiltinTypes b, const WasmCgRuntime* rt, + KitCompiler* c, KitCg* cg, WasmCgBuiltinTypes b, const WasmCgRuntime* rt, KitCgLocal instance_local, const WasmModule* m, uint32_t global_index) { KitCgTypeId ptr_ty = kit_cg_type_ptr(c, wasm_cg_type(c, b, m->globals[global_index].type), 0); @@ -651,7 +677,7 @@ static void wasm_cg_push_table_entry_lvalue(KitCg* cg, const WasmCgRuntime* rt, /* Push a pointer rvalue to instance->tables[table_index].entries[index_local]. * The index is loaded from a temp local supplied by the caller (mirrors the * previous helper's signature). */ -static void wasm_cg_push_table_entry_ptr(KitCg* cg, KitCgBuiltinTypes b, +static void wasm_cg_push_table_entry_ptr(KitCg* cg, WasmCgBuiltinTypes b, const WasmCgRuntime* rt, KitCgLocal instance_local, uint32_t table_index, @@ -695,8 +721,9 @@ static void wasm_cg_push_passive_elem_storage_array_lvalue( kit_cg_field(cg, rt->passive_elem_storage_field[elemidx]); } -static void wasm_cg_memory_check(KitCompiler* c, KitCg* cg, KitCgBuiltinTypes b, - const WasmModule* m, const WasmCgRuntime* rt, +static void wasm_cg_memory_check(KitCompiler* c, KitCg* cg, + WasmCgBuiltinTypes b, const WasmModule* m, + const WasmCgRuntime* rt, KitCgLocal instance_local, const WasmInsn* in) { uint32_t width = wasm_mem_width(in->kind); @@ -737,7 +764,7 @@ static void wasm_cg_memory_check(KitCompiler* c, KitCg* cg, KitCgBuiltinTypes b, /* Compute the absolute address (data_ptr + addr_on_tos + offset) as a * pointer rvalue. Stack: [addr] -> [void*]. */ -static void wasm_cg_memory_addr_from_tos(KitCg* cg, KitCgBuiltinTypes b, +static void wasm_cg_memory_addr_from_tos(KitCg* cg, WasmCgBuiltinTypes b, const WasmCgRuntime* rt, const WasmModule* m, KitCgLocal instance_local, @@ -756,7 +783,7 @@ static void wasm_cg_memory_addr_from_tos(KitCg* cg, KitCgBuiltinTypes b, kit_cg_int_to_ptr(cg, rt->i8_ptr_ty); } -static void wasm_cg_rotate(KitCompiler* c, KitCg* cg, KitCgBuiltinTypes b, +static void wasm_cg_rotate(KitCompiler* c, KitCg* cg, WasmCgBuiltinTypes b, WasmValType vt, int right) { KitCgTypeId ty = wasm_cg_type(c, b, vt); KitCgMemAccess mem = wasm_cg_mem(c, b, vt); @@ -795,8 +822,9 @@ static void wasm_cg_rotate(KitCompiler* c, KitCg* cg, KitCgBuiltinTypes b, } static void wasm_cg_checked_divrem(KitCompiler* c, KitCg* cg, - KitCgBuiltinTypes b, const WasmCgRuntime* rt, - WasmValType vt, KitCgIntBinOp op) { + WasmCgBuiltinTypes b, + const WasmCgRuntime* rt, WasmValType vt, + KitCgIntBinOp op) { KitCgTypeId ty = wasm_cg_type(c, b, vt); KitCgMemAccess mem = wasm_cg_mem(c, b, vt); KitCgLocalAttrs attrs; @@ -844,7 +872,7 @@ static void wasm_cg_checked_divrem(KitCompiler* c, KitCg* cg, } static void wasm_cg_checked_trunc(KitCompiler* c, KitCg* cg, - KitCgBuiltinTypes b, const WasmCgRuntime* rt, + WasmCgBuiltinTypes b, const WasmCgRuntime* rt, WasmValType src, WasmValType dst, int is_unsigned) { KitCgTypeId src_ty = wasm_cg_type(c, b, src); @@ -1022,7 +1050,7 @@ static int wasm_fp_cmp_op(uint8_t kind, KitCgFpCmpOp* out) { } } -static void wasm_cg_call_func(KitCompiler* c, KitCg* cg, KitCgBuiltinTypes b, +static void wasm_cg_call_func(KitCompiler* c, KitCg* cg, WasmCgBuiltinTypes b, const WasmFunc* f, const WasmCgRuntime* rt, KitCgSym sym, KitCgTypeId func_type, KitCgLocal instance_local, uint32_t func_index, @@ -1082,7 +1110,7 @@ static void wasm_cg_call_func(KitCompiler* c, KitCg* cg, KitCgBuiltinTypes b, /* Intern a NUL-terminated string as readonly const data and return its * symbol. Each call mints a fresh symbol; the caller is responsible for * deduplication if that matters. */ -static KitCgSym wasm_cg_intern_cstr(KitCg* cg, KitCgBuiltinTypes b, +static KitCgSym wasm_cg_intern_cstr(KitCg* cg, WasmCgBuiltinTypes b, const char* s) { size_t len = 0; while (s && s[len]) ++len; @@ -1095,7 +1123,7 @@ static KitCgSym wasm_cg_intern_cstr(KitCg* cg, KitCgBuiltinTypes b, * count (=0) is emitted; descriptor/type arrays are omitted so the binder can * no-op early via kit_jit_lookup returning NULL. */ static void wasm_cg_emit_host_import_metadata(KitCompiler* c, KitCg* cg, - KitCgBuiltinTypes b, + WasmCgBuiltinTypes b, const WasmModule* m, const WasmCgRuntime* rt, KitArena* arena) { @@ -1508,7 +1536,7 @@ static void wasm_cg_emit_host_import_metadata(KitCompiler* c, KitCg* cg, * The memory-layout array intentionally contains only fixed-width integers, so * the host reader can consume it without target pointer-size ambiguity. */ static void wasm_cg_emit_runtime_layout_metadata(KitCompiler* c, KitCg* cg, - KitCgBuiltinTypes b, + WasmCgBuiltinTypes b, const WasmModule* m, const WasmCgRuntime* rt) { KitCgTypeId u8_ty = b.id[KIT_CG_BUILTIN_I8]; @@ -1598,7 +1626,7 @@ static void wasm_cg_emit_runtime_layout_metadata(KitCompiler* c, KitCg* cg, /* Bounds-check that addr + n <= size. addr/n/size are all treated as i64 * (caller zero-extends i32 inputs first). Traps on overflow or out-of-range * via the rt bounds trap; falls through on success. */ -static void wasm_cg_bulk_bounds_check(KitCg* cg, KitCgBuiltinTypes b, +static void wasm_cg_bulk_bounds_check(KitCg* cg, WasmCgBuiltinTypes b, const WasmCgRuntime* rt, KitCgLocal addr_local, KitCgLocal n_local, KitCgLocal size_local) { @@ -1633,7 +1661,7 @@ static void wasm_cg_bulk_bounds_check(KitCg* cg, KitCgBuiltinTypes b, * pointer comparison. Locals are pre-stored i64 values; src_base and * dst_base are i8* values pre-stored in locals. */ static void wasm_cg_emit_byte_copy_loop( - KitCg* cg, KitCgBuiltinTypes b, const WasmCgRuntime* rt, + KitCg* cg, WasmCgBuiltinTypes b, const WasmCgRuntime* rt, KitCgLocal dst_base_local, KitCgLocal src_base_local, KitCgLocal dst_addr_local, KitCgLocal src_addr_local, KitCgLocal n_local) { KitCgMemAccess i64_mem = wasm_cg_mem_type(b.id[KIT_CG_BUILTIN_I64]); @@ -1757,7 +1785,7 @@ static void wasm_cg_emit_byte_copy_loop( } /* Emit a byte-fill loop: writes (val & 0xff) into dst_base+dst_addr..+n. */ -static void wasm_cg_emit_byte_fill_loop(KitCg* cg, KitCgBuiltinTypes b, +static void wasm_cg_emit_byte_fill_loop(KitCg* cg, WasmCgBuiltinTypes b, const WasmCgRuntime* rt, KitCgLocal dst_base_local, KitCgLocal dst_addr_local, @@ -1808,7 +1836,7 @@ static void wasm_cg_emit_byte_fill_loop(KitCg* cg, KitCgBuiltinTypes b, /* Like wasm_cg_emit_byte_copy_loop but for table entries (struct-sized). */ static void wasm_cg_emit_table_copy_loop( - KitCompiler* c, KitCg* cg, KitCgBuiltinTypes b, const WasmCgRuntime* rt, + KitCompiler* c, KitCg* cg, WasmCgBuiltinTypes b, const WasmCgRuntime* rt, KitCgLocal dst_base_local, KitCgLocal src_base_local, KitCgLocal dst_idx_local, KitCgLocal src_idx_local, KitCgLocal n_local) { KitCgMemAccess i64_mem = wasm_cg_mem_type(b.id[KIT_CG_BUILTIN_I64]); @@ -1970,7 +1998,7 @@ static void wasm_cg_emit_table_copy_loop( } static void wasm_cg_cache_funcref_entry( - KitCompiler* c, KitCg* cg, KitCgBuiltinTypes b, const WasmCgRuntime* rt, + KitCompiler* c, KitCg* cg, WasmCgBuiltinTypes b, const WasmCgRuntime* rt, KitCgLocal ref_local, KitCgLocal fn_local, KitCgLocal typeidx_local, KitCgMemAccess ref_mem, KitCgMemAccess i32_mem) { KitCgLabel is_null = kit_cg_label_new(cg); @@ -2018,13 +2046,13 @@ static void wasm_cg_cache_funcref_entry( * * A frame opened in dead code (after a branch terminator, before the matching * else/end) is marked `entry_dead` and owns no CG scope — it only tracks - * nesting so the matching end pops the right depth. `base` is the CG value-stack - * depth just below the block's result region, used to discard the operands a - * Wasm polymorphic/unreachable region leaves behind. */ + * nesting so the matching end pops the right depth. `base` is the CG + * value-stack depth just below the block's result region, used to discard the + * operands a Wasm polymorphic/unreachable region leaves behind. */ typedef struct WasmCgControl { - uint8_t kind; /* WASM_INSN_BLOCK / LOOP / IF */ - uint8_t seen_else; /* an else has been processed (if frames) */ - uint8_t entry_dead; /* opened in dead code => no CG scope */ + uint8_t kind; /* WASM_INSN_BLOCK / LOOP / IF */ + uint8_t seen_else; /* an else has been processed (if frames) */ + uint8_t entry_dead; /* opened in dead code => no CG scope */ uint8_t pad; KitCgScope scope; /* valid iff !entry_dead */ uint32_t base; /* CG value-stack depth below the block's results */ @@ -2038,7 +2066,7 @@ typedef struct WasmCgControl { /* Resolve a block/loop/if instruction's signature into CG-type arrays stored on * `fr` (arena-owned). */ -static void wasm_cg_resolve_sig(KitCompiler* c, KitCgBuiltinTypes b, +static void wasm_cg_resolve_sig(KitCompiler* c, WasmCgBuiltinTypes b, const WasmModule* m, KitArena* arena, const WasmInsn* in, WasmCgControl* fr) { WasmValType scratch; @@ -2085,7 +2113,8 @@ static KitCgLocal* wasm_cg_save_values(KitCg* cg, KitArena* arena, attrs.flags = KIT_CG_LOCAL_COMPILER_TEMP; for (k = 0; k < n; ++k) tmps[k] = kit_cg_local(cg, types[k], attrs); for (k = 0; k < n; ++k) - kit_cg_local_write(cg, tmps[n - 1u - k], wasm_cg_mem_type(types[n - 1u - k])); + kit_cg_local_write(cg, tmps[n - 1u - k], + wasm_cg_mem_type(types[n - 1u - k])); if (restore) wasm_cg_push_temps(cg, types, tmps, n); return tmps; } @@ -2102,7 +2131,8 @@ static void wasm_cg_branch_to(KitCg* cg, WasmCgControl* fr) { } } -/* The label values a branch to `fr` transfers (loop -> params, else results). */ +/* The label values a branch to `fr` transfers (loop -> params, else results). + */ static const KitCgTypeId* wasm_cg_label_types(const WasmCgControl* fr, uint32_t* n) { if (fr->kind == WASM_INSN_LOOP) { @@ -2129,9 +2159,10 @@ static void wasm_cg_enter_else(KitCg* cg, WasmCgControl* fr, int then_dead) { * (just nesting); otherwise it opens the structured scope, and an `if` also * lifts the condition, snapshots its params for the else arm, and branches the * false arm to a fresh else label. */ -static void wasm_cg_open_frame(KitCompiler* c, KitCg* cg, KitCgBuiltinTypes b, +static void wasm_cg_open_frame(KitCompiler* c, KitCg* cg, WasmCgBuiltinTypes b, const WasmModule* m, KitArena* arena, - WasmCgControl* fr, const WasmInsn* in, int dead) { + WasmCgControl* fr, const WasmInsn* in, + int dead) { KitCgScopeSig sig; memset(fr, 0, sizeof *fr); fr->kind = in->kind; @@ -2158,7 +2189,8 @@ static void wasm_cg_open_frame(KitCompiler* c, KitCg* cg, KitCgBuiltinTypes b, /* Lift the condition above the params, snapshot the params for the else * arm, then re-test the condition once the block scope is open. */ kit_cg_local_write(cg, cond_tmp, i32_mem); - fr->param_tmps = wasm_cg_save_values(cg, arena, fr->params, fr->nparams, 1); + fr->param_tmps = + wasm_cg_save_values(cg, arena, fr->params, fr->nparams, 1); fr->scope = kit_cg_block_begin_sig(cg, &sig); kit_cg_local_read(cg, cond_tmp, i32_mem); kit_cg_branch_false(cg, fr->else_label); @@ -2171,7 +2203,8 @@ static void wasm_cg_open_frame(KitCompiler* c, KitCg* cg, KitCgBuiltinTypes b, fr->base = kit_cg_stack_depth(cg) - fr->nparams; } -/* Process an `else`: returns the new dead state (the else arm's reachability). */ +/* Process an `else`: returns the new dead state (the else arm's reachability). + */ static int wasm_cg_handle_else(KitCg* cg, WasmCgControl* fr, int dead) { if (fr->entry_dead) return dead; /* else of a dead if; stays dead */ wasm_cg_enter_else(cg, fr, dead); @@ -2198,7 +2231,7 @@ static int wasm_cg_handle_end(KitCg* cg, WasmCgControl* fr, int dead) { } void wasm_emit_cg_into(KitCompiler* c, KitCg* cg, const WasmModule* m) { - KitCgBuiltinTypes b = kit_cg_builtin_types(c); + WasmCgBuiltinTypes b = wasm_cg_builtin_types(c); WasmCgRuntime rt; /* A KitArena owns transient frontend-side codegen state — sym tables, func * types, per-function local arrays, instance-record field tables, call @@ -2219,6 +2252,7 @@ void wasm_emit_cg_into(KitCompiler* c, KitCg* cg, const WasmModule* m) { KitCgFuncSig sig; KitCgDecl decl; memset(&sig, 0, sizeof sig); + sig.result.type = b.id[KIT_CG_BUILTIN_VOID]; sig.call_conv = KIT_CG_CC_TARGET_C; rt.trap_func_ty = kit_cg_type_func(c, sig); if (!rt.trap_func_ty) @@ -2244,6 +2278,7 @@ void wasm_emit_cg_into(KitCompiler* c, KitCg* cg, const WasmModule* m) { memset(&init_param, 0, sizeof init_param); init_param.type = rt.instance_ptr_ty; memset(&sig, 0, sizeof sig); + sig.result.type = b.id[KIT_CG_BUILTIN_VOID]; sig.params = &init_param; sig.nparams = 1; sig.call_conv = KIT_CG_CC_TARGET_C; @@ -2270,6 +2305,7 @@ void wasm_emit_cg_into(KitCompiler* c, KitCg* cg, const WasmModule* m) { cg_params[j + 1u].type = wasm_cg_type(c, b, f->params[j]); } memset(&sig, 0, sizeof sig); + sig.result.type = b.id[KIT_CG_BUILTIN_VOID]; if (f->nresults) sig.result.type = wasm_cg_type(c, b, f->results[0]); sig.params = cg_params; sig.nparams = f->nparams + 1u; @@ -2298,7 +2334,7 @@ void wasm_emit_cg_into(KitCompiler* c, KitCg* cg, const WasmModule* m) { } for (uint32_t k = 0; k < WASM_TRAP_COUNT; ++k) { kit_cg_func_begin(cg, rt.trap_syms[k]); - wasm_cg_emit_raw_trap(cg); + wasm_cg_emit_raw_trap(c, cg); kit_cg_func_end(cg); } if (init_sym) { @@ -2710,7 +2746,8 @@ void wasm_emit_cg_into(KitCompiler* c, KitCg* cg, const WasmModule* m) { sw.ncases = in.ntargets - 1u; sw.default_label = per_target[in.ntargets - 1u]; /* br_table is dense-by-construction (case values 0..N-1); hint a - * jump table. Targets that ignore the hint fall back to a cmp chain. */ + * jump table. Targets that ignore the hint fall back to a cmp chain. + */ sw.hint = KIT_CG_SWITCH_JUMP_TABLE; kit_cg_switch(cg, sw); for (q = 0; q < ndistinct; ++q) { @@ -2824,6 +2861,7 @@ void wasm_emit_cg_into(KitCompiler* c, KitCg* cg, const WasmModule* m) { for (uint32_t p = 0; p < t->nparams; ++p) indirect_params[p + 1u].type = wasm_cg_type(c, b, t->params[p]); memset(&indirect_sig, 0, sizeof indirect_sig); + indirect_sig.result.type = b.id[KIT_CG_BUILTIN_VOID]; if (t->nresults) indirect_sig.result.type = wasm_cg_type(c, b, t->results[0]); indirect_sig.params = indirect_params; @@ -2996,6 +3034,7 @@ void wasm_emit_cg_into(KitCompiler* c, KitCg* cg, const WasmModule* m) { for (uint32_t p = 0; p < t->nparams; ++p) ref_params[p + 1u].type = wasm_cg_type(c, b, t->params[p]); memset(&ref_sig, 0, sizeof ref_sig); + ref_sig.result.type = b.id[KIT_CG_BUILTIN_VOID]; if (t->nresults) ref_sig.result.type = wasm_cg_type(c, b, t->results[0]); ref_sig.params = ref_params; diff --git a/mk/test.mk b/mk/test.mk @@ -495,6 +495,7 @@ CG_API_TEST_BIN = build/test/cg_api_test CG_SWITCH_TEST_BIN = build/test/cg_switch_test CG_FP_CMP_TEST_BIN = build/test/cg_fp_cmp_test CG_CONTROL_TEST_BIN = build/test/cg_control_test +CG_CONST_TEST_BIN = build/test/cg_const_test STRENGTH_REDUCE_TEST_BIN = build/test/strength_reduce_test TARGET_TEST_BIN = build/test/target_test HASH_TEST_BIN = build/test/hash_test @@ -505,6 +506,7 @@ NATIVE_DIRECT_TARGET_TEST_BIN = build/test/native_direct_target_test test-cg-api: $(TARGET_TEST_BIN) $(CG_API_TEST_BIN) $(CG_SWITCH_TEST_BIN) \ $(CG_FP_CMP_TEST_BIN) $(CG_CONTROL_TEST_BIN) \ + $(CG_CONST_TEST_BIN) \ $(STRENGTH_REDUCE_TEST_BIN) \ $(PANIC_RECOVERY_TEST_BIN) $(TARGET_TEST_BIN) @@ -512,6 +514,7 @@ test-cg-api: $(TARGET_TEST_BIN) $(CG_API_TEST_BIN) $(CG_SWITCH_TEST_BIN) \ $(CG_SWITCH_TEST_BIN) $(CG_FP_CMP_TEST_BIN) $(CG_CONTROL_TEST_BIN) + $(CG_CONST_TEST_BIN) $(STRENGTH_REDUCE_TEST_BIN) $(PANIC_RECOVERY_TEST_BIN) diff --git a/mk/test_unit.mk b/mk/test_unit.mk @@ -31,7 +31,7 @@ UNIT_CFLAGS_INTERNAL = $(HOST_CFLAGS) -Iinclude -Isrc -Itest UNIT_TESTS_PUBLIC := \ ar_test target_test cg_api_test cg_switch_test cg_fp_cmp_test \ - cg_control_test hash_test \ + cg_control_test cg_const_test hash_test \ panic_recovery_test profile_test \ rv64_jit_test rv32_jit_test aa64_inline_test rv64_inline_test x64_inline_test \ strength_reduce_test @@ -44,6 +44,7 @@ cg_api_test_SRC := test/api/cg_type_test.c cg_switch_test_SRC := test/api/cg_switch_test.c cg_fp_cmp_test_SRC := test/api/cg_fp_cmp_test.c cg_control_test_SRC := test/api/cg_control_test.c +cg_const_test_SRC := test/api/cg_const_test.c strength_reduce_test_SRC := test/cg/strength_reduce_test.c rv64_jit_test_SRC := test/link/rv64_jit_test.c rv32_jit_test_SRC := test/link/rv32_jit_test.c diff --git a/src/abi/abi.c b/src/abi/abi.c @@ -124,7 +124,9 @@ static ABIRecordLayout* compute_record_layout(TargetABI* a, KitCgTypeId id) { ABIRecordLayout* L = arena_new(a->c->tu, ABIRecordLayout); const CgType* t = cg_type_get(a->c, id); if (!L) return NULL; - if (!t || t->kind != KIT_CG_TYPE_RECORD) return NULL; + if (!t || t->kind != KIT_CG_TYPE_RECORD || + !(t->record.flags & CG_TYPE_RECORD_COMPLETE)) + return NULL; memset(L, 0, sizeof *L); ABIFieldLayout* fl = NULL; if (t->record.nfields) { @@ -151,7 +153,9 @@ const ABIRecordLayout* abi_cg_record_layout(TargetABI* a, KitCgTypeId id) { const CgType* t = cg_type_get(a->c, id); ABIRecordLayout** hit; ABIRecordLayout* L; - if (!t || t->kind != KIT_CG_TYPE_RECORD) return NULL; + if (!t || t->kind != KIT_CG_TYPE_RECORD || + !(t->record.flags & CG_TYPE_RECORD_COMPLETE)) + return NULL; hit = AbiRecLayoutMap_get(&a->rec_cache, id); if (hit) return *hit; L = compute_record_layout(a, id); diff --git a/src/arch/c_target/c_emit.c b/src/arch/c_target/c_emit.c @@ -2297,9 +2297,12 @@ void c_emit_ret(CTarget* t, CGLocal value) { /* CG emits a defensive void-return epilogue at the end of every function. For * a non-void function that's unreachable; emitting a bare `return;` would * trip -Wreturn-type. Spell it as `__builtin_unreachable()` so the host C - * compiler sees the path is dead without us inventing a fake value. */ + * compiler sees the path is dead without us inventing a fake value. A genuine + * void return (the function's result is the void builtin) must still emit + * `return;` -- testing against KIT_CG_TYPE_NONE here would misfire, since the + * cutover represents void as the void builtin, not NONE. */ if (value == CG_LOCAL_NONE && t->cur_fn) { - if (t->cur_fn->result_type != KIT_CG_TYPE_NONE) { + if (!cg_type_is_void(t->c, t->cur_fn->result_type)) { cbuf_puts(&t->body, " __builtin_unreachable();\n"); t->last_was_terminator = 1; return; diff --git a/src/arch/riscv/emu.c b/src/arch/riscv/emu.c @@ -74,12 +74,11 @@ static KitCgTypeId rv64_emu_func_type(KitCompiler* c, KitCgTypeId ret, static void rv64_emu_lift_syms_init(KitCompiler* c, KitCg* cg, Rv64EmuLiftSyms* out) { - KitCgBuiltinTypes bi = kit_cg_builtin_types(c); KitCgTypeId params[5]; memset(out, 0, sizeof(*out)); - out->void_ty = bi.id[KIT_CG_BUILTIN_VOID]; - out->i32 = bi.id[KIT_CG_BUILTIN_I32]; - out->i64 = bi.id[KIT_CG_BUILTIN_I64]; + out->void_ty = kit_cg_type_builtin(c, KIT_CG_BUILTIN_VOID); + out->i32 = kit_cg_type_builtin(c, KIT_CG_BUILTIN_I32); + out->i64 = kit_cg_type_builtin(c, KIT_CG_BUILTIN_I64); out->i64_ptr = kit_cg_type_ptr(c, out->i64, 0); out->thread_ptr = emu_thread_type((Compiler*)c); diff --git a/src/cg/arith.c b/src/cg/arith.c @@ -28,6 +28,7 @@ static int api_try_fold_int_convert(KitCg* g, ConvKind ck, KitCgTypeId sty, void api_cg_binop(KitCg* g, BinOp iop, u32 flags) { ApiSValue b, a; + ApiConstValue cb, ca, result_const; CgTarget* T; KitCgTypeId ty; Operand ra, rb; @@ -37,10 +38,20 @@ void api_cg_binop(KitCg* g, BinOp iop, u32 flags) { i64 folded; int can_delay; if (!g) return; + cb = api_const_at(g, 0); + ca = api_const_at(g, 1); T = g->target; b = api_pop(g); a = api_pop(g); ty = a.type ? a.type : b.type; + api_const_fold_binop(g, iop, ty, ca, cb, flags, &result_const); + if (api_unevaluated(g)) { + api_release(g, &a); + api_release(g, &b); + api_push(g, api_uneval_value(g, ty)); + api_const_set_top(g, result_const); + return; + } /* Delayability is a pure function of (ty, flags), neither of which changes * below (strength-reduce rewrites the op and operands, not the type), so * classify the foldable int once instead of re-deriving it at each of the @@ -52,6 +63,7 @@ void api_cg_binop(KitCg* g, BinOp iop, u32 flags) { api_release(g, &a); api_release(g, &b); api_push(g, api_make_sv(api_op_imm(folded, ty), ty)); + api_const_set_top(g, result_const); return; } @@ -60,11 +72,11 @@ void api_cg_binop(KitCg* g, BinOp iop, u32 flags) { * identity / fallback machinery as any other shift or and. */ if (!flags) api_try_strength_reduce(g, &iop, ty, &a, &b); - if (can_delay && - api_try_fold_arith_chain(g, iop, ty, &a, &b, &folded_sv)) { + if (can_delay && api_try_fold_arith_chain(g, iop, ty, &a, &b, &folded_sv)) { api_release(g, &a); api_release(g, &b); api_push(g, folded_sv); + api_const_set_top(g, result_const); return; } @@ -81,20 +93,21 @@ void api_cg_binop(KitCg* g, BinOp iop, u32 flags) { api_release(g, &a); api_release(g, &b); api_push(g, folded_sv); + api_const_set_top(g, result_const); return; } - if (can_delay && - (ra.kind == OPK_LOCAL || rb.kind == OPK_LOCAL) && + if (can_delay && (ra.kind == OPK_LOCAL || rb.kind == OPK_LOCAL) && (ra.kind == OPK_LOCAL || ra.kind == OPK_IMM) && (rb.kind == OPK_LOCAL || rb.kind == OPK_IMM)) { int a_owned = api_sv_owns_operand_local(&a, &ra); int b_owned = api_sv_owns_operand_local(&b, &rb); api_push(g, api_make_arith_binop(g, iop, ra, rb, ty, a_owned, b_owned)); - if (a_owned) a.res = RES_INHERENT; - if (b_owned) b.res = RES_INHERENT; + if (a_owned) api_sv_set_res(&a, RES_INHERENT); + if (b_owned) api_sv_set_res(&b, RES_INHERENT); api_release(g, &a); api_release(g, &b); + api_const_set_top(g, result_const); return; } @@ -108,10 +121,12 @@ void api_cg_binop(KitCg* g, BinOp iop, u32 flags) { api_release(g, &a); api_release(g, &b); api_push(g, api_make_sv(dst, ty)); + api_const_set_top(g, result_const); } void api_cg_unop(KitCg* g, UnOp iop, u32 flags) { ApiSValue a; + ApiConstValue ca, result_const; CgTarget* T; KitCgTypeId ty; Operand ra; @@ -121,9 +136,17 @@ void api_cg_unop(KitCg* g, UnOp iop, u32 flags) { i64 folded; int can_delay; if (!g) return; + ca = api_const_at(g, 0); T = g->target; a = api_pop(g); ty = a.type ? a.type : a.op.type; + api_const_fold_unop(g, iop, ty, ca, flags, &result_const); + if (api_unevaluated(g)) { + api_release(g, &a); + api_push(g, api_uneval_value(g, ty)); + api_const_set_top(g, result_const); + return; + } /* Pure function of (ty, flags); classify the foldable int once for both delay * gates below. */ can_delay = api_can_delay_int_arith(g, ty, flags); @@ -140,6 +163,7 @@ void api_cg_unop(KitCg* g, UnOp iop, u32 flags) { T->unop(T, iop, dst, ra); api_release(g, &a); api_push(g, api_make_sv(dst, ty)); + api_const_set_top(g, result_const); return; } @@ -147,9 +171,10 @@ void api_cg_unop(KitCg* g, UnOp iop, u32 flags) { * place. For FP this flips ordered<->unordered as well as the relation (via * api_invert_cmp), so `!(a<b)` becomes UGE (NaN -> true), matching IEEE * negation. The inverted compare keeps the same i32 result type. */ - if (iop == UO_NOT && a.kind == SV_CMP) { + if (iop == UO_NOT && api_sv_kind(&a) == SV_CMP) { a.delayed->cmp.op = api_invert_cmp(a.delayed->cmp.op); api_push(g, a); + api_const_set_top(g, result_const); return; } @@ -157,12 +182,14 @@ void api_cg_unop(KitCg* g, UnOp iop, u32 flags) { api_try_fold_int_unop(g, iop, ty, a.op.v.imm, &folded)) { api_release(g, &a); api_push(g, api_make_sv(api_op_imm(folded, ty), ty)); + api_const_set_top(g, result_const); return; } if (can_delay && api_try_fold_unary_chain(&a, iop, ty, &folded_sv)) { api_release(g, &a); api_push(g, folded_sv); + api_const_set_top(g, result_const); return; } @@ -170,8 +197,9 @@ void api_cg_unop(KitCg* g, UnOp iop, u32 flags) { if (can_delay && ra.kind == OPK_LOCAL) { int a_owned = api_sv_owns_operand_local(&a, &ra); api_push(g, api_make_arith_unop(g, iop, ra, ty, a_owned)); - if (a_owned) a.res = RES_INHERENT; + if (a_owned) api_sv_set_res(&a, RES_INHERENT); api_release(g, &a); + api_const_set_top(g, result_const); return; } rr = api_alloc_temp_local(g, ty); @@ -180,25 +208,38 @@ void api_cg_unop(KitCg* g, UnOp iop, u32 flags) { T->unop(T, iop, dst, ra); api_release(g, &a); api_push(g, api_make_sv(dst, ty)); + api_const_set_top(g, result_const); } void api_cg_cmp(KitCg* g, CmpOp cop) { ApiSValue b, a; + ApiConstValue cb, ca, result_const; KitCgTypeId opty; KitCgTypeId i32; Operand ra, rb; i64 folded; if (!g) return; + cb = api_const_at(g, 0); + ca = api_const_at(g, 1); b = api_pop(g); a = api_pop(g); opty = a.type ? a.type : b.type; i32 = builtin_id(KIT_CG_BUILTIN_I32); + api_const_fold_cmp(g, cop, ca, cb, &result_const); + if (api_unevaluated(g)) { + api_release(g, &a); + api_release(g, &b); + api_push(g, api_uneval_value(g, i32)); + api_const_set_top(g, result_const); + return; + } if (api_sv_op_is(&a, OPK_IMM) && api_sv_op_is(&b, OPK_IMM) && api_try_fold_int_cmp(g, cop, opty, a.op.v.imm, b.op.v.imm, &folded)) { api_release(g, &a); api_release(g, &b); api_push(g, api_make_sv(api_op_imm(folded, i32), i32)); + api_const_set_top(g, result_const); return; } @@ -212,9 +253,9 @@ void api_cg_cmp(KitCg* g, CmpOp cop) { * unchanged via api_materialize_cmp_to, which calls T->cmp with the same * opcode the eager path used to. */ api_push(g, - api_make_cmp(g, cop, ra, rb, i32, - api_sv_owns_operand_local(&a, &ra), + api_make_cmp(g, cop, ra, rb, i32, api_sv_owns_operand_local(&a, &ra), api_sv_owns_operand_local(&b, &rb))); + api_const_set_top(g, result_const); } int api_try_i128_convert(KitCg* g, ConvKind ck, KitCgTypeId sty, @@ -224,6 +265,7 @@ int api_try_wide8_convert(KitCg* g, ConvKind ck, KitCgTypeId sty, void api_cg_convert_kind(KitCg* g, KitCgTypeId dst_type, ConvKind ck) { ApiSValue v; + ApiConstValue in_const, result_const; CgTarget* T; KitCgTypeId sty; KitCgTypeId dty; @@ -237,16 +279,25 @@ void api_cg_convert_kind(KitCg* g, KitCgTypeId dst_type, ConvKind ck) { * return-before-pop order on an invalid dst_type. */ dty = api_unalias_type(g->c, dst_type); if (!dty) return; + in_const = api_const_at(g, 0); v = api_pop(g); sty = api_unalias_type(g->c, v.type ? v.type : v.op.type); if (!sty) { api_release(g, &v); return; } + api_const_fold_convert(g, ck, sty, dty, in_const, &result_const); + if (api_unevaluated(g)) { + api_release(g, &v); + api_push(g, api_uneval_value(g, dty)); + api_const_set_top(g, result_const); + return; + } if (sty == dty) { v.type = dty; v.op.type = dty; api_push(g, v); + api_const_set_top(g, result_const); return; } if (api_sv_op_is(&v, OPK_IMM)) { @@ -259,11 +310,18 @@ void api_cg_convert_kind(KitCg* g, KitCgTypeId dst_type, ConvKind ck) { api_push(g, api_make_wide8_int_const(g, folded, dty)); else api_push(g, api_make_sv(api_op_imm(folded, dty), dty)); + api_const_set_top(g, result_const); return; } } - if (api_try_i128_convert(g, ck, sty, dty, &v)) return; - if (api_try_wide8_convert(g, ck, sty, dty, &v)) return; + if (api_try_i128_convert(g, ck, sty, dty, &v)) { + api_const_set_top(g, result_const); + return; + } + if (api_try_wide8_convert(g, ck, sty, dty, &v)) { + api_const_set_top(g, result_const); + return; + } /* A bitcast between two narrow types of equal width and register class — * pointer<->pointer-width integer (the frontend's ptr_to_int/int_to_ptr that * brackets every field-offset add) or pointer<->pointer — reinterprets the @@ -292,6 +350,7 @@ void api_cg_convert_kind(KitCg* g, KitCgTypeId dst_type, ConvKind ck) { v.type = dty; v.op.type = dty; api_push(g, v); + api_const_set_top(g, result_const); return; } } @@ -307,12 +366,9 @@ void api_cg_convert_kind(KitCg* g, KitCgTypeId dst_type, ConvKind ck) { Operand dst_addr; Operand src_addr; AggregateAccess agg; - src_lv.lvalue = 1; - dst_addr = api_lvalue_addr( - g, - &(ApiSValue){ - .op = dst_lv, .type = dty, .kind = SV_OPERAND, .lvalue = 1}, - ptr_ty); + ApiSValue dst_place = api_make_lv(dst_lv, dty); + api_sv_set_lvalue(&src_lv, 1); + dst_addr = api_lvalue_addr(g, &dst_place, ptr_ty); src_addr = api_lvalue_addr(g, &src_lv, cg_type_ptr_to(g->c, sty)); memset(&agg, 0, sizeof agg); agg.size = 16; @@ -336,6 +392,7 @@ void api_cg_convert_kind(KitCg* g, KitCgTypeId dst_type, ConvKind ck) { } api_release(g, &v); api_push(g, api_make_lv(dst_lv, dty)); + api_const_set_top(g, result_const); return; } @@ -352,6 +409,7 @@ void api_cg_convert_kind(KitCg* g, KitCgTypeId dst_type, ConvKind ck) { T->convert(T, ck, dst, src); api_release(g, &v); api_push(g, api_make_sv(dst, dty)); + api_const_set_top(g, result_const); } /* ============================================================ @@ -1122,8 +1180,23 @@ int api_try_i128_convert(KitCg* g, ConvKind ck, KitCgTypeId sty, void kit_cg_int_binop(KitCg* g, KitCgIntBinOp op, uint32_t flags) { BinOp iop = api_map_int_binop(op); + ApiConstValue result_const; + KitCgTypeId ty = KIT_CG_TYPE_NONE; + if (g && g->sp >= 2u) { + ApiConstValue cb = api_const_at(g, 0); + ApiConstValue ca = api_const_at(g, 1); + ty = api_sv_type(&g->stack[g->sp - 2u]); + api_const_fold_binop(g, iop, ty, ca, cb, flags, &result_const); + } else { + result_const = api_const_unknown(KIT_CG_TYPE_NONE); + } + if (api_unevaluated(g)) { + api_cg_binop(g, iop, flags); + return; + } if (g && (api_i128_stack_top(g, 0) || api_i128_stack_top(g, 1))) { api_i128_binop(g, iop); + api_const_set_top(g, result_const); return; } /* 64-bit int split into 32-bit lanes: mul/div/rem/shift become __*di3 @@ -1136,6 +1209,7 @@ void kit_cg_int_binop(KitCg* g, KitCgIntBinOp op, uint32_t flags) { api_wideint64_binop(g, iop); else api_wide64_binop_inline(g, iop); + api_const_set_top(g, result_const); return; } api_cg_binop(g, iop, flags); @@ -1143,8 +1217,22 @@ void kit_cg_int_binop(KitCg* g, KitCgIntBinOp op, uint32_t flags) { void kit_cg_int_unop(KitCg* g, KitCgIntUnOp op, uint32_t flags) { UnOp iop = api_map_int_unop(op); + ApiConstValue result_const; + KitCgTypeId ty = KIT_CG_TYPE_NONE; + if (g && g->sp >= 1u) { + ApiConstValue ca = api_const_at(g, 0); + ty = api_sv_type(&g->stack[g->sp - 1u]); + api_const_fold_unop(g, iop, ty, ca, flags, &result_const); + } else { + result_const = api_const_unknown(KIT_CG_TYPE_NONE); + } + if (api_unevaluated(g)) { + api_cg_unop(g, iop, flags); + return; + } if (g && api_i128_stack_top(g, 0) && (iop == UO_NEG || iop == UO_BNOT)) { api_i128_unop(g, iop); + api_const_set_top(g, result_const); return; } /* Split 64-bit int: neg/bnot are inline 2-word lane ops; logical-not (!x) is @@ -1152,6 +1240,7 @@ void kit_cg_int_unop(KitCg* g, KitCgIntUnOp op, uint32_t flags) { if (g && api_wide64_stack_top(g, 0)) { if (iop == UO_NEG || iop == UO_BNOT) { api_wide64_unop_inline(g, iop); + api_const_set_top(g, result_const); return; } if (iop == UO_NOT) { @@ -1164,6 +1253,7 @@ void kit_cg_int_unop(KitCg* g, KitCgIntUnOp op, uint32_t flags) { kit_cg_push_int(g, 0, i32); api_cg_cmp(g, CMP_EQ); api_cg_convert_kind(g, ty, CV_ZEXT); + api_const_set_top(g, result_const); return; } } @@ -1172,12 +1262,26 @@ void kit_cg_int_unop(KitCg* g, KitCgIntUnOp op, uint32_t flags) { void kit_cg_int_cmp(KitCg* g, KitCgIntCmpOp op) { CmpOp cop = api_map_int_cmp(op); + ApiConstValue result_const; + if (g && g->sp >= 2u) { + ApiConstValue cb = api_const_at(g, 0); + ApiConstValue ca = api_const_at(g, 1); + api_const_fold_cmp(g, cop, ca, cb, &result_const); + } else { + result_const = api_const_unknown(builtin_id(KIT_CG_BUILTIN_I32)); + } + if (api_unevaluated(g)) { + api_cg_cmp(g, cop); + return; + } if (g && (api_i128_stack_top(g, 0) || api_i128_stack_top(g, 1))) { api_i128_cmp(g, cop); + api_const_set_top(g, result_const); return; } if (g && (api_wide64_stack_top(g, 0) || api_wide64_stack_top(g, 1))) { api_wide64_cmp_inline(g, cop); + api_const_set_top(g, result_const); return; } api_cg_cmp(g, cop); @@ -1339,6 +1443,10 @@ void api_f128_call_unary(KitCg* g, const char* name, KitCgTypeId ret, void kit_cg_fp_binop(KitCg* g, KitCgFpBinOp op, uint32_t flags) { (void)flags; + if (api_unevaluated(g)) { + api_cg_binop(g, api_map_fp_binop(op), 0); + return; + } if (api_f128_stack_top(g, 0) || api_f128_stack_top(g, 1)) { api_softfp_binop(g, api_f128_binop_helper(op), builtin_id(KIT_CG_BUILTIN_F128), "f128"); @@ -1363,6 +1471,10 @@ void kit_cg_fp_unop(KitCg* g, KitCgFpUnOp op, uint32_t flags) { if (op != KIT_CG_FP_NEG) { compiler_panic(g->c, g->cur_loc, "KitCg: FP unary op unsupported"); } + if (api_unevaluated(g)) { + api_cg_unop(g, UO_FNEG, 0); + return; + } if (api_f128_stack_top(g, 0)) { KitCgTypeId f128 = builtin_id(KIT_CG_BUILTIN_F128); api_f128_call_unary(g, "__negtf2", f128, f128); @@ -1502,6 +1614,10 @@ void kit_cg_fp_cmp(KitCg* g, KitCgFpCmpOp op) { * must split "equal" from "unordered", need a second (__unord*2) call. The * convention is width-neutral, so the same logic drives the tf and df * suffixes via api_softfp_cmp. */ + if (api_unevaluated(g)) { + api_cg_cmp(g, api_map_fp_cmp(op)); + return; + } if (api_f128_stack_top(g, 0) || api_f128_stack_top(g, 1)) { api_softfp_cmp(g, op, "tf", builtin_id(KIT_CG_BUILTIN_F128)); return; @@ -1547,6 +1663,11 @@ void kit_cg_fpext(KitCg* g, KitCgTypeId dst) { * redundant: for a valid id resolve_type(dst)==dst, for an invalid id every * predicate is false and the fall-through validates. */ KitCgTypeId dty = dst; + if (!g) return; + if (api_unevaluated(g)) { + api_cg_convert_kind(g, dst, CV_FEXT); + return; + } if (api_is_f128_type(g->c, dty)) { ApiSValue v = api_pop(g); KitCgTypeId sty = api_unalias_type(g->c, api_sv_type(&v)); @@ -1569,8 +1690,14 @@ void kit_cg_fpext(KitCg* g, KitCgTypeId dst) { void kit_cg_fptrunc(KitCg* g, KitCgTypeId dst) { /* resolve_type(dst)==dst for any valid id; the predicates / fall-through - * validate, so drop the redundant standalone validation (see kit_cg_fpext). */ + * validate, so drop the redundant standalone validation (see kit_cg_fpext). + */ KitCgTypeId dty = dst; + if (!g) return; + if (api_unevaluated(g)) { + api_cg_convert_kind(g, dst, CV_FTRUNC); + return; + } if (api_f128_stack_top(g, 0)) { ApiSValue v = api_pop(g); KitCgTypeId f128 = builtin_id(KIT_CG_BUILTIN_F128); @@ -1645,6 +1772,10 @@ static void api_fp_conv_name(char* buf, size_t cap, ApiFpConvOp op, void kit_cg_sint_to_float(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) { (void)rounding; + if (api_unevaluated(g)) { + api_cg_convert_kind(g, dst, CV_ITOF_S); + return; + } if (api_is_f128_type(g->c, dst)) { ApiSValue v = api_pop(g); KitCgTypeId sty = api_unalias_type(g->c, api_sv_type(&v)); @@ -1652,8 +1783,7 @@ void kit_cg_sint_to_float(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) { char name[16]; api_fp_conv_name(name, sizeof name, API_FPCONV_SINT_TO_FLOAT, "tf", sz); api_push(g, v); - api_f128_call_unary(g, name, dst, - api_int_builtin_for_size(sz)); + api_f128_call_unary(g, name, dst, api_int_builtin_for_size(sz)); return; } /* signed int -> soft double: __floatsidf (i32) / __floatdidf (i64). */ @@ -1664,20 +1794,17 @@ void kit_cg_sint_to_float(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) { char name[16]; api_fp_conv_name(name, sizeof name, API_FPCONV_SINT_TO_FLOAT, "df", sz); api_push(g, v); - api_f128_call_unary(g, name, dst, - api_int_builtin_for_size(sz)); + api_f128_call_unary(g, name, dst, api_int_builtin_for_size(sz)); return; } /* signed split-i64 -> hardware single float: use __floatdisf. */ if (api_wide64_stack_top(g, 0)) { - api_f128_call_unary(g, "__floatdisf", dst, - builtin_id(KIT_CG_BUILTIN_I64)); + api_f128_call_unary(g, "__floatdisf", dst, builtin_id(KIT_CG_BUILTIN_I64)); return; } /* i32 -> soft single float (ilp32, no FPU): __floatsisf. */ if (api_type_is_soft_single(g, dst)) { - api_f128_call_unary(g, "__floatsisf", dst, - builtin_id(KIT_CG_BUILTIN_I32)); + api_f128_call_unary(g, "__floatsisf", dst, builtin_id(KIT_CG_BUILTIN_I32)); return; } api_cg_convert_kind(g, dst, CV_ITOF_S); @@ -1685,6 +1812,10 @@ void kit_cg_sint_to_float(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) { void kit_cg_uint_to_float(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) { (void)rounding; + if (api_unevaluated(g)) { + api_cg_convert_kind(g, dst, CV_ITOF_U); + return; + } if (api_is_f128_type(g->c, dst)) { ApiSValue v = api_pop(g); KitCgTypeId sty = api_unalias_type(g->c, api_sv_type(&v)); @@ -1692,8 +1823,7 @@ void kit_cg_uint_to_float(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) { char name[16]; api_fp_conv_name(name, sizeof name, API_FPCONV_UINT_TO_FLOAT, "tf", sz); api_push(g, v); - api_f128_call_unary(g, name, dst, - api_int_builtin_for_size(sz)); + api_f128_call_unary(g, name, dst, api_int_builtin_for_size(sz)); return; } /* unsigned int -> soft double: __floatunsidf (i32) / __floatundidf (i64). */ @@ -1704,8 +1834,7 @@ void kit_cg_uint_to_float(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) { char name[16]; api_fp_conv_name(name, sizeof name, API_FPCONV_UINT_TO_FLOAT, "df", sz); api_push(g, v); - api_f128_call_unary(g, name, dst, - api_int_builtin_for_size(sz)); + api_f128_call_unary(g, name, dst, api_int_builtin_for_size(sz)); return; } /* unsigned i64 -> hardware single float: __floatundisf. */ @@ -1725,6 +1854,10 @@ void kit_cg_uint_to_float(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) { void kit_cg_float_to_sint(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) { (void)rounding; + if (api_unevaluated(g)) { + api_cg_convert_kind(g, dst, CV_FTOI_S); + return; + } if (api_f128_stack_top(g, 0)) { KitCgTypeId dty = dst; u32 sz = (u32)abi_cg_sizeof(g->c->abi, dty); @@ -1749,8 +1882,7 @@ void kit_cg_float_to_sint(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) { } /* hardware single float -> split-i64: use __fixsfdi. */ if (api_is_wide8_scalar_type(g->c, dst)) { - api_f128_call_unary(g, "__fixsfdi", dst, - builtin_id(KIT_CG_BUILTIN_F32)); + api_f128_call_unary(g, "__fixsfdi", dst, builtin_id(KIT_CG_BUILTIN_F32)); return; } /* soft single float -> signed int <=32 (ilp32, no FPU): __fixsfsi. */ @@ -1766,6 +1898,10 @@ void kit_cg_float_to_sint(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) { void kit_cg_float_to_uint(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) { (void)rounding; + if (api_unevaluated(g)) { + api_cg_convert_kind(g, dst, CV_FTOI_U); + return; + } if (api_f128_stack_top(g, 0)) { KitCgTypeId dty = dst; u32 sz = (u32)abi_cg_sizeof(g->c->abi, dty); @@ -1790,8 +1926,7 @@ void kit_cg_float_to_uint(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) { } /* hardware single float -> split-u64: use __fixunssfdi. */ if (api_is_wide8_scalar_type(g->c, dst)) { - api_f128_call_unary(g, "__fixunssfdi", dst, - builtin_id(KIT_CG_BUILTIN_F32)); + api_f128_call_unary(g, "__fixunssfdi", dst, builtin_id(KIT_CG_BUILTIN_F32)); return; } /* soft single float -> unsigned int <=32 (ilp32, no FPU): __fixunssfsi. */ @@ -1926,6 +2061,26 @@ void kit_cg_intrinsic(KitCg* g, KitCgIntrinsic intrin, uint32_t nargs, u32 ndst = 0; Heap* h; if (!g) return; + if (api_unevaluated(g)) { + KitCgTypeId rty_u = resolve_type(g->c, result_type); + for (u32 i = 0; i < nargs; ++i) { + ApiSValue sv = api_pop(g); + api_release(g, &sv); + } + if (api_intrinsic_is_overflow(intrin)) { + KitCgTypeId bool_ty = builtin_id(KIT_CG_BUILTIN_BOOL); + if (!rty_u) rty_u = builtin_id(KIT_CG_BUILTIN_I32); + api_push(g, api_uneval_value(g, rty_u)); + api_const_set_top(g, api_const_unknown(rty_u)); + api_push(g, api_uneval_value(g, bool_ty)); + api_const_set_top(g, api_const_unknown(bool_ty)); + } else if (!api_intrinsic_is_void(intrin) && + (rty_u && !cg_type_is_void(g->c, rty_u))) { + api_push(g, api_uneval_value(g, rty_u)); + api_const_set_top(g, api_const_unknown(rty_u)); + } + return; + } /* readcyclecounter returns a 64-bit value. On rv32 that is a register pair * the single-register native intrinsic path can't carry, and the read spans * the cycle/cycleh CSRs with a re-read loop, so route to the libkit_rt helper diff --git a/src/cg/asm.c b/src/cg/asm.c @@ -101,6 +101,22 @@ void kit_cg_inline_asm(KitCg* g, KitCgInlineAsm asm_block) { uint32_t clobber_abi_sets = asm_block.clobber_abi_sets; (void)asm_block.flags; if (!g) return; + if (api_unevaluated(g)) { + uint32_t ninout_u = 0; + for (u32 i = 0; i < noutputs; ++i) + if (outputs[i].dir == KIT_CG_ASM_INOUT) ninout_u++; + for (u32 i = 0; i < ninputs + ninout_u; ++i) { + ApiSValue sv = api_pop(g); + api_release(g, &sv); + } + for (u32 i = 0; i < noutputs; ++i) { + KitCgTypeId oty = resolve_type(g->c, outputs[i].type); + if (!oty) oty = builtin_id(KIT_CG_BUILTIN_I64); + api_push(g, api_uneval_value(g, oty)); + api_const_set_top(g, api_const_unknown(oty)); + } + return; + } api_local_const_memory_boundary(g); T = g->target; h = g->c->ctx->heap; @@ -253,7 +269,7 @@ void kit_cg_inline_asm(KitCg* g, KitCgInlineAsm asm_block) { cg_type_ptr_to(g->c, ity ? ity : builtin_id(KIT_CG_BUILTIN_VOID)); Operand dst = api_lvalue_addr(g, &in_svs[i], pty); in_svs[i].op = api_op_indirect(dst.v.local, 0, ity); - in_svs[i].res = RES_LOCAL; + api_sv_set_res(&in_svs[i], RES_LOCAL); in_ops[i] = in_svs[i].op; } else { compiler_panic(g->c, g->cur_loc, @@ -297,7 +313,7 @@ void kit_cg_inline_asm(KitCg* g, KitCgInlineAsm asm_block) { for (u32 i = 0; i < g->sp; ++i) { ApiSValue* sv = &g->stack[i]; CGLocal local_id; - if (sv->res != RES_LOCAL) continue; + if (api_sv_res(sv) != RES_LOCAL) continue; local_id = api_local_of_sv(sv); api_asm_memory_clobber_sv(g, sv, local_id); } @@ -310,7 +326,8 @@ void kit_cg_inline_asm(KitCg* g, KitCgInlineAsm asm_block) { for (u32 i = 0; i < noutputs; ++i) { KitCgTypeId oty = outs[i].type ? outs[i].type : fallback_ty; ApiSValue sv = api_make_sv(out_ops[i], oty); - if (!out_local_owned[i] && sv.res == RES_LOCAL) sv.res = RES_INHERENT; + if (!out_local_owned[i] && api_sv_res(&sv) == RES_LOCAL) + api_sv_set_res(&sv, RES_INHERENT); api_push(g, sv); } @@ -325,6 +342,7 @@ void kit_cg_inline_asm(KitCg* g, KitCgInlineAsm asm_block) { void kit_cg_file_scope_asm(KitCg* g, KitSlice asm_source) { if (!g || !asm_source.s) return; + if (api_unevaluated(g)) return; if (g->check_only) return; if (g->target && g->target->file_scope_asm) { g->target->file_scope_asm(g->target, asm_source.s, asm_source.len); diff --git a/src/cg/atomic.c b/src/cg/atomic.c @@ -104,7 +104,10 @@ static KitCgSym cg_atomic_runtime_sym(KitCg* g, const char* name, for (u32 i = 0; i < nparams; ++i) ps[i].type = params[i]; memset(&sig, 0, sizeof sig); memset(&result, 0, sizeof result); - result.type = ret; /* ret == KIT_CG_TYPE_NONE -> void result */ + /* A function result is always a valid type id post-cutover; a void helper + * (e.g. __atomic_store_8) is the void builtin, never KIT_CG_TYPE_NONE, which + * kit_cg_type_func rejects. Normalize a NONE/0 ret to the void builtin. */ + result.type = ret ? ret : builtin_id(KIT_CG_BUILTIN_VOID); sig.result = result; sig.params = ps; sig.nparams = nparams; @@ -144,6 +147,12 @@ void kit_cg_atomic_load(KitCg* g, KitCgMemAccess access, KitCgMemOrder order) { pty = api_sv_type(&ptr); val_ty = resolve_type(g->c, access.type); if (!val_ty) val_ty = api_atomic_pointee(g, pty, "KitCg: atomic_load"); + if (api_unevaluated(g)) { + api_release(g, &ptr); + api_push(g, api_uneval_value(g, val_ty)); + api_const_set_top(g, api_const_unknown(val_ty)); + return; + } api_require_pointer_value(g, "atomic_load pointer", pty); if (cg_atomic_needs_libcall(g, val_ty)) { /* u64 __atomic_load_8(const void* ptr, int memorder) */ @@ -177,6 +186,11 @@ void kit_cg_atomic_store(KitCg* g, KitCgMemAccess access, KitCgMemOrder order) { pty = api_sv_type(&ptr); val_ty = resolve_type(g->c, access.type); if (!val_ty) val_ty = api_atomic_pointee(g, pty, "KitCg: atomic_store"); + if (api_unevaluated(g)) { + api_release(g, &val); + api_release(g, &ptr); + return; + } api_require_pointer_value(g, "atomic_store pointer", pty); api_validate_memory_value(g, "atomic_store", val_ty, api_sv_type(&val)); if (cg_atomic_needs_libcall(g, val_ty)) { @@ -215,6 +229,13 @@ void kit_cg_atomic_rmw(KitCg* g, KitCgMemAccess access, KitCgAtomicOp op, pty = api_sv_type(&ptr); val_ty = resolve_type(g->c, access.type); if (!val_ty) val_ty = api_atomic_pointee(g, pty, "KitCg: atomic_rmw"); + if (api_unevaluated(g)) { + api_release(g, &val); + api_release(g, &ptr); + api_push(g, api_uneval_value(g, val_ty)); + api_const_set_top(g, api_const_unknown(val_ty)); + return; + } api_require_pointer_value(g, "atomic_rmw pointer", pty); api_validate_memory_value(g, "atomic_rmw", val_ty, api_sv_type(&val)); if (cg_atomic_needs_libcall(g, val_ty)) { @@ -266,6 +287,17 @@ void kit_cg_atomic_cmpxchg(KitCg* g, KitCgMemAccess access, pty = api_sv_type(&ptr); val_ty = resolve_type(g->c, access.type); if (!val_ty) val_ty = api_atomic_pointee(g, pty, "KitCg: atomic_cmpxchg"); + if (api_unevaluated(g)) { + bool_ty = builtin_id(KIT_CG_BUILTIN_BOOL); + api_release(g, &desired); + api_release(g, &expected); + api_release(g, &ptr); + api_push(g, api_uneval_value(g, val_ty)); + api_const_set_top(g, api_const_unknown(val_ty)); + api_push(g, api_uneval_value(g, bool_ty)); + api_const_set_top(g, api_const_unknown(bool_ty)); + return; + } api_require_pointer_value(g, "atomic_cmpxchg pointer", pty); api_validate_memory_value(g, "atomic_cmpxchg expected", val_ty, api_sv_type(&expected)); @@ -347,6 +379,7 @@ void kit_cg_atomic_cmpxchg(KitCg* g, KitCgMemAccess access, void kit_cg_atomic_fence(KitCg* g, KitCgMemOrder order) { if (!g) return; + if (api_unevaluated(g)) return; api_local_const_memory_boundary(g); g->target->fence(g->target, order); } diff --git a/src/cg/call.c b/src/cg/call.c @@ -97,16 +97,19 @@ void api_pack_call_arg(KitCg* g, CGLocal* out, KitCgTypeId fty, u32 idx) { * * Reversing the on-stack order (rather than popping all up front) preserves the * api_temp_dead semantics exactly: the not-yet-materialized arguments stay on - * the stack, so a temp shared between arguments (e.g. f(t*2, t*3)) is scanned as - * live until its last (highest-index) use and is coalesced there, never killed - * prematurely. */ + * the stack, so a temp shared between arguments (e.g. f(t*2, t*3)) is scanned + * as live until its last (highest-index) use and is coalesced there, never + * killed prematurely. */ static void api_pack_call_args_in_order(KitCg* g, CGLocal* args, u32 nargs, KitCgTypeId fty) { u32 base = g->sp - nargs; /* arg0's stack slot (callee, if any, sits below) */ for (u32 i = 0; i < nargs / 2u; ++i) { ApiSValue tmp = g->stack[base + i]; + ApiConstValue ctmp = g->const_stack[base + i]; g->stack[base + i] = g->stack[base + nargs - 1u - i]; + g->const_stack[base + i] = g->const_stack[base + nargs - 1u - i]; g->stack[base + nargs - 1u - i] = tmp; + g->const_stack[base + nargs - 1u - i] = ctmp; } for (u32 i = 0; i < nargs; ++i) api_pack_call_arg(g, &args[i], fty, i); } @@ -115,6 +118,10 @@ CGLocal api_alloc_call_result(KitCg* g, KitCgTypeId ret_ty) { return api_alloc_temp_local(g, ret_ty); } +static int api_type_has_value(KitCg* g, KitCgTypeId ty) { + return ty != KIT_CG_TYPE_NONE && !cg_type_is_void(g->c, ty); +} + void api_push_call_result(KitCg* g, CGLocal result, KitCgTypeId ret_ty) { Operand op = api_op_local(result, ret_ty); /* An aggregate result is a PLACE (it is addressed/copied, never a scalar @@ -205,6 +212,21 @@ void kit_cg_call(KitCg* g, uint32_t nargs, KitCgTypeId fn_type, compiler_panic(g->c, g->cur_loc, "KitCg: call stack underflow"); return; } + if (api_unevaluated(g)) { + u32 i; + result_type = cg_type_func_result_id(g->c, fty); + for (i = 0; i < nargs; ++i) { + ApiSValue arg = api_pop(g); + api_release(g, &arg); + } + callee = api_pop(g); + api_release(g, &callee); + if (api_type_has_value(g, result_type)) { + api_push(g, api_uneval_value(g, result_type)); + api_const_set_top(g, api_const_unknown(result_type)); + } + return; + } args = api_alloc_call_args(g, nargs); api_pack_call_args_in_order(g, args, nargs, fty); @@ -234,7 +256,7 @@ void kit_cg_call(KitCg* g, uint32_t nargs, KitCgTypeId fn_type, desc.flags = emit_tail ? CG_CALL_TAIL : CG_CALL_NONE; result_type = emit_tail ? KIT_CG_TYPE_NONE : cg_type_func_result_id(g->c, fty); - if (result_type != KIT_CG_TYPE_NONE) + if (api_type_has_value(g, result_type)) desc.result = api_alloc_call_result(g, result_type); (void)T; @@ -261,6 +283,19 @@ void api_call_symbol_common(KitCg* g, KitCgSym sym, uint32_t nargs, compiler_panic(g->c, g->cur_loc, "KitCg: call stack underflow"); return; } + if (api_unevaluated(g)) { + u32 i; + result_type = cg_type_func_result_id(g->c, fty); + for (i = 0; i < nargs; ++i) { + ApiSValue arg = api_pop(g); + api_release(g, &arg); + } + if (api_type_has_value(g, result_type)) { + api_push(g, api_uneval_value(g, result_type)); + api_const_set_top(g, api_const_unknown(result_type)); + } + return; + } args = api_alloc_call_args(g, nargs); api_pack_call_args_in_order(g, args, nargs, fty); callee_op = api_op_global((ObjSymId)sym, 0, cg_type_ptr_to(g->c, fty)); @@ -282,7 +317,7 @@ void api_call_symbol_common(KitCg* g, KitCgSym sym, uint32_t nargs, desc.flags = emit_tail ? CG_CALL_TAIL : CG_CALL_NONE; result_type = emit_tail ? KIT_CG_TYPE_NONE : cg_type_func_result_id(g->c, fty); - if (result_type != KIT_CG_TYPE_NONE) + if (api_type_has_value(g, result_type)) desc.result = api_alloc_call_result(g, result_type); api_finish_call(g, &desc, want_tail, emit_tail); } @@ -298,7 +333,14 @@ void kit_cg_ret(KitCg* g) { CGLocal value; if (!g) return; rty = g->fn_desc.result_type; - if (rty == KIT_CG_TYPE_NONE) { + if (api_unevaluated(g)) { + if (api_type_has_value(g, rty)) { + v = api_pop(g); + api_release(g, &v); + } + return; + } + if (!api_type_has_value(g, rty)) { g->target->ret(g->target, CG_LOCAL_NONE); return; } diff --git a/src/cg/const.c b/src/cg/const.c @@ -0,0 +1,552 @@ +#include "cg/internal.h" + +static u64 const_mask(u32 width) { + if (width >= 64u) return UINT64_MAX; + return (1ull << width) - 1ull; +} + +static int const_type_width(KitCg* g, KitCgTypeId type, u32* width_out) { + u32 width; + if (!g || !width_out) return 0; + width = api_int_like_width(g->c, type); + if (!width || width > 128u) return 0; + *width_out = width; + return 1; +} + +static KitCgConstInt const_normalize(KitCgConstInt v, u32 width) { + v.width = (uint16_t)width; + if (!v.known || width == 0 || width > 128u) { + memset(&v, 0, sizeof v); + v.width = (uint16_t)width; + return v; + } + if (width < 64u) { + v.lo &= const_mask(width); + v.hi = 0; + } else if (width == 64u) { + v.hi = 0; + } else if (width < 128u) { + v.hi &= const_mask(width - 64u); + } + v.known = 1; + return v; +} + +static int const_is_zero(KitCgConstInt v) { + return v.known && v.lo == 0 && v.hi == 0; +} + +static int const_sign_bit(KitCgConstInt v) { + u32 width = v.width; + if (!width) return 0; + if (width <= 64u) return ((v.lo >> (width - 1u)) & 1u) != 0; + return ((v.hi >> (width - 65u)) & 1u) != 0; +} + +static int const_cmp_unsigned(KitCgConstInt a, KitCgConstInt b) { + if (a.hi != b.hi) return a.hi < b.hi ? -1 : 1; + if (a.lo != b.lo) return a.lo < b.lo ? -1 : 1; + return 0; +} + +static int const_cmp_signed(KitCgConstInt a, KitCgConstInt b) { + int as = const_sign_bit(a); + int bs = const_sign_bit(b); + if (as != bs) return as ? -1 : 1; + return const_cmp_unsigned(a, b); +} + +static u64 const_low_masked(KitCgConstInt v) { + if (v.width >= 64u) return v.lo; + return v.lo & const_mask(v.width); +} + +static u64 const_abs_signed64_bits(u64 bits, u32 width, int* neg_out) { + u64 mask = const_mask(width); + int neg = width && ((bits >> (width - 1u)) & 1u) != 0; + bits &= mask; + if (neg) bits = ((~bits) + 1u) & mask; + if (neg_out) *neg_out = neg; + return bits; +} + +static int const_divrem64(BinOp op, u32 width, u64 a, u64 b, u64* out) { + u64 mask = const_mask(width); + if (!out || b == 0) return 0; + a &= mask; + b &= mask; + switch (op) { + case BO_UDIV: + *out = (a / b) & mask; + return 1; + case BO_UREM: + *out = (a % b) & mask; + return 1; + case BO_SDIV: + case BO_SREM: { + int an, bn; + u64 aa = const_abs_signed64_bits(a, width, &an); + u64 bb = const_abs_signed64_bits(b, width, &bn); + u64 r; + if (bb == 0) return 0; + if (op == BO_SDIV) { + r = aa / bb; + if (an != bn) r = ((~r) + 1u) & mask; + } else { + r = aa % bb; + if (an) r = ((~r) + 1u) & mask; + } + *out = r & mask; + return 1; + } + default: + return 0; + } +} + +static KitCgConstInt const_add(KitCgConstInt a, KitCgConstInt b) { + KitCgConstInt r = a; + u64 lo = a.lo + b.lo; + u64 carry = lo < a.lo; + r.lo = lo; + r.hi = a.hi + b.hi + carry; + return const_normalize(r, r.width); +} + +static KitCgConstInt const_sub(KitCgConstInt a, KitCgConstInt b) { + KitCgConstInt r = a; + u64 borrow = a.lo < b.lo; + r.lo = a.lo - b.lo; + r.hi = a.hi - b.hi - borrow; + return const_normalize(r, r.width); +} + +static KitCgConstInt const_mul(KitCgConstInt a, KitCgConstInt b) { + KitCgConstInt r = a; + u16 al[8]; + u16 bl[8]; + u16 rl[8]; + u32 i; + u32 k; + for (i = 0; i < 4u; ++i) { + al[i] = (u16)(a.lo >> (i * 16u)); + bl[i] = (u16)(b.lo >> (i * 16u)); + al[i + 4u] = (u16)(a.hi >> (i * 16u)); + bl[i + 4u] = (u16)(b.hi >> (i * 16u)); + rl[i] = 0; + rl[i + 4u] = 0; + } + { + u64 carry = 0; + for (k = 0; k < 8u; ++k) { + u64 sum = carry; + for (i = 0; i <= k; ++i) sum += (u32)al[i] * (u32)bl[k - i]; + rl[k] = (u16)sum; + carry = sum >> 16; + } + } + r.lo = 0; + r.hi = 0; + for (i = 0; i < 4u; ++i) { + r.lo |= (u64)rl[i] << (i * 16u); + r.hi |= (u64)rl[i + 4u] << (i * 16u); + } + return const_normalize(r, r.width); +} + +static KitCgConstInt const_shl(KitCgConstInt a, u32 sh) { + KitCgConstInt r = a; + sh &= (u32)(a.width - 1u); + if (sh == 0) return const_normalize(r, r.width); + if (sh >= 64u) { + r.hi = a.lo << (sh - 64u); + r.lo = 0; + } else { + r.hi = (a.hi << sh) | (a.lo >> (64u - sh)); + r.lo = a.lo << sh; + } + return const_normalize(r, r.width); +} + +static KitCgConstInt const_lshr(KitCgConstInt a, u32 sh) { + KitCgConstInt r = a; + sh &= (u32)(a.width - 1u); + if (sh == 0) return const_normalize(r, r.width); + if (sh >= 64u) { + r.lo = a.hi >> (sh - 64u); + r.hi = 0; + } else { + r.lo = (a.lo >> sh) | (a.hi << (64u - sh)); + r.hi = a.hi >> sh; + } + return const_normalize(r, r.width); +} + +static KitCgConstInt const_ashr(KitCgConstInt a, u32 sh) { + KitCgConstInt r = const_lshr(a, sh); + u32 width = a.width; + if (!const_sign_bit(a)) return r; + sh &= (u32)(width - 1u); + if (sh == 0) return r; + if (width <= 64u) { + u64 fill = ~const_mask(width - sh); + r.lo |= fill & const_mask(width); + r.hi = 0; + } else { + u32 top_bits = sh; + while (top_bits) { + u32 bit = width - top_bits; + if (bit < 64u) + r.lo |= 1ull << bit; + else + r.hi |= 1ull << (bit - 64u); + --top_bits; + } + } + return const_normalize(r, width); +} + +static KitCgConstInt const_sext(KitCgConstInt v, u32 src_width, u32 dst_width) { + int neg; + v = const_normalize(v, src_width); + neg = const_sign_bit(v); + v.width = (uint16_t)dst_width; + if (neg && dst_width > src_width) { + u32 bit; + for (bit = src_width; bit < dst_width; ++bit) { + if (bit < 64u) + v.lo |= 1ull << bit; + else + v.hi |= 1ull << (bit - 64u); + } + } + return const_normalize(v, dst_width); +} + +int api_unevaluated(KitCg* g) { return g && g->unevaluated_depth != 0; } + +ApiSValue api_uneval_value(KitCg* g, KitCgTypeId type) { + KitCgTypeId ty = type ? resolve_type(g->c, type) : KIT_CG_TYPE_NONE; + return api_make_sv(api_op_imm(0, ty), ty); +} + +ApiSValue api_uneval_place(KitCg* g, KitCgTypeId type) { + KitCgTypeId ty = type ? resolve_type(g->c, type) : KIT_CG_TYPE_NONE; + return api_make_lv(api_op_indirect(CG_LOCAL_NONE, 0, ty), ty); +} + +ApiConstValue api_const_unknown(KitCgTypeId type) { + ApiConstValue cv; + memset(&cv, 0, sizeof cv); + cv.type = type; + return cv; +} + +ApiConstValue api_const_int_result(KitCg* g, KitCgTypeId type, u64 lo, u64 hi, + int is_signed) { + ApiConstValue cv = api_const_unknown(type); + u32 width; + if (!const_type_width(g, type, &width)) return cv; + cv.value.lo = lo; + cv.value.hi = hi; + cv.value.width = (uint16_t)width; + cv.value.is_signed = is_signed ? 1u : 0u; + cv.value.known = 1u; + cv.value = const_normalize(cv.value, width); + return cv; +} + +ApiConstValue api_const_for_push(KitCg* g, KitCgTypeId type, + const KitCgConstInt* value) { + ApiConstValue cv = api_const_unknown(type); + u32 width; + if (!value || !value->known || !const_type_width(g, type, &width)) return cv; + cv.type = type; + cv.value = const_normalize(*value, width); + cv.value.known = 1u; + return cv; +} + +ApiConstValue api_const_from_sv(KitCg* g, const ApiSValue* sv) { + KitCgTypeId type; + if (!g || !sv) return api_const_unknown(KIT_CG_TYPE_NONE); + type = api_sv_type(sv); + if (api_sv_kind(sv) == SV_OPERAND && sv->op.kind == OPK_IMM) { + return api_const_int_result(g, type, (u64)sv->op.v.imm, 0, 0); + } + return api_const_unknown(type); +} + +ApiConstValue api_const_at(KitCg* g, u32 depth) { + if (!g || depth >= g->sp || !g->const_stack) + return api_const_unknown(KIT_CG_TYPE_NONE); + return g->const_stack[g->sp - 1u - depth]; +} + +void api_const_set_top(KitCg* g, ApiConstValue value) { + if (!g || !g->sp || !g->const_stack) return; + g->const_stack[g->sp - 1u] = value; +} + +void api_const_set_at(KitCg* g, u32 depth, ApiConstValue value) { + if (!g || depth >= g->sp || !g->const_stack) return; + g->const_stack[g->sp - 1u - depth] = value; +} + +void api_const_copy_top_from(KitCg* g, ApiConstValue value) { + api_const_set_top(g, value); +} + +int api_const_fold_binop(KitCg* g, BinOp op, KitCgTypeId type, ApiConstValue a, + ApiConstValue b, u32 flags, ApiConstValue* out) { + ApiConstValue r; + u32 width; + if (!out) return 0; + r = api_const_unknown(type); + if (!g || flags || !a.value.known || !b.value.known || + !const_type_width(g, type, &width)) { + *out = r; + return 0; + } + a.value = const_normalize(a.value, width); + b.value = const_normalize(b.value, width); + r = api_const_int_result(g, type, 0, 0, a.value.is_signed); + switch (op) { + case BO_IADD: + r.value = const_add(a.value, b.value); + break; + case BO_ISUB: + r.value = const_sub(a.value, b.value); + break; + case BO_IMUL: + r.value = const_mul(a.value, b.value); + break; + case BO_AND: + r.value.lo = a.value.lo & b.value.lo; + r.value.hi = a.value.hi & b.value.hi; + r.value = const_normalize(r.value, width); + break; + case BO_OR: + r.value.lo = a.value.lo | b.value.lo; + r.value.hi = a.value.hi | b.value.hi; + r.value = const_normalize(r.value, width); + break; + case BO_XOR: + r.value.lo = a.value.lo ^ b.value.lo; + r.value.hi = a.value.hi ^ b.value.hi; + r.value = const_normalize(r.value, width); + break; + case BO_SHL: + r.value = const_shl(a.value, (u32)b.value.lo); + break; + case BO_SHR_U: + r.value = const_lshr(a.value, (u32)b.value.lo); + break; + case BO_SHR_S: + r.value = const_ashr(a.value, (u32)b.value.lo); + break; + case BO_SDIV: + case BO_UDIV: + case BO_SREM: + case BO_UREM: { + u64 folded; + if (width > 64u || !const_divrem64(op, width, const_low_masked(a.value), + const_low_masked(b.value), &folded)) { + *out = api_const_unknown(type); + return 0; + } + r.value = + api_const_int_result(g, type, folded, 0, a.value.is_signed).value; + break; + } + default: + *out = api_const_unknown(type); + return 0; + } + r.type = type; + r.value.known = 1u; + *out = r; + return 1; +} + +int api_const_fold_unop(KitCg* g, UnOp op, KitCgTypeId type, ApiConstValue a, + u32 flags, ApiConstValue* out) { + ApiConstValue r; + u32 width; + if (!out) return 0; + r = api_const_unknown(type); + if (!g || flags || !a.value.known || !const_type_width(g, type, &width)) { + *out = r; + return 0; + } + a.value = const_normalize(a.value, width); + r = api_const_int_result(g, type, 0, 0, a.value.is_signed); + switch (op) { + case UO_NEG: + r.value.lo = ~a.value.lo; + r.value.hi = ~a.value.hi; + r.value = const_add(const_normalize(r.value, width), + api_const_int_result(g, type, 1, 0, 0).value); + break; + case UO_NOT: + r.value.lo = const_is_zero(a.value) ? 1u : 0u; + r.value.hi = 0; + r.value = const_normalize(r.value, width); + break; + case UO_BNOT: + r.value.lo = ~a.value.lo; + r.value.hi = ~a.value.hi; + r.value = const_normalize(r.value, width); + break; + default: + *out = api_const_unknown(type); + return 0; + } + r.type = type; + r.value.known = 1u; + *out = r; + return 1; +} + +int api_const_fold_cmp(KitCg* g, CmpOp op, ApiConstValue a, ApiConstValue b, + ApiConstValue* out) { + KitCgTypeId bool_ty = builtin_id(KIT_CG_BUILTIN_I32); + ApiConstValue r; + u32 width = a.value.width ? a.value.width : b.value.width; + int cmp; + int ok; + if (!out) return 0; + r = api_const_unknown(bool_ty); + if (!g || !a.value.known || !b.value.known || !width || width > 128u) { + *out = r; + return 0; + } + a.value = const_normalize(a.value, width); + b.value = const_normalize(b.value, width); + switch (op) { + case CMP_EQ: + ok = a.value.lo == b.value.lo && a.value.hi == b.value.hi; + break; + case CMP_NE: + ok = a.value.lo != b.value.lo || a.value.hi != b.value.hi; + break; + case CMP_LT_U: + ok = const_cmp_unsigned(a.value, b.value) < 0; + break; + case CMP_LE_U: + ok = const_cmp_unsigned(a.value, b.value) <= 0; + break; + case CMP_GT_U: + ok = const_cmp_unsigned(a.value, b.value) > 0; + break; + case CMP_GE_U: + ok = const_cmp_unsigned(a.value, b.value) >= 0; + break; + case CMP_LT_S: + cmp = const_cmp_signed(a.value, b.value); + ok = cmp < 0; + break; + case CMP_LE_S: + cmp = const_cmp_signed(a.value, b.value); + ok = cmp <= 0; + break; + case CMP_GT_S: + cmp = const_cmp_signed(a.value, b.value); + ok = cmp > 0; + break; + case CMP_GE_S: + cmp = const_cmp_signed(a.value, b.value); + ok = cmp >= 0; + break; + default: + *out = r; + return 0; + } + r = api_const_int_result(g, bool_ty, ok ? 1u : 0u, 0, 0); + *out = r; + return 1; +} + +int api_const_fold_convert(KitCg* g, ConvKind ck, KitCgTypeId src_type, + KitCgTypeId dst_type, ApiConstValue in, + ApiConstValue* out) { + ApiConstValue r; + u32 sw; + u32 dw; + if (!out) return 0; + r = api_const_unknown(dst_type); + if (!g || !in.value.known || !const_type_width(g, src_type, &sw) || + !const_type_width(g, dst_type, &dw)) { + *out = r; + return 0; + } + switch (ck) { + case CV_TRUNC: + case CV_ZEXT: + r = api_const_int_result(g, dst_type, in.value.lo, in.value.hi, + in.value.is_signed); + break; + case CV_SEXT: + r = api_const_int_result(g, dst_type, 0, 0, 1); + r.value = const_sext(in.value, sw, dw); + r.value.known = 1u; + r.value.is_signed = 1u; + break; + case CV_BITCAST: + if (sw != dw) { + *out = r; + return 0; + } + r = api_const_int_result(g, dst_type, in.value.lo, in.value.hi, + in.value.is_signed); + break; + default: + *out = r; + return 0; + } + r.type = dst_type; + *out = r; + return r.value.known != 0; +} + +void kit_cg_unevaluated_push(KitCg* g) { + if (!g) return; + ++g->unevaluated_depth; + if (g->unevaluated_depth == 0) { + compiler_panic(g->c, g->cur_loc, "KitCg: unevaluated depth overflow"); + g->unevaluated_depth = UINT32_MAX; + } +} + +void kit_cg_unevaluated_pop(KitCg* g) { + if (!g) return; + if (!g->unevaluated_depth) { + compiler_panic(g->c, g->cur_loc, "KitCg: unevaluated pop underflow"); + return; + } + --g->unevaluated_depth; +} + +int kit_cg_top_const_int_ex(KitCg* g, KitCgConstInt* out_value) { + ApiConstValue cv; + if (!g || !g->sp) return 0; + cv = api_const_at(g, 0); + if (!cv.value.known) return 0; + if (out_value) *out_value = cv.value; + return 1; +} + +int kit_cg_top_const_i64(KitCg* g, int64_t* out_value) { + KitCgConstInt v; + if (!out_value || !kit_cg_top_const_int_ex(g, &v) || v.width > 64u) return 0; + if (v.is_signed) + *out_value = api_sign_extend_width(v.lo, v.width); + else + *out_value = (int64_t)const_low_masked(v); + return 1; +} + +int kit_cg_top_const_int(KitCg* g, int64_t* out_value) { + return kit_cg_top_const_i64(g, out_value); +} diff --git a/src/cg/control.c b/src/cg/control.c @@ -3,17 +3,20 @@ KitCgLabel kit_cg_label_new(KitCg* g) { if (!g) return KIT_CG_LABEL_NONE; + if (api_unevaluated(g)) return KIT_CG_LABEL_NONE; return (KitCgLabel)g->target->label_new(g->target); } void kit_cg_label_place(KitCg* g, KitCgLabel label) { if (!g) return; + if (api_unevaluated(g)) return; api_local_const_control_boundary(g); g->target->label_place(g->target, (Label)label); } void kit_cg_jump(KitCg* g, KitCgLabel label) { if (!g) return; + if (api_unevaluated(g)) return; api_local_const_control_boundary(g); g->target->jump(g->target, (Label)label); } @@ -22,15 +25,19 @@ void api_branch_if(KitCg* g, ApiSValue* v, int branch_when_true, Label label) { CgTarget* T; KitCgTypeId ty; if (!g) return; + if (api_unevaluated(g)) { + api_release(g, v); + return; + } api_local_const_control_boundary(g); T = g->target; ty = v->type ? v->type : builtin_id(KIT_CG_BUILTIN_I32); - if (v->op.kind == OPK_IMM && v->kind == SV_OPERAND) { + if (v->op.kind == OPK_IMM && api_sv_kind(v) == SV_OPERAND) { if ((v->op.v.imm != 0) == !!branch_when_true) T->jump(T, label); api_release(g, v); return; } - if (v->kind == SV_CMP) { + if (api_sv_kind(v) == SV_CMP) { CmpOp op = branch_when_true ? v->delayed->cmp.op : api_invert_cmp(v->delayed->cmp.op); /* Flag dead-transient operands so the -O0 backend drops them at the branch @@ -130,36 +137,72 @@ typedef struct CGSwitchPlan { u64 span; } CGSwitchPlan; -/* Single pass over the cases array: derive (vmin, vmax, span) in the - * selector type's signed interpretation. Frontends store case values as - * the u64 bit pattern of an i64 sign-extended from the selector's - * width, so api_sign_extend_width recovers the source ordering for - * both signed and unsigned selectors that fit in i64. Returns 0 if the - * selector type is unusable for our policy (>64 bits) or if span - * overflows u64. */ +/* Single pass over the cases array, deriving (vmin, span) for the tightest + * dispatch window. Dispatch is `idx = (sel - vmin) mod 2^width` with an + * unsigned bounds check, so the cases live on a circle of size 2^width and + * the minimal enclosing window is the complement of the largest gap between + * them. We don't sort to find that gap; instead we evaluate the two windows + * whose seam sits at a fixed point and take the smaller: + * + * - the *signed* window [smin, smax], seam at the signed midpoint — tight + * for values near 0 or near 2^width (e.g. a (T)-1 case folded next to + * small positives); + * - the *unsigned* window [umin, umax], seam at 0 / 2^width — tight for + * values straddling the signed midpoint. + * + * A table-eligible cluster (span <= a few thousand) is far smaller than + * 2^(width-1), so it can cross at most one of those two seams; the smaller + * window is therefore the true minimum. Selector signedness isn't even + * visible here (CG integer builtins carry only a width), and both windows + * dispatch correctly under the modular index, so taking the min is a pure + * code-size win. Returns 0 if the selector type is unusable (>64 bits) or + * both windows degenerate to the full range. */ static int cg_switch_extents(Compiler* c, const CGSwitchDesc* d, i64* out_vmin, u64* out_span) { u32 width; - i64 vmin; - i64 vmax; + u64 mask; + i64 smin; + i64 smax; + u64 umin; + u64 umax; u32 i; + u64 sspan; + u64 uspan; width = kit_cg_type_int_width((KitCompiler*)c, d->selector_type); if (!width || width > 64u) return 0; - vmin = INT64_MAX; - vmax = INT64_MIN; + if (d->ncases == 0) return 0; + mask = (width >= 64u) ? UINT64_MAX : (((u64)1u << width) - 1u); + smin = INT64_MAX; + smax = INT64_MIN; + umin = UINT64_MAX; + umax = 0; for (i = 0; i < d->ncases; ++i) { - i64 vi = (width == 64u) ? (i64)d->cases[i].value + u64 raw = d->cases[i].value & mask; + i64 sv = (width == 64u) ? (i64)d->cases[i].value : api_sign_extend_width(d->cases[i].value, width); - if (vi < vmin) vmin = vi; - if (vi > vmax) vmax = vi; + if (sv < smin) smin = sv; + if (sv > smax) smax = sv; + if (raw < umin) umin = raw; + if (raw > umax) umax = raw; } - if (vmax < vmin) return 0; + /* span = delta + 1; a delta of UINT64_MAX is the degenerate full-range + * case the table can't represent. */ { - u64 delta = (u64)vmax - (u64)vmin; - if (delta == UINT64_MAX) return 0; - *out_span = delta + 1u; + u64 sdelta = (u64)smax - (u64)smin; + sspan = sdelta == UINT64_MAX ? 0u : sdelta + 1u; + } + { + u64 udelta = umax - umin; + uspan = udelta == UINT64_MAX ? 0u : udelta + 1u; + } + if (sspan == 0u && uspan == 0u) return 0; + if (uspan != 0u && (sspan == 0u || uspan < sspan)) { + *out_span = uspan; + *out_vmin = (i64)umin; /* a bit pattern; emission indexes modularly */ + } else { + *out_span = sspan; + *out_vmin = smin; } - *out_vmin = vmin; return 1; } @@ -255,11 +298,16 @@ static void cg_emit_switch_table(KitCg* g, const CGSwitchDesc* d, if (!labels) compiler_panic(c, g->cur_loc, "kit_cg_switch: oom"); for (i = 0; i < plan->span; ++i) labels[i] = d->default_label; width = sel_w; - for (i = 0; i < d->ncases; ++i) { - i64 vi = (width == 64u) ? (i64)d->cases[i].value - : api_sign_extend_width(d->cases[i].value, width); - u64 table_index = (u64)(vi - plan->vmin); - labels[table_index] = d->cases[i].label; + { + /* Index modularly: vmin may be the unsigned-window origin (a value with + * the sign bit set), so a signed `vi - vmin` could overflow. This mirrors + * the runtime `idx = sel - vmin` computed in sel_ty, and reproduces the + * old non-wrapping subtraction exactly for the signed-window case. */ + u64 mask = (width >= 64u) ? UINT64_MAX : (((u64)1u << width) - 1u); + for (i = 0; i < d->ncases; ++i) { + u64 table_index = ((u64)d->cases[i].value - (u64)plan->vmin) & mask; + labels[table_index] = d->cases[i].label; + } } table_sym = api_emit_label_table(g, labels, (u32)plan->span); h->free(h, labels, (size_t)plan->span * sizeof *labels); @@ -313,6 +361,11 @@ void kit_cg_switch(KitCg* g, KitCgSwitch sw) { int native_switch_override; if (!g) return; if (g->sp == 0) return; + if (api_unevaluated(g)) { + selector = api_pop(g); + api_release(g, &selector); + return; + } api_local_const_control_boundary(g); memset(&desc, 0, sizeof desc); desc.selector_type = resolve_type(g->c, sw.selector_type); @@ -385,6 +438,11 @@ void kit_cg_push_label_addr(KitCg* g, KitCgLabel label, KitCgTypeId ptr_type) { if (!g) return; ty = resolve_type(g->c, ptr_type); if (!ty) ty = cg_type_ptr_to(g->c, builtin_id(KIT_CG_BUILTIN_VOID)); + if (api_unevaluated(g)) { + api_push(g, api_uneval_value(g, ty)); + api_const_set_top(g, api_const_unknown(ty)); + return; + } r = api_alloc_temp_local(g, ty); dst = api_op_local(r, ty); g->target->load_label_addr(g->target, dst, (Label)label); @@ -397,6 +455,11 @@ void kit_cg_computed_goto(KitCg* g, const KitCgLabel* valid_targets, KitCgTypeId target_ty; Operand target_op; if (!g) return; + if (api_unevaluated(g)) { + target = api_pop(g); + api_release(g, &target); + return; + } if (!valid_targets || ntargets == 0) { compiler_panic(g->c, g->cur_loc, "kit_cg_computed_goto: valid_targets must be non-empty"); @@ -413,6 +476,7 @@ void kit_cg_computed_goto(KitCg* g, const KitCgLabel* valid_targets, void kit_cg_unreachable(KitCg* g) { if (!g) return; + if (api_unevaluated(g)) return; api_local_const_control_boundary(g); g->target->unreachable(g->target); } @@ -498,12 +562,17 @@ static void api_scope_store_one(KitCg* g, CGLocal local, KitCgTypeId type, } /* Reload one carry local onto the value stack (fresh temp, load, push). */ -static void api_scope_reload_one(KitCg* g, CGLocal local, KitCgTypeId type) { +static void api_scope_reload_one(KitCg* g, CGLocal local, KitCgTypeId type, + const void* lang_type, u16 lang_flags) { CGLocal r = api_alloc_temp_local(g, type); Operand dst = api_op_local(r, type); Operand src = api_op_local(local, type); + ApiSValue sv; g->target->load(g->target, dst, src, api_mem_for_lvalue(g, &src, type)); - api_push(g, api_make_sv(dst, type)); + sv = api_make_sv(dst, type); + sv.lang_type = lang_type; + sv.lang_flags = lang_flags; + api_push(g, sv); } /* Pop the scope's N results into a caller array (out[i] = result i, value-stack @@ -519,6 +588,8 @@ void api_scope_store_results(KitCg* g, ApiCgScope* s) { for (k = 0; k < s->nresults; ++k) { u32 idx = s->nresults - 1u - k; /* TOS popped first -> highest index */ ApiSValue v = api_pop(g); + s->result_lang_types[idx] = v.lang_type; + s->result_lang_flags[idx] = v.lang_flags; api_scope_store_one(g, s->result_locals[idx], s->result_types[idx], &v); } } @@ -527,7 +598,8 @@ void api_scope_store_results(KitCg* g, ApiCgScope* s) { void api_scope_push_results(KitCg* g, ApiCgScope* s) { u32 k; for (k = 0; k < s->nresults; ++k) - api_scope_reload_one(g, s->result_locals[k], s->result_types[k]); + api_scope_reload_one(g, s->result_locals[k], s->result_types[k], + s->result_lang_types[k], s->result_lang_flags[k]); } /* Pop N params off the value stack into the loop-param carry locals. */ @@ -536,6 +608,8 @@ static void api_scope_store_params(KitCg* g, ApiCgScope* s) { for (k = 0; k < s->nparams; ++k) { u32 idx = s->nparams - 1u - k; ApiSValue v = api_pop(g); + s->param_lang_types[idx] = v.lang_type; + s->param_lang_flags[idx] = v.lang_flags; api_scope_store_one(g, s->param_locals[idx], s->param_types[idx], &v); } } @@ -544,7 +618,8 @@ static void api_scope_store_params(KitCg* g, ApiCgScope* s) { static void api_scope_reload_params(KitCg* g, ApiCgScope* s) { u32 k; for (k = 0; k < s->nparams; ++k) - api_scope_reload_one(g, s->param_locals[k], s->param_types[k]); + api_scope_reload_one(g, s->param_locals[k], s->param_types[k], + s->param_lang_types[k], s->param_lang_flags[k]); } /* Allocate a memory-resident carry local of the given type. `resolved` is the @@ -575,8 +650,12 @@ static int api_scope_setup_sig(KitCg* g, ApiCgScope* s, if (nr <= API_CG_SCOPE_SIG_INLINE && np <= API_CG_SCOPE_SIG_INLINE) { s->result_locals = s->result_locals_inl; s->result_types = s->result_types_inl; + s->result_lang_types = s->result_lang_types_inl; + s->result_lang_flags = s->result_lang_flags_inl; s->param_locals = s->param_locals_inl; s->param_types = s->param_types_inl; + s->param_lang_types = s->param_lang_types_inl; + s->param_lang_flags = s->param_lang_flags_inl; return 1; } { @@ -591,17 +670,46 @@ static int api_scope_setup_sig(KitCg* g, ApiCgScope* s, s->result_types = blk + nr; s->param_locals = blk + 2u * nr; s->param_types = blk + 2u * nr + np; + s->heap_lang_types_block = + (const void**)h->alloc(h, sizeof(void*) * (nr + np), _Alignof(void*)); + s->heap_lang_flags_block = + (u16*)h->alloc(h, sizeof(u16) * (nr + np), _Alignof(u16)); + if (!s->heap_lang_types_block || !s->heap_lang_flags_block) { + if (s->heap_lang_types_block) + h->free(h, s->heap_lang_types_block, sizeof(void*) * (nr + np)); + if (s->heap_lang_flags_block) + h->free(h, s->heap_lang_flags_block, sizeof(u16) * (nr + np)); + h->free(h, blk, sizeof(u32) * total); + s->heap_block = NULL; + s->heap_lang_types_block = NULL; + s->heap_lang_flags_block = NULL; + return 0; + } + s->result_lang_types = s->heap_lang_types_block; + s->param_lang_types = s->heap_lang_types_block + nr; + s->result_lang_flags = s->heap_lang_flags_block; + s->param_lang_flags = s->heap_lang_flags_block + nr; return 1; } } static void api_scope_free_sig(KitCg* g, ApiCgScope* s) { + Heap* h = g->c->ctx->heap; if (s->heap_block) { - Heap* h = g->c->ctx->heap; u32 total = 2u * s->nresults + 2u * s->nparams; h->free(h, s->heap_block, sizeof(u32) * total); s->heap_block = NULL; } + if (s->heap_lang_types_block) { + h->free(h, s->heap_lang_types_block, + sizeof(void*) * (s->nresults + s->nparams)); + s->heap_lang_types_block = NULL; + } + if (s->heap_lang_flags_block) { + h->free(h, s->heap_lang_flags_block, + sizeof(u16) * (s->nresults + s->nparams)); + s->heap_lang_flags_block = NULL; + } } static KitCgScope api_scope_begin_sig_kind(KitCg* g, u8 kind, @@ -683,23 +791,47 @@ static KitCgScope api_scope_begin_sig_kind(KitCg* g, u8 kind, return api_scope_handle(idx, s->generation); } -KitCgScope kit_cg_scope_begin(KitCg* g, KitCgTypeId result_type) { +KitCgScope kit_cg_scope_begin(KitCg* g) { + KitCgScopeSig sig; + memset(&sig, 0, sizeof sig); + return api_scope_begin_sig_kind(g, (u8)SCOPE_LOOP, &sig); +} + +KitCgScope kit_cg_scope_begin_value(KitCg* g, KitCgTypeId result_type) { KitCgScopeSig sig; + KitCgTypeId resolved; memset(&sig, 0, sizeof sig); - if (g && resolve_type(g->c, result_type) != KIT_CG_TYPE_NONE) { - sig.results = &result_type; - sig.nresults = 1u; + if (!g) return 0; + resolved = resolve_type(g->c, result_type); + if (resolved == KIT_CG_TYPE_NONE || cg_type_is_void(g->c, resolved)) { + compiler_panic(g->c, g->cur_loc, + "KitCg: value scope requires non-void result type"); + return 0; } + sig.results = &result_type; + sig.nresults = 1u; return api_scope_begin_sig_kind(g, (u8)SCOPE_LOOP, &sig); } -KitCgScope kit_cg_block_begin(KitCg* g, KitCgTypeId result_type) { +KitCgScope kit_cg_block_begin(KitCg* g) { + KitCgScopeSig sig; + memset(&sig, 0, sizeof sig); + return api_scope_begin_sig_kind(g, (u8)SCOPE_BLOCK, &sig); +} + +KitCgScope kit_cg_block_begin_value(KitCg* g, KitCgTypeId result_type) { KitCgScopeSig sig; + KitCgTypeId resolved; memset(&sig, 0, sizeof sig); - if (g && resolve_type(g->c, result_type) != KIT_CG_TYPE_NONE) { - sig.results = &result_type; - sig.nresults = 1u; + if (!g) return 0; + resolved = resolve_type(g->c, result_type); + if (resolved == KIT_CG_TYPE_NONE || cg_type_is_void(g->c, resolved)) { + compiler_panic(g->c, g->cur_loc, + "KitCg: value block requires non-void result type"); + return 0; } + sig.results = &result_type; + sig.nresults = 1u; return api_scope_begin_sig_kind(g, (u8)SCOPE_BLOCK, &sig); } @@ -777,8 +909,11 @@ void kit_cg_scope_end_unreachable(KitCg* g, KitCgScope scope) { static void api_scope_store_results_from(KitCg* g, ApiCgScope* s, ApiSValue* rs) { u32 k; - for (k = 0; k < s->nresults; ++k) + for (k = 0; k < s->nresults; ++k) { + s->result_lang_types[k] = rs[k].lang_type; + s->result_lang_flags[k] = rs[k].lang_flags; api_scope_store_one(g, s->result_locals[k], s->result_types[k], &rs[k]); + } } /* Shared body of break_true / break_false: pop the condition, then — when the @@ -805,7 +940,7 @@ static void api_break_cond(KitCg* g, KitCgScope scope, int break_when, _Alignof(ApiSValue)); if (!rs) compiler_panic(g->c, g->cur_loc, "KitCg: out of memory"); api_scope_pop_results(g, s, rs); - if (cond.kind == SV_OPERAND && cond.op.kind == OPK_IMM) { + if (api_sv_kind(&cond) == SV_OPERAND && cond.op.kind == OPK_IMM) { if ((cond.op.v.imm != 0) == !!break_when) { api_scope_store_results_from(g, s, rs); api_local_const_control_boundary(g); @@ -895,6 +1030,12 @@ void kit_cg_alloca(KitCg* g, uint32_t align, KitCgTypeId result_ptr_type) { sz = api_pop(g); pty = resolve_type(g->c, result_ptr_type); if (!pty) pty = cg_type_ptr_to(g->c, builtin_id(KIT_CG_BUILTIN_VOID)); + if (api_unevaluated(g)) { + api_release(g, &sz); + api_push(g, api_uneval_value(g, pty)); + api_const_set_top(g, api_const_unknown(pty)); + return; + } sz_op = api_sv_op_is(&sz, OPK_IMM) ? sz.op : api_force_local(g, &sz, api_sv_type(&sz)); @@ -912,6 +1053,10 @@ void kit_cg_vararg_start(KitCg* g) { if (!g) return; T = g->target; ap = api_pop(g); + if (api_unevaluated(g)) { + api_release(g, &ap); + return; + } ap_op = api_force_local(g, &ap, api_sv_type(&ap)); T->va_start_(T, ap_op); api_release(g, &ap); @@ -929,6 +1074,12 @@ void kit_cg_vararg_next(KitCg* g, KitCgTypeId type) { ty = resolve_type(g->c, type); if (!ty) return; ap = api_pop(g); + if (api_unevaluated(g)) { + api_release(g, &ap); + api_push(g, api_uneval_value(g, ty)); + api_const_set_top(g, api_const_unknown(ty)); + return; + } ap_op = api_force_local(g, &ap, api_sv_type(&ap)); rr = api_alloc_temp_local(g, ty); dst = api_op_local(rr, ty); @@ -944,6 +1095,10 @@ void kit_cg_vararg_end(KitCg* g) { if (!g) return; T = g->target; ap = api_pop(g); + if (api_unevaluated(g)) { + api_release(g, &ap); + return; + } ap_op = api_force_local(g, &ap, api_sv_type(&ap)); T->va_end_(T, ap_op); api_release(g, &ap); @@ -957,6 +1112,11 @@ void kit_cg_vararg_copy(KitCg* g) { T = g->target; src = api_pop(g); dst = api_pop(g); + if (api_unevaluated(g)) { + api_release(g, &src); + api_release(g, &dst); + return; + } src_op = api_force_local(g, &src, api_sv_type(&src)); dst_op = api_force_local(g, &dst, api_sv_type(&dst)); T->va_copy_(T, dst_op, src_op); @@ -984,6 +1144,11 @@ void kit_cg_memcpy(KitCg* g, uint64_t size, KitCgMemAccess dst_access, T = g->target; src = api_pop(g); dst = api_pop(g); + if (api_unevaluated(g)) { + api_release(g, &src); + api_release(g, &dst); + return; + } api_require_pointer_value(g, "memcpy destination", api_sv_type(&dst)); api_require_pointer_value(g, "memcpy source", api_sv_type(&src)); dst_op = api_force_local(g, &dst, api_sv_type(&dst)); @@ -1010,6 +1175,11 @@ void kit_cg_memmove(KitCg* g, uint64_t size, KitCgMemAccess dst_access, } src = api_pop(g); dst = api_pop(g); + if (api_unevaluated(g)) { + api_release(g, &src); + api_release(g, &dst); + return; + } api_require_pointer_value(g, "memmove destination", api_sv_type(&dst)); api_require_pointer_value(g, "memmove source", api_sv_type(&src)); args[0] = api_force_local(g, &dst, api_sv_type(&dst)); @@ -1034,6 +1204,10 @@ void kit_cg_memset(KitCg* g, uint8_t val, uint64_t size, } T = g->target; dst = api_pop(g); + if (api_unevaluated(g)) { + api_release(g, &dst); + return; + } api_require_pointer_value(g, "memset destination", api_sv_type(&dst)); dst_op = api_force_local(g, &dst, api_sv_type(&dst)); byte_val = api_op_imm((i64)val, KIT_CG_TYPE_NONE); @@ -1097,6 +1271,13 @@ static void api_cg_elem(KitCg* g, u32 elem_size, int64_t offset) { elemsz = elem_size ? elem_size : (u32)abi_cg_sizeof(g->c->abi, elem_ty); idx_ty = idx.type ? idx.type : idx.op.type; if (!idx_ty) idx_ty = builtin_id(KIT_CG_BUILTIN_I64); + if (api_unevaluated(g)) { + api_release(g, &base); + api_release(g, &idx); + api_push(g, api_uneval_place(g, elem_ty)); + api_const_set_top(g, api_const_unknown(elem_ty)); + return; + } idx_op = api_force_local_unless_imm(g, &idx, idx_ty); /* Constant index folds entirely into the displacement — no instructions, just @@ -1107,7 +1288,7 @@ static void api_cg_elem(KitCg* g, u32 elem_size, int64_t offset) { i64 ofs; Operand place; if (cg_checked_scaled_offset(idx_op.v.imm, elemsz, offset, &ofs) && - base.kind == SV_OPERAND && base.op.kind == OPK_GLOBAL && + api_sv_kind(&base) == SV_OPERAND && base.op.kind == OPK_GLOBAL && !__builtin_add_overflow(base.op.v.global.addend, ofs, &ofs)) { place = api_op_global(base.op.v.global.sym, ofs, elem_ty); } else { @@ -1212,9 +1393,84 @@ void kit_cg_elem_scaled(KitCg* g, uint32_t elem_size, int64_t offset) { api_cg_elem(g, elem_size, offset); } +static void api_cg_field_at_place(KitCg* g, ApiSValue base, + int64_t field_offset, KitCgTypeId field_ty) { + CgTarget* T; + KitCgTypeId base_ty; + KitCgTypeId base_ptr_ty; + Operand result; + if (!g) return; + T = g->target; + api_ensure_local(g, &base); + if (!api_is_lvalue_sv(&base)) { + compiler_panic(g->c, g->cur_loc, + "KitCg: field_at requires a place; deref a pointer first"); + api_release(g, &base); + return; + } + field_ty = resolve_type(g->c, field_ty); + if (!field_ty) { + compiler_panic(g->c, g->cur_loc, "KitCg: field_at has invalid field type"); + api_release(g, &base); + return; + } + if (api_unevaluated(g)) { + api_release(g, &base); + api_push(g, api_uneval_place(g, field_ty)); + api_const_set_top(g, api_const_unknown(field_ty)); + return; + } + base_ty = api_sv_type(&base); + if (!base_ty) base_ty = builtin_id(KIT_CG_BUILTIN_VOID); + base_ptr_ty = cg_type_ptr_to(g->c, base_ty); + if (base.op.kind == OPK_GLOBAL) { + i64 addend; + if (!__builtin_add_overflow(base.op.v.global.addend, field_offset, + &addend)) { + result = api_op_global(base.op.v.global.sym, addend, field_ty); + api_release(g, &base); + api_push(g, api_make_lv(result, field_ty)); + return; + } + } else if (base.op.kind == OPK_INDIRECT && field_offset >= INT32_MIN && + field_offset <= INT32_MAX) { + i32 ofs; + if (!__builtin_add_overflow(base.op.v.ind.ofs, (i32)field_offset, &ofs)) { + /* Fold the field offset into the displacement, preserving any index/scale + * a preceding `elem` left so `p[i].f` stays one + * [base+index*scale+off]. */ + result = api_op_indirect_indexed(base.op.v.ind.base, base.op.v.ind.index, + base.op.v.ind.log2_scale, ofs, field_ty); + api_release(g, &base); + api_push(g, api_make_lv(result, field_ty)); + return; + } + } + { + Operand base_addr = api_lvalue_addr(g, &base, base_ptr_ty); + if (field_offset == 0) { + result = base_addr; + } else { + CGLocal fr = api_alloc_temp_local(g, base_ptr_ty); + result = api_op_local(fr, base_ptr_ty); + T->binop(T, BO_IADD, result, base_addr, + api_op_imm(field_offset, base_ptr_ty)); + } + api_release(g, &base); + api_push( + g, api_make_lv(api_op_indirect(result.v.local, 0, field_ty), field_ty)); + } +} + +void kit_cg_field_at(KitCg* g, int64_t byte_offset, KitCgTypeId field_type) { + ApiSValue base; + if (!g) return; + base = api_pop(g); + api_cg_field_at_place(g, base, byte_offset, field_type); +} + void kit_cg_field(KitCg* g, uint32_t field_index) { ApiSValue base; - CgTarget* T; KitCgTypeId rec_ty; KitCgTypeId base_ty; KitCgTypeId field_ty; @@ -1222,10 +1478,7 @@ void kit_cg_field(KitCg* g, uint32_t field_index) { const CgType* rec_info; const ABIRecordLayout* layout; u32 field_offset; - Operand result; - CGLocal rr; if (!g) return; - T = g->target; base = api_pop(g); api_ensure_local(g, &base); base_ty = api_sv_type(&base); @@ -1251,6 +1504,22 @@ void kit_cg_field(KitCg* g, uint32_t field_index) { } field_ty = rec_info->record.fields[field_index].type; field_offset = layout->fields[field_index].offset; + if (api_unevaluated(g)) { + ApiSValue sv = api_uneval_place(g, field_ty); + if (layout->fields[field_index].bit_width != 0 || + (rec_info->record.fields[field_index].flags & KIT_CG_FIELD_BITFIELD) != + 0) { + sv.bitfield.bit_offset = layout->fields[field_index].bit_offset; + sv.bitfield.bit_width = layout->fields[field_index].bit_width; + sv.bitfield.bit_storage_size = layout->fields[field_index].storage_size; + sv.bitfield.bit_signed = + rec_info->record.fields[field_index].bit_signed ? 1u : 0u; + } + api_release(g, &base); + api_push(g, sv); + api_const_set_top(g, api_const_unknown(field_ty)); + return; + } if (layout->fields[field_index].bit_width != 0 || (rec_info->record.fields[field_index].flags & KIT_CG_FIELD_BITFIELD) != 0) { @@ -1276,36 +1545,7 @@ void kit_cg_field(KitCg* g, uint32_t field_index) { api_push(g, sv); return; } - if (base.op.kind == OPK_GLOBAL) { - result = - api_op_global(base.op.v.global.sym, - base.op.v.global.addend + (i64)field_offset, field_ty); - api_push(g, api_make_lv(result, field_ty)); - } else if (base.op.kind == OPK_INDIRECT && field_offset <= (u32)INT32_MAX && - base.op.v.ind.ofs <= INT32_MAX - (i32)field_offset) { - /* Fold the field offset into the displacement, preserving any index/scale - * a preceding `elem` left so `p[i].f` stays one [base+index*scale+off]. */ - result = api_op_indirect_indexed( - base.op.v.ind.base, base.op.v.ind.index, base.op.v.ind.log2_scale, - base.op.v.ind.ofs + (i32)field_offset, field_ty); - api_push(g, api_make_lv(result, field_ty)); - } else { - Operand base_addr; - rr = api_alloc_temp_local(g, rec_ptr_ty); - base_addr = api_op_local(rr, rec_ptr_ty); - T->addr_of(T, base_addr, base.op); - api_release(g, &base); - if (field_offset == 0) { - result = base_addr; - } else { - CGLocal fr = api_alloc_temp_local(g, rec_ptr_ty); - result = api_op_local(fr, rec_ptr_ty); - T->binop(T, BO_IADD, result, base_addr, - api_op_imm((i64)field_offset, rec_ptr_ty)); - } - api_push( - g, api_make_lv(api_op_indirect(result.v.local, 0, field_ty), field_ty)); - } + api_cg_field_at_place(g, base, (int64_t)field_offset, field_ty); } void kit_cg_field_bits(KitCg* g, uint16_t bit_offset, uint16_t bit_width, diff --git a/src/cg/fold.c b/src/cg/fold.c @@ -141,11 +141,10 @@ ApiSValue api_make_cmp(KitCg* g, CmpOp op, Operand a, Operand b, .b = b, .a_owned = a_owned ? 1u : 0u, .b_owned = b_owned ? 1u : 0u}; - return (ApiSValue){.kind = SV_CMP, - .type = result_ty, - .res = RES_INHERENT, + return (ApiSValue){.type = result_ty, .source_local = KIT_CG_LOCAL_NONE, - .delayed = d}; + .delayed = d, + .flags = API_SV_PACK(SV_CMP, RES_INHERENT, 0, 0)}; } CmpOp api_invert_cmp(CmpOp op) { @@ -204,23 +203,24 @@ CmpOp api_invert_cmp(CmpOp op) { void api_release_cmp(KitCg* g, ApiSValue* sv) { api_delayed_free(g, sv->delayed); sv->delayed = NULL; - sv->kind = SV_OPERAND; + api_sv_set_kind(sv, SV_OPERAND); } void api_materialize_cmp_to(KitCg* g, ApiSValue* sv, Operand dst) { ApiDelayed* d = sv->delayed; - /* Flag dead-transient operands so the -O0 backend drops them after the compare - * instead of spilling them at the next barrier (eager dead-operand drop). */ + /* Flag dead-transient operands so the -O0 backend drops them after the + * compare instead of spilling them at the next barrier (eager dead-operand + * drop). */ Operand a = api_op_kill_if_dead(g, d->cmp.a, dst); Operand b = api_op_kill_if_dead(g, d->cmp.b, dst); g->target->cmp(g->target, d->cmp.op, dst, a, b); api_delayed_free(g, d); sv->delayed = NULL; - sv->kind = SV_OPERAND; + api_sv_set_kind(sv, SV_OPERAND); sv->op = dst; sv->type = dst.type; - sv->res = RES_LOCAL; - sv->lvalue = 0; + api_sv_set_res(sv, RES_LOCAL); + api_sv_set_lvalue(sv, 0); } /* ============================================================ @@ -241,11 +241,10 @@ ApiSValue api_make_arith_unop(KitCg* g, UnOp op, Operand a, KitCgTypeId ty, .un_op = op, .a = a, .a_owned = a_owned ? 1u : 0u}; - return (ApiSValue){.kind = SV_ARITH, - .type = ty, - .res = RES_INHERENT, + return (ApiSValue){.type = ty, .source_local = KIT_CG_LOCAL_NONE, - .delayed = d}; + .delayed = d, + .flags = API_SV_PACK(SV_ARITH, RES_INHERENT, 0, 0)}; } ApiSValue api_make_arith_binop(KitCg* g, BinOp op, Operand a, Operand b, @@ -257,17 +256,16 @@ ApiSValue api_make_arith_binop(KitCg* g, BinOp op, Operand a, Operand b, .b = b, .a_owned = a_owned ? 1u : 0u, .b_owned = b_owned ? 1u : 0u}; - return (ApiSValue){.kind = SV_ARITH, - .type = ty, - .res = RES_INHERENT, + return (ApiSValue){.type = ty, .source_local = KIT_CG_LOCAL_NONE, - .delayed = d}; + .delayed = d, + .flags = API_SV_PACK(SV_ARITH, RES_INHERENT, 0, 0)}; } void api_release_arith(KitCg* g, ApiSValue* sv) { api_delayed_free(g, sv->delayed); sv->delayed = NULL; - sv->kind = SV_OPERAND; + api_sv_set_kind(sv, SV_OPERAND); } void api_materialize_arith_to(KitCg* g, ApiSValue* sv, Operand dst) { @@ -277,18 +275,19 @@ void api_materialize_arith_to(KitCg* g, ApiSValue* sv, Operand dst) { g->target->unop(g->target, d->arith.un_op, dst, a); } else { /* Flag dead-transient operands so the -O0 backend drops them after the op - * instead of spilling them at the next barrier (eager dead-operand drop). */ + * instead of spilling them at the next barrier (eager dead-operand drop). + */ Operand a = api_op_kill_if_dead(g, d->arith.a, dst); Operand b = api_op_kill_if_dead(g, d->arith.b, dst); g->target->binop(g->target, d->arith.bin_op, dst, a, b); } api_delayed_free(g, d); sv->delayed = NULL; - sv->kind = SV_OPERAND; + api_sv_set_kind(sv, SV_OPERAND); sv->op = dst; sv->type = dst.type; - sv->res = RES_LOCAL; - sv->lvalue = 0; + api_sv_set_res(sv, RES_LOCAL); + api_sv_set_lvalue(sv, 0); } int api_arith_rhs_reusable(const ApiSValue* sv) { @@ -344,8 +343,8 @@ int api_try_strength_reduce(KitCg* g, BinOp* op, KitCgTypeId ty, ApiSValue* a, int a_imm, b_imm; if (!g || !op || !a || !b) return 0; if (!api_foldable_int_type(g->c, ty, &width)) return 0; - a_imm = a->kind == SV_OPERAND && a->op.kind == OPK_IMM; - b_imm = b->kind == SV_OPERAND && b->op.kind == OPK_IMM; + a_imm = api_sv_kind(a) == SV_OPERAND && a->op.kind == OPK_IMM; + b_imm = api_sv_kind(b) == SV_OPERAND && b->op.kind == OPK_IMM; switch (*op) { case BO_IMUL: { /* Both-imm is constant-folded before we get here; need exactly one, and @@ -424,20 +423,21 @@ int api_try_collapse_binop_identity(KitCg* g, BinOp op, KitCgTypeId ty, u64 av = 0; u64 bv = 0; if (!api_foldable_int_type(g->c, ty, &width)) return 0; - if (a->kind == SV_OPERAND && a->op.kind == OPK_IMM) + if (api_sv_kind(a) == SV_OPERAND && a->op.kind == OPK_IMM) av = api_mask_width((u64)a->op.v.imm, width); - if (b->kind == SV_OPERAND && b->op.kind == OPK_IMM) + if (api_sv_kind(b) == SV_OPERAND && b->op.kind == OPK_IMM) bv = api_mask_width((u64)b->op.v.imm, width); - if (b->kind == SV_OPERAND && b->op.kind == OPK_IMM && a->kind == SV_OPERAND && - a->op.kind != OPK_IMM && api_op_is_int_identity(g, op, ty, b->op.v.imm)) { + if (api_sv_kind(b) == SV_OPERAND && b->op.kind == OPK_IMM && + api_sv_kind(a) == SV_OPERAND && a->op.kind != OPK_IMM && + api_op_is_int_identity(g, op, ty, b->op.v.imm)) { *out = api_make_sv_with_local_ownership( a->op, ty, api_sv_owns_operand_local(a, &a->op)); - a->res = RES_INHERENT; + api_sv_set_res(a, RES_INHERENT); return 1; } - if (b->kind == SV_OPERAND && b->op.kind == OPK_IMM && a->kind == SV_OPERAND && - a->op.kind != OPK_IMM && + if (api_sv_kind(b) == SV_OPERAND && b->op.kind == OPK_IMM && + api_sv_kind(a) == SV_OPERAND && a->op.kind != OPK_IMM && (op == BO_SREM || op == BO_UREM || op == BO_IMUL || op == BO_AND || op == BO_OR)) { if ((op == BO_SREM || op == BO_UREM) && bv == 1) { @@ -454,18 +454,19 @@ int api_try_collapse_binop_identity(KitCg* g, BinOp op, KitCgTypeId ty, return 1; } } - if (a->kind == SV_OPERAND && a->op.kind == OPK_IMM && b->kind == SV_OPERAND && - b->op.kind != OPK_IMM && + if (api_sv_kind(a) == SV_OPERAND && a->op.kind == OPK_IMM && + api_sv_kind(b) == SV_OPERAND && b->op.kind != OPK_IMM && (op == BO_IADD || op == BO_IMUL || op == BO_OR || op == BO_XOR || op == BO_AND) && api_op_is_int_identity(g, op, ty, a->op.v.imm)) { *out = api_make_sv_with_local_ownership( b->op, ty, api_sv_owns_operand_local(b, &b->op)); - b->res = RES_INHERENT; + api_sv_set_res(b, RES_INHERENT); return 1; } - if (a->kind == SV_OPERAND && a->op.kind == OPK_IMM && b->kind == SV_OPERAND && - b->op.kind != OPK_IMM && (op == BO_IMUL || op == BO_AND || op == BO_OR)) { + if (api_sv_kind(a) == SV_OPERAND && a->op.kind == OPK_IMM && + api_sv_kind(b) == SV_OPERAND && b->op.kind != OPK_IMM && + (op == BO_IMUL || op == BO_AND || op == BO_OR)) { if ((op == BO_IMUL || op == BO_AND) && av == 0) { *out = api_make_sv(api_op_imm(0, ty), ty); return 1; @@ -484,9 +485,10 @@ int api_try_fold_arith_chain(KitCg* g, BinOp op, KitCgTypeId ty, ApiSValue* a, i64 folded; BinOp result_op; ApiDelayed* ad; - if (a->kind != SV_ARITH || a->delayed->arith.kind != API_DELAYED_BINOP || + if (api_sv_kind(a) != SV_ARITH || + a->delayed->arith.kind != API_DELAYED_BINOP || a->delayed->arith.a.kind != OPK_LOCAL || - a->delayed->arith.b.kind != OPK_IMM || b->kind != SV_OPERAND || + a->delayed->arith.b.kind != OPK_IMM || api_sv_kind(b) != SV_OPERAND || b->op.kind != OPK_IMM) { return 0; } @@ -524,23 +526,23 @@ int api_try_fold_arith_chain(KitCg* g, BinOp op, KitCgTypeId ty, ApiSValue* a, } break; case BO_XOR: - if (op != BO_XOR || !api_try_fold_int_binop(g, BO_XOR, ty, - ad->arith.b.v.imm, - b->op.v.imm, &folded)) + if (op != BO_XOR || + !api_try_fold_int_binop(g, BO_XOR, ty, ad->arith.b.v.imm, b->op.v.imm, + &folded)) return 0; result_op = BO_XOR; break; case BO_AND: - if (op != BO_AND || !api_try_fold_int_binop(g, BO_AND, ty, - ad->arith.b.v.imm, - b->op.v.imm, &folded)) + if (op != BO_AND || + !api_try_fold_int_binop(g, BO_AND, ty, ad->arith.b.v.imm, b->op.v.imm, + &folded)) return 0; result_op = BO_AND; break; case BO_OR: - if (op != BO_OR || !api_try_fold_int_binop(g, BO_OR, ty, - ad->arith.b.v.imm, b->op.v.imm, - &folded)) + if (op != BO_OR || + !api_try_fold_int_binop(g, BO_OR, ty, ad->arith.b.v.imm, b->op.v.imm, + &folded)) return 0; result_op = BO_OR; break; @@ -557,7 +559,8 @@ int api_try_fold_arith_chain(KitCg* g, BinOp op, KitCgTypeId ty, ApiSValue* a, } /* Chain-folds into an updated delayed binop. Move a's payload to out (a * pointer transfer, not a deep copy) and null a's pointer so the caller's - * api_release reclaims nothing — out owns the payload until it materializes. */ + * api_release reclaims nothing — out owns the payload until it materializes. + */ ad->arith.bin_op = result_op; ad->arith.b.v.imm = folded; *out = *a; @@ -567,7 +570,7 @@ int api_try_fold_arith_chain(KitCg* g, BinOp op, KitCgTypeId ty, ApiSValue* a, int api_try_fold_unary_chain(ApiSValue* a, UnOp op, KitCgTypeId ty, ApiSValue* out) { - if (op != UO_BNOT || a->kind != SV_ARITH || + if (op != UO_BNOT || api_sv_kind(a) != SV_ARITH || a->delayed->arith.kind != API_DELAYED_UNOP || a->delayed->arith.un_op != UO_BNOT || a->delayed->arith.a.kind != OPK_LOCAL) { @@ -575,9 +578,8 @@ int api_try_fold_unary_chain(ApiSValue* a, UnOp op, KitCgTypeId ty, } /* out borrows a's input operand (a plain value); a keeps its payload for the * caller's api_release to reclaim. */ - *out = - api_make_sv_with_local_ownership(a->delayed->arith.a, ty, - a->delayed->arith.a_owned); + *out = api_make_sv_with_local_ownership(a->delayed->arith.a, ty, + a->delayed->arith.a_owned); return 1; } diff --git a/src/cg/internal.h b/src/cg/internal.h @@ -9,6 +9,7 @@ #include "abi/abi.h" #include "asm/asm.h" +#include "cg/cgtarget.h" #include "cg/type.h" #include "core/arena.h" #include "core/heap.h" @@ -16,7 +17,6 @@ #include "core/segvec.h" #include "core/slice.h" #include "core/strbuf.h" -#include "cg/cgtarget.h" #include "debug/debug.h" #include "obj/obj.h" @@ -102,17 +102,70 @@ typedef struct ApiSValue { * A pointer (not the 64-byte union inline) keeps the node small; only the * SV_CMP / SV_ARITH producers in fold.c allocate one. */ ApiDelayed* delayed; + const void* lang_type; /* opaque frontend-owned slot fact */ KitCgTypeId type; - u8 kind; - u8 res; - u8 pinned; - u8 lvalue; KitCgLocal source_local; ApiBitField bitfield; /* bit_width != 0 marks a bit-field PLACE subkind */ + u16 lang_flags; /* frontend-defined flags; CG only copies/clears */ + u16 flags; /* packed ApiSValueKind/SResidency/pinned/lvalue */ } ApiSValue; +typedef struct ApiConstValue { + KitCgConstInt value; + KitCgTypeId type; +} ApiConstValue; + #define API_CG_STACK_INITIAL 16u +#define API_SV_KIND_SHIFT 0u +#define API_SV_RES_SHIFT 2u +#define API_SV_PINNED_SHIFT 4u +#define API_SV_LVALUE_SHIFT 5u +#define API_SV_KIND_MASK 0x3u +#define API_SV_RES_MASK 0x3u +#define API_SV_BOOL_MASK 0x1u +#define API_SV_PACK(kind_, res_, pinned_, lvalue_) \ + ((u16)((((u16)(kind_) & API_SV_KIND_MASK) << API_SV_KIND_SHIFT) | \ + (((u16)(res_) & API_SV_RES_MASK) << API_SV_RES_SHIFT) | \ + (((u16)(pinned_) & API_SV_BOOL_MASK) << API_SV_PINNED_SHIFT) | \ + (((u16)(lvalue_) & API_SV_BOOL_MASK) << API_SV_LVALUE_SHIFT))) + +static inline ApiSValueKind api_sv_kind(const ApiSValue* sv) { + return (ApiSValueKind)((sv->flags >> API_SV_KIND_SHIFT) & API_SV_KIND_MASK); +} + +static inline void api_sv_set_kind(ApiSValue* sv, ApiSValueKind kind) { + sv->flags = (u16)((sv->flags & ~(API_SV_KIND_MASK << API_SV_KIND_SHIFT)) | + (((u16)kind & API_SV_KIND_MASK) << API_SV_KIND_SHIFT)); +} + +static inline SResidency api_sv_res(const ApiSValue* sv) { + return (SResidency)((sv->flags >> API_SV_RES_SHIFT) & API_SV_RES_MASK); +} + +static inline void api_sv_set_res(ApiSValue* sv, SResidency res) { + sv->flags = (u16)((sv->flags & ~(API_SV_RES_MASK << API_SV_RES_SHIFT)) | + (((u16)res & API_SV_RES_MASK) << API_SV_RES_SHIFT)); +} + +static inline int api_sv_pinned(const ApiSValue* sv) { + return (int)((sv->flags >> API_SV_PINNED_SHIFT) & API_SV_BOOL_MASK); +} + +static inline void api_sv_set_pinned(ApiSValue* sv, int pinned) { + sv->flags = (u16)((sv->flags & ~(API_SV_BOOL_MASK << API_SV_PINNED_SHIFT)) | + (((u16)pinned & API_SV_BOOL_MASK) << API_SV_PINNED_SHIFT)); +} + +static inline int api_sv_lvalue_flag(const ApiSValue* sv) { + return (int)((sv->flags >> API_SV_LVALUE_SHIFT) & API_SV_BOOL_MASK); +} + +static inline void api_sv_set_lvalue(ApiSValue* sv, int lvalue) { + sv->flags = (u16)((sv->flags & ~(API_SV_BOOL_MASK << API_SV_LVALUE_SHIFT)) | + (((u16)lvalue & API_SV_BOOL_MASK) << API_SV_LVALUE_SHIFT)); +} + /* Largest scalar the codegen lowers as a native (lock-free) atomic. All * current targets — aa64, x64, rv64, wasm32 — provide 8-byte (i64-width) * atomics, so this is both the legality ceiling and the lock-free ceiling. @@ -139,15 +192,25 @@ typedef struct ApiCgScope { * nparams==0 reproduces the old single-result path byte-for-byte. */ CGLocal* result_locals; KitCgTypeId* result_types; + const void** result_lang_types; + u16* result_lang_flags; CGLocal* param_locals; KitCgTypeId* param_types; + const void** param_lang_types; + u16* param_lang_flags; u32 nresults; u32 nparams; CGLocal result_locals_inl[API_CG_SCOPE_SIG_INLINE]; KitCgTypeId result_types_inl[API_CG_SCOPE_SIG_INLINE]; + const void* result_lang_types_inl[API_CG_SCOPE_SIG_INLINE]; + u16 result_lang_flags_inl[API_CG_SCOPE_SIG_INLINE]; CGLocal param_locals_inl[API_CG_SCOPE_SIG_INLINE]; KitCgTypeId param_types_inl[API_CG_SCOPE_SIG_INLINE]; - u32* heap_block; /* non-NULL when arity spilled to heap; freed at scope_end */ + const void* param_lang_types_inl[API_CG_SCOPE_SIG_INLINE]; + u16 param_lang_flags_inl[API_CG_SCOPE_SIG_INLINE]; + u32* heap_block; /* non-NULL when u32 arity vectors spilled to heap */ + const void** heap_lang_types_block; + u16* heap_lang_flags_block; u32 generation; u8 active; u8 pad[3]; @@ -202,8 +265,10 @@ struct KitCg { u8 lifecycle_pad[2]; ApiSValue* stack; + ApiConstValue* const_stack; u32 sp; u32 cap; + u32 unevaluated_depth; /* -O0 transient liveness. local_refs[h] = number of live value-stack entries * that reference CGLocal handle h (maintained at api_push/api_pop, plus a @@ -212,20 +277,21 @@ struct KitCg { * backend recycles (Fix A) or coalesces. The count is used only as a * fast-reject prefilter: a transient is treated as dead solely when the count * reads 0 AND a confirming stack scan (api_temp_dead) agrees, so any reseat - * gap costs at most a missed optimization, never a miscompile. Sized on demand - * by handle; cleared per function. NULL/0 until first use. */ + * gap costs at most a missed optimization, never a miscompile. Sized on + * demand by handle; cleared per function. NULL/0 until first use. */ u32* local_refs; /* live value-stack reference count per handle */ u32* local_temp_gen; /* == func_gen iff the handle is this function's temp */ u32 local_track_cap; - u32 func_gen; /* bumped each function; stamps temps without O(cap) clears */ - u8 coalesce; /* -O0 copy/dup coalescing + finer reclaim enabled */ + u32 func_gen; /* bumped each function; stamps temps without O(cap) clears */ + u8 coalesce; /* -O0 copy/dup coalescing + finer reclaim enabled */ u8 coalesce_known; /* coalesce resolved from env (once) */ u8 coalesce_pad[2]; /* Off-node pool for SV_CMP / SV_ARITH delayed payloads. The arena is reset * per function (api_delayed_reset at func_begin, where the stack is also * dropped); delayed_free is an intrusive freelist that reuses payloads within - * a function so the arena only grows to the live delayed-value working set. */ + * a function so the arena only grows to the live delayed-value working set. + */ Arena delayed_arena; ApiDelayed* delayed_free; u8 delayed_arena_init; @@ -375,7 +441,10 @@ ApiCgScope* api_scope_from_handle(KitCg* g, KitCgScope scope, int require_top, int api_scope_has_result(const ApiCgScope* s); void api_scope_store_results(KitCg* g, ApiCgScope* s); void api_scope_push_results(KitCg* g, ApiCgScope* s); -KitCgScope kit_cg_scope_begin(KitCg* g, KitCgTypeId result_type); +KitCgScope kit_cg_scope_begin(KitCg* g); +KitCgScope kit_cg_scope_begin_value(KitCg* g, KitCgTypeId result_type); +KitCgScope kit_cg_block_begin(KitCg* g); +KitCgScope kit_cg_block_begin_value(KitCg* g, KitCgTypeId result_type); KitCgScope kit_cg_scope_begin_sig(KitCg* g, const KitCgScopeSig* sig); KitCgScope kit_cg_block_begin_sig(KitCg* g, const KitCgScopeSig* sig); void kit_cg_scope_store_params(KitCg* g, KitCgScope scope); @@ -444,10 +513,17 @@ void kit_cg_load(KitCg* g, KitCgMemAccess access); void kit_cg_addr(KitCg* g); void kit_cg_deref(KitCg* g, int64_t offset); void kit_cg_store(KitCg* g, KitCgMemAccess access); +void kit_cg_store_keep(KitCg* g, KitCgMemAccess access); +void kit_cg_field_at(KitCg* g, int64_t byte_offset, KitCgTypeId field_type); void kit_cg_dup(KitCg* g); void kit_cg_dup2(KitCg* g); void kit_cg_swap(KitCg* g); void kit_cg_drop(KitCg* g); +KitCgSlotInfo kit_cg_slot_info(KitCg* g, uint32_t depth_from_top); +void kit_cg_retag_top(KitCg* g, const void* lang_type, uint16_t lang_flags); +void kit_cg_retag_at(KitCg* g, uint32_t depth_from_top, const void* lang_type, + uint16_t lang_flags); +void kit_cg_set_top_flags(KitCg* g, uint16_t set, uint16_t clear); uint32_t kit_cg_stack_depth(KitCg* g); int kit_cg_top_const_int(KitCg* g, int64_t* out_value); void kit_cg_rot3(KitCg* g); @@ -507,6 +583,28 @@ MemAccess api_mem_for_bitfield(KitCg* g, const ApiSValue* sv, void api_stack_grow(KitCg* g, u32 want); void api_push(KitCg* g, ApiSValue v); ApiSValue api_pop(KitCg* g); +int api_unevaluated(KitCg* g); +ApiSValue api_uneval_value(KitCg* g, KitCgTypeId type); +ApiSValue api_uneval_place(KitCg* g, KitCgTypeId type); +ApiConstValue api_const_unknown(KitCgTypeId type); +ApiConstValue api_const_from_sv(KitCg* g, const ApiSValue* sv); +ApiConstValue api_const_at(KitCg* g, u32 depth); +void api_const_set_top(KitCg* g, ApiConstValue value); +void api_const_set_at(KitCg* g, u32 depth, ApiConstValue value); +void api_const_copy_top_from(KitCg* g, ApiConstValue value); +ApiConstValue api_const_for_push(KitCg* g, KitCgTypeId type, + const KitCgConstInt* value); +int api_const_fold_binop(KitCg* g, BinOp op, KitCgTypeId type, ApiConstValue a, + ApiConstValue b, u32 flags, ApiConstValue* out); +int api_const_fold_unop(KitCg* g, UnOp op, KitCgTypeId type, ApiConstValue a, + u32 flags, ApiConstValue* out); +int api_const_fold_cmp(KitCg* g, CmpOp op, ApiConstValue a, ApiConstValue b, + ApiConstValue* out); +int api_const_fold_convert(KitCg* g, ConvKind ck, KitCgTypeId src_type, + KitCgTypeId dst_type, ApiConstValue in, + ApiConstValue* out); +ApiConstValue api_const_int_result(KitCg* g, KitCgTypeId type, u64 lo, u64 hi, + int is_signed); CGLocal api_local_of_sv(const ApiSValue* sv); void api_set_owned_local(ApiSValue* sv, CGLocal r); KitCgTypeId api_owned_local_type(KitCg* g, const ApiSValue* sv); @@ -527,22 +625,23 @@ Operand api_force_local(KitCg* g, ApiSValue* v, KitCgTypeId ty); Operand api_force_local_unless_imm(KitCg* g, ApiSValue* v, KitCgTypeId ty); void api_release(KitCg* g, ApiSValue* sv); -/* -O0 transient liveness (see KitCg.local_refs). api_coalesce_on returns whether - * the copy/dup coalescing + finer-reclaim mechanisms are enabled this run. - * api_temp_dead reports whether transient `local` is provably dead right now (no - * live value-stack entry references it): count==0 confirmed by a stack scan. - * api_reseat_{begin,end} bracket an in-place change to a stack entry's operand so - * its references are re-accounted. */ +/* -O0 transient liveness (see KitCg.local_refs). api_coalesce_on returns + * whether the copy/dup coalescing + finer-reclaim mechanisms are enabled this + * run. api_temp_dead reports whether transient `local` is provably dead right + * now (no live value-stack entry references it): count==0 confirmed by a stack + * scan. api_reseat_{begin,end} bracket an in-place change to a stack entry's + * operand so its references are re-accounted. */ int api_coalesce_on(KitCg* g); int api_temp_dead(KitCg* g, CGLocal local); -/* Flag OP for the -O0 backend's eager dead-operand drop: when coalescing is on and - * OP names a transient that api_temp_dead confirms is dead after the consuming op, - * AND OP is distinct from that op's destination DST (so it is not the result being - * written), return OP with OPK_FLAG_KILL set; otherwise return OP unchanged. The - * NativeDirectTarget then drops OP's live cache register after the op (no spill, no - * reload) instead of leaving it resident to be flush-stored at the next barrier. - * Pass a non-OPK_LOCAL operand for DST (e.g. the store's memory place) when the op - * has no local destination — the self-guard is then a no-op. */ +/* Flag OP for the -O0 backend's eager dead-operand drop: when coalescing is on + * and OP names a transient that api_temp_dead confirms is dead after the + * consuming op, AND OP is distinct from that op's destination DST (so it is not + * the result being written), return OP with OPK_FLAG_KILL set; otherwise return + * OP unchanged. The NativeDirectTarget then drops OP's live cache register + * after the op (no spill, no reload) instead of leaving it resident to be + * flush-stored at the next barrier. Pass a non-OPK_LOCAL operand for DST (e.g. + * the store's memory place) when the op has no local destination — the + * self-guard is then a no-op. */ Operand api_op_kill_if_dead(KitCg* g, Operand op, Operand dst); void api_reseat_begin(KitCg* g, const ApiSValue* sv); void api_reseat_end(KitCg* g, const ApiSValue* sv); @@ -559,6 +658,8 @@ void api_store_f128_bytes(KitCg* g, CGLocal local, KitCgTypeId ty, const u8 bytes[16]); void api_wide16_sext_imm_bytes(KitCg* g, i64 imm, u8 bytes[16]); ApiSValue api_make_wide16_int_const(KitCg* g, i64 value, KitCgTypeId ty); +ApiSValue api_make_wide16_int_const_bits(KitCg* g, u64 lo, u64 hi, + KitCgTypeId ty); void api_encode_binary128_from_double(KitCg* g, double value, u8 out[16]); ApiSValue api_make_f128_const(KitCg* g, double value, KitCgTypeId ty); ApiSValue api_wide16_materialize_lvalue(KitCg* g, ApiSValue* v, KitCgTypeId ty); diff --git a/src/cg/local.c b/src/cg/local.c @@ -75,7 +75,9 @@ KitCgLocal kit_cg_local(KitCg* g, KitCgTypeId type, KitCgLocalAttrs attrs) { desc.align = attrs.align ? attrs.align : abi_cg_alignof(g->c->abi, type); if (api_local_requires_memory(g, ty, attrs)) desc.flags |= CG_LOCAL_MEMORY_REQUIRED; - if (g->target->local) + if (api_unevaluated(g)) { + storage = CG_LOCAL_NONE; + } else if (g->target->local) storage = g->target->local(g->target, &desc); else storage = api_frame_local_storage(g, &desc); @@ -121,7 +123,8 @@ KitCgLocal kit_cg_param(KitCg* g, uint32_t index, KitCgTypeId type, if (api_local_requires_memory(g, ty, attrs)) pd.flags |= CG_LOCAL_MEMORY_REQUIRED; pd.loc = g->cur_loc; - storage = g->target->param(g->target, &pd); + storage = + api_unevaluated(g) ? CG_LOCAL_NONE : g->target->param(g->target, &pd); rec = &g->locals[g->nlocals++]; memset(rec, 0, sizeof *rec); diff --git a/src/cg/memory.c b/src/cg/memory.c @@ -2,23 +2,56 @@ void kit_cg_push_int(KitCg* g, uint64_t value, KitCgTypeId type) { KitCgTypeId ty; + ApiConstValue cv; if (!g) return; ty = resolve_type(g->c, type); if (!ty) return; + cv = api_const_int_result(g, ty, value, 0, 0); + if (api_unevaluated(g)) { + api_push(g, api_uneval_value(g, ty)); + api_const_set_top(g, cv); + return; + } /* A 16-byte scalar immediate cannot be represented by the 64-bit op.v.imm * alone; materialize it into addressable storage with both lanes * sign-extended so no downstream consumer sees an undefined high half. */ if (api_is_wide16_scalar_type(g->c, ty)) { api_push(g, api_make_wide16_int_const(g, (i64)value, ty)); + api_const_set_top(g, cv); return; } /* Split-lane 8-byte int: the 64-bit value fits in op.v.imm, but the value is * memory-resident, so materialize it as two 32-bit lanes. */ if (api_is_wide8_scalar_type(g->c, ty)) { api_push(g, api_make_wide8_int_const(g, (i64)value, ty)); + api_const_set_top(g, cv); return; } api_push(g, api_make_sv(api_op_imm((i64)value, ty), ty)); + api_const_set_top(g, cv); +} + +void kit_cg_push_const_int(KitCg* g, KitCgTypeId type, + const KitCgConstInt* value) { + KitCgTypeId ty; + ApiConstValue cv; + if (!g) return; + ty = resolve_type(g->c, type); + if (!ty) return; + cv = api_const_for_push(g, ty, value); + if (api_unevaluated(g)) { + api_push(g, api_uneval_value(g, ty)); + api_const_set_top(g, cv); + return; + } + if (cv.value.known && cv.value.width > 64u) { + api_push(g, + api_make_wide16_int_const_bits(g, cv.value.lo, cv.value.hi, ty)); + api_const_set_top(g, cv); + return; + } + kit_cg_push_int(g, cv.value.known ? cv.value.lo : 0, ty); + api_const_set_top(g, cv); } void kit_cg_push_float(KitCg* g, double value, KitCgTypeId type) { @@ -35,6 +68,11 @@ void kit_cg_push_float(KitCg* g, double value, KitCgTypeId type) { if (!g) return; ty = resolve_type(g->c, type); if (!ty) return; + if (api_unevaluated(g)) { + api_push(g, api_uneval_value(g, ty)); + api_const_set_top(g, api_const_unknown(ty)); + return; + } if (api_is_f128_type(g->c, ty)) { api_push(g, api_make_f128_const(g, value, ty)); return; @@ -67,10 +105,13 @@ void kit_cg_push_float(KitCg* g, double value, KitCgTypeId type) { void kit_cg_push_null(KitCg* g, KitCgTypeId ptr_type) { KitCgTypeId ty; + ApiConstValue cv; if (!g) return; ty = resolve_type(g->c, ptr_type); if (!ty) return; + cv = api_const_int_result(g, ty, 0, 0, 0); api_push(g, api_make_sv(api_op_imm(0, ty), ty)); + api_const_set_top(g, cv); } static int api_const_data_can_defer(const KitCg* g) { @@ -119,6 +160,7 @@ KitCgSym kit_cg_const_data(KitCg* g, const uint8_t* data, size_t len, KitCgDecl attrs; int defer; if (!g) return KIT_CG_SYM_NONE; + if (api_unevaluated(g)) return KIT_CG_SYM_NONE; c = g->c; ob = g->obj; pty = resolve_type(c, pointee_type); @@ -156,6 +198,7 @@ KitCgSym kit_cg_const_data(KitCg* g, const uint8_t* data, size_t len, void api_push_local_lvalue(KitCg* g, CGLocal local, KitCgTypeId type) { if (!g) return; api_push(g, api_make_lv(api_op_local(local, type), type)); + api_const_set_top(g, api_const_unknown(type)); } void api_push_source_local_lvalue(KitCg* g, KitCgLocal source_local, @@ -187,6 +230,11 @@ void kit_cg_push_symbol_addr(KitCg* g, KitCgSym sym, int64_t addend) { ty = api_sym_type(g, sym); if (!ty) ty = builtin_id(KIT_CG_BUILTIN_VOID); ptr_ty = cg_type_ptr_to(g->c, ty); + if (api_unevaluated(g)) { + api_push(g, api_uneval_value(g, ptr_ty)); + api_const_set_top(g, api_const_unknown(ptr_ty)); + return; + } if (api_sym_is_tls(g, sym)) { CGLocal r = api_alloc_temp_local(g, ptr_ty); Operand dst = api_op_local(r, ptr_ty); @@ -226,6 +274,18 @@ static Operand place_operand_for_access(Operand op, KitCgTypeId access_ty) { } } +static KitCgMemAccess api_load_access_with_hints(KitCg* g, + KitCgMemAccess access, + KitCgTypeId access_ty, + int is_bitfield) { + if (!is_bitfield && (access.flags & KIT_CG_MEM_SOURCE_SIGNED) && + cg_type_is_int(g->c, access_ty) && + (u32)api_mem_type_size(g, access_ty, "load") < 4u) { + access.flags |= KIT_CG_MEM_SEXT_LOAD; + } + return access; +} + /* Load a VALUE from the PLACE on TOS. The place encodes the full address (built * by push_local / deref / field / elem); there is no EA rider. Strict: the * operand must be a PLACE — a pointer VALUE must be `deref`'d first. */ @@ -262,6 +322,8 @@ void kit_cg_load(KitCg* g, KitCgMemAccess access) { "size %u, lvalue size %u", (unsigned)access_size, (unsigned)lvalue_size); } + base.lang_type = NULL; + base.lang_flags = 0; api_push(g, base); return; } @@ -272,6 +334,14 @@ void kit_cg_load(KitCg* g, KitCgMemAccess access) { access_ty = ty; if (!is_bitfield) api_require_scalar_mem_type(g, "load", access_ty); + access = api_load_access_with_hints(g, access, access_ty, is_bitfield); + + if (api_unevaluated(g)) { + api_release(g, &base); + api_push(g, api_uneval_value(g, access_ty)); + api_const_set_top(g, api_const_unknown(access_ty)); + return; + } /* Source-local constant load. */ if (!is_bitfield && base.source_local != KIT_CG_LOCAL_NONE && @@ -286,14 +356,15 @@ void kit_cg_load(KitCg* g, KitCgMemAccess access) { * (api_type_pred_bits also warms the entry's unalias cache), then read the * aggregate bit and the unalias terminal locally instead of re-decoding. */ if (!is_bitfield && base.source_local != KIT_CG_LOCAL_NONE && - base.op.kind == OPK_LOCAL && !api_sv_local_storage_is_aggregate(g, &base)) { + base.op.kind == OPK_LOCAL && + !api_sv_local_storage_is_aggregate(g, &base)) { KitCgTypeId base_ty = api_sv_type(&base); u8 base_bits = api_type_pred_bits(g->c, base_ty); u8 ty_bits = api_type_pred_bits(g->c, ty); if (!(base_bits & API_PRED_AGGREGATE) && !(ty_bits & API_PRED_AGGREGATE) && api_unalias_type(g->c, base_ty) == api_unalias_type(g->c, ty)) { - base.lvalue = 0; - base.res = RES_FIXED_LOCAL; + api_sv_set_lvalue(&base, 0); + api_sv_set_res(&base, RES_FIXED_LOCAL); api_push(g, base); return; } @@ -344,9 +415,15 @@ void kit_cg_deref(KitCg* g, int64_t offset) { } pointee = cg_type_pointee(g->c, pty); if (!pointee) pointee = builtin_id(KIT_CG_BUILTIN_VOID); + if (api_unevaluated(g)) { + api_release(g, &v); + api_push(g, api_uneval_place(g, pointee)); + api_const_set_top(g, api_const_unknown(pointee)); + return; + } /* A symbol address derefs to a global place, preserving direct * (PC-relative/absolute) addressing rather than materializing the address. */ - if (v.kind == SV_OPERAND && v.op.kind == OPK_GLOBAL) { + if (api_sv_kind(&v) == SV_OPERAND && v.op.kind == OPK_GLOBAL) { api_push(g, api_make_lv(api_op_global(v.op.v.global.sym, v.op.v.global.addend + offset, pointee), @@ -374,16 +451,35 @@ void kit_cg_addr(KitCg* g) { pty = cg_type_ptr_to(g->c, api_sv_type(&v)); if (v.source_local != KIT_CG_LOCAL_NONE) api_local_const_address_taken(g, v.source_local); + if (api_unevaluated(g)) { + api_release(g, &v); + api_push(g, api_uneval_value(g, pty)); + api_const_set_top(g, api_const_unknown(pty)); + return; + } dst = api_lvalue_addr(g, &v, pty); api_release(g, &v); api_push(g, api_make_sv(dst, pty)); } -/* Store the VALUE on TOS into the PLACE beneath it. Stack: [place, value] -> - * []. The place encodes the full address; there is no EA rider. Strict: the - * destination must be a PLACE — a pointer VALUE must be `deref`'d first. */ -void kit_cg_store(KitCg* g, KitCgMemAccess access) { +static void api_push_kept_store_value(KitCg* g, const ApiSValue* rv, + Operand src, KitCgTypeId ty, + ApiConstValue cv) { + ApiSValue out = api_make_sv_with_local_ownership( + src, ty, api_sv_owns_operand_local(rv, &src)); + out.lang_type = rv->lang_type; + out.lang_flags = rv->lang_flags; + api_push(g, out); + api_const_copy_top_from(g, cv); +} + +/* Store the VALUE on TOS into the PLACE beneath it. keep_result selects + * [place,value] -> [value] for assignment expressions without forcing delayed + * RHS values before the scalar-local store fast path can use them. */ +static void api_cg_store_impl(KitCg* g, KitCgMemAccess access, + int keep_result) { ApiSValue base, rv; + ApiConstValue rv_const; CgTarget* T; KitCgTypeId ty; KitCgTypeId access_ty; @@ -397,6 +493,7 @@ void kit_cg_store(KitCg* g, KitCgMemAccess access) { if (access.flags & KIT_CG_MEM_VOLATILE) api_local_const_memory_boundary(g); /* Stack: [base, value] - pop value, then base. */ + rv_const = api_const_at(g, 0); rv = api_pop(g); base = api_pop(g); is_lvalue = api_is_lvalue_sv(&base); @@ -410,6 +507,11 @@ void kit_cg_store(KitCg* g, KitCgMemAccess access) { ty = api_mem_access_type(g, access, api_sv_type(&base), "store"); access_ty = ty; + if (api_unevaluated(g)) { + api_release(g, &base); + api_release(g, &rv); + return; + } /* Aggregate store: memcpy through the source place. `ty` is stable here, so * decode its aggregate predicate once and reuse it below; likewise decode the @@ -422,10 +524,10 @@ void kit_cg_store(KitCg* g, KitCgMemAccess access) { int src_ptr_rvalue; AggregateAccess agg; u32 src_size; - u32 dst_size = ty_is_agg ? api_mem_type_size(g, ty, "store") - : api_mem_type_size(g, api_sv_type(&base), "store"); - u32 access_size = - ty_is_agg ? api_mem_type_size(g, ty, "store") : dst_size; + u32 dst_size = ty_is_agg + ? api_mem_type_size(g, ty, "store") + : api_mem_type_size(g, api_sv_type(&base), "store"); + u32 access_size = ty_is_agg ? api_mem_type_size(g, ty, "store") : dst_size; src_ptr_rvalue = !api_is_lvalue_sv(&rv) && (rv_bits & API_PRED_PTR); src_size = src_ptr_rvalue ? access_size : api_mem_type_size(g, api_sv_type(&rv), "store"); @@ -458,6 +560,11 @@ void kit_cg_store(KitCg* g, KitCgMemAccess access) { agg.align = access.align ? access.align : abi_cg_alignof(g->c->abi, ty); T->copy_bytes(T, dst_addr, src_addr, agg); api_release(g, &base); + if (keep_result) { + api_push(g, rv); + api_const_copy_top_from(g, rv_const); + return; + } api_release(g, &rv); return; } @@ -480,20 +587,20 @@ void kit_cg_store(KitCg* g, KitCgMemAccess access) { } /* Does this store land a scalar value straight into a local's own storage - * (a plain `x = <expr>` / `int x = <expr>`, not bit-field/aggregate/indirect)? - * Single-decode: `ty`'s aggregate bit was already taken (ty_is_agg); decode - * base's id once for both its aggregate bit and its unalias terminal. */ - int scalar_local_place = !is_bitfield && - base.source_local != KIT_CG_LOCAL_NONE && - base.op.kind == OPK_LOCAL && - !api_sv_local_storage_is_aggregate(g, &base) && - !ty_is_agg; + * (a plain `x = <expr>` / `int x = <expr>`, not + * bit-field/aggregate/indirect)? Single-decode: `ty`'s aggregate bit was + * already taken (ty_is_agg); decode base's id once for both its aggregate bit + * and its unalias terminal. */ + int scalar_local_place = + !is_bitfield && base.source_local != KIT_CG_LOCAL_NONE && + base.op.kind == OPK_LOCAL && + !api_sv_local_storage_is_aggregate(g, &base) && !ty_is_agg; if (scalar_local_place) { KitCgTypeId base_ty = api_sv_type(&base); u8 base_bits = api_type_pred_bits(g->c, base_ty); - scalar_local_place = !(base_bits & API_PRED_AGGREGATE) && - api_unalias_type(g->c, base_ty) == - api_unalias_type(g->c, ty); + scalar_local_place = + !(base_bits & API_PRED_AGGREGATE) && + api_unalias_type(g->c, base_ty) == api_unalias_type(g->c, ty); } /* A still-delayed arith/cmp value going into a scalar local: emit the op @@ -503,13 +610,25 @@ void kit_cg_store(KitCg* g, KitCgMemAccess access) { * api_materialize_*_to with dst=base.op emits the same op the old temp got, * just landing in base's storage; binops read both sources before writing the * dst, so a self-referential RHS like `b = b + c` stays correct.) */ - if (scalar_local_place && (rv.kind == SV_ARITH || rv.kind == SV_CMP)) { + if (scalar_local_place && + (api_sv_kind(&rv) == SV_ARITH || api_sv_kind(&rv) == SV_CMP)) { Operand dst = base.op; - if (rv.kind == SV_ARITH) + if (api_sv_kind(&rv) == SV_ARITH) api_materialize_arith_to(g, &rv, dst); else api_materialize_cmp_to(g, &rv, dst); api_local_const_clear(api_local_from_handle(g, base.source_local)); + if (keep_result) { + ApiSValue out = api_make_sv(dst, ty); + out.lang_type = rv.lang_type; + out.lang_flags = rv.lang_flags; + api_sv_set_res(&out, RES_FIXED_LOCAL); + api_release(g, &base); + api_release(g, &rv); + api_push(g, out); + api_const_copy_top_from(g, rv_const); + return; + } api_release(g, &base); api_release(g, &rv); return; @@ -537,7 +656,7 @@ void kit_cg_store(KitCg* g, KitCgMemAccess access) { /* `x = <value in a dead transient>`: flag the source dead so the -O0 * backend renames its register to x instead of emitting a routing mov * (rv, its last reference, was popped above). */ - if (api_coalesce_on(g) && src.kind == OPK_LOCAL && + if (!keep_result && api_coalesce_on(g) && src.kind == OPK_LOCAL && api_temp_dead(g, src.v.local)) src.flags |= OPK_FLAG_KILL; T->copy(T, dst, src); @@ -545,6 +664,10 @@ void kit_cg_store(KitCg* g, KitCgMemAccess access) { if (base.source_local != KIT_CG_LOCAL_NONE) api_local_const_clear(api_local_from_handle(g, base.source_local)); } + if (keep_result) { + src.flags &= (uint8_t)~OPK_FLAG_KILL; + api_push_kept_store_value(g, &rv, src, ty, rv_const); + } api_release(g, &base); api_release(g, &rv); return; @@ -572,12 +695,12 @@ void kit_cg_store(KitCg* g, KitCgMemAccess access) { api_local_const_memory_boundary(g); } - /* Flag a dead-transient store value so the -O0 backend drops it after the store - * instead of spilling it at the next barrier (eager dead-operand drop). mem_op - * is the memory place (OPK_INDIRECT/GLOBAL, never a scalar local — that is the - * scalar_local_place fast path above), so the dst self-guard is a no-op and the - * store has already read the address before the drop. */ - src = api_op_kill_if_dead(g, src, mem_op); + /* Flag a dead-transient store value so the -O0 backend drops it after the + * store instead of spilling it at the next barrier (eager dead-operand drop). + * mem_op is the memory place (OPK_INDIRECT/GLOBAL, never a scalar local — + * that is the scalar_local_place fast path above), so the dst self-guard is a + * no-op and the store has already read the address before the drop. */ + if (!keep_result) src = api_op_kill_if_dead(g, src, mem_op); if (is_bitfield) { /* A bit-field store rides the generic `store` with a bit-field MemAccess; * the CgTarget impl does the read-modify-write insert. */ @@ -587,10 +710,22 @@ void kit_cg_store(KitCg* g, KitCgMemAccess access) { T->store(T, mem_op, src, api_mem_from_access(g, &mem_op, access)); } + if (keep_result) { + src.flags &= (uint8_t)~OPK_FLAG_KILL; + api_push_kept_store_value(g, &rv, src, ty, rv_const); + } api_release(g, &base); api_release(g, &rv); } +void kit_cg_store(KitCg* g, KitCgMemAccess access) { + api_cg_store_impl(g, access, 0); +} + +void kit_cg_store_keep(KitCg* g, KitCgMemAccess access) { + api_cg_store_impl(g, access, 1); +} + /* ============================================================ * Stack manipulation * ============================================================ */ @@ -598,15 +733,22 @@ void kit_cg_store(KitCg* g, KitCgMemAccess access) { void kit_cg_dup(KitCg* g) { ApiSValue v, dup; ApiSValue* top; + ApiConstValue cv; KitCgTypeId ty; CGLocal r; Operand dst; if (!g || g->sp == 0) return; top = &g->stack[g->sp - 1]; + cv = api_const_at(g, 0); + if (api_unevaluated(g)) { + api_push(g, *top); + api_const_copy_top_from(g, cv); + return; + } api_ensure_local(g, top); v = *top; - if (v.res != RES_LOCAL) { - if (v.res == RES_FIXED_LOCAL && !api_is_lvalue_sv(&v) && + if (api_sv_res(&v) != RES_LOCAL) { + if (api_sv_res(&v) == RES_FIXED_LOCAL && !api_is_lvalue_sv(&v) && v.op.kind == OPK_LOCAL) { ty = api_owned_local_type(g, &v); r = api_alloc_temp_local(g, ty); @@ -615,41 +757,46 @@ void kit_cg_dup(KitCg* g) { api_op_local((CGLocal)api_local_of_sv(&v), ty)); dup = v; api_set_owned_local(&dup, r); - dup.res = RES_LOCAL; - dup.pinned = 0; + api_sv_set_res(&dup, RES_LOCAL); + api_sv_set_pinned(&dup, 0); dup.source_local = KIT_CG_LOCAL_NONE; g->stack[g->sp - 1] = dup; api_push(g, v); + api_const_copy_top_from(g, cv); return; } api_push(g, v); + api_const_copy_top_from(g, cv); return; } if (api_coalesce_on(g)) { /* Lazy dup: push a second reference to the owned temp instead of copying it * into a fresh one. Both entries only ever read the write-once value temp, - * so they can share it; the value-stack refcount (now 2) keeps finer-reclaim - * and copy-adoption from recycling the temp while it is still shared, and it - * is freed once both references are gone (Fix A at the statement boundary, or - * finer reclaim when the count returns to 0). Eliminates the common - * assignment-result dup whose value is stored once and then discarded. */ + * so they can share it; the value-stack refcount (now 2) keeps + * finer-reclaim and copy-adoption from recycling the temp while it is still + * shared, and it is freed once both references are gone (Fix A at the + * statement boundary, or finer reclaim when the count returns to 0). + * Eliminates the common assignment-result dup whose value is stored once + * and then discarded. */ dup = v; - dup.pinned = 0; + api_sv_set_pinned(&dup, 0); api_push(g, dup); + api_const_copy_top_from(g, cv); return; } - top->pinned = 1; + api_sv_set_pinned(top, 1); ty = api_owned_local_type(g, &v); r = api_alloc_temp_local(g, ty); dst = api_op_local(r, ty); g->target->copy(g->target, dst, api_op_local((CGLocal)api_local_of_sv(&v), ty)); - g->stack[g->sp - 1].pinned = 0; + api_sv_set_pinned(&g->stack[g->sp - 1], 0); dup = v; api_set_owned_local(&dup, r); - dup.res = RES_LOCAL; - dup.pinned = 0; + api_sv_set_res(&dup, RES_LOCAL); + api_sv_set_pinned(&dup, 0); api_push(g, dup); + api_const_copy_top_from(g, cv); } /* Duplicate the top two value-stack entries. The lower of the two is the deeper @@ -685,10 +832,14 @@ void kit_cg_dup2(KitCg* g) { void kit_cg_swap(KitCg* g) { ApiSValue tmp; + ApiConstValue ctmp; if (!g || g->sp < 2) return; tmp = g->stack[g->sp - 1]; g->stack[g->sp - 1] = g->stack[g->sp - 2]; g->stack[g->sp - 2] = tmp; + ctmp = g->const_stack[g->sp - 1]; + g->const_stack[g->sp - 1] = g->const_stack[g->sp - 2]; + g->const_stack[g->sp - 2] = ctmp; } void kit_cg_drop(KitCg* g) { @@ -698,30 +849,57 @@ void kit_cg_drop(KitCg* g) { api_release(g, &v); } -uint32_t kit_cg_stack_depth(KitCg* g) { return g ? g->sp : 0u; } +KitCgSlotInfo kit_cg_slot_info(KitCg* g, uint32_t depth_from_top) { + KitCgSlotInfo info; + memset(&info, 0, sizeof info); + if (!g || depth_from_top >= g->sp) return info; + { + ApiSValue* sv = &g->stack[g->sp - 1u - depth_from_top]; + info.cg_type = api_sv_type(sv); + info.lang_type = sv->lang_type; + info.lang_flags = sv->lang_flags; + } + return info; +} -int kit_cg_top_const_int(KitCg* g, int64_t* out_value) { - ApiSValue* v; - KitCgTypeId ty; - u32 width; - if (!g || !out_value || !g->sp) return 0; - v = &g->stack[g->sp - 1u]; - if (v->kind != SV_OPERAND || v->op.kind != OPK_IMM) return 0; - ty = api_sv_type(v); - if (!api_foldable_int_like_type(g->c, ty, &width)) return 0; - *out_value = api_fold_result(g->c, ty, (u64)v->op.v.imm, width); - return 1; +void kit_cg_retag_at(KitCg* g, uint32_t depth_from_top, const void* lang_type, + uint16_t lang_flags) { + ApiSValue* sv; + if (!g || depth_from_top >= g->sp) return; + sv = &g->stack[g->sp - 1u - depth_from_top]; + sv->lang_type = lang_type; + sv->lang_flags = lang_flags; +} + +void kit_cg_retag_top(KitCg* g, const void* lang_type, uint16_t lang_flags) { + kit_cg_retag_at(g, 0, lang_type, lang_flags); } +void kit_cg_set_top_flags(KitCg* g, uint16_t set, uint16_t clear) { + ApiSValue* sv; + if (!g || !g->sp) return; + sv = &g->stack[g->sp - 1u]; + sv->lang_flags = (uint16_t)((sv->lang_flags | set) & (uint16_t)~clear); +} + +uint32_t kit_cg_stack_depth(KitCg* g) { return g ? g->sp : 0u; } + void kit_cg_rot3(KitCg* g) { ApiSValue a, b, c; + ApiConstValue ca, cb, cc; if (!g || g->sp < 3) return; a = g->stack[g->sp - 3]; b = g->stack[g->sp - 2]; c = g->stack[g->sp - 1]; + ca = g->const_stack[g->sp - 3]; + cb = g->const_stack[g->sp - 2]; + cc = g->const_stack[g->sp - 1]; g->stack[g->sp - 3] = b; g->stack[g->sp - 2] = c; g->stack[g->sp - 1] = a; + g->const_stack[g->sp - 3] = cb; + g->const_stack[g->sp - 2] = cc; + g->const_stack[g->sp - 1] = ca; } /* ============================================================ diff --git a/src/cg/session.c b/src/cg/session.c @@ -17,6 +17,10 @@ static void cg_free_obj_state(KitCg* g) { h->free(h, g->stack, sizeof(ApiSValue) * g->cap); g->stack = NULL; } + if (g->const_stack) { + h->free(h, g->const_stack, sizeof(ApiConstValue) * g->cap); + g->const_stack = NULL; + } if (g->locals) { h->free(h, g->locals, sizeof(*g->locals) * g->locals_cap); g->locals = NULL; @@ -54,6 +58,7 @@ static void cg_free_obj_state(KitCg* g) { g->delayed_free = NULL; g->sp = 0; g->cap = 0; + g->unevaluated_depth = 0; g->nlocals = 0; g->const_head = KIT_CG_LOCAL_NONE; g->locals_cap = 0; @@ -496,8 +501,9 @@ void kit_cg_func_end(KitCg* g) { void kit_cg_reclaim_temps(KitCg* g) { if (!g) return; /* Only safe when the value stack is empty: every transient temp is then - * provably dead. The guard also makes this a no-op inside any future construct - * (e.g. a statement-expression) that leaves a value live across a boundary. */ + * provably dead. The guard also makes this a no-op inside any future + * construct (e.g. a statement-expression) that leaves a value live across a + * boundary. */ if (g->sp != 0) return; if (g->target && g->target->reclaim_temps) g->target->reclaim_temps(g->target); diff --git a/src/cg/type.c b/src/cg/type.c @@ -23,7 +23,7 @@ typedef struct CgApiType { const KitCgField* fields; const KitCgEnumValue* values; const KitCgFuncParam* params; - KitCgFuncResult result; /* result.type == KIT_CG_TYPE_NONE == void */ + KitCgFuncResult result; /* void builtin means no value */ KitCgCallConv call_conv; u8 kind; u8 abi_variadic; @@ -58,34 +58,34 @@ typedef struct CgApiType { * cg/type.h so multi-predicate-on-same-id call sites can decode an id once. */ /* High bit of CgApiType.cached_class: set once the class has been computed, so - * a genuine {WK_NARROW, non-aggregate} (packed 0) is told apart from unfilled. */ + * a genuine {WK_NARROW, non-aggregate} (packed 0) is told apart from unfilled. + */ #define API_TYPE_CLASS_CACHED 0x80u -SEGVEC_DEFINE(CgApiTypes, CgApiType, CG_API_TYPE_SEG_SHIFT); +enum { CG_API_TYPES_SEG_SHIFT = 6 }; + +SEGVEC_DEFINE(CgApiTypes, CgApiType, CG_API_TYPES_SEG_SHIFT); /* Structural dedup of derived ptr/array types, O(1) instead of a linear scan of * the whole type table on every derivation (which made each derived-type use * O(#types) — the dominant frontend hotspot on type-heavy input like sqlite). - * The key packs the structural identity into a u64: ptr = (pointee<<32)|addr_sp, - * array = (elem<<32)|count. Both component high halves (pointee/elem) are real - * type ids, never KIT_CG_TYPE_NONE (0), so the packed key is always nonzero and - * 0 is a safe empty-slot sentinel. Value is the interned KitCgTypeId. */ + * Pointer keys fit in a u64: ptr = (pointee<<32)|addr_sp. Arrays use the + * structural hashset below so their full uint64_t count participates in + * identity. */ static inline u64 cg_ptr_key(KitCgTypeId pointee, u32 address_space) { return ((u64)pointee << 32) | (u64)address_space; } -static inline u64 cg_array_key(KitCgTypeId elem, u64 count) { - return ((u64)elem << 32) | (count & 0xffffffffu); -} KIT_HASHMAP_DEFINE(CgPtrMap, u64, KitCgTypeId, hash_u64); -KIT_HASHMAP_DEFINE(CgArrayMap, u64, KitCgTypeId, hash_u64); -/* Function types have a structural key (result + params + variadic + conv) that - * a scalar hashmap can't express, so dedup them through a structural hashset of - * the stored CgApiType* (SEGVEC entries have stable addresses). The callbacks - * are defined further down (after the shared result/param comparators); forward - * declarations let the set be defined here, before CgApiState embeds it. */ +/* Array and function types have structural keys that a scalar hashmap cannot + * express (array counts are full uint64_t; function keys include attrs), so + * dedup them through structural hashsets of the stored CgApiType* (SEGVEC + * entries have stable addresses). The callbacks are defined further down. */ +static u32 cg_array_hash(CgApiType* const e); +static int cg_array_eq(CgApiType* const a, CgApiType* const b); static u32 cg_func_hash(CgApiType* const e); static int cg_func_eq(CgApiType* const a, CgApiType* const b); +KIT_HASHSET_DEFINE(CgArraySet, CgApiType*, cg_array_hash, cg_array_eq); KIT_HASHSET_DEFINE(CgFuncSet, CgApiType*, cg_func_hash, cg_func_eq); typedef struct CgApiState { @@ -93,7 +93,7 @@ typedef struct CgApiState { CgApiTypes types; /* Structural dedup indexes for derived ptr/array/func types (see above). */ CgPtrMap ptr_index; - CgArrayMap array_index; + CgArraySet array_index; CgFuncSet func_index; CgType builtins[KIT_CG_BUILTIN_COUNT]; /* Packed API_TYPE_CLASS_* per builtin, precomputed at init so the hottest @@ -108,32 +108,22 @@ typedef struct CgApiState { u8 pad[3]; } CgApiState; -static KitCgTypeId type_id_from_tuple(u32 seg, u32 index) { - return (KitCgTypeId)((seg << CG_API_TYPE_SEG_SHIFT) | - (index & CG_API_TYPE_SEG_MASK)); -} - KitCgTypeId builtin_id(KitCgBuiltinType t) { - return type_id_from_tuple(CG_API_TYPE_BUILTIN_SEG, (u32)t); + if ((u32)t >= KIT_CG_BUILTIN_COUNT) return KIT_CG_TYPE_NONE; + return (KitCgTypeId)((u32)t + 1u); } static int decode_user_id(KitCgTypeId id, u32* index_out) { - u32 seg = id >> CG_API_TYPE_SEG_SHIFT; - u32 off = id & CG_API_TYPE_SEG_MASK; - if (seg < CG_API_TYPE_USER_SEG_BIAS) return 0; - *index_out = - ((seg - CG_API_TYPE_USER_SEG_BIAS) << CG_API_TYPE_SEG_SHIFT) | off; + if (!index_out) return 0; + if (id <= KIT_CG_BUILTIN_COUNT) return 0; + *index_out = id - (KitCgTypeId)KIT_CG_BUILTIN_COUNT - 1u; return 1; } static KitCgTypeId user_id_from_index(u32 index) { - u32 raw_seg = index >> CG_API_TYPE_SEG_SHIFT; - u32 off = index & CG_API_TYPE_SEG_MASK; - u32 seg_limit = UINT32_MAX >> CG_API_TYPE_SEG_SHIFT; - if (raw_seg > seg_limit - CG_API_TYPE_USER_SEG_BIAS) { + if (index > UINT32_MAX - (u32)KIT_CG_BUILTIN_COUNT - 1u) return KIT_CG_TYPE_NONE; - } - return type_id_from_tuple(raw_seg + CG_API_TYPE_USER_SEG_BIAS, off); + return (KitCgTypeId)((u32)KIT_CG_BUILTIN_COUNT + 1u + index); } static u64 cg_align_to(u64 n, u32 align) { @@ -307,7 +297,8 @@ static void cg_api_init_builtins(Compiler* c, CgApiState* s) { * both live (builtin_cg_type_init already consults c->abi for the va_list * builtin, so it is ready). */ for (u32 i = 0; i < KIT_CG_BUILTIN_COUNT; ++i) { - s->builtin_class[i] = api_compute_type_class(c, builtin_id((KitCgBuiltinType)i)); + s->builtin_class[i] = + api_compute_type_class(c, builtin_id((KitCgBuiltinType)i)); /* Builtins are never aliases, so the kind-derived predicate bitset is the * exact, final value — classify the node once here, then every predicate * query is one indexed load (see api_type_pred). */ @@ -319,13 +310,11 @@ static void cg_api_init_builtins(Compiler* c, CgApiState* s) { u8 api_type_class(Compiler* c, KitCgTypeId ty) { CgApiState* s; CgApiType* e; - u32 seg; if (ty == KIT_CG_TYPE_NONE) return 0; s = c->cg_api ? (CgApiState*)c->cg_api : cg_api_get(c); if (!s) return api_compute_type_class(c, ty); - seg = ty >> CG_API_TYPE_SEG_SHIFT; - if (seg == CG_API_TYPE_BUILTIN_SEG) { - u32 off = ty & CG_API_TYPE_SEG_MASK; + if (ty <= KIT_CG_BUILTIN_COUNT) { + u32 off = ty - 1u; if (off < KIT_CG_BUILTIN_COUNT) return s->builtin_class[off]; return api_compute_type_class(c, ty); } @@ -340,7 +329,7 @@ int api_type_layout_get(Compiler* c, KitCgTypeId ty, u32* size, u32* align, u8* scalar_kind, u8* signed_, u8* atomic) { CgApiType* e; if (ty == KIT_CG_TYPE_NONE) return 0; - if ((ty >> CG_API_TYPE_SEG_SHIFT) == CG_API_TYPE_BUILTIN_SEG) return 0; + if (ty <= KIT_CG_BUILTIN_COUNT) return 0; e = api_type_from_id(c, ty); if (!e || !e->abi_cached) return 0; *size = e->abi_size; @@ -355,7 +344,8 @@ void api_type_layout_put(Compiler* c, KitCgTypeId ty, u32 size, u32 align, u8 scalar_kind, u8 signed_, u8 atomic) { CgApiType* e; if (ty == KIT_CG_TYPE_NONE) return; - if ((ty >> CG_API_TYPE_SEG_SHIFT) == CG_API_TYPE_BUILTIN_SEG) return; + if (ty <= KIT_CG_BUILTIN_COUNT) return; + if (!align && scalar_kind == ABI_SC_VOID) return; e = api_type_from_id(c, ty); if (!e) return; e->abi_size = size; @@ -378,7 +368,7 @@ static CgApiState* cg_api_get(Compiler* c) { s->heap = h; CgApiTypes_init(&s->types, h); CgPtrMap_init(&s->ptr_index, h); - CgArrayMap_init(&s->array_index, h); + CgArraySet_init(&s->array_index, h); CgFuncSet_init(&s->func_index, h); c->cg_api = s; c->cg_api_free = cg_api_fini; @@ -387,15 +377,12 @@ static CgApiState* cg_api_get(Compiler* c) { } const CgType* cg_type_get(Compiler* c, KitCgTypeId id) { - u32 seg; u32 off; CgApiState* s; CgApiType* e; if (!c || id == KIT_CG_TYPE_NONE) return NULL; - seg = id >> CG_API_TYPE_SEG_SHIFT; - off = id & CG_API_TYPE_SEG_MASK; - if (seg == CG_API_TYPE_BUILTIN_SEG) { - if (off >= KIT_CG_BUILTIN_COUNT) return NULL; + if (id <= KIT_CG_BUILTIN_COUNT) { + off = id - 1u; /* Fast path: the per-compiler state (with its builtin table) is created * on first use and then stays put, so inline the already-initialized case * -- the hot per-op type query becomes a load + index instead of a call @@ -409,13 +396,11 @@ const CgType* cg_type_get(Compiler* c, KitCgTypeId id) { } uint64_t cg_type_size(Compiler* c, KitCgTypeId id) { - const CgType* ty = cg_type_get(c, id); - return ty ? ty->size : 0; + return c && c->abi ? abi_cg_sizeof(c->abi, id) : 0; } uint32_t cg_type_align(Compiler* c, KitCgTypeId id) { - const CgType* ty = cg_type_get(c, id); - return ty ? ty->align : 0; + return c && c->abi ? abi_cg_alignof(c->abi, id) : 0; } /* Predicate bitset of a single (already-unaliased) CgType node. The kind->bit @@ -442,15 +427,16 @@ static u8 api_pred_bits_for_kind(const CgType* ty) { /* Full API_PRED_* bitset for `id` via exactly one decode: builtin fast path (no * aliasing, no entry) or a descriptor load off the user-type entry, filled - * lazily on first miss. Multi-predicate-on-the-same-id call sites read this once - * and then test the masks locally instead of decoding `id` per predicate. */ + * lazily on first miss. Multi-predicate-on-the-same-id call sites read this + * once and then test the masks locally instead of decoding `id` per predicate. + */ u8 api_type_pred_bits(Compiler* c, KitCgTypeId id) { CgApiType* e; if (id == KIT_CG_TYPE_NONE) return 0; - if ((id >> CG_API_TYPE_SEG_SHIFT) == CG_API_TYPE_BUILTIN_SEG) { + if (id <= KIT_CG_BUILTIN_COUNT) { /* Builtins are never aliases; their predicate bitset is precomputed at * init, so this is one indexed load (no cg_type_get + re-classify). */ - u32 off = id & CG_API_TYPE_SEG_MASK; + u32 off = id - 1u; CgApiState* s = c->cg_api ? (CgApiState*)c->cg_api : cg_api_get(c); if (s && off < KIT_CG_BUILTIN_COUNT) return s->builtin_pred[off]; return api_pred_bits_for_kind(cg_type_get(c, id)); @@ -467,8 +453,8 @@ u8 api_type_pred_bits(Compiler* c, KitCgTypeId id) { return e->pred_bits; } -/* One predicate query for `id`: returns (bits & mask) != 0 off the single-decode - * bitset. Mirrors api_type_class's CACHED-bit memo. */ +/* One predicate query for `id`: returns (bits & mask) != 0 off the + * single-decode bitset. Mirrors api_type_class's CACHED-bit memo. */ static int api_type_pred(Compiler* c, KitCgTypeId id, u8 mask) { return (api_type_pred_bits(c, id) & mask) != 0; } @@ -513,13 +499,11 @@ KitCgTypeId cg_type_func_ret_id(Compiler* c, KitCgTypeId id) { if (ty && ty->kind == KIT_CG_TYPE_ALIAS) return cg_type_func_ret_id(c, ty->alias.base); if (!ty || ty->kind != KIT_CG_TYPE_FUNC) return KIT_CG_TYPE_NONE; - return ty->func.result.type ? ty->func.result.type - : builtin_id(KIT_CG_BUILTIN_VOID); + return ty->func.result.type; } KitCgTypeId cg_func_ret_type(const CgType* fnty) { - return fnty->func.result.type ? fnty->func.result.type - : builtin_id(KIT_CG_BUILTIN_VOID); + return fnty->func.result.type; } KitCgTypeId cg_type_func_result_id(Compiler* c, KitCgTypeId id) { @@ -565,11 +549,16 @@ static KitCgTypeId find_ptr_type_id(Compiler* c, KitCgTypeId pointee, static KitCgTypeId find_array_type_id(Compiler* c, KitCgTypeId elem, u64 count) { CgApiState* s; - const KitCgTypeId* hit; + CgApiType probe; + CgApiType* hit; if (!c || !c->cg_api) return KIT_CG_TYPE_NONE; s = (CgApiState*)c->cg_api; - hit = CgArrayMap_get(&s->array_index, cg_array_key(elem, count)); - return hit ? *hit : KIT_CG_TYPE_NONE; + memset(&probe, 0, sizeof probe); + probe.kind = CG_API_TYPE_ARRAY; + probe.base = elem; + probe.array_count = count; + hit = CgArraySet_find(&s->array_index, &probe); + return hit ? hit->self_id : KIT_CG_TYPE_NONE; } static int cg_params_eq(const KitCgFuncParam* a, const KitCgFuncParam* b, @@ -587,6 +576,16 @@ static int cg_result_eq(const KitCgFuncResult* a, const KitCgFuncResult* b) { memcmp(&a->attrs, &b->attrs, sizeof(a->attrs)) == 0; } +static u32 cg_array_hash(CgApiType* const e) { + u32 h = hash_u32((u32)e->base); + h ^= hash_u64(e->array_count) + 0x9e3779b9u + (h << 6) + (h >> 2); + return h; +} + +static int cg_array_eq(CgApiType* const a, CgApiType* const b) { + return a->base == b->base && a->array_count == b->array_count; +} + /* Structural hash/eq over a function type's identity — the same fields the old * linear scan compared. Only CG_API_TYPE_FUNC entries enter the set, so neither * callback re-checks kind. The result/param attrs are compared (not hashed): @@ -633,7 +632,6 @@ static CgApiType* api_type_from_id(Compiler* c, KitCgTypeId id) { CgApiState* s; CgApiType* e; if (!c || id == KIT_CG_TYPE_NONE) return NULL; - if ((id >> CG_API_TYPE_SEG_SHIFT) == CG_API_TYPE_BUILTIN_SEG) return NULL; if (!decode_user_id(id, &index)) return NULL; s = (CgApiState*)c->cg_api; if (!s) return NULL; @@ -650,8 +648,7 @@ KitCgTypeId api_unalias_type(Compiler* c, KitCgTypeId id) { const CgType* ty; KitCgTypeId start = id; /* Builtins are never aliases — no entry, no chain to walk or cache. */ - if (id == KIT_CG_TYPE_NONE || - (id >> CG_API_TYPE_SEG_SHIFT) == CG_API_TYPE_BUILTIN_SEG) { + if (id == KIT_CG_TYPE_NONE || id <= KIT_CG_BUILTIN_COUNT) { ty = cg_type_get(c, id); return ty ? id : KIT_CG_TYPE_NONE; } @@ -703,6 +700,80 @@ static CgTypeField* copy_cg_fields(Compiler* c, const KitCgField* src, u32 n) { return dst; } +static int cg_type_record_complete(const CgType* ty) { + return ty && ty->kind == KIT_CG_TYPE_RECORD && + (ty->record.flags & CG_TYPE_RECORD_COMPLETE) != 0; +} + +static int cg_type_complete_id(Compiler* c, KitCgTypeId id) { + const CgType* ty = cg_type_get(c, id); + if (!ty) return 0; + switch (ty->kind) { + case KIT_CG_TYPE_ALIAS: + return cg_type_complete_id(c, ty->alias.base); + case KIT_CG_TYPE_ARRAY: + return cg_type_complete_id(c, ty->array.elem); + case KIT_CG_TYPE_RECORD: + return cg_type_record_complete(ty); + default: + return 1; + } +} + +static int cg_type_sized_id(Compiler* c, KitCgTypeId id) { + ABITypeInfo ti; + const CgType* ty; + id = api_unalias_type(c, id); + ty = cg_type_get(c, id); + if (!ty) return 0; + switch (ty->kind) { + case KIT_CG_TYPE_VOID: + return 0; + case KIT_CG_TYPE_FUNC: + return 0; + case KIT_CG_TYPE_RECORD: + if (!cg_type_record_complete(ty)) return 0; + break; + case KIT_CG_TYPE_ARRAY: + if (!cg_type_sized_id(c, ty->array.elem)) return 0; + break; + default: + break; + } + ti = abi_cg_type_info(c->abi, id); + return ti.align != 0; +} + +static int cg_type_valid_by_value(Compiler* c, KitCgTypeId id) { + const CgType* ty = cg_type_get(c, api_unalias_type(c, id)); + if (!ty) return 0; + if (ty->kind == KIT_CG_TYPE_VOID || ty->kind == KIT_CG_TYPE_FUNC) return 0; + return cg_type_sized_id(c, id); +} + +static int cg_type_contains_by_value(Compiler* c, KitCgTypeId id, + KitCgTypeId needle) { + const CgType* ty; + id = api_unalias_type(c, id); + if (!id) return 0; + if (id == needle) return 1; + ty = cg_type_get(c, id); + if (!ty) return 0; + switch (ty->kind) { + case KIT_CG_TYPE_ARRAY: + return cg_type_contains_by_value(c, ty->array.elem, needle); + case KIT_CG_TYPE_RECORD: + if (!cg_type_record_complete(ty)) return 0; + for (u32 i = 0; i < ty->record.nfields; ++i) { + if (cg_type_contains_by_value(c, ty->record.fields[i].type, needle)) + return 1; + } + return 0; + default: + return 0; + } +} + static int cg_type_layout_record(Compiler* c, CgType* cg) { u32 max_align = 1; u64 size = 0; @@ -812,12 +883,14 @@ static int cg_type_set_ptr(Compiler* c, CgApiType* e, KitCgTypeId pointee, static int cg_type_set_array(Compiler* c, CgApiType* e, KitCgTypeId elem, u64 count) { - const CgType* ety = cg_type_get(c, elem); - if (!ety) return 0; + ABITypeInfo ety; + if (!cg_type_sized_id(c, elem)) return 0; + ety = abi_cg_type_info(c->abi, elem); + if (ety.size && count > UINT32_MAX / ety.size) return 0; memset(&e->cg, 0, sizeof(e->cg)); e->cg.kind = KIT_CG_TYPE_ARRAY; - e->cg.size = ety->size * count; - e->cg.align = ety->align; + e->cg.size = (u64)ety.size * count; + e->cg.align = ety.align; e->cg.array.elem = elem; e->cg.array.count = count; return 1; @@ -826,11 +899,13 @@ static int cg_type_set_array(Compiler* c, CgApiType* e, KitCgTypeId elem, static int cg_type_set_alias(Compiler* c, CgApiType* e, KitSym name, KitCgTypeId base) { const CgType* bty = cg_type_get(c, base); + ABITypeInfo ti; if (!bty) return 0; + ti = abi_cg_type_info(c->abi, base); memset(&e->cg, 0, sizeof(e->cg)); e->cg.kind = KIT_CG_TYPE_ALIAS; - e->cg.size = bty->size; - e->cg.align = bty->align; + e->cg.size = ti.size; + e->cg.align = ti.align; e->cg.alias.name = name; e->cg.alias.base = base; return 1; @@ -856,16 +931,18 @@ static int cg_type_set_enum(Compiler* c, CgApiType* e, KitSym tag, KitCgTypeId base, KitCgEnumValue* values, u32 nvalues) { const CgType* bty; + ABITypeInfo ti; if (base == KIT_CG_TYPE_NONE) base = builtin_id(KIT_CG_BUILTIN_I32); bty = cg_type_get(c, base); if (!bty || !(bty->kind == KIT_CG_TYPE_INT || bty->kind == KIT_CG_TYPE_BOOL)) { return 0; } + ti = abi_cg_type_info(c->abi, base); memset(&e->cg, 0, sizeof(e->cg)); e->cg.kind = KIT_CG_TYPE_ENUM; - e->cg.size = bty->size; - e->cg.align = bty->align; + e->cg.size = ti.size; + e->cg.align = ti.align; e->cg.enum_.tag = tag; e->cg.enum_.base = base; e->cg.enum_.values = values; @@ -875,9 +952,12 @@ static int cg_type_set_enum(Compiler* c, CgApiType* e, KitSym tag, static int cg_type_set_func(Compiler* c, CgApiType* e, KitCgFuncSig sig, KitCgFuncParam* params) { - if (sig.result.type && !cg_type_get(c, sig.result.type)) return 0; + KitCgTypeId void_ty = builtin_id(KIT_CG_BUILTIN_VOID); + if (!sig.result.type || !cg_type_get(c, sig.result.type)) return 0; + if (sig.result.type != void_ty && !cg_type_valid_by_value(c, sig.result.type)) + return 0; for (u32 i = 0; i < sig.nparams; ++i) { - if (!cg_type_get(c, sig.params[i].type)) return 0; + if (!cg_type_valid_by_value(c, sig.params[i].type)) return 0; } memset(&e->cg, 0, sizeof(e->cg)); e->cg.kind = KIT_CG_TYPE_FUNC; @@ -891,17 +971,7 @@ static int cg_type_set_func(Compiler* c, CgApiType* e, KitCgFuncSig sig, return 1; } -KitCgBuiltinTypes kit_cg_builtin_types(KitCompiler* c) { - KitCgBuiltinTypes out; - (void)c; - memset(&out, 0, sizeof(out)); - for (u32 i = 0; i < KIT_CG_BUILTIN_COUNT; ++i) { - out.id[i] = builtin_id((KitCgBuiltinType)i); - } - return out; -} - -KitCgTypeId kit_cg_builtin_type_id(KitCompiler* c, KitCgBuiltinType which) { +KitCgTypeId kit_cg_type_builtin(KitCompiler* c, KitCgBuiltinType which) { (void)c; if ((u32)which >= KIT_CG_BUILTIN_COUNT) return KIT_CG_TYPE_NONE; return builtin_id(which); @@ -931,7 +1001,7 @@ KitCgTypeId kit_cg_type_array(KitCompiler* c, KitCgTypeId elem, uint64_t count) { KitCgTypeId id; CgApiType* e; - if (!cg_type_get(c, elem) || count > UINT32_MAX) return KIT_CG_TYPE_NONE; + if (!cg_type_sized_id(c, elem)) return KIT_CG_TYPE_NONE; id = find_array_type_id(c, elem, count); if (id != KIT_CG_TYPE_NONE) return id; e = type_alloc(c, &id); @@ -942,8 +1012,7 @@ KitCgTypeId kit_cg_type_array(KitCompiler* c, KitCgTypeId elem, if (!cg_type_set_array(c, e, elem, count)) { return KIT_CG_TYPE_NONE; } - CgArrayMap_set(&((CgApiState*)c->cg_api)->array_index, - cg_array_key(elem, count), id); + CgArraySet_add(&((CgApiState*)c->cg_api)->array_index, e); return id; } @@ -959,44 +1028,56 @@ KitCgTypeId kit_cg_type_alias(KitCompiler* c, KitSym name, KitCgTypeId base) { return cg_type_set_alias(c, e, name, base) ? id : KIT_CG_TYPE_NONE; } -KitCgTypeId kit_cg_type_record(KitCompiler* c, KitSym tag, - const KitCgField* fields, uint32_t nfields) { - KitCgRecordDesc desc; - memset(&desc, 0, sizeof desc); - desc.tag = tag; - desc.fields = fields; - desc.nfields = nfields; - return kit_cg_type_record_ex(c, &desc); +KitCgTypeId kit_cg_type_record_decl(KitCompiler* c, KitSym tag, int is_union) { + KitCgTypeId id; + CgApiType* e; + if (!c) return KIT_CG_TYPE_NONE; + e = type_alloc(c, &id); + if (!e) return KIT_CG_TYPE_NONE; + e->name = tag; + e->count = 0; + e->fields = NULL; + e->kind = CG_API_TYPE_RECORD; + memset(&e->cg, 0, sizeof(e->cg)); + e->cg.kind = KIT_CG_TYPE_RECORD; + e->cg.record.tag = tag; + e->cg.record.is_union = is_union != 0; + return id; } -KitCgTypeId kit_cg_type_record_ex(KitCompiler* c, const KitCgRecordDesc* desc) { - KitCgTypeId id; +KitStatus kit_cg_type_record_complete(KitCompiler* c, KitCgTypeId record, + const KitCgRecordDesc* desc) { CgApiType* e; KitCgField* copied = NULL; if (!c || !desc || (desc->nfields && !desc->fields) || desc->nfields > UINT16_MAX) { - return KIT_CG_TYPE_NONE; + return KIT_INVALID; } + e = api_type_from_id(c, record); + if (!e || e->cg.kind != KIT_CG_TYPE_RECORD) return KIT_INVALID; + if (e->cg.record.flags & CG_TYPE_RECORD_COMPLETE) return KIT_INVALID; + if (e->cg.record.is_union != (desc->is_union != 0)) return KIT_INVALID; + if (desc->nfields) { copied = arena_array(&c->global->arena, KitCgField, desc->nfields); - if (!copied) return KIT_CG_TYPE_NONE; + if (!copied) return KIT_NOMEM; } - for (u32 i = 0; i < desc->nfields; ++i) { - if (!cg_type_get(c, desc->fields[i].type)) return KIT_CG_TYPE_NONE; + KitCgTypeId fty = desc->fields[i].type; + if (!cg_type_valid_by_value(c, fty)) return KIT_INVALID; + if (cg_type_contains_by_value(c, fty, record)) return KIT_INVALID; copied[i] = desc->fields[i]; } - e = type_alloc(c, &id); - if (!e) return KIT_CG_TYPE_NONE; - e->name = desc->tag; + + e->name = desc->tag ? desc->tag : e->name; e->count = desc->nfields; e->fields = copied; - e->kind = CG_API_TYPE_RECORD; - if (!cg_type_set_record(c, e, desc->tag, desc->fields, desc->nfields, - desc->is_union, desc->align_override, 0)) { - return KIT_CG_TYPE_NONE; + if (!cg_type_set_record(c, e, e->name, desc->fields, desc->nfields, + desc->is_union, desc->align_override, + CG_TYPE_RECORD_COMPLETE)) { + return KIT_INVALID; } - return id; + return KIT_OK; } KitCgTypeId kit_cg_type_enum(KitCompiler* c, KitSym tag, KitCgTypeId base, @@ -1032,16 +1113,19 @@ KitCgTypeId kit_cg_type_func(KitCompiler* c, KitCgFuncSig sig) { if (!c || (sig.nparams && !sig.params) || sig.nparams > UINT16_MAX) { return KIT_CG_TYPE_NONE; } - if (sig.result.type && !cg_type_get(c, sig.result.type)) + if (!sig.result.type || !cg_type_get(c, sig.result.type)) return KIT_CG_TYPE_NONE; + if (sig.result.type != builtin_id(KIT_CG_BUILTIN_VOID) && + !cg_type_valid_by_value(c, sig.result.type)) + return KIT_CG_TYPE_NONE; + for (u32 i = 0; i < sig.nparams; ++i) { + if (!cg_type_valid_by_value(c, sig.params[i].type)) return KIT_CG_TYPE_NONE; + } id = find_func_type_id(c, sig); if (id != KIT_CG_TYPE_NONE) return id; if (sig.nparams) { copied = copy_cg_params(c, sig.params, sig.nparams); if (!copied) return KIT_CG_TYPE_NONE; - for (u32 i = 0; i < sig.nparams; ++i) { - if (!cg_type_get(c, sig.params[i].type)) return KIT_CG_TYPE_NONE; - } } e = type_alloc(c, &id); if (!e) return KIT_CG_TYPE_NONE; @@ -1066,11 +1150,174 @@ uint32_t kit_cg_type_align(KitCompiler* c, KitCgTypeId id) { return cg_type_align(c, id); } +KitCgTypeId kit_cg_type_resolve_alias(KitCompiler* c, KitCgTypeId id) { + return api_unalias_type(c, id); +} + +int kit_cg_type_is_void(KitCompiler* c, KitCgTypeId id) { + KitCgTypeId u = api_unalias_type(c, id); + const CgType* ty = cg_type_get(c, u); + return ty && ty->kind == KIT_CG_TYPE_VOID; +} + +int kit_cg_type_is_complete(KitCompiler* c, KitCgTypeId id) { + return cg_type_complete_id(c, id); +} + +int kit_cg_type_is_sized(KitCompiler* c, KitCgTypeId id) { + return cg_type_sized_id(c, id); +} + +int kit_cg_func_result_has_value(KitCompiler* c, KitCgFuncResult result) { + return result.type != KIT_CG_TYPE_NONE && + !kit_cg_type_is_void(c, result.type); +} + KitCgTypeKind kit_cg_type_kind(KitCompiler* c, KitCgTypeId id) { const CgType* ty = cg_type_get(c, id); return ty ? ty->kind : KIT_CG_TYPE_VOID; } +static KitCgStorageKind cg_storage_kind_from_abi(const CgType* ty, + ABITypeInfo ti) { + switch ((ABIScalarKind)ti.scalar_kind) { + case ABI_SC_VOID: + if (ty && + (ty->kind == KIT_CG_TYPE_ARRAY || ty->kind == KIT_CG_TYPE_RECORD)) + return KIT_CG_STORAGE_AGGREGATE; + return KIT_CG_STORAGE_VOID; + case ABI_SC_BOOL: + return KIT_CG_STORAGE_BOOL; + case ABI_SC_INT: + return KIT_CG_STORAGE_INT; + case ABI_SC_FLOAT: + return KIT_CG_STORAGE_FLOAT; + case ABI_SC_PTR: + return KIT_CG_STORAGE_PTR; + } + return KIT_CG_STORAGE_VOID; +} + +static uint16_t cg_type_scalar_width(Compiler* c, const CgType* ty) { + if (!ty) return 0; + switch (ty->kind) { + case KIT_CG_TYPE_BOOL: + case KIT_CG_TYPE_INT: + return (uint16_t)ty->integer.width; + case KIT_CG_TYPE_FLOAT: + return (uint16_t)ty->fp.width; + case KIT_CG_TYPE_ENUM: + return (uint16_t)kit_cg_type_int_width((KitCompiler*)c, ty->enum_.base); + default: + return 0; + } +} + +KitStatus kit_cg_type_info(KitCompiler* kc, KitCgTypeId id, + KitCgTypeInfo* out) { + Compiler* c = (Compiler*)kc; + const CgType* exact; + const CgType* storage; + KitCgTypeId storage_id; + ABITypeInfo ti; + if (!out) return KIT_INVALID; + memset(out, 0, sizeof(*out)); + exact = cg_type_get(c, id); + if (!exact) return KIT_INVALID; + storage_id = api_unalias_type(c, id); + storage = cg_type_get(c, storage_id); + if (!storage) return KIT_INVALID; + + out->id = id; + out->storage_id = storage_id; + out->kind = exact->kind; + if (id <= KIT_CG_BUILTIN_COUNT) out->flags |= KIT_CG_TYPEF_BUILTIN; + if (exact->kind == KIT_CG_TYPE_RECORD || exact->kind == KIT_CG_TYPE_ENUM || + exact->kind == KIT_CG_TYPE_ALIAS) + out->flags |= KIT_CG_TYPEF_NOMINAL; + if (cg_type_complete_id(c, id)) out->flags |= KIT_CG_TYPEF_COMPLETE; + if (cg_type_sized_id(c, id)) out->flags |= KIT_CG_TYPEF_SIZED; + + ti = abi_cg_type_info(c->abi, storage_id); + if (ti.align || storage->kind == KIT_CG_TYPE_VOID) { + out->layout.size = ti.size; + out->layout.align = ti.align; + out->layout.scalar_width = cg_type_scalar_width(c, storage); + out->layout.storage_kind = (uint8_t)cg_storage_kind_from_abi(storage, ti); + out->layout.valid = 1; + } + return KIT_OK; +} + +static int cg_type_same_storage_rec(Compiler* c, KitCgTypeId a, KitCgTypeId b) { + const CgType* ta; + const CgType* tb; + a = api_unalias_type(c, a); + b = api_unalias_type(c, b); + if (!a || !b) return 0; + if (a == b) return 1; + ta = cg_type_get(c, a); + tb = cg_type_get(c, b); + if (!ta || !tb) return 0; + if (ta->kind == KIT_CG_TYPE_ENUM) + return cg_type_same_storage_rec(c, ta->enum_.base, b); + if (tb->kind == KIT_CG_TYPE_ENUM) + return cg_type_same_storage_rec(c, a, tb->enum_.base); + if (ta->kind != tb->kind) return 0; + switch (ta->kind) { + case KIT_CG_TYPE_VOID: + case KIT_CG_TYPE_BOOL: + case KIT_CG_TYPE_INT: + case KIT_CG_TYPE_FLOAT: { + ABITypeInfo ai = abi_cg_type_info(c->abi, a); + ABITypeInfo bi = abi_cg_type_info(c->abi, b); + return ai.scalar_kind == bi.scalar_kind && ai.size == bi.size && + ai.align == bi.align; + } + case KIT_CG_TYPE_PTR: + return ta->ptr.address_space == tb->ptr.address_space && + cg_type_same_storage_rec(c, ta->ptr.pointee, tb->ptr.pointee); + case KIT_CG_TYPE_ARRAY: + return ta->array.count == tb->array.count && + cg_type_same_storage_rec(c, ta->array.elem, tb->array.elem); + case KIT_CG_TYPE_FUNC: + if (ta->func.nparams != tb->func.nparams || + ta->func.call_conv != tb->func.call_conv || + ta->func.abi_variadic != tb->func.abi_variadic || + memcmp(&ta->func.result.attrs, &tb->func.result.attrs, + sizeof(ta->func.result.attrs)) != 0) + return 0; + if (!cg_type_same_storage_rec(c, ta->func.result.type, + tb->func.result.type)) + return 0; + for (u32 i = 0; i < ta->func.nparams; ++i) { + if (memcmp(&ta->func.params[i].attrs, &tb->func.params[i].attrs, + sizeof(ta->func.params[i].attrs)) != 0) + return 0; + if (!cg_type_same_storage_rec(c, ta->func.params[i].type, + tb->func.params[i].type)) + return 0; + } + return 1; + case KIT_CG_TYPE_RECORD: + return 0; + case KIT_CG_TYPE_ALIAS: + return 0; + case KIT_CG_TYPE_ENUM: + return 0; + case KIT_CG_TYPE_VARARG_STATE: { + ABITypeInfo ai = abi_cg_type_info(c->abi, a); + ABITypeInfo bi = abi_cg_type_info(c->abi, b); + return ai.size == bi.size && ai.align == bi.align; + } + } + return 0; +} + +int kit_cg_type_same_storage(KitCompiler* c, KitCgTypeId a, KitCgTypeId b) { + return cg_type_same_storage_rec((Compiler*)c, a, b); +} + uint32_t kit_cg_type_int_width(KitCompiler* c, KitCgTypeId id) { const CgType* ty = cg_type_get(c, id); if (!ty) return 0; @@ -1078,7 +1325,7 @@ uint32_t kit_cg_type_int_width(KitCompiler* c, KitCgTypeId id) { return ty->integer.width; } if (ty->kind == KIT_CG_TYPE_ENUM) { - return (uint32_t)ty->size * 8u; + return kit_cg_type_int_width(c, ty->enum_.base); } if (ty->kind == KIT_CG_TYPE_ALIAS) { return kit_cg_type_int_width(c, ty->alias.base); @@ -1153,9 +1400,21 @@ int kit_cg_type_func_is_variadic(KitCompiler* c, KitCgTypeId id) { return ty && ty->kind == KIT_CG_TYPE_FUNC && ty->func.abi_variadic; } +KitSym kit_cg_type_record_tag(KitCompiler* c, KitCgTypeId id) { + const CgType* ty = cg_type_get(c, id); + return (ty && ty->kind == KIT_CG_TYPE_RECORD) ? ty->record.tag : 0; +} + +int kit_cg_type_record_is_union(KitCompiler* c, KitCgTypeId id) { + const CgType* ty = cg_type_get(c, id); + return ty && ty->kind == KIT_CG_TYPE_RECORD && ty->record.is_union; +} + uint32_t kit_cg_type_record_nfields(KitCompiler* c, KitCgTypeId id) { const CgType* ty = cg_type_get(c, id); - return (ty && ty->kind == KIT_CG_TYPE_RECORD) ? ty->record.nfields : 0; + return (ty && ty->kind == KIT_CG_TYPE_RECORD && cg_type_record_complete(ty)) + ? ty->record.nfields + : 0; } KitStatus kit_cg_type_record_field(KitCompiler* c, KitCgTypeId id, @@ -1163,7 +1422,8 @@ KitStatus kit_cg_type_record_field(KitCompiler* c, KitCgTypeId id, uint64_t* offset_out) { const CgType* ty = cg_type_get(c, id); const CgTypeField* f; - if (!ty || ty->kind != KIT_CG_TYPE_RECORD || index >= ty->record.nfields) { + if (!ty || ty->kind != KIT_CG_TYPE_RECORD || !cg_type_record_complete(ty) || + index >= ty->record.nfields) { return KIT_NOT_FOUND; } f = &ty->record.fields[index]; @@ -1182,6 +1442,42 @@ KitStatus kit_cg_type_record_field(KitCompiler* c, KitCgTypeId id, return KIT_OK; } +KitSym kit_cg_type_enum_tag(KitCompiler* c, KitCgTypeId id) { + const CgType* ty = cg_type_get(c, id); + return (ty && ty->kind == KIT_CG_TYPE_ENUM) ? ty->enum_.tag : 0; +} + +KitCgTypeId kit_cg_type_enum_base(KitCompiler* c, KitCgTypeId id) { + const CgType* ty = cg_type_get(c, id); + return (ty && ty->kind == KIT_CG_TYPE_ENUM) ? ty->enum_.base + : KIT_CG_TYPE_NONE; +} + +uint32_t kit_cg_type_enum_nvalues(KitCompiler* c, KitCgTypeId id) { + const CgType* ty = cg_type_get(c, id); + return (ty && ty->kind == KIT_CG_TYPE_ENUM) ? ty->enum_.nvalues : 0; +} + +KitStatus kit_cg_type_enum_value(KitCompiler* c, KitCgTypeId id, uint32_t index, + KitCgEnumValue* out) { + const CgType* ty = cg_type_get(c, id); + if (!ty || ty->kind != KIT_CG_TYPE_ENUM || index >= ty->enum_.nvalues) + return KIT_NOT_FOUND; + if (out) *out = ty->enum_.values[index]; + return KIT_OK; +} + +KitSym kit_cg_type_alias_name(KitCompiler* c, KitCgTypeId id) { + const CgType* ty = cg_type_get(c, id); + return (ty && ty->kind == KIT_CG_TYPE_ALIAS) ? ty->alias.name : 0; +} + +KitCgTypeId kit_cg_type_alias_base(KitCompiler* c, KitCgTypeId id) { + const CgType* ty = cg_type_get(c, id); + return (ty && ty->kind == KIT_CG_TYPE_ALIAS) ? ty->alias.base + : KIT_CG_TYPE_NONE; +} + int kit_cg_target_supports_call_conv(KitCompiler* c, KitCgCallConv cc) { const ArchImpl* a; if (!c) return 0; @@ -1270,7 +1566,7 @@ void cg_api_fini(Compiler* c) { s = (CgApiState*)c->cg_api; CgApiTypes_fini(&s->types); CgPtrMap_fini(&s->ptr_index); - CgArrayMap_fini(&s->array_index); + CgArraySet_fini(&s->array_index); CgFuncSet_fini(&s->func_index); s->heap->free(s->heap, s, sizeof(*s)); c->cg_api = NULL; diff --git a/src/cg/type.h b/src/cg/type.h @@ -39,7 +39,7 @@ typedef struct CgType { u64 count; } array; struct { - KitCgFuncResult result; /* result.type == KIT_CG_TYPE_NONE == void */ + KitCgFuncResult result; /* void builtin means no value */ KitCgFuncParam* params; u32 nparams; KitCgCallConv call_conv; @@ -66,6 +66,8 @@ typedef struct CgType { }; } CgType; +#define CG_TYPE_RECORD_COMPLETE 0x1u + const CgType* cg_type_get(Compiler*, KitCgTypeId); uint64_t cg_type_size(Compiler*, KitCgTypeId); uint32_t cg_type_align(Compiler*, KitCgTypeId); @@ -74,14 +76,6 @@ int cg_type_is_float(Compiler*, KitCgTypeId); int cg_type_is_ptr(Compiler*, KitCgTypeId); int cg_type_is_record(Compiler*, KitCgTypeId); -enum { - CG_API_TYPE_SEG_SHIFT = 6, - CG_API_TYPE_SEG_SIZE = 1u << CG_API_TYPE_SEG_SHIFT, - CG_API_TYPE_SEG_MASK = CG_API_TYPE_SEG_SIZE - 1u, - CG_API_TYPE_BUILTIN_SEG = 1u, - CG_API_TYPE_USER_SEG_BIAS = 2u, -}; - KitCgTypeId builtin_id(KitCgBuiltinType); KitCgTypeId resolve_type(Compiler*, KitCgTypeId); KitCgTypeId api_unalias_type(Compiler*, KitCgTypeId); @@ -92,8 +86,8 @@ int cg_type_is_aggregate(Compiler*, KitCgTypeId); * cg_type_is_* predicates are (bits & API_PRED_*) tests over this set; a site * that asks several predicates of the SAME id reads the bitset once via * api_type_pred_bits and tests the masks locally (one decode, not N). The - * membership rules MUST match cg_type_is_* exactly: INT covers INT|BOOL|ENUM and - * AGGREGATE == RECORD. */ + * membership rules MUST match cg_type_is_* exactly: INT covers INT|BOOL|ENUM + * and AGGREGATE == RECORD. */ #define API_PRED_INT 0x01u #define API_PRED_FLOAT 0x02u #define API_PRED_PTR 0x04u @@ -118,10 +112,10 @@ int api_is_wide8_scalar_type(Compiler*, KitCgTypeId); * Pure function of the type and the target's fixed float-ABI / split-lane * policy. WK_NARROW == 0 is the common fast path. */ typedef enum ApiWideKind { - WK_NARROW = 0, /* not wide / not soft-float: the common fast path */ - WK_I128, /* 128-bit integer (api_is_i128_type) */ - WK_F128, /* 128-bit float / long double (api_is_f128_type) */ - WK_WIDE8, /* int split into two 32-bit lanes (api_is_wide8_scalar_type) */ + WK_NARROW = 0, /* not wide / not soft-float: the common fast path */ + WK_I128, /* 128-bit integer (api_is_i128_type) */ + WK_F128, /* 128-bit float / long double (api_is_f128_type) */ + WK_WIDE8, /* int split into two 32-bit lanes (api_is_wide8_scalar_type) */ WK_SOFT_DOUBLE, /* f64 on a target without hardware double */ WK_SOFT_SINGLE, /* f32 on a pure-soft target (no FP unit) */ } ApiWideKind; @@ -129,8 +123,8 @@ typedef enum ApiWideKind { /* api_type_class packs a type's codegen classification into one byte: the * ApiWideKind in the low bits plus an aggregate-place bit. Both are pure * functions of (type, target), so the result is memoized per type id (builtins - * precomputed at cg_api init, user types lazily) rather than re-derived on every - * value push. */ + * precomputed at cg_api init, user types lazily) rather than re-derived on + * every value push. */ #define API_TYPE_CLASS_WIDE_MASK 0x07u #define API_TYPE_CLASS_AGGREGATE 0x08u u8 api_type_class(Compiler*, KitCgTypeId); @@ -149,12 +143,12 @@ void api_type_layout_put(Compiler*, KitCgTypeId, u32 size, u32 align, KitCgTypeId cg_type_ptr_to(Compiler*, KitCgTypeId); KitCgTypeId cg_type_pointee(Compiler*, KitCgTypeId); -/* The function's result type, or the VOID builtin when it returns nothing. */ +/* The function's result type. A no-value return is the VOID builtin. */ KitCgTypeId cg_type_func_ret_id(Compiler*, KitCgTypeId); -/* The function's raw result type, or KIT_CG_TYPE_NONE when it returns nothing - * (the "has a result" predicate; cg_type_func_ret_id maps none -> VOID). */ +/* Same as cg_type_func_ret_id, kept for callers that distinguish the + * single-result path from the full KitCgFuncResult descriptor. */ KitCgTypeId cg_type_func_result_id(Compiler*, KitCgTypeId); -/* First-result type (or VOID) given a resolved KIT_CG_TYPE_FUNC CgType. For +/* First-result type given a resolved KIT_CG_TYPE_FUNC CgType. For * the single-result/void consumers (ABI classification, source backends) that * already hold the CgType. */ KitCgTypeId cg_func_ret_type(const CgType* fnty); diff --git a/src/cg/value.c b/src/cg/value.c @@ -2,17 +2,17 @@ /* The cached wide-class tag (ApiBitField.wide_kind) reuses the bit-field * member's trailing pad: ApiBitField stays at its original 12-byte footprint - * (u16 + u16 + u32 + u8 bit_signed + u8 wide_kind + u8 pad[2]), and the node has - * no slack past it, so the value-stack node size is unchanged. */ + * (u16 + u16 + u32 + u8 bit_signed + u8 wide_kind + u8 pad[2]). Inline language + * facts fit by packing the CG-owned state bytes into ApiSValue.flags. */ _Static_assert(sizeof(ApiBitField) == 12, "wide_kind must reuse ApiBitField pad, not grow it"); -_Static_assert(offsetof(ApiSValue, bitfield) + sizeof(ApiBitField) == - sizeof(ApiSValue), - "ApiSValue size must be unchanged by the cached wide_kind tag"); /* The 64-byte delayed cmp/arith payload lives off the node behind a pointer * (see ApiDelayed); the value-stack node must stay small. */ -_Static_assert(sizeof(ApiSValue) <= 64, - "delayed payload must be off-node; ApiSValue stays small"); +_Static_assert(sizeof(ApiSValue) == 64, + "ApiSValue must stay exactly one 64-byte stack node"); +_Static_assert(offsetof(ApiSValue, flags) + sizeof(((ApiSValue*)0)->flags) == + sizeof(ApiSValue), + "ApiSValue flags should close the packed 64-byte node"); /* The operand/value constructors run on the hottest per-operand codegen path. * Each uses a designated compound literal rather than memset + field stores: it @@ -35,20 +35,18 @@ Operand api_op_global(ObjSymId sym, i64 addend, KitCgTypeId ty) { } Operand api_op_indirect(CGLocal base, i32 ofs, KitCgTypeId ty) { - return (Operand){ - .kind = OPK_INDIRECT, - .type = ty, - .v.ind = {.base = base, .index = CG_LOCAL_NONE, .ofs = ofs}}; + return (Operand){.kind = OPK_INDIRECT, + .type = ty, + .v.ind = {.base = base, .index = CG_LOCAL_NONE, .ofs = ofs}}; } Operand api_op_indirect_indexed(CGLocal base, CGLocal index, u8 log2_scale, i32 ofs, KitCgTypeId ty) { - return (Operand){.kind = OPK_INDIRECT, - .type = ty, - .v.ind = {.base = base, - .index = index, - .log2_scale = log2_scale, - .ofs = ofs}}; + return (Operand){ + .kind = OPK_INDIRECT, + .type = ty, + .v.ind = { + .base = base, .index = index, .log2_scale = log2_scale, .ofs = ofs}}; } u8 api_residency_for(const Operand* o) { @@ -61,23 +59,23 @@ ApiSValue api_make_sv(Operand op, KitCgTypeId ty) { * 64-byte delayed union, the bitfield rider, the flags) still zero-fill, but * the compiler emits direct stores for the live fields and skips the * out-of-line memset of the whole node on this hot per-operand path. */ - return (ApiSValue){.kind = SV_OPERAND, - .op = op, - .type = ty, - .res = api_residency_for(&op), - .source_local = KIT_CG_LOCAL_NONE}; + return (ApiSValue){ + .op = op, + .type = ty, + .source_local = KIT_CG_LOCAL_NONE, + .flags = API_SV_PACK(SV_OPERAND, api_residency_for(&op), 0, 0)}; } ApiSValue api_make_lv(Operand op, KitCgTypeId ty) { ApiSValue sv = api_make_sv(op, ty); - sv.lvalue = 1; + api_sv_set_lvalue(&sv, 1); return sv; } ApiSValue api_make_sv_with_local_ownership(Operand op, KitCgTypeId ty, int owned) { ApiSValue sv = api_make_sv(op, ty); - if (op.kind == OPK_LOCAL && !owned) sv.res = RES_FIXED_LOCAL; + if (op.kind == OPK_LOCAL && !owned) api_sv_set_res(&sv, RES_FIXED_LOCAL); return sv; } @@ -91,11 +89,11 @@ int api_operand_can_address(const Operand* o) { } int api_sv_op_is(const ApiSValue* sv, OpKind kind) { - return sv->kind == SV_OPERAND && sv->op.kind == kind; + return api_sv_kind(sv) == SV_OPERAND && sv->op.kind == kind; } int api_sv_op_is_local_or_imm(const ApiSValue* sv) { - return sv->kind == SV_OPERAND && + return api_sv_kind(sv) == SV_OPERAND && (sv->op.kind == OPK_IMM || sv->op.kind == OPK_LOCAL); } @@ -111,7 +109,7 @@ int api_sv_op_is_local_or_imm(const ApiSValue* sv) { * kit_cg_field — and source_local only co-occurs with OPK_LOCAL, so * api_operand_can_address already covers both. */ int api_is_lvalue_sv(const ApiSValue* sv) { - return sv->lvalue && sv->kind == SV_OPERAND && + return api_sv_lvalue_flag(sv) && api_sv_kind(sv) == SV_OPERAND && api_operand_can_address(&sv->op); } @@ -143,14 +141,22 @@ void api_stack_grow(KitCg* g, u32 want) { Heap* h = g->c->ctx->heap; u32 cap = g->cap; ApiSValue* nb; + ApiConstValue* nc; if (cap >= want) return; while (cap < want) cap = cap ? cap * 2u : API_CG_STACK_INITIAL; nb = (ApiSValue*)h->alloc(h, sizeof(ApiSValue) * cap, _Alignof(ApiSValue)); + nc = (ApiConstValue*)h->alloc(h, sizeof(ApiConstValue) * cap, + _Alignof(ApiConstValue)); if (g->stack) { memcpy(nb, g->stack, sizeof(ApiSValue) * g->sp); h->free(h, g->stack, sizeof(ApiSValue) * g->cap); } + if (g->const_stack) { + memcpy(nc, g->const_stack, sizeof(ApiConstValue) * g->sp); + h->free(h, g->const_stack, sizeof(ApiConstValue) * g->cap); + } g->stack = nb; + g->const_stack = nc; g->cap = cap; } @@ -164,11 +170,12 @@ void api_push(KitCg* g, ApiSValue v) { * wide/soft-float dispatch class — both pure functions of the type, computed * once per type id rather than re-derived (cg_type_get + alias-chase + ABI * round trips) on every push. */ - u8 cls = api_type_class(g->c, ty); + u8 cls = ty ? api_type_class(g->c, ty) : 0; /* An aggregate (record) can only ever be a PLACE: it is addressed, loaded, * and passed by SRET/BYVAL/BYREF, never materialized as a scalar VALUE. Catch * any aggregate VALUE at the point it would enter the stack. i128/f128 are - * scalars (not aggregates), so they remain valid VALUEs and are unaffected. */ + * scalars (not aggregates), so they remain valid VALUEs and are unaffected. + */ if ((cls & API_TYPE_CLASS_AGGREGATE) && !api_is_lvalue_sv(&v)) { compiler_panic(g->c, g->cur_loc, "KitCg: aggregate must be a place, not a value; load the " @@ -180,8 +187,10 @@ void api_push(KitCg* g, ApiSValue v) { * stays valid for every later reader. */ v.bitfield.wide_kind = cls & API_TYPE_CLASS_WIDE_MASK; api_stack_grow(g, g->sp + 1); - g->stack[g->sp++] = v; - api_sv_adjust_refs(g, &v, +1); + g->stack[g->sp] = v; + g->const_stack[g->sp] = api_const_from_sv(g, &v); + g->sp++; + if (!api_unevaluated(g)) api_sv_adjust_refs(g, &v, +1); } ApiSValue api_pop(KitCg* g) { @@ -190,14 +199,15 @@ ApiSValue api_pop(KitCg* g) { compiler_panic(g->c, g->cur_loc, "KitCg: stack underflow"); } r = g->stack[--g->sp]; - api_sv_adjust_refs(g, &r, -1); + if (!api_unevaluated(g)) api_sv_adjust_refs(g, &r, -1); return r; } /* ---- local helpers ---- */ CGLocal api_local_of_sv(const ApiSValue* sv) { - if (sv->kind == SV_ARITH || sv->kind == SV_CMP) return (CGLocal)CG_LOCAL_NONE; + if (api_sv_kind(sv) == SV_ARITH || api_sv_kind(sv) == SV_CMP) + return (CGLocal)CG_LOCAL_NONE; if (sv->op.kind == OPK_LOCAL) return sv->op.v.local; if (sv->op.kind == OPK_INDIRECT) return sv->op.v.ind.base; return (CGLocal)CG_LOCAL_NONE; @@ -223,11 +233,13 @@ KitCgTypeId api_owned_local_type(KitCg* g, const ApiSValue* sv) { CGLocal api_alloc_temp_local(KitCg* g, KitCgTypeId ty) { CGLocalDesc d; CGLocal local; + if (api_unevaluated(g)) return CG_LOCAL_NONE; memset(&d, 0, sizeof d); d.type = ty; /* A compiler temporary: its home is reclaimable once the value stack drops it * at a statement boundary (kit_cg_reclaim_temps), so the single-pass frame is - * bounded by max-simultaneous-live temps rather than a per-subexpression sum. */ + * bounded by max-simultaneous-live temps rather than a per-subexpression sum. + */ d.flags |= CG_LOCAL_TRANSIENT; if (ty) { ABITypeInfo ti = abi_cg_type_info(g->c->abi, ty); @@ -360,14 +372,16 @@ void api_validate_memory_value(KitCg* g, const char* who, KitCgTypeId access_ty, } int api_sv_owns_operand_local(const ApiSValue* sv, const Operand* op) { - return sv->res == RES_LOCAL && op->kind == OPK_LOCAL && + return api_sv_res(sv) == RES_LOCAL && op->kind == OPK_LOCAL && sv->op.kind == OPK_LOCAL && sv->op.v.local == op->v.local; } void api_ensure_local(KitCg* g, ApiSValue* sv) { - if (sv->kind == SV_CMP) { + if (api_unevaluated(g)) return; + if (api_sv_kind(sv) == SV_CMP) { KitCgTypeId ty = api_sv_type(sv); - KitCgTypeId uty = api_unalias_type(g->c, ty); /* compute the dst terminal once */ + KitCgTypeId uty = + api_unalias_type(g->c, ty); /* compute the dst terminal once */ Operand dst; if (sv->delayed->cmp.a_owned && sv->delayed->cmp.a.kind == OPK_LOCAL && api_unalias_type(g->c, sv->delayed->cmp.a.type) == uty) { @@ -384,9 +398,10 @@ void api_ensure_local(KitCg* g, ApiSValue* sv) { api_materialize_cmp_to(g, sv, dst); return; } - if (sv->kind == SV_ARITH) { + if (api_sv_kind(sv) == SV_ARITH) { KitCgTypeId ty = api_sv_type(sv); - KitCgTypeId uty = api_unalias_type(g->c, ty); /* compute the dst terminal once */ + KitCgTypeId uty = + api_unalias_type(g->c, ty); /* compute the dst terminal once */ Operand dst; if (sv->delayed->arith.a_owned && sv->delayed->arith.a.kind == OPK_LOCAL && api_unalias_type(g->c, sv->delayed->arith.a.type) == uty) { @@ -409,6 +424,7 @@ void api_ensure_local(KitCg* g, ApiSValue* sv) { Operand api_force_local(KitCg* g, ApiSValue* v, KitCgTypeId ty) { CgTarget* T = g->target; ty = api_unalias_type(g->c, ty); + if (api_unevaluated(g)) return api_op_imm(0, ty); api_ensure_local(g, v); if (v->op.kind == OPK_LOCAL && !api_is_lvalue_sv(v)) { return v->op; @@ -425,7 +441,7 @@ Operand api_force_local(KitCg* g, ApiSValue* v, KitCgTypeId ty) { compiler_panic(g->c, g->cur_loc, "KitCg: cannot force operand to local"); } v->op = dst; - v->res = RES_LOCAL; + api_sv_set_res(v, RES_LOCAL); return dst; } @@ -435,12 +451,12 @@ Operand api_force_local_unless_imm(KitCg* g, ApiSValue* v, KitCgTypeId ty) { } void api_release(KitCg* g, ApiSValue* sv) { - if (sv->kind == SV_CMP) { + if (api_sv_kind(sv) == SV_CMP) { api_release_cmp(g, sv); - } else if (sv->kind == SV_ARITH) { + } else if (api_sv_kind(sv) == SV_ARITH) { api_release_arith(g, sv); } - sv->res = RES_INHERENT; + api_sv_set_res(sv, RES_INHERENT); } /* ---- -O0 transient liveness (refcount + confirming scan) ---- */ @@ -454,8 +470,8 @@ int api_coalesce_on(KitCg* g) { return g->coalesce; } -/* Mark `local` (a fresh api_alloc_temp_local handle) as this function's temp and - * zero its live-reference count. */ +/* Mark `local` (a fresh api_alloc_temp_local handle) as this function's temp + * and zero its live-reference count. */ static void api_track_temp(KitCg* g, CGLocal local) { Heap* h = g->c->ctx->heap; u32 idx = (u32)local; @@ -514,13 +530,13 @@ static void api_op_adjust_refs(KitCg* g, const Operand* op, int delta) { /* All temp locals an SValue references: its operand plus, for a delayed * cmp/arith, its pending operands. */ static void api_sv_adjust_refs(KitCg* g, const ApiSValue* sv, int delta) { - if (sv->kind == SV_OPERAND) { + if (api_sv_kind(sv) == SV_OPERAND) { api_op_adjust_refs(g, &sv->op, delta); } else if (sv->delayed) { - if (sv->kind == SV_CMP) { + if (api_sv_kind(sv) == SV_CMP) { api_op_adjust_refs(g, &sv->delayed->cmp.a, delta); api_op_adjust_refs(g, &sv->delayed->cmp.b, delta); - } else if (sv->kind == SV_ARITH) { + } else if (api_sv_kind(sv) == SV_ARITH) { api_op_adjust_refs(g, &sv->delayed->arith.a, delta); if (sv->delayed->arith.kind == API_DELAYED_BINOP) api_op_adjust_refs(g, &sv->delayed->arith.b, delta); @@ -536,31 +552,32 @@ void api_reseat_end(KitCg* g, const ApiSValue* sv) { } /* Ground-truth scan: how many live value-stack entries reference temp `local`. - * The arbiter when the incremental count reads 0 (so a reseat-accounting gap can - * only cost a missed optimization, never a miscompile). */ + * The arbiter when the incremental count reads 0 (so a reseat-accounting gap + * can only cost a missed optimization, never a miscompile). */ static u32 api_temp_scan_refs(KitCg* g, CGLocal local) { u32 n = 0; for (u32 i = 0; i < g->sp; ++i) { const ApiSValue* sv = &g->stack[i]; const Operand* op = &sv->op; - if (sv->kind == SV_OPERAND) { - if (op->kind == OPK_LOCAL && op->v.local == local) n++; + if (api_sv_kind(sv) == SV_OPERAND) { + if (op->kind == OPK_LOCAL && op->v.local == local) + n++; else if (op->kind == OPK_INDIRECT && (op->v.ind.base == local || op->v.ind.index == local)) n++; } else if (sv->delayed) { const ApiDelayed* d = sv->delayed; - const Operand* a = sv->kind == SV_CMP ? &d->cmp.a : &d->arith.a; - const Operand* b = sv->kind == SV_CMP ? &d->cmp.b : &d->arith.b; - int has_b = sv->kind == SV_CMP || d->arith.kind == API_DELAYED_BINOP; + const Operand* a = api_sv_kind(sv) == SV_CMP ? &d->cmp.a : &d->arith.a; + const Operand* b = api_sv_kind(sv) == SV_CMP ? &d->cmp.b : &d->arith.b; + int has_b = + api_sv_kind(sv) == SV_CMP || d->arith.kind == API_DELAYED_BINOP; if ((a->kind == OPK_LOCAL && a->v.local == local) || (a->kind == OPK_INDIRECT && (a->v.ind.base == local || a->v.ind.index == local))) n++; - if (has_b && - ((b->kind == OPK_LOCAL && b->v.local == local) || - (b->kind == OPK_INDIRECT && - (b->v.ind.base == local || b->v.ind.index == local)))) + if (has_b && ((b->kind == OPK_LOCAL && b->v.local == local) || + (b->kind == OPK_INDIRECT && + (b->v.ind.base == local || b->v.ind.index == local)))) n++; } } @@ -569,8 +586,9 @@ static u32 api_temp_scan_refs(KitCg* g, CGLocal local) { int api_temp_dead(KitCg* g, CGLocal local) { if (local == CG_LOCAL_NONE || !api_handle_is_temp(g, local)) return 0; - if (g->local_refs[(u32)local] > 0) return 0; /* fast reject: still referenced */ - return api_temp_scan_refs(g, local) == 0; /* confirm before acting */ + if (g->local_refs[(u32)local] > 0) + return 0; /* fast reject: still referenced */ + return api_temp_scan_refs(g, local) == 0; /* confirm before acting */ } Operand api_op_kill_if_dead(KitCg* g, Operand op, Operand dst) { @@ -704,6 +722,7 @@ Operand api_lvalue_addr(KitCg* g, ApiSValue* v, KitCgTypeId pty) { ApiSourceLocal* rec; CGLocal r; Operand dst; + if (api_unevaluated(g)) return api_op_imm(0, pty); api_local_const_address_taken(g, v->source_local); api_ensure_local(g, v); if (!api_is_lvalue_sv(v)) { diff --git a/src/cg/wide.c b/src/cg/wide.c @@ -46,6 +46,21 @@ ApiSValue api_make_wide16_int_const(KitCg* g, i64 value, KitCgTypeId ty) { return api_make_sv(api_op_local(local, ty), ty); } +ApiSValue api_make_wide16_int_const_bits(KitCg* g, u64 lo, u64 hi, + KitCgTypeId ty) { + u8 bytes[16]; + CGLocal local; + for (u32 i = 0; i < 8; ++i) { + u32 lo_idx = g->c->target.big_endian ? 15u - i : i; + u32 hi_idx = g->c->target.big_endian ? 7u - i : 8u + i; + bytes[lo_idx] = (u8)(lo >> (i * 8u)); + bytes[hi_idx] = (u8)(hi >> (i * 8u)); + } + local = api_f128_temp_local(g, ty); + api_store_f128_bytes(g, local, ty, bytes); + return api_make_sv(api_op_local(local, ty), ty); +} + void api_store_f128_bytes(KitCg* g, CGLocal local, KitCgTypeId ty, const u8 bytes[16]) { KitCgTypeId i64_ty = builtin_id(KIT_CG_BUILTIN_I64); @@ -208,10 +223,10 @@ Operand api_wide8_addr(KitCg* g, ApiSValue* v, KitCgTypeId ty) { * no-op (kind is now SV_OPERAND), so the flag survives. An operand that is * already a place is left untouched by api_ensure_local and flows through as * before. */ - if (lv.kind != SV_OPERAND) api_ensure_local(g, &lv); + if (api_sv_kind(&lv) != SV_OPERAND) api_ensure_local(g, &lv); lv.type = ty; lv.op.type = ty; - lv.lvalue = 1; + api_sv_set_lvalue(&lv, 1); return api_lvalue_addr(g, &lv, cg_type_ptr_to(g->c, ty)); } @@ -247,14 +262,14 @@ void api_wide8_store_lane(KitCg* g, Operand addr, i32 off, Operand val) { ApiSValue api_wide16_materialize_lvalue(KitCg* g, ApiSValue* v, KitCgTypeId ty) { if (v->op.kind == OPK_LOCAL) { - v->lvalue = 1; + api_sv_set_lvalue(v, 1); return *v; } if (v->op.kind == OPK_INDIRECT) { ApiSValue out = *v; out.type = ty; out.op.type = ty; - out.lvalue = 1; + api_sv_set_lvalue(&out, 1); return out; } if (v->op.kind == OPK_GLOBAL) { @@ -279,7 +294,7 @@ ApiSValue api_wide16_materialize_lvalue(KitCg* g, ApiSValue* v, compiler_panic( g->c, g->cur_loc, "KitCg: 16-byte scalar value is not addressable (kind %u, op %u)", - (unsigned)v->kind, (unsigned)v->op.kind); + (unsigned)api_sv_kind(v), (unsigned)v->op.kind); return *v; } @@ -316,6 +331,17 @@ void api_runtime_call_values(KitCg* g, const char* name, KitCgTypeId ret, const KitCgTypeId* params, u32 nparams, ApiSValue* args) { KitCgCallAttrs attrs; + if (api_unevaluated(g)) { + (void)name; + (void)params; + for (u32 i = 0; i < nparams; ++i) + if (args) api_release(g, &args[i]); + if (ret != KIT_CG_TYPE_NONE) { + api_push(g, api_uneval_value(g, ret)); + api_const_set_top(g, api_const_unknown(ret)); + } + return; + } KitCgSym sym = api_runtime_helper(g, name, ret, params, nparams); memset(&attrs, 0, sizeof attrs); for (u32 i = 0; i < nparams; ++i) api_push(g, args[i]); diff --git a/src/emu/cpu.c b/src/emu/cpu.c @@ -121,20 +121,18 @@ EmuCPUState* emu_thread_cpu(EmuThread* t) { return t ? t->cpu : NULL; } KitCgTypeId emu_thread_type(Compiler* c) { return kit_cg_type_ptr( (KitCompiler*)c, - kit_cg_builtin_types((KitCompiler*)c).id[KIT_CG_BUILTIN_VOID], 0); + kit_cg_type_builtin((KitCompiler*)c, KIT_CG_BUILTIN_VOID), 0); } KitCgTypeId emu_block_fn_type(Compiler* c) { - KitCgBuiltinTypes bi; KitCgFuncParam param; KitCgFuncResult result; KitCgFuncSig sig; - bi = kit_cg_builtin_types((KitCompiler*)c); memset(&param, 0, sizeof(param)); param.type = emu_thread_type(c); memset(&sig, 0, sizeof(sig)); memset(&result, 0, sizeof(result)); - result.type = bi.id[KIT_CG_BUILTIN_I64]; + result.type = kit_cg_type_builtin((KitCompiler*)c, KIT_CG_BUILTIN_I64); sig.result = result; sig.params = &param; sig.nparams = 1; diff --git a/src/opt/pass_combine.c b/src/opt/pass_combine.c @@ -6,12 +6,6 @@ #include "core/arena.h" #include "opt/opt_internal.h" -enum { - COMBINE_CG_TYPE_SEG_SHIFT = 6, - COMBINE_CG_TYPE_SEG_MASK = (1u << COMBINE_CG_TYPE_SEG_SHIFT) - 1u, - COMBINE_CG_TYPE_BUILTIN_SEG = 1u, -}; - /* O1 combine, MIR-shaped (see mir-gen.c:8808-9146). One forward pass per BB * maintains last-def / last-mem-def tracking; per-BB fixpoint loop iterates * until no fold fires. Rewrites in this file: @@ -558,8 +552,9 @@ static int use_after_clobber_before_redef(const Block* bl, i32 prod_idx, /* ---- ConvKind helpers (for combine_exts) ---- */ static u32 builtin_int_bytes(KitCgTypeId t) { - if ((t >> COMBINE_CG_TYPE_SEG_SHIFT) != COMBINE_CG_TYPE_BUILTIN_SEG) return 0; - KitCgBuiltinType b = (KitCgBuiltinType)(t & COMBINE_CG_TYPE_SEG_MASK); + KitCgBuiltinType b; + if (t == KIT_CG_TYPE_NONE || t > (KitCgTypeId)KIT_CG_BUILTIN_COUNT) return 0; + b = (KitCgBuiltinType)(t - 1u); switch (b) { case KIT_CG_BUILTIN_BOOL: case KIT_CG_BUILTIN_I8: diff --git a/src/opt/pass_lower.c b/src/opt/pass_lower.c @@ -1,3 +1,4 @@ +#include <stdint.h> #include <stdlib.h> #include <string.h> @@ -9,19 +10,22 @@ #include "core/strbuf.h" #include "opt/opt_internal.h" -enum { - OPT_CG_TYPE_SEG_SHIFT = 6, - OPT_CG_TYPE_SEG_MASK = (1u << OPT_CG_TYPE_SEG_SHIFT) - 1u, - OPT_CG_TYPE_BUILTIN_SEG = 1u, -}; +static int direct_builtin_from_type(KitCgTypeId t, KitCgBuiltinType* out) { + if (t == KIT_CG_TYPE_NONE || t > (KitCgTypeId)KIT_CG_BUILTIN_COUNT) return 0; + *out = (KitCgBuiltinType)(t - 1u); + return 1; +} static u32 type_size_fallback(const Func* f, KitCgTypeId t) { KitCgBuiltinType b; if (!t) return f->opt_target.ptr_size ? f->opt_target.ptr_size : 8u; - if ((t >> OPT_CG_TYPE_SEG_SHIFT) != OPT_CG_TYPE_BUILTIN_SEG) { + if (f && f->c) { + uint64_t size = kit_cg_type_size((KitCompiler*)f->c, t); + if (size && size <= UINT32_MAX) return (u32)size; + } + if (!direct_builtin_from_type(t, &b)) { return f->opt_target.ptr_size ? f->opt_target.ptr_size : 8u; } - b = (KitCgBuiltinType)(t & OPT_CG_TYPE_SEG_MASK); switch (b) { case KIT_CG_BUILTIN_BOOL: case KIT_CG_BUILTIN_I8: diff --git a/test/api/abi_classify_test.c b/test/api/abi_classify_test.c @@ -51,6 +51,10 @@ static KitCompiler* new_compiler(KitArchKind arch, KitOSKind os, return c; } +static KitCgTypeId builtin(KitCompiler* c, KitCgBuiltinType which) { + return kit_cg_type_builtin(c, which); +} + /* Build a function type `ret_ty fn(arg_ty)` and return its ABIFuncInfo. */ static const ABIFuncInfo* classify_fn(KitCompiler* c, KitCgTypeId ret_ty, KitCgTypeId arg_ty) { @@ -61,11 +65,9 @@ static const ABIFuncInfo* classify_fn(KitCompiler* c, KitCgTypeId ret_ty, memset(&param, 0, sizeof param); param.type = arg_ty; memset(&sig, 0, sizeof sig); - if (ret_ty != kit_cg_builtin_types(c).id[KIT_CG_BUILTIN_VOID]) { - memset(&sig_result, 0, sizeof sig_result); - sig_result.type = ret_ty; - sig.result = sig_result; - } + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = ret_ty; + sig.result = sig_result; sig.params = &param; sig.nparams = 1; fn = kit_cg_type_func(c, sig); @@ -175,17 +177,20 @@ static void expect_direct_2(const char* tag, const ABIArgInfo* ai, u8 c0, u8 c1, static KitCgTypeId record2(KitCompiler* c, KitCgTypeId a, KitCgTypeId b) { KitCgField f[2]; + KitCgRecordDesc desc; memset(f, 0, sizeof f); f[0].type = a; f[1].type = b; - return kit_cg_type_record(c, 0, f, 2); + memset(&desc, 0, sizeof desc); + desc.fields = f; + desc.nfields = 2; + return kit_cg_type_record(c, &desc); } static void check_target(KitArchKind arch, KitOSKind os, KitObjFmt obj) { KitCompiler* c = new_compiler(arch, os, obj); - KitCgBuiltinTypes bi = kit_cg_builtin_types(c); - KitCgTypeId i128_ty = bi.id[KIT_CG_BUILTIN_I128]; - KitCgTypeId f128_ty = bi.id[KIT_CG_BUILTIN_F128]; + KitCgTypeId i128_ty = builtin(c, KIT_CG_BUILTIN_I128); + KitCgTypeId f128_ty = builtin(c, KIT_CG_BUILTIN_F128); EXPECT(i128_ty != KIT_CG_TYPE_NONE, "%s/%s: missing i128 builtin", arch_name(arch), os_name(os)); EXPECT(f128_ty != KIT_CG_TYPE_NONE, "%s/%s: missing f128 builtin", @@ -245,12 +250,12 @@ static void check_target(KitArchKind arch, KitOSKind os, KitObjFmt obj) { } if (arch == KIT_ARCH_X86_64) { - KitCgTypeId f64_i64 = - record2(c, bi.id[KIT_CG_BUILTIN_F64], bi.id[KIT_CG_BUILTIN_I64]); - KitCgTypeId i64_f64 = - record2(c, bi.id[KIT_CG_BUILTIN_I64], bi.id[KIT_CG_BUILTIN_F64]); - KitCgTypeId f32x2 = - record2(c, bi.id[KIT_CG_BUILTIN_F32], bi.id[KIT_CG_BUILTIN_F32]); + KitCgTypeId f64_i64 = record2(c, builtin(c, KIT_CG_BUILTIN_F64), + builtin(c, KIT_CG_BUILTIN_I64)); + KitCgTypeId i64_f64 = record2(c, builtin(c, KIT_CG_BUILTIN_I64), + builtin(c, KIT_CG_BUILTIN_F64)); + KitCgTypeId f32x2 = record2(c, builtin(c, KIT_CG_BUILTIN_F32), + builtin(c, KIT_CG_BUILTIN_F32)); { const ABIFuncInfo* fi = classify_fn(c, f64_i64, f64_i64); snprintf(tag, sizeof tag, "%s/%s {double,long} arg", arch_name(arch), @@ -312,9 +317,9 @@ static void check_target(KitArchKind arch, KitOSKind os, KitObjFmt obj) { /* Build a record with N i8 fields (so size == N and align == 1). */ static KitCgTypeId make_i8_record(KitCompiler* c, const char* tag_name, u32 nfields) { - KitCgBuiltinTypes bi = kit_cg_builtin_types(c); - KitCgTypeId i8 = bi.id[KIT_CG_BUILTIN_I8]; + KitCgTypeId i8 = builtin(c, KIT_CG_BUILTIN_I8); KitCgField fields[16]; + KitCgRecordDesc desc; static const char* const names[16] = {"f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "fa", "fb", "fc", "fd", "fe", "ff"}; @@ -324,22 +329,28 @@ static KitCgTypeId make_i8_record(KitCompiler* c, const char* tag_name, fields[i].name = kit_sym_intern(c, kit_slice_cstr(names[i])); fields[i].type = i8; } - return kit_cg_type_record(c, kit_sym_intern(c, kit_slice_cstr(tag_name)), - fields, nfields); + memset(&desc, 0, sizeof desc); + desc.tag = kit_sym_intern(c, kit_slice_cstr(tag_name)); + desc.fields = fields; + desc.nfields = nfields; + return kit_cg_type_record(c, &desc); } /* Build a record { i64 a; i64 b; } — size 16, align 8. */ static KitCgTypeId make_two_i64_record(KitCompiler* c, const char* tag_n) { - KitCgBuiltinTypes bi = kit_cg_builtin_types(c); - KitCgTypeId i64 = bi.id[KIT_CG_BUILTIN_I64]; + KitCgTypeId i64 = builtin(c, KIT_CG_BUILTIN_I64); KitCgField fields[2]; + KitCgRecordDesc desc; memset(fields, 0, sizeof fields); fields[0].name = kit_sym_intern(c, KIT_SLICE_LIT("a")); fields[0].type = i64; fields[1].name = kit_sym_intern(c, KIT_SLICE_LIT("b")); fields[1].type = i64; - return kit_cg_type_record(c, kit_sym_intern(c, kit_slice_cstr(tag_n)), fields, - 2); + memset(&desc, 0, sizeof desc); + desc.tag = kit_sym_intern(c, kit_slice_cstr(tag_n)); + desc.fields = fields; + desc.nfields = 2; + return kit_cg_type_record(c, &desc); } /* Classify a function `ret_ty fn(p0, p1, ..., pN-1)` and return its info. */ @@ -354,11 +365,9 @@ static const ABIFuncInfo* classify_fn_n(KitCompiler* c, KitCgTypeId ret_ty, memset(params, 0, sizeof params); for (u32 i = 0; i < nargs; ++i) params[i].type = arg_types[i]; memset(&sig, 0, sizeof sig); - if (ret_ty != kit_cg_builtin_types(c).id[KIT_CG_BUILTIN_VOID]) { - memset(&sig_result, 0, sizeof sig_result); - sig_result.type = ret_ty; - sig.result = sig_result; - } + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = ret_ty; + sig.result = sig_result; sig.params = params; sig.nparams = nargs; sig.abi_variadic = variadic ? true : false; @@ -400,10 +409,10 @@ static void expect_direct_1x_int(const char* tag, const ABIArgInfo* ai, * placement is codegen, not classifier output). */ static void test_win64_specifics(void) { KitCompiler* c = new_compiler(KIT_ARCH_X86_64, KIT_OS_WINDOWS, KIT_OBJ_COFF); - KitCgBuiltinTypes bi = kit_cg_builtin_types(c); - KitCgTypeId i32 = bi.id[KIT_CG_BUILTIN_I32]; - KitCgTypeId f64 = bi.id[KIT_CG_BUILTIN_F64]; - KitCgTypeId voidp = kit_cg_type_ptr(c, bi.id[KIT_CG_BUILTIN_VOID], 0); + KitCgTypeId void_ty = builtin(c, KIT_CG_BUILTIN_VOID); + KitCgTypeId i32 = builtin(c, KIT_CG_BUILTIN_I32); + KitCgTypeId f64 = builtin(c, KIT_CG_BUILTIN_F64); + KitCgTypeId voidp = kit_cg_type_ptr(c, void_ty, 0); KitCgTypeId rec1 = make_i8_record(c, "S1", 1); KitCgTypeId rec3 = make_i8_record(c, "S3", 3); KitCgTypeId rec16 = make_two_i64_record(c, "S16"); @@ -423,8 +432,7 @@ static void test_win64_specifics(void) { * classifier emits per-arg parts regardless. */ { KitCgTypeId args[5] = {i32, i32, i32, i32, i32}; - const ABIFuncInfo* fi = - classify_fn_n(c, bi.id[KIT_CG_BUILTIN_VOID], args, 5, 0); + const ABIFuncInfo* fi = classify_fn_n(c, void_ty, args, 5, 0); EXPECT(fi->nparams == 5, "win64 5xint: nparams=%u want 5", (unsigned)fi->nparams); for (u32 i = 0; i < 5; ++i) { @@ -437,8 +445,7 @@ static void test_win64_specifics(void) { /* Case 3: void f(double,double,double,double,double) — 5 doubles. */ { KitCgTypeId args[5] = {f64, f64, f64, f64, f64}; - const ABIFuncInfo* fi = - classify_fn_n(c, bi.id[KIT_CG_BUILTIN_VOID], args, 5, 0); + const ABIFuncInfo* fi = classify_fn_n(c, void_ty, args, 5, 0); EXPECT(fi->nparams == 5, "win64 5xfp: nparams=%u want 5", (unsigned)fi->nparams); for (u32 i = 0; i < 5; ++i) { @@ -453,8 +460,7 @@ static void test_win64_specifics(void) { * sharing is a codegen call-site concern. */ { KitCgTypeId args[4] = {i32, f64, i32, f64}; - const ABIFuncInfo* fi = - classify_fn_n(c, bi.id[KIT_CG_BUILTIN_VOID], args, 4, 0); + const ABIFuncInfo* fi = classify_fn_n(c, void_ty, args, 4, 0); EXPECT(fi->nparams == 4, "win64 mix: nparams=%u want 4", (unsigned)fi->nparams); expect_direct_1x_int("win64 mix arg[0]", &fi->params[0], 4); @@ -491,8 +497,7 @@ static void test_win64_specifics(void) { * goes by hidden pointer (BYVAL) on Win64. */ { KitCgTypeId args[1] = {rec3}; - const ABIFuncInfo* fi = - classify_fn_n(c, bi.id[KIT_CG_BUILTIN_VOID], args, 1, 0); + const ABIFuncInfo* fi = classify_fn_n(c, void_ty, args, 1, 0); EXPECT(fi->nparams == 1, "win64 S3 arg: nparams=%u want 1", (unsigned)fi->nparams); expect_indirect_align("win64 S3 arg", &fi->params[0], /*is_return=*/0, @@ -527,8 +532,8 @@ static void test_win64_specifics(void) { * and FP parameters to variadic functions are routed through integer slots. */ static void test_aarch64_windows_variadic(void) { KitCompiler* c = new_compiler(KIT_ARCH_ARM_64, KIT_OS_WINDOWS, KIT_OBJ_COFF); - KitCgBuiltinTypes bi = kit_cg_builtin_types(c); - KitCgTypeId f64 = bi.id[KIT_CG_BUILTIN_F64]; + KitCgTypeId void_ty = builtin(c, KIT_CG_BUILTIN_VOID); + KitCgTypeId f64 = builtin(c, KIT_CG_BUILTIN_F64); KitCgTypeId args[1] = {f64}; ABITypeInfo vi = abi_va_list_info(((Compiler*)c)->abi); @@ -541,14 +546,12 @@ static void test_aarch64_windows_variadic(void) { (unsigned)vi.scalar_kind, (unsigned)ABI_SC_PTR); { - const ABIFuncInfo* fi = - classify_fn_n(c, bi.id[KIT_CG_BUILTIN_VOID], args, 1, 0); + const ABIFuncInfo* fi = classify_fn_n(c, void_ty, args, 1, 0); expect_direct_1x_fp("aarch64/windows nonvariadic double arg", &fi->params[0], 8); } { - const ABIFuncInfo* fi = - classify_fn_n(c, bi.id[KIT_CG_BUILTIN_VOID], args, 1, 1); + const ABIFuncInfo* fi = classify_fn_n(c, void_ty, args, 1, 1); expect_direct_1x_int("aarch64/windows variadic double arg", &fi->params[0], 8); EXPECT(fi->vararg_on_stack == 0, @@ -560,11 +563,10 @@ static void test_aarch64_windows_variadic(void) { static void test_apple_arm64_stack_traits(void) { KitCompiler* c = new_compiler(KIT_ARCH_ARM_64, KIT_OS_MACOS, KIT_OBJ_MACHO); - KitCgBuiltinTypes bi = kit_cg_builtin_types(c); - KitCgTypeId i32 = bi.id[KIT_CG_BUILTIN_I32]; + KitCgTypeId void_ty = builtin(c, KIT_CG_BUILTIN_VOID); + KitCgTypeId i32 = builtin(c, KIT_CG_BUILTIN_I32); KitCgTypeId args[1] = {i32}; - const ABIFuncInfo* fi = - classify_fn_n(c, bi.id[KIT_CG_BUILTIN_VOID], args, 1, 1); + const ABIFuncInfo* fi = classify_fn_n(c, void_ty, args, 1, 1); EXPECT(fi->vararg_on_stack == 1, "apple arm64 variadic: vararg_on_stack=%u want 1", @@ -589,22 +591,21 @@ static void check_scalar_split_lane_target(KitArchKind arch, KitOSKind os, KitObjFmt obj, const char* tag, u32 want_i64, u32 want_f64) { KitCompiler* c = new_compiler(arch, os, obj); - KitCgBuiltinTypes bi = kit_cg_builtin_types(c); TargetABI* abi = ((Compiler*)c)->abi; - - EXPECT( - abi_cg_scalar_split_lane_size(abi, bi.id[KIT_CG_BUILTIN_I64]) == want_i64, - "%s i64 split lane=%u want %u", tag, - (unsigned)abi_cg_scalar_split_lane_size(abi, bi.id[KIT_CG_BUILTIN_I64]), - (unsigned)want_i64); - EXPECT( - abi_cg_scalar_split_lane_size(abi, bi.id[KIT_CG_BUILTIN_F64]) == want_f64, - "%s f64 split lane=%u want %u", tag, - (unsigned)abi_cg_scalar_split_lane_size(abi, bi.id[KIT_CG_BUILTIN_F64]), - (unsigned)want_f64); - EXPECT(abi_cg_scalar_split_lane_size(abi, bi.id[KIT_CG_BUILTIN_I32]) == 0, + KitCgTypeId i64 = builtin(c, KIT_CG_BUILTIN_I64); + KitCgTypeId f64 = builtin(c, KIT_CG_BUILTIN_F64); + KitCgTypeId i32 = builtin(c, KIT_CG_BUILTIN_I32); + KitCgTypeId f32 = builtin(c, KIT_CG_BUILTIN_F32); + + EXPECT(abi_cg_scalar_split_lane_size(abi, i64) == want_i64, + "%s i64 split lane=%u want %u", tag, + (unsigned)abi_cg_scalar_split_lane_size(abi, i64), (unsigned)want_i64); + EXPECT(abi_cg_scalar_split_lane_size(abi, f64) == want_f64, + "%s f64 split lane=%u want %u", tag, + (unsigned)abi_cg_scalar_split_lane_size(abi, f64), (unsigned)want_f64); + EXPECT(abi_cg_scalar_split_lane_size(abi, i32) == 0, "%s i32 should not be split-lane", tag); - EXPECT(abi_cg_scalar_split_lane_size(abi, bi.id[KIT_CG_BUILTIN_F32]) == 0, + EXPECT(abi_cg_scalar_split_lane_size(abi, f32) == 0, "%s f32 should not be split-lane", tag); kit_compiler_free(c); diff --git a/test/api/cg_const_test.c b/test/api/cg_const_test.c @@ -0,0 +1,223 @@ +#include <kit/cg.h> +#include <kit/core.h> +#include <kit/object.h> +#include <stdint.h> +#include <string.h> + +#include "lib/kit_unit.h" + +static int make_cg(KitUnit* u, KitCompiler* c, KitCg** cg_out, + KitObjBuilder** ob_out) { + KitCodeOptions opts; + KitObjBuilder* ob = NULL; + KitCg* cg = NULL; + memset(&opts, 0, sizeof opts); + CU_CHECK_RET(u, kit_obj_builder_new(c, &ob) == KIT_OK && ob, + "obj builder allocation failed"); + CU_CHECK_RET(u, kit_cg_new(c, &cg) == KIT_OK && cg, "cg new failed"); + CU_CHECK_RET(u, kit_cg_begin(cg, ob, &opts) == KIT_OK, "cg begin failed"); + *cg_out = cg; + *ob_out = ob; + return 1; +} + +static void destroy_cg(KitCg* cg, KitObjBuilder* ob) { + kit_cg_free(cg); + kit_obj_builder_free(ob); +} + +static void expect_const(KitUnit* u, KitCg* cg, uint16_t width, uint64_t lo, + uint64_t hi, const char* what) { + KitCgConstInt v; + memset(&v, 0, sizeof v); + CU_EXPECT(u, kit_cg_top_const_int_ex(cg, &v), "%s should be known", what); + CU_EXPECT(u, v.known && v.width == width && v.lo == lo && v.hi == hi, + "%s got known=%u width=%u hi=%llu lo=%llu", what, (unsigned)v.known, + (unsigned)v.width, (unsigned long long)v.hi, + (unsigned long long)v.lo); +} + +static void test_file_scope_unevaluated(KitUnit* u, KitCompiler* c) { + KitCgTypeId i32 = kit_cg_type_builtin(c, KIT_CG_BUILTIN_I32); + KitCgTypeId i64 = kit_cg_type_builtin(c, KIT_CG_BUILTIN_I64); + KitCg* cg = NULL; + KitObjBuilder* ob = NULL; + int64_t i64v = 0; + if (!make_cg(u, c, &cg, &ob)) return; + + kit_cg_unevaluated_push(cg); + kit_cg_push_int(cg, 40, i64); + kit_cg_push_int(cg, 2, i64); + kit_cg_int_binop(cg, KIT_CG_INT_ADD, 0); + expect_const(u, cg, 64, 42, 0, "file-scope add"); + CU_EXPECT(u, kit_cg_top_const_i64(cg, &i64v) && i64v == 42, + "i64 convenience query should see 42"); + + kit_cg_unevaluated_push(cg); + kit_cg_push_int(cg, 7, i32); + kit_cg_push_int(cg, 3, i32); + kit_cg_int_binop(cg, KIT_CG_INT_MUL, 0); + expect_const(u, cg, 32, 21, 0, "nested multiply"); + kit_cg_drop(cg); + kit_cg_unevaluated_pop(cg); + + expect_const(u, cg, 64, 42, 0, "outer value after nested pop"); + kit_cg_drop(cg); + kit_cg_unevaluated_pop(cg); + CU_EXPECT(u, kit_cg_stack_depth(cg) == 0, "unevaluated stack should empty"); + + destroy_cg(cg, ob); +} + +static void test_divrem_and_unknown(KitUnit* u, KitCompiler* c) { + KitCgTypeId i64 = kit_cg_type_builtin(c, KIT_CG_BUILTIN_I64); + KitCg* cg = NULL; + KitObjBuilder* ob = NULL; + KitCgConstInt v; + if (!make_cg(u, c, &cg, &ob)) return; + kit_cg_unevaluated_push(cg); + + kit_cg_push_int(cg, 10, i64); + kit_cg_push_int(cg, 3, i64); + kit_cg_int_binop(cg, KIT_CG_INT_UDIV, 0); + expect_const(u, cg, 64, 3, 0, "u64 div"); + kit_cg_drop(cg); + + kit_cg_push_int(cg, 10, i64); + kit_cg_push_int(cg, 3, i64); + kit_cg_int_binop(cg, KIT_CG_INT_UREM, 0); + expect_const(u, cg, 64, 1, 0, "u64 rem"); + kit_cg_drop(cg); + + kit_cg_push_int(cg, 10, i64); + kit_cg_push_int(cg, 0, i64); + kit_cg_int_binop(cg, KIT_CG_INT_UDIV, 0); + memset(&v, 0, sizeof v); + CU_EXPECT(u, !kit_cg_top_const_int_ex(cg, &v), + "divide by zero should produce unknown constant"); + kit_cg_drop(cg); + + kit_cg_unevaluated_pop(cg); + destroy_cg(cg, ob); +} + +static KitCgConstInt u128(uint64_t hi, uint64_t lo) { + KitCgConstInt v; + memset(&v, 0, sizeof v); + v.lo = lo; + v.hi = hi; + v.width = 128; + v.known = 1; + return v; +} + +static void test_i128_bit_folds(KitUnit* u, KitCompiler* c) { + KitCgTypeId i128 = kit_cg_type_builtin(c, KIT_CG_BUILTIN_I128); + KitCg* cg = NULL; + KitObjBuilder* ob = NULL; + KitCgConstInt v; + if (!make_cg(u, c, &cg, &ob)) return; + kit_cg_unevaluated_push(cg); + + v = u128(0, UINT64_MAX); + kit_cg_push_const_int(cg, i128, &v); + kit_cg_push_int(cg, 1, i128); + kit_cg_int_binop(cg, KIT_CG_INT_ADD, 0); + expect_const(u, cg, 128, 0, 1, "i128 add carry"); + kit_cg_drop(cg); + + v = u128(1, 0); + kit_cg_push_const_int(cg, i128, &v); + kit_cg_push_int(cg, 2, i128); + kit_cg_int_binop(cg, KIT_CG_INT_MUL, 0); + expect_const(u, cg, 128, 0, 2, "i128 mul low 128"); + kit_cg_drop(cg); + + kit_cg_push_int(cg, 1, i128); + kit_cg_push_int(cg, 65, i128); + kit_cg_int_binop(cg, KIT_CG_INT_SHL, 0); + expect_const(u, cg, 128, 0, 2, "i128 shl"); + kit_cg_drop(cg); + + v = u128(1, 0); + kit_cg_push_const_int(cg, i128, &v); + v = u128(2, 0); + kit_cg_push_const_int(cg, i128, &v); + kit_cg_int_cmp(cg, KIT_CG_INT_LT_U); + expect_const(u, cg, 32, 1, 0, "i128 cmp"); + kit_cg_drop(cg); + + v = u128(4, 0); + kit_cg_push_const_int(cg, i128, &v); + kit_cg_push_int(cg, 2, i128); + kit_cg_int_binop(cg, KIT_CG_INT_UDIV, 0); + CU_EXPECT(u, !kit_cg_top_const_int_ex(cg, NULL), + "i128 div is intentionally unknown"); + kit_cg_drop(cg); + + kit_cg_unevaluated_pop(cg); + destroy_cg(cg, ob); +} + +static void test_dummy_handle_stack_effects(KitUnit* u, KitCompiler* c) { + KitCgTypeId i32 = kit_cg_type_builtin(c, KIT_CG_BUILTIN_I32); + KitCgTypeId fn; + KitCgFuncResult result; + KitCgFuncSig sig; + KitCgDecl decl; + KitCgSym sym; + KitCg* cg = NULL; + KitObjBuilder* ob = NULL; + if (!make_cg(u, c, &cg, &ob)) return; + + memset(&result, 0, sizeof result); + result.type = i32; + memset(&sig, 0, sizeof sig); + sig.result = result; + sig.call_conv = KIT_CG_CC_TARGET_C; + fn = kit_cg_type_func(c, sig); + memset(&decl, 0, sizeof decl); + decl.kind = KIT_CG_DECL_FUNC; + decl.linkage_name = kit_sym_intern(c, KIT_SLICE_LIT("callee")); + decl.display_name = decl.linkage_name; + decl.type = fn; + decl.sym.bind = KIT_SB_GLOBAL; + decl.sym.visibility = KIT_CG_VIS_DEFAULT; + sym = kit_cg_decl(cg, decl); + + kit_cg_unevaluated_push(cg); + kit_cg_push_label_addr(cg, kit_cg_label_new(cg), kit_cg_type_ptr(c, i32, 0)); + CU_EXPECT(u, kit_cg_stack_depth(cg) == 1, "label addr should push one"); + CU_EXPECT(u, !kit_cg_top_const_int_ex(cg, NULL), + "label address should not be an integer constant"); + kit_cg_drop(cg); + + kit_cg_push_symbol_addr(cg, sym, 0); + kit_cg_call(cg, 0, fn, (KitCgCallAttrs){0}); + CU_EXPECT(u, kit_cg_stack_depth(cg) == 1, "call should push result"); + CU_EXPECT(u, !kit_cg_top_const_int_ex(cg, NULL), + "call result should be unknown"); + kit_cg_drop(cg); + kit_cg_unevaluated_pop(cg); + + destroy_cg(cg, ob); +} + +int main(void) { + KitUnit u; + KitCompiler* c = NULL; + KitTargetSpec t; + kit_unit_init(&u); + t = kit_unit_target(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF); + CU_EXPECT(&u, kit_unit_compiler_new(&u, t, &c) == KIT_OK && c, + "compiler allocation failed"); + if (c) { + test_file_scope_unevaluated(&u, c); + test_divrem_and_unknown(&u, c); + test_i128_bit_folds(&u, c); + test_dummy_handle_stack_effects(&u, c); + kit_compiler_free(c); + } + kit_unit_summary(&u, "cg_const_test"); + return kit_unit_status(&u); +} diff --git a/test/api/cg_control_test.c b/test/api/cg_control_test.c @@ -72,8 +72,7 @@ static int64_t loop_expected(int64_t n) { * `continue` (in the break_* shapes). */ static void build_loop_fn(KitCompiler* c, KitCg* cg, const char* name, Variant variant) { - KitCgBuiltinTypes bi = kit_cg_builtin_types(c); - KitCgTypeId i32 = bi.id[KIT_CG_BUILTIN_I32]; + KitCgTypeId i32 = kit_cg_type_builtin(c, KIT_CG_BUILTIN_I32); KitCgFuncParam param; KitCgFuncResult result; KitCgFuncSig sig; @@ -120,7 +119,7 @@ static void build_loop_fn(KitCompiler* c, KitCg* cg, const char* name, kit_cg_push_int(cg, 0, i32); kit_cg_local_write(cg, acc_local, ma); - scope = kit_cg_scope_begin(cg, KIT_CG_TYPE_NONE); + scope = kit_cg_scope_begin(cg); if (variant == V_BREAK_TRUE || variant == V_BREAK_FALSE) { /* test at top: break out when i has reached n */ @@ -177,11 +176,12 @@ static void build_loop_fn(KitCompiler* c, KitCg* cg, const char* name, * so the same interpreter/emit harness as the conditional variants applies. */ /* Boilerplate: declare `int <name>(int)` and open it; returns the param - * local and fills *out_i32 with the i32 type id and *out_ma with an i32 access. */ + * local and fills *out_i32 with the i32 type id and *out_ma with an i32 access. + */ static KitCgLocal begin_int_int_fn(KitCompiler* c, KitCg* cg, const char* name, - KitCgTypeId* out_i32, KitCgMemAccess* out_ma) { - KitCgBuiltinTypes bi = kit_cg_builtin_types(c); - KitCgTypeId i32 = bi.id[KIT_CG_BUILTIN_I32]; + KitCgTypeId* out_i32, + KitCgMemAccess* out_ma) { + KitCgTypeId i32 = kit_cg_type_builtin(c, KIT_CG_BUILTIN_I32); KitCgFuncParam param; KitCgFuncResult result; KitCgFuncSig sig; @@ -217,7 +217,8 @@ static KitCgLocal begin_int_int_fn(KitCompiler* c, KitCg* cg, const char* name, /* int two_result_block(int x) { return (x+1) + (x+2); } // == 2x + 3 * via a block carrying two i32 results that scope_end yields bottom->top. */ -static void build_two_result_block(KitCompiler* c, KitCg* cg, const char* name) { +static void build_two_result_block(KitCompiler* c, KitCg* cg, + const char* name) { KitCgTypeId i32; KitCgMemAccess ma; KitCgLocal x = begin_int_int_fn(c, cg, name, &i32, &ma); @@ -240,10 +241,10 @@ static void build_two_result_block(KitCompiler* c, KitCg* cg, const char* name) kit_cg_func_end(cg); } -/* int one_param_loop(int n) { int acc=0; for (int i=0;i<n;i++) acc+=i; return acc; } - * The loop carries i as a single param across the back edge; acc is a plain - * local. Exercises scope_begin_sig(1 param) + store_params + continue, with the - * loop exited by falling through the body bottom (no break). */ +/* int one_param_loop(int n) { int acc=0; for (int i=0;i<n;i++) acc+=i; return + * acc; } The loop carries i as a single param across the back edge; acc is a + * plain local. Exercises scope_begin_sig(1 param) + store_params + continue, + * with the loop exited by falling through the body bottom (no break). */ static void build_one_param_loop(KitCompiler* c, KitCg* cg, const char* name) { KitCgTypeId i32; KitCgMemAccess ma; @@ -333,9 +334,9 @@ static void build_two_param_loop(KitCompiler* c, KitCg* cg, const char* name) { kit_cg_local_read(cg, i_l, ma); kit_cg_push_int(cg, 1, i32); kit_cg_int_binop(cg, KIT_CG_INT_ADD, 0); - kit_cg_local_write(cg, i_l, ma); /* i += 1 */ - kit_cg_local_read(cg, i_l, ma); /* fresh param i */ - kit_cg_local_read(cg, acc_l, ma); /* fresh param acc */ + kit_cg_local_write(cg, i_l, ma); /* i += 1 */ + kit_cg_local_read(cg, i_l, ma); /* fresh param i */ + kit_cg_local_read(cg, acc_l, ma); /* fresh param acc */ kit_cg_scope_store_params(cg, loop); /* reverse-pop into [i,acc] locals */ kit_cg_jump(cg, kit_cg_scope_continue_label(cg, loop)); kit_cg_label_place(cg, done); @@ -486,13 +487,15 @@ typedef struct { static KitStatus reject_body(KitCompiler* c, void* user) { RejectCtx* r = (RejectCtx*)user; - KitCgBuiltinTypes bi = kit_cg_builtin_types(c); KitCgFuncSig sig; + KitCgFuncResult result; KitCgDecl decl; KitCgSym sym; KitCgScope blk; - (void)bi; memset(&sig, 0, sizeof sig); + memset(&result, 0, sizeof result); + result.type = kit_cg_type_builtin(c, KIT_CG_BUILTIN_VOID); + sig.result = result; sig.call_conv = KIT_CG_CC_TARGET_C; /* void() */ memset(&decl, 0, sizeof decl); decl.kind = KIT_CG_DECL_FUNC; @@ -503,7 +506,7 @@ static KitStatus reject_body(KitCompiler* c, void* user) { decl.sym.visibility = KIT_CG_VIS_DEFAULT; sym = kit_cg_decl(r->cg, decl); kit_cg_func_begin(r->cg, sym); - blk = kit_cg_block_begin(r->cg, KIT_CG_TYPE_NONE); + blk = kit_cg_block_begin(r->cg); kit_cg_continue(r->cg, blk); /* <- must panic: blocks have no loop header */ kit_cg_scope_end(r->cg, blk); kit_cg_ret(r->cg); diff --git a/test/api/cg_fp_cmp_test.c b/test/api/cg_fp_cmp_test.c @@ -109,10 +109,9 @@ static double bits_to_double(uint64_t b) { * declared symbol; the function is captured by `name` for interp lookup. */ static void build_cmp_fn(KitCompiler* c, KitCg* cg, const char* name, KitCgFpCmpOp op, int use_f128) { - KitCgBuiltinTypes bi = kit_cg_builtin_types(c); - KitCgTypeId i32 = bi.id[KIT_CG_BUILTIN_I32]; - KitCgTypeId f64 = bi.id[KIT_CG_BUILTIN_F64]; - KitCgTypeId f128 = bi.id[KIT_CG_BUILTIN_F128]; + KitCgTypeId i32 = kit_cg_type_builtin(c, KIT_CG_BUILTIN_I32); + KitCgTypeId f64 = kit_cg_type_builtin(c, KIT_CG_BUILTIN_F64); + KitCgTypeId f128 = kit_cg_type_builtin(c, KIT_CG_BUILTIN_F128); KitCgFuncParam params[2]; KitCgFuncResult result; KitCgFuncSig sig; diff --git a/test/api/cg_switch_test.c b/test/api/cg_switch_test.c @@ -17,6 +17,8 @@ #include <kit/cg.h> #include <kit/core.h> +#include <kit/disasm.h> +#include <kit/interp.h> #include <kit/object.h> #include <stdarg.h> #include <stdio.h> @@ -247,12 +249,325 @@ static void run_all_shapes(KitCompiler* c, KitCgTypeId i32_ty, } } +/* ---- Jump-table decision -------------------------------------------- * + * + * The shape checks above only assert that lowering succeeds. These build a + * function, emit an ELF object for the test compiler's (aa64) target, + * disassemble .text, and report whether the dispatch became a real jump + * table — detectable as the AArch64 indirect register branch `br`, which + * the cmp/branch chain never emits. This is what lets us assert the + * *policy* decision (table vs chain), not just structural success. */ + +/* Build `int32_t <name>(<sel> x){ switch(x){...} }` from `sh` into an + * already-begun cg, shared by the disasm and interpreter checks. Returns 0 + * on success, -1 on a harness failure. */ +static int emit_switch_fn(KitCompiler* c, KitCg* cg, const char* name, + KitCgTypeId i32_ty, const SwitchShape* sh) { + KitCgFuncParam param_desc; + KitCgFuncResult sig_result; + KitCgFuncSig sig; + KitCgDecl decl; + KitCgSym sym; + KitCgLocalAttrs attrs; + KitCgLocal param; + KitCgLabel default_lbl; + KitCgLabel end_lbl; + KitCgLabel* case_lbls = NULL; + KitCgSwitchCase* cases = NULL; + KitCgSwitch sw; + int rc = -1; + uint32_t i; + + memset(&param_desc, 0, sizeof param_desc); + param_desc.type = sh->selector_type; + memset(&sig_result, 0, sizeof sig_result); + sig_result.type = i32_ty; + memset(&sig, 0, sizeof sig); + sig.result = sig_result; + sig.params = &param_desc; + sig.nparams = 1; + sig.call_conv = KIT_CG_CC_TARGET_C; + + memset(&decl, 0, sizeof decl); + decl.kind = KIT_CG_DECL_FUNC; + decl.linkage_name = kit_sym_intern(c, kit_slice_cstr(name)); + decl.display_name = decl.linkage_name; + decl.type = kit_cg_type_func(c, sig); + decl.sym.bind = KIT_SB_GLOBAL; + decl.sym.visibility = KIT_CG_VIS_DEFAULT; + sym = kit_cg_decl(cg, decl); + if (sym == KIT_CG_SYM_NONE) return -1; + + kit_cg_func_begin(cg, sym); + memset(&attrs, 0, sizeof attrs); + attrs.name = kit_sym_intern(c, KIT_SLICE_LIT("x")); + param = kit_cg_param(cg, 0, sh->selector_type, attrs); + if (param == KIT_CG_LOCAL_NONE) return -1; + + end_lbl = kit_cg_label_new(cg); + default_lbl = sh->has_default ? kit_cg_label_new(cg) : end_lbl; + if (sh->ncases) { + case_lbls = (KitCgLabel*)malloc(sh->ncases * sizeof *case_lbls); + cases = (KitCgSwitchCase*)malloc(sh->ncases * sizeof *cases); + if (!case_lbls || !cases) goto done; + for (i = 0; i < sh->ncases; ++i) { + case_lbls[i] = kit_cg_label_new(cg); + cases[i].value = (uint64_t)sh->values[i]; + cases[i].label = case_lbls[i]; + } + } + + kit_cg_push_local(cg, param); + kit_cg_load( + cg, (KitCgMemAccess){.type = sh->selector_type, + .align = kit_cg_type_align(c, sh->selector_type)}); + memset(&sw, 0, sizeof sw); + sw.selector_type = sh->selector_type; + sw.default_label = default_lbl; + sw.cases = cases; + sw.ncases = sh->ncases; + sw.hint = sh->hint; + kit_cg_switch(cg, sw); + + for (i = 0; i < sh->ncases; ++i) { + kit_cg_label_place(cg, case_lbls[i]); + kit_cg_push_int(cg, (uint64_t)(int64_t)sh->results[i], i32_ty); + kit_cg_ret(cg); + } + if (sh->has_default) { + kit_cg_label_place(cg, default_lbl); + kit_cg_push_int(cg, (uint64_t)(int64_t)sh->default_result, i32_ty); + kit_cg_ret(cg); + } + kit_cg_label_place(cg, end_lbl); + kit_cg_push_int(cg, (uint64_t)(int64_t)-1, i32_ty); + kit_cg_ret(cg); + kit_cg_func_end(cg); + rc = 0; + +done: + free(case_lbls); + free(cases); + return rc; +} + +/* 1 iff .text contains an aa64 indirect register branch (BR). -1 on harness + * failure. */ +static int switch_lowers_to_table(KitCompiler* c, KitCgTypeId i32_ty, + const SwitchShape* sh, int opt_level) { + KitCodeOptions opts; + KitObjBuilder* ob; + KitCg* cg = NULL; + KitWriter* writer = NULL; + KitObjFile* file = NULL; + KitSlice bytes; + KitObjSection text_sec; + const uint8_t* data = NULL; + size_t len = 0; + KitDisasmContext dc; + KitDisasmIter* it = NULL; + KitInsn insn; + int found = -1; + + memset(&opts, 0, sizeof opts); + opts.opt_level = opt_level; + ob = new_obj(c); + if (!ob) return -1; + if (kit_cg_new(c, &cg) != KIT_OK || !cg) { + kit_obj_builder_free(ob); + return -1; + } + if (kit_cg_begin(cg, ob, &opts) != KIT_OK) goto done; + if (emit_switch_fn(c, cg, "f", i32_ty, sh) != 0) goto done; + + if (kit_cg_finish(cg, NULL) != KIT_OK) goto done; + if (kit_cg_detach(cg) != KIT_OK) goto done; + + if (kit_writer_mem(&g_u.heap, &writer) != KIT_OK || !writer) goto done; + if (kit_obj_builder_emit(ob, writer) != KIT_OK) goto done; + bytes.data = kit_writer_mem_bytes(writer, &len); + bytes.len = len; + if (kit_obj_open(&g_u.ctx, KIT_SLICE_LIT("<sw-test>"), &bytes, &file) != + KIT_OK) + goto done; + if (kit_obj_section_by_name(file, KIT_SLICE_LIT(".text"), &text_sec) != + KIT_OK) + goto done; + if (kit_obj_section_data(file, text_sec, &data, &len) != KIT_OK) goto done; + + memset(&dc, 0, sizeof dc); + dc.target = kit_compiler_target(c); + dc.context = g_u.ctx; + if (kit_disasm_iter_new(&dc, data, len, 0, file, &it) != KIT_OK || !it) + goto done; + found = 0; + while (kit_disasm_iter_next(it, &insn) == KIT_ITER_ITEM) { + if (insn.mnemonic.len == 2 && insn.mnemonic.s && + insn.mnemonic.s[0] == 'b' && insn.mnemonic.s[1] == 'r') { + found = 1; + break; + } + } + +done: + if (it) kit_disasm_iter_free(it); + if (file) kit_obj_free(file); + if (writer) kit_writer_close(writer); + if (cg) kit_cg_free(cg); + kit_obj_builder_free(ob); + return found; +} + +static void run_table_decision_checks(KitCompiler* c, KitCgTypeId i32_ty, + KitCgTypeId i64_ty) { + /* Six contiguous 64-bit values straddling the signed midpoint 2^63. + * As *unsigned* they span just [2^63-3, 2^63+2] (span 6, table-friendly), + * but a signed-only reading sees them at both ends of the i64 range and + * the table is rejected. The selector signedness isn't even visible to + * the lowering (CG has only I64), so the planner must take the tighter of + * the signed- and unsigned-seam windows. */ + static const uint64_t MID = (uint64_t)1 << 63; + static int64_t mid_vals[6]; + static const int32_t mid_res[] = {200, 201, 202, 203, 204, 205}; + /* Dense small positive values: a table under either reading — the + * positive control that the detector reports tables when one is emitted. */ + static const int64_t lo_vals[] = {10, 11, 12, 13, 14, 15}; + static const int32_t lo_res[] = {100, 101, 102, 103, 104, 105}; + /* Genuinely sparse: span far exceeds MAX_SPAN under either window, so no + * table even with the JUMP_TABLE hint — the negative control. */ + static const int64_t sparse_vals[] = {0, 1ll << 20, 1ll << 40, 1ll << 60}; + static const int32_t sparse_res[] = {1, 2, 3, 4}; + SwitchShape mid; + SwitchShape lo; + SwitchShape sparse; + int r; + uint32_t i; + + for (i = 0; i < 6; ++i) mid_vals[i] = (int64_t)(MID - 3 + i); + + memset(&mid, 0, sizeof mid); + mid.name = "u64_midpoint"; + mid.selector_type = i64_ty; + mid.values = mid_vals; + mid.results = mid_res; + mid.ncases = 6; + mid.has_default = 1; + mid.default_result = 999; + mid.hint = KIT_CG_SWITCH_JUMP_TABLE; + + lo = mid; + lo.name = "dense_low"; + lo.values = lo_vals; + lo.results = lo_res; + + sparse = mid; + sparse.name = "sparse"; + sparse.values = sparse_vals; + sparse.results = sparse_res; + sparse.ncases = 4; + + /* Positive control: a dense low switch must produce a table. */ + r = switch_lowers_to_table(c, i32_ty, &lo, /*opt_level=*/0); + EXPECT(r == 1, "dense low switch should lower to a jump table (got %d)", r); + + /* The fix: midpoint-straddling 64-bit cases are dense under the unsigned + * window and must lower to a table rather than a cmp/branch chain. */ + r = switch_lowers_to_table(c, i32_ty, &mid, /*opt_level=*/0); + EXPECT(r == 1, + "u64 midpoint-straddling switch should lower to a jump table (got %d)", + r); + + /* Negative control: a truly sparse switch stays a chain even when the + * frontend forces the jump-table hint. */ + r = switch_lowers_to_table(c, i32_ty, &sparse, /*opt_level=*/0); + EXPECT(r == 0, "sparse switch must not lower to a jump table (got %d)", r); +} + +/* Execute the midpoint switch through the target-independent interpreter. + * At O1 the JUMP_TABLE plan reaches cg_emit_switch_table, which records the + * `idx = sel - vmin` / unsigned-bounds / table-load / indirect-branch IR; the + * interp materializes the table as interp pcs and runs those very ops, so this + * exercises the new unsigned-window index math end to end (not just that a + * table was chosen). Every in-set selector must reach its arm and every + * out-of-set selector must reach default. */ +static void run_table_exec_check(KitCompiler* c, KitCgTypeId i32_ty, + KitCgTypeId i64_ty) { + static const uint64_t MID = (uint64_t)1 << 63; + static int64_t mid_vals[6]; + static const int32_t mid_res[] = {200, 201, 202, 203, 204, 205}; + SwitchShape mid; + KitInterpProgram* pp; + KitObjBuilder* ob = NULL; + KitCg* cg = NULL; + KitCodeOptions opts; + KitInterpFunc* fn; + uint32_t i; + + for (i = 0; i < 6; ++i) mid_vals[i] = (int64_t)(MID - 3 + i); + memset(&mid, 0, sizeof mid); + mid.name = "u64_midpoint_exec"; + mid.selector_type = i64_ty; + mid.values = mid_vals; + mid.results = mid_res; + mid.ncases = 6; + mid.has_default = 1; + mid.default_result = 999; + mid.hint = KIT_CG_SWITCH_JUMP_TABLE; + + pp = kit_interp_program_new(c); + EXPECT(pp != NULL, "exec: interp_program_new failed"); + if (!pp) return; + kit_interp_program_attach(pp, c); + + ob = new_obj(c); + EXPECT(ob != NULL, "exec: obj_new"); + EXPECT(kit_cg_new(c, &cg) == KIT_OK && cg, "exec: cg_new"); + if (ob && cg) { + memset(&opts, 0, sizeof opts); + opts.opt_level = 1; /* interp capture requires the optimizer pass */ + kit_cg_begin(cg, ob, &opts); + EXPECT(emit_switch_fn(c, cg, "mid", i32_ty, &mid) == 0, "exec: build"); + EXPECT(kit_cg_finish(cg, NULL) == KIT_OK, "exec: finish"); + EXPECT(kit_cg_detach(cg) == KIT_OK, "exec: detach"); + + fn = kit_interp_lookup(pp, kit_slice_cstr("mid")); + EXPECT(fn != NULL, "exec: mid not captured"); + if (fn) { + for (i = 0; i < 6; ++i) { + uint64_t args[1] = {(uint64_t)mid_vals[i]}; + int64_t ret = 0; + KitInterpStatus s = kit_interp_call_args(pp, fn, args, 1, &ret); + EXPECT(s == KIT_INTERP_DONE && ret == mid_res[i], + "mid(%#llx): want %d got %lld (status %d)", + (unsigned long long)args[0], mid_res[i], (long long)ret, (int)s); + } + /* Out-of-set selectors stress both bounds edges (just below vmin, just + * above vmax) and a far-away value — all must reach default. */ + { + uint64_t outs[] = {MID - 4, MID + 3, 0, MID + 1000}; + uint32_t no = (uint32_t)(sizeof outs / sizeof outs[0]); + for (i = 0; i < no; ++i) { + uint64_t args[1] = {outs[i]}; + int64_t ret = 0; + KitInterpStatus s = kit_interp_call_args(pp, fn, args, 1, &ret); + EXPECT(s == KIT_INTERP_DONE && ret == 999, + "mid(%#llx): want default 999 got %lld (status %d)", + (unsigned long long)outs[i], (long long)ret, (int)s); + } + } + } + } + + if (cg) kit_cg_free(cg); + if (ob) kit_obj_builder_free(ob); + kit_interp_program_free(pp); +} + /* ---- Entry ----------------------------------------------------------- */ int main(void) { KitTargetSpec target; KitCompiler* c = NULL; - KitCgBuiltinTypes bi; KitCgTypeId i32_ty; KitCgTypeId i64_ty; @@ -264,14 +579,15 @@ int main(void) { return 2; } - bi = kit_cg_builtin_types(c); - i32_ty = bi.id[KIT_CG_BUILTIN_I32]; - i64_ty = bi.id[KIT_CG_BUILTIN_I64]; + i32_ty = kit_cg_type_builtin(c, KIT_CG_BUILTIN_I32); + i64_ty = kit_cg_type_builtin(c, KIT_CG_BUILTIN_I64); EXPECT(i32_ty != KIT_CG_TYPE_NONE, "i32 builtin id is none"); EXPECT(i64_ty != KIT_CG_TYPE_NONE, "i64 builtin id is none"); run_all_shapes(c, i32_ty, i64_ty, /*opt_level=*/0); run_all_shapes(c, i32_ty, i64_ty, /*opt_level=*/1); + run_table_decision_checks(c, i32_ty, i64_ty); + run_table_exec_check(c, i32_ty, i64_ty); kit_compiler_free(c); kit_unit_summary(&g_u, "cg_switch_test"); diff --git a/test/api/cg_type_test.c b/test/api/cg_type_test.c @@ -1342,6 +1342,158 @@ static void exercise_cg_memop_constfold_shape(KitCompiler* c, kit_writer_close(dump); } +static KitCg* begin_void_cg_func(KitCompiler* c, const char* name, + KitObjBuilder** ob_out) { + KitCodeOptions opts; + KitObjBuilder* ob; + KitCg* cg; + KitCgFuncSig sig; + KitCgFuncResult result; + KitCgDecl decl; + KitCgSym sym; + + if (ob_out) *ob_out = NULL; + memset(&opts, 0, sizeof opts); + ob = new_obj(c); + EXPECT(ob != NULL, "%s obj builder allocation failed", name); + if (!ob) return NULL; + cg = NULL; + EXPECT(kit_cg_new(c, &cg) == KIT_OK && cg != NULL, "%s cg allocation failed", + name); + if (!cg) { + kit_obj_builder_free(ob); + return NULL; + } + EXPECT(kit_cg_begin(cg, ob, &opts) == KIT_OK, "%s cg begin failed", name); + + memset(&sig, 0, sizeof sig); + memset(&result, 0, sizeof result); + result.type = kit_cg_type_builtin(c, KIT_CG_BUILTIN_VOID); + sig.result = result; + sig.call_conv = KIT_CG_CC_TARGET_C; + memset(&decl, 0, sizeof decl); + decl.kind = KIT_CG_DECL_FUNC; + decl.linkage_name = kit_sym_intern(c, kit_slice_cstr(name)); + decl.display_name = decl.linkage_name; + decl.type = kit_cg_type_func(c, sig); + decl.sym.bind = KIT_SB_LOCAL; + decl.sym.visibility = KIT_CG_VIS_DEFAULT; + sym = kit_cg_decl(cg, decl); + EXPECT(sym != KIT_CG_SYM_NONE, "%s decl failed", name); + if (sym == KIT_CG_SYM_NONE) { + kit_cg_free(cg); + kit_obj_builder_free(ob); + return NULL; + } + kit_cg_func_begin(cg, sym); + if (ob_out) *ob_out = ob; + return cg; +} + +static void exercise_cg_slot_lang_facts(KitCompiler* c, KitCgTypeId i32_ty) { + static const char tag_a[] = "slot-a"; + static const char tag_b[] = "slot-b"; + KitObjBuilder* ob; + KitCg* cg = begin_void_cg_func(c, "cg_slot_lang_facts", &ob); + KitCgSlotInfo info; + KitCgScope scope; + if (!cg) return; + + kit_cg_push_int(cg, 1, i32_ty); + kit_cg_retag_top(cg, tag_a, 0x0003u); + kit_cg_dup(cg); + info = kit_cg_slot_info(cg, 0); + EXPECT(info.cg_type == i32_ty && info.lang_type == tag_a && + info.lang_flags == 0x0003u, + "dup should copy top slot facts"); + info = kit_cg_slot_info(cg, 1); + EXPECT(info.lang_type == tag_a && info.lang_flags == 0x0003u, + "dup should preserve original slot facts"); + kit_cg_retag_at(cg, 1, tag_b, 0x0010u); + kit_cg_set_top_flags(cg, 0x0004u, 0x0001u); + info = kit_cg_slot_info(cg, 0); + EXPECT(info.lang_type == tag_a && info.lang_flags == 0x0006u, + "set_top_flags should update only top flags"); + info = kit_cg_slot_info(cg, 1); + EXPECT(info.lang_type == tag_b && info.lang_flags == 0x0010u, + "retag_at should update depth-selected slot"); + kit_cg_drop(cg); + kit_cg_drop(cg); + + kit_cg_push_int(cg, 2, i32_ty); + kit_cg_retag_top(cg, tag_a, 0x00ffu); + kit_cg_push_int(cg, 3, i32_ty); + kit_cg_retag_top(cg, tag_b, 0x00eeu); + kit_cg_int_binop(cg, KIT_CG_INT_ADD, 0); + info = kit_cg_slot_info(cg, 0); + EXPECT( + info.cg_type == i32_ty && info.lang_type == NULL && info.lang_flags == 0, + "fresh binop producer should clear slot facts"); + kit_cg_drop(cg); + + scope = kit_cg_block_begin_value(cg, i32_ty); + kit_cg_push_int(cg, 4, i32_ty); + kit_cg_retag_top(cg, tag_b, 0x0033u); + kit_cg_scope_end(cg, scope); + info = kit_cg_slot_info(cg, 0); + EXPECT(info.cg_type == i32_ty && info.lang_type == tag_b && + info.lang_flags == 0x0033u, + "scope result mover should preserve slot facts"); + kit_cg_drop(cg); + + kit_cg_ret(cg); + kit_cg_func_end(cg); + kit_cg_free(cg); + kit_obj_builder_free(ob); +} + +static void exercise_cg_field_at_store_keep(KitCompiler* c, KitCgTypeId i32_ty, + KitCgTypeId rec_ty) { + static const char value_tag[] = "assigned-value"; + KitObjBuilder* ob; + KitCg* cg = begin_void_cg_func(c, "cg_field_at_store_keep", &ob); + KitCgLocalAttrs attrs; + KitCgLocal local; + KitCgMemAccess mem; + KitCgSlotInfo info; + if (!cg) return; + + memset(&attrs, 0, sizeof attrs); + attrs.name = kit_sym_intern(c, KIT_SLICE_LIT("r")); + local = kit_cg_local(cg, rec_ty, attrs); + EXPECT(local != KIT_CG_LOCAL_NONE, "field_at local allocation failed"); + + memset(&mem, 0, sizeof mem); + mem.type = i32_ty; + mem.align = kit_cg_type_align(c, i32_ty); + + kit_cg_push_local(cg, local); + kit_cg_field_at(cg, 4, i32_ty); + kit_cg_push_int(cg, 42, i32_ty); + kit_cg_retag_top(cg, value_tag, 0x0042u); + kit_cg_store_keep(cg, mem); + EXPECT(kit_cg_stack_depth(cg) == 1u, "store_keep should leave one value"); + info = kit_cg_slot_info(cg, 0); + EXPECT(info.cg_type == i32_ty && info.lang_type == value_tag && + info.lang_flags == 0x0042u, + "store_keep should preserve assigned value slot facts"); + kit_cg_drop(cg); + + kit_cg_push_local(cg, local); + kit_cg_field_at(cg, 4, i32_ty); + kit_cg_load(cg, mem); + info = kit_cg_slot_info(cg, 0); + EXPECT( + info.cg_type == i32_ty && info.lang_type == NULL && info.lang_flags == 0, + "load through field_at should produce a freshly untagged value"); + kit_cg_drop(cg); + + kit_cg_ret(cg); + kit_cg_func_end(cg); + kit_cg_free(cg); + kit_obj_builder_free(ob); +} + typedef struct BadStoreCtx { KitCompiler* c; KitCgTypeId i32_ty; @@ -1354,6 +1506,7 @@ static KitCg* cg_begin_bad_store_func(KitCompiler* c, const char* name) { KitObjBuilder* ob; KitCg* cg; KitCgFuncSig sig; + KitCgFuncResult result; KitCgDecl decl; KitCgSym sym; @@ -1371,6 +1524,9 @@ static KitCg* cg_begin_bad_store_func(KitCompiler* c, const char* name) { } memset(&sig, 0, sizeof sig); + memset(&result, 0, sizeof result); + result.type = kit_cg_type_builtin(c, KIT_CG_BUILTIN_VOID); + sig.result = result; sig.call_conv = KIT_CG_CC_TARGET_C; memset(&decl, 0, sizeof decl); decl.kind = KIT_CG_DECL_FUNC; @@ -1381,6 +1537,11 @@ static KitCg* cg_begin_bad_store_func(KitCompiler* c, const char* name) { decl.sym.visibility = KIT_CG_VIS_DEFAULT; sym = kit_cg_decl(cg, decl); EXPECT(sym != KIT_CG_SYM_NONE, "bad-store function decl failed"); + if (sym == KIT_CG_SYM_NONE) { + kit_cg_free(cg); + kit_obj_builder_free(ob); + return NULL; + } kit_cg_func_begin(cg, sym); return cg; } @@ -1562,7 +1723,6 @@ static void exercise_cg_free_does_not_finish(KitCompiler* c, int main(void) { KitTargetSpec target; KitCompiler* c; - KitCgBuiltinTypes bi; KitCgTypeId void_ty; KitCgTypeId i8_ty; KitCgTypeId i32_ty; @@ -1571,12 +1731,21 @@ int main(void) { KitCgTypeId va_list_ty; KitCgTypeId ptr_i32; KitCgTypeId array_i32; + KitCgTypeId huge_array_a; + KitCgTypeId huge_array_b; KitCgFuncParam params[2]; KitCgFuncSig sig; KitCgTypeId fn; KitCgTypeId alias; + KitCgTypeId alias_ptr_i32; + KitCgTypeId ptr_alias_i32; + KitCgTypeInfo info; KitCgTypeId rec; KitCgTypeId rec_ex; + KitCgTypeId rec_decl; + KitCgTypeId rec_ptr; + KitCgTypeId rec_self_ptr; + KitCgTypeId rec_bad; KitCgTypeId enm; KitCgRecordDesc rdesc; KitCgField fields[2]; @@ -1593,19 +1762,25 @@ int main(void) { return 2; } - bi = kit_cg_builtin_types(c); - void_ty = bi.id[KIT_CG_BUILTIN_VOID]; - i8_ty = bi.id[KIT_CG_BUILTIN_I8]; - i32_ty = bi.id[KIT_CG_BUILTIN_I32]; - i64_ty = bi.id[KIT_CG_BUILTIN_I64]; - f64_ty = bi.id[KIT_CG_BUILTIN_F64]; - va_list_ty = bi.id[KIT_CG_BUILTIN_VARARG_STATE]; + void_ty = kit_cg_type_builtin(c, KIT_CG_BUILTIN_VOID); + i8_ty = kit_cg_type_builtin(c, KIT_CG_BUILTIN_I8); + i32_ty = kit_cg_type_builtin(c, KIT_CG_BUILTIN_I32); + i64_ty = kit_cg_type_builtin(c, KIT_CG_BUILTIN_I64); + f64_ty = kit_cg_type_builtin(c, KIT_CG_BUILTIN_F64); + va_list_ty = kit_cg_type_builtin(c, KIT_CG_BUILTIN_VARARG_STATE); EXPECT(void_ty != KIT_CG_TYPE_NONE, "void builtin id is none"); EXPECT(i8_ty != KIT_CG_TYPE_NONE, "i8 builtin id is none"); EXPECT(i32_ty != KIT_CG_TYPE_NONE, "i32 builtin id is none"); EXPECT(f64_ty != KIT_CG_TYPE_NONE, "f64 builtin id is none"); EXPECT(va_list_ty != KIT_CG_TYPE_NONE, "va_list builtin id is none"); EXPECT(void_ty != i32_ty && i32_ty != f64_ty, "builtin ids collide"); + EXPECT(void_ty == 1u, "void builtin should be first direct id"); + EXPECT(i8_ty == (KitCgTypeId)KIT_CG_BUILTIN_I8 + 1u, + "builtin id should be direct enum+1"); + EXPECT(!kit_cg_type_is_void(c, KIT_CG_TYPE_NONE), + "invalid type id must not masquerade as void"); + EXPECT(kit_cg_type_is_void(c, void_ty), "void builtin predicate failed"); + EXPECT(!kit_cg_type_is_sized(c, void_ty), "void should not be sized"); ptr_i32 = kit_cg_type_ptr(c, i32_ty, 0); array_i32 = kit_cg_type_array(c, i32_ty, 4); @@ -1622,14 +1797,36 @@ int main(void) { EXPECT(kit_cg_type_ptr_pointee(c, ptr_i32) == i32_ty, "ptr pointee mismatch"); EXPECT(kit_cg_type_array_elem(c, array_i32) == i32_ty, "array elem mismatch"); EXPECT(kit_cg_type_array_count(c, array_i32) == 4, "array count mismatch"); + huge_array_a = kit_cg_type_array(c, i32_ty, (uint64_t)UINT32_MAX + 1u); + huge_array_b = kit_cg_type_array(c, i32_ty, 1); + EXPECT(huge_array_a == KIT_CG_TYPE_NONE, + "out-of-range array object should be rejected"); + EXPECT(huge_array_b != KIT_CG_TYPE_NONE, + "small array after huge rejection should still work"); alias = kit_cg_type_alias(c, kit_sym_intern(c, KIT_SLICE_LIT("I")), i32_ty); EXPECT(alias != KIT_CG_TYPE_NONE && alias != i32_ty, "alias id should be fresh"); EXPECT(kit_cg_type_kind(c, alias) == KIT_CG_TYPE_ALIAS, "alias kind mismatch"); + EXPECT(kit_cg_type_resolve_alias(c, alias) == i32_ty, + "alias should resolve to base"); EXPECT(kit_cg_type_size(c, alias) == kit_cg_type_size(c, i32_ty), "alias size mismatch"); + EXPECT(kit_cg_type_info(c, alias, &info) == KIT_OK, "alias info failed"); + EXPECT(info.kind == KIT_CG_TYPE_ALIAS && info.storage_id == i32_ty, + "alias info exact/storage mismatch"); + EXPECT(info.layout.valid && info.layout.storage_kind == KIT_CG_STORAGE_INT && + info.layout.scalar_width == 32, + "alias storage info mismatch"); + EXPECT(kit_cg_type_same_storage(c, alias, i32_ty), + "alias should share storage with base"); + alias_ptr_i32 = + kit_cg_type_alias(c, kit_sym_intern(c, KIT_SLICE_LIT("PI")), i32_ty); + ptr_alias_i32 = kit_cg_type_ptr(c, alias_ptr_i32, 0); + EXPECT(ptr_alias_i32 != KIT_CG_TYPE_NONE, "ptr(alias(i32)) failed"); + EXPECT(kit_cg_type_same_storage(c, ptr_alias_i32, ptr_i32), + "ptr(alias(i32)) should share storage with ptr(i32)"); memset(fields, 0, sizeof fields); fields[0].name = kit_sym_intern(c, KIT_SLICE_LIT("a")); @@ -1638,10 +1835,15 @@ int main(void) { fields[1].name = kit_sym_intern(c, KIT_SLICE_LIT("b")); fields[1].type = ptr_i32; fields[1].align_override = 1; - rec = kit_cg_type_record(c, kit_sym_intern(c, KIT_SLICE_LIT("R")), fields, 2); + memset(&rdesc, 0, sizeof rdesc); + rdesc.tag = kit_sym_intern(c, KIT_SLICE_LIT("R")); + rdesc.fields = fields; + rdesc.nfields = 2; + rec = kit_cg_type_record(c, &rdesc); EXPECT(rec != KIT_CG_TYPE_NONE, "record type failed"); - EXPECT(kit_cg_type_record(c, kit_sym_intern(c, KIT_SLICE_LIT("Bad")), fields, - 0) != KIT_CG_TYPE_NONE, + memset(&rdesc, 0, sizeof rdesc); + rdesc.tag = kit_sym_intern(c, KIT_SLICE_LIT("Bad")); + EXPECT(kit_cg_type_record(c, &rdesc) != KIT_CG_TYPE_NONE, "empty record type failed"); EXPECT(kit_cg_type_kind(c, rec) == KIT_CG_TYPE_RECORD, "record kind mismatch"); @@ -1662,8 +1864,11 @@ int main(void) { fields[1].type = i32_ty; fields[1].flags = KIT_CG_FIELD_BITFIELD; fields[1].bit_width = 3; - rec_ex = - kit_cg_type_record(c, kit_sym_intern(c, KIT_SLICE_LIT("BF")), fields, 2); + memset(&rdesc, 0, sizeof rdesc); + rdesc.tag = kit_sym_intern(c, KIT_SLICE_LIT("BF")); + rdesc.fields = fields; + rdesc.nfields = 2; + rec_ex = kit_cg_type_record(c, &rdesc); EXPECT(rec_ex != KIT_CG_TYPE_NONE, "bit-field record type failed"); EXPECT(kit_cg_type_size(c, rec_ex) == 4, "bit-field record size mismatch"); EXPECT(kit_cg_type_record_field(c, rec_ex, 1, &field_out, &field_off) == 0, @@ -1684,7 +1889,7 @@ int main(void) { rdesc.nfields = 2; rdesc.is_union = 1; rdesc.align_override = 16; - rec_ex = kit_cg_type_record_ex(c, &rdesc); + rec_ex = kit_cg_type_record(c, &rdesc); EXPECT(rec_ex != KIT_CG_TYPE_NONE, "record desc type failed"); EXPECT(kit_cg_type_size(c, rec_ex) == 16, "record desc size mismatch"); EXPECT(kit_cg_type_align(c, rec_ex) == 16, "record desc align mismatch"); @@ -1692,6 +1897,52 @@ int main(void) { "record desc field query failed"); EXPECT(field_off == 0, "union field offset mismatch"); + rec_decl = + kit_cg_type_record_decl(c, kit_sym_intern(c, KIT_SLICE_LIT("Node")), 0); + EXPECT(rec_decl != KIT_CG_TYPE_NONE, "record decl failed"); + EXPECT(kit_cg_type_kind(c, rec_decl) == KIT_CG_TYPE_RECORD, + "record decl kind mismatch"); + EXPECT(!kit_cg_type_is_complete(c, rec_decl), + "fresh record decl should be incomplete"); + EXPECT(!kit_cg_type_is_sized(c, rec_decl), + "fresh record decl should be unsized"); + EXPECT(kit_cg_type_record_field(c, rec_decl, 0, NULL, NULL) == KIT_NOT_FOUND, + "incomplete record should not expose fields"); + rec_ptr = kit_cg_type_ptr(c, rec_decl, 0); + EXPECT(rec_ptr != KIT_CG_TYPE_NONE, + "pointer to incomplete record should be legal"); + EXPECT(kit_cg_type_array(c, rec_decl, 1) == KIT_CG_TYPE_NONE, + "array of incomplete record should fail"); + memset(fields, 0, sizeof fields); + fields[0].name = kit_sym_intern(c, KIT_SLICE_LIT("next")); + fields[0].type = rec_ptr; + memset(&rdesc, 0, sizeof rdesc); + rdesc.tag = kit_cg_type_record_tag(c, rec_decl); + rdesc.fields = fields; + rdesc.nfields = 1; + EXPECT(kit_cg_type_record_complete(c, rec_decl, &rdesc) == KIT_OK, + "record completion with self pointer failed"); + EXPECT(kit_cg_type_is_complete(c, rec_decl), + "completed record should be complete"); + EXPECT(kit_cg_type_is_sized(c, rec_decl), "completed record should be sized"); + EXPECT(kit_cg_type_record_complete(c, rec_decl, &rdesc) == KIT_INVALID, + "record completion should be one-shot"); + + rec_bad = kit_cg_type_record_decl( + c, kit_sym_intern(c, KIT_SLICE_LIT("ByValueSelf")), 0); + memset(fields, 0, sizeof fields); + fields[0].name = kit_sym_intern(c, KIT_SLICE_LIT("self")); + fields[0].type = rec_bad; + memset(&rdesc, 0, sizeof rdesc); + rdesc.tag = kit_cg_type_record_tag(c, rec_bad); + rdesc.fields = fields; + rdesc.nfields = 1; + EXPECT(kit_cg_type_record_complete(c, rec_bad, &rdesc) == KIT_INVALID, + "recursive by-value record should fail"); + rec_self_ptr = kit_cg_type_ptr(c, rec_bad, 0); + EXPECT(rec_self_ptr != KIT_CG_TYPE_NONE, + "pointer to failed incomplete record should still be legal"); + vals[0].name = kit_sym_intern(c, KIT_SLICE_LIT("A")); vals[0].value = 1; vals[1].name = kit_sym_intern(c, KIT_SLICE_LIT("B")); @@ -1720,6 +1971,17 @@ int main(void) { EXPECT(kit_cg_type_func_nparams(c, fn) == 2, "function param count mismatch"); EXPECT(kit_cg_type_func_param(c, fn, 1).type == ptr_i32, "function param mismatch"); + memset(&sig, 0, sizeof sig); + sig.result.type = void_ty; + fn = kit_cg_type_func(c, sig); + EXPECT(fn != KIT_CG_TYPE_NONE, "void function type failed"); + EXPECT(kit_cg_type_func_result(c, fn).type == void_ty, + "void function result should use void builtin"); + EXPECT(!kit_cg_func_result_has_value(c, kit_cg_type_func_result(c, fn)), + "void function should not have a value result"); + memset(&sig, 0, sizeof sig); + EXPECT(kit_cg_type_func(c, sig) == KIT_CG_TYPE_NONE, + "function result NONE should be rejected"); sig.nparams = 70000; EXPECT(kit_cg_type_func(c, sig) == KIT_CG_TYPE_NONE, "oversized function param list should fail"); @@ -1734,6 +1996,8 @@ int main(void) { exercise_cg_literal_folds(c, i32_ty); exercise_cg_constfold_phases(c, i32_ty, i8_ty); exercise_cg_memop_constfold_shape(c, i32_ty, i64_ty, ptr_i32); + exercise_cg_slot_lang_facts(c, i32_ty); + exercise_cg_field_at_store_keep(c, i32_ty, rec); exercise_cg_memory_mismatch_diags(c, i32_ty, i64_ty, rec); exercise_compile_session_two_deltas(c); exercise_cg_begin_end_two_objects(c); diff --git a/test/arch/inline_public_test.h b/test/arch/inline_public_test.h @@ -46,8 +46,9 @@ static inline KitTargetSpec it_target(KitArchKind arch) { static inline KitStatus it_emit_func(KitCompiler* c, void* user) { InlineEmit* emit = (InlineEmit*)user; KitCg* cg = NULL; - KitCgBuiltinTypes bi; + KitCgTypeId i64 = kit_cg_type_builtin(c, KIT_CG_BUILTIN_I64); KitCgFuncSig sig; + KitCgFuncResult result; KitCgDecl decl; KitCgSym sym; KitCodeOptions opts; @@ -57,8 +58,10 @@ static inline KitStatus it_emit_func(KitCompiler* c, void* user) { memset(&opts, 0, sizeof opts); if (kit_cg_begin(cg, emit->ob, &opts) != KIT_OK) return KIT_ERR; - bi = kit_cg_builtin_types(c); memset(&sig, 0, sizeof sig); + memset(&result, 0, sizeof result); + result.type = kit_cg_type_builtin(c, KIT_CG_BUILTIN_VOID); + sig.result = result; sig.call_conv = KIT_CG_CC_TARGET_C; memset(&decl, 0, sizeof decl); @@ -72,7 +75,7 @@ static inline KitStatus it_emit_func(KitCompiler* c, void* user) { if (sym == KIT_CG_SYM_NONE) return KIT_ERR; kit_cg_func_begin(cg, sym); - emit->body(c, cg, bi.id[KIT_CG_BUILTIN_I64]); + emit->body(c, cg, i64); kit_cg_ret(cg); kit_cg_func_end(cg); if (kit_cg_finish(cg, NULL) != KIT_OK) return KIT_ERR; diff --git a/test/cg/ir_recorder_test.c b/test/cg/ir_recorder_test.c @@ -24,7 +24,6 @@ typedef struct TestCtx { static void tc_init(TestCtx* tc) { KitTargetSpec target; - KitCgBuiltinTypes b; memset(tc, 0, sizeof *tc); target = kit_unit_target(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF); if (kit_unit_compiler_new(&g_u, target, (KitCompiler**)&tc->c) != KIT_OK || @@ -32,9 +31,9 @@ static void tc_init(TestCtx* tc) { fprintf(stderr, "fatal: compiler allocation failed\n"); abort(); } - b = kit_cg_builtin_types(tc->c); - tc->i32 = b.id[KIT_CG_BUILTIN_I32]; - tc->ptr = kit_cg_type_ptr(tc->c, b.id[KIT_CG_BUILTIN_VOID], 0); + tc->i32 = kit_cg_type_builtin(tc->c, KIT_CG_BUILTIN_I32); + tc->ptr = kit_cg_type_ptr(tc->c, + kit_cg_type_builtin(tc->c, KIT_CG_BUILTIN_VOID), 0); } static void tc_fini(TestCtx* tc) { diff --git a/test/cg/native_direct_target_test.c b/test/cg/native_direct_target_test.c @@ -23,7 +23,6 @@ typedef struct TestCtx { static void tc_init(TestCtx* tc) { KitTargetSpec target; - KitCgBuiltinTypes b; memset(tc, 0, sizeof *tc); target = kit_unit_target(KIT_ARCH_X86_64, KIT_OS_LINUX, KIT_OBJ_ELF); if (kit_unit_compiler_new(&g_u, target, (KitCompiler**)&tc->c) != KIT_OK || @@ -31,9 +30,9 @@ static void tc_init(TestCtx* tc) { fprintf(stderr, "fatal: compiler allocation failed\n"); abort(); } - b = kit_cg_builtin_types(tc->c); - tc->i32 = b.id[KIT_CG_BUILTIN_I32]; - tc->ptr = kit_cg_type_ptr(tc->c, b.id[KIT_CG_BUILTIN_VOID], 0); + tc->i32 = kit_cg_type_builtin(tc->c, KIT_CG_BUILTIN_I32); + tc->ptr = kit_cg_type_ptr(tc->c, + kit_cg_type_builtin(tc->c, KIT_CG_BUILTIN_VOID), 0); } static void tc_fini(TestCtx* tc) { @@ -57,6 +56,7 @@ typedef enum MockEventKind { EV_PLAN_CALL, EV_EMIT_CALL, EV_PLAN_RET, + EV_MOVE, EV_RET, } MockEventKind; @@ -189,6 +189,10 @@ static void mock_binop(NativeTarget* t, BinOp op, NativeLoc dst, NativeLoc a, ev(mock_of(t), EV_BINOP, op, dst.v.reg, (a.v.reg << 16) | b.v.reg); } +static void mock_move_rr(NativeTarget* t, NativeRegLoc dst, NativeRegLoc src) { + ev(mock_of(t), EV_MOVE, dst.v.reg, src.v.reg, dst.type); +} + static void mock_emit_call(NativeTarget* t, const NativeCallPlan* plan) { EXPECT(plan->callee.kind == NATIVE_LOC_REG, "frame callee should be materialized for call"); @@ -234,6 +238,7 @@ static void mock_native_init(MockNative* m, Compiler* c) { m->base.store = mock_store; m->base.load_imm = mock_load_imm; m->base.binop = mock_binop; + m->base.move_rr = mock_move_rr; m->base.emit_call = mock_emit_call; m->base.plan_ret = mock_plan_ret; m->base.ret = mock_ret; diff --git a/test/cg/strength_reduce_test.c b/test/cg/strength_reduce_test.c @@ -43,7 +43,6 @@ typedef struct EmitCtx { static KitStatus emit_binop_fn(KitCompiler* c, void* user) { EmitCtx* ctx = (EmitCtx*)user; KitCg* cg = NULL; - KitCgBuiltinTypes bi; KitCgTypeId i64_ty; KitCgFuncParam param_desc; KitCgFuncResult sig_result; @@ -60,8 +59,7 @@ static KitStatus emit_binop_fn(KitCompiler* c, void* user) { opts.opt_level = 0; /* the -O0 peephole is the subject under test */ if (kit_cg_begin(cg, ctx->ob, &opts) != KIT_OK) return KIT_ERR; - bi = kit_cg_builtin_types(c); - i64_ty = bi.id[KIT_CG_BUILTIN_I64]; + i64_ty = kit_cg_type_builtin(c, KIT_CG_BUILTIN_I64); memset(&param_desc, 0, sizeof param_desc); param_desc.type = i64_ty; diff --git a/test/interp/interp_smoke_test.c b/test/interp/interp_smoke_test.c @@ -39,7 +39,6 @@ typedef struct TestCtx { static void tc_init(TestCtx* tc) { KitTargetSpec target; - KitCgBuiltinTypes b; memset(tc, 0, sizeof *tc); target = kit_unit_target(KIT_ARCH_ARM_64, KIT_OS_MACOS, KIT_OBJ_MACHO); if (kit_unit_compiler_new(&g_u, target, (KitCompiler**)&tc->c) != KIT_OK || @@ -47,10 +46,9 @@ static void tc_init(TestCtx* tc) { fprintf(stderr, "fatal: compiler allocation failed\n"); abort(); } - b = kit_cg_builtin_types(tc->c); - tc->i32 = b.id[KIT_CG_BUILTIN_I32]; - tc->i64 = b.id[KIT_CG_BUILTIN_I64]; - tc->f64 = b.id[KIT_CG_BUILTIN_F64]; + tc->i32 = kit_cg_type_builtin(tc->c, KIT_CG_BUILTIN_I32); + tc->i64 = kit_cg_type_builtin(tc->c, KIT_CG_BUILTIN_I64); + tc->f64 = kit_cg_type_builtin(tc->c, KIT_CG_BUILTIN_F64); } static void tc_fini(TestCtx* tc) { diff --git a/test/opt/cg_ir_lower_test.c b/test/opt/cg_ir_lower_test.c @@ -27,7 +27,6 @@ typedef struct TestCtx { static void tc_init(TestCtx* tc) { KitTargetSpec target; - KitCgBuiltinTypes b; memset(tc, 0, sizeof *tc); target = kit_unit_target(KIT_ARCH_ARM_64, KIT_OS_MACOS, KIT_OBJ_MACHO); if (kit_unit_compiler_new(&g_u, target, (KitCompiler**)&tc->c) != KIT_OK || @@ -35,8 +34,7 @@ static void tc_init(TestCtx* tc) { fprintf(stderr, "fatal: compiler allocation failed\n"); abort(); } - b = kit_cg_builtin_types(tc->c); - tc->i32 = b.id[KIT_CG_BUILTIN_I32]; + tc->i32 = kit_cg_type_builtin(tc->c, KIT_CG_BUILTIN_I32); } static void tc_fini(TestCtx* tc) { diff --git a/test/opt/tiny_inline_test.c b/test/opt/tiny_inline_test.c @@ -39,7 +39,6 @@ typedef struct TestCtx { static void tc_init(TestCtx* tc) { KitTargetSpec target; - KitCgBuiltinTypes b; KitCgFuncSig sig; KitCgFuncParam params[1]; memset(tc, 0, sizeof *tc); @@ -49,9 +48,9 @@ static void tc_init(TestCtx* tc) { fprintf(stderr, "fatal: compiler allocation failed\n"); abort(); } - b = kit_cg_builtin_types(tc->c); - tc->i32 = b.id[KIT_CG_BUILTIN_I32]; - tc->ptr = kit_cg_type_ptr(tc->c, b.id[KIT_CG_BUILTIN_VOID], 0); + tc->i32 = kit_cg_type_builtin(tc->c, KIT_CG_BUILTIN_I32); + tc->ptr = kit_cg_type_ptr(tc->c, + kit_cg_type_builtin(tc->c, KIT_CG_BUILTIN_VOID), 0); memset(&sig, 0, sizeof sig); memset(params, 0, sizeof params); params[0].type = tc->i32;