Skip to content

Commit b45a98a

Browse files
feat: add missing apis for c-api
Signed-off-by: Henry <mail@henrygressmann.de>
1 parent fa7b34f commit b45a98a

7 files changed

Lines changed: 163 additions & 73 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
### Added
11+
12+
- Added new `Memory::data` and `Memory::data_mut` methods for direct access to linear memory.
13+
- Added several internal methods to support a future c-api crate
14+
815
## [0.11.0] - 2026-09-19
916

1017
This release adds garbage collection and typed function reference support, single-pass parsing, and an optional tail-call dispatch backend. The internal instruction representation is now 8 bytes, down from 16, and pluggable memory backends were removed for performance reasons (however, `ResourceLimiter` was added to allow, reject, or trap memory allocation and growth requests).

‎crates/tinywasm/src/func/mod.rs‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,19 @@ pub struct Function {
3939
}
4040

4141
impl Function {
42+
/// Resolves a function reference into a callable handle in its owning store.
43+
///
44+
/// Returns an error if the reference belongs to another store or does not
45+
/// identify a valid function.
46+
pub fn from_func_ref(store: &Store, reference: FuncRef) -> Result<Self> {
47+
let addr = reference.addr(store.id()).ok_or(crate::Trap::InvalidStore)?;
48+
if !store.state.funcs.contains(addr) {
49+
return Err(crate::Trap::InvalidReference.into());
50+
}
51+
let module_id = if store.state.funcs.is_host(addr) { 0 } else { store.state.funcs.wasm(addr).owner };
52+
Ok(Self { item: StoreItem::new(store.id(), addr), module_id })
53+
}
54+
4255
/// Returns this function as a Store-aware WebAssembly function reference.
4356
pub fn as_func_ref(&self, store: &Store) -> Result<FuncRef> {
4457
self.item.validate_store(store)?;

‎crates/tinywasm/src/imports.rs‎

Lines changed: 65 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,33 @@ impl Imports {
170170
self
171171
}
172172

173-
/// Returns an explicitly defined import without cloning its handle.
174-
pub(crate) fn defined(&self, import: &Import) -> Option<&Extern> {
175-
self.externs.get(import.module.as_ref())?.get(import.name.as_ref())
173+
/// Resolves names in module import order, with explicit definitions taking precedence.
174+
pub(crate) fn resolve(&self, module: &Module) -> Result<Vec<Extern>> {
175+
module
176+
.imports
177+
.iter()
178+
.map(|import| {
179+
if let Some(value) =
180+
self.externs.get(import.module.as_ref()).and_then(|items| items.get(import.name.as_ref()))
181+
{
182+
return Ok(value.clone());
183+
}
184+
let instance =
185+
self.modules.get(import.module.as_ref()).ok_or_else(|| LinkingError::unknown_import(import))?;
186+
let item = instance.extern_item(&import.name).map_err(|_| LinkingError::unknown_import(import))?;
187+
Ok(match item {
188+
crate::ExternItem::Func(value) => Extern::Function(value),
189+
crate::ExternItem::Global(value) => Extern::Global(value),
190+
crate::ExternItem::Memory(value) => Extern::Memory(value),
191+
crate::ExternItem::Table(value) => Extern::Table(value),
192+
crate::ExternItem::Tag(value) => Extern::Tag(value),
193+
})
194+
})
195+
.collect()
176196
}
197+
}
177198

199+
impl ResolvedImports {
178200
fn compare_types<T: PartialEq>(import: &Import, actual: &T, expected: &T) -> Result<()> {
179201
if expected != actual {
180202
return cold!(Err(LinkingError::incompatible_import_type(import).into()));
@@ -223,12 +245,16 @@ impl Imports {
223245
Ok(())
224246
}
225247

226-
pub(crate) fn link(
227-
&self,
248+
/// Validates and binds imports in module import order.
249+
pub(crate) fn new(
228250
store: &mut crate::Store,
229251
module: &Module,
230252
type_addrs: &[TypeAddr],
253+
externs: &[Extern],
231254
) -> Result<ResolvedImports> {
255+
if externs.len() != module.imports.len() {
256+
return Err(crate::Error::other("ordered import count does not match module"));
257+
}
232258
let (global_count, table_count, mem_count, func_count, tag_count) =
233259
module.imports.iter().fold((0, 0, 0, 0, 0), |(g, t, m, f, e), import| match import.kind {
234260
ImportKind::Global(_) => (g + 1, t, m, f, e),
@@ -246,50 +272,42 @@ impl Imports {
246272
tags: Vec::with_capacity(tag_count + module.tags.len()),
247273
};
248274

249-
for import in &*module.imports {
250-
let val = if let Some(defined) = self.defined(import) {
251-
match defined {
252-
Extern::Global(global) => {
253-
global.0.validate_store(store)?;
254-
ExternVal::Global(global.0.addr)
255-
}
256-
Extern::Table(table) => {
257-
table.0.validate_store(store)?;
258-
ExternVal::Table(table.0.addr)
259-
}
260-
Extern::Memory(memory) => {
261-
memory.0.validate_store(store)?;
262-
ExternVal::Memory(memory.0.addr)
263-
}
264-
Extern::Function(func) => {
265-
func.item.validate_store(store)?;
266-
ExternVal::Func(func.addr())
267-
}
268-
Extern::HostFunction(func) => {
269-
let ImportKind::Function(type_idx) = import.kind else {
270-
return cold!(Err(LinkingError::incompatible_import_type(import).into()));
271-
};
272-
let expected_type_addr = type_addrs
273-
.get(type_idx as usize)
274-
.ok_or_else(|| LinkingError::incompatible_import_type(import))?;
275-
let actual_ty = func.resolve_import_type(type_addrs)?;
276-
let actual_type_addr = store.register_host_type(&actual_ty);
277-
if !store.state.type_addr_is_subtype(actual_type_addr, *expected_type_addr) {
278-
return cold!(Err(LinkingError::incompatible_import_type(import).into()));
279-
}
280-
ExternVal::Func(func.instantiate_registered(store, actual_type_addr).addr())
281-
}
282-
Extern::Tag(tag) => {
283-
tag.0.validate_store(store)?;
284-
ExternVal::Tag(tag.0.addr)
275+
for (import, defined) in module.imports.iter().zip(externs) {
276+
let val = match defined {
277+
Extern::Global(global) => {
278+
global.0.validate_store(store)?;
279+
ExternVal::Global(global.0.addr)
280+
}
281+
Extern::Table(table) => {
282+
table.0.validate_store(store)?;
283+
ExternVal::Table(table.0.addr)
284+
}
285+
Extern::Memory(memory) => {
286+
memory.0.validate_store(store)?;
287+
ExternVal::Memory(memory.0.addr)
288+
}
289+
Extern::Function(func) => {
290+
func.item.validate_store(store)?;
291+
ExternVal::Func(func.addr())
292+
}
293+
Extern::HostFunction(func) => {
294+
let ImportKind::Function(type_idx) = import.kind else {
295+
return cold!(Err(LinkingError::incompatible_import_type(import).into()));
296+
};
297+
let expected_type_addr = type_addrs
298+
.get(type_idx as usize)
299+
.ok_or_else(|| LinkingError::incompatible_import_type(import))?;
300+
let actual_ty = func.resolve_import_type(type_addrs)?;
301+
let actual_type_addr = store.register_host_type(&actual_ty);
302+
if !store.state.type_addr_is_subtype(actual_type_addr, *expected_type_addr) {
303+
return cold!(Err(LinkingError::incompatible_import_type(import).into()));
285304
}
305+
ExternVal::Func(func.instantiate_registered(store, actual_type_addr).addr())
306+
}
307+
Extern::Tag(tag) => {
308+
tag.0.validate_store(store)?;
309+
ExternVal::Tag(tag.0.addr)
286310
}
287-
} else {
288-
let Some(instance) = self.modules.get(import.module.as_ref()) else {
289-
return cold!(Err(LinkingError::unknown_import(import).into()));
290-
};
291-
instance.validate_store(store)?;
292-
instance.export_addr(&import.name).ok_or_else(|| LinkingError::unknown_import(import))?
293311
};
294312

295313
if val.kind() != (&import.kind).into() {

‎crates/tinywasm/src/instance.rs‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,10 +173,23 @@ impl ModuleInstance {
173173
///
174174
/// See <https://webassembly.github.io/spec/core/exec/modules.html#exec-instantiation>
175175
pub fn instantiate_no_start(store: &mut Store, module: &Module, imports: Option<&Imports>) -> Result<Self> {
176+
let default_imports = Imports::default();
177+
let imports = imports.unwrap_or(&default_imports).resolve(module)?;
178+
Self::instantiate_inner(store, module, &imports)
179+
}
180+
181+
/// **Unstable**: This method is for internal use only and may change or be removed at any time.
182+
#[doc(hidden)]
183+
pub fn instantiate_ordered(store: &mut Store, module: &Module, imports: &[crate::Extern]) -> Result<Self> {
184+
let instance = Self::instantiate_inner(store, module, imports)?;
185+
instance.start(store)?;
186+
Ok(instance)
187+
}
188+
189+
fn instantiate_inner(store: &mut Store, module: &Module, imports: &[crate::Extern]) -> Result<Self> {
176190
let type_addrs = store.register_module_types(&module.types);
177191
let id = store.next_module_instance_id();
178-
let default_imports = Imports::default();
179-
let mut addrs = imports.unwrap_or(&default_imports).link(store, module, &type_addrs)?;
192+
let mut addrs = crate::imports::ResolvedImports::new(store, module, &type_addrs, imports)?;
180193
let imported_funcs = addrs.funcs.len();
181194
addrs.funcs.extend(store.init_funcs(&module.funcs, id, &module.func_type_idxs[imported_funcs..], &type_addrs));
182195
addrs.tags.extend(store.init_tags(&module.tags, &type_addrs));

‎crates/tinywasm/src/reference/store.rs‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,30 @@ impl crate::std::io::Seek for MemoryCursor<'_> {
351351
}
352352

353353
impl Memory {
354+
/// **Unstable**: This method is for internal use only and may change or be removed at any time.
355+
///
356+
/// Returns a raw mutable pointer to the memory's backing allocation.
357+
/// Growth may invalidate the pointer. The caller must keep the store alive
358+
/// and uphold Rust's aliasing rules when dereferencing it.
359+
#[doc(hidden)]
360+
pub fn data_ptr(&self, store: &mut Store) -> Result<*mut u8> {
361+
Ok(self.instance_mut(store)?.inner.data_ptr())
362+
}
363+
364+
/// Borrows the complete linear memory.
365+
///
366+
/// The borrow prevents execution or growth through this store until it ends.
367+
pub fn data<'a>(&self, store: &'a Store) -> Result<&'a [u8]> {
368+
Ok(self.instance(store)?.inner.data())
369+
}
370+
371+
/// Borrows the complete linear memory exclusively.
372+
///
373+
/// The borrow prevents execution or growth through this store until it ends.
374+
pub fn data_mut<'a>(&self, store: &'a mut Store) -> Result<&'a mut [u8]> {
375+
Ok(self.instance_mut(store)?.inner.data_mut())
376+
}
377+
354378
/// Create a new memory in the given store.
355379
pub fn try_new(store: &mut Store, ty: MemoryType) -> Result<Self> {
356380
let addr = store.state.memories.len() as MemAddr;

‎crates/tinywasm/src/store/memory/vec.rs‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,23 @@ pub(crate) struct VecMemory {
1212
}
1313

1414
impl VecMemory {
15+
/// Returns a raw mutable pointer to the memory's backing allocation.
16+
/// Growth may invalidate the pointer. The caller must keep the store alive
17+
/// and uphold Rust's aliasing rules when dereferencing it.
18+
pub(crate) fn data_ptr(&mut self) -> *mut u8 {
19+
self.data.as_mut_ptr()
20+
}
21+
22+
/// Borrows the backing bytes.
23+
pub(crate) fn data(&self) -> &[u8] {
24+
&self.data
25+
}
26+
27+
/// Borrows the backing bytes exclusively.
28+
pub(crate) fn data_mut(&mut self) -> &mut [u8] {
29+
&mut self.data
30+
}
31+
1532
/// Tries to create a new memory with `len` zero-initialized bytes.
1633
pub(crate) fn try_new(_arch: MemoryArch, len: usize, _max_len: usize) -> Result<Self, crate::Trap> {
1734
let mut data = Vec::new();

‎examples/doom/src/runtime.rs‎

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::io::{Read, Seek, SeekFrom, Write};
44
use std::path::{Path, PathBuf};
55
use std::time::{Duration, Instant};
66

7-
use anyhow::Result;
7+
use anyhow::{Context, Result};
88
use tinywasm::{FuncContext, HostFunction, Imports, ModuleInstance, Store};
99

1010
const IMPORT_MODULE: &str = "env";
@@ -18,7 +18,6 @@ pub struct Runtime {
1818
key_down: tinywasm::FunctionTyped<i32, ()>,
1919
key_up: tinywasm::FunctionTyped<i32, ()>,
2020
memory: tinywasm::Memory,
21-
framebuffer_bytes: Vec<u8>,
2221
}
2322

2423
impl Runtime {
@@ -41,18 +40,7 @@ impl Runtime {
4140
memory.write_cstring_bytes(&mut store, buf_ptr, &wad_path_string)?;
4241
init.call(&mut store, ())?;
4342

44-
let width = SCREEN_WIDTH;
45-
let height = SCREEN_HEIGHT;
46-
47-
Ok(Self {
48-
store,
49-
update,
50-
framebuffer,
51-
key_down,
52-
key_up,
53-
memory,
54-
framebuffer_bytes: vec![0; width * height * 4],
55-
})
43+
Ok(Self { store, update, framebuffer, key_down, key_up, memory })
5644
}
5745

5846
pub fn tick(&mut self) -> Result<()> {
@@ -61,11 +49,15 @@ impl Runtime {
6149
}
6250

6351
pub fn write_framebuffer(&mut self, dst: &mut [u32]) -> Result<()> {
64-
let ptr = self.framebuffer.call(&mut self.store, ())? as usize;
65-
self.memory.read_exact(&self.store, ptr, &mut self.framebuffer_bytes)?;
66-
for (index, pixel) in dst.iter_mut().enumerate() {
67-
let byte_index = index * 4;
68-
let chunk = &self.framebuffer_bytes[byte_index..byte_index + 4];
52+
let ptr = self.framebuffer.call(&mut self.store, ())? as u32 as usize;
53+
let framebuffer = self
54+
.memory
55+
.data(&self.store)?
56+
.get(ptr..)
57+
.and_then(|data| data.get(..SCREEN_WIDTH * SCREEN_HEIGHT * 4))
58+
.context("framebuffer is outside guest memory")?;
59+
anyhow::ensure!(dst.len() <= SCREEN_WIDTH * SCREEN_HEIGHT, "framebuffer destination is too large");
60+
for (pixel, chunk) in dst.iter_mut().zip(framebuffer.chunks_exact(4)) {
6961
*pixel = ((chunk[0] as u32) << 16) | ((chunk[1] as u32) << 8) | chunk[2] as u32;
7062
}
7163
Ok(())
@@ -228,13 +220,19 @@ fn build_imports() -> Imports {
228220
imports.define(
229221
IMPORT_MODULE,
230222
"host_write",
231-
HostFunction::from(|mut ctx: FuncContext<'_>, (handle, buf_ptr, count): (i32, i32, i32)| {
232-
let data = ctx.memory("memory")?.read_vec(ctx.store(), buf_ptr as usize, count.max(0) as usize)?;
233-
let state = ctx.state_mut::<HostState>().unwrap();
234-
let Some(file) = state.files.get_mut(&handle) else {
223+
HostFunction::from(|ctx: FuncContext<'_>, (handle, buf_ptr, count): (i32, i32, i32)| {
224+
let data = ctx.memory("memory")?.data(ctx.store())?;
225+
let offset = buf_ptr as u32 as usize;
226+
let len = count.max(0) as usize;
227+
let data = data
228+
.get(offset..)
229+
.and_then(|data| data.get(..len))
230+
.ok_or(tinywasm::Trap::MemoryOutOfBounds { offset, len, max: data.len() })?;
231+
let state = ctx.state::<HostState>().unwrap();
232+
let Some(mut file) = state.files.get(&handle) else {
235233
return Ok(-1);
236234
};
237-
let written = file.write(&data).map_err(|err| tinywasm::Error::Other(err.to_string()))?;
235+
let written = file.write(data).map_err(|err| tinywasm::Error::Other(err.to_string()))?;
238236
Ok(written as i32)
239237
}),
240238
);

0 commit comments

Comments
 (0)