Skip to content

Architecture: Meld Platform — WRT as Embedded Host Runtime with Embassy HAL Bridge #34

Description

@avrabe

Summary

This issue defines the architecture for integrating Synth into the Meld Platform — a unified system where Loom optimizes, Synth compiles, and WRT/Meld provides the runtime, component composition, and hardware bridge for safety-critical embedded deployments.

The core insight: WRT already implements all host component functionality (WASI dispatch, component linking, capability enforcement, memory management). Instead of reimplementing this for bare metal, we cross-compile WRT as a no_std static library and bridge its HostImportHandler to Embassy's embedded-hal traits. Synth's job becomes narrower: compile application components to native ARM, generating calls into the Meld runtime for imports.

The Meld Platform

Loom optimizes → Synth compiles → Meld deploys

┌──────────────────────────────────────────────────────┐
│  Meld Platform                                        │
│                                                       │
│  ┌─────────┐    ┌─────────┐    ┌──────────────────┐  │
│  │  Loom   │───▶│  Synth  │───▶│  Meld Runtime    │  │
│  │ Optimize│    │ Compile │    │  (formerly WRT)  │  │
│  │ WASM→   │    │ WASM→   │    │  Deploy + Execute│  │
│  │ WASM    │    │ ARM     │    │  + HW Bridge     │  │
│  └─────────┘    └─────────┘    └──────────────────┘  │
│                                                       │
│  Formal verification at every stage:                  │
│  Loom: Z3+Coq  |  Synth: Z3  |  Meld: Kani+ASIL-D  │
└──────────────────────────────────────────────────────┘

Architecture

Deployment Stack

┌───────────────────────────────────────────────────┐
│  Application Components                            │
│  ┌─────────────┐  ┌─────────────┐  ┌───────────┐ │
│  │ Component A  │  │ Component B  │  │ Comp C    │ │
│  │ Synth→ARM   │  │ WASM in WRT │  │ Synth→ARM │ │
│  └──────┬──────┘  └──────┬──────┘  └─────┬─────┘ │
│         │  Component Model Interface      │        │
│  ┌──────┴─────────────────┴───────────────┴─────┐ │
│  │  Meld Runtime (WRT, cross-compiled no_std)    │ │
│  │  • ComponentLinker — resolve imports/exports  │ │
│  │  • WasiDispatcher — route WASI calls          │ │
│  │  • CapabilityEngine — enforce access control  │ │
│  │  • StacklessEngine — interpret non-AOT modules│ │
│  │  • safe_managed_alloc! — static memory budgets│ │
│  ├───────────────────────────────────────────────┤ │
│  │  meld-platform-embassy (thin bridge, ~500 LOC)│ │
│  │  wasi:io/streams   → embedded_io::{Read,Write}│ │
│  │  wasi:clocks/mono  → embassy_time::Instant    │ │
│  │  wasi:random       → RngCore peripheral       │ │
│  │  wasi:cli/stdout   → UART Write               │ │
│  ├───────────────────────────────────────────────┤ │
│  │  Embassy HAL (embassy-stm32/nrf/rp)           │ │
│  │  UART, SPI, I2C, Timers, GPIO, DMA, RNG      │ │
│  ├───────────────────────────────────────────────┤ │
│  │  Hardware (Cortex-M / RISC-V)                 │ │
│  └───────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────┘

Mixed-Mode Execution

The Component Model doesn't care how an export is implemented. This enables:

  • Hot paths: Synth-compiled to native ARM (~85% native performance)
  • Flexible components: WASM interpreted by Meld's StacklessEngine (updatable, sandboxed)
  • Host interfaces: Embassy bridge providing WASI via real hardware

All three execution modes are wired together by Meld's ComponentLinker using the same WIT interface contracts.

Synth's Narrowed Responsibility

With Meld handling host dispatch, Synth only needs to:

  1. Compile application component functions to native ARM
  2. Generate import stubs that call into Meld's component linker
  3. Produce relocatable ELF objects that link with the Meld static library

Synth does NOT need to:

  • Implement WASI (Meld handles this)
  • Resolve host functions (Meld's ComponentLinker does this)
  • Manage memory layout for host access (Meld's MemoryAccessor handles this)
  • Implement capability checking (Meld's CapabilityEngine handles this)

Changes Required in Synth

1. Preserve Import Metadata

File: synth-core/src/wasm_decoder.rs

Currently import metadata (module name, function name, type) is discarded during parsing. Store it in DecodedModule:

pub struct DecodedModule {
    pub functions: Vec<DecodedFunction>,
    pub imports: Vec<ImportEntry>,  // NEW
    // ...
}

pub struct ImportEntry {
    pub module: String,      // e.g., "wasi:cli/stdout"
    pub name: String,        // e.g., "write"
    pub kind: ImportKind,
    pub type_index: u32,     // function signature
}

2. Generate Meld-Compatible Import Stubs

File: synth-synthesis/src/instruction_selector.rs

For imported functions, generate a call to meld_dispatch_import instead of a direct BL:

// For import calls: set up arguments per AAPCS, then call Meld dispatcher
Call(func_idx) if func_idx < num_imports => {
    // R0 = import_index (compile-time constant)
    // R1 = args pointer (on stack)
    // R2 = memory base (R10)
    vec![
        ArmOp::MovImm { rd: Reg::R0, imm: func_idx },
        ArmOp::Bl { label: "__meld_dispatch_import".to_string() },
    ]
}

3. ELF Relocation Support

File: synth-backend/src/elf_builder.rs

Generate proper relocations for calls to Meld runtime:

  • Add .rel.text section with R_ARM_CALL entries
  • Mark __meld_dispatch_import as STB_GLOBAL / SHN_UNDEF
  • Mark all Synth-compiled exports as STB_GLOBAL with addresses
  • Support static linking with arm-none-eabi-ld

4. Define the Synth↔Meld ABI

A stable calling convention between Synth-compiled code and Meld runtime:

__meld_dispatch_import(import_index: u32, args: *const Value, num_args: u32) -> Value
    R0 = import_index  (Synth assigns monotonic indices per component)
    R1 = args pointer  (stack-allocated Value array)
    R2 = num_args
    Returns: R0 = result value (or R0:R1 for i64)

__meld_get_memory_base() -> *mut u8
    Returns: R0 = pointer to WASM linear memory

__meld_register_native_export(component_id: u32, export_name: *const u8, fn_ptr: fn())
    Called during startup to register Synth-compiled functions with Meld's linker

5. Linker Script Updates

File: synth-backend/src/linker_script.rs

Add sections and symbols for Meld integration:

EXTERN(__meld_dispatch_import)
EXTERN(__meld_get_memory_base)

SECTIONS {
    .text : {
        *(.text.synth.*)         /* Synth-compiled component code */
        *(.text.meld.*)          /* Meld runtime */
        *(.text.embassy.*)       /* Embassy HAL drivers */
    } > FLASH

    .meld_import_table : {
        __meld_import_table_start = .;
        *(.meld.imports)         /* Import descriptor table */
        __meld_import_table_end = .;
    } > FLASH

    .wasm_linear_memory (NOLOAD) : {
        __wasm_memory_start = .;
        . = . + __WASM_MEMORY_SIZE;
        __wasm_memory_end = .;
    } > RAM
}

Changes Required in Meld (WRT)

Tracked separately in the WRT/Meld repository. Key items:

  • Embassy platform bridge crate (meld-platform-embassy / wrt-platform-embassy)
  • HostImportHandler trait: replace Vec<Value> with BoundedVec for no_std
  • Native component registration API (for Synth-compiled exports)
  • Cross-compilation verification: thumbv7em-none-eabihf
  • Threading primitives: cortex-m critical section alternative to std::sync::Mutex
  • Export __meld_dispatch_import as C-linkage function for Synth interop

Changes Required in Loom

Minimal:

  • Update description: "Part of the Meld Platform"
  • Ensure loom-shared types are compatible with Meld's IR where applicable
  • Optional: Meld CLI integration (meld optimize delegates to Loom)

Naming Alignment

Project Role Description
Meld (formerly WRT) Runtime + Platform WebAssembly component platform for safety-critical embedded systems
Loom Optimizer Formally verified WebAssembly optimizer. Part of the Meld Platform
Synth AOT Compiler WebAssembly component synthesizer for ARM and RISC-V. Part of the Meld Platform

Tagline: "Loom optimizes. Synth compiles. Meld deploys."

Milestones

M1: Import Pipeline

  • Preserve import metadata in DecodedModule
  • Generate __meld_dispatch_import calls for imported functions
  • Basic ELF relocations for external symbols

M2: Meld ABI

  • Define and document the Synth↔Meld calling convention
  • Implement __meld_dispatch_import in Meld as extern "C" function
  • Static linking proof-of-concept (Synth .o + Meld .a → firmware.elf)

M3: Mixed-Mode Demo

  • Single component: Synth-compiled + Meld runtime + Embassy UART
  • "Hello World" over UART on STM32F4 or nRF52
  • Verify formal properties across the boundary

M4: Multi-Component

  • Multiple components with mixed execution (some Synth-native, some interpreted)
  • ComponentLinker resolves both native and WASM implementations
  • Full WASI subset via Embassy bridge

References

Activity

  1. avrabe commented on Feb 19, 2026

    @avrabe
    ContributorAuthor

    Update: Meld Already Exists as Static Component Fuser

    The initial issue assumed Meld was only a concept. It turns out pulseengine/meld already exists (in /Users/r/git/unkown-project/) as a static WebAssembly component fuser with a working implementation:

    • meld-core: Parser → Resolver → Merger → Adapter Generator → Encoder pipeline
    • meld-cli: CLI with meld fuse command
    • Supports multi-memory and shared memory strategies
    • Generates Canonical ABI trampolines for cross-component calls
    • Formally verified with Rocq 9.0 proofs
    • Supply chain attestation via wsc-attestation

    Revised Pipeline

    This significantly simplifies Synth's job. The pipeline is:

    Components → Meld (fuse to single module) → Loom (optimize) → Synth (compile to ARM)
                                                                           ↓
                                                                     WRT (runtime) + Embassy (HAL)
    

    Synth receives a single fused core module from Meld+Loom — not raw components. This means:

    • No component linking needed in Synth — Meld already resolved all cross-component imports
    • No Canonical ABI in Synth — Meld already generated adapter trampolines
    • Simpler import story — remaining imports are only host/WASI imports (everything internal was fused)
    • Whole-program visible — Synth sees all functions, can optimize globally

    What Changes in This Issue

    The Synth↔WRT ABI section remains valid. The changes to Synth's import handling are simpler:

    • Imports in the fused module are ONLY host imports (WASI, platform)
    • No inter-component imports to resolve
    • Generate BL __wrt_dispatch_import for each remaining import
    • Link with WRT static library that provides the dispatcher

    The four-tool story:

    • Meld fuses (build time, eliminates component boundaries)
    • Loom optimizes (build time, whole-program optimization)
    • Synth compiles (build time, WASM → native ARM)
    • WRT executes (deploy time, runtime services + Embassy HAL bridge)
  2. avrabe commented on Mar 1, 2026

    @avrabe
    ContributorAuthor

    Cross-Repo Architecture Audit — Confirming the Design

    Explored all four PulseEngine repos to validate the architecture in this issue. Key findings:

    Meld (component fuser)

    • Uses shared-everything linking — after fusion, all component-to-component imports are resolved to direct internal function calls
    • Component Model boundaries (instances, canonical ABI lift/lower) are eliminated statically
    • Output is a single core WASM module with only host imports remaining
    • This means post-Meld, only host function connectors need runtime dispatch

    Loom (optimizer)

    • Has 6 additional optimization passes specifically for Meld output (fused-component passes)
    • These clean up fusion seams: dead import tables, redundant type canonicalization, inlined adapter thunks
    • By the time Synth sees the module, no Component Model artifacts remain

    Kiln/wrt2 (runtime)

    • Component Model is a facade implementation — infrastructure exists but execution paths are incomplete
    • Stackless execution model for no_std embedded (aligned with this issue's design)
    • WASI dispatch works for primitives; complex types bypass Canonical ABI
    • 20+ crates with bounded collections throughout (no_std ready)
    • No current integration with Synth

    Implication for This Issue

    The architecture described here is correct and validated by the actual codebases:

    • Synth receives a flat core WASM module (post-Meld/Loom), not components
    • The only external symbols are host imports (WASI, HAL)
    • is the right abstraction — a host function trampoline, not Component Model runtime
    • Kiln's StacklessEngine can handle residual WASM (floats, i64) that Synth doesn't transcode

    Starting M1: Import Pipeline

    Beginning implementation of import metadata preservation and stub generation.

  3. avrabe commented on Mar 1, 2026

    @avrabe
    ContributorAuthor

    M1: Import Pipeline — Complete

    Commit c10d9c4 implements M1 on the Synth side:

    What was added

    Component Change
    wasm_decoder.rs ImportEntry / ImportKind structs, imports + num_imported_funcs fields on DecodedModule
    instruction_selector.rs Call(idx) where idx < num_imports emits MOV R0, #idx; BL __meld_dispatch_import
    elf_builder.rs Relocation struct, .rel.text section, add_undefined_symbol() for SHN_UNDEF externals
    linker_script.rs .meld_import_table, .wasm_linear_memory, EXTERN(__meld_dispatch_import)
    CompileConfig num_imports field threaded through CLI → backend → instruction selector

    ABI contract (Synth side)

    Synth-compiled code calls imports via:

    MOV R0, #<import_index>    ; Which import to dispatch
    BL  __meld_dispatch_import ; Provided by Meld static library
    

    The ELF contains:

    • R_ARM_CALL relocation at each BL __meld_dispatch_import site
    • __meld_dispatch_import as STB_GLOBAL / SHN_UNDEF symbol
    • .meld.imports section with import descriptor table

    Next: M2 (Meld ABI)

    • Define __meld_dispatch_import(import_index: u32, ...) -> u32 in Meld/wrt2
    • Static linking proof-of-concept: Synth .o + Meld .a → firmware.elf
  4. avrabe commented on Mar 1, 2026

    @avrabe
    ContributorAuthor

    M2 Complete: Kiln-Synth Bridge Crate + ABI Integration Tests

    kiln-synth-bridge (Kiln/wrt2 repo)

    Branch: feat/kiln-synth-bridge (kiln@07bb45f)

    New crate providing the C-ABI bridge between Synth-compiled ARM code and the Kiln runtime:

    • __meld_dispatch_import(import_index: u32, arg0: u32) -> u32 — extern "C" symbol that Synth BL instructions target
    • __meld_get_memory_base() -> *mut u8 — returns WASM linear memory pointer
    • meld_bridge_init() / meld_bridge_register_import() — initialization API
    • Fixed-size import descriptor table (64 entries, deterministic memory)
    • Builds as both rlib (Rust) and staticlib (ARM linker)
    • Edition 2024 compliant (unsafe(no_mangle), raw pointer static access)
    • 4 tests passing

    Meld ABI Integration Tests (Synth repo)

    Commit: synth@53d046f

    5 new tests in crates/synth-backend/tests/meld_abi_test.rs:

    1. Import dispatch stub generation — verifies Call(0) with num_imports=1 generates MOV R0, #0; BL __meld_dispatch_import
    2. Local function call not dispatch — verifies Call(1) with num_imports=1 generates BL func_1 (not dispatch)
    3. ELF relocation for Meld dispatch — verifies add_undefined_symbol + add_relocation(R_ARM_CALL) produces valid ELF
    4. Full Meld ABI pipeline — end-to-end: WASM with imports → instruction selection → ARM encoding → ELF with .rel.text
    5. Multiple import dispatch — verifies 2 imports get distinct index MOVs

    ABI Contract (frozen)

    Synth side:                          Kiln side:
    MOV R0, #import_index    →    __meld_dispatch_import(index, arg0)
    BL  __meld_dispatch_import       looks up ImportDescriptor[index]
    ; result in R0                   calls registered DispatchFn
                                     returns result in R0
    

    Next: M3 (Mixed-Mode Demo)

    Link a Synth .o with Kiln libkiln_synth_bridge.a into a single firmware ELF and execute on QEMU/Renode.

  5. avrabe commented on Mar 1, 2026

    @avrabe
    ContributorAuthor

    Triage Update: M1 + M2 Complete, M3 Next

    M1: Import Pipeline — DONE

    • ImportEntry / ImportKind structs in synth-core/src/wasm_decoder.rs
    • imports and num_imported_funcs fields on DecodedModule
    • Import calls generate MOV R0, #idx; BL __meld_dispatch_import in instruction selector
    • ELF builder supports Relocation, .rel.text section, add_undefined_symbol()
    • Linker script supports .meld_import_table, EXTERN(__meld_dispatch_import)

    M2: Meld ABI — DONE

    • kiln-synth-bridge crate exists in Kiln repo with extern "C" dispatch function
    • 5 integration tests in crates/synth-backend/tests/meld_abi_test.rs
    • Full pipeline tested: WASM with imports → instruction selection → ARM encoding → ELF with .rel.text
    • ABI contract frozen (see M2 comment above)

    M3: Mixed-Mode Demo — NOT STARTED

    • Need to link Synth .o + Kiln libkiln_synth_bridge.a → firmware.elf
    • Need Embassy HAL bridge for UART on STM32F4 or nRF52
    • Blocked on Create ARM cross-compilation toolchain #27 (ARM cross-compilation toolchain for linking)

    M4: Multi-Component — NOT STARTED

    • Requires M3 completion first
  6. avrabe commented on Jun 10, 2026

    @avrabe
    ContributorAuthor

    Closing here — this is meld's architecture, tracked in the meld repo. Since this was filed, the platform split solidified: synth = compiler (this repo), kiln = interpreter/runtime, meld = platform/fusion — and the WRT-as-embedded-host-runtime + Embassy HAL bridge design belongs to meld's scope. synth's side of the contract (compile components to native ARM, call into the runtime for imports) is being worked concretely in #237/#181 (native-pointer ABI) and #275 (self-contained firmware). If the Embassy-bridge architecture is still live, it should be (re)filed in pulseengine/meld where the owning code lives.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions