Roadmap search
Versions, deliverables, workstreams, tasks, and pages
Dynamic types and mapping (v0.3 scope)
src/content/docs/platform-spec/compiler/codegen-and-ir/dynamic-types-and-mapping.mdx
import SpecPageHeader from '@beskid/beskid-ui/platform-spec/SpecPageHeader.astro'; import SpecSection from '@beskid/beskid-ui/platform-spec/SpecSection.astro';
<SpecPageHeader status="Standard" ownerName="Piotr Mikstacki" ownerEmail="pmikstacki@cybernomad.it" submitterName="Piotr Mikstacki" submitterEmail="pmikstacki@cybernomad.it" /> <SpecSection title="v0.3 scope" id="scope"> This article specifies the v0.3 **dynamic typing** surface: a GC-visible dynamic cell, compile-time object-to-object mapping for eligible `[Serialize]` shapes, and a runtime fallback when static shape information is unavailable at compile time.Goals:
- Dynamic type representation in HIR/CLIF for schema-evolving payloads without language-level reflection.
- AOT object-to-object mapping for known serializable types at compile time.
- Runtime mapping for dynamic values when static shape is unavailable.
- Eligibility enforced by Serialization Mod analyzers — mapping applies only to types classified as serializable; codegen MUST consult that signal and MUST NOT bypass it. </SpecSection>
Cell header layout (normative):
| Field | Width | Semantics |
|---|---|---|
shape_id | u32 | Registered object shape tag used by mapping tables |
flags | u32 | Reserved (zero in v0.3) |
payload | pointer | Arena-allocated static object or inline buffer; traced when non-null |
Allocation: cell headers and mapping target objects MUST be allocated through the runtime arena builtins (dynamic_cell_create, dynamic_object_alloc) to honour Phase A single-mutator rules. Codegen MUST NOT introduce parallel allocators for dynamic cells.
GC tracing: non-null payload pointers are treated as heap references and traced by the runtime collector; null payloads are ignored.
Lowering entry points: cell creation, static wrap, checked cast, AOT map, and fallback map are emitted as imported runtime builtins from compiler/crates/beskid_codegen/src/lowering/expressions/dynamic.rs. Type mapping for the dynamic alias is centralized in compiler/crates/beskid_codegen/src/lowering/types.rs.
</SpecSection>
Eligibility (normative):
- Delegated to Serialization Mod analyzers on the analysis surface; until a dedicated analyzer signal is threaded into
TypeResult, codegen applies the same structural rules as the[Serialize]contract: only structs whose fields are primitives or nested eligible structs, with deterministic declaration-order field lists. - Ineligible pairs MUST fail lowering with structured diagnostic
E2013(IneligibleSerializeMapping) — never silent mapping or runtime panic from codegen.
Mapping kinds (v0.3):
- Identity mapping: source and destination structs share field names, order, and types; field steps copy memory ranges in declaration order.
- Transform mapping: same eligibility gate; v0.3 registers explicit offset/size steps in the runtime shape table (future Serialization Mod generators populate the table).
Shape identifiers: stable per resolved item id (FNV-1a hash in v0.3) so AOT tables and runtime registration agree across codegen and tests.
Implementation: compiler/crates/beskid_codegen/src/lowering/expressions/mapping.rs (lowering) and compiler/crates/beskid_codegen/src/lowering/expressions/serialize.rs (eligibility).
</SpecSection>
Algorithm:
- Reject null cell payload →
DYNAMIC_ERR_NULL_PAYLOAD. - Require registered source and destination shapes →
DYNAMIC_ERR_UNKNOWN_SRC_SHAPE/DYNAMIC_ERR_UNKNOWN_DST_SHAPE. - Look up field steps for
(cell.shape_id, dst_shape)in the runtime mapping table. - On missing or incompatible mapping →
DYNAMIC_ERR_INCOMPATIBLE(E-dynamic-map-001); this path is deterministic and structured — no implicit reflection or best-effort coercion.
Fallback shares the same field-step table as the AOT path (compiler/crates/beskid_runtime/src/dynamic/table.rs, compiler/crates/beskid_runtime/src/dynamic/fallback.rs).
</SpecSection>
Cross-reference: Compiler Mod SDK for analyzer contracts; Serialization packages for serializable type constraints. </SpecSection>
<SpecSection title="Implementation anchors" id="implementation-anchors"> - CLIF lowering: `compiler/crates/beskid_codegen/src/lowering/expressions/dynamic.rs` - AOT mapping + eligibility: `compiler/crates/beskid_codegen/src/lowering/expressions/mapping.rs`, `compiler/crates/beskid_codegen/src/lowering/expressions/serialize.rs` - Dynamic CLIF type mapping: `compiler/crates/beskid_codegen/src/lowering/types.rs` - Codegen diagnostic **`E2013`**: `compiler/crates/beskid_codegen/src/diagnostics.rs` - Runtime cell + tables + fallback: `compiler/crates/beskid_runtime/src/dynamic/` - C ABI builtins: `compiler/crates/beskid_runtime/src/builtins/dynamic.rs` - ABI symbol registration: `compiler/crates/beskid_abi/src/symbols.rs`, `compiler/crates/beskid_abi/src/builtins.rs` - Integration tests: `compiler/crates/beskid_tests/src/codegen/dynamic_types/`, `compiler/crates/beskid_tests/src/runtime/dynamic/` </SpecSection>Decisions
-
Pointer-to-cell CLIF representation.
dynamiclowers to a single pointer (I64on 64-bit System V) rather than a split(shape, payload)pair in CLIF, keeping call conventions aligned with other heap objects and allowing checked casts to inspect the header at runtime. -
Arena-only allocation. Dynamic cells and mapping outputs use runtime arena builtins to preserve Phase A single-mutator invariants; embedders must not allocate parallel cell pools outside the Beskid heap.
-
Shared shape/mapping table for AOT and fallback. One registration API (
register_shape,register_mapping) serves compile-time-known mappings and runtime fallback probes, avoiding divergent copy semantics. -
Deterministic incompatibility errors. Missing mappings return stable integer status codes surfaced as
E-dynamic-map-001/DYNAMIC_ERR_INCOMPATIBLE; codegen ineligibility usesE2013so failures are diagnosable without undefined behaviour. -
Structural eligibility stand-in. Until Serialization Mod analyzers attach a dedicated bit on
TypeResult, codegen applies structural[Serialize]rules and refuses ineligible pairs at lowering time — never at runtime via reflection. -
FNV-1a shape ids from resolved items. v0.3 derives
shape_idfromItemIdbytes so tests and AOT lowering agree without a separate shape registry in the analysis crate; mod-generated tables will replace this hash in a later track.