Skip to content

Commit ae8999b

Browse files
committed
Adjust allocation strategy for TinyGo-derived modules so they don't immediately crash.
1 parent 6ed1788 commit ae8999b

File tree

2 files changed

+55
-3
lines changed

2 files changed

+55
-3
lines changed

crates/wasmparser/src/readers/core/custom.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ impl<'a> CustomSectionReader<'a> {
103103
/// Return value of [`CustomSectionReader::as_known`].
104104
///
105105
/// Note that this is `#[non_exhaustive]` because depending on crate features
106-
/// this enumeration will different entries.
106+
/// this enumeration will contain different entries.
107107
#[allow(missing_docs)]
108108
#[non_exhaustive]
109109
pub enum KnownCustom<'a> {

crates/wit-component/src/gc.rs

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,48 @@ impl<'a> Module<'a> {
500500
live_iter(&self.live_tables, self.tables.iter())
501501
}
502502

503+
/// Returns whether the module appears to have been generated by TinyGo. Absent
504+
/// any evidence, returns false.
505+
fn is_from_tiny_go(&self) -> bool {
506+
self.producers.as_ref().map_or(false, |producers| {
507+
producers.iter().any(|(field, values)| {
508+
field == "processed-by"
509+
&& values
510+
.iter()
511+
.any(|(processor, _version)| processor == "TinyGo")
512+
})
513+
})
514+
}
515+
516+
/// Returns a new Wasm function body which implements `cabi_realloc` to call a
517+
/// `malloc` that's exported from the main module.
518+
fn realloc_via_malloc(&self) -> Result<wasm_encoder::Function> {
519+
// (func $cabi_realloc (;someIndex;) (type someType) (param i32 i32 i32 i32) (result i32)
520+
// local.get 3
521+
// call $malloc
522+
// )
523+
524+
// Find `malloc`.
525+
let malloc_index = match self.exports.get("malloc") {
526+
None => bail!(
527+
"found no exported `malloc` function to use to allocate the adapter's state in a TinyGo-derived module"
528+
),
529+
Some(export) => {
530+
if export.kind != ExternalKind::Func {
531+
bail!("found a `malloc` export, but it was not a function")
532+
}
533+
export.index
534+
}
535+
};
536+
537+
let mut func = wasm_encoder::Function::new([]);
538+
539+
func.instructions().local_get(3); // desired new size
540+
func.instructions().call(malloc_index);
541+
542+
Ok(func)
543+
}
544+
503545
/// Encodes this `Module` to a new wasm module which is gc'd and only
504546
/// contains the items that are live as calculated by the `liveness` pass.
505547
fn encode(&mut self, main_module_realloc: Option<&str>) -> Result<Vec<u8>> {
@@ -733,10 +775,20 @@ impl<'a> Module<'a> {
733775

734776
if sp.is_some() && (realloc_index.is_none() || allocation_state.is_none()) {
735777
// Either the main module does _not_ export a realloc function, or it is not safe to use for stack
736-
// allocation because we have no way to short-circuit reentrance, so we'll use `memory.grow` instead.
778+
// allocation because we have no way to short-circuit reentrance, so we'll provide our own realloc
779+
// function instead.
737780
realloc_index = Some(num_func_imports + funcs.len());
738781
funcs.function(add_realloc_type(&mut types));
739-
code.function(&realloc_via_memory_grow());
782+
783+
// If it appears the module was emitted by TinyGo, we delegate to its `malloc()` function. (TinyGo
784+
// assumes its GC has reign over the whole memory and quickly overwrites the adapter's State struct
785+
// unless we inform its GC of the memory we use.)
786+
if self.is_from_tiny_go() {
787+
code.function(&self.realloc_via_malloc()?);
788+
} else {
789+
// If it's not TinyGo, use `memory.grow` instead.
790+
code.function(&realloc_via_memory_grow());
791+
}
740792
}
741793

742794
// Inject a start function to initialize the stack pointer which will be local to this module. This only

0 commit comments

Comments
 (0)