Skip to content
Beskid Platform specification

Beskid

Jump to a Beskid service

Beskid

Jump to a Beskid service

Dynamic types and mapping (v0.3 scope)

Platform spec node

Dynamic types and mapping (v0.3 scope)

Spec standingStandard

Owner
Piotr Mikstacki
Submitter
Piotr Mikstacki

v0.3 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:

  1. Dynamic type representation in HIR/CLIF for schema-evolving payloads without language-level reflection.
  2. AOT object-to-object mapping for known serializable types at compile time.
  3. Runtime mapping for dynamic values when static shape is unavailable.
  4. Eligibility enforced by Serialization Mod analyzers — mapping applies only to types classified as serializable; codegen MUST consult that signal and MUST NOT bypass it.

HIR/CLIF representation

The dynamic surface (named alias until a language primitive lands) lowers to a pointer-sized CLIF value referencing a runtime DynamicCell header allocated through the runtime arena.

Cell header layout (normative):

FieldWidthSemantics
shape_idu32Registered object shape tag used by mapping tables
flagsu32Reserved (zero in v0.3)
payloadpointerArena-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.

AOT object-to-object mapping

When both source and destination static shapes are known and mod-eligible, codegen emits a direct call to dynamic_map_aot(src_shape, dst_shape, src_ptr, dst_out) after eligibility checks.

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).

Runtime fallback

When compile-time shape information is unavailable (value already wrapped in a DynamicCell), generated code calls dynamic_map_fallback(cell, dst_shape, dst_out).

Algorithm:

  1. Reject null cell payload → DYNAMIC_ERR_NULL_PAYLOAD.
  2. Require registered source and destination shapes → DYNAMIC_ERR_UNKNOWN_SRC_SHAPE / DYNAMIC_ERR_UNKNOWN_DST_SHAPE.
  3. Look up field steps for (cell.shape_id, dst_shape) in the runtime mapping table.
  4. 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).

Eligibility and Serialization Mods

Mapping MUST NOT bypass Serialization Mod analyzer classification. Codegen reads the analyzer signal on the typed program (structural stand-in until mod host wiring lands) via require_mapping_eligible in serialize.rs.

Cross-reference: Compiler Mod SDK for analyzer contracts; Serialization packages for serializable type constraints.

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/
  1. Pointer-to-cell CLIF representation. dynamic lowers to a single pointer (I64 on 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.

  2. 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.

  3. 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.

  4. Deterministic incompatibility errors. Missing mappings return stable integer status codes surfaced as E-dynamic-map-001 / DYNAMIC_ERR_INCOMPATIBLE; codegen ineligibility uses E2013 so failures are diagnosable without undefined behaviour.

  5. 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.

  6. FNV-1a shape ids from resolved items. v0.3 derives shape_id from ItemId bytes 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.