use tensor_wasm_core::types::InstanceId;
use tensor_wasm_wasi_gpu::abi::{AbiError, FN_LAUNCH, MODULE};
use tensor_wasm_wasi_gpu::host::{add_to_linker, HasWasiCuda, WasiCudaContext};
use tensor_wasm_wasi_gpu::kernel_args::{
build_kernel_param_storage, encode_argv, LoweredArg, LoweredArgSnapshot,
};
use tensor_wasm_wasi_gpu::registry::KernelEntry;
struct TestStore {
cuda: WasiCudaContext,
}
impl HasWasiCuda for TestStore {
fn wasi_cuda(&self) -> &WasiCudaContext {
&self.cuda
}
}
fn make_engine_and_linker() -> (wasmtime::Engine, wasmtime::Linker<TestStore>) {
let config = wasmtime::Config::new();
let engine = wasmtime::Engine::new(&config).expect("engine");
let mut linker: wasmtime::Linker<TestStore> = wasmtime::Linker::new(&engine);
add_to_linker(&mut linker).expect("add_to_linker");
(engine, linker)
}
#[cfg(feature = "cuda")]
fn make_managed_engine_and_linker() -> (wasmtime::Engine, wasmtime::Linker<TestStore>) {
use tensor_wasm_mem::wasm_memory::TensorWasmMemoryCreator;
let mut config = wasmtime::Config::new();
config.with_host_memory(std::sync::Arc::new(TensorWasmMemoryCreator::default()));
config.guard_before_linear_memory(false);
config.memory_reservation(0);
config.memory_guard_size(0);
config.async_support(true);
config.async_stack_size(16 << 20);
let engine = wasmtime::Engine::new(&config).expect("engine");
let mut linker: wasmtime::Linker<TestStore> = wasmtime::Linker::new(&engine);
add_to_linker(&mut linker).expect("add_to_linker");
(engine, linker)
}
fn build_launch_wat(argv_bytes: &[u8], argv_offset: usize, kernel_id: u64) -> String {
let data_literal = wat_data_literal(argv_bytes);
format!(
r#"
(module
(import "{m}" "{fn_name}"
(func $launch (param i64 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)))
(memory (export "memory") 4)
(data (i32.const {data_offset}) "{data_literal}")
(func (export "launch_with_args") (result i32)
(call $launch
(i64.const {kernel_id})
(i32.const 1) (i32.const 1) (i32.const 1)
(i32.const 1) (i32.const 1) (i32.const 1)
(i32.const 0)
(i32.const {argv_offset})
(i32.const {argv_len}))))
"#,
m = MODULE,
fn_name = FN_LAUNCH,
data_offset = argv_offset,
argv_len = argv_bytes.len(),
kernel_id = kernel_id,
data_literal = data_literal,
)
}
fn register_stub_kernel(ctx: &WasiCudaContext, owner: InstanceId, name: &str) -> u64 {
let kid = ctx
.registry
.register(KernelEntry {
owner,
entry: name.into(),
ptx_bytes_len: 256,
#[cfg(feature = "cuda")]
module: None,
})
.expect("register");
kid.0
}
fn wat_data_literal(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len() * 4);
for b in bytes {
s.push_str(&format!("\\{:02x}", b));
}
s
}
#[allow(clippy::too_many_arguments)]
fn build_vector_add_launch_wat(
argv_bytes: &[u8],
argv_offset: usize,
kernel_id: u64,
grid_x: u32,
block_x: u32,
data_regions: &[(usize, Vec<u8>)],
) -> String {
let argv_literal = wat_data_literal(argv_bytes);
let mut data_segments = String::new();
for (offset, bytes) in data_regions {
let literal = wat_data_literal(bytes);
data_segments.push_str(&format!(
"\n (data (i32.const {offset}) \"{literal}\")"
));
}
format!(
r#"
(module
(import "{m}" "{fn_name}"
(func $launch (param i64 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)))
(memory (export "memory") 4)
(data (i32.const {argv_offset}) "{argv_literal}"){data_segments}
(func (export "launch_with_args") (result i32)
(call $launch
(i64.const {kernel_id})
(i32.const {grid_x}) (i32.const 1) (i32.const 1)
(i32.const {block_x}) (i32.const 1) (i32.const 1)
(i32.const 0)
(i32.const {argv_offset})
(i32.const {argv_len}))))
"#,
m = MODULE,
fn_name = FN_LAUNCH,
argv_offset = argv_offset,
argv_len = argv_bytes.len(),
argv_literal = argv_literal,
data_segments = data_segments,
kernel_id = kernel_id,
grid_x = grid_x,
block_x = block_x,
)
}
#[cfg(feature = "cuda")]
fn ensure_cuda_initialized() {
tensor_wasm_wasi_gpu::cuda_ctx::ensure_current_context()
.expect("ensure_cuda_initialized: ensure_current_context");
}
#[cfg(feature = "cuda")]
fn register_real_kernel(
ctx: &WasiCudaContext,
owner: InstanceId,
name: &str,
ptx_bytes: &[u8],
) -> (u64, bool) {
ensure_cuda_initialized();
let ptx_str = std::str::from_utf8(ptx_bytes).expect("PTX is valid UTF-8");
let module = match cust::module::Module::from_ptx(ptx_str, &[]) {
Ok(m) => Some(std::sync::Arc::new(m)),
Err(e) => {
eprintln!(
"register_real_kernel: Module::from_ptx rejected `{name}` PTX \
({e:?}); falling back to module:None. The host launch path \
will surface InvalidKernel, which the caller can treat as a \
SM-mismatch skip."
);
None
}
};
let loaded = module.is_some();
let kid = ctx
.registry
.register(KernelEntry {
owner,
entry: name.into(),
ptx_bytes_len: ptx_bytes.len(),
module,
})
.expect("register");
(kid.0, loaded)
}
#[cfg(not(feature = "cuda"))]
#[allow(dead_code)]
fn register_real_kernel(
_ctx: &WasiCudaContext,
_owner: InstanceId,
_name: &str,
_ptx_bytes: &[u8],
) -> (u64, bool) {
panic!(
"register_real_kernel requires --features cuda; \
the no-CUDA build has no way to load a real PTX module."
);
}
#[cfg(feature = "cuda")]
fn device_compute_capability() -> (i32, i32) {
use cust::sys as cuda_sys;
ensure_cuda_initialized();
unsafe {
let mut dev: cuda_sys::CUdevice = 0;
let res = cuda_sys::cuDeviceGet(&mut dev as *mut cuda_sys::CUdevice, 0);
assert_eq!(
res,
cuda_sys::cudaError_enum::CUDA_SUCCESS,
"cuDeviceGet(0) failed: {res:?}"
);
let mut major = 0i32;
let mut minor = 0i32;
let r_major = cuda_sys::cuDeviceGetAttribute(
&mut major as *mut i32,
cuda_sys::CUdevice_attribute_enum::CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR,
dev,
);
let r_minor = cuda_sys::cuDeviceGetAttribute(
&mut minor as *mut i32,
cuda_sys::CUdevice_attribute_enum::CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR,
dev,
);
assert_eq!(
r_major,
cuda_sys::cudaError_enum::CUDA_SUCCESS,
"cuDeviceGetAttribute(COMPUTE_CAPABILITY_MAJOR) failed: {r_major:?}"
);
assert_eq!(
r_minor,
cuda_sys::cudaError_enum::CUDA_SUCCESS,
"cuDeviceGetAttribute(COMPUTE_CAPABILITY_MINOR) failed: {r_minor:?}"
);
(major, minor)
}
}
#[cfg(feature = "cuda")]
fn select_vector_add_ptx() -> (&'static [u8], &'static str) {
let (major, minor) = device_compute_capability();
if major >= 8 {
(VECTOR_ADD_PTX, "sm_80")
} else {
eprintln!(
"select_vector_add_ptx: device compute capability {major}.{minor} < 8.0; \
using the sm_75 fixture."
);
(VECTOR_ADD_PTX_SM75, "sm_75")
}
}
#[tokio::test]
async fn scalar_argv_round_trips_through_launch_path() {
let (engine, linker) = make_engine_and_linker();
let owner = InstanceId(301);
let mut ctx = WasiCudaContext::new(owner);
ctx.enable_wasi_cuda();
let kid = register_stub_kernel(&ctx, owner, "scalar_kernel");
let expected = vec![
LoweredArg::I32(-13),
LoweredArg::I64(1_234_567_890_123),
LoweredArg::F32(2.5_f32),
LoweredArg::F64(3.5_f64),
LoweredArg::U32(0x1234_5678),
LoweredArg::U64(0xDEAD_BEEF_C0FF_EE00),
];
let argv = encode_argv(&expected);
let wat = build_launch_wat(&argv, 1024, kid);
let wasm = wat::parse_str(&wat).unwrap();
let module = wasmtime::Module::new(&engine, &wasm).expect("compile");
let mut store = wasmtime::Store::new(&engine, TestStore { cuda: ctx });
let instance = linker
.instantiate_async(&mut store, &module)
.await
.expect("instantiate");
let f = instance
.get_typed_func::<(), i32>(&mut store, "launch_with_args")
.expect("typed func");
let rc = f.call_async(&mut store, ()).await.expect("call");
#[cfg(not(feature = "cuda"))]
assert_eq!(
rc,
AbiError::NotAvailable.code(),
"no-CUDA host: launch reports NotAvailable after parsing argv"
);
#[cfg(feature = "cuda")]
assert_eq!(
rc,
AbiError::InvalidKernel.code(),
"CUDA host: stub kernel has no PTX module; launch rejects with InvalidKernel"
);
let recorded = store.data().wasi_cuda().last_lowered_args();
let expected_snapshots: Vec<LoweredArgSnapshot> =
expected.iter().map(LoweredArgSnapshot::from).collect();
assert_eq!(
recorded, expected_snapshots,
"parsed argv must round-trip the original LoweredArg sequence"
);
}
#[tokio::test]
async fn pointer_argv_round_trips_through_launch_path() {
let (engine, linker) = make_engine_and_linker();
let owner = InstanceId(302);
let mut ctx = WasiCudaContext::new(owner);
ctx.enable_wasi_cuda();
let kid = register_stub_kernel(&ctx, owner, "pointer_kernel");
let expected = vec![
LoweredArg::ptr_for_encoding(256, 64),
LoweredArg::I32(7),
LoweredArg::ptr_for_encoding(4096, 128),
];
let argv = encode_argv(&expected);
let wat = build_launch_wat(&argv, 8192, kid);
let wasm = wat::parse_str(&wat).unwrap();
let module = wasmtime::Module::new(&engine, &wasm).expect("compile");
let mut store = wasmtime::Store::new(&engine, TestStore { cuda: ctx });
let instance = linker
.instantiate_async(&mut store, &module)
.await
.expect("instantiate");
let f = instance
.get_typed_func::<(), i32>(&mut store, "launch_with_args")
.expect("typed func");
let rc = f.call_async(&mut store, ()).await.expect("call");
#[cfg(not(feature = "cuda"))]
assert_eq!(rc, AbiError::NotAvailable.code());
#[cfg(feature = "cuda")]
assert_eq!(rc, AbiError::InvalidKernel.code());
let recorded = store.data().wasi_cuda().last_lowered_args();
assert_eq!(recorded.len(), 3, "expected three lowered args");
match &recorded[0] {
LoweredArgSnapshot::Ptr { guest_offset, len } => {
assert_eq!(*guest_offset, 256);
assert_eq!(*len, 64);
}
other => panic!("idx 0 expected Ptr, got {other:?}"),
}
assert!(matches!(recorded[1], LoweredArgSnapshot::I32(7)));
match &recorded[2] {
LoweredArgSnapshot::Ptr { guest_offset, len } => {
assert_eq!(*guest_offset, 4096);
assert_eq!(*len, 128);
}
other => panic!("idx 2 expected Ptr, got {other:?}"),
}
}
#[tokio::test]
async fn pointer_argv_out_of_bounds_returns_invalid_pointer() {
let (engine, linker) = make_engine_and_linker();
let owner = InstanceId(303);
let mut ctx = WasiCudaContext::new(owner);
ctx.enable_wasi_cuda();
let kid = register_stub_kernel(&ctx, owner, "oob_ptr_kernel");
let expected = vec![LoweredArg::ptr_for_encoding(
4 * 65536 - 256, 1024,
)];
let argv = encode_argv(&expected);
let wat = build_launch_wat(&argv, 1024, kid);
let wasm = wat::parse_str(&wat).unwrap();
let module = wasmtime::Module::new(&engine, &wasm).expect("compile");
let mut store = wasmtime::Store::new(&engine, TestStore { cuda: ctx });
let instance = linker
.instantiate_async(&mut store, &module)
.await
.expect("instantiate");
let f = instance
.get_typed_func::<(), i32>(&mut store, "launch_with_args")
.expect("typed func");
let rc = f.call_async(&mut store, ()).await.expect("call");
assert_eq!(
rc,
AbiError::InvalidPointer.code(),
"pointer arg whose [ptr, ptr+len) spans past memory end must \
return InvalidPointer (got {rc})"
);
let recorded = store.data().wasi_cuda().last_lowered_args();
assert!(
recorded.is_empty(),
"last_lowered_args must remain empty on parse failure"
);
}
#[tokio::test]
#[ignore = "requires CUDA hardware"]
async fn scalar_argv_real_cuda_launch() {
let (engine, linker) = make_engine_and_linker();
let owner = InstanceId(401);
let mut ctx = WasiCudaContext::new(owner);
ctx.enable_wasi_cuda();
let kid = register_stub_kernel(&ctx, owner, "scalar_add");
let argv = encode_argv(&[LoweredArg::I32(2), LoweredArg::I32(3)]);
let wat = build_launch_wat(&argv, 1024, kid);
let wasm = wat::parse_str(&wat).unwrap();
let module = wasmtime::Module::new(&engine, &wasm).expect("compile");
let mut store = wasmtime::Store::new(&engine, TestStore { cuda: ctx });
let instance = linker
.instantiate_async(&mut store, &module)
.await
.expect("instantiate");
let f = instance
.get_typed_func::<(), i32>(&mut store, "launch_with_args")
.expect("typed func");
let _rc = f.call_async(&mut store, ()).await.expect("call");
#[cfg(feature = "cuda")]
{
}
}
#[tokio::test]
#[ignore = "requires CUDA hardware"]
async fn pointer_argv_real_cuda_launch() {
let (engine, linker) = make_engine_and_linker();
let owner = InstanceId(402);
let mut ctx = WasiCudaContext::new(owner);
ctx.enable_wasi_cuda();
let kid = register_stub_kernel(&ctx, owner, "pointer_copy");
let argv = encode_argv(&[
LoweredArg::ptr_for_encoding(0, 1024),
LoweredArg::ptr_for_encoding(2048, 1024),
LoweredArg::U32(256),
]);
let wat = build_launch_wat(&argv, 8192, kid);
let wasm = wat::parse_str(&wat).unwrap();
let module = wasmtime::Module::new(&engine, &wasm).expect("compile");
let mut store = wasmtime::Store::new(&engine, TestStore { cuda: ctx });
let instance = linker
.instantiate_async(&mut store, &module)
.await
.expect("instantiate");
let f = instance
.get_typed_func::<(), i32>(&mut store, "launch_with_args")
.expect("typed func");
let _rc = f.call_async(&mut store, ()).await.expect("call");
#[cfg(feature = "cuda")]
{
}
}
#[cfg(feature = "cuda")]
const VECTOR_ADD_PTX: &[u8] = include_bytes!("fixtures/vector_add.ptx");
#[cfg(feature = "cuda")]
const VECTOR_ADD_PTX_SM75: &[u8] = include_bytes!("fixtures/vector_add_sm75.ptx");
#[tokio::test]
#[ignore = "requires CUDA hardware"]
async fn vector_add_end_to_end_real_ptx_real_kernel() {
#[cfg(not(feature = "cuda"))]
{
eprintln!(
"vector_add_end_to_end_real_ptx_real_kernel: skipping (built without --features cuda)"
);
}
#[cfg(feature = "cuda")]
{
const N: usize = 64;
const ELEM_BYTES: usize = std::mem::size_of::<f32>();
const REGION_BYTES: usize = N * ELEM_BYTES;
const A_OFFSET: usize = 1024;
const B_OFFSET: usize = 2048;
const C_OFFSET: usize = 3072;
const ARGV_OFFSET: usize = 8192;
let mut a_bytes = Vec::with_capacity(REGION_BYTES);
let mut b_bytes = Vec::with_capacity(REGION_BYTES);
let mut c_bytes = Vec::with_capacity(REGION_BYTES);
for i in 0..N {
a_bytes.extend_from_slice(&(i as f32).to_le_bytes());
b_bytes.extend_from_slice(&(100.0_f32 + i as f32).to_le_bytes());
c_bytes.extend_from_slice(&0.0_f32.to_le_bytes());
}
let (engine, linker) = make_managed_engine_and_linker();
let owner = InstanceId(403);
let mut ctx = WasiCudaContext::new(owner);
ctx.enable_wasi_cuda();
let (ptx, arch) = select_vector_add_ptx();
let (kid, loaded) = register_real_kernel(&ctx, owner, "vector_add", ptx);
if !loaded {
panic!(
"vector_add_end_to_end_real_ptx_real_kernel: the {arch} fixture \
(arch-matched to this device) was rejected by the driver JIT; \
last error: {:?}",
ctx.last_error()
);
}
let argv = encode_argv(&[
LoweredArg::ptr_for_encoding(A_OFFSET as u32, REGION_BYTES as u32),
LoweredArg::ptr_for_encoding(B_OFFSET as u32, REGION_BYTES as u32),
LoweredArg::ptr_for_encoding(C_OFFSET as u32, REGION_BYTES as u32),
LoweredArg::U32(N as u32),
]);
let wat = build_vector_add_launch_wat(
&argv,
ARGV_OFFSET,
kid,
1,
N as u32,
&[
(A_OFFSET, a_bytes.clone()),
(B_OFFSET, b_bytes.clone()),
(C_OFFSET, c_bytes.clone()),
],
);
let wasm = wat::parse_str(&wat).expect("parse vector_add WAT");
let module = wasmtime::Module::new(&engine, &wasm).expect("compile vector_add WAT");
let mut store = wasmtime::Store::new(&engine, TestStore { cuda: ctx });
let instance = linker
.instantiate_async(&mut store, &module)
.await
.expect("instantiate");
let f = instance
.get_typed_func::<(), i32>(&mut store, "launch_with_args")
.expect("typed func");
let rc = f.call_async(&mut store, ()).await.expect("call");
assert_eq!(
rc,
0,
"vector_add launch must return 0 on a CUDA host with the SM_80 \
fixture; last error: {:?}",
store.data().wasi_cuda().last_error()
);
let memory = instance.get_memory(&mut store, "memory").expect("memory");
let mut readback = vec![0u8; REGION_BYTES];
memory
.read(&store, C_OFFSET, &mut readback)
.expect("read c region");
for i in 0..N {
let off = i * ELEM_BYTES;
let mut buf = [0u8; ELEM_BYTES];
buf.copy_from_slice(&readback[off..off + ELEM_BYTES]);
let got = f32::from_le_bytes(buf);
let want = 100.0_f32 + 2.0_f32 * (i as f32);
assert_eq!(
got, want,
"c[{i}] mismatch: kernel produced {got}, expected {want} \
(= 100 + 2*{i})"
);
}
}
}
#[tokio::test]
async fn dispatch_pipeline_compiles_against_real_module_bytes() {
const N: usize = 64;
const ELEM_BYTES: usize = std::mem::size_of::<f32>();
const REGION_BYTES: usize = N * ELEM_BYTES;
const A_OFFSET: usize = 1024;
const B_OFFSET: usize = 2048;
const C_OFFSET: usize = 3072;
const ARGV_OFFSET: usize = 8192;
let mut a_bytes = Vec::with_capacity(REGION_BYTES);
let mut b_bytes = Vec::with_capacity(REGION_BYTES);
let c_bytes = vec![0u8; REGION_BYTES];
for i in 0..N {
a_bytes.extend_from_slice(&(i as f32).to_le_bytes());
b_bytes.extend_from_slice(&(100.0_f32 + i as f32).to_le_bytes());
}
let (engine, linker) = make_engine_and_linker();
let owner = InstanceId(404);
let mut ctx = WasiCudaContext::new(owner);
ctx.enable_wasi_cuda();
#[cfg(feature = "cuda")]
let kid = {
let (id, _loaded) =
register_real_kernel(&ctx, owner, "vector_add", select_vector_add_ptx().0);
id
};
#[cfg(not(feature = "cuda"))]
let kid = register_stub_kernel(&ctx, owner, "vector_add");
let argv = encode_argv(&[
LoweredArg::ptr_for_encoding(A_OFFSET as u32, REGION_BYTES as u32),
LoweredArg::ptr_for_encoding(B_OFFSET as u32, REGION_BYTES as u32),
LoweredArg::ptr_for_encoding(C_OFFSET as u32, REGION_BYTES as u32),
LoweredArg::U32(N as u32),
]);
let wat = build_vector_add_launch_wat(
&argv,
ARGV_OFFSET,
kid,
1,
N as u32,
&[
(A_OFFSET, a_bytes),
(B_OFFSET, b_bytes),
(C_OFFSET, c_bytes),
],
);
let wasm = wat::parse_str(&wat).expect("parse vector_add WAT");
let module = wasmtime::Module::new(&engine, &wasm).expect("compile vector_add WAT");
let mut store = wasmtime::Store::new(&engine, TestStore { cuda: ctx });
let instance = linker
.instantiate_async(&mut store, &module)
.await
.expect("instantiate");
let f = instance
.get_typed_func::<(), i32>(&mut store, "launch_with_args")
.expect("typed func");
let rc = f.call_async(&mut store, ()).await.expect("call");
#[cfg(not(feature = "cuda"))]
{
assert_eq!(
rc,
AbiError::NotAvailable.code(),
"no-CUDA host: launch must report NotAvailable after parsing argv (got {rc})"
);
}
#[cfg(feature = "cuda")]
{
assert_ne!(
rc,
0,
"a real-module pointer-arg launch against NON-managed (host-heap) \
linear memory must be refused by the device-addressability guard, \
not silently succeed (would risk a context-poisoning \
CUDA_ERROR_ILLEGAL_ADDRESS). last_error: {:?}",
store.data().wasi_cuda().last_error()
);
let allowed = [
AbiError::MalformedPtx.code(),
AbiError::InvalidKernel.code(),
AbiError::LaunchFailed.code(),
];
assert!(
allowed.contains(&rc),
"CUDA host: launch return code must be one of {allowed:?} \
(MalformedPtx / InvalidKernel / LaunchFailed); got {rc}. \
Anything else (InvalidArgs / InvalidPointer / NotAvailable) \
indicates the typed-argv marshalling regressed before the call \
reached the CUDA driver. last_error: {:?}",
store.data().wasi_cuda().last_error()
);
}
let recorded = store.data().wasi_cuda().last_lowered_args();
assert_eq!(
recorded.len(),
4,
"expected four lowered args (3 pointers + 1 scalar); got {}",
recorded.len()
);
assert!(matches!(recorded[3], LoweredArgSnapshot::U32(n) if n == N as u32));
}
#[test]
fn build_kernel_param_storage_uses_single_backing_buffer() {
let args = vec![
LoweredArg::I32(1),
LoweredArg::I64(2),
LoweredArg::F32(3.0),
LoweredArg::F64(4.0),
LoweredArg::U32(5),
LoweredArg::U64(6),
LoweredArg::I32(7),
LoweredArg::I64(8),
LoweredArg::F32(9.0),
LoweredArg::F64(10.0),
LoweredArg::U32(11),
LoweredArg::U64(12),
LoweredArg::I32(13),
LoweredArg::I64(14),
LoweredArg::F32(15.0),
LoweredArg::F64(16.0),
];
assert_eq!(args.len(), 16);
let storage = build_kernel_param_storage(&args);
assert_eq!(storage.len(), args.len());
let backing = storage.backing();
let slot_ptrs = storage.slot_ptrs();
assert_eq!(slot_ptrs.len(), args.len(), "one pointer slot per argument");
let base = backing.as_ptr() as usize;
let end = base + backing.len();
for (i, &slot) in slot_ptrs.iter().enumerate() {
let slot_addr = slot as usize;
assert!(
slot_addr >= base && slot_addr < end,
"slot {i} pointer {slot:p} is not inside backing buffer \
[{base:#x}, {end:#x}) — per-arg boxing has regressed"
);
}
assert!(
backing.len() >= 72,
"backing buffer too small for 16 scalars; got {}",
backing.len()
);
}