Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,10 @@ cd2607360a17a63bd83a6c99e33c4545f467b57e.

Copyright 2026 the Wazy authors.
Licensed under the Apache License, Version 2.0. See the repository root LICENSE.

Selected Component Model conformance fixtures and test scenarios are adapted
from github.com/bytecodealliance/wasmtime at commit
899e66bef961f63a795a371a19a1db019ef9e015.

Copyright the Wasmtime authors.
Licensed under Apache-2.0 WITH LLVM-exception.
8 changes: 8 additions & 0 deletions internal/engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ type Function interface {
CallWithStack(context.Context, []uint64) error
}

// IsHostFunction reports whether fn is a synthetic function implemented by
// the host. Such functions can only be entered through a core wasm import and
// must not be called directly by component lifecycle code.
func IsHostFunction(fn Function) bool {
_, ok := fn.(*hostFunction)
return ok
}

type Memory interface {
Size() uint32
Read(uint32, uint32) ([]byte, bool)
Expand Down
45 changes: 43 additions & 2 deletions internal/instance/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -1160,6 +1160,16 @@ func Instantiate(ctx context.Context, r wazy.Runtime, componentBytes []byte, opt
// table (not just funcs), and a core func index space where canon-produced
// funcs (lower, resource.*) and core-level func aliases interleave.
func needsGraphPath(comp *binary.Component) bool {
// The trivial path intentionally binds only the lifted core function and
// does not resolve canonical options. Routing a lift with post-return (or
// any other option carrying runtime behavior) through it would silently
// discard that behavior. The graph path resolves and validates the full
// option set.
for _, canon := range comp.Canons {
if len(canon.Opts) != 0 {
return true
}
}
for _, ci := range comp.CoreInstances {
if ci.Kind != 0x01 {
continue
Expand Down Expand Up @@ -1823,7 +1833,7 @@ func (in *Instance) invokeEntered(ctx context.Context, be *boundExport, exportNa
}
putCoreValueSlice(coreArgsPtr) // coreArgs' bits are now copied into stack; done with it

if err := be.coreFn.CallWithStack(ctx, stack); err != nil {
if err := callCoreWithStack(ctx, be.coreFn, stack); err != nil {
putUint64Slice(stackPtr)
in.poisoned.Store(true) // guest code actually ran and trapped -- see this func's doc
err = wrapUnreachableTrap(err)
Expand Down Expand Up @@ -1855,7 +1865,7 @@ func (in *Instance) invokeEntered(ctx context.Context, be *boundExport, exportNa
}
// post-return takes the same flat results as params; CallWithStack lets
// it reuse rawResults' own buffer (the guest reads params, writes none).
if err := be.postReturnFn.CallWithStack(ctx, rawResults); err != nil {
if err := callCoreWithStack(ctx, be.postReturnFn, rawResults); err != nil {
putUint64Slice(stackPtr)
in.poisoned.Store(true) // guest code actually ran and trapped -- see this func's doc
return nil, fmt.Errorf("component/instance: export %q: post-return %q: %w", exportName, be.postReturnFuncName, err)
Expand All @@ -1866,6 +1876,34 @@ func (in *Instance) invokeEntered(ctx context.Context, be *boundExport, exportNa
return results, nil
}

// callCoreWithStack converts an error panic raised by a Component Model host
// adapter into the trap error returned by the public component call. The core
// engine already returns its own traps as errors, but deliberately re-panics
// unknown host panic values. Canonical ABI adapters use error panics to abort a
// guest call when lifting or lowering detects invalid guest-controlled memory.
// Non-error panics remain programmer bugs and are not hidden.
func callCoreWithStack(ctx context.Context, fn api.Function, stack []uint64) (err error) {
defer func() {
if recovered := recover(); recovered != nil {
if recoveredErr, ok := recovered.(error); ok {
err = recoveredErr
return
}
panic(recovered)
}
}()
// Some core engines use a zero-length stack as the sentinel for "nothing
// to execute" in their optimized path. A real () -> () function must still
// run: post-return functions commonly have that signature and may trap or
// perform mandatory cleanup. Use the ordinary entry point for this one
// shape so it cannot be skipped.
if len(stack) == 0 {
_, err = fn.Call(ctx)
return err
}
return fn.CallWithStack(ctx, stack)
}

// lowerParams lowers each component-level argument into its flattened core
// values, in parameter order, using be's precomputed per-param type/
// usesMemory/error (see finalizeBoundExport) instead of recomputing them.
Expand Down Expand Up @@ -2100,6 +2138,9 @@ func (in *Instance) DropResource(ctx context.Context, iface, resourceName string
dtorName := iface + "#[dtor]" + resourceName
for _, mod := range in.closers {
if fn := safeExportedFunction(mod, dtorName); fn != nil {
if api.IsHostFunction(fn) {
continue
}
if _, err := fn.Call(ctx, uint64(rep)); err != nil {
return fmt.Errorf("component/instance: DropResource %s/%s: destructor: %w", iface, resourceName, err)
}
Expand Down
Binary file added testdata/wasmtime/post_return_scalars.wasm
Binary file not shown.
32 changes: 32 additions & 0 deletions testdata/wasmtime/post_return_scalars.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
;; Adapted from Wasmtime tests/all/component_model/post_return.rs at
;; 899e66bef961f63a795a371a19a1db019ef9e015.
;; Licensed under Apache-2.0 WITH LLVM-exception.
(component
(core module $m
(func (export "i32") (result i32) i32.const 1)
(func (export "i64") (result i64) i64.const 2)
(func (export "f32") (result f32) f32.const 3)
(func (export "f64") (result f64) f64.const 4)
(func (export "post-i32") (param i32)
local.get 0 i32.const 1 i32.ne if unreachable end)
(func (export "post-i64") (param i64)
local.get 0 i64.const 2 i64.ne if unreachable end)
(func (export "post-f32") (param f32)
local.get 0 f32.const 3 f32.ne if unreachable end)
(func (export "post-f64") (param f64)
local.get 0 f64.const 4 f64.ne if unreachable end)
)
(core instance $i (instantiate $m))
(alias core export $i "post-i32" (core func $post-i32))
(alias core export $i "post-i64" (core func $post-i64))
(alias core export $i "post-f32" (core func $post-f32))
(alias core export $i "post-f64" (core func $post-f64))
(func (export "i32") (result u32)
(canon lift (core func $i "i32") (post-return $post-i32)))
(func (export "i64") (result u64)
(canon lift (core func $i "i64") (post-return $post-i64)))
(func (export "f32") (result float32)
(canon lift (core func $i "f32") (post-return $post-f32)))
(func (export "f64") (result float64)
(canon lift (core func $i "f64") (post-return $post-f64)))
)
Binary file added testdata/wasmtime/post_return_string.wasm
Binary file not shown.
22 changes: 22 additions & 0 deletions testdata/wasmtime/post_return_string.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
;; Adapted from Wasmtime tests/all/component_model/post_return.rs at
;; 899e66bef961f63a795a371a19a1db019ef9e015.
;; Licensed under Apache-2.0 WITH LLVM-exception.
(component
(core module $m
(memory (export "memory") 1)
(func (export "get") (result i32)
(i32.store offset=0 (i32.const 8) (i32.const 100))
(i32.store offset=4 (i32.const 8) (i32.const 11))
i32.const 8)
(func (export "post") (param i32)
local.get 0 i32.const 8 i32.ne if unreachable end)
(data (i32.const 100) "hello world")
)
(core instance $i (instantiate $m))
(alias core export $i "memory" (core memory $memory))
(alias core export $i "post" (core func $post))
(func (export "get") (result string)
(canon lift (core func $i "get")
(post-return $post)
(memory $memory)))
)
Binary file added testdata/wasmtime/post_return_trap.wasm
Binary file not shown.
13 changes: 13 additions & 0 deletions testdata/wasmtime/post_return_trap.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
;; Adapted from Wasmtime tests/all/component_model/post_return.rs at
;; 899e66bef961f63a795a371a19a1db019ef9e015.
;; Licensed under Apache-2.0 WITH LLVM-exception.
(component
(core module $m
(func (export "call"))
(func (export "post") unreachable)
)
(core instance $i (instantiate $m))
(alias core export $i "post" (core func $post))
(func (export "call")
(canon lift (core func $i "call") (post-return $post)))
)
Binary file added testdata/wasmtime/string_length_overflow.wasm
Binary file not shown.
48 changes: 48 additions & 0 deletions testdata/wasmtime/string_length_overflow.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
;; Adapted from Wasmtime tests/all/component_model/strings.rs at
;; 899e66bef961f63a795a371a19a1db019ef9e015.
;; Licensed under Apache-2.0 WITH LLVM-exception.
(component
(component $receiver
(core module $m
(func (export "") (param i32 i32))
(func (export "realloc") (param i32 i32 i32 i32) (result i32)
i32.const 0)
(memory (export "memory") 1)
)
(core instance $m (instantiate $m))
(alias core export $m "realloc" (core func $realloc))
(alias core export $m "memory" (core memory $memory))
(func (export "accept") (param "value" string)
(canon lift (core func $m "")
(realloc $realloc)
(memory $memory)
string-encoding=utf8)
)
)

(component $sender
(import "accept" (func $accept (param "value" string)))
(core module $memory
(memory (export "memory") 1)
)
(core instance $memory (instantiate $memory))
(alias core export $memory "memory" (core memory $linear-memory))
(core func $accept (canon lower (func $accept)
string-encoding=utf8
(memory $linear-memory)))
(core module $call
(import "" "accept" (func $accept (param i32 i32)))
(func (export "call") (param i32)
(call $accept (i32.const 1000) (local.get 0)))
)
(core instance $call (instantiate $call
(with "" (instance (export "accept" (func $accept))))))
(func (export "call") (param "length" u32)
(canon lift (core func $call "call")))
)

(instance $receiver (instantiate $receiver))
(instance $sender (instantiate $sender
(with "accept" (func $receiver "accept"))))
(export "call" (func $sender "call"))
)
Binary file added testdata/wasmtime/string_ptr_oob.wasm
Binary file not shown.
46 changes: 46 additions & 0 deletions testdata/wasmtime/string_ptr_oob.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
;; Adapted from Wasmtime tests/all/component_model/strings.rs at
;; 899e66bef961f63a795a371a19a1db019ef9e015.
;; Licensed under Apache-2.0 WITH LLVM-exception.
(component
(component $receiver
(core module $m
(func (export "") (param i32 i32))
(func (export "realloc") (param i32 i32 i32 i32) (result i32)
i32.const 0)
(memory (export "memory") 1)
)
(core instance $m (instantiate $m))
(alias core export $m "realloc" (core func $realloc))
(alias core export $m "memory" (core memory $memory))
(func (export "accept") (param "value" string)
(canon lift (core func $m "")
(realloc $realloc)
(memory $memory)
string-encoding=utf8)
)
)

(component $sender
(import "accept" (func $accept (param "value" string)))
(core module $memory
(memory (export "memory") 1)
)
(core instance $memory (instantiate $memory))
(alias core export $memory "memory" (core memory $linear-memory))
(core func $accept (canon lower (func $accept)
string-encoding=utf8
(memory $linear-memory)))
(core module $start
(import "" "accept" (func $accept (param i32 i32)))
(func $start
(call $accept (i32.const 0x80000000) (i32.const 1)))
(start $start)
)
(core instance (instantiate $start
(with "" (instance (export "accept" (func $accept))))))
)

(instance $receiver (instantiate $receiver))
(instance $sender (instantiate $sender
(with "accept" (func $receiver "accept"))))
)
Binary file added testdata/wasmtime/string_realloc_oob.wasm
Binary file not shown.
48 changes: 48 additions & 0 deletions testdata/wasmtime/string_realloc_oob.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
;; Adapted from Wasmtime tests/all/component_model/strings.rs at
;; 899e66bef961f63a795a371a19a1db019ef9e015.
;; Licensed under Apache-2.0 WITH LLVM-exception.
(component
(component $receiver
(core module $m
(func (export "") (param i32 i32))
(func (export "realloc") (param i32 i32 i32 i32) (result i32)
i32.const 100000)
(memory (export "memory") 1)
)
(core instance $m (instantiate $m))
(alias core export $m "realloc" (core func $realloc))
(alias core export $m "memory" (core memory $memory))
(func (export "accept") (param "value" string)
(canon lift (core func $m "")
(realloc $realloc)
(memory $memory)
string-encoding=utf8)
)
)

(component $sender
(import "accept" (func $accept (param "value" string)))
(core module $memory
(memory (export "memory") 1)
(data (i32.const 1000) "0123456789")
)
(core instance $memory (instantiate $memory))
(alias core export $memory "memory" (core memory $linear-memory))
(core func $accept (canon lower (func $accept)
string-encoding=utf8
(memory $linear-memory)))
(core module $call
(import "" "accept" (func $accept (param i32 i32)))
(func (export "call")
(call $accept (i32.const 1000) (i32.const 10)))
)
(core instance $call (instantiate $call
(with "" (instance (export "accept" (func $accept))))))
(func (export "call") (canon lift (core func $call "call")))
)

(instance $receiver (instantiate $receiver))
(instance $sender (instantiate $sender
(with "accept" (func $receiver "accept"))))
(export "call" (func $sender "call"))
)
Loading
Loading