wasmerio/wasmer · error

Cannot read non-scalar value from memory

Error message

Cannot read non-scalar value from memory

What it means

`read_value` in `call_dynamic` reconstructs `Value`s from linear memory by reading fixed-size byte slices. Reference types (ExternRef, FuncRef, ExceptionRef) have no byte-slice representation, so when the function's result/parameter type is a reference the code panics rather than producing an invalid value.

Source

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

        }
    }};
}

fn read_value(
    memory: &MemoryView,
    offset: &mut u64,
    max: u64,
    strict: bool,
    ty: &Type,
) -> Result<Option<Value>, MemoryAccessError> {
    match ty {
        Type::I32 => read_value!(memory, *offset, max, strict, i32, I32, 4),
        Type::I64 => read_value!(memory, *offset, max, strict, i64, I64, 8),
        Type::F32 => read_value!(memory, *offset, max, strict, f32, F32, 4),
        Type::F64 => read_value!(memory, *offset, max, strict, f64, F64, 8),
        Type::V128 => read_value!(memory, *offset, max, strict, u128, V128, 16),
        // ExternRef, FuncRef, and ExceptionRef cannot be represented as byte slices
        _ => panic!("Cannot read non-scalar value from memory"),
    }
}

/// Call a function from the `__indirect_function_table` with parameters and results from memory.
///
/// This function can be used to call functions whose types are not known at
/// compile time of the caller. It is the callers responsibility to ensure
/// that the passed parameters and results match the signature of the function
/// being called.
///
/// ### Format of the values and results buffer
///
/// The buffers contain all values sequentially. i32, and f32 are 4 bytes,
/// i64 and f64 are 8 bytes, v128 is 16 bytes.
///     
/// For example if the function takes an i32 and an i64, the values buffer will
/// be 12 bytes long, with the first 4 bytes being the i32 and the next 8
/// bytes being the i64.

View on GitHub (pinned to 8c4b9ee9d3)

Solutions

  1. Only dynamically call functions whose results and parameters are scalar types; wrap ref-returning functions with a scalar adapter
  2. Return references via a host-managed side table (i32 handle) and read the handle from memory instead
  3. Pre-validate the callee's `Type` list before invoking `call_dynamic` and reject reference types with a proper error
  4. Use the regular (non-dynamic) call path for functions that traffic in reference types

Example fix

// before
type_results.iter().for_each(|t| read_value(memory, &mut off, max, strict, t)); // panics on Ref
// after
if type_results.iter().any(|t| matches!(t, Type::Ref(_))) {
    return Err(Errno::Notsup);
}
Defensive patterns

Strategy: validation

Validate before calling

// Ensure all result/param types are scalar before reading/writing memory
if type_results.iter().any(|t| matches!(t, Type::Ref(_)))
    || type_params.iter().any(|t| matches!(t, Type::Ref(_)))
{
    return Err(Errno::Notsup);
}

Type guard

fn all_scalar_types(types: &[Type]) -> bool {
    types.iter().all(|t| matches!(t, Type::I32 | Type::I64 | Type::F32 | Type::F64 | Type::V128))
}

Try / catch

let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| read_value(memory, &mut off, max, strict, &ty)));
if result.is_err() { /* ref result: resolve via side table or return Notsup */ }

Prevention

When it happens

Trigger: Calling a function via `call_dynamic` whose signature declares a reference-typed result or parameter; reading results back from memory after an indirect call when the callee returns externref/funcref/exceptionref.

Common situations: Modules compiled with reference-types enabled being invoked through the WASIX dynamic closure mechanism; type confusion where the indirect-function-table slot type changed but the caller's scalar-only expectations did not; generated stubs that don't filter ref types.

Related errors


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