pub struct CallIndirectGuards {
pub tables: Vec<TableGuards>,
pub type_ids_byte_offset: Option<u32>,
pub type_ids_image: Vec<u32>,
pub type_class_ids: Vec<u32>,
}Expand description
#642/#650: everything the call_indirect lowering needs to emit its
guards — computed once per module by
DecodedModule::call_indirect_guards and threaded to the instruction
selector via CompileConfig.
§The R11 multi-table layout contract (#650)
The runtime/harness links every funcref table as ONE contiguous region of
raw 4-byte code pointers based at R11, in declaration order (imported
tables first): table 0 at R11 + 0, table N at
R11 + sum(size(0..N)) * 4. The offsets are compile-time constants
because tables are provably fixed-size (table.grow/table.set are
unsupported ops whose functions loud-skip at decode — #642). A
single-table module degenerates to the pre-#650 contract (table 0 at
R11, offset 0) BY CONSTRUCTION, keeping its emitted bytes identical.
WASM Core §4.4.8 requires call_indirect to trap when index >= table.size and when the callee’s type does not match the instruction’s
expected type. The region stores no size fields and no type ids, so, per
table:
-
the BOUNDS check is emitted at runtime against THAT table’s compile-time
table_sizeimmediate (sound: fixed-size, see above), and -
the TYPE check is discharged at COMPILE time: for expected type
t,tables[n].type_reject[t]isNoneonly when every INITIALIZED slot of tablenverifiably holds a function whose signature structurally equals typet(the closed-world property — no runtime mismatch is then possible). Otherwise it holds the reason, and the lowering declines LOUDLY rather than emit an unchecked indirect branch, and -
a NULL (uninitialized) slot traps at RUNTIME (#664): the layout contract requires the runtime/harness to link every uninitialized slot as a ZERO word (null funcref has no code address; 0 is never a valid function pointer in the region), and when
has_null_slotsis set the dispatch emits a null check on the loaded pointer (CMP #0→ trap) between the bounds guard and the indirect branch. A fully-initialized table (has_null_slots == false) keeps the pre-#664 dispatch bytes identical BY CONSTRUCTION, and -
a HETEROGENEOUS table (mixed signatures — the closed-world property cannot hold for ANY expected type) is dispatched through a runtime type check against the type-id sidecar (#676): a parallel
u32array the layout contract places atR11 + type_ids_byte_offset(immediately after the LAST table’s pointer words, i.e. atsum(size(0..num_tables)) * 4), mirroring the pointer region slot for slot — table N’s type-ids start atR11 + type_ids_byte_offset + base_byte_offset(N). Each word is the slot’s STRUCTURAL signature class id: structurally-equal function types share one dense id (1-based, first-occurrence order over the type section); id 0 is reserved for null slots, so the type compare (expected ids are always >= 1) subsumes the #664 null trap in the sameCMP. The dispatch loadstype_id[idx], compares it against the expected type’s class id (compile-time immediate) and traps (UDF) on mismatch — WASM Core §4.4.8’s runtime type check — before the pointer load andBLX. The sidecar words are emitted into the relocatable object as the.synth.table_type_idssection (non-ALLOC metadata, like.meld_import_table): the runtime/harness that links the pointer region copies them toR11 + type_ids_byte_offsetverbatim — it never re-derives ids. A module with NO heterogeneous table emits no sidecar and no runtime type check anywhere: homogeneous dispatch bytes stay identical BY CONSTRUCTION (the #650 offset-0 / #664null_check: falsetrick).pre-#664 dispatch bytes identical BY CONSTRUCTION.
§Companion: the self-contained SRAM layout contract (#687)
The R11 register above is ALSO the linear-memory base register, whose
placement inside SRAM is governed by the self-contained image’s stack
layout: --stack-layout=high (default) keeps linmem at the SRAM start
with the stack growing down from the top; --stack-layout=low reserves
the stack at the SRAM BOTTOM and shifts linmem/globals (and the optimized
path’s absolute 0x2000_0100 base) up by the stack size, so an overflow
BusFaults below SRAM instead of silently corrupting them. The full layout
tables live on build_multi_func_cortex_m_elf in synth-cli (the builder
that owns the addresses). Relocatable/host-linked objects are NOT covered
— their linker script owns the layout, and the flag is refused there.
Fields§
§tables: Vec<TableGuards>Per-table guard inputs, indexed by table index (imports first). The
default (empty — no module context) DECLINES every call_indirect.
type_ids_byte_offset: Option<u32>#676: byte offset of the type-id sidecar within the R11 region — the
total pointer-region size, sum(size(0..num_tables)) * 4. Some
only when a sidecar exists: at least one table is heterogeneous
(see TableGuards::runtime_type_check) AND every table’s size is
compile-time known (otherwise the sidecar base is not a constant and
heterogeneous dispatches keep declining). None = no sidecar.
type_ids_image: Vec<u32>#676: the sidecar image — one u32 structural class id per slot
across ALL tables in region order (0 = null slot). Emitted into the
object as .synth.table_type_ids; empty exactly when
type_ids_byte_offset is None. A table whose image is not
statically known contributes ZERO words (it declines at the
lowering, and 0 never equals an expected class id, so even a rogue
dispatch would trap, not branch).
type_class_ids: Vec<u32>#676: per module type index, that type’s structural class id
(1-based, dense; structurally-equal duplicate types share an id).
The expected-type immediate the dispatch compares against. Empty
when type_ids_byte_offset is None (no sidecar — never consulted).
Implementations§
Trait Implementations§
Source§impl Clone for CallIndirectGuards
impl Clone for CallIndirectGuards
Source§fn clone(&self) -> CallIndirectGuards
fn clone(&self) -> CallIndirectGuards
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for CallIndirectGuards
impl Debug for CallIndirectGuards
Source§impl Default for CallIndirectGuards
impl Default for CallIndirectGuards
Source§fn default() -> CallIndirectGuards
fn default() -> CallIndirectGuards
impl Eq for CallIndirectGuards
Source§impl PartialEq for CallIndirectGuards
impl PartialEq for CallIndirectGuards
impl StructuralPartialEq for CallIndirectGuards
Auto Trait Implementations§
impl Freeze for CallIndirectGuards
impl RefUnwindSafe for CallIndirectGuards
impl Send for CallIndirectGuards
impl Sync for CallIndirectGuards
impl Unpin for CallIndirectGuards
impl UnsafeUnpin for CallIndirectGuards
impl UnwindSafe for CallIndirectGuards
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.