Skip to content

Commit

Permalink
Implement variant translation in fused adapters
Browse files Browse the repository at this point in the history
This commit implements the most general case of variants for fused
adapter trampolines. Additionally a number of other primitive types are
filled out here to assist with testing variants. The implementation
internally was relatively straightforward given the shape of variants,
but there's room for future optimization as necessary especially around
converting locals to various types.

This commit also introduces a "one off" fuzzer for adapters to ensure
that the generated adapter is valid. I hope to extend this fuzz
generator as more types are implemented to assist in various corner
cases that might arise. For now the fuzzer simply tests that the output
wasm module is valid, not that it actually executes correctly. I hope to
integrate with a fuzzer along the lines of bytecodealliance#4307 one day to test the
run-time-correctness of the generated adapters as well, at which point
this fuzzer would become obsolete.

Finally this commit also fixes an issue with `u8` translation where
upper bits weren't zero'd out and were passed raw across modules.
Instead smaller-than-32 types now all mask out their upper bits and do
sign-extension as appropriate for unsigned/signed variants.
  • Loading branch information
alexcrichton committed Jul 26, 2022
1 parent 1321c23 commit 993f0f1
Show file tree
Hide file tree
Showing 12 changed files with 1,156 additions and 49 deletions.
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ members = [
"crates/bench-api",
"crates/c-api",
"crates/cli-flags",
"crates/environ/fuzz",
"examples/fib-debug/wasm",
"examples/wasi/wasm",
"examples/tokio/wasm",
Expand Down
7 changes: 6 additions & 1 deletion crates/environ/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,14 @@ object = { version = "0.29.0", default-features = false, features = ['read_core'
target-lexicon = "0.12"
wasm-encoder = { version = "0.14.0", optional = true }
wasmprinter = { version = "0.2.37", optional = true }
wasmtime-component-util = { path = "../component-util", version = "=0.40.0", optional = true }

[badges]
maintenance = { status = "actively-developed" }

[features]
component-model = ["dep:wasm-encoder", "dep:wasmprinter"]
component-model = [
"dep:wasm-encoder",
"dep:wasmprinter",
"dep:wasmtime-component-util",
]
2 changes: 2 additions & 0 deletions crates/environ/fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
corpus
artifacts
23 changes: 23 additions & 0 deletions crates/environ/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "wasmtime-environ-fuzz"
version = "0.0.0"
authors = ["Automatically generated"]
publish = false
edition = "2018"

[package.metadata]
cargo-fuzz = true

[dependencies]
arbitrary = { version = "1.1.0", features = ["derive"] }
env_logger = "0.9.0"
libfuzzer-sys = "0.4"
wasmparser = "0.87.0"
wasmprinter = "0.2.37"
wasmtime-environ = { path = "..", features = ["component-model"] }

[[bin]]
name = "fact-valid-module"
path = "fuzz_targets/fact-valid-module.rs"
test = false
doc = false
212 changes: 212 additions & 0 deletions crates/environ/fuzz/fuzz_targets/fact-valid-module.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
//! A simple fuzzer for FACT
//!
//! This is an intentionally small fuzzer which is intended to only really be
//! used during the development of FACT itself when generating adapter modules.
//! This creates arbitrary adapter signatures and then generates the required
//! trampoline for that adapter ensuring that the final output wasm module is a
//! valid wasm module. This doesn't actually validate anything about the
//! correctness of the trampoline, only that it's valid wasm.

#![no_main]

use arbitrary::{Arbitrary, Unstructured};
use libfuzzer_sys::fuzz_target;
use std::fmt;
use wasmparser::{Validator, WasmFeatures};
use wasmtime_environ::component::*;
use wasmtime_environ::fact::Module;

#[derive(Arbitrary, Debug)]
struct GenAdapterModule {
debug: bool,
adapters: Vec<GenAdapter>,
}

#[derive(Arbitrary, Debug)]
struct GenAdapter {
ty: FuncType,
post_return: bool,
}

#[derive(Arbitrary, Debug)]
struct FuncType {
params: Vec<ValType>,
result: ValType,
}

#[derive(Arbitrary, Debug)]
enum ValType {
Unit,
U8,
S8,
U16,
S16,
U32,
S32,
U64,
S64,
Float32,
Float64,
Record(Vec<ValType>),
Tuple(Vec<ValType>),
Variant(NonZeroLenVec<ValType>),
}

pub struct NonZeroLenVec<T>(Vec<T>);

impl<'a, T: Arbitrary<'a>> Arbitrary<'a> for NonZeroLenVec<T> {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let mut items = Vec::arbitrary(u)?;
if items.is_empty() {
items.push(u.arbitrary()?);
}
Ok(NonZeroLenVec(items))
}
}

impl<T: fmt::Debug> fmt::Debug for NonZeroLenVec<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}

fuzz_target!(|module: GenAdapterModule| {
drop(env_logger::try_init());

let mut types = ComponentTypesBuilder::default();

let mut next_def = 0;
let mut dummy_def = || {
next_def += 1;
CoreDef::Adapter(AdapterIndex::from_u32(next_def))
};
let mut next_memory = 0;
let mut dummy_memory = || {
// Limit the number of memory imports generated since `wasmparser` has a
// hardcoded limit of ~100 for now anyway.
if next_memory < 20 {
next_memory += 1;
}
CoreExport {
instance: RuntimeInstanceIndex::from_u32(next_memory),
item: ExportItem::Name(String::new()),
}
};

let mut adapters = Vec::new();
for adapter in module.adapters.iter() {
let mut params = Vec::new();
for param in adapter.ty.params.iter() {
params.push((None, intern(&mut types, param)));
}
let result = intern(&mut types, &adapter.ty.result);
let signature = types.add_func_type(TypeFunc {
params: params.into(),
result,
});
adapters.push(Adapter {
lift_ty: signature,
lower_ty: signature,
lower_options: AdapterOptions {
instance: RuntimeComponentInstanceIndex::from_u32(0),
string_encoding: StringEncoding::Utf8,
// Pessimistically assume that memory/realloc are going to be
// required for this trampoline and provide it. Avoids doing
// calculations to figure out whether they're necessary and
// simplifies the fuzzer here without reducing coverage within FACT
// itself.
memory: Some(dummy_memory()),
realloc: Some(dummy_def()),
// Lowering never allows `post-return`
post_return: None,
},
lift_options: AdapterOptions {
instance: RuntimeComponentInstanceIndex::from_u32(1),
string_encoding: StringEncoding::Utf8,
memory: Some(dummy_memory()),
realloc: Some(dummy_def()),
post_return: if adapter.post_return {
Some(dummy_def())
} else {
None
},
},
func: dummy_def(),
});
}
let types = types.finish();
let mut module = Module::new(&types, module.debug);
for (i, adapter) in adapters.iter().enumerate() {
module.adapt(&format!("adapter{i}"), adapter);
}
let wasm = module.encode();
let result = Validator::new_with_features(WasmFeatures {
multi_memory: true,
..WasmFeatures::default()
})
.validate_all(&wasm);

let err = match result {
Ok(_) => return,
Err(e) => e,
};
eprintln!("invalid wasm module: {err:?}");
for adapter in adapters.iter() {
eprintln!("adapter type: {:?}", types[adapter.lift_ty]);
}
std::fs::write("invalid.wasm", &wasm).unwrap();
match wasmprinter::print_bytes(&wasm) {
Ok(s) => std::fs::write("invalid.wat", &s).unwrap(),
Err(_) => drop(std::fs::remove_file("invalid.wat")),
}

panic!()
});

fn intern(types: &mut ComponentTypesBuilder, ty: &ValType) -> InterfaceType {
match ty {
ValType::Unit => InterfaceType::Unit,
ValType::U8 => InterfaceType::U8,
ValType::S8 => InterfaceType::S8,
ValType::U16 => InterfaceType::U16,
ValType::S16 => InterfaceType::S16,
ValType::U32 => InterfaceType::U32,
ValType::S32 => InterfaceType::S32,
ValType::U64 => InterfaceType::U64,
ValType::S64 => InterfaceType::S64,
ValType::Float32 => InterfaceType::Float32,
ValType::Float64 => InterfaceType::Float64,
ValType::Record(tys) => {
let ty = TypeRecord {
fields: tys
.iter()
.enumerate()
.map(|(i, ty)| RecordField {
name: format!("f{i}"),
ty: intern(types, ty),
})
.collect(),
};
InterfaceType::Record(types.add_record_type(ty))
}
ValType::Tuple(tys) => {
let ty = TypeTuple {
types: tys.iter().map(|ty| intern(types, ty)).collect(),
};
InterfaceType::Tuple(types.add_tuple_type(ty))
}
ValType::Variant(NonZeroLenVec(cases)) => {
let ty = TypeVariant {
cases: cases
.iter()
.enumerate()
.map(|(i, ty)| VariantCase {
name: format!("c{i}"),
ty: intern(types, ty),
})
.collect(),
};
InterfaceType::Variant(types.add_variant_type(ty))
}
}
}
32 changes: 24 additions & 8 deletions crates/environ/src/component/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ impl ComponentTypesBuilder {
.collect(),
result: self.valtype(&ty.result),
};
intern(&mut self.functions, &mut self.component_types.functions, ty)
self.add_func_type(ty)
}

fn defined_type(&mut self, ty: &wasmparser::ComponentDefinedType<'_>) -> InterfaceType {
Expand Down Expand Up @@ -636,7 +636,7 @@ impl ComponentTypesBuilder {
})
.collect(),
};
intern(&mut self.records, &mut self.component_types.records, record)
self.add_record_type(record)
}

fn variant_type(&mut self, cases: &[wasmparser::VariantCase<'_>]) -> TypeVariantIndex {
Expand All @@ -654,18 +654,14 @@ impl ComponentTypesBuilder {
})
.collect(),
};
intern(
&mut self.variants,
&mut self.component_types.variants,
variant,
)
self.add_variant_type(variant)
}

fn tuple_type(&mut self, types: &[wasmparser::ComponentValType]) -> TypeTupleIndex {
let tuple = TypeTuple {
types: types.iter().map(|ty| self.valtype(ty)).collect(),
};
intern(&mut self.tuples, &mut self.component_types.tuples, tuple)
self.add_tuple_type(tuple)
}

fn flags_type(&mut self, flags: &[&str]) -> TypeFlagsIndex {
Expand Down Expand Up @@ -704,6 +700,26 @@ impl ComponentTypesBuilder {
expected,
)
}

/// Interns a new function type within this type information.
pub fn add_func_type(&mut self, ty: TypeFunc) -> TypeFuncIndex {
intern(&mut self.functions, &mut self.component_types.functions, ty)
}

/// Interns a new record type within this type information.
pub fn add_record_type(&mut self, ty: TypeRecord) -> TypeRecordIndex {
intern(&mut self.records, &mut self.component_types.records, ty)
}

/// Interns a new tuple type within this type information.
pub fn add_tuple_type(&mut self, ty: TypeTuple) -> TypeTupleIndex {
intern(&mut self.tuples, &mut self.component_types.tuples, ty)
}

/// Interns a new variant type within this type information.
pub fn add_variant_type(&mut self, ty: TypeVariant) -> TypeVariantIndex {
intern(&mut self.variants, &mut self.component_types.variants, ty)
}
}

// Forward the indexing impl to the internal `TypeTables`
Expand Down
Loading

0 comments on commit 993f0f1

Please sign in to comment.