wasmerio/wasmer · error

Cannot write non-scalar value as bytes

Error message

Cannot write non-scalar value as bytes

What it means

`write_value` in the `__main__`-style dynamic closure call syscall (`call_dynamic`) serializes `Value`s into linear memory as raw bytes. Only scalar types (i32/i64/f32/f64/v128) have byte representations; ExternRef, FuncRef, and ExceptionRef values cannot be written as byte slices, so the code panics instead of writing garbage.

Source

Thrown at lib/wasix/src/syscalls/wasix/call_dynamic.rs:33

        }
    }};
}

fn write_value(
    memory: &MemoryView,
    offset: &mut u64,
    max: u64,
    strict: bool,
    value: &Value,
) -> Result<bool, MemoryAccessError> {
    match value {
        Value::I32(value) => write_value!(memory, *offset, max, strict, value),
        Value::I64(value) => write_value!(memory, *offset, max, strict, value),
        Value::F32(value) => write_value!(memory, *offset, max, strict, value),
        Value::F64(value) => write_value!(memory, *offset, max, strict, value),
        Value::V128(value) => write_value!(memory, *offset, max, strict, value),
        // ExternRef, FuncRef, and ExceptionRef cannot be represented as byte slices
        _ => panic!("Cannot write non-scalar value as bytes"),
    }
}

macro_rules! read_value {
    ($memory:expr, $offset:expr, $max:expr, $strict:expr, $ty:ident, $val:ident, $len:expr) => {{
        if $offset + $len > $max {
            Ok(if $strict {
                None
            } else {
                Some(Value::$val($ty::default()))
            })
        } else {
            let mut buffer = [0u8; $len];
            $memory.read($offset, &mut buffer)?;
            $offset += $len;
            Ok(Some(Value::$val($ty::from_le_bytes(buffer))))
        }
    }};

View on GitHub (pinned to 8c4b9ee9d3)

Solutions

  1. Restrict dynamically called functions to scalar-only signatures (i32/i64/f32/f64/v128) — wrap ref-taking functions with a scalar adapter
  2. Pass references through a side table (index as i32) instead of embedding them in the dynamic call's memory arguments
  3. Validate the target function's type against scalar-only types before invoking `call_dynamic`
  4. If refs are required, use direct (non-dynamic) Wasm invocation through the linker where references are supported

Example fix

// before: dynamic call with a funcref arg
args.push(Value::FuncRef(Some(funcref)));
write_value(memory, &mut off, max, strict, &Value::FuncRef(...)); // panics
// after: pass an index into a side table
args.push(Value::I32(funcrev_index as i32));
write_value(memory, &mut off, max, strict, &Value::I32(funcrev_index as i32));
Defensive patterns

Strategy: validation

Validate before calling

// Reject non-scalar argument values before the dynamic call
if !args.iter().all(|v| matches!(v,
    Value::I32(_) | Value::I64(_) | Value::F32(_) | Value::F64(_) | Value::V128(_)))
{
    return Err(Errno::Notsup);
}

Type guard

fn is_scalar_value(v: &Value) -> bool {
    matches!(v, Value::I32(_) | Value::I64(_) | Value::F32(_) | Value::F64(_) | Value::V128(_))
}

Try / catch

let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| write_value(memory, &mut off, max, strict, &value)));
if result.is_err() { /* handle non-scalar arg: use side-table handle instead */ }

Prevention

When it happens

Trigger: Calling a function via `call_dynamic` (indirect `__indirect_function_table` invocation) whose parameters include a reference type (externref/funcref/exceptionref), with argument values supplied from memory or host code.

Common situations: Dynamically calling WASIX closure functions from modules that use the reference-types proposal; host code building argument buffers for dynamic calls with non-scalar signatures; ABI mismatches where a table slot's function type contains refs but the caller assumes scalars.

Related errors


AI-assisted analysis of wasmerio/wasmer@8c4b9ee9d3 (2026-09-01). Data as JSON: /api/errors/05b7b62d15cca39f. Report an issue: GitHub.