Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/wasmparser/src/readers/core/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ impl<'a> CustomSectionReader<'a> {
/// Return value of [`CustomSectionReader::as_known`].
///
/// Note that this is `#[non_exhaustive]` because depending on crate features
/// this enumeration will different entries.
/// this enumeration will contain different entries.
#[allow(missing_docs)]
#[non_exhaustive]
pub enum KnownCustom<'a> {
Expand Down
26 changes: 26 additions & 0 deletions crates/wit-component/src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ use std::collections::HashMap;
use std::hash::Hash;
use std::mem;
use wasm_encoder::*;
use wasmparser::KnownCustom::Producers;
use wasmparser::Payload::CustomSection;
use wasmparser::{Validator, WasmFeatures};
use wit_parser::{
Function, FunctionKind, InterfaceId, LiveTypes, Resolve, Stability, Type, TypeDefKind, TypeId,
Expand Down Expand Up @@ -2670,11 +2672,34 @@ pub(super) struct Adapter {
library_info: Option<LibraryInfo>,
}

/// Returns whether the module appears to have been generated by TinyGo. Absent
/// any evidence, returns false.
fn is_produced_by_tiny_go(module: &[u8]) -> Result<bool> {
for section in wasmparser::Parser::new(0).parse_all(module) {
if let CustomSection(custom_reader) = section? {
if let Producers(producers_reader) = custom_reader.as_known() {
for field in producers_reader {
let field = field?;
if field.name == "processed-by" {
for value in field.values.into_iter() {
if value?.name == "TinyGo" {
return Ok(true);
}
}
}
}
return Ok(false);
}
}
}
return Ok(false);
}
/// An encoder of components based on `wit` interface definitions.
#[derive(Default)]
pub struct ComponentEncoder {
module: Vec<u8>,
module_import_map: Option<ModuleImportMap>,
module_is_produced_by_tiny_go: bool,
pub(super) metadata: Bindgen,
validate: bool,
pub(super) main_module_exports: IndexSet<WorldKey>,
Expand All @@ -2693,6 +2718,7 @@ impl ComponentEncoder {
/// core module.
pub fn module(mut self, module: &[u8]) -> Result<Self> {
let (wasm, metadata) = self.decode(module.as_ref())?;
self.module_is_produced_by_tiny_go = is_produced_by_tiny_go(&module)?;
let (wasm, module_import_map) = ModuleImportMap::new(wasm)?;
let exports = self
.merge_metadata(metadata)
Expand Down
60 changes: 46 additions & 14 deletions crates/wit-component/src/encoding/world.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use super::{Adapter, ComponentEncoder, LibraryInfo, RequiredOptions};
use crate::gc::ReallocScheme;
use crate::validation::{
Import, ImportMap, ValidatedModule, validate_adapter_module, validate_module,
};
use anyhow::{Context, Result};
use anyhow::{Context, Result, bail};
use indexmap::{IndexMap, IndexSet};
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use wasmparser::{FuncType, ValType};
use wit_parser::{
Function, InterfaceId, LiveTypes, Resolve, TypeDefKind, TypeId, TypeOwner, WorldId, WorldItem,
WorldKey,
Expand All @@ -24,7 +26,7 @@ pub struct WorldAdapter<'a> {
/// `EncodingState` as this information doesn't change throughout the encoding
/// process.
pub struct ComponentWorld<'a> {
/// Encoder configuration with modules, the document ,etc.
/// Encoder configuration with modules, the document, etc.
pub encoder: &'a ComponentEncoder,
/// Validation information of the input module, or `None` in `--types-only`
/// mode.
Expand Down Expand Up @@ -83,6 +85,34 @@ impl<'a> ComponentWorld<'a> {
Ok(ret)
}

/// Given that there is no realloc function exported from the main module,
/// returns the realloc scheme we should use.
///
/// Return an error if we need to use an exported malloc() and it wasn't
/// there.
fn fallback_realloc_scheme(&self) -> Result<ReallocScheme<'_>> {
if self.encoder.module_is_produced_by_tiny_go {
// If it appears the module was emitted by TinyGo, we delegate to
// its `malloc()` function. (TinyGo assumes its GC has rein over the
// whole memory and quickly overwrites the adapter's
// `memory.grow`-allocated State struct, causing a crash. So we use
// `malloc()` to inform TinyGo's GC of the memory we use.)
let malloc_type = FuncType::new([ValType::I32], [ValType::I32]);
match self.info.exports.get_func_type("malloc") {
Some(func_type) if *func_type == malloc_type => Ok(ReallocScheme::Malloc("malloc")),
Some(_) => bail!(
"TinyGo-derived wasm had a malloc() export, but it lacked the expected type of malloc(i32) -> i32"
),
None => bail!(
"TinyGo-derived wasm lacked a malloc() export; we don't know how else to reserve space for the adapter's state from its GC"
),
}
} else {
// If it's not TinyGo, use `memory.grow` instead.
Ok(ReallocScheme::MemoryGrow)
}
}

/// Process adapters which are required here. Iterate over all
/// adapters and figure out what functions are required from the
/// adapter itself, either because the functions are imported by the
Expand Down Expand Up @@ -121,8 +151,8 @@ impl<'a> ComponentWorld<'a> {
} else {
// Without `library_info` this means that this is an adapter.
// The goal of the adapter is to provide a suite of symbols that
// can be imported, but not all symbols may be imported. Here
// the module is trimmed down to only what's needed by the
// can be imported, but perhaps not all symbols are imported.
// Here the module is trimmed down to only what's needed by the
// original main module.
//
// The main module requires `required_by_import` above, but
Expand Down Expand Up @@ -163,17 +193,19 @@ impl<'a> ComponentWorld<'a> {
required.insert(name.to_string());
}

let realloc = if self.encoder.realloc_via_memory_grow {
// User explicitly requested memory-grow-based realloc. We
// give them that unless it would definitely crash.
self.fallback_realloc_scheme()?
} else {
match self.info.exports.realloc_to_import_into_adapter() {
Some(name) => ReallocScheme::Realloc(name),
None => self.fallback_realloc_scheme()?,
}
};
Cow::Owned(
crate::gc::run(
wasm,
&required,
if self.encoder.realloc_via_memory_grow {
None
} else {
self.info.exports.realloc_to_import_into_adapter()
},
)
.context("failed to reduce input adapter module to its minimal size")?,
crate::gc::run(wasm, &required, realloc)
.context("failed to reduce input adapter module to its minimal size")?,
)
};
let info = validate_adapter_module(
Expand Down
Loading