From 92a4e6ec8d6cc43c638740062d8667c95893a405 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 19 Feb 2021 11:55:22 -0800 Subject: [PATCH 01/22] Start adding support for a "next" ABI This commit implements support for new ABI which is an evolution of the current ABI specific to WASI. The main purpose of this ABI is to support all possible types in all places (e.g. multiple results, multiple params, lists of records of variants of structs of lists, etc...). --- tools/witx/src/abi.rs | 988 ++++++++++++++++++++------ tools/witx/src/parser.rs | 68 +- tools/witx/src/validate.rs | 19 +- tools/witx/tests/witxt.rs | 194 +++-- tools/witx/tests/witxt/abi-next.witxt | 671 +++++++++++++++++ tools/witx/tests/witxt/abi.witxt | 209 +++--- 6 files changed, 1747 insertions(+), 402 deletions(-) create mode 100644 tools/witx/tests/witxt/abi-next.witxt diff --git a/tools/witx/src/abi.rs b/tools/witx/src/abi.rs index 0e1ca9cb4..96f28f7e2 100644 --- a/tools/witx/src/abi.rs +++ b/tools/witx/src/abi.rs @@ -21,8 +21,22 @@ //! generators will need to implement the various instructions to support APIs. use crate::{ - BuiltinType, Id, IntRepr, InterfaceFunc, InterfaceFuncParam, NamedType, Type, TypeRef, + BuiltinType, Id, IntRepr, InterfaceFunc, InterfaceFuncParam, NamedType, RecordDatatype, + RecordKind, Type, TypeRef, Variant, }; +use std::mem; + +/// A raw WebAssembly signature with params and results. +#[derive(Debug)] +pub struct WasmSignature { + /// The WebAssembly parameters of this function. + pub params: Vec, + /// The WebAssembly results of this function. + pub results: Vec, + /// The raw types, if needed, returned through return pointer located in + /// `params`. + pub retptr: Option>, +} /// Enumerates wasm types used by interface types when lowering/lifting. #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -57,6 +71,9 @@ pub enum Abi { /// Note that this ABI is limited notably in its return values where it can /// only return 0 results or one `Result` lookalike. Preview1, + + /// TODO + Next, } // Helper macro for defining instructions without having to have tons of @@ -64,7 +81,7 @@ pub enum Abi { macro_rules! def_instruction { ( $( #[$enum_attr:meta] )* - pub enum Instruction<'a> { + pub enum $name:ident<'a> { $( $( #[$attr:meta] )* $variant:ident $( { @@ -76,7 +93,7 @@ macro_rules! def_instruction { } ) => { $( #[$enum_attr] )* - pub enum Instruction<'a> { + pub enum $name<'a> { $( $( #[$attr] )* $variant $( { @@ -87,7 +104,7 @@ macro_rules! def_instruction { )* } - impl Instruction<'_> { + impl $name<'_> { /// How many operands does this instruction pop from the stack? #[allow(unused_variables)] pub fn operands_len(&self) -> usize { @@ -126,9 +143,8 @@ def_instruction! { /// Depending on the context this may refer to wasm parameters or /// interface types parameters. GetArg { nth: usize } : [0] => [1], - /// Takes the value off the top of the stack and writes it into linear - /// memory. Pushes the address in linear memory as an `i32`. - AddrOf : [1] => [1], + /// Pushes the constant `val` onto the stack. + I32Const { val: i32 } : [0] => [1], /// Converts an interface type `char` value to a 32-bit integer /// representing the unicode scalar value. I32FromChar : [1] => [1], @@ -140,8 +156,6 @@ def_instruction! { I32FromU32 : [1] => [1], /// Converts an interface type `s32` value to a wasm `i32`. I32FromS32 : [1] => [1], - /// Converts a language-specific `usize` value to a wasm `i32`. - I32FromUsize : [1] => [1], /// Converts an interface type `u16` value to a wasm `i32`. I32FromU16 : [1] => [1], /// Converts an interface type `s16` value to a wasm `i32`. @@ -150,28 +164,15 @@ def_instruction! { I32FromU8 : [1] => [1], /// Converts an interface type `s8` value to a wasm `i32`. I32FromS8 : [1] => [1], + /// Converts a language-specific `usize` value to a wasm `i32`. + I32FromUsize : [1] => [1], /// Converts a language-specific C `char` value to a wasm `i32`. I32FromChar8 : [1] => [1], - /// Converts a language-specific pointer value to a wasm `i32`. - I32FromPointer : [1] => [1], - /// Converts a language-specific pointer value to a wasm `i32`. - I32FromConstPointer : [1] => [1], /// Converts a language-specific handle value to a wasm `i32`. I32FromHandle { ty: &'a NamedType } : [1] => [1], - /// Converts a language-specific record-of-bools to the packed - /// representation as an `i32`. - I32FromBitflags { ty: &'a NamedType } : [1] => [1], - /// Converts a language-specific record-of-bools to the packed - /// representation as an `i64`. - I64FromBitflags { ty: &'a NamedType } : [1] => [1], /// Converts an interface type list into its pointer/length, pushing /// them both on the stack. ListPointerLength : [1] => [2], - /// Pops two `i32` values from the stack and creates a list from them of - /// the specified type. The first operand is the pointer in linear - /// memory to the start of the list and the second operand is the - /// length. - ListFromPointerLength { ty: &'a TypeRef } : [2] => [1], /// Conversion an interface type `f32` value to a wasm `f32`. /// /// This may be a noop for some implementations, but it's here in case the @@ -184,6 +185,12 @@ def_instruction! { /// native language representation of `f64` is different than the wasm /// representation of `f64`. F64FromIf64 : [1] => [1], + /// Converts a native wasm `i32` to a language-specific C `char`. + /// + /// This will truncate the upper bits of the `i32`. + Char8FromI32 : [1] => [1], + /// Converts a native wasm `i32` to a language-specific `usize`. + UsizeFromI32 : [1] => [1], /// Represents a call to a raw WebAssembly API. The module/name are /// provided inline as well as the types if necessary. @@ -229,18 +236,124 @@ def_instruction! { /// /// It's safe to assume that the `i32` is indeed a valid unicode code point. CharFromI32 : [1] => [1], - /// Converts a native wasm `i32` to a language-specific C `char`. - /// - /// This will truncate the upper bits of the `i32`. - Char8FromI32 : [1] => [1], - /// Converts a native wasm `i32` to a language-specific `usize`. - UsizeFromI32 : [1] => [1], /// Converts a native wasm `f32` to an interface type `f32`. If32FromF32 : [1] => [1], /// Converts a native wasm `f64` to an interface type `f64`. If64FromF64 : [1] => [1], /// Converts a native wasm `i32` to an interface type `handle`. HandleFromI32 { ty: &'a NamedType } : [1] => [1], + /// Returns `amt` values on the stack. This is always the last + /// instruction. + Return { amt: usize } : [*amt] => [0], + + /// Pops a record value off the stack, decomposes the record to all of + /// its fields, and then pushes the fields onto the stack. + RecordLower { ty: &'a RecordDatatype } : [1] => [ty.members.len()], + /// Pops all fields for a record off the stack and then composes them + /// into a record. + RecordLift { ty: &'a RecordDatatype } : [ty.members.len()] => [1], + + /// This is a special instruction used at the entry of blocks used as + /// part of `ResultLower`, representing that the payload of that variant + /// being matched on should be pushed onto the stack. + VariantPayload : [0] => [1], + + /// Pops a variant off the stack as well as `ty.cases.len()` blocks + /// from the code generator. Uses each of those blocks and the value + /// from the stack to produce `nresults` of items. + VariantLower { + ty: &'a Variant, + nresults: usize, + } : [1] => [*nresults], + + /// Pops an `i32` off the stack as well as `ty.cases.len()` blocks + /// from the code generator. Uses each of those blocks and the value + /// from the stack to produce a final variant. + VariantLift { ty: &'a Variant } : [1] => [1], + + /// Casts the top N items on the stack using the `Bitcast` enum + /// provided. Consumes the same number of operands that this produces. + Bitcasts { casts: &'a [Bitcast] } : [casts.len()] => [casts.len()], + + /// Pushes a number of constant zeros for each wasm type on the stack. + ConstZero { tys: &'a [WasmType] } : [0] => [tys.len()], + + /// Pops an `i32` from the stack and loads a little-endian `i32` from + /// it, using the specified constant offset. + I32Load { offset: i32 } : [1] => [1], + /// Pops an `i32` from the stack and loads a little-endian `i64` from + /// it, using the specified constant offset. + I64Load { offset: i32 } : [1] => [1], + /// Pops an `i32` from the stack and loads a little-endian `f32` from + /// it, using the specified constant offset. + F32Load { offset: i32 } : [1] => [1], + /// Pops an `i32` from the stack and loads a little-endian `f64` from + /// it, using the specified constant offset. + F64Load { offset: i32 } : [1] => [1], + /// Pops an `i32` address from the stack and then an `i32` value. + /// Stores the value in little-endian at the pointer specified plus the + /// constant `offset`. + I32Store { offset: i32 } : [2] => [0], + /// Pops an `i32` address from the stack and then an `i64` value. + /// Stores the value in little-endian at the pointer specified plus the + /// constant `offset`. + I64Store { offset: i32 } : [2] => [0], + /// Pops an `i32` address from the stack and then an `f32` value. + /// Stores the value in little-endian at the pointer specified plus the + /// constant `offset`. + F32Store { offset: i32 } : [2] => [0], + /// Pops an `i32` address from the stack and then an `f64` value. + /// Stores the value in little-endian at the pointer specified plus the + /// constant `offset`. + F64Store { offset: i32 } : [2] => [0], + + /// An instruction from an extended instruction set that's specific to + /// `*.witx` and the "Preview1" ABI. + Witx { + instr: &'a WitxInstruction<'a>, + } : [instr.operands_len()] => [instr.results_len()], + } +} + +#[derive(Debug, PartialEq)] +pub enum Bitcast { + // Bitcasts + F32ToF64, + F32ToI32, + F64ToI64, + I32ToI64, + + // Bitcasts + F64ToF32, + I32ToF32, + I64ToF64, + I64ToI32, + + None, +} + +def_instruction! { + #[derive(Debug)] + pub enum WitxInstruction<'a> { + /// Takes the value off the top of the stack and writes it into linear + /// memory. Pushes the address in linear memory as an `i32`. + AddrOf : [1] => [1], + /// Converts a language-specific pointer value to a wasm `i32`. + I32FromPointer : [1] => [1], + /// Converts a language-specific pointer value to a wasm `i32`. + I32FromConstPointer : [1] => [1], + /// Converts a language-specific record-of-bools to the packed + /// representation as an `i32`. + I32FromBitflags { ty: &'a NamedType } : [1] => [1], + /// Converts a language-specific record-of-bools to the packed + /// representation as an `i64`. + I64FromBitflags { ty: &'a NamedType } : [1] => [1], + /// Pops two `i32` values from the stack and creates a list from them of + /// the specified type. The first operand is the pointer in linear + /// memory to the start of the list and the second operand is the + /// length. + ListFromPointerLength { ty: &'a TypeRef } : [2] => [1], + /// Converts a native wasm `i32` to a language-specific pointer. PointerFromI32 { ty: &'a TypeRef }: [1] => [1], /// Converts a native wasm `i32` to a language-specific pointer. @@ -249,13 +362,6 @@ def_instruction! { BitflagsFromI32 { ty: &'a NamedType } : [1] => [1], /// Converts a native wasm `i64` to a language-specific record-of-bools. BitflagsFromI64 { ty: &'a NamedType } : [1] => [1], - /// Acquires the return pointer `n` and pushes an `i32` on the stack. - /// - /// Implementations of [`Bindgen`] may have [`Bindgen::allocate_space`] - /// called to reserve space in memory for the result of a computation to - /// get written. This instruction acquires a pointer to the space - /// reserved in `allocate_space`. - ReturnPointerGet { n: usize } : [0] => [1], /// Loads the interface types value from an `i32` pointer popped from /// the stack. Load { ty: &'a NamedType } : [1] => [1], @@ -307,13 +413,6 @@ def_instruction! { /// WASI. The raw return `i32` of a function is re-pushed onto the /// stack for reuse. ReuseReturn : [0] => [1], - /// Returns `amt` values on the stack. This is always the last - /// instruction. - Return { amt: usize } : [*amt] => [0], - /// This is a special instruction used at the entry of blocks used as - /// part of `ResultLower`, representing that the payload of that variant - /// being matched on should be pushed onto the stack. - VariantPayload : [0] => [1], } } @@ -324,9 +423,20 @@ impl Abi { /// they're indeed representable. pub fn validate( &self, - _params: &[InterfaceFuncParam], + params: &[InterfaceFuncParam], results: &[InterfaceFuncParam], ) -> Result<(), String> { + match self { + Abi::Preview1 => { + // validated below... + } + Abi::Next => { + for ty in params.iter().chain(results) { + validate_no_witx(ty.tref.type_())?; + } + return Ok(()); + } + } assert_eq!(*self, Abi::Preview1); match results.len() { 0 => {} @@ -377,6 +487,49 @@ impl Abi { } } +fn validate_no_witx(ty: &Type) -> Result<(), String> { + match ty { + Type::Record(r) => { + match r.kind { + RecordKind::Bitflags(_) => { + return Err("cannot use `(@witx bitflags)` in this ABI".to_string()) + } + RecordKind::Tuple | RecordKind::Other => {} + } + for r in r.members.iter() { + validate_no_witx(r.tref.type_())?; + } + Ok(()) + } + Type::Variant(v) => { + if v.tag_repr != IntRepr::U32 { + return Err("cannot use `(@witx tag)` in this ABI".to_string()); + } + for case in v.cases.iter() { + if let Some(ty) = &case.tref { + validate_no_witx(ty.type_())?; + } + } + Ok(()) + } + Type::Handle(_) => Ok(()), + Type::List(t) => validate_no_witx(t.type_()), + Type::Pointer(_) => return Err("cannot use `(@witx pointer)` in this ABI".to_string()), + Type::ConstPointer(_) => { + return Err("cannot use `(@witx const_pointer)` in this ABI".to_string()) + } + Type::Builtin(BuiltinType::U8 { lang_c_char: true }) => { + return Err("cannot use `(@witx char8)` in this ABI".to_string()); + } + Type::Builtin(BuiltinType::U32 { + lang_ptr_size: true, + }) => { + return Err("cannot use `(@witx usize)` in this ABI".to_string()); + } + Type::Builtin(_) => Ok(()), + } +} + /// Trait for language implementors to use to generate glue code between native /// WebAssembly signatures and interface types signatures. /// @@ -393,7 +546,7 @@ pub trait Bindgen { /// The intermediate type for fragments of code for this type. /// /// For most languages `String` is a suitable intermediate type. - type Operand; + type Operand: Clone; /// Emit code to implement the given instruction. /// @@ -410,12 +563,24 @@ pub trait Bindgen { results: &mut Vec, ); - /// Allocates temporary space in linear memory indexed by `slot` with enough - /// space to store `ty`. + /// Allocates temporary space in linear memory for the type `ty`. /// /// This is called when calling some wasm functions where a return pointer - /// is needed. - fn allocate_space(&mut self, slot: usize, ty: &NamedType); + /// is needed. Only used for the `Abi::Preview1` ABI. + /// + /// Returns an `Operand` which has type `i32` and is the base of the typed + /// allocation in memory. + fn allocate_typed_space(&mut self, ty: &NamedType) -> Self::Operand; + + /// Allocates temporary space in linear memory for a fixed number of `i64` + /// values. + /// + /// This is only called in the `Abi::Next` ABI for when a function would + /// otherwise have multiple results. + /// + /// Returns an `Operand` which has type `i32` and points to the base of the + /// fixed-size-array allocation. + fn allocate_i64_array(&mut self, amt: usize) -> Self::Operand; /// Enters a new block of code to generate code for. /// @@ -441,7 +606,7 @@ pub trait Bindgen { /// block before `push_block` was called. This must also save the results /// of the current block internally for instructions like `ResultLift` to /// use later. - fn finish_block(&mut self, operand: Option); + fn finish_block(&mut self, operand: &mut Vec); } impl InterfaceFunc { @@ -449,70 +614,51 @@ impl InterfaceFunc { /// /// The first entry returned is the list of parameters and the second entry /// is the list of results for the wasm function signature. - pub fn wasm_signature(&self) -> (Vec, Vec) { - assert_eq!(self.abi, Abi::Preview1); + pub fn wasm_signature(&self) -> WasmSignature { let mut params = Vec::new(); let mut results = Vec::new(); for param in self.params.iter() { match &**param.tref.type_() { - Type::Builtin(BuiltinType::S8) - | Type::Builtin(BuiltinType::U8 { .. }) - | Type::Builtin(BuiltinType::S16) - | Type::Builtin(BuiltinType::U16) - | Type::Builtin(BuiltinType::S32) - | Type::Builtin(BuiltinType::U32 { .. }) - | Type::Builtin(BuiltinType::Char) + Type::Builtin(_) | Type::Pointer(_) | Type::ConstPointer(_) | Type::Handle(_) - | Type::Variant(_) => params.push(WasmType::I32), - - Type::Record(r) => match r.bitflags_repr() { - Some(repr) => params.push(WasmType::from(repr)), - None => params.push(WasmType::I32), - }, - - Type::Builtin(BuiltinType::S64) | Type::Builtin(BuiltinType::U64) => { - params.push(WasmType::I64) - } - - Type::Builtin(BuiltinType::F32) => params.push(WasmType::F32), - Type::Builtin(BuiltinType::F64) => params.push(WasmType::F64), - - Type::List(_) => { - params.push(WasmType::I32); - params.push(WasmType::I32); + | Type::List(_) => { + push_wasm(param.tref.type_(), &mut params); } + ty @ Type::Variant(_) => match self.abi { + Abi::Preview1 => params.push(WasmType::I32), + Abi::Next => push_wasm(ty, &mut params), + }, + Type::Record(r) => match self.abi { + Abi::Preview1 => match r.bitflags_repr() { + Some(repr) => params.push(WasmType::from(repr)), + None => params.push(WasmType::I32), + }, + Abi::Next => push_wasm(param.tref.type_(), &mut params), + }, } } for param in self.results.iter() { match &**param.tref.type_() { - Type::Builtin(BuiltinType::S8) - | Type::Builtin(BuiltinType::U8 { .. }) - | Type::Builtin(BuiltinType::S16) - | Type::Builtin(BuiltinType::U16) - | Type::Builtin(BuiltinType::S32) - | Type::Builtin(BuiltinType::U32 { .. }) - | Type::Builtin(BuiltinType::Char) + Type::List(_) + | Type::Builtin(_) | Type::Pointer(_) | Type::ConstPointer(_) - | Type::Handle(_) => results.push(WasmType::I32), - - Type::Builtin(BuiltinType::S64) | Type::Builtin(BuiltinType::U64) => { - results.push(WasmType::I64) + | Type::Record(_) + | Type::Handle(_) => { + push_wasm(param.tref.type_(), &mut results); } - Type::Builtin(BuiltinType::F32) => results.push(WasmType::F32), - Type::Builtin(BuiltinType::F64) => results.push(WasmType::F64), - - Type::Record(r) => match r.bitflags_repr() { - Some(repr) => results.push(WasmType::from(repr)), - None => unreachable!(), - }, - Type::List(_) => unreachable!(), - Type::Variant(v) => { + match self.abi { + Abi::Preview1 => {} // handled below + Abi::Next => { + push_wasm(param.tref.type_(), &mut results); + continue; + } + } results.push(match v.tag_repr { IntRepr::U64 => WasmType::I64, IntRepr::U32 | IntRepr::U16 | IntRepr::U8 => WasmType::I32, @@ -534,7 +680,21 @@ impl InterfaceFunc { } } } - (params, results) + + // Rust/C don't support multi-value well right now, so if a function + // would have multiple results then instead truncate it to have 0 + // results and instead insert a return pointer. + let mut retptr = None; + if results.len() > 1 { + params.push(WasmType::I32); + retptr = Some(mem::take(&mut results)); + } + + WasmSignature { + params, + results, + retptr, + } } /// Generates an abstract sequence of instructions which represents this @@ -553,103 +713,214 @@ impl InterfaceFunc { /// and it will also automatically convert the results of the WASI function /// back to a language-specific value. pub fn call_wasm(&self, module: &Id, bindgen: &mut impl Bindgen) { - assert_eq!(self.abi, Abi::Preview1); - Generator { - bindgen, - operands: vec![], - results: vec![], - stack: vec![], - } - .call_wasm(module, self); + Generator::new(self.abi, bindgen).call_wasm(module, self); } /// This is the dual of [`InterfaceFunc::call_wasm`], except that instead of /// calling a wasm signature it generates code to come from a wasm signature /// and call an interface types signature. pub fn call_interface(&self, module: &Id, bindgen: &mut impl Bindgen) { - assert_eq!(self.abi, Abi::Preview1); - Generator { - bindgen, - operands: vec![], - results: vec![], - stack: vec![], - } - .call_interface(module, self); + Generator::new(self.abi, bindgen).call_interface(module, self); } } struct Generator<'a, B: Bindgen> { + abi: Abi, bindgen: &'a mut B, operands: Vec, results: Vec, stack: Vec, + return_pointers: Vec, } -impl Generator<'_, B> { +impl<'a, B: Bindgen> Generator<'a, B> { + fn new(abi: Abi, bindgen: &'a mut B) -> Generator<'a, B> { + Generator { + abi, + bindgen, + operands: Vec::new(), + results: Vec::new(), + stack: Vec::new(), + return_pointers: Vec::new(), + } + } + fn call_wasm(&mut self, module: &Id, func: &InterfaceFunc) { - // Translate all parameters which are interface values by lowering them - // to their wasm types. - for (nth, param) in func.params.iter().enumerate() { + // Push all parameters for this function onto the stack, and then + // batch-lower everything all at once. + for nth in 0..func.params.len() { self.emit(&Instruction::GetArg { nth }); - self.lower(¶m.tref, None); } + self.lower_all(&func.params, None); - // If necessary for our ABI, insert return pointers for any returned - // values through a result. - assert!(func.results.len() < 2); - if let Some(result) = func.results.get(0) { - self.prep_return_pointer(&result.tref.type_()); - } + // If necessary we may need to prepare a return pointer for this ABI. + // The `Preview1` ABI has most return values returned through pointers, + // and the `Next` ABI returns more-than-one values through a return + // pointer. + let sig = func.wasm_signature(); + self.prep_return_pointer(&sig, &func.results); - let (params, results) = func.wasm_signature(); + // Now that all the wasm args are prepared we can call the actual wasm + // function. + assert_eq!(self.stack.len(), sig.params.len()); self.emit(&Instruction::CallWasm { module: module.as_str(), name: func.name.as_str(), - params: ¶ms, - results: &results, + params: &sig.params, + results: &sig.results, }); - // Lift the return value if one is present. - if let Some(result) = func.results.get(0) { - self.lift(&result.tref, true); + // In the `Next` ABI we model multiple return values by going through + // memory. Remove that indirection here by loading everything to + // simulate the function having many return values in our stack + // discipline. + if let Some(actual) = &sig.retptr { + self.load_retptr(actual); } + // Batch-lift all result values now that all the function's return + // values are on the stack. + self.lift_all(&func.results, true); + self.emit(&Instruction::Return { amt: func.results.len(), }); + assert!(self.stack.is_empty()); + } + + fn load_retptr(&mut self, types: &[WasmType]) { + assert_eq!(self.return_pointers.len(), 1); + for (i, ty) in types.iter().enumerate() { + self.stack.push(self.return_pointers[0].clone()); + let offset = (i * 8) as i32; + match ty { + WasmType::I32 => self.emit(&Instruction::I32Load { offset }), + WasmType::I64 => self.emit(&Instruction::I64Load { offset }), + WasmType::F32 => self.emit(&Instruction::F32Load { offset }), + WasmType::F64 => self.emit(&Instruction::F64Load { offset }), + } + } } fn call_interface(&mut self, module: &Id, func: &InterfaceFunc) { - // Lift all wasm parameters into interface types first. - // - // Note that consuming arguments is somewhat janky right now by manually - // giving lists a second argument for their length. In the future we'll - // probably want to refactor the `lift` function to internally know how - // to consume arguments. - let mut nth = 0; - for param in func.params.iter() { - self.emit(&Instruction::GetArg { nth }); - nth += 1; - if let Type::List(_) = &**param.tref.type_() { - self.emit(&Instruction::GetArg { nth }); - nth += 1; + // Use `GetArg` to push all relevant arguments onto the stack. Note + // that we can't use the signature of this function directly due to + // various conversions and return pointers, so we need to somewhat + // manually calculate all the arguments which are converted as + // interface types arguments below. + let sig = func.wasm_signature(); + let nargs = match self.abi { + Abi::Preview1 => { + func.params.len() + + func + .params + .iter() + .filter(|t| match &**t.tref.type_() { + Type::List(_) => true, + _ => false, + }) + .count() } - self.lift(¶m.tref, false); + Abi::Next => sig.params.len() - sig.retptr.is_some() as usize, + }; + for nth in 0..nargs { + self.emit(&Instruction::GetArg { nth }); } + // Once everything is on the stack we can lift all arguments one-by-one + // into their interface-types equivalent. + self.lift_all(&func.params, false); + + // ... and that allows us to call the interface types function ... self.emit(&Instruction::CallInterface { module: module.as_str(), func, }); - // Like above the current ABI only has at most one result, so lower it - // here if necessary. - if let Some(result) = func.results.get(0) { - self.lower(&result.tref, Some(&mut nth)); + // ... and at the end we lower everything back into return values. + self.lower_all(&func.results, Some(nargs)); + + // Our ABI dictates that a list of returned types are returned through + // memories, so after we've got all the values on the stack perform + // all of the stores here. + if let Some(tys) = &sig.retptr { + self.store_retptr(tys, sig.params.len() - 1); } - let (_params, results) = func.wasm_signature(); - self.emit(&Instruction::Return { amt: results.len() }); + self.emit(&Instruction::Return { + amt: sig.results.len(), + }); + assert!(self.stack.is_empty()); + } + + /// Assumes that the wasm values to create `tys` are all located on the + /// stack. + /// + /// Inserts instructions necesesary to lift those types into their + /// interface types equivalent. + fn lift_all(&mut self, tys: &[InterfaceFuncParam], is_return: bool) { + let mut temp = Vec::new(); + let operands = tys + .iter() + .rev() + .map(|ty| { + let ntys = match self.abi { + Abi::Preview1 => match &**ty.tref.type_() { + Type::List(_) => 2, + _ => 1, + }, + Abi::Next => { + temp.truncate(0); + push_wasm(ty.tref.type_(), &mut temp); + temp.len() + } + }; + self.stack + .drain(self.stack.len() - ntys..) + .collect::>() + }) + .collect::>(); + for (operands, ty) in operands.into_iter().rev().zip(tys) { + self.stack.extend(operands); + self.lift(&ty.tref, is_return); + } + } + + /// Assumes that the value for `tys` is already on the stack, and then + /// converts all of those values into their wasm types by lowering each + /// argument in-order. + fn lower_all(&mut self, tys: &[InterfaceFuncParam], mut nargs: Option) { + let operands = self + .stack + .drain(self.stack.len() - tys.len()..) + .collect::>(); + for (operand, ty) in operands.into_iter().zip(tys) { + self.stack.push(operand); + self.lower(&ty.tref, nargs.as_mut()); + } + } + + /// Assumes `types.len()` values are on the stack and stores them all into + /// the return pointer of this function, specified in the last argument. + /// + /// This is only used with `Abi::Next`. + fn store_retptr(&mut self, types: &[WasmType], retptr_arg: usize) { + self.emit(&Instruction::GetArg { nth: retptr_arg }); + let retptr = self.stack.pop().unwrap(); + for (i, ty) in types.iter().enumerate().rev() { + self.stack.push(retptr.clone()); + let offset = (i * 8) as i32; + match ty { + WasmType::I32 => self.emit(&Instruction::I32Store { offset }), + WasmType::I64 => self.emit(&Instruction::I64Store { offset }), + WasmType::F32 => self.emit(&Instruction::F32Store { offset }), + WasmType::F64 => self.emit(&Instruction::F64Store { offset }), + } + } + } + + fn witx(&mut self, instr: &WitxInstruction<'_>) { + self.emit(&Instruction::Witx { instr }); } fn emit(&mut self, inst: &Instruction<'_>) { @@ -680,8 +951,21 @@ impl Generator<'_, B> { self.stack.extend(self.results.drain(..)); } + fn finish_block(&mut self, size: usize) { + self.operands.clear(); + assert!( + size <= self.stack.len(), + "not enough operands on stack for finishing block", + ); + self.operands + .extend(self.stack.drain((self.stack.len() - size)..)); + self.bindgen.finish_block(&mut self.operands); + } + fn lower(&mut self, ty: &TypeRef, retptr: Option<&mut usize>) { use Instruction::*; + use WitxInstruction::*; + match &**ty.type_() { Type::Builtin(BuiltinType::S8) => self.emit(&I32FromS8), Type::Builtin(BuiltinType::U8 { lang_c_char: true }) => self.emit(&I32FromChar8), @@ -698,29 +982,48 @@ impl Generator<'_, B> { Type::Builtin(BuiltinType::S64) => self.emit(&I64FromS64), Type::Builtin(BuiltinType::U64) => self.emit(&I64FromU64), Type::Builtin(BuiltinType::Char) => self.emit(&I32FromChar), - Type::Pointer(_) => self.emit(&I32FromPointer), - Type::ConstPointer(_) => self.emit(&I32FromConstPointer), + Type::Builtin(BuiltinType::F32) => self.emit(&F32FromIf32), + Type::Builtin(BuiltinType::F64) => self.emit(&F64FromIf64), + Type::Pointer(_) => self.witx(&I32FromPointer), + Type::ConstPointer(_) => self.witx(&I32FromConstPointer), Type::Handle(_) => self.emit(&I32FromHandle { ty: match ty { TypeRef::Name(ty) => ty, _ => unreachable!(), }, }), + Type::List(_) => self.emit(&ListPointerLength), Type::Record(r) => { - let ty = match ty { - TypeRef::Name(ty) => ty, - _ => unreachable!(), - }; - match r.bitflags_repr() { - Some(IntRepr::U64) => self.emit(&I64FromBitflags { ty }), - Some(_) => self.emit(&I32FromBitflags { ty }), - None => self.emit(&AddrOf), + if let Some(repr) = r.bitflags_repr() { + let ty = match ty { + TypeRef::Name(ty) => ty, + _ => unreachable!(), + }; + match repr { + IntRepr::U64 => return self.witx(&I64FromBitflags { ty }), + _ => return self.witx(&I32FromBitflags { ty }), + } + } + match self.abi { + Abi::Preview1 => self.witx(&AddrOf), + Abi::Next => { + self.emit(&RecordLower { ty: r }); + let fields = self + .stack + .drain(self.stack.len() - r.members.len()..) + .collect::>(); + for (member, field) in r.members.iter().zip(fields) { + self.stack.push(field); + self.lower(&member.tref, None); + } + } } } - Type::Variant(v) => { + + Type::Variant(v) if self.abi == Abi::Preview1 => { // Enum-like variants are simply lowered to their discriminant. if v.is_enum() { - return self.emit(&EnumLower { + return self.witx(&EnumLower { ty: match ty { TypeRef::Name(n) => n, _ => unreachable!(), @@ -732,7 +1035,7 @@ impl Generator<'_, B> { // otherwise it's an argument and we just pass the address. let retptr = match retptr { Some(ptr) => ptr, - None => return self.emit(&AddrOf), + None => return self.witx(&AddrOf), }; // For the return position we emit some blocks to lower the @@ -748,13 +1051,13 @@ impl Generator<'_, B> { let store = |me: &mut Self, ty: &TypeRef, n| { me.emit(&GetArg { nth: *retptr + n }); match ty { - TypeRef::Name(ty) => me.emit(&Store { ty }), + TypeRef::Name(ty) => me.witx(&Store { ty }), _ => unreachable!(), } }; match &**ok.type_() { Type::Record(r) if r.is_tuple() => { - self.emit(&TupleLower { + self.witx(&TupleLower { amt: r.members.len(), }); // Note that `rev()` is used here due to the order @@ -767,56 +1070,119 @@ impl Generator<'_, B> { _ => store(self, ok, 0), } }; - self.bindgen.finish_block(None); + self.finish_block(0); self.bindgen.push_block(); - let err_expr = if let Some(ty) = err { + if let Some(ty) = err { self.emit(&VariantPayload); self.lower(ty, None); - Some(self.stack.pop().unwrap()) - } else { - None - }; - self.bindgen.finish_block(err_expr); + } + self.finish_block(err.is_some() as usize); - self.emit(&ResultLower { ok, err }); + self.witx(&ResultLower { ok, err }); + } + + Type::Variant(v) => { + let mut results = Vec::new(); + let mut temp = Vec::new(); + let mut casts = Vec::new(); + push_wasm(ty.type_(), &mut results); + for (i, case) in v.cases.iter().enumerate() { + self.bindgen.push_block(); + self.emit(&I32Const { val: i as i32 }); + let mut pushed = 1; + if let Some(ty) = &case.tref { + // Using the payload of this block we lower the type to + // raw wasm values. + self.emit(&VariantPayload); + self.lower(ty, None); + + // Determine the types of all the wasm values we just + // pushed, and record how many. If we pushed too few + // then we'll need to push some zeros after this. + temp.truncate(0); + push_wasm(ty.type_(), &mut temp); + pushed += temp.len(); + + // For all the types pushed we may need to insert some + // bitcasts. This will go through and cast everything + // to the right type to ensure all blocks produce the + // same set of results. + casts.truncate(0); + for (actual, expected) in temp.iter().zip(&results[1..]) { + casts.push(cast(*actual, *expected)); + } + if casts.iter().any(|c| *c != Bitcast::None) { + self.emit(&Bitcasts { casts: &casts }); + } + } + + // If we haven't pushed enough items in this block to match + // what other variants are pushing then we need to push + // some zeros. + if pushed < results.len() { + self.emit(&ConstZero { + tys: &results[pushed..], + }); + } + self.finish_block(results.len()); + } + self.emit(&VariantLower { + ty: v, + nresults: results.len(), + }); } - Type::Builtin(BuiltinType::F32) => self.emit(&F32FromIf32), - Type::Builtin(BuiltinType::F64) => self.emit(&F64FromIf64), - Type::List(_) => self.emit(&ListPointerLength), } } - fn prep_return_pointer(&mut self, ty: &Type) { - // Return pointers are only needed for `Result`... - let variant = match ty { - Type::Variant(v) => v, - _ => return, - }; - // ... and only if `T` is actually present in `Result` - let ok = match &variant.cases[0].tref { - Some(t) => t, - None => return, - }; + fn prep_return_pointer(&mut self, sig: &WasmSignature, results: &[InterfaceFuncParam]) { + match self.abi { + Abi::Preview1 => { + assert!(results.len() < 2); + let ty = match results.get(0) { + Some(ty) => ty.tref.type_(), + None => return, + }; + // Return pointers are only needed for `Result`... + let variant = match &**ty { + Type::Variant(v) => v, + _ => return, + }; + // ... and only if `T` is actually present in `Result` + let ok = match &variant.cases[0].tref { + Some(t) => t, + None => return, + }; - // Tuples have each individual item in a separate return pointer while - // all other types go through a singular return pointer. - let mut n = 0; - let mut prep = |ty: &TypeRef| { - match ty { - TypeRef::Name(ty) => self.bindgen.allocate_space(n, ty), - _ => unreachable!(), + // Tuples have each individual item in a separate return pointer while + // all other types go through a singular return pointer. + let mut prep = |ty: &TypeRef| { + let ptr = match ty { + TypeRef::Name(ty) => self.bindgen.allocate_typed_space(ty), + _ => unreachable!(), + }; + self.return_pointers.push(ptr.clone()); + self.stack.push(ptr); + }; + match &**ok.type_() { + Type::Record(r) if r.is_tuple() => { + for member in r.members.iter() { + prep(&member.tref); + } + } + _ => prep(ok), + } } - self.emit(&Instruction::ReturnPointerGet { n }); - n += 1; - }; - match &**ok.type_() { - Type::Record(r) if r.is_tuple() => { - for member in r.members.iter() { - prep(&member.tref); + // If a return pointer was automatically injected into this function + // then we need to allocate a proper amount of space for it and then + // add it to the stack to get passed to the callee. + Abi::Next => { + if let Some(results) = &sig.retptr { + let ptr = self.bindgen.allocate_i64_array(results.len()); + self.return_pointers.push(ptr.clone()); + self.stack.push(ptr.clone()); } } - _ => prep(ok), } } @@ -824,6 +1190,8 @@ impl Generator<'_, B> { // `lower` function above. This is intentional and should be kept this way! fn lift(&mut self, ty: &TypeRef, is_return: bool) { use Instruction::*; + use WitxInstruction::*; + match &**ty.type_() { Type::Builtin(BuiltinType::S8) => self.emit(&S8FromI32), Type::Builtin(BuiltinType::U8 { lang_c_char: true }) => self.emit(&Char8FromI32), @@ -842,24 +1210,62 @@ impl Generator<'_, B> { Type::Builtin(BuiltinType::Char) => self.emit(&CharFromI32), Type::Builtin(BuiltinType::F32) => self.emit(&If32FromF32), Type::Builtin(BuiltinType::F64) => self.emit(&If64FromF64), - Type::Pointer(ty) => self.emit(&PointerFromI32 { ty }), - Type::ConstPointer(ty) => self.emit(&ConstPointerFromI32 { ty }), + Type::Pointer(ty) => self.witx(&PointerFromI32 { ty }), + Type::ConstPointer(ty) => self.witx(&ConstPointerFromI32 { ty }), Type::Handle(_) => self.emit(&HandleFromI32 { ty: match ty { TypeRef::Name(ty) => ty, _ => unreachable!(), }, }), - Type::Variant(v) => { + Type::List(ty) => self.witx(&ListFromPointerLength { ty }), + Type::Record(r) => { + if let Some(repr) = r.bitflags_repr() { + let ty = match ty { + TypeRef::Name(ty) => ty, + _ => unreachable!(), + }; + match repr { + IntRepr::U64 => return self.witx(&BitflagsFromI64 { ty }), + _ => return self.witx(&BitflagsFromI32 { ty }), + } + } + match self.abi { + Abi::Preview1 => { + let ty = match ty { + TypeRef::Name(ty) => ty, + _ => unreachable!(), + }; + self.witx(&Load { ty }) + } + Abi::Next => { + let mut temp = Vec::new(); + push_wasm(ty.type_(), &mut temp); + let mut args = self + .stack + .drain(self.stack.len() - temp.len()..) + .collect::>(); + for member in r.members.iter() { + temp.truncate(0); + push_wasm(member.tref.type_(), &mut temp); + self.stack.extend(args.drain(..temp.len())); + self.lift(&member.tref, false); + } + self.emit(&RecordLift { ty: r }); + } + } + } + + Type::Variant(v) if self.abi == Abi::Preview1 => { if v.is_enum() { - return self.emit(&EnumLift { + return self.witx(&EnumLift { ty: match ty { TypeRef::Name(n) => n, _ => unreachable!(), }, }); } else if !is_return { - return self.emit(&Load { + return self.witx(&Load { ty: match ty { TypeRef::Name(n) => n, _ => unreachable!(), @@ -869,13 +1275,13 @@ impl Generator<'_, B> { let (ok, err) = v.as_expected().unwrap(); self.bindgen.push_block(); - let ok_expr = if let Some(ok) = ok { + if let Some(ok) = ok { let mut n = 0; let mut load = |ty: &TypeRef| { - self.emit(&ReturnPointerGet { n }); + self.stack.push(self.return_pointers[n].clone()); n += 1; match ty { - TypeRef::Name(ty) => self.emit(&Load { ty }), + TypeRef::Name(ty) => self.witx(&Load { ty }), _ => unreachable!(), } }; @@ -884,42 +1290,156 @@ impl Generator<'_, B> { for member in r.members.iter() { load(&member.tref); } - self.emit(&TupleLift { + self.witx(&TupleLift { amt: r.members.len(), }); } _ => load(ok), } - Some(self.stack.pop().unwrap()) - } else { - None - }; - self.bindgen.finish_block(ok_expr); + } + self.finish_block(ok.is_some() as usize); self.bindgen.push_block(); - let err_expr = if let Some(ty) = err { - self.emit(&ReuseReturn); + if let Some(ty) = err { + self.witx(&ReuseReturn); self.lift(ty, false); - Some(self.stack.pop().unwrap()) - } else { - None - }; - self.bindgen.finish_block(err_expr); + } + self.finish_block(err.is_some() as usize); - self.emit(&ResultLift); + self.witx(&ResultLift); } - Type::Record(r) => { - let ty = match ty { - TypeRef::Name(ty) => ty, - _ => unreachable!(), + + Type::Variant(v) => { + let mut params = Vec::new(); + let mut temp = Vec::new(); + let mut casts = Vec::new(); + push_wasm(ty.type_(), &mut params); + let block_inputs = self + .stack + .drain(self.stack.len() - params.len() + 1..) + .collect::>(); + for case in v.cases.iter() { + self.bindgen.push_block(); + if let Some(ty) = &case.tref { + // Push only the values we need for this variant onto + // the stack. + temp.truncate(0); + push_wasm(ty.type_(), &mut temp); + self.stack + .extend(block_inputs[..temp.len()].iter().cloned()); + + // Cast all the types we have on the stack to the actual + // types needed for this variant, if necessary. + casts.truncate(0); + for (actual, expected) in temp.iter().zip(¶ms[1..]) { + casts.push(cast(*expected, *actual)); + } + if casts.iter().any(|c| *c != Bitcast::None) { + self.emit(&Bitcasts { casts: &casts }); + } + + // Then recursively lift this variant's payload. + self.lift(ty, false); + } + self.finish_block(case.tref.is_some() as usize); + } + self.emit(&VariantLift { ty: v }); + } + } + } +} + +fn push_wasm(ty: &Type, result: &mut Vec) { + match ty { + Type::Builtin(BuiltinType::S8) + | Type::Builtin(BuiltinType::U8 { .. }) + | Type::Builtin(BuiltinType::S16) + | Type::Builtin(BuiltinType::U16) + | Type::Builtin(BuiltinType::S32) + | Type::Builtin(BuiltinType::U32 { .. }) + | Type::Builtin(BuiltinType::Char) + | Type::Pointer(_) + | Type::ConstPointer(_) + | Type::Handle(_) => result.push(WasmType::I32), + + Type::Builtin(BuiltinType::U64) | Type::Builtin(BuiltinType::S64) => { + result.push(WasmType::I64) + } + Type::Builtin(BuiltinType::F32) => result.push(WasmType::F32), + Type::Builtin(BuiltinType::F64) => result.push(WasmType::F64), + + Type::Record(r) => match r.bitflags_repr() { + Some(repr) => result.push(repr.into()), + None => { + for member in r.members.iter() { + push_wasm(member.tref.type_(), result); + } + } + }, + + Type::List(_) => { + result.push(WasmType::I32); + result.push(WasmType::I32); + } + + Type::Variant(v) => { + result.push(v.tag_repr.into()); + let start = result.len(); + let mut temp = Vec::new(); + + // Push each case's type onto a temporary vector, and then merge + // that vector into our final list starting at `start`. Note + // that this requires some degree of "unification" so we can + // handle things like `Result` where that turns into + // `[i32 i32]` where the second `i32` might be the `f32` + // bitcasted. + for case in v.cases.iter() { + let ty = match &case.tref { + Some(ty) => ty, + None => continue, }; - match r.bitflags_repr() { - Some(IntRepr::U64) => self.emit(&BitflagsFromI64 { ty }), - Some(_) => self.emit(&BitflagsFromI32 { ty }), - None => self.emit(&Load { ty }), + push_wasm(ty.type_(), &mut temp); + + for (i, ty) in temp.drain(..).enumerate() { + match result.get_mut(start + i) { + Some(prev) => *prev = unify(*prev, ty), + None => result.push(ty), + } } } - Type::List(ty) => self.emit(&ListFromPointerLength { ty }), } } } + +fn unify(a: WasmType, b: WasmType) -> WasmType { + use WasmType::*; + + match (a, b) { + (I64, _) | (_, I64) | (I32, F64) | (F64, I32) => I64, + + (I32, I32) | (I32, F32) | (F32, I32) => I32, + + (F32, F32) => F32, + (F64, F64) | (F32, F64) | (F64, F32) => F64, + } +} + +fn cast(from: WasmType, to: WasmType) -> Bitcast { + use WasmType::*; + + match (from, to) { + (I32, I32) | (I64, I64) | (F32, F32) | (F64, F64) => Bitcast::None, + + (I32, I64) => Bitcast::I32ToI64, + (F32, F64) => Bitcast::F32ToF64, + (F32, I32) => Bitcast::F32ToI32, + (F64, I64) => Bitcast::F64ToI64, + + (I64, I32) => Bitcast::I64ToI32, + (F64, F32) => Bitcast::F64ToF32, + (I32, F32) => Bitcast::I32ToF32, + (I64, F64) => Bitcast::I64ToF64, + + (F32, I64) | (I64, F32) | (F64, I32) | (I32, F64) => unreachable!(), + } +} diff --git a/tools/witx/src/parser.rs b/tools/witx/src/parser.rs index 33cf745ff..ee7fd46bc 100644 --- a/tools/witx/src/parser.rs +++ b/tools/witx/src/parser.rs @@ -1,4 +1,4 @@ -use crate::BuiltinType; +use crate::{Abi, BuiltinType}; use wast::parser::{Parse, Parser, Peek, Result}; ///! Parser turns s-expressions into unvalidated syntax constructs. @@ -486,7 +486,6 @@ impl<'a> Parse<'a> for RecordSyntax<'a> { fn parse(parser: Parser<'a>) -> Result { parser.parse::()?; let mut fields = Vec::new(); - fields.push(parser.parse()?); while !parser.is_empty() { fields.push(parser.parse()?); } @@ -616,7 +615,7 @@ impl<'a> Parse<'a> for ModuleSyntax<'a> { #[derive(Debug, Clone, PartialEq, Eq)] pub enum ModuleDeclSyntax<'a> { Import(ModuleImportSyntax<'a>), - Func(InterfaceFuncSyntax<'a>), + Export(InterfaceFuncSyntax<'a>), } impl<'a> Parse<'a> for ModuleDeclSyntax<'a> { @@ -626,7 +625,13 @@ impl<'a> Parse<'a> for ModuleDeclSyntax<'a> { if l.peek::() { Ok(ModuleDeclSyntax::Import(p.parse()?)) } else if l.peek::() { - Ok(ModuleDeclSyntax::Func(p.parse()?)) + Ok(ModuleDeclSyntax::Export( + InterfaceFuncSyntax::parse_interface(p)?, + )) + } else if l.peek::() { + Ok(ModuleDeclSyntax::Export(InterfaceFuncSyntax::parse_export( + p, + )?)) } else { Err(l.error()) } @@ -676,6 +681,7 @@ impl Parse<'_> for ImportTypeSyntax { #[derive(Debug, Clone)] pub struct InterfaceFuncSyntax<'a> { + pub abi: Abi, pub export: &'a str, pub export_loc: wast::Span, pub params: Vec>>, @@ -683,8 +689,8 @@ pub struct InterfaceFuncSyntax<'a> { pub noreturn: bool, } -impl<'a> Parse<'a> for InterfaceFuncSyntax<'a> { - fn parse(parser: Parser<'a>) -> Result { +impl<'a> InterfaceFuncSyntax<'a> { + fn parse_interface(parser: Parser<'a>) -> Result { parser.parse::()?; parser.parse::()?; @@ -693,6 +699,46 @@ impl<'a> Parse<'a> for InterfaceFuncSyntax<'a> { Ok((p.cur_span(), p.parse()?)) })?; + let (params, results, noreturn) = InterfaceFuncSyntax::parse_func_params(parser)?; + + Ok(InterfaceFuncSyntax { + abi: Abi::Preview1, + export, + export_loc, + params, + results, + noreturn, + }) + } + + fn parse_export(parser: Parser<'a>) -> Result { + parser.parse::()?; + + let export_loc = parser.cur_span(); + let export = parser.parse()?; + + let (params, results, noreturn) = parser.parens(|p| { + p.parse::()?; + InterfaceFuncSyntax::parse_func_params(parser) + })?; + + Ok(InterfaceFuncSyntax { + abi: Abi::Next, + export, + export_loc, + params, + results, + noreturn, + }) + } + + fn parse_func_params( + parser: Parser<'a>, + ) -> Result<( + Vec>>, + Vec>>, + bool, + )> { let mut params = Vec::new(); let mut results = Vec::new(); let mut noreturn = false; @@ -717,14 +763,7 @@ impl<'a> Parse<'a> for InterfaceFuncSyntax<'a> { } } } - - Ok(InterfaceFuncSyntax { - export, - export_loc, - params, - results, - noreturn, - }) + Ok((params, results, noreturn)) } } @@ -733,6 +772,7 @@ enum InterfaceFuncField<'a> { Result(FieldSyntax<'a>), Noreturn, } + impl<'a> Parse<'a> for InterfaceFuncField<'a> { fn parse(parser: Parser<'a>) -> Result { parser.parens(|p| { diff --git a/tools/witx/src/validate.rs b/tools/witx/src/validate.rs index cf4d0ad7f..ebd9388cf 100644 --- a/tools/witx/src/validate.rs +++ b/tools/witx/src/validate.rs @@ -5,7 +5,7 @@ use crate::{ HandleSyntax, ImportTypeSyntax, ModuleDeclSyntax, RecordSyntax, TupleSyntax, TypedefSyntax, UnionSyntax, VariantSyntax, }, - Abi, BuiltinType, Case, Constant, Definition, Document, Entry, HandleDatatype, Id, IntRepr, + BuiltinType, Case, Constant, Definition, Document, Entry, HandleDatatype, Id, IntRepr, InterfaceFunc, InterfaceFuncParam, Location, Module, ModuleDefinition, ModuleEntry, ModuleImport, ModuleImportVariant, NamedType, RecordDatatype, RecordKind, RecordMember, Type, TypeRef, Variant, @@ -57,6 +57,8 @@ pub enum ValidationError { reason: String, location: Location, }, + #[error("Variant has zero cases")] + ZeroCaseVariant { location: Location }, } impl ValidationError { @@ -70,6 +72,7 @@ impl ValidationError { | Abi { location, .. } | AnonymousRecord { location, .. } | UnionSizeMismatch { location, .. } + | ZeroCaseVariant { location } | InvalidUnionField { location, .. } | InvalidUnionTag { location, .. } => { format!("{}\n{}", location.highlight_source_with(witxio), &self) @@ -503,6 +506,11 @@ impl DocValidationScope<'_> { syntax: &VariantSyntax, span: wast::Span, ) -> Result { + if syntax.cases.len() == 0 { + return Err(ValidationError::ZeroCaseVariant { + location: self.location(span), + }); + } let (tag_repr, names) = self.union_tag_repr(&syntax.tag, span)?; if let Some(names) = &names { @@ -659,7 +667,7 @@ impl<'a> ModuleValidation<'a> { .insert(name, ModuleEntry::Import(Rc::downgrade(&rc_import))); Ok(ModuleDefinition::Import(rc_import)) } - ModuleDeclSyntax::Func(syntax) => { + ModuleDeclSyntax::Export(syntax) => { let loc = self.doc.location(syntax.export_loc); let name = self.scope.introduce(syntax.export, loc)?; let mut argnames = IdentValidation::new(); @@ -699,14 +707,15 @@ impl<'a> ModuleValidation<'a> { }) .collect::, _>>()?; let noreturn = syntax.noreturn; - let abi = Abi::Preview1; - abi.validate(¶ms, &results) + syntax + .abi + .validate(¶ms, &results) .map_err(|reason| ValidationError::Abi { reason, location: self.doc.location(syntax.export_loc), })?; let rc_func = Rc::new(InterfaceFunc { - abi, + abi: syntax.abi, name: name.clone(), params, results, diff --git a/tools/witx/tests/witxt.rs b/tools/witx/tests/witxt.rs index 7c002fe1b..e0b9b8aa2 100644 --- a/tools/witx/tests/witxt.rs +++ b/tools/witx/tests/witxt.rs @@ -203,22 +203,28 @@ impl WitxtRunner<'_> { let module = doc.modules().next().ok_or_else(|| anyhow!("no modules"))?; let func = module.funcs().next().ok_or_else(|| anyhow!("no funcs"))?; - let (params, results) = func.wasm_signature(); - if params != wasm_params { - bail!("expected params {:?}, found {:?}", wasm_params, params); + let sig = func.wasm_signature(); + if sig.params != wasm_params { + bail!("expected params {:?}, found {:?}", wasm_params, sig.params); } - if results != wasm_results { - bail!("expected results {:?}, found {:?}", wasm_results, results); + if sig.results != wasm_results { + bail!( + "expected results {:?}, found {:?}", + wasm_results, + sig.results + ); } let mut check = AbiBindgen { abi: wasm.instrs.iter(), err: None, contents, + next_rp: 0, }; func.call_wasm(&module.name, &mut check); check.check()?; check.abi = interface.instrs.iter(); + check.next_rp = 0; func.call_interface(&module.name, &mut check); check.check()?; } @@ -283,6 +289,7 @@ struct AbiBindgen<'a> { abi: std::slice::Iter<'a, (wast::Span, &'a str)>, err: Option, contents: &'a str, + next_rp: usize, } impl AbiBindgen<'_> { @@ -302,11 +309,11 @@ impl AbiBindgen<'_> { Some((span, s)) => { let (line, col) = span.linecol_in(self.contents); self.err = Some(anyhow!( - "line {}:{} - expected `{}` found `{}`", + "line {}:{} - expected `{}` but adapter produced `{}`", line + 1, col + 1, - name, s, + name, )); } None => { @@ -317,20 +324,47 @@ impl AbiBindgen<'_> { } } } + + fn assert_op(&mut self, op: &Operand) { + match op { + Operand::Op(n) => self.assert(&format!("arg{}", n)), + Operand::Ret(n) => self.assert(&format!("ret{}", n)), + Operand::Field(n) => self.assert(&format!("field.{}", n)), + Operand::Rp(n) => self.assert(&format!("rp{}", n)), + Operand::InstResult => {} + } + } + + fn assert_mem(&mut self, inst: &str, offset: i32) { + self.assert(inst); + self.assert(&format!("offset={}", offset)); + } +} + +#[derive(Clone)] +enum Operand { + Op(usize), + Ret(usize), + Field(String), + Rp(usize), + InstResult, } impl witx::Bindgen for AbiBindgen<'_> { - type Operand = (); + type Operand = Operand; fn emit( &mut self, inst: &Instruction<'_>, - _operands: &mut Vec, + operands: &mut Vec, results: &mut Vec, ) { use witx::Instruction::*; + use witx::WitxInstruction::*; match inst { - GetArg { nth } => self.assert(&format!("get-arg{}", nth)), - AddrOf => self.assert("addr-of"), + GetArg { nth } => { + self.assert(&format!("get-arg{}", nth)); + results.push(Operand::Op(*nth)); + } I32FromChar => self.assert("i32.from_char"), I64FromU64 => self.assert("i64.from_u64"), I64FromS64 => self.assert("i64.from_s64"), @@ -342,15 +376,25 @@ impl witx::Bindgen for AbiBindgen<'_> { I32FromU8 => self.assert("i32.from_u8"), I32FromS8 => self.assert("i32.from_s8"), I32FromChar8 => self.assert("i32.from_char8"), - I32FromPointer => self.assert("i32.from_pointer"), - I32FromConstPointer => self.assert("i32.from_const_pointer"), I32FromHandle { .. } => self.assert("i32.from_handle"), - ListPointerLength => self.assert("list.pointer_length"), - ListFromPointerLength { .. } => self.assert("list.from_pointer_length"), F32FromIf32 => self.assert("f32.from_if32"), F64FromIf64 => self.assert("f64.from_if64"), - CallWasm { .. } => self.assert("call.wasm"), - CallInterface { .. } => self.assert("call.interface"), + ListPointerLength => self.assert("list.pointer_length"), + CallWasm { + results: call_results, + .. + } => { + self.assert("call.wasm"); + for i in 0..call_results.len() { + results.push(Operand::Ret(i)); + } + } + CallInterface { func, .. } => { + self.assert("call.interface"); + for i in 0..func.results.len() { + results.push(Operand::Ret(i)); + } + } S8FromI32 => self.assert("s8.from_i32"), U8FromI32 => self.assert("u8.from_i32"), S16FromI32 => self.assert("s16.from_i32"), @@ -365,39 +409,103 @@ impl witx::Bindgen for AbiBindgen<'_> { If32FromF32 => self.assert("if32.from_f32"), If64FromF64 => self.assert("if64.from_f64"), HandleFromI32 { .. } => self.assert("handle.from_i32"), - PointerFromI32 { .. } => self.assert("pointer.from_i32"), - ConstPointerFromI32 { .. } => self.assert("const_pointer.from_i32"), - ReturnPointerGet { n } => self.assert(&format!("return_pointer.get{}", n)), - ResultLift => self.assert("result.lift"), - ResultLower { .. } => self.assert("result.lower"), - EnumLift { .. } => self.assert("enum.lift"), - EnumLower { .. } => self.assert("enum.lower"), - TupleLift { .. } => self.assert("tuple.lift"), - TupleLower { .. } => self.assert("tuple.lower"), - ReuseReturn => self.assert("reuse_return"), - Load { .. } => self.assert("load"), - Store { .. } => self.assert("store"), Return { .. } => self.assert("return"), VariantPayload => self.assert("variant-payload"), - I32FromBitflags { .. } => self.assert("i32.from_bitflags"), - BitflagsFromI32 { .. } => self.assert("bitflags.from_i32"), - I64FromBitflags { .. } => self.assert("i64.from_bitflags"), - BitflagsFromI64 { .. } => self.assert("bitflags.from_i64"), + RecordLift { .. } => self.assert("record-lift"), + RecordLower { ty } => { + self.assert("record-lower"); + for member in ty.members.iter() { + results.push(Operand::Field(member.name.as_str().to_string())); + } + } + + I32Const { val } => { + self.assert("i32.const"); + self.assert(&val.to_string()); + } + VariantLower { .. } => self.assert("variant-lower"), + VariantLift { .. } => self.assert("variant-lift"), + Bitcasts { casts } => { + for (cast, operand) in casts.iter().zip(operands.drain(..)) { + match cast { + witx::Bitcast::None => self.assert("nocast"), + witx::Bitcast::I32ToI64 => self.assert("i32-to-i64"), + witx::Bitcast::F32ToF64 => self.assert("f32-to-f64"), + witx::Bitcast::F32ToI32 => self.assert("f32-to-i32"), + witx::Bitcast::F64ToI64 => self.assert("f64-to-i64"), + witx::Bitcast::I64ToI32 => self.assert("i64-to-i32"), + witx::Bitcast::F64ToF32 => self.assert("f64-to-f32"), + witx::Bitcast::I32ToF32 => self.assert("i32-to-f32"), + witx::Bitcast::I64ToF64 => self.assert("i64-to-f64"), + } + self.assert_op(&operand); + } + } + ConstZero { tys } => { + for ty in tys.iter() { + match ty { + witx::WasmType::I32 => self.assert("i32.const"), + witx::WasmType::I64 => self.assert("i64.const"), + witx::WasmType::F32 => self.assert("f32.const"), + witx::WasmType::F64 => self.assert("f64.const"), + } + self.assert("0"); + } + } + I32Load { offset } => self.assert_mem("i32.load", *offset), + I64Load { offset } => self.assert_mem("i64.load", *offset), + F32Load { offset } => self.assert_mem("f32.load", *offset), + F64Load { offset } => self.assert_mem("f64.load", *offset), + I32Store { offset } => self.assert_mem("i32.store", *offset), + I64Store { offset } => self.assert_mem("i64.store", *offset), + F32Store { offset } => self.assert_mem("f32.store", *offset), + F64Store { offset } => self.assert_mem("f64.store", *offset), + Witx { instr } => match instr { + I32FromBitflags { .. } => self.assert("i32.from_bitflags"), + BitflagsFromI32 { .. } => self.assert("bitflags.from_i32"), + I64FromBitflags { .. } => self.assert("i64.from_bitflags"), + BitflagsFromI64 { .. } => self.assert("bitflags.from_i64"), + PointerFromI32 { .. } => self.assert("pointer.from_i32"), + ConstPointerFromI32 { .. } => self.assert("const_pointer.from_i32"), + ResultLift => self.assert("result.lift"), + ResultLower { .. } => self.assert("result.lower"), + EnumLift { .. } => self.assert("enum.lift"), + EnumLower { .. } => self.assert("enum.lower"), + TupleLift { .. } => self.assert("tuple.lift"), + TupleLower { .. } => self.assert("tuple.lower"), + ReuseReturn => self.assert("reuse_return"), + ListFromPointerLength { .. } => self.assert("list.from_pointer_length"), + Load { .. } => self.assert("load"), + Store { .. } => self.assert("store"), + I32FromPointer => self.assert("i32.from_pointer"), + I32FromConstPointer => self.assert("i32.from_const_pointer"), + AddrOf => self.assert("addr-of"), + }, + } + + for op in operands.iter() { + self.assert_op(op); } - for _ in 0..inst.results_len() { - results.push(()); + + while results.len() < inst.results_len() { + results.push(Operand::InstResult); } } - fn allocate_space(&mut self, _: usize, _: &witx::NamedType) { - self.assert("allocate-space"); + fn allocate_typed_space(&mut self, _: &witx::NamedType) -> Self::Operand { + self.next_rp += 1; + Operand::Rp(self.next_rp - 1) + } + + fn allocate_i64_array(&mut self, _: usize) -> Self::Operand { + Operand::Rp(0) } fn push_block(&mut self) { self.assert("block.push"); } - fn finish_block(&mut self, _operand: Option) { + fn finish_block(&mut self, _operands: &mut Vec) { self.assert("block.finish"); } } @@ -645,9 +753,13 @@ impl<'a> Parse<'a> for Abi<'a> { let mut instrs = Vec::new(); while !parser.is_empty() { instrs.push(parser.step(|cursor| { - let (kw, next) = cursor - .keyword() - .ok_or_else(|| cursor.error("expected keyword"))?; + let (kw, next) = match cursor.keyword() { + Some(pair) => pair, + None => match cursor.integer() { + Some((i, next)) => (i.src(), next), + None => return Err(cursor.error("expected keyword or integer")), + }, + }; Ok(((cursor.cur_span(), kw), next)) })?); } diff --git a/tools/witx/tests/witxt/abi-next.witxt b/tools/witx/tests/witxt/abi-next.witxt new file mode 100644 index 000000000..d4ec448c4 --- /dev/null +++ b/tools/witx/tests/witxt/abi-next.witxt @@ -0,0 +1,671 @@ +(assert_abi + (witx (module $x (export "f" (func)))) + (wasm) + (call_wasm call.wasm return) + (call_interface call.interface return) +) + +;; scalar arguments +(assert_abi + (witx (module $x (export "f" (func (param $p u8))))) + (wasm (param i32)) + (call_wasm get-arg0 i32.from_u8 arg0 call.wasm return) + (call_interface get-arg0 u8.from_i32 arg0 call.interface return) +) +(assert_abi + (witx (module $x (export "f" (func (param $p s8))))) + (wasm (param i32)) + (call_wasm get-arg0 i32.from_s8 arg0 call.wasm return) + (call_interface get-arg0 s8.from_i32 arg0 call.interface return) +) +(assert_abi + (witx (module $x (export "f" (func (param $p u16))))) + (wasm (param i32)) + (call_wasm get-arg0 i32.from_u16 arg0 call.wasm return) + (call_interface get-arg0 u16.from_i32 arg0 call.interface return) +) +(assert_abi + (witx (module $x (export "f" (func (param $p s16))))) + (wasm (param i32)) + (call_wasm get-arg0 i32.from_s16 arg0 call.wasm return) + (call_interface get-arg0 s16.from_i32 arg0 call.interface return) +) +(assert_abi + (witx (module $x (export "f" (func (param $p u32))))) + (wasm (param i32)) + (call_wasm get-arg0 i32.from_u32 arg0 call.wasm return) + (call_interface get-arg0 u32.from_i32 arg0 call.interface return) +) +(assert_abi + (witx (module $x (export "f" (func (param $p s32))))) + (wasm (param i32)) + (call_wasm get-arg0 i32.from_s32 arg0 call.wasm return) + (call_interface get-arg0 s32.from_i32 arg0 call.interface return) +) +(assert_abi + (witx (module $x (export "f" (func (param $p u64))))) + (wasm (param i64)) + (call_wasm get-arg0 i64.from_u64 arg0 call.wasm return) + (call_interface get-arg0 u64.from_i64 arg0 call.interface return) +) +(assert_abi + (witx (module $x (export "f" (func (param $p s64))))) + (wasm (param i64)) + (call_wasm get-arg0 i64.from_s64 arg0 call.wasm return) + (call_interface get-arg0 s64.from_i64 arg0 call.interface return) +) +(assert_abi + (witx (module $x (export "f" (func (param $p f32))))) + (wasm (param f32)) + (call_wasm get-arg0 f32.from_if32 arg0 call.wasm return) + (call_interface get-arg0 if32.from_f32 arg0 call.interface return) +) +(assert_abi + (witx (module $x (export "f" (func (param $p f64))))) + (wasm (param f64)) + (call_wasm get-arg0 f64.from_if64 arg0 call.wasm return) + (call_interface get-arg0 if64.from_f64 arg0 call.interface return) +) +(assert_abi + (witx (module $x (export "f" (func (param $p char))))) + (wasm (param i32)) + (call_wasm get-arg0 i32.from_char arg0 call.wasm return) + (call_interface get-arg0 char.from_i32 arg0 call.interface return) +) + +;; handle argument +(assert_abi + (witx + (typename $y (handle)) + (module $x (export "f" (func (param $p $y)))) + ) + (wasm (param i32)) + (call_wasm get-arg0 i32.from_handle arg0 call.wasm return) + (call_interface get-arg0 handle.from_i32 arg0 call.interface return) +) + +;; record arguments +(assert_abi + (witx + (typename $y (record)) + (module $x (export "f" (func (param $p $y)))) + ) + (wasm) + (call_wasm get-arg0 record-lower arg0 call.wasm return) + (call_interface record-lift call.interface return) +) + +(assert_abi + (witx + (typename $y (record (field $a u32))) + (module $x (export "f" (func (param $p $y)))) + ) + (wasm (param i32)) + (call_wasm + get-arg0 + record-lower arg0 + i32.from_u32 field.a + call.wasm + return) + (call_interface + get-arg0 + u32.from_i32 arg0 + record-lift + call.interface + return) +) +(assert_abi + (witx + (typename $y (record + (field $a u32) + (field $b s64) + )) + (module $x (export "f" (func (param $p $y)))) + ) + (wasm (param i32 i64)) + (call_wasm + get-arg0 + record-lower arg0 + i32.from_u32 field.a + i64.from_s64 field.b + call.wasm + return) + (call_interface + get-arg0 + get-arg1 + u32.from_i32 arg0 + s64.from_i64 arg1 + record-lift + call.interface + return) +) +(assert_abi + (witx + (typename $empty (record)) + (typename $tuple (tuple f64 u32)) + (typename $y (record + (field $a u32) + (field $b $empty) + (field $c $tuple) + )) + (module $x (export "f" (func (param $p $y)))) + ) + (wasm (param i32 f64 i32)) + (call_wasm + get-arg0 + record-lower arg0 + i32.from_u32 field.a + record-lower field.b + record-lower field.c + f64.from_if64 field.0 + i32.from_u32 field.1 + call.wasm + return) + (call_interface + get-arg0 + get-arg1 + get-arg2 + u32.from_i32 arg0 + record-lift ;; empty struct lift + if64.from_f64 arg1 + u32.from_i32 arg2 + record-lift ;; tuple lift + record-lift ;; final struct lift + call.interface + return) +) + +;; variant arguments +(assert_abi + (witx + (typename $y (enum $a $b)) + (module $x (export "f" (func (param $p $y)))) + ) + (wasm (param i32)) + (call_wasm + get-arg0 + block.push + i32.const 0 + block.finish + block.push + i32.const 1 + block.finish + variant-lower arg0 + call.wasm + return) + (call_interface + get-arg0 + block.push + block.finish + block.push + block.finish + variant-lift arg0 + call.interface + return) +) + +(assert_abi + (witx + (typename $y (union u32 f32)) + (module $x (export "f" (func (param $p $y)))) + ) + (wasm (param i32 i32)) + (call_wasm + get-arg0 + block.push + i32.const 0 + variant-payload + i32.from_u32 + block.finish + block.push + i32.const 1 + variant-payload + f32.from_if32 + f32-to-i32 + block.finish + variant-lower arg0 + call.wasm + return) + (call_interface + get-arg0 + get-arg1 + block.push + u32.from_i32 arg1 + block.finish + block.push + i32-to-f32 arg1 + if32.from_f32 + block.finish + variant-lift arg0 + call.interface + return) +) + +(assert_abi + (witx + (typename $y (variant (case $none) (case $some f64))) + (module $x (export "f" (func (param $p $y)))) + ) + (wasm (param i32 f64)) + (call_wasm + get-arg0 + block.push + i32.const 0 + f64.const 0 + block.finish + block.push + i32.const 1 + variant-payload + f64.from_if64 + block.finish + variant-lower arg0 + call.wasm + return) + (call_interface + get-arg0 + get-arg1 + block.push + block.finish + block.push + if64.from_f64 arg1 + block.finish + variant-lift arg0 + call.interface + return) +) + +;; list arguments +(assert_abi + (witx + (module $x (export "f" (func (param $p (list u8))))) + ) + (wasm (param i32 i32)) + (call_wasm + get-arg0 + list.pointer_length arg0 + call.wasm + return) + (call_interface + get-arg0 + get-arg1 + list.from_pointer_length arg0 arg1 + call.interface + return) +) + +;; flavorful parameters +(assert_abi + (witx + (typename $a (union (list u8) f32)) + (typename $b (tuple f64 bool)) + (typename $c (record + (field $a $a) + (field $b $b) + )) + + (typename $d (tuple f32 f64)) + (typename $e (variant + (case $a) + (case $b $d) + (case $c) + )) + (module $x (export "f" (func + (param $p1 $c) + (param $p2 s32) + (param $p3 $e) + ))) + ) + (wasm (param i32 i32 i32 f64 i32 i32 i32 f32 f64)) + (call_wasm + get-arg0 + get-arg1 + get-arg2 + + ;; conversion of $p1 + record-lower arg0 + ;; conversion of $c.a + block.push + i32.const 0 + variant-payload + list.pointer_length + block.finish + block.push + i32.const 1 + variant-payload + f32.from_if32 + f32-to-i32 + i32.const 0 + block.finish + variant-lower field.a + + ;; conversion of $c.b + record-lower field.b + f64.from_if64 field.0 + block.push + i32.const 0 + block.finish + block.push + i32.const 1 + block.finish + variant-lower field.1 + + ;; conversion of $p2 + i32.from_s32 arg1 + + ;; conversion of $p3 + block.push + i32.const 0 + f32.const 0 + f64.const 0 + block.finish + block.push + i32.const 1 + variant-payload + record-lower + f32.from_if32 field.0 + f64.from_if64 field.1 + block.finish + block.push + i32.const 2 + f32.const 0 + f64.const 0 + block.finish + variant-lower arg2 + call.wasm + return) + + (call_interface + get-arg0 + get-arg1 + get-arg2 + get-arg3 + get-arg4 + get-arg5 + get-arg6 + get-arg7 + get-arg8 + + ;; decoding p1 + ;; decode $a + block.push + list.from_pointer_length arg1 arg2 + block.finish + block.push + i32-to-f32 arg1 + if32.from_f32 + block.finish + variant-lift arg0 ;; lift into $a + + if64.from_f64 arg3 ;; decode $b field 0 + block.push + block.finish + block.push + block.finish + variant-lift arg4 ;; decode $b field 1's boolean + record-lift ;; lift into $b + + record-lift ;; lift into $c + + ;; decoding $p2 + s32.from_i32 arg5 + + ;; decoding $p3 + block.push + block.finish + block.push + if32.from_f32 arg7 + if64.from_f64 arg8 + record-lift ;; lift into $d + block.finish + block.push + block.finish + variant-lift arg6 ;; lift into $e + + call.interface + + return) +) + +;; scalar results +(assert_abi + (witx (module $x (export "f" (func (result $p u8))))) + (wasm (result i32)) + (call_wasm call.wasm u8.from_i32 ret0 return) + (call_interface call.interface i32.from_u8 ret0 return) +) +(assert_abi + (witx (module $x (export "f" (func (result $p s8))))) + (wasm (result i32)) + (call_wasm call.wasm s8.from_i32 ret0 return) + (call_interface call.interface i32.from_s8 ret0 return) +) +(assert_abi + (witx (module $x (export "f" (func (result $p u16))))) + (wasm (result i32)) + (call_wasm call.wasm u16.from_i32 ret0 return) + (call_interface call.interface i32.from_u16 ret0 return) +) +(assert_abi + (witx (module $x (export "f" (func (result $p s16))))) + (wasm (result i32)) + (call_wasm call.wasm s16.from_i32 ret0 return) + (call_interface call.interface i32.from_s16 ret0 return) +) +(assert_abi + (witx (module $x (export "f" (func (result $p u32))))) + (wasm (result i32)) + (call_wasm call.wasm u32.from_i32 ret0 return) + (call_interface call.interface i32.from_u32 ret0 return) +) +(assert_abi + (witx (module $x (export "f" (func (result $p s32))))) + (wasm (result i32)) + (call_wasm call.wasm s32.from_i32 ret0 return) + (call_interface call.interface i32.from_s32 ret0 return) +) +(assert_abi + (witx (module $x (export "f" (func (result $p u64))))) + (wasm (result i64)) + (call_wasm call.wasm u64.from_i64 ret0 return) + (call_interface call.interface i64.from_u64 ret0 return) +) +(assert_abi + (witx (module $x (export "f" (func (result $p s64))))) + (wasm (result i64)) + (call_wasm call.wasm s64.from_i64 ret0 return) + (call_interface call.interface i64.from_s64 ret0 return) +) +(assert_abi + (witx (module $x (export "f" (func (result $p f32))))) + (wasm (result f32)) + (call_wasm call.wasm if32.from_f32 ret0 return) + (call_interface call.interface f32.from_if32 ret0 return) +) +(assert_abi + (witx (module $x (export "f" (func (result $p f64))))) + (wasm (result f64)) + (call_wasm call.wasm if64.from_f64 ret0 return) + (call_interface call.interface f64.from_if64 ret0 return) +) +(assert_abi + (witx (module $x (export "f" (func (result $p char))))) + (wasm (result i32)) + (call_wasm call.wasm char.from_i32 ret0 return) + (call_interface call.interface i32.from_char ret0 return) +) + +;; handle result +(assert_abi + (witx + (typename $y (handle)) + (module $x (export "f" (func (result $p $y)))) + ) + (wasm (result i32)) + (call_wasm call.wasm handle.from_i32 ret0 return) + (call_interface call.interface i32.from_handle ret0 return) +) + +;; record results +(assert_abi + (witx + (typename $y (record)) + (module $x (export "f" (func (result $p $y)))) + ) + (wasm) + (call_wasm call.wasm record-lift return) + (call_interface call.interface record-lower ret0 return) +) +(assert_abi + (witx + (typename $y (record (field $a s32))) + (module $x (export "f" (func (result $p $y)))) + ) + (wasm (result i32)) + (call_wasm call.wasm s32.from_i32 ret0 record-lift return) + (call_interface + call.interface + record-lower ret0 + i32.from_s32 field.a + return) +) +(assert_abi + (witx + (typename $y (record (field $a s32) (field $b u32))) + (module $x (export "f" (func (result $p $y)))) + ) + (wasm (param i32)) + (call_wasm + call.wasm rp0 + i32.load offset=0 rp0 + i32.load offset=8 rp0 + s32.from_i32 + u32.from_i32 + record-lift return) + (call_interface + call.interface + record-lower ret0 + i32.from_s32 field.a + i32.from_u32 field.b + get-arg0 + i32.store offset=8 arg0 + i32.store offset=0 arg0 + return) +) + +;; variant results +(assert_abi + (witx + (typename $y (enum $a $b)) + (module $x (export "f" (func (result $p $y)))) + ) + (wasm (result i32)) + (call_wasm + call.wasm + block.push + block.finish + block.push + block.finish + variant-lift ret0 + return) + (call_interface + call.interface + block.push + i32.const 0 + block.finish + block.push + i32.const 1 + block.finish + variant-lower ret0 + return) +) + +(assert_abi + (witx + (typename $y (variant (case $a f32) (case $b))) + (module $x (export "f" (func (result $p $y)))) + ) + (wasm (param i32)) + (call_wasm + call.wasm rp0 + i32.load offset=0 rp0 + f32.load offset=8 rp0 + block.push + if32.from_f32 + block.finish + block.push + block.finish + variant-lift + return) + (call_interface + call.interface + block.push + i32.const 0 + variant-payload + f32.from_if32 + block.finish + block.push + i32.const 1 + f32.const 0 + block.finish + variant-lower ret0 + get-arg0 + f32.store offset=8 arg0 + i32.store offset=0 arg0 + return) +) + +;; multiple results +(assert_abi + (witx + (module $x (export "f" (func (result $a s32) (result $b f64) (result $c bool)))) + ) + (wasm (param i32)) + (call_wasm + call.wasm rp0 + i32.load offset=0 rp0 + f64.load offset=8 rp0 + i32.load offset=16 rp0 + + s32.from_i32 + if64.from_f64 + block.push + block.finish + block.push + block.finish + variant-lift + return + ) + (call_interface + call.interface + i32.from_s32 ret0 + f64.from_if64 ret1 + block.push + i32.const 0 + block.finish + block.push + i32.const 1 + block.finish + variant-lower ret2 + + get-arg0 + + i32.store offset=16 arg0 + f64.store offset=8 arg0 + i32.store offset=0 arg0 + return + ) +) + +;; invalid +(assert_invalid + (witx (module $x (export "f" (func (result $x (@witx char8)))))) + "cannot use `(@witx char8)" +) +(assert_invalid + (witx (module $x (export "f" (func (result $x (@witx pointer u8)))))) + "cannot use `(@witx pointer)" +) +(assert_invalid + (witx (module $x (export "f" (func (result $x (@witx const_pointer u8)))))) + "cannot use `(@witx const_pointer)" +) diff --git a/tools/witx/tests/witxt/abi.witxt b/tools/witx/tests/witxt/abi.witxt index ccdad7bf4..ae2b42d8e 100644 --- a/tools/witx/tests/witxt/abi.witxt +++ b/tools/witx/tests/witxt/abi.witxt @@ -9,92 +9,92 @@ (assert_abi (witx (module $x (@interface func (export "f") (param $p u8)))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_u8 call.wasm return) - (call_interface get-arg0 u8.from_i32 call.interface return) + (call_wasm get-arg0 i32.from_u8 arg0 call.wasm return) + (call_interface get-arg0 u8.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p s8)))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_s8 call.wasm return) - (call_interface get-arg0 s8.from_i32 call.interface return) + (call_wasm get-arg0 i32.from_s8 arg0 call.wasm return) + (call_interface get-arg0 s8.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p u16)))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_u16 call.wasm return) - (call_interface get-arg0 u16.from_i32 call.interface return) + (call_wasm get-arg0 i32.from_u16 arg0 call.wasm return) + (call_interface get-arg0 u16.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p s16)))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_s16 call.wasm return) - (call_interface get-arg0 s16.from_i32 call.interface return) + (call_wasm get-arg0 i32.from_s16 arg0 call.wasm return) + (call_interface get-arg0 s16.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p u32)))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_u32 call.wasm return) - (call_interface get-arg0 u32.from_i32 call.interface return) + (call_wasm get-arg0 i32.from_u32 arg0 call.wasm return) + (call_interface get-arg0 u32.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p s32)))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_s32 call.wasm return) - (call_interface get-arg0 s32.from_i32 call.interface return) + (call_wasm get-arg0 i32.from_s32 arg0 call.wasm return) + (call_interface get-arg0 s32.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p u64)))) (wasm (param i64)) - (call_wasm get-arg0 i64.from_u64 call.wasm return) - (call_interface get-arg0 u64.from_i64 call.interface return) + (call_wasm get-arg0 i64.from_u64 arg0 call.wasm return) + (call_interface get-arg0 u64.from_i64 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p s64)))) (wasm (param i64)) - (call_wasm get-arg0 i64.from_s64 call.wasm return) - (call_interface get-arg0 s64.from_i64 call.interface return) + (call_wasm get-arg0 i64.from_s64 arg0 call.wasm return) + (call_interface get-arg0 s64.from_i64 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p f32)))) (wasm (param f32)) - (call_wasm get-arg0 f32.from_if32 call.wasm return) - (call_interface get-arg0 if32.from_f32 call.interface return) + (call_wasm get-arg0 f32.from_if32 arg0 call.wasm return) + (call_interface get-arg0 if32.from_f32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p f64)))) (wasm (param f64)) - (call_wasm get-arg0 f64.from_if64 call.wasm return) - (call_interface get-arg0 if64.from_f64 call.interface return) + (call_wasm get-arg0 f64.from_if64 arg0 call.wasm return) + (call_interface get-arg0 if64.from_f64 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p (@witx usize))))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_usize call.wasm return) - (call_interface get-arg0 usize.from_i32 call.interface return) + (call_wasm get-arg0 i32.from_usize arg0 call.wasm return) + (call_interface get-arg0 usize.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p (@witx char8))))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_char8 call.wasm return) - (call_interface get-arg0 char8.from_i32 call.interface return) + (call_wasm get-arg0 i32.from_char8 arg0 call.wasm return) + (call_interface get-arg0 char8.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p char)))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_char call.wasm return) - (call_interface get-arg0 char.from_i32 call.interface return) + (call_wasm get-arg0 i32.from_char arg0 call.wasm return) + (call_interface get-arg0 char.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p (@witx pointer u8))))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_pointer call.wasm return) - (call_interface get-arg0 pointer.from_i32 call.interface return) + (call_wasm get-arg0 i32.from_pointer arg0 call.wasm return) + (call_interface get-arg0 pointer.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p (@witx const_pointer u8))))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_const_pointer call.wasm return) - (call_interface get-arg0 const_pointer.from_i32 call.interface return) + (call_wasm get-arg0 i32.from_const_pointer arg0 call.wasm return) + (call_interface get-arg0 const_pointer.from_i32 arg0 call.interface return) ) ;; flags parameter @@ -104,8 +104,8 @@ (module $x (@interface func (export "f") (param $p $a))) ) (wasm (param i32)) - (call_wasm get-arg0 i32.from_bitflags call.wasm return) - (call_interface get-arg0 bitflags.from_i32 call.interface return) + (call_wasm get-arg0 i32.from_bitflags arg0 call.wasm return) + (call_interface get-arg0 bitflags.from_i32 arg0 call.interface return) ) (assert_abi (witx @@ -113,8 +113,8 @@ (module $x (@interface func (export "f") (param $p $a))) ) (wasm (param i64)) - (call_wasm get-arg0 i64.from_bitflags call.wasm return) - (call_interface get-arg0 bitflags.from_i64 call.interface return) + (call_wasm get-arg0 i64.from_bitflags arg0 call.wasm return) + (call_interface get-arg0 bitflags.from_i64 arg0 call.interface return) ) ;; struct parameter @@ -124,8 +124,8 @@ (module $x (@interface func (export "f") (param $p $a))) ) (wasm (param i32)) - (call_wasm get-arg0 addr-of call.wasm return) - (call_interface get-arg0 load call.interface return) + (call_wasm get-arg0 addr-of arg0 call.wasm return) + (call_interface get-arg0 load arg0 call.interface return) ) ;; handle parameter @@ -135,8 +135,8 @@ (module $x (@interface func (export "f") (param $p $a))) ) (wasm (param i32)) - (call_wasm get-arg0 i32.from_handle call.wasm return) - (call_interface get-arg0 handle.from_i32 call.interface return) + (call_wasm get-arg0 i32.from_handle arg0 call.wasm return) + (call_interface get-arg0 handle.from_i32 arg0 call.interface return) ) ;; list parameter @@ -145,8 +145,13 @@ (module $x (@interface func (export "f") (param $p (list u8)))) ) (wasm (param i32 i32)) - (call_wasm get-arg0 list.pointer_length call.wasm return) - (call_interface get-arg0 get-arg1 list.from_pointer_length call.interface return) + (call_wasm get-arg0 list.pointer_length arg0 call.wasm return) + (call_interface + get-arg0 + get-arg1 + list.from_pointer_length arg0 arg1 + call.interface + return) ) ;; variant parameter -- some not allowed at this time @@ -156,8 +161,8 @@ (module $x (@interface func (export "f") (param $p $a))) ) (wasm (param i32)) - (call_wasm get-arg0 enum.lower call.wasm return) - (call_interface get-arg0 enum.lift call.interface return) + (call_wasm get-arg0 enum.lower arg0 call.wasm return) + (call_interface get-arg0 enum.lift arg0 call.interface return) ) (assert_abi (witx @@ -165,111 +170,111 @@ (module $x (@interface func (export "f") (param $p $a))) ) (wasm (param i32)) - (call_wasm get-arg0 addr-of call.wasm return) - (call_interface get-arg0 load call.interface return) + (call_wasm get-arg0 addr-of arg0 call.wasm return) + (call_interface get-arg0 load arg0 call.interface return) ) ;; scalar returns (assert_abi (witx (module $x (@interface func (export "f") (result $p u8)))) (wasm (result i32)) - (call_wasm call.wasm u8.from_i32 return) - (call_interface call.interface i32.from_u8 return) + (call_wasm call.wasm u8.from_i32 ret0 return) + (call_interface call.interface i32.from_u8 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p s8)))) (wasm (result i32)) - (call_wasm call.wasm s8.from_i32 return) - (call_interface call.interface i32.from_s8 return) + (call_wasm call.wasm s8.from_i32 ret0 return) + (call_interface call.interface i32.from_s8 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p u16)))) (wasm (result i32)) - (call_wasm call.wasm u16.from_i32 return) - (call_interface call.interface i32.from_u16 return) + (call_wasm call.wasm u16.from_i32 ret0 return) + (call_interface call.interface i32.from_u16 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p s16)))) (wasm (result i32)) - (call_wasm call.wasm s16.from_i32 return) - (call_interface call.interface i32.from_s16 return) + (call_wasm call.wasm s16.from_i32 ret0 return) + (call_interface call.interface i32.from_s16 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p u32)))) (wasm (result i32)) - (call_wasm call.wasm u32.from_i32 return) - (call_interface call.interface i32.from_u32 return) + (call_wasm call.wasm u32.from_i32 ret0 return) + (call_interface call.interface i32.from_u32 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p s32)))) (wasm (result i32)) - (call_wasm call.wasm s32.from_i32 return) - (call_interface call.interface i32.from_s32 return) + (call_wasm call.wasm s32.from_i32 ret0 return) + (call_interface call.interface i32.from_s32 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p u64)))) (wasm (result i64)) - (call_wasm call.wasm u64.from_i64 return) - (call_interface call.interface i64.from_u64 return) + (call_wasm call.wasm u64.from_i64 ret0 return) + (call_interface call.interface i64.from_u64 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p s64)))) (wasm (result i64)) - (call_wasm call.wasm s64.from_i64 return) - (call_interface call.interface i64.from_s64 return) + (call_wasm call.wasm s64.from_i64 ret0 return) + (call_interface call.interface i64.from_s64 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p f32)))) (wasm (result f32)) - (call_wasm call.wasm if32.from_f32 return) - (call_interface call.interface f32.from_if32 return) + (call_wasm call.wasm if32.from_f32 ret0 return) + (call_interface call.interface f32.from_if32 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p f64)))) (wasm (result f64)) - (call_wasm call.wasm if64.from_f64 return) - (call_interface call.interface f64.from_if64 return) + (call_wasm call.wasm if64.from_f64 ret0 return) + (call_interface call.interface f64.from_if64 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p (@witx usize))))) (wasm (result i32)) - (call_wasm call.wasm usize.from_i32 return) - (call_interface call.interface i32.from_usize return) + (call_wasm call.wasm usize.from_i32 ret0 return) + (call_interface call.interface i32.from_usize ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p (@witx char8))))) (wasm (result i32)) - (call_wasm call.wasm char8.from_i32 return) - (call_interface call.interface i32.from_char8 return) + (call_wasm call.wasm char8.from_i32 ret0 return) + (call_interface call.interface i32.from_char8 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p char)))) (wasm (result i32)) - (call_wasm call.wasm char.from_i32 return) - (call_interface call.interface i32.from_char return) + (call_wasm call.wasm char.from_i32 ret0 return) + (call_interface call.interface i32.from_char ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p (@witx pointer u8))))) (wasm (result i32)) - (call_wasm call.wasm pointer.from_i32 return) - (call_interface call.interface i32.from_pointer return) + (call_wasm call.wasm pointer.from_i32 ret0 return) + (call_interface call.interface i32.from_pointer ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p (@witx const_pointer u8))))) (wasm (result i32)) - (call_wasm call.wasm const_pointer.from_i32 return) - (call_interface call.interface i32.from_const_pointer return) + (call_wasm call.wasm const_pointer.from_i32 ret0 return) + (call_interface call.interface i32.from_const_pointer ret0 return) ) -;; flags return +;; flags ret0 return (assert_abi (witx (typename $a (flags $x $y)) (module $x (@interface func (export "f") (result $p $a))) ) (wasm (result i32)) - (call_wasm call.wasm bitflags.from_i32 return) - (call_interface call.interface i32.from_bitflags return) + (call_wasm call.wasm bitflags.from_i32 ret0 return) + (call_interface call.interface i32.from_bitflags ret0 return) ) (assert_abi (witx @@ -277,19 +282,19 @@ (module $x (@interface func (export "f") (result $p $a))) ) (wasm (result i64)) - (call_wasm call.wasm bitflags.from_i64 return) - (call_interface call.interface i64.from_bitflags return) + (call_wasm call.wasm bitflags.from_i64 ret0 return) + (call_interface call.interface i64.from_bitflags ret0 return) ) -;; handle return +;; handle ret0 return (assert_abi (witx (typename $a (handle)) (module $x (@interface func (export "f") (result $p $a))) ) (wasm (result i32)) - (call_wasm call.wasm handle.from_i32 return) - (call_interface call.interface i32.from_handle return) + (call_wasm call.wasm handle.from_i32 ret0 return) + (call_interface call.interface i32.from_handle ret0 return) ) ;; struct return -- not supported @@ -360,7 +365,7 @@ enum.lift block.finish ;; consumes 2 blocks and uses the return value of the call to discriminate - result.lift + result.lift ret0 return) (call_interface @@ -377,7 +382,7 @@ block.finish ;; consume the 2 blocks and lower based on the call - result.lower + result.lower ret0 return) ) @@ -392,16 +397,11 @@ (wasm (param i32) (result i32)) (call_wasm - ;; make space for the return value and push its pointer - allocate-space - return_pointer.get0 - - call.wasm + call.wasm rp0 ;; ok block, load the return pointer and have it be the result for the `Ok` block.push - return_pointer.get0 - load + load rp0 block.finish block.push @@ -409,7 +409,7 @@ enum.lift block.finish - result.lift + result.lift ret0 return) (call_interface @@ -419,7 +419,7 @@ block.push variant-payload get-arg0 - store + store arg0 block.finish block.push @@ -427,7 +427,7 @@ enum.lower block.finish - result.lower + result.lower ret0 return) ) @@ -443,18 +443,11 @@ (wasm (param i32 i32) (result i32)) (call_wasm - allocate-space - return_pointer.get0 - allocate-space - return_pointer.get1 - - call.wasm + call.wasm rp0 rp1 block.push - return_pointer.get0 - load - return_pointer.get1 - load + load rp0 + load rp1 tuple.lift block.finish @@ -463,7 +456,7 @@ enum.lift block.finish - result.lift + result.lift ret0 return) (call_interface @@ -475,9 +468,9 @@ variant-payload tuple.lower get-arg1 - store + store arg1 get-arg0 - store + store arg0 block.finish block.push @@ -485,6 +478,6 @@ enum.lower block.finish - result.lower + result.lower ret0 return) ) From abc2c9eee205c6dc0dce0b62f488a1563b507113 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 22 Feb 2021 17:32:34 -0800 Subject: [PATCH 02/22] Implement reading/writing to/from memory This is necessary to implement lists-of-lists properly with translation/validation. --- tools/witx/src/abi.rs | 287 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 282 insertions(+), 5 deletions(-) diff --git a/tools/witx/src/abi.rs b/tools/witx/src/abi.rs index 96f28f7e2..3347bb8fc 100644 --- a/tools/witx/src/abi.rs +++ b/tools/witx/src/abi.rs @@ -170,9 +170,50 @@ def_instruction! { I32FromChar8 : [1] => [1], /// Converts a language-specific handle value to a wasm `i32`. I32FromHandle { ty: &'a NamedType } : [1] => [1], - /// Converts an interface type list into its pointer/length, pushing - /// them both on the stack. - ListPointerLength : [1] => [2], + /// Lowers a list whose elements can be directly copied and interpreted + /// with no validation whatsoever. + /// + /// Pops a list value from the stack and pushes the pointer/length onto + /// the stack. Note that this consumes the list and the list must be an + /// owned allocation. + ListCanonLower { element: &'a TypeRef } : [1] => [2], + /// Lowers a list whose elements are copied one-by-one into a new list. + /// + /// Pops a list value from the stack and pushes the pointer/length onto + /// the stack. Note that this consumes the list as an owned allocation + /// and must produce an owned allocation. + /// + /// This operation also pops a block from the block stack which is used + /// as the iteration body of writing each element of the list consumed. + ListLower { element: &'a TypeRef } : [1] => [2], + /// Lifts a list which has a canonical representation into an interface + /// types value. + /// + /// This will consume two `i32` values from the stack, a pointer and a + /// length, and then produces an interface value list. Note that the + /// pointer/length popped are **owned** and need to be deallocated when + /// the interface type is dropped. + ListCanonLift { element: &'a TypeRef } : [2] => [1], + /// Lifts a list which into an interface types value. + /// + /// This will consume two `i32` values from the stack, a pointer and a + /// length, and then produces an interface value list. Note that the + /// pointer/length popped are **owned** and need to be deallocated when + /// the interface type is dropped. + /// + /// This will also pop a block from the block stack which is how to + /// read each individual element from the list. + ListLift { element: &'a TypeRef } : [2] => [1], + /// Pushes an operand onto the stack representing the list item from + /// each iteration of the list. + /// + /// This is only used inside of blocks related to lowering lists. + IterElem : [0] => [1], + /// Pushes an operand onto the stack representing the base pointer of + /// the next element in a list. + /// + /// This is sused for both lifting and lowering lists. + IterBasePointer : [0] => [1], /// Conversion an interface type `f32` value to a wasm `f32`. /// /// This may be a noop for some implementations, but it's here in case the @@ -281,6 +322,22 @@ def_instruction! { /// Pops an `i32` from the stack and loads a little-endian `i32` from /// it, using the specified constant offset. I32Load { offset: i32 } : [1] => [1], + /// Pops an `i32` from the stack and loads a little-endian `i8` from + /// it, using the specified constant offset. The value loaded is the + /// zero-extended to 32-bits + I32Load8U { offset: i32 } : [1] => [1], + /// Pops an `i32` from the stack and loads a little-endian `i8` from + /// it, using the specified constant offset. The value loaded is the + /// sign-extended to 32-bits + I32Load8S { offset: i32 } : [1] => [1], + /// Pops an `i32` from the stack and loads a little-endian `i16` from + /// it, using the specified constant offset. The value loaded is the + /// zero-extended to 32-bits + I32Load16U { offset: i32 } : [1] => [1], + /// Pops an `i32` from the stack and loads a little-endian `i16` from + /// it, using the specified constant offset. The value loaded is the + /// sign-extended to 32-bits + I32Load16S { offset: i32 } : [1] => [1], /// Pops an `i32` from the stack and loads a little-endian `i64` from /// it, using the specified constant offset. I64Load { offset: i32 } : [1] => [1], @@ -290,10 +347,19 @@ def_instruction! { /// Pops an `i32` from the stack and loads a little-endian `f64` from /// it, using the specified constant offset. F64Load { offset: i32 } : [1] => [1], + /// Pops an `i32` address from the stack and then an `i32` value. /// Stores the value in little-endian at the pointer specified plus the /// constant `offset`. I32Store { offset: i32 } : [2] => [0], + /// Pops an `i32` address from the stack and then an `i32` value. + /// Stores the low 8 bits of the value in little-endian at the pointer + /// specified plus the constant `offset`. + I32Store8 { offset: i32 } : [2] => [0], + /// Pops an `i32` address from the stack and then an `i32` value. + /// Stores the low 16 bits of the value in little-endian at the pointer + /// specified plus the constant `offset`. + I32Store16 { offset: i32 } : [2] => [0], /// Pops an `i32` address from the stack and then an `i64` value. /// Stores the value in little-endian at the pointer specified plus the /// constant `offset`. @@ -353,6 +419,8 @@ def_instruction! { /// memory to the start of the list and the second operand is the /// length. ListFromPointerLength { ty: &'a TypeRef } : [2] => [1], + /// Pushes the pointer/length of a list as two `i32` parameters. + ListPointerLength : [1] => [2], /// Converts a native wasm `i32` to a language-specific pointer. PointerFromI32 { ty: &'a TypeRef }: [1] => [1], @@ -992,7 +1060,22 @@ impl<'a, B: Bindgen> Generator<'a, B> { _ => unreachable!(), }, }), - Type::List(_) => self.emit(&ListPointerLength), + Type::List(element) => match self.abi { + Abi::Preview1 => self.witx(&ListPointerLength), + Abi::Next => { + if type_all_bits_valid(element.type_()) { + self.emit(&ListCanonLower { element }); + } else { + self.bindgen.push_block(); + self.emit(&IterElem); + self.emit(&IterBasePointer); + let addr = self.stack.pop().unwrap(); + self.write_to_memory(element, addr, 0); + self.finish_block(0); + self.emit(&ListLower { element }); + } + } + }, Type::Record(r) => { if let Some(repr) = r.bitflags_repr() { let ty = match ty { @@ -1218,7 +1301,21 @@ impl<'a, B: Bindgen> Generator<'a, B> { _ => unreachable!(), }, }), - Type::List(ty) => self.witx(&ListFromPointerLength { ty }), + Type::List(element) => match self.abi { + Abi::Preview1 => self.witx(&ListFromPointerLength { ty: element }), + Abi::Next => { + if type_all_bits_valid(element.type_()) { + self.emit(&ListCanonLift { element }); + } else { + self.bindgen.push_block(); + self.emit(&IterBasePointer); + let addr = self.stack.pop().unwrap(); + self.read_from_memory(element, addr, 0); + self.finish_block(1); + self.emit(&ListLift { element }); + } + } + }, Type::Record(r) => { if let Some(repr) = r.bitflags_repr() { let ty = match ty { @@ -1347,6 +1444,160 @@ impl<'a, B: Bindgen> Generator<'a, B> { } } } + + fn write_to_memory(&mut self, ty: &TypeRef, addr: B::Operand, offset: i32) { + use Instruction::*; + + match &**ty.type_() { + // Builtin types need different flavors of storage instructions + // depending on the size of the value written. + Type::Builtin(b) => { + self.lower(ty, None); + self.stack.push(addr); + match b { + BuiltinType::S8 | BuiltinType::U8 { .. } => self.emit(&I32Store8 { offset }), + BuiltinType::S16 | BuiltinType::U16 => self.emit(&I32Store16 { offset }), + BuiltinType::S32 | BuiltinType::U32 { .. } | BuiltinType::Char => { + self.emit(&I32Store { offset }) + } + BuiltinType::S64 | BuiltinType::U64 => self.emit(&I64Store { offset }), + BuiltinType::F32 => self.emit(&F32Store { offset }), + BuiltinType::F64 => self.emit(&F64Store { offset }), + } + } + + // Lowering all these types produces an `i32` which we can easily + // store into memory. + Type::Pointer(_) | Type::ConstPointer(_) | Type::Handle(_) => { + self.lower(ty, None); + self.stack.push(addr); + self.emit(&I32Store { offset }) + } + + // After lowering the list there's two i32 values on the stack + // which we write into memory, writing the pointer into the low address + // and the length into the high address. + Type::List(_) => { + self.lower(ty, None); + self.stack.push(addr.clone()); + self.emit(&I32Store { offset: offset + 4 }); + self.stack.push(addr); + self.emit(&I32Store { offset }); + } + + // Decompose the record into its components and then write all the + // components into memory one-by-one. + Type::Record(r) => { + self.emit(&RecordLower { ty: r }); + let fields = self + .stack + .drain(self.stack.len() - r.members.len()..) + .collect::>(); + for (layout, field) in r.member_layout().iter().zip(fields) { + self.stack.push(field); + self.write_to_memory( + &layout.member.tref, + addr.clone(), + offset + (layout.offset as i32), + ); + } + } + + // Each case will get its own block, and the first item in each + // case is writing the discriminant. After that if we have a + // payload we write the payload after the discriminant, aligned up + // to the type's alignment. + Type::Variant(v) => { + let payload_offset = offset + (v.payload_offset() as i32); + for (i, case) in v.cases.iter().enumerate() { + self.bindgen.push_block(); + self.emit(&I32Const { val: i as i32 }); + self.stack.push(addr.clone()); + self.emit(&I32Store { offset }); + if let Some(ty) = &case.tref { + self.emit(&VariantPayload); + self.write_to_memory(ty, addr.clone(), payload_offset); + } + self.finish_block(0); + } + self.emit(&VariantLower { ty: v, nresults: 0 }); + } + } + } + + fn read_from_memory(&mut self, ty: &TypeRef, addr: B::Operand, offset: i32) { + use Instruction::*; + + match &**ty.type_() { + // Builtin types need different flavors of load instructions + // depending on the size of the value written, but then they're all + // lifted the same way. + Type::Builtin(b) => { + self.stack.push(addr); + match b { + BuiltinType::S8 => self.emit(&I32Load8S { offset }), + BuiltinType::U8 { .. } => self.emit(&I32Load8U { offset }), + BuiltinType::S16 => self.emit(&I32Load16S { offset }), + BuiltinType::U16 => self.emit(&I32Load16U { offset }), + BuiltinType::S32 | BuiltinType::U32 { .. } | BuiltinType::Char => { + self.emit(&I32Load { offset }) + } + BuiltinType::S64 | BuiltinType::U64 => self.emit(&I64Load { offset }), + BuiltinType::F32 => self.emit(&F32Load { offset }), + BuiltinType::F64 => self.emit(&F64Load { offset }), + } + self.lift(ty, false); + } + + // These types are all easily lifted from an `i32` + Type::Pointer(_) | Type::ConstPointer(_) | Type::Handle(_) => { + self.stack.push(addr); + self.emit(&I32Load { offset }); + self.lift(ty, false); + } + + // Read the pointer/len and then perform the standard lifting + // proceses. + Type::List(_) => { + self.stack.push(addr.clone()); + self.emit(&I32Load { offset }); + self.stack.push(addr); + self.emit(&I32Load { offset: offset + 4 }); + self.lift(ty, false); + } + + // Read and lift each field individually, adjusting the offset + // as we go along, then aggregate all the fields into the record. + Type::Record(r) => { + for layout in r.member_layout() { + self.read_from_memory( + &layout.member.tref, + addr.clone(), + offset + (layout.offset as i32), + ); + } + self.emit(&RecordLift { ty: r }); + } + + // Each case will get its own block, and we'll dispatch to the + // right block based on the `i32.load` we initially perform. Each + // individual block is pretty simple and just reads the payload type + // from the corresponding offset if one is available. + Type::Variant(v) => { + self.stack.push(addr.clone()); + self.emit(&I32Load { offset }); + let payload_offset = offset + (v.payload_offset() as i32); + for case in v.cases.iter() { + self.bindgen.push_block(); + if let Some(ty) = &case.tref { + self.read_from_memory(ty, addr.clone(), payload_offset); + } + self.finish_block(case.tref.is_some() as usize); + } + self.emit(&VariantLift { ty: v }); + } + } + } } fn push_wasm(ty: &Type, result: &mut Vec) { @@ -1443,3 +1694,29 @@ fn cast(from: WasmType, to: WasmType) -> Bitcast { (F32, I64) | (I64, F32) | (F64, I32) | (I32, F64) => unreachable!(), } } + +fn type_all_bits_valid(ty: &Type) -> bool { + match ty { + Type::Record(r) => r + .members + .iter() + .all(|t| type_all_bits_valid(t.tref.type_())), + + Type::Builtin(BuiltinType::Char) | Type::Variant(_) | Type::Handle(_) | Type::List(_) => { + false + } + + Type::Builtin(BuiltinType::U8 { .. }) + | Type::Builtin(BuiltinType::S8) + | Type::Builtin(BuiltinType::U16) + | Type::Builtin(BuiltinType::S16) + | Type::Builtin(BuiltinType::U32 { .. }) + | Type::Builtin(BuiltinType::S32) + | Type::Builtin(BuiltinType::U64) + | Type::Builtin(BuiltinType::S64) + | Type::Builtin(BuiltinType::F32) + | Type::Builtin(BuiltinType::F64) + | Type::Pointer(_) + | Type::ConstPointer(_) => true, + } +} From 5e6c8430b68cb60308475d3b9a4d5c4656afbcfa Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 22 Feb 2021 17:42:58 -0800 Subject: [PATCH 03/22] Add some tests for read/write memory --- tools/witx/tests/witxt.rs | 16 +++- tools/witx/tests/witxt/abi-next.witxt | 120 +++++++++++++++++++++++++- 2 files changed, 131 insertions(+), 5 deletions(-) diff --git a/tools/witx/tests/witxt.rs b/tools/witx/tests/witxt.rs index e0b9b8aa2..7ba393669 100644 --- a/tools/witx/tests/witxt.rs +++ b/tools/witx/tests/witxt.rs @@ -379,7 +379,6 @@ impl witx::Bindgen for AbiBindgen<'_> { I32FromHandle { .. } => self.assert("i32.from_handle"), F32FromIf32 => self.assert("f32.from_if32"), F64FromIf64 => self.assert("f64.from_if64"), - ListPointerLength => self.assert("list.pointer_length"), CallWasm { results: call_results, .. @@ -453,13 +452,27 @@ impl witx::Bindgen for AbiBindgen<'_> { } } I32Load { offset } => self.assert_mem("i32.load", *offset), + I32Load8U { offset } => self.assert_mem("i32.load8u", *offset), + I32Load8S { offset } => self.assert_mem("i32.load8s", *offset), + I32Load16U { offset } => self.assert_mem("i32.load16u", *offset), + I32Load16S { offset } => self.assert_mem("i32.load16s", *offset), I64Load { offset } => self.assert_mem("i64.load", *offset), F32Load { offset } => self.assert_mem("f32.load", *offset), F64Load { offset } => self.assert_mem("f64.load", *offset), I32Store { offset } => self.assert_mem("i32.store", *offset), + I32Store8 { offset } => self.assert_mem("i32.store8", *offset), + I32Store16 { offset } => self.assert_mem("i32.store16", *offset), I64Store { offset } => self.assert_mem("i64.store", *offset), F32Store { offset } => self.assert_mem("f32.store", *offset), F64Store { offset } => self.assert_mem("f64.store", *offset), + + ListCanonLower { .. } => self.assert("list.canon_lower"), + ListLower { .. } => self.assert("list.lower"), + ListCanonLift { .. } => self.assert("list.canon_lift"), + ListLift { .. } => self.assert("list.lift"), + IterElem => self.assert("iter-elem"), + IterBasePointer => self.assert("iter-base-pointer"), + Witx { instr } => match instr { I32FromBitflags { .. } => self.assert("i32.from_bitflags"), BitflagsFromI32 { .. } => self.assert("bitflags.from_i32"), @@ -475,6 +488,7 @@ impl witx::Bindgen for AbiBindgen<'_> { TupleLower { .. } => self.assert("tuple.lower"), ReuseReturn => self.assert("reuse_return"), ListFromPointerLength { .. } => self.assert("list.from_pointer_length"), + ListPointerLength => self.assert("list.pointer_length"), Load { .. } => self.assert("load"), Store { .. } => self.assert("store"), I32FromPointer => self.assert("i32.from_pointer"), diff --git a/tools/witx/tests/witxt/abi-next.witxt b/tools/witx/tests/witxt/abi-next.witxt index d4ec448c4..217da71be 100644 --- a/tools/witx/tests/witxt/abi-next.witxt +++ b/tools/witx/tests/witxt/abi-next.witxt @@ -282,13 +282,13 @@ (wasm (param i32 i32)) (call_wasm get-arg0 - list.pointer_length arg0 + list.canon_lower arg0 call.wasm return) (call_interface get-arg0 get-arg1 - list.from_pointer_length arg0 arg1 + list.canon_lift arg0 arg1 call.interface return) ) @@ -327,7 +327,7 @@ block.push i32.const 0 variant-payload - list.pointer_length + list.canon_lower block.finish block.push i32.const 1 @@ -388,7 +388,7 @@ ;; decoding p1 ;; decode $a block.push - list.from_pointer_length arg1 arg2 + list.canon_lift arg1 arg2 block.finish block.push i32-to-f32 arg1 @@ -669,3 +669,115 @@ (witx (module $x (export "f" (func (result $x (@witx const_pointer u8)))))) "cannot use `(@witx const_pointer)" ) + +;; fancy list parameters +(assert_abi + (witx + (module $x (export "f" (func (param $a (list (tuple u32 u32)))))) + ) + (wasm (param i32 i32)) + (call_wasm + get-arg0 + list.canon_lower arg0 + call.wasm + return + ) + (call_interface + get-arg0 + get-arg1 + list.canon_lift arg0 arg1 + call.interface + return + ) +) +(assert_abi + (witx + (module $x (export "f" (func (param $a (list bool))))) + ) + (wasm (param i32 i32)) + (call_wasm + get-arg0 + block.push + iter-elem + iter-base-pointer + block.push + i32.const 0 + i32.store offset=0 + block.finish + block.push + i32.const 1 + i32.store offset=0 + block.finish + variant-lower + block.finish + list.lower arg0 + call.wasm + return + ) + (call_interface + get-arg0 + get-arg1 + block.push + iter-base-pointer + i32.load offset=0 + block.push + block.finish + block.push + block.finish + variant-lift + block.finish + list.lift arg0 arg1 + call.interface + return + ) +) +(assert_abi + (witx + (typename $a (record (field $x bool) (field $y (list u8)))) + (module $x (export "f" (func (param $a (list $a))))) + ) + (wasm (param i32 i32)) + (call_wasm + get-arg0 + block.push + iter-elem + iter-base-pointer + record-lower + block.push + i32.const 0 + i32.store offset=0 + block.finish + block.push + i32.const 1 + i32.store offset=0 + block.finish + variant-lower field.x + list.canon_lower field.y + i32.store offset=8 + i32.store offset=4 + block.finish + list.lower arg0 + call.wasm + return + ) + (call_interface + get-arg0 + get-arg1 + block.push + iter-base-pointer + i32.load offset=0 + block.push + block.finish + block.push + block.finish + variant-lift + i32.load offset=4 + i32.load offset=8 + list.canon_lift + record-lift + block.finish + list.lift arg0 arg1 + call.interface + return + ) +) From c008c8e6d7ff7efc1b3cffefdb724b18940518e8 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 23 Feb 2021 14:24:54 -0800 Subject: [PATCH 04/22] Fill out a bit and fix a few bugs --- tools/witx/src/abi.rs | 171 +++++++++++++++----------- tools/witx/src/ast.rs | 7 ++ tools/witx/tests/witxt.rs | 4 +- tools/witx/tests/witxt/abi-next.witxt | 17 +++ 4 files changed, 125 insertions(+), 74 deletions(-) diff --git a/tools/witx/src/abi.rs b/tools/witx/src/abi.rs index 3347bb8fc..0c4b74f3b 100644 --- a/tools/witx/src/abi.rs +++ b/tools/witx/src/abi.rs @@ -176,7 +176,10 @@ def_instruction! { /// Pops a list value from the stack and pushes the pointer/length onto /// the stack. Note that this consumes the list and the list must be an /// owned allocation. - ListCanonLower { element: &'a TypeRef } : [1] => [2], + ListCanonLower { + element: &'a TypeRef, + malloc: String, + } : [1] => [2], /// Lowers a list whose elements are copied one-by-one into a new list. /// /// Pops a list value from the stack and pushes the pointer/length onto @@ -185,7 +188,10 @@ def_instruction! { /// /// This operation also pops a block from the block stack which is used /// as the iteration body of writing each element of the list consumed. - ListLower { element: &'a TypeRef } : [1] => [2], + ListLower { + element: &'a TypeRef, + malloc: String, + } : [1] => [2], /// Lifts a list which has a canonical representation into an interface /// types value. /// @@ -193,7 +199,10 @@ def_instruction! { /// length, and then produces an interface value list. Note that the /// pointer/length popped are **owned** and need to be deallocated when /// the interface type is dropped. - ListCanonLift { element: &'a TypeRef } : [2] => [1], + ListCanonLift { + element: &'a TypeRef, + free: String, + } : [2] => [1], /// Lifts a list which into an interface types value. /// /// This will consume two `i32` values from the stack, a pointer and a @@ -203,7 +212,10 @@ def_instruction! { /// /// This will also pop a block from the block stack which is how to /// read each individual element from the list. - ListLift { element: &'a TypeRef } : [2] => [1], + ListLift { + element: &'a TypeRef, + free: String, + } : [2] => [1], /// Pushes an operand onto the stack representing the list item from /// each iteration of the list. /// @@ -289,10 +301,16 @@ def_instruction! { /// Pops a record value off the stack, decomposes the record to all of /// its fields, and then pushes the fields onto the stack. - RecordLower { ty: &'a RecordDatatype } : [1] => [ty.members.len()], + RecordLower { + ty: &'a RecordDatatype, + name: Option<&'a NamedType>, + } : [1] => [ty.members.len()], /// Pops all fields for a record off the stack and then composes them /// into a record. - RecordLift { ty: &'a RecordDatatype } : [ty.members.len()] => [1], + RecordLift { + ty: &'a RecordDatatype, + name: Option<&'a NamedType>, + } : [ty.members.len()] => [1], /// This is a special instruction used at the entry of blocks used as /// part of `ResultLower`, representing that the payload of that variant @@ -304,13 +322,17 @@ def_instruction! { /// from the stack to produce `nresults` of items. VariantLower { ty: &'a Variant, + name: Option<&'a NamedType>, nresults: usize, } : [1] => [*nresults], /// Pops an `i32` off the stack as well as `ty.cases.len()` blocks /// from the code generator. Uses each of those blocks and the value /// from the stack to produce a final variant. - VariantLift { ty: &'a Variant } : [1] => [1], + VariantLift { + ty: &'a Variant, + name: Option<&'a NamedType>, + } : [1] => [1], /// Casts the top N items on the stack using the `Bitcast` enum /// provided. Consumes the same number of operands that this produces. @@ -383,17 +405,19 @@ def_instruction! { #[derive(Debug, PartialEq)] pub enum Bitcast { - // Bitcasts + // Upcasts F32ToF64, F32ToI32, F64ToI64, I32ToI64, + F32ToI64, - // Bitcasts + // Downcasts F64ToF32, I32ToF32, I64ToF64, I64ToI32, + I64ToF32, None, } @@ -1055,16 +1079,14 @@ impl<'a, B: Bindgen> Generator<'a, B> { Type::Pointer(_) => self.witx(&I32FromPointer), Type::ConstPointer(_) => self.witx(&I32FromConstPointer), Type::Handle(_) => self.emit(&I32FromHandle { - ty: match ty { - TypeRef::Name(ty) => ty, - _ => unreachable!(), - }, + ty: ty.name().unwrap(), }), Type::List(element) => match self.abi { Abi::Preview1 => self.witx(&ListPointerLength), Abi::Next => { - if type_all_bits_valid(element.type_()) { - self.emit(&ListCanonLower { element }); + let malloc = String::from("witx_malloc"); + if type_all_bits_valid(element) || is_char(element) { + self.emit(&ListCanonLower { element, malloc }); } else { self.bindgen.push_block(); self.emit(&IterElem); @@ -1072,16 +1094,13 @@ impl<'a, B: Bindgen> Generator<'a, B> { let addr = self.stack.pop().unwrap(); self.write_to_memory(element, addr, 0); self.finish_block(0); - self.emit(&ListLower { element }); + self.emit(&ListLower { element, malloc }); } } }, Type::Record(r) => { if let Some(repr) = r.bitflags_repr() { - let ty = match ty { - TypeRef::Name(ty) => ty, - _ => unreachable!(), - }; + let ty = ty.name().unwrap(); match repr { IntRepr::U64 => return self.witx(&I64FromBitflags { ty }), _ => return self.witx(&I32FromBitflags { ty }), @@ -1090,7 +1109,10 @@ impl<'a, B: Bindgen> Generator<'a, B> { match self.abi { Abi::Preview1 => self.witx(&AddrOf), Abi::Next => { - self.emit(&RecordLower { ty: r }); + self.emit(&RecordLower { + ty: r, + name: ty.name(), + }); let fields = self .stack .drain(self.stack.len() - r.members.len()..) @@ -1107,10 +1129,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { // Enum-like variants are simply lowered to their discriminant. if v.is_enum() { return self.witx(&EnumLower { - ty: match ty { - TypeRef::Name(n) => n, - _ => unreachable!(), - }, + ty: ty.name().unwrap(), }); } @@ -1133,10 +1152,9 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&VariantPayload); let store = |me: &mut Self, ty: &TypeRef, n| { me.emit(&GetArg { nth: *retptr + n }); - match ty { - TypeRef::Name(ty) => me.witx(&Store { ty }), - _ => unreachable!(), - } + me.witx(&Store { + ty: ty.name().unwrap(), + }); }; match &**ok.type_() { Type::Record(r) if r.is_tuple() => { @@ -1213,6 +1231,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&VariantLower { ty: v, nresults: results.len(), + name: ty.name(), }); } } @@ -1240,10 +1259,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { // Tuples have each individual item in a separate return pointer while // all other types go through a singular return pointer. let mut prep = |ty: &TypeRef| { - let ptr = match ty { - TypeRef::Name(ty) => self.bindgen.allocate_typed_space(ty), - _ => unreachable!(), - }; + let ptr = self.bindgen.allocate_typed_space(ty.name().unwrap()); self.return_pointers.push(ptr.clone()); self.stack.push(ptr); }; @@ -1296,32 +1312,27 @@ impl<'a, B: Bindgen> Generator<'a, B> { Type::Pointer(ty) => self.witx(&PointerFromI32 { ty }), Type::ConstPointer(ty) => self.witx(&ConstPointerFromI32 { ty }), Type::Handle(_) => self.emit(&HandleFromI32 { - ty: match ty { - TypeRef::Name(ty) => ty, - _ => unreachable!(), - }, + ty: ty.name().unwrap(), }), Type::List(element) => match self.abi { Abi::Preview1 => self.witx(&ListFromPointerLength { ty: element }), Abi::Next => { - if type_all_bits_valid(element.type_()) { - self.emit(&ListCanonLift { element }); + let free = String::from("witx_free"); + if type_all_bits_valid(element) || is_char(element) { + self.emit(&ListCanonLift { element, free }); } else { self.bindgen.push_block(); self.emit(&IterBasePointer); let addr = self.stack.pop().unwrap(); self.read_from_memory(element, addr, 0); self.finish_block(1); - self.emit(&ListLift { element }); + self.emit(&ListLift { element, free }); } } }, Type::Record(r) => { if let Some(repr) = r.bitflags_repr() { - let ty = match ty { - TypeRef::Name(ty) => ty, - _ => unreachable!(), - }; + let ty = ty.name().unwrap(); match repr { IntRepr::U64 => return self.witx(&BitflagsFromI64 { ty }), _ => return self.witx(&BitflagsFromI32 { ty }), @@ -1329,10 +1340,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { } match self.abi { Abi::Preview1 => { - let ty = match ty { - TypeRef::Name(ty) => ty, - _ => unreachable!(), - }; + let ty = ty.name().unwrap(); self.witx(&Load { ty }) } Abi::Next => { @@ -1348,7 +1356,10 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.stack.extend(args.drain(..temp.len())); self.lift(&member.tref, false); } - self.emit(&RecordLift { ty: r }); + self.emit(&RecordLift { + ty: r, + name: ty.name(), + }); } } } @@ -1356,17 +1367,11 @@ impl<'a, B: Bindgen> Generator<'a, B> { Type::Variant(v) if self.abi == Abi::Preview1 => { if v.is_enum() { return self.witx(&EnumLift { - ty: match ty { - TypeRef::Name(n) => n, - _ => unreachable!(), - }, + ty: ty.name().unwrap(), }); } else if !is_return { return self.witx(&Load { - ty: match ty { - TypeRef::Name(n) => n, - _ => unreachable!(), - }, + ty: ty.name().unwrap(), }); } @@ -1377,10 +1382,9 @@ impl<'a, B: Bindgen> Generator<'a, B> { let mut load = |ty: &TypeRef| { self.stack.push(self.return_pointers[n].clone()); n += 1; - match ty { - TypeRef::Name(ty) => self.witx(&Load { ty }), - _ => unreachable!(), - } + self.witx(&Load { + ty: ty.name().unwrap(), + }); }; match &**ok.type_() { Type::Record(r) if r.is_tuple() => { @@ -1440,7 +1444,10 @@ impl<'a, B: Bindgen> Generator<'a, B> { } self.finish_block(case.tref.is_some() as usize); } - self.emit(&VariantLift { ty: v }); + self.emit(&VariantLift { + ty: v, + name: ty.name(), + }); } } } @@ -1488,7 +1495,10 @@ impl<'a, B: Bindgen> Generator<'a, B> { // Decompose the record into its components and then write all the // components into memory one-by-one. Type::Record(r) => { - self.emit(&RecordLower { ty: r }); + self.emit(&RecordLower { + ty: r, + name: ty.name(), + }); let fields = self .stack .drain(self.stack.len() - r.members.len()..) @@ -1520,7 +1530,11 @@ impl<'a, B: Bindgen> Generator<'a, B> { } self.finish_block(0); } - self.emit(&VariantLower { ty: v, nresults: 0 }); + self.emit(&VariantLower { + ty: v, + nresults: 0, + name: ty.name(), + }); } } } @@ -1576,7 +1590,10 @@ impl<'a, B: Bindgen> Generator<'a, B> { offset + (layout.offset as i32), ); } - self.emit(&RecordLift { ty: r }); + self.emit(&RecordLift { + ty: r, + name: ty.name(), + }); } // Each case will get its own block, and we'll dispatch to the @@ -1594,7 +1611,10 @@ impl<'a, B: Bindgen> Generator<'a, B> { } self.finish_block(case.tref.is_some() as usize); } - self.emit(&VariantLift { ty: v }); + self.emit(&VariantLift { + ty: v, + name: ty.name(), + }); } } } @@ -1691,16 +1711,15 @@ fn cast(from: WasmType, to: WasmType) -> Bitcast { (I32, F32) => Bitcast::I32ToF32, (I64, F64) => Bitcast::I64ToF64, - (F32, I64) | (I64, F32) | (F64, I32) | (I32, F64) => unreachable!(), + (F32, I64) => Bitcast::F32ToI64, + (I64, F32) => Bitcast::I64ToF32, + (F64, I32) | (I32, F64) => unreachable!(), } } -fn type_all_bits_valid(ty: &Type) -> bool { - match ty { - Type::Record(r) => r - .members - .iter() - .all(|t| type_all_bits_valid(t.tref.type_())), +fn type_all_bits_valid(ty: &TypeRef) -> bool { + match &**ty.type_() { + Type::Record(r) => r.members.iter().all(|t| type_all_bits_valid(&t.tref)), Type::Builtin(BuiltinType::Char) | Type::Variant(_) | Type::Handle(_) | Type::List(_) => { false @@ -1720,3 +1739,9 @@ fn type_all_bits_valid(ty: &Type) -> bool { | Type::ConstPointer(_) => true, } } +fn is_char(ty: &TypeRef) -> bool { + match &**ty.type_() { + Type::Builtin(BuiltinType::Char) => true, + _ => false, + } +} diff --git a/tools/witx/src/ast.rs b/tools/witx/src/ast.rs index ee985d302..773a74d52 100644 --- a/tools/witx/src/ast.rs +++ b/tools/witx/src/ast.rs @@ -183,6 +183,13 @@ impl TypeRef { } } + pub fn name(&self) -> Option<&NamedType> { + match self { + TypeRef::Name(n) => Some(n), + TypeRef::Value(_) => None, + } + } + pub fn named(&self) -> bool { match self { TypeRef::Name(_) => true, diff --git a/tools/witx/tests/witxt.rs b/tools/witx/tests/witxt.rs index 7ba393669..c70451f89 100644 --- a/tools/witx/tests/witxt.rs +++ b/tools/witx/tests/witxt.rs @@ -411,7 +411,7 @@ impl witx::Bindgen for AbiBindgen<'_> { Return { .. } => self.assert("return"), VariantPayload => self.assert("variant-payload"), RecordLift { .. } => self.assert("record-lift"), - RecordLower { ty } => { + RecordLower { ty, .. } => { self.assert("record-lower"); for member in ty.members.iter() { results.push(Operand::Field(member.name.as_str().to_string())); @@ -436,6 +436,8 @@ impl witx::Bindgen for AbiBindgen<'_> { witx::Bitcast::F64ToF32 => self.assert("f64-to-f32"), witx::Bitcast::I32ToF32 => self.assert("i32-to-f32"), witx::Bitcast::I64ToF64 => self.assert("i64-to-f64"), + witx::Bitcast::F32ToI64 => self.assert("f32-to-i64"), + witx::Bitcast::I64ToF32 => self.assert("i64-to-f32"), } self.assert_op(&operand); } diff --git a/tools/witx/tests/witxt/abi-next.witxt b/tools/witx/tests/witxt/abi-next.witxt index 217da71be..07c8d45b0 100644 --- a/tools/witx/tests/witxt/abi-next.witxt +++ b/tools/witx/tests/witxt/abi-next.witxt @@ -292,6 +292,23 @@ call.interface return) ) +(assert_abi + (witx + (module $x (export "f" (func (param $p (list char))))) + ) + (wasm (param i32 i32)) + (call_wasm + get-arg0 + list.canon_lower arg0 + call.wasm + return) + (call_interface + get-arg0 + get-arg1 + list.canon_lift arg0 arg1 + call.interface + return) +) ;; flavorful parameters (assert_abi From c1495cbf018148b260cb86570fddbe35a960f47c Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 25 Feb 2021 08:52:41 -0800 Subject: [PATCH 05/22] Update ABIs with more modes of calling Born out of recent discussions and realizations that we'll need an owned/borrowed distinction for arguments where possible. --- tools/witx/src/abi.rs | 774 +++++++++++++++++--------- tools/witx/tests/witxt.rs | 72 ++- tools/witx/tests/witxt/abi-next.witxt | 176 +++--- tools/witx/tests/witxt/abi.witxt | 176 +++--- 4 files changed, 747 insertions(+), 451 deletions(-) diff --git a/tools/witx/src/abi.rs b/tools/witx/src/abi.rs index 0c4b74f3b..dd2bc48db 100644 --- a/tools/witx/src/abi.rs +++ b/tools/witx/src/abi.rs @@ -143,8 +143,75 @@ def_instruction! { /// Depending on the context this may refer to wasm parameters or /// interface types parameters. GetArg { nth: usize } : [0] => [1], + + // Integer const/manipulation instructions + /// Pushes the constant `val` onto the stack. I32Const { val: i32 } : [0] => [1], + /// Casts the top N items on the stack using the `Bitcast` enum + /// provided. Consumes the same number of operands that this produces. + Bitcasts { casts: &'a [Bitcast] } : [casts.len()] => [casts.len()], + /// Pushes a number of constant zeros for each wasm type on the stack. + ConstZero { tys: &'a [WasmType] } : [0] => [tys.len()], + + // Memory load/store instructions + + /// Pops an `i32` from the stack and loads a little-endian `i32` from + /// it, using the specified constant offset. + I32Load { offset: i32 } : [1] => [1], + /// Pops an `i32` from the stack and loads a little-endian `i8` from + /// it, using the specified constant offset. The value loaded is the + /// zero-extended to 32-bits + I32Load8U { offset: i32 } : [1] => [1], + /// Pops an `i32` from the stack and loads a little-endian `i8` from + /// it, using the specified constant offset. The value loaded is the + /// sign-extended to 32-bits + I32Load8S { offset: i32 } : [1] => [1], + /// Pops an `i32` from the stack and loads a little-endian `i16` from + /// it, using the specified constant offset. The value loaded is the + /// zero-extended to 32-bits + I32Load16U { offset: i32 } : [1] => [1], + /// Pops an `i32` from the stack and loads a little-endian `i16` from + /// it, using the specified constant offset. The value loaded is the + /// sign-extended to 32-bits + I32Load16S { offset: i32 } : [1] => [1], + /// Pops an `i32` from the stack and loads a little-endian `i64` from + /// it, using the specified constant offset. + I64Load { offset: i32 } : [1] => [1], + /// Pops an `i32` from the stack and loads a little-endian `f32` from + /// it, using the specified constant offset. + F32Load { offset: i32 } : [1] => [1], + /// Pops an `i32` from the stack and loads a little-endian `f64` from + /// it, using the specified constant offset. + F64Load { offset: i32 } : [1] => [1], + + /// Pops an `i32` address from the stack and then an `i32` value. + /// Stores the value in little-endian at the pointer specified plus the + /// constant `offset`. + I32Store { offset: i32 } : [2] => [0], + /// Pops an `i32` address from the stack and then an `i32` value. + /// Stores the low 8 bits of the value in little-endian at the pointer + /// specified plus the constant `offset`. + I32Store8 { offset: i32 } : [2] => [0], + /// Pops an `i32` address from the stack and then an `i32` value. + /// Stores the low 16 bits of the value in little-endian at the pointer + /// specified plus the constant `offset`. + I32Store16 { offset: i32 } : [2] => [0], + /// Pops an `i32` address from the stack and then an `i64` value. + /// Stores the value in little-endian at the pointer specified plus the + /// constant `offset`. + I64Store { offset: i32 } : [2] => [0], + /// Pops an `i32` address from the stack and then an `f32` value. + /// Stores the value in little-endian at the pointer specified plus the + /// constant `offset`. + F32Store { offset: i32 } : [2] => [0], + /// Pops an `i32` address from the stack and then an `f64` value. + /// Stores the value in little-endian at the pointer specified plus the + /// constant `offset`. + F64Store { offset: i32 } : [2] => [0], + + // Scalar lifting/lowering + /// Converts an interface type `char` value to a 32-bit integer /// representing the unicode scalar value. I32FromChar : [1] => [1], @@ -168,64 +235,6 @@ def_instruction! { I32FromUsize : [1] => [1], /// Converts a language-specific C `char` value to a wasm `i32`. I32FromChar8 : [1] => [1], - /// Converts a language-specific handle value to a wasm `i32`. - I32FromHandle { ty: &'a NamedType } : [1] => [1], - /// Lowers a list whose elements can be directly copied and interpreted - /// with no validation whatsoever. - /// - /// Pops a list value from the stack and pushes the pointer/length onto - /// the stack. Note that this consumes the list and the list must be an - /// owned allocation. - ListCanonLower { - element: &'a TypeRef, - malloc: String, - } : [1] => [2], - /// Lowers a list whose elements are copied one-by-one into a new list. - /// - /// Pops a list value from the stack and pushes the pointer/length onto - /// the stack. Note that this consumes the list as an owned allocation - /// and must produce an owned allocation. - /// - /// This operation also pops a block from the block stack which is used - /// as the iteration body of writing each element of the list consumed. - ListLower { - element: &'a TypeRef, - malloc: String, - } : [1] => [2], - /// Lifts a list which has a canonical representation into an interface - /// types value. - /// - /// This will consume two `i32` values from the stack, a pointer and a - /// length, and then produces an interface value list. Note that the - /// pointer/length popped are **owned** and need to be deallocated when - /// the interface type is dropped. - ListCanonLift { - element: &'a TypeRef, - free: String, - } : [2] => [1], - /// Lifts a list which into an interface types value. - /// - /// This will consume two `i32` values from the stack, a pointer and a - /// length, and then produces an interface value list. Note that the - /// pointer/length popped are **owned** and need to be deallocated when - /// the interface type is dropped. - /// - /// This will also pop a block from the block stack which is how to - /// read each individual element from the list. - ListLift { - element: &'a TypeRef, - free: String, - } : [2] => [1], - /// Pushes an operand onto the stack representing the list item from - /// each iteration of the list. - /// - /// This is only used inside of blocks related to lowering lists. - IterElem : [0] => [1], - /// Pushes an operand onto the stack representing the base pointer of - /// the next element in a list. - /// - /// This is sused for both lifting and lowering lists. - IterBasePointer : [0] => [1], /// Conversion an interface type `f32` value to a wasm `f32`. /// /// This may be a noop for some implementations, but it's here in case the @@ -238,28 +247,6 @@ def_instruction! { /// native language representation of `f64` is different than the wasm /// representation of `f64`. F64FromIf64 : [1] => [1], - /// Converts a native wasm `i32` to a language-specific C `char`. - /// - /// This will truncate the upper bits of the `i32`. - Char8FromI32 : [1] => [1], - /// Converts a native wasm `i32` to a language-specific `usize`. - UsizeFromI32 : [1] => [1], - - /// Represents a call to a raw WebAssembly API. The module/name are - /// provided inline as well as the types if necessary. - CallWasm { - module: &'a str, - name: &'a str, - params: &'a [WasmType], - results: &'a [WasmType], - } : [params.len()] => [results.len()], - - /// Same as `CallWasm`, except the dual where an interface is being - /// called rather than a raw wasm function. - CallInterface { - module: &'a str, - func: &'a InterfaceFunc, - } : [func.params.len()] => [func.results.len()], /// Converts a native wasm `i32` to an interface type `s8`. /// @@ -293,11 +280,209 @@ def_instruction! { If32FromF32 : [1] => [1], /// Converts a native wasm `f64` to an interface type `f64`. If64FromF64 : [1] => [1], - /// Converts a native wasm `i32` to an interface type `handle`. - HandleFromI32 { ty: &'a NamedType } : [1] => [1], - /// Returns `amt` values on the stack. This is always the last - /// instruction. - Return { amt: usize } : [*amt] => [0], + /// Converts a native wasm `i32` to a language-specific C `char`. + /// + /// This will truncate the upper bits of the `i32`. + Char8FromI32 : [1] => [1], + /// Converts a native wasm `i32` to a language-specific `usize`. + UsizeFromI32 : [1] => [1], + + // Handles + + /// Converts a "borrowed" handle into a wasm `i32` value. + /// + /// A "borrowed" handle in this case means one where ownership is not + /// being relinquished. This is only used for lowering interface types + /// parameters. + /// + /// Situations that this is used are: + /// + /// * A wasm exported function receives, as a parameter, handles defined + /// by the wasm module itself. This is effectively proof of ownership + /// by an external caller (be it host or wasm module) and the + /// ownership of the handle still lies with the caller. The wasm + /// module is only receiving a reference to the resource. + /// + /// * A wasm module is calling an import with a handle defined by the + /// import's module. Sort of the converse of the previous case this + /// means that the wasm module is handing out a reference to a + /// resource that it owns. The type in the wasm module, for example, + /// needs to reflect this. + /// + /// This instruction is not used for return values in either + /// export/import positions. + I32FromBorrowedHandle { ty: &'a NamedType } : [1] => [1], + + /// Converts an "owned" handle into a wasm `i32` value. + /// + /// This conversion is used for handle values which are crossing a + /// module boundary for perhaps the first time. Some example cases of + /// when this conversion is used are: + /// + /// * When a host defines a function to be imported, returned handles + /// use this instruction. Handles being returned to wasm a granting a + /// capability, which means that this new capability is typically + /// wrapped up in a new integer descriptor. + /// + /// * When a wasm module calls an imported function with a type defined + /// by itself, then it's granting a capability to the callee. This + /// means that the wasm module's type is being granted for the first + /// time, possibly, so it needs to be an owned value that's consumed. + /// Note that this doesn't actually happen with `*.witx` today due to + /// the lack of handle type imports. + /// + /// * When a wasm module export returns a handle defined within the + /// module, then it's similar to calling an imported function with + /// that handle. The capability is being granted to the caller of the + /// export, so the owned value is wrapped up in an `i32`. + /// + /// * When a host is calling a wasm module with a capability defined by + /// the host, its' similar to the host import returning a capability. + /// This would be granting the wasm module with the capability so an + /// owned version with a fresh handle is passed to the wasm module. + /// Note that this doesn't happen today with `*.witx` due to the lack + /// of handle type imports. + /// + /// Basically this instruction is used for handle->wasm conversions + /// depending on the calling context and where the handle type in + /// question was defined. + I32FromOwnedHandle { ty: &'a NamedType } : [1] => [1], + + /// Converts a native wasm `i32` into an owned handle value. + /// + /// This is the converse of `I32FromOwnedHandle` and is used in similar + /// situations: + /// + /// * A host definition of an import receives a handle defined in the + /// module itself. + /// * A wasm module calling an import receives a handle defined by the + /// import. + /// * A wasm module's export receives a handle defined by an external + /// module. + /// * A host calling a wasm export receives a handle defined in the + /// module. + /// + /// Note that like `I32FromOwnedHandle` the first and third bullets + /// above don't happen today because witx can't express type imports + /// just yet. + HandleOwnedFromI32 { ty: &'a NamedType } : [1] => [1], + + /// Converts a native wasm `i32` into a borrowedhandle value. + /// + /// This is the converse of `I32FromBorrowedHandle` and is used in similar + /// situations: + /// + /// * An exported wasm function receives, as a parameter, a handle that + /// is defined by the wasm module. + /// * An host-defined imported function is receiving a handle, as a + /// parameter, that is defined by the host itself. + HandleBorrowedFromI32 { ty: &'a NamedType } : [1] => [1], + + // lists + + /// Lowers a list where the element's layout in the native language is + /// expected to match the canonical ABI definition of interface types. + /// + /// Pops a list value from the stack and pushes the pointer/length onto + /// the stack. If `malloc` is set to `Some` then this is expected to + /// *consume* the list which means that the data needs to be copied. An + /// allocation/copy is expected when: + /// + /// * A host is calling a wasm export with a list (it needs to copy the + /// list in to the callee's module, allocating space with `malloc`) + /// * A wasm export is returning a list (it's expected to use `malloc` + /// to give ownership of the list to the caller. + /// * A host is returning a list in a import definition, meaning that + /// space needs to be allocated in the caller with `malloc`). + /// + /// A copy does not happen (e.g. `malloc` is `None`) when: + /// + /// * A wasm module calls an import with the list. In this situation + /// it's expected the caller will know how to access this module's + /// memory (e.g. the host has raw access or wasm-to-wasm communication + /// would copy the list). + /// + /// Note that the adapter is not responsible for cleaning up this list. + /// It's either owned by the caller (`malloc` is `None`) or the + /// receiving end is responsible for cleanup (`malloc` is `Some`) + ListCanonLower { + element: &'a TypeRef, + malloc: Option, + } : [1] => [2], + + /// Lowers a list where the element's layout in the native language is + /// not expected to match the canonical ABI definition of interface + /// types. + /// + /// Pops a list value from the stack and pushes the pointer/length onto + /// the stack. This operation also pops a block from the block stack + /// which is used as the iteration body of writing each element of the + /// list consumed. + /// + /// The `malloc` function is expected to be used within the + /// wasm module to allocate memory. The `owned` flag is set similar to + /// the ownership of `ListCanonLower`, meaning it's always `true` except + /// for when the lowering is happening for a wasm module calling an + /// imported function. + /// + /// Note, though, that the `owned` flag is only describing the interface + /// types value we're lowering from. This instruction is expected to + /// always create an owned allocation as part of lowering. Lifting on + /// the other side will always deallocate the list passed. + ListLower { + element: &'a TypeRef, + malloc: String, + owned: bool, + } : [1] => [2], + + /// Lifts a list which has a canonical representation into an interface + /// types value. + /// + /// The term "canonical" representation here means that the + /// representation of the interface types value in the native language + /// exactly matches the canonical ABI definition of the type. + /// + /// This will consume two `i32` values from the stack, a pointer and a + /// length, and then produces an interface value list. If the `free` + /// field is set to `Some` then the pointer/length should be considered + /// an owned allocation and need to be deallocated by the receiver. If + /// it is set to `None` then a view is provided but it does not need to + /// be deallocated. + /// + /// The `free` field is set to `Some` in similar situations as described + /// by `ListCanonLower`. + ListCanonLift { + element: &'a TypeRef, + free: Option, + } : [2] => [1], + + /// Lifts a list which into an interface types value. + /// + /// This will consume two `i32` values from the stack, a pointer and a + /// length, and then produces an interface value list. Note that the + /// pointer/length popped are **owned** and need to be deallocated with + /// the wasm `free` function when the list is no longer needed. + /// + /// This will also pop a block from the block stack which is how to + /// read each individual element from the list. + ListLift { + element: &'a TypeRef, + free: String, + } : [2] => [1], + + /// Pushes an operand onto the stack representing the list item from + /// each iteration of the list. + /// + /// This is only used inside of blocks related to lowering lists. + IterElem : [0] => [1], + + /// Pushes an operand onto the stack representing the base pointer of + /// the next element in a list. + /// + /// This is used for both lifting and lowering lists. + IterBasePointer : [0] => [1], + + // records /// Pops a record value off the stack, decomposes the record to all of /// its fields, and then pushes the fields onto the stack. @@ -305,6 +490,7 @@ def_instruction! { ty: &'a RecordDatatype, name: Option<&'a NamedType>, } : [1] => [ty.members.len()], + /// Pops all fields for a record off the stack and then composes them /// into a record. RecordLift { @@ -312,6 +498,8 @@ def_instruction! { name: Option<&'a NamedType>, } : [ty.members.len()] => [1], + // variants + /// This is a special instruction used at the entry of blocks used as /// part of `ResultLower`, representing that the payload of that variant /// being matched on should be pushed onto the stack. @@ -334,66 +522,29 @@ def_instruction! { name: Option<&'a NamedType>, } : [1] => [1], - /// Casts the top N items on the stack using the `Bitcast` enum - /// provided. Consumes the same number of operands that this produces. - Bitcasts { casts: &'a [Bitcast] } : [casts.len()] => [casts.len()], + // calling/control flow - /// Pushes a number of constant zeros for each wasm type on the stack. - ConstZero { tys: &'a [WasmType] } : [0] => [tys.len()], + /// Represents a call to a raw WebAssembly API. The module/name are + /// provided inline as well as the types if necessary. + CallWasm { + module: &'a str, + name: &'a str, + params: &'a [WasmType], + results: &'a [WasmType], + } : [params.len()] => [results.len()], - /// Pops an `i32` from the stack and loads a little-endian `i32` from - /// it, using the specified constant offset. - I32Load { offset: i32 } : [1] => [1], - /// Pops an `i32` from the stack and loads a little-endian `i8` from - /// it, using the specified constant offset. The value loaded is the - /// zero-extended to 32-bits - I32Load8U { offset: i32 } : [1] => [1], - /// Pops an `i32` from the stack and loads a little-endian `i8` from - /// it, using the specified constant offset. The value loaded is the - /// sign-extended to 32-bits - I32Load8S { offset: i32 } : [1] => [1], - /// Pops an `i32` from the stack and loads a little-endian `i16` from - /// it, using the specified constant offset. The value loaded is the - /// zero-extended to 32-bits - I32Load16U { offset: i32 } : [1] => [1], - /// Pops an `i32` from the stack and loads a little-endian `i16` from - /// it, using the specified constant offset. The value loaded is the - /// sign-extended to 32-bits - I32Load16S { offset: i32 } : [1] => [1], - /// Pops an `i32` from the stack and loads a little-endian `i64` from - /// it, using the specified constant offset. - I64Load { offset: i32 } : [1] => [1], - /// Pops an `i32` from the stack and loads a little-endian `f32` from - /// it, using the specified constant offset. - F32Load { offset: i32 } : [1] => [1], - /// Pops an `i32` from the stack and loads a little-endian `f64` from - /// it, using the specified constant offset. - F64Load { offset: i32 } : [1] => [1], + /// Same as `CallWasm`, except the dual where an interface is being + /// called rather than a raw wasm function. + CallInterface { + module: &'a str, + func: &'a InterfaceFunc, + } : [func.params.len()] => [func.results.len()], - /// Pops an `i32` address from the stack and then an `i32` value. - /// Stores the value in little-endian at the pointer specified plus the - /// constant `offset`. - I32Store { offset: i32 } : [2] => [0], - /// Pops an `i32` address from the stack and then an `i32` value. - /// Stores the low 8 bits of the value in little-endian at the pointer - /// specified plus the constant `offset`. - I32Store8 { offset: i32 } : [2] => [0], - /// Pops an `i32` address from the stack and then an `i32` value. - /// Stores the low 16 bits of the value in little-endian at the pointer - /// specified plus the constant `offset`. - I32Store16 { offset: i32 } : [2] => [0], - /// Pops an `i32` address from the stack and then an `i64` value. - /// Stores the value in little-endian at the pointer specified plus the - /// constant `offset`. - I64Store { offset: i32 } : [2] => [0], - /// Pops an `i32` address from the stack and then an `f32` value. - /// Stores the value in little-endian at the pointer specified plus the - /// constant `offset`. - F32Store { offset: i32 } : [2] => [0], - /// Pops an `i32` address from the stack and then an `f64` value. - /// Stores the value in little-endian at the pointer specified plus the - /// constant `offset`. - F64Store { offset: i32 } : [2] => [0], + /// Returns `amt` values on the stack. This is always the last + /// instruction. + Return { amt: usize } : [*amt] => [0], + + // ... /// An instruction from an extended instruction set that's specific to /// `*.witx` and the "Preview1" ABI. @@ -804,20 +955,56 @@ impl InterfaceFunc { /// language-specific values into the wasm types to call a WASI function, /// and it will also automatically convert the results of the WASI function /// back to a language-specific value. - pub fn call_wasm(&self, module: &Id, bindgen: &mut impl Bindgen) { - Generator::new(self.abi, bindgen).call_wasm(module, self); + pub fn call(&self, module: &Id, mode: CallMode, bindgen: &mut impl Bindgen) { + Generator::new(self.abi, mode, bindgen).call(module, self); } +} - /// This is the dual of [`InterfaceFunc::call_wasm`], except that instead of - /// calling a wasm signature it generates code to come from a wasm signature - /// and call an interface types signature. - pub fn call_interface(&self, module: &Id, bindgen: &mut impl Bindgen) { - Generator::new(self.abi, bindgen).call_interface(module, self); - } +/// Modes of calling a WebAssembly or host function. +/// +/// Each mode may have a slightly different codegen for some types, so +/// [`InterfaceFunc::call`] takes this as a parameter to know in what context +/// the invocation is happening within. +pub enum CallMode { + /// A defined export is being called. + /// + /// This typically means that a code generator is generating a shim function + /// to get exported from a WebAssembly module and the shim is calling a + /// native language function defined within the wasm module. In this mode + /// arguments are being lifted from wasm types to interface types, and + /// results are being lowered. + DefinedExport, + + /// A declared export is being called. + /// + /// This typically means that a code generator is generating calls to a + /// WebAssembly module, for example a JS host calling a WebAssembly + /// instance. In this mode the native language's arguments are being lowered + /// to wasm values and the results of the function are lifted back into the + /// native language. + DeclaredExport, + + /// A defined import is being called. + /// + /// This is typically used for code generators that are creating bindings in + /// a host for a function imported by WebAssembly. In this mode a function + /// with a wasm signature is generated and that function's wasm arguments + /// are lifted into the native language's arguments. The results are then + /// lowered back into wasm arguments to return. + DefinedImport, + + /// A declared import is being called + /// + /// This is typically used for code generators that are calling an imported + /// function from within a wasm module. In this mode native language + /// arguments are lowered to wasm values, and the results of the import are + /// lifted back into the native language. + DeclaredImport, } struct Generator<'a, B: Bindgen> { abi: Abi, + mode: CallMode, bindgen: &'a mut B, operands: Vec, results: Vec, @@ -826,9 +1013,10 @@ struct Generator<'a, B: Bindgen> { } impl<'a, B: Bindgen> Generator<'a, B> { - fn new(abi: Abi, bindgen: &'a mut B) -> Generator<'a, B> { + fn new(abi: Abi, mode: CallMode, bindgen: &'a mut B) -> Generator<'a, B> { Generator { abi, + mode, bindgen, operands: Vec::new(), results: Vec::new(), @@ -837,46 +1025,103 @@ impl<'a, B: Bindgen> Generator<'a, B> { } } - fn call_wasm(&mut self, module: &Id, func: &InterfaceFunc) { - // Push all parameters for this function onto the stack, and then - // batch-lower everything all at once. - for nth in 0..func.params.len() { - self.emit(&Instruction::GetArg { nth }); - } - self.lower_all(&func.params, None); - - // If necessary we may need to prepare a return pointer for this ABI. - // The `Preview1` ABI has most return values returned through pointers, - // and the `Next` ABI returns more-than-one values through a return - // pointer. + fn call(&mut self, module: &Id, func: &InterfaceFunc) { let sig = func.wasm_signature(); - self.prep_return_pointer(&sig, &func.results); - - // Now that all the wasm args are prepared we can call the actual wasm - // function. - assert_eq!(self.stack.len(), sig.params.len()); - self.emit(&Instruction::CallWasm { - module: module.as_str(), - name: func.name.as_str(), - params: &sig.params, - results: &sig.results, - }); - - // In the `Next` ABI we model multiple return values by going through - // memory. Remove that indirection here by loading everything to - // simulate the function having many return values in our stack - // discipline. - if let Some(actual) = &sig.retptr { - self.load_retptr(actual); - } - // Batch-lift all result values now that all the function's return - // values are on the stack. - self.lift_all(&func.results, true); + match self.mode { + CallMode::DeclaredExport | CallMode::DeclaredImport => { + // Push all parameters for this function onto the stack, and + // then batch-lower everything all at once. + for nth in 0..func.params.len() { + self.emit(&Instruction::GetArg { nth }); + } + self.lower_all(&func.params, None); + + // If necessary we may need to prepare a return pointer for this + // ABI. The `Preview1` ABI has most return values returned + // through pointers, and the `Next` ABI returns more-than-one + // values through a return pointer. + self.prep_return_pointer(&sig, &func.results); + + // Now that all the wasm args are prepared we can call the + // actual wasm function. + assert_eq!(self.stack.len(), sig.params.len()); + self.emit(&Instruction::CallWasm { + module: module.as_str(), + name: func.name.as_str(), + params: &sig.params, + results: &sig.results, + }); + + // In the `Next` ABI we model multiple return values by going + // through memory. Remove that indirection here by loading + // everything to simulate the function having many return values + // in our stack discipline. + if let Some(actual) = &sig.retptr { + self.load_retptr(actual); + } + + // Batch-lift all result values now that all the function's return + // values are on the stack. + self.lift_all(&func.results, true); + + self.emit(&Instruction::Return { + amt: func.results.len(), + }); + } + + CallMode::DefinedExport | CallMode::DefinedImport => { + // Use `GetArg` to push all relevant arguments onto the stack. + // Note that we can't use the signature of this function + // directly due to various conversions and return pointers, so + // we need to somewhat manually calculate all the arguments + // which are converted as interface types arguments below. + let sig = func.wasm_signature(); + let nargs = match self.abi { + Abi::Preview1 => { + func.params.len() + + func + .params + .iter() + .filter(|t| match &**t.tref.type_() { + Type::List(_) => true, + _ => false, + }) + .count() + } + Abi::Next => sig.params.len() - sig.retptr.is_some() as usize, + }; + for nth in 0..nargs { + self.emit(&Instruction::GetArg { nth }); + } + + // Once everything is on the stack we can lift all arguments + // one-by-one into their interface-types equivalent. + self.lift_all(&func.params, false); + + // ... and that allows us to call the interface types function + self.emit(&Instruction::CallInterface { + module: module.as_str(), + func, + }); + + // ... and at the end we lower everything back into return + // values. + self.lower_all(&func.results, Some(nargs)); + + // Our ABI dictates that a list of returned types are returned + // through memories, so after we've got all the values on the + // stack perform all of the stores here. + if let Some(tys) = &sig.retptr { + self.store_retptr(tys, sig.params.len() - 1); + } + + self.emit(&Instruction::Return { + amt: sig.results.len(), + }); + } + } - self.emit(&Instruction::Return { - amt: func.results.len(), - }); assert!(self.stack.is_empty()); } @@ -894,57 +1139,6 @@ impl<'a, B: Bindgen> Generator<'a, B> { } } - fn call_interface(&mut self, module: &Id, func: &InterfaceFunc) { - // Use `GetArg` to push all relevant arguments onto the stack. Note - // that we can't use the signature of this function directly due to - // various conversions and return pointers, so we need to somewhat - // manually calculate all the arguments which are converted as - // interface types arguments below. - let sig = func.wasm_signature(); - let nargs = match self.abi { - Abi::Preview1 => { - func.params.len() - + func - .params - .iter() - .filter(|t| match &**t.tref.type_() { - Type::List(_) => true, - _ => false, - }) - .count() - } - Abi::Next => sig.params.len() - sig.retptr.is_some() as usize, - }; - for nth in 0..nargs { - self.emit(&Instruction::GetArg { nth }); - } - - // Once everything is on the stack we can lift all arguments one-by-one - // into their interface-types equivalent. - self.lift_all(&func.params, false); - - // ... and that allows us to call the interface types function ... - self.emit(&Instruction::CallInterface { - module: module.as_str(), - func, - }); - - // ... and at the end we lower everything back into return values. - self.lower_all(&func.results, Some(nargs)); - - // Our ABI dictates that a list of returned types are returned through - // memories, so after we've got all the values on the stack perform - // all of the stores here. - if let Some(tys) = &sig.retptr { - self.store_retptr(tys, sig.params.len() - 1); - } - - self.emit(&Instruction::Return { - amt: sig.results.len(), - }); - assert!(self.stack.is_empty()); - } - /// Assumes that the wasm values to create `tys` are all located on the /// stack. /// @@ -1078,15 +1272,74 @@ impl<'a, B: Bindgen> Generator<'a, B> { Type::Builtin(BuiltinType::F64) => self.emit(&F64FromIf64), Type::Pointer(_) => self.witx(&I32FromPointer), Type::ConstPointer(_) => self.witx(&I32FromConstPointer), - Type::Handle(_) => self.emit(&I32FromHandle { - ty: ty.name().unwrap(), - }), + Type::Handle(_) => { + let ty = ty.name().unwrap(); + let borrowed = match self.mode { + // There's one of three possible situations we're in: + // + // * The handle is defined by the wasm module itself. This + // is the only actual possible scenario today due to how + // witx is defined. In this situation the handle is owned + // by the host and "proof of ownership" is being offered + // and there's no need to relinquish ownership. + // + // * The handle is defined by the host, and it's passing it + // to a wasm module. This should use an owned conversion. + // This isn't expressible in today's `*.witx` format. + // + // * The handle is defined by neither the host or the wasm + // mdoule. This means that the host is passing a + // capability from another wasm module into this one, + // meaning it's doing so by reference since the host is + // retaining access to its own + // + // Note, again, only the first bullet here is possible + // today, hence the hardcoded `true` value. We'll need to + // refactor `witx` to expose the other possibilities. + CallMode::DeclaredExport => true, + + // Like above there's one of three possibilities. This means + // a wasm module is calling an imported function with a + // handle, and the handle is owned by either the wasm + // module, the imported function we're calling, or neither. + // + // An owned value is only used when the wasm module passes + // its own handle to the import. Otherwise a borrowed value + // is always used because the wasm module retains ownership + // of the original handle since it's a reference to + // something owned elsewhere. + // + // Today the only expressible case is that an imported + // function receives a handle that it itself defined, so + // it's always borrowed. + CallMode::DeclaredImport => true, + + // This means that a return value is being lowered, which is + // never borrowed. + CallMode::DefinedExport | CallMode::DefinedImport => false, + }; + if borrowed { + self.emit(&I32FromBorrowedHandle { ty }); + } else { + self.emit(&I32FromOwnedHandle { ty }); + } + } Type::List(element) => match self.abi { Abi::Preview1 => self.witx(&ListPointerLength), Abi::Next => { + // Lowering parameters calling a declared import means we + // don't need to pass ownership, but we pass ownership in + // all other cases. + let owned = match self.mode { + CallMode::DeclaredImport => false, + _ => true, + }; let malloc = String::from("witx_malloc"); if type_all_bits_valid(element) || is_char(element) { - self.emit(&ListCanonLower { element, malloc }); + self.emit(&ListCanonLower { + element, + malloc: if owned { Some(malloc) } else { None }, + }); } else { self.bindgen.push_block(); self.emit(&IterElem); @@ -1094,7 +1347,11 @@ impl<'a, B: Bindgen> Generator<'a, B> { let addr = self.stack.pop().unwrap(); self.write_to_memory(element, addr, 0); self.finish_block(0); - self.emit(&ListLower { element, malloc }); + self.emit(&ListLower { + element, + malloc, + owned, + }); } } }, @@ -1311,15 +1568,36 @@ impl<'a, B: Bindgen> Generator<'a, B> { Type::Builtin(BuiltinType::F64) => self.emit(&If64FromF64), Type::Pointer(ty) => self.witx(&PointerFromI32 { ty }), Type::ConstPointer(ty) => self.witx(&ConstPointerFromI32 { ty }), - Type::Handle(_) => self.emit(&HandleFromI32 { - ty: ty.name().unwrap(), - }), + Type::Handle(_) => { + let ty = ty.name().unwrap(); + // For more information on these values see the comments in + // `lower` above. + let borrowed = match self.mode { + CallMode::DefinedExport | CallMode::DefinedImport => true, + CallMode::DeclaredExport | CallMode::DeclaredImport => false, + }; + if borrowed { + self.emit(&HandleBorrowedFromI32 { ty }); + } else { + self.emit(&HandleOwnedFromI32 { ty }); + } + } Type::List(element) => match self.abi { Abi::Preview1 => self.witx(&ListFromPointerLength { ty: element }), Abi::Next => { + // Lifting the arguments of a defined import means that, if + // possible, the caller still retains ownership and we don't + // free anything. + let owned = match self.mode { + CallMode::DefinedImport => false, + _ => true, + }; let free = String::from("witx_free"); if type_all_bits_valid(element) || is_char(element) { - self.emit(&ListCanonLift { element, free }); + self.emit(&ListCanonLift { + element, + free: if owned { Some(free) } else { None }, + }); } else { self.bindgen.push_block(); self.emit(&IterBasePointer); diff --git a/tools/witx/tests/witxt.rs b/tools/witx/tests/witxt.rs index c70451f89..076de7225 100644 --- a/tools/witx/tests/witxt.rs +++ b/tools/witx/tests/witxt.rs @@ -194,9 +194,8 @@ impl WitxtRunner<'_> { } WitxtDirective::AssertAbi { witx, - wasm, - interface, wasm_signature: (wasm_params, wasm_results), + abis, .. } => { let doc = witx.document(contents, test)?; @@ -215,18 +214,16 @@ impl WitxtRunner<'_> { ); } - let mut check = AbiBindgen { - abi: wasm.instrs.iter(), - err: None, - contents, - next_rp: 0, - }; - func.call_wasm(&module.name, &mut check); - check.check()?; - check.abi = interface.instrs.iter(); - check.next_rp = 0; - func.call_interface(&module.name, &mut check); - check.check()?; + for abi in abis { + let mut check = AbiBindgen { + abi: abi.instrs.iter(), + err: None, + contents, + next_rp: 0, + }; + func.call(&module.name, abi.mode, &mut check); + check.check()?; + } } } Ok(()) @@ -376,7 +373,8 @@ impl witx::Bindgen for AbiBindgen<'_> { I32FromU8 => self.assert("i32.from_u8"), I32FromS8 => self.assert("i32.from_s8"), I32FromChar8 => self.assert("i32.from_char8"), - I32FromHandle { .. } => self.assert("i32.from_handle"), + I32FromBorrowedHandle { .. } => self.assert("i32.from_borrowed_handle"), + I32FromOwnedHandle { .. } => self.assert("i32.from_owned_handle"), F32FromIf32 => self.assert("f32.from_if32"), F64FromIf64 => self.assert("f64.from_if64"), CallWasm { @@ -407,7 +405,8 @@ impl witx::Bindgen for AbiBindgen<'_> { UsizeFromI32 => self.assert("usize.from_i32"), If32FromF32 => self.assert("if32.from_f32"), If64FromF64 => self.assert("if64.from_f64"), - HandleFromI32 { .. } => self.assert("handle.from_i32"), + HandleBorrowedFromI32 { .. } => self.assert("handle.borrowed_from_i32"), + HandleOwnedFromI32 { .. } => self.assert("handle.owned_from_i32"), Return { .. } => self.assert("return"), VariantPayload => self.assert("variant-payload"), RecordLift { .. } => self.assert("record-lift"), @@ -544,6 +543,10 @@ mod kw { wast::custom_keyword!(i64); wast::custom_keyword!(f32); wast::custom_keyword!(f64); + wast::custom_keyword!(declared_import); + wast::custom_keyword!(declared_export); + wast::custom_keyword!(defined_import); + wast::custom_keyword!(defined_export); } struct Witxt<'a> { @@ -577,8 +580,7 @@ enum WitxtDirective<'a> { span: wast::Span, witx: Witx<'a>, wasm_signature: (Vec, Vec), - wasm: Abi<'a>, - interface: Abi<'a>, + abis: Vec>, }, } @@ -642,14 +644,13 @@ impl<'a> Parse<'a> for WitxtDirective<'a> { } Ok((params, results)) })?, - wasm: parser.parens(|p| { - p.parse::()?; - p.parse() - })?, - interface: parser.parens(|p| { - p.parse::()?; - p.parse() - })?, + abis: { + let mut abis = Vec::new(); + while !parser.is_empty() { + abis.push(parser.parens(|p| p.parse())?) + } + abis + }, }) } else { Err(l.error()) @@ -761,11 +762,28 @@ impl<'a> Parse<'a> for RepEquality { } struct Abi<'a> { + mode: witx::CallMode, instrs: Vec<(wast::Span, &'a str)>, } impl<'a> Parse<'a> for Abi<'a> { fn parse(parser: Parser<'a>) -> parser::Result { + let mut l = parser.lookahead1(); + let mode = if l.peek::() { + parser.parse::()?; + witx::CallMode::DeclaredImport + } else if l.peek::() { + parser.parse::()?; + witx::CallMode::DeclaredExport + } else if l.peek::() { + parser.parse::()?; + witx::CallMode::DefinedImport + } else if l.peek::() { + parser.parse::()?; + witx::CallMode::DefinedExport + } else { + return Err(l.error()); + }; let mut instrs = Vec::new(); while !parser.is_empty() { instrs.push(parser.step(|cursor| { @@ -779,6 +797,6 @@ impl<'a> Parse<'a> for Abi<'a> { Ok(((cursor.cur_span(), kw), next)) })?); } - Ok(Abi { instrs }) + Ok(Abi { mode, instrs }) } } diff --git a/tools/witx/tests/witxt/abi-next.witxt b/tools/witx/tests/witxt/abi-next.witxt index 07c8d45b0..073b211b6 100644 --- a/tools/witx/tests/witxt/abi-next.witxt +++ b/tools/witx/tests/witxt/abi-next.witxt @@ -1,76 +1,76 @@ (assert_abi (witx (module $x (export "f" (func)))) (wasm) - (call_wasm call.wasm return) - (call_interface call.interface return) + (declared_import call.wasm return) + (defined_import call.interface return) ) ;; scalar arguments (assert_abi (witx (module $x (export "f" (func (param $p u8))))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_u8 arg0 call.wasm return) - (call_interface get-arg0 u8.from_i32 arg0 call.interface return) + (declared_import get-arg0 i32.from_u8 arg0 call.wasm return) + (defined_import get-arg0 u8.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (export "f" (func (param $p s8))))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_s8 arg0 call.wasm return) - (call_interface get-arg0 s8.from_i32 arg0 call.interface return) + (declared_import get-arg0 i32.from_s8 arg0 call.wasm return) + (defined_import get-arg0 s8.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (export "f" (func (param $p u16))))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_u16 arg0 call.wasm return) - (call_interface get-arg0 u16.from_i32 arg0 call.interface return) + (declared_import get-arg0 i32.from_u16 arg0 call.wasm return) + (defined_import get-arg0 u16.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (export "f" (func (param $p s16))))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_s16 arg0 call.wasm return) - (call_interface get-arg0 s16.from_i32 arg0 call.interface return) + (declared_import get-arg0 i32.from_s16 arg0 call.wasm return) + (defined_import get-arg0 s16.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (export "f" (func (param $p u32))))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_u32 arg0 call.wasm return) - (call_interface get-arg0 u32.from_i32 arg0 call.interface return) + (declared_import get-arg0 i32.from_u32 arg0 call.wasm return) + (defined_import get-arg0 u32.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (export "f" (func (param $p s32))))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_s32 arg0 call.wasm return) - (call_interface get-arg0 s32.from_i32 arg0 call.interface return) + (declared_import get-arg0 i32.from_s32 arg0 call.wasm return) + (defined_import get-arg0 s32.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (export "f" (func (param $p u64))))) (wasm (param i64)) - (call_wasm get-arg0 i64.from_u64 arg0 call.wasm return) - (call_interface get-arg0 u64.from_i64 arg0 call.interface return) + (declared_import get-arg0 i64.from_u64 arg0 call.wasm return) + (defined_import get-arg0 u64.from_i64 arg0 call.interface return) ) (assert_abi (witx (module $x (export "f" (func (param $p s64))))) (wasm (param i64)) - (call_wasm get-arg0 i64.from_s64 arg0 call.wasm return) - (call_interface get-arg0 s64.from_i64 arg0 call.interface return) + (declared_import get-arg0 i64.from_s64 arg0 call.wasm return) + (defined_import get-arg0 s64.from_i64 arg0 call.interface return) ) (assert_abi (witx (module $x (export "f" (func (param $p f32))))) (wasm (param f32)) - (call_wasm get-arg0 f32.from_if32 arg0 call.wasm return) - (call_interface get-arg0 if32.from_f32 arg0 call.interface return) + (declared_import get-arg0 f32.from_if32 arg0 call.wasm return) + (defined_import get-arg0 if32.from_f32 arg0 call.interface return) ) (assert_abi (witx (module $x (export "f" (func (param $p f64))))) (wasm (param f64)) - (call_wasm get-arg0 f64.from_if64 arg0 call.wasm return) - (call_interface get-arg0 if64.from_f64 arg0 call.interface return) + (declared_import get-arg0 f64.from_if64 arg0 call.wasm return) + (defined_import get-arg0 if64.from_f64 arg0 call.interface return) ) (assert_abi (witx (module $x (export "f" (func (param $p char))))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_char arg0 call.wasm return) - (call_interface get-arg0 char.from_i32 arg0 call.interface return) + (declared_import get-arg0 i32.from_char arg0 call.wasm return) + (defined_import get-arg0 char.from_i32 arg0 call.interface return) ) ;; handle argument @@ -80,8 +80,8 @@ (module $x (export "f" (func (param $p $y)))) ) (wasm (param i32)) - (call_wasm get-arg0 i32.from_handle arg0 call.wasm return) - (call_interface get-arg0 handle.from_i32 arg0 call.interface return) + (declared_import get-arg0 i32.from_borrowed_handle arg0 call.wasm return) + (defined_import get-arg0 handle.borrowed_from_i32 arg0 call.interface return) ) ;; record arguments @@ -91,8 +91,8 @@ (module $x (export "f" (func (param $p $y)))) ) (wasm) - (call_wasm get-arg0 record-lower arg0 call.wasm return) - (call_interface record-lift call.interface return) + (declared_import get-arg0 record-lower arg0 call.wasm return) + (defined_import record-lift call.interface return) ) (assert_abi @@ -101,13 +101,13 @@ (module $x (export "f" (func (param $p $y)))) ) (wasm (param i32)) - (call_wasm + (declared_import get-arg0 record-lower arg0 i32.from_u32 field.a call.wasm return) - (call_interface + (defined_import get-arg0 u32.from_i32 arg0 record-lift @@ -123,14 +123,14 @@ (module $x (export "f" (func (param $p $y)))) ) (wasm (param i32 i64)) - (call_wasm + (declared_import get-arg0 record-lower arg0 i32.from_u32 field.a i64.from_s64 field.b call.wasm return) - (call_interface + (defined_import get-arg0 get-arg1 u32.from_i32 arg0 @@ -151,7 +151,7 @@ (module $x (export "f" (func (param $p $y)))) ) (wasm (param i32 f64 i32)) - (call_wasm + (declared_import get-arg0 record-lower arg0 i32.from_u32 field.a @@ -161,7 +161,7 @@ i32.from_u32 field.1 call.wasm return) - (call_interface + (defined_import get-arg0 get-arg1 get-arg2 @@ -182,7 +182,7 @@ (module $x (export "f" (func (param $p $y)))) ) (wasm (param i32)) - (call_wasm + (declared_import get-arg0 block.push i32.const 0 @@ -193,7 +193,7 @@ variant-lower arg0 call.wasm return) - (call_interface + (defined_import get-arg0 block.push block.finish @@ -210,7 +210,7 @@ (module $x (export "f" (func (param $p $y)))) ) (wasm (param i32 i32)) - (call_wasm + (declared_import get-arg0 block.push i32.const 0 @@ -226,7 +226,7 @@ variant-lower arg0 call.wasm return) - (call_interface + (defined_import get-arg0 get-arg1 block.push @@ -247,7 +247,7 @@ (module $x (export "f" (func (param $p $y)))) ) (wasm (param i32 f64)) - (call_wasm + (declared_import get-arg0 block.push i32.const 0 @@ -261,7 +261,7 @@ variant-lower arg0 call.wasm return) - (call_interface + (defined_import get-arg0 get-arg1 block.push @@ -280,12 +280,12 @@ (module $x (export "f" (func (param $p (list u8))))) ) (wasm (param i32 i32)) - (call_wasm + (declared_import get-arg0 list.canon_lower arg0 call.wasm return) - (call_interface + (defined_import get-arg0 get-arg1 list.canon_lift arg0 arg1 @@ -297,12 +297,12 @@ (module $x (export "f" (func (param $p (list char))))) ) (wasm (param i32 i32)) - (call_wasm + (declared_import get-arg0 list.canon_lower arg0 call.wasm return) - (call_interface + (defined_import get-arg0 get-arg1 list.canon_lift arg0 arg1 @@ -333,7 +333,7 @@ ))) ) (wasm (param i32 i32 i32 f64 i32 i32 i32 f32 f64)) - (call_wasm + (declared_import get-arg0 get-arg1 get-arg2 @@ -391,7 +391,7 @@ call.wasm return) - (call_interface + (defined_import get-arg0 get-arg1 get-arg2 @@ -447,68 +447,68 @@ (assert_abi (witx (module $x (export "f" (func (result $p u8))))) (wasm (result i32)) - (call_wasm call.wasm u8.from_i32 ret0 return) - (call_interface call.interface i32.from_u8 ret0 return) + (declared_import call.wasm u8.from_i32 ret0 return) + (defined_import call.interface i32.from_u8 ret0 return) ) (assert_abi (witx (module $x (export "f" (func (result $p s8))))) (wasm (result i32)) - (call_wasm call.wasm s8.from_i32 ret0 return) - (call_interface call.interface i32.from_s8 ret0 return) + (declared_import call.wasm s8.from_i32 ret0 return) + (defined_import call.interface i32.from_s8 ret0 return) ) (assert_abi (witx (module $x (export "f" (func (result $p u16))))) (wasm (result i32)) - (call_wasm call.wasm u16.from_i32 ret0 return) - (call_interface call.interface i32.from_u16 ret0 return) + (declared_import call.wasm u16.from_i32 ret0 return) + (defined_import call.interface i32.from_u16 ret0 return) ) (assert_abi (witx (module $x (export "f" (func (result $p s16))))) (wasm (result i32)) - (call_wasm call.wasm s16.from_i32 ret0 return) - (call_interface call.interface i32.from_s16 ret0 return) + (declared_import call.wasm s16.from_i32 ret0 return) + (defined_import call.interface i32.from_s16 ret0 return) ) (assert_abi (witx (module $x (export "f" (func (result $p u32))))) (wasm (result i32)) - (call_wasm call.wasm u32.from_i32 ret0 return) - (call_interface call.interface i32.from_u32 ret0 return) + (declared_import call.wasm u32.from_i32 ret0 return) + (defined_import call.interface i32.from_u32 ret0 return) ) (assert_abi (witx (module $x (export "f" (func (result $p s32))))) (wasm (result i32)) - (call_wasm call.wasm s32.from_i32 ret0 return) - (call_interface call.interface i32.from_s32 ret0 return) + (declared_import call.wasm s32.from_i32 ret0 return) + (defined_import call.interface i32.from_s32 ret0 return) ) (assert_abi (witx (module $x (export "f" (func (result $p u64))))) (wasm (result i64)) - (call_wasm call.wasm u64.from_i64 ret0 return) - (call_interface call.interface i64.from_u64 ret0 return) + (declared_import call.wasm u64.from_i64 ret0 return) + (defined_import call.interface i64.from_u64 ret0 return) ) (assert_abi (witx (module $x (export "f" (func (result $p s64))))) (wasm (result i64)) - (call_wasm call.wasm s64.from_i64 ret0 return) - (call_interface call.interface i64.from_s64 ret0 return) + (declared_import call.wasm s64.from_i64 ret0 return) + (defined_import call.interface i64.from_s64 ret0 return) ) (assert_abi (witx (module $x (export "f" (func (result $p f32))))) (wasm (result f32)) - (call_wasm call.wasm if32.from_f32 ret0 return) - (call_interface call.interface f32.from_if32 ret0 return) + (declared_import call.wasm if32.from_f32 ret0 return) + (defined_import call.interface f32.from_if32 ret0 return) ) (assert_abi (witx (module $x (export "f" (func (result $p f64))))) (wasm (result f64)) - (call_wasm call.wasm if64.from_f64 ret0 return) - (call_interface call.interface f64.from_if64 ret0 return) + (declared_import call.wasm if64.from_f64 ret0 return) + (defined_import call.interface f64.from_if64 ret0 return) ) (assert_abi (witx (module $x (export "f" (func (result $p char))))) (wasm (result i32)) - (call_wasm call.wasm char.from_i32 ret0 return) - (call_interface call.interface i32.from_char ret0 return) + (declared_import call.wasm char.from_i32 ret0 return) + (defined_import call.interface i32.from_char ret0 return) ) ;; handle result @@ -518,8 +518,8 @@ (module $x (export "f" (func (result $p $y)))) ) (wasm (result i32)) - (call_wasm call.wasm handle.from_i32 ret0 return) - (call_interface call.interface i32.from_handle ret0 return) + (declared_import call.wasm handle.owned_from_i32 ret0 return) + (defined_import call.interface i32.from_owned_handle ret0 return) ) ;; record results @@ -529,8 +529,8 @@ (module $x (export "f" (func (result $p $y)))) ) (wasm) - (call_wasm call.wasm record-lift return) - (call_interface call.interface record-lower ret0 return) + (declared_import call.wasm record-lift return) + (defined_import call.interface record-lower ret0 return) ) (assert_abi (witx @@ -538,8 +538,8 @@ (module $x (export "f" (func (result $p $y)))) ) (wasm (result i32)) - (call_wasm call.wasm s32.from_i32 ret0 record-lift return) - (call_interface + (declared_import call.wasm s32.from_i32 ret0 record-lift return) + (defined_import call.interface record-lower ret0 i32.from_s32 field.a @@ -551,14 +551,14 @@ (module $x (export "f" (func (result $p $y)))) ) (wasm (param i32)) - (call_wasm + (declared_import call.wasm rp0 i32.load offset=0 rp0 i32.load offset=8 rp0 s32.from_i32 u32.from_i32 record-lift return) - (call_interface + (defined_import call.interface record-lower ret0 i32.from_s32 field.a @@ -576,7 +576,7 @@ (module $x (export "f" (func (result $p $y)))) ) (wasm (result i32)) - (call_wasm + (declared_import call.wasm block.push block.finish @@ -584,7 +584,7 @@ block.finish variant-lift ret0 return) - (call_interface + (defined_import call.interface block.push i32.const 0 @@ -602,7 +602,7 @@ (module $x (export "f" (func (result $p $y)))) ) (wasm (param i32)) - (call_wasm + (declared_import call.wasm rp0 i32.load offset=0 rp0 f32.load offset=8 rp0 @@ -613,7 +613,7 @@ block.finish variant-lift return) - (call_interface + (defined_import call.interface block.push i32.const 0 @@ -637,7 +637,7 @@ (module $x (export "f" (func (result $a s32) (result $b f64) (result $c bool)))) ) (wasm (param i32)) - (call_wasm + (declared_import call.wasm rp0 i32.load offset=0 rp0 f64.load offset=8 rp0 @@ -652,7 +652,7 @@ variant-lift return ) - (call_interface + (defined_import call.interface i32.from_s32 ret0 f64.from_if64 ret1 @@ -693,13 +693,13 @@ (module $x (export "f" (func (param $a (list (tuple u32 u32)))))) ) (wasm (param i32 i32)) - (call_wasm + (declared_import get-arg0 list.canon_lower arg0 call.wasm return ) - (call_interface + (defined_import get-arg0 get-arg1 list.canon_lift arg0 arg1 @@ -712,7 +712,7 @@ (module $x (export "f" (func (param $a (list bool))))) ) (wasm (param i32 i32)) - (call_wasm + (declared_import get-arg0 block.push iter-elem @@ -731,7 +731,7 @@ call.wasm return ) - (call_interface + (defined_import get-arg0 get-arg1 block.push @@ -754,7 +754,7 @@ (module $x (export "f" (func (param $a (list $a))))) ) (wasm (param i32 i32)) - (call_wasm + (declared_import get-arg0 block.push iter-elem @@ -777,7 +777,7 @@ call.wasm return ) - (call_interface + (defined_import get-arg0 get-arg1 block.push diff --git a/tools/witx/tests/witxt/abi.witxt b/tools/witx/tests/witxt/abi.witxt index ae2b42d8e..8d46dff17 100644 --- a/tools/witx/tests/witxt/abi.witxt +++ b/tools/witx/tests/witxt/abi.witxt @@ -1,100 +1,100 @@ (assert_abi (witx (module $x (@interface func (export "f")))) (wasm) - (call_wasm call.wasm return) - (call_interface call.interface return) + (declared_import call.wasm return) + (defined_import call.interface return) ) ;; scalar arguments (assert_abi (witx (module $x (@interface func (export "f") (param $p u8)))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_u8 arg0 call.wasm return) - (call_interface get-arg0 u8.from_i32 arg0 call.interface return) + (declared_import get-arg0 i32.from_u8 arg0 call.wasm return) + (defined_import get-arg0 u8.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p s8)))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_s8 arg0 call.wasm return) - (call_interface get-arg0 s8.from_i32 arg0 call.interface return) + (declared_import get-arg0 i32.from_s8 arg0 call.wasm return) + (defined_import get-arg0 s8.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p u16)))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_u16 arg0 call.wasm return) - (call_interface get-arg0 u16.from_i32 arg0 call.interface return) + (declared_import get-arg0 i32.from_u16 arg0 call.wasm return) + (defined_import get-arg0 u16.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p s16)))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_s16 arg0 call.wasm return) - (call_interface get-arg0 s16.from_i32 arg0 call.interface return) + (declared_import get-arg0 i32.from_s16 arg0 call.wasm return) + (defined_import get-arg0 s16.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p u32)))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_u32 arg0 call.wasm return) - (call_interface get-arg0 u32.from_i32 arg0 call.interface return) + (declared_import get-arg0 i32.from_u32 arg0 call.wasm return) + (defined_import get-arg0 u32.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p s32)))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_s32 arg0 call.wasm return) - (call_interface get-arg0 s32.from_i32 arg0 call.interface return) + (declared_import get-arg0 i32.from_s32 arg0 call.wasm return) + (defined_import get-arg0 s32.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p u64)))) (wasm (param i64)) - (call_wasm get-arg0 i64.from_u64 arg0 call.wasm return) - (call_interface get-arg0 u64.from_i64 arg0 call.interface return) + (declared_import get-arg0 i64.from_u64 arg0 call.wasm return) + (defined_import get-arg0 u64.from_i64 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p s64)))) (wasm (param i64)) - (call_wasm get-arg0 i64.from_s64 arg0 call.wasm return) - (call_interface get-arg0 s64.from_i64 arg0 call.interface return) + (declared_import get-arg0 i64.from_s64 arg0 call.wasm return) + (defined_import get-arg0 s64.from_i64 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p f32)))) (wasm (param f32)) - (call_wasm get-arg0 f32.from_if32 arg0 call.wasm return) - (call_interface get-arg0 if32.from_f32 arg0 call.interface return) + (declared_import get-arg0 f32.from_if32 arg0 call.wasm return) + (defined_import get-arg0 if32.from_f32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p f64)))) (wasm (param f64)) - (call_wasm get-arg0 f64.from_if64 arg0 call.wasm return) - (call_interface get-arg0 if64.from_f64 arg0 call.interface return) + (declared_import get-arg0 f64.from_if64 arg0 call.wasm return) + (defined_import get-arg0 if64.from_f64 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p (@witx usize))))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_usize arg0 call.wasm return) - (call_interface get-arg0 usize.from_i32 arg0 call.interface return) + (declared_import get-arg0 i32.from_usize arg0 call.wasm return) + (defined_import get-arg0 usize.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p (@witx char8))))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_char8 arg0 call.wasm return) - (call_interface get-arg0 char8.from_i32 arg0 call.interface return) + (declared_import get-arg0 i32.from_char8 arg0 call.wasm return) + (defined_import get-arg0 char8.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p char)))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_char arg0 call.wasm return) - (call_interface get-arg0 char.from_i32 arg0 call.interface return) + (declared_import get-arg0 i32.from_char arg0 call.wasm return) + (defined_import get-arg0 char.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p (@witx pointer u8))))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_pointer arg0 call.wasm return) - (call_interface get-arg0 pointer.from_i32 arg0 call.interface return) + (declared_import get-arg0 i32.from_pointer arg0 call.wasm return) + (defined_import get-arg0 pointer.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p (@witx const_pointer u8))))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_const_pointer arg0 call.wasm return) - (call_interface get-arg0 const_pointer.from_i32 arg0 call.interface return) + (declared_import get-arg0 i32.from_const_pointer arg0 call.wasm return) + (defined_import get-arg0 const_pointer.from_i32 arg0 call.interface return) ) ;; flags parameter @@ -104,8 +104,8 @@ (module $x (@interface func (export "f") (param $p $a))) ) (wasm (param i32)) - (call_wasm get-arg0 i32.from_bitflags arg0 call.wasm return) - (call_interface get-arg0 bitflags.from_i32 arg0 call.interface return) + (declared_import get-arg0 i32.from_bitflags arg0 call.wasm return) + (defined_import get-arg0 bitflags.from_i32 arg0 call.interface return) ) (assert_abi (witx @@ -113,8 +113,8 @@ (module $x (@interface func (export "f") (param $p $a))) ) (wasm (param i64)) - (call_wasm get-arg0 i64.from_bitflags arg0 call.wasm return) - (call_interface get-arg0 bitflags.from_i64 arg0 call.interface return) + (declared_import get-arg0 i64.from_bitflags arg0 call.wasm return) + (defined_import get-arg0 bitflags.from_i64 arg0 call.interface return) ) ;; struct parameter @@ -124,8 +124,8 @@ (module $x (@interface func (export "f") (param $p $a))) ) (wasm (param i32)) - (call_wasm get-arg0 addr-of arg0 call.wasm return) - (call_interface get-arg0 load arg0 call.interface return) + (declared_import get-arg0 addr-of arg0 call.wasm return) + (defined_import get-arg0 load arg0 call.interface return) ) ;; handle parameter @@ -135,8 +135,8 @@ (module $x (@interface func (export "f") (param $p $a))) ) (wasm (param i32)) - (call_wasm get-arg0 i32.from_handle arg0 call.wasm return) - (call_interface get-arg0 handle.from_i32 arg0 call.interface return) + (declared_import get-arg0 i32.from_borrowed_handle arg0 call.wasm return) + (defined_import get-arg0 handle.borrowed_from_i32 arg0 call.interface return) ) ;; list parameter @@ -145,8 +145,8 @@ (module $x (@interface func (export "f") (param $p (list u8)))) ) (wasm (param i32 i32)) - (call_wasm get-arg0 list.pointer_length arg0 call.wasm return) - (call_interface + (declared_import get-arg0 list.pointer_length arg0 call.wasm return) + (defined_import get-arg0 get-arg1 list.from_pointer_length arg0 arg1 @@ -161,8 +161,8 @@ (module $x (@interface func (export "f") (param $p $a))) ) (wasm (param i32)) - (call_wasm get-arg0 enum.lower arg0 call.wasm return) - (call_interface get-arg0 enum.lift arg0 call.interface return) + (declared_import get-arg0 enum.lower arg0 call.wasm return) + (defined_import get-arg0 enum.lift arg0 call.interface return) ) (assert_abi (witx @@ -170,100 +170,100 @@ (module $x (@interface func (export "f") (param $p $a))) ) (wasm (param i32)) - (call_wasm get-arg0 addr-of arg0 call.wasm return) - (call_interface get-arg0 load arg0 call.interface return) + (declared_import get-arg0 addr-of arg0 call.wasm return) + (defined_import get-arg0 load arg0 call.interface return) ) ;; scalar returns (assert_abi (witx (module $x (@interface func (export "f") (result $p u8)))) (wasm (result i32)) - (call_wasm call.wasm u8.from_i32 ret0 return) - (call_interface call.interface i32.from_u8 ret0 return) + (declared_import call.wasm u8.from_i32 ret0 return) + (defined_import call.interface i32.from_u8 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p s8)))) (wasm (result i32)) - (call_wasm call.wasm s8.from_i32 ret0 return) - (call_interface call.interface i32.from_s8 ret0 return) + (declared_import call.wasm s8.from_i32 ret0 return) + (defined_import call.interface i32.from_s8 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p u16)))) (wasm (result i32)) - (call_wasm call.wasm u16.from_i32 ret0 return) - (call_interface call.interface i32.from_u16 ret0 return) + (declared_import call.wasm u16.from_i32 ret0 return) + (defined_import call.interface i32.from_u16 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p s16)))) (wasm (result i32)) - (call_wasm call.wasm s16.from_i32 ret0 return) - (call_interface call.interface i32.from_s16 ret0 return) + (declared_import call.wasm s16.from_i32 ret0 return) + (defined_import call.interface i32.from_s16 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p u32)))) (wasm (result i32)) - (call_wasm call.wasm u32.from_i32 ret0 return) - (call_interface call.interface i32.from_u32 ret0 return) + (declared_import call.wasm u32.from_i32 ret0 return) + (defined_import call.interface i32.from_u32 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p s32)))) (wasm (result i32)) - (call_wasm call.wasm s32.from_i32 ret0 return) - (call_interface call.interface i32.from_s32 ret0 return) + (declared_import call.wasm s32.from_i32 ret0 return) + (defined_import call.interface i32.from_s32 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p u64)))) (wasm (result i64)) - (call_wasm call.wasm u64.from_i64 ret0 return) - (call_interface call.interface i64.from_u64 ret0 return) + (declared_import call.wasm u64.from_i64 ret0 return) + (defined_import call.interface i64.from_u64 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p s64)))) (wasm (result i64)) - (call_wasm call.wasm s64.from_i64 ret0 return) - (call_interface call.interface i64.from_s64 ret0 return) + (declared_import call.wasm s64.from_i64 ret0 return) + (defined_import call.interface i64.from_s64 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p f32)))) (wasm (result f32)) - (call_wasm call.wasm if32.from_f32 ret0 return) - (call_interface call.interface f32.from_if32 ret0 return) + (declared_import call.wasm if32.from_f32 ret0 return) + (defined_import call.interface f32.from_if32 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p f64)))) (wasm (result f64)) - (call_wasm call.wasm if64.from_f64 ret0 return) - (call_interface call.interface f64.from_if64 ret0 return) + (declared_import call.wasm if64.from_f64 ret0 return) + (defined_import call.interface f64.from_if64 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p (@witx usize))))) (wasm (result i32)) - (call_wasm call.wasm usize.from_i32 ret0 return) - (call_interface call.interface i32.from_usize ret0 return) + (declared_import call.wasm usize.from_i32 ret0 return) + (defined_import call.interface i32.from_usize ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p (@witx char8))))) (wasm (result i32)) - (call_wasm call.wasm char8.from_i32 ret0 return) - (call_interface call.interface i32.from_char8 ret0 return) + (declared_import call.wasm char8.from_i32 ret0 return) + (defined_import call.interface i32.from_char8 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p char)))) (wasm (result i32)) - (call_wasm call.wasm char.from_i32 ret0 return) - (call_interface call.interface i32.from_char ret0 return) + (declared_import call.wasm char.from_i32 ret0 return) + (defined_import call.interface i32.from_char ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p (@witx pointer u8))))) (wasm (result i32)) - (call_wasm call.wasm pointer.from_i32 ret0 return) - (call_interface call.interface i32.from_pointer ret0 return) + (declared_import call.wasm pointer.from_i32 ret0 return) + (defined_import call.interface i32.from_pointer ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p (@witx const_pointer u8))))) (wasm (result i32)) - (call_wasm call.wasm const_pointer.from_i32 ret0 return) - (call_interface call.interface i32.from_const_pointer ret0 return) + (declared_import call.wasm const_pointer.from_i32 ret0 return) + (defined_import call.interface i32.from_const_pointer ret0 return) ) ;; flags ret0 return @@ -273,8 +273,8 @@ (module $x (@interface func (export "f") (result $p $a))) ) (wasm (result i32)) - (call_wasm call.wasm bitflags.from_i32 ret0 return) - (call_interface call.interface i32.from_bitflags ret0 return) + (declared_import call.wasm bitflags.from_i32 ret0 return) + (defined_import call.interface i32.from_bitflags ret0 return) ) (assert_abi (witx @@ -282,8 +282,8 @@ (module $x (@interface func (export "f") (result $p $a))) ) (wasm (result i64)) - (call_wasm call.wasm bitflags.from_i64 ret0 return) - (call_interface call.interface i64.from_bitflags ret0 return) + (declared_import call.wasm bitflags.from_i64 ret0 return) + (defined_import call.interface i64.from_bitflags ret0 return) ) ;; handle ret0 return @@ -293,8 +293,8 @@ (module $x (@interface func (export "f") (result $p $a))) ) (wasm (result i32)) - (call_wasm call.wasm handle.from_i32 ret0 return) - (call_interface call.interface i32.from_handle ret0 return) + (declared_import call.wasm handle.owned_from_i32 ret0 return) + (defined_import call.interface i32.from_owned_handle ret0 return) ) ;; struct return -- not supported @@ -354,7 +354,7 @@ ) (wasm (result i32)) - (call_wasm + (declared_import call.wasm ;; ok block, nothing happens block.push @@ -368,7 +368,7 @@ result.lift ret0 return) - (call_interface + (defined_import call.interface ;; ok block, nothing happens @@ -396,7 +396,7 @@ ) (wasm (param i32) (result i32)) - (call_wasm + (declared_import call.wasm rp0 ;; ok block, load the return pointer and have it be the result for the `Ok` @@ -412,7 +412,7 @@ result.lift ret0 return) - (call_interface + (defined_import call.interface ;; store the successful result at the first return pointer (the first param) @@ -442,7 +442,7 @@ ) (wasm (param i32 i32) (result i32)) - (call_wasm + (declared_import call.wasm rp0 rp1 block.push @@ -459,7 +459,7 @@ result.lift ret0 return) - (call_interface + (defined_import call.interface ;; note the reverse order since we're consuming the results of lowering the From 0030d39a4ccb9d1a2c9880b646af3108d2e654ec Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 25 Feb 2021 09:00:49 -0800 Subject: [PATCH 06/22] Automatically infer structs as bitflags/tuple/etc Even if they aren't declared as such. --- tools/witx/src/ast.rs | 37 ++++++++++++++++++++++++++++++++ tools/witx/src/validate.rs | 8 +++---- tools/witx/tests/witxt/abi.witxt | 8 +++++++ 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/tools/witx/src/ast.rs b/tools/witx/src/ast.rs index 773a74d52..296d2ce10 100644 --- a/tools/witx/src/ast.rs +++ b/tools/witx/src/ast.rs @@ -379,6 +379,43 @@ impl RecordDatatype { } } +impl RecordKind { + pub fn infer(members: &[RecordMember]) -> RecordKind { + if members.len() == 0 { + return RecordKind::Other; + } + + // Structs-of-bools are classified to get represented as bitflags. + if members.iter().all(|t| is_bool(&t.tref)) { + match members.len() { + n if n <= 8 => return RecordKind::Bitflags(IntRepr::U8), + n if n <= 16 => return RecordKind::Bitflags(IntRepr::U16), + n if n <= 32 => return RecordKind::Bitflags(IntRepr::U32), + n if n <= 64 => return RecordKind::Bitflags(IntRepr::U64), + _ => {} + } + } + + // Members with consecutive integer names get represented as tuples. + if members + .iter() + .enumerate() + .all(|(i, m)| m.name.as_str().parse().ok() == Some(i)) + { + return RecordKind::Tuple; + } + + return RecordKind::Other; + + fn is_bool(t: &TypeRef) -> bool { + match &**t.type_() { + Type::Variant(v) => v.is_bool(), + _ => false, + } + } + } +} + /// A type which represents how values can be one of a set of possible cases. /// /// This type maps to an `enum` in languages like Rust, but doesn't have an diff --git a/tools/witx/src/validate.rs b/tools/witx/src/validate.rs index ebd9388cf..76e90bf54 100644 --- a/tools/witx/src/validate.rs +++ b/tools/witx/src/validate.rs @@ -422,8 +422,8 @@ impl DocValidationScope<'_> { span: wast::Span, ) -> Result { let repr = match syntax.repr { - Some(ty) => self.validate_int_repr(&ty, span)?, - None => IntRepr::U32, + Some(ty) => Some(RecordKind::Bitflags(self.validate_int_repr(&ty, span)?)), + None => None, }; let mut flags_scope = IdentValidation::new(); let mut members = Vec::new(); @@ -437,7 +437,7 @@ impl DocValidationScope<'_> { }); } Ok(RecordDatatype { - kind: RecordKind::Bitflags(repr), + kind: repr.unwrap_or_else(|| RecordKind::infer(&members)), members, }) } @@ -461,7 +461,7 @@ impl DocValidationScope<'_> { .collect::, _>>()?; Ok(RecordDatatype { - kind: RecordKind::Other, + kind: RecordKind::infer(&members), members, }) } diff --git a/tools/witx/tests/witxt/abi.witxt b/tools/witx/tests/witxt/abi.witxt index 8d46dff17..defa4d568 100644 --- a/tools/witx/tests/witxt/abi.witxt +++ b/tools/witx/tests/witxt/abi.witxt @@ -116,6 +116,14 @@ (declared_import get-arg0 i64.from_bitflags arg0 call.wasm return) (defined_import get-arg0 bitflags.from_i64 arg0 call.interface return) ) +(assert_abi + (witx + (typename $a (record (field $x bool))) + (module $x (@interface func (export "f") (param $p $a))) + ) + (wasm (param i32)) + (declared_import get-arg0 i32.from_bitflags arg0 call.wasm return) +) ;; struct parameter (assert_abi From fc59732f808d1c93bf8c19da05bcc5ed3d255f00 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 25 Feb 2021 09:15:28 -0800 Subject: [PATCH 07/22] Support bitflags in the Next ABI Mostly just updating read/write to load/store the appropriate size of the bitflags instead of decomposing into structs-of-bools. --- tools/witx/src/abi.rs | 166 +++++++++++++++++--------- tools/witx/tests/witxt.rs | 9 +- tools/witx/tests/witxt/abi-next.witxt | 66 ++++++++++ 3 files changed, 180 insertions(+), 61 deletions(-) diff --git a/tools/witx/src/abi.rs b/tools/witx/src/abi.rs index dd2bc48db..7d9ec127e 100644 --- a/tools/witx/src/abi.rs +++ b/tools/witx/src/abi.rs @@ -21,8 +21,8 @@ //! generators will need to implement the various instructions to support APIs. use crate::{ - BuiltinType, Id, IntRepr, InterfaceFunc, InterfaceFuncParam, NamedType, RecordDatatype, - RecordKind, Type, TypeRef, Variant, + BuiltinType, Id, IntRepr, InterfaceFunc, InterfaceFuncParam, NamedType, RecordDatatype, Type, + TypeRef, Variant, }; use std::mem; @@ -498,6 +498,29 @@ def_instruction! { name: Option<&'a NamedType>, } : [ty.members.len()] => [1], + /// Converts a language-specific record-of-bools to the packed + /// representation as an `i32`. + I32FromBitflags { + ty: &'a RecordDatatype, + name: &'a NamedType, + } : [1] => [1], + /// Converts a language-specific record-of-bools to the packed + /// representation as an `i64`. + I64FromBitflags { + ty: &'a RecordDatatype, + name: &'a NamedType, + } : [1] => [1], + /// Converts a native wasm `i32` to a language-specific record-of-bools. + BitflagsFromI32 { + ty: &'a RecordDatatype, + name: &'a NamedType, + } : [1] => [1], + /// Converts a native wasm `i64` to a language-specific record-of-bools. + BitflagsFromI64 { + ty: &'a RecordDatatype, + name: &'a NamedType, + } : [1] => [1], + // variants /// This is a special instruction used at the entry of blocks used as @@ -583,12 +606,6 @@ def_instruction! { I32FromPointer : [1] => [1], /// Converts a language-specific pointer value to a wasm `i32`. I32FromConstPointer : [1] => [1], - /// Converts a language-specific record-of-bools to the packed - /// representation as an `i32`. - I32FromBitflags { ty: &'a NamedType } : [1] => [1], - /// Converts a language-specific record-of-bools to the packed - /// representation as an `i64`. - I64FromBitflags { ty: &'a NamedType } : [1] => [1], /// Pops two `i32` values from the stack and creates a list from them of /// the specified type. The first operand is the pointer in linear /// memory to the start of the list and the second operand is the @@ -601,10 +618,6 @@ def_instruction! { PointerFromI32 { ty: &'a TypeRef }: [1] => [1], /// Converts a native wasm `i32` to a language-specific pointer. ConstPointerFromI32 { ty: &'a TypeRef } : [1] => [1], - /// Converts a native wasm `i32` to a language-specific record-of-bools. - BitflagsFromI32 { ty: &'a NamedType } : [1] => [1], - /// Converts a native wasm `i64` to a language-specific record-of-bools. - BitflagsFromI64 { ty: &'a NamedType } : [1] => [1], /// Loads the interface types value from an `i32` pointer popped from /// the stack. Load { ty: &'a NamedType } : [1] => [1], @@ -733,12 +746,6 @@ impl Abi { fn validate_no_witx(ty: &Type) -> Result<(), String> { match ty { Type::Record(r) => { - match r.kind { - RecordKind::Bitflags(_) => { - return Err("cannot use `(@witx bitflags)` in this ABI".to_string()) - } - RecordKind::Tuple | RecordKind::Other => {} - } for r in r.members.iter() { validate_no_witx(r.tref.type_())?; } @@ -1357,10 +1364,10 @@ impl<'a, B: Bindgen> Generator<'a, B> { }, Type::Record(r) => { if let Some(repr) = r.bitflags_repr() { - let ty = ty.name().unwrap(); + let name = ty.name().unwrap(); match repr { - IntRepr::U64 => return self.witx(&I64FromBitflags { ty }), - _ => return self.witx(&I32FromBitflags { ty }), + IntRepr::U64 => return self.emit(&I64FromBitflags { ty: r, name }), + _ => return self.emit(&I32FromBitflags { ty: r, name }), } } match self.abi { @@ -1610,10 +1617,10 @@ impl<'a, B: Bindgen> Generator<'a, B> { }, Type::Record(r) => { if let Some(repr) = r.bitflags_repr() { - let ty = ty.name().unwrap(); + let name = ty.name().unwrap(); match repr { - IntRepr::U64 => return self.witx(&BitflagsFromI64 { ty }), - _ => return self.witx(&BitflagsFromI32 { ty }), + IntRepr::U64 => return self.emit(&BitflagsFromI64 { ty: r, name }), + _ => return self.emit(&BitflagsFromI32 { ty: r, name }), } } match self.abi { @@ -1770,26 +1777,54 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&I32Store { offset }); } - // Decompose the record into its components and then write all the - // components into memory one-by-one. - Type::Record(r) => { - self.emit(&RecordLower { - ty: r, - name: ty.name(), - }); - let fields = self - .stack - .drain(self.stack.len() - r.members.len()..) - .collect::>(); - for (layout, field) in r.member_layout().iter().zip(fields) { - self.stack.push(field); - self.write_to_memory( - &layout.member.tref, - addr.clone(), - offset + (layout.offset as i32), - ); + Type::Record(r) => match r.bitflags_repr() { + // Bitflags just have their appropriate width written into + // memory. + Some(repr) => { + let name = ty.name().unwrap(); + let store = match repr { + IntRepr::U64 => { + self.emit(&I64FromBitflags { ty: r, name }); + I64Store { offset } + } + IntRepr::U32 => { + self.emit(&I32FromBitflags { ty: r, name }); + I32Store { offset } + } + IntRepr::U16 => { + self.emit(&I32FromBitflags { ty: r, name }); + I32Store16 { offset } + } + IntRepr::U8 => { + self.emit(&I32FromBitflags { ty: r, name }); + I32Store8 { offset } + } + }; + self.stack.push(addr); + self.emit(&store); } - } + + // Decompose the record into its components and then write all + // the components into memory one-by-one. + None => { + self.emit(&RecordLower { + ty: r, + name: ty.name(), + }); + let fields = self + .stack + .drain(self.stack.len() - r.members.len()..) + .collect::>(); + for (layout, field) in r.member_layout().iter().zip(fields) { + self.stack.push(field); + self.write_to_memory( + &layout.member.tref, + addr.clone(), + offset + (layout.offset as i32), + ); + } + } + }, // Each case will get its own block, and the first item in each // case is writing the discriminant. After that if we have a @@ -1858,21 +1893,38 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.lift(ty, false); } - // Read and lift each field individually, adjusting the offset - // as we go along, then aggregate all the fields into the record. - Type::Record(r) => { - for layout in r.member_layout() { - self.read_from_memory( - &layout.member.tref, - addr.clone(), - offset + (layout.offset as i32), - ); + Type::Record(r) => match r.bitflags_repr() { + // Bitflags just have their appropriate size read from + // memory. + Some(repr) => { + let name = ty.name().unwrap(); + let (load, cast) = match repr { + IntRepr::U64 => (I64Load { offset }, I64FromBitflags { ty: r, name }), + IntRepr::U32 => (I32Load { offset }, I32FromBitflags { ty: r, name }), + IntRepr::U16 => (I32Load16U { offset }, I32FromBitflags { ty: r, name }), + IntRepr::U8 => (I32Load8U { offset }, I32FromBitflags { ty: r, name }), + }; + self.stack.push(addr); + self.emit(&load); + self.emit(&cast); } - self.emit(&RecordLift { - ty: r, - name: ty.name(), - }); - } + // Read and lift each field individually, adjusting the offset + // as we go along, then aggregate all the fields into the + // record. + None => { + for layout in r.member_layout() { + self.read_from_memory( + &layout.member.tref, + addr.clone(), + offset + (layout.offset as i32), + ); + } + self.emit(&RecordLift { + ty: r, + name: ty.name(), + }); + } + }, // Each case will get its own block, and we'll dispatch to the // right block based on the `i32.load` we initially perform. Each diff --git a/tools/witx/tests/witxt.rs b/tools/witx/tests/witxt.rs index 076de7225..8c641e2f6 100644 --- a/tools/witx/tests/witxt.rs +++ b/tools/witx/tests/witxt.rs @@ -474,11 +474,12 @@ impl witx::Bindgen for AbiBindgen<'_> { IterElem => self.assert("iter-elem"), IterBasePointer => self.assert("iter-base-pointer"), + I32FromBitflags { .. } => self.assert("i32.from_bitflags"), + BitflagsFromI32 { .. } => self.assert("bitflags.from_i32"), + I64FromBitflags { .. } => self.assert("i64.from_bitflags"), + BitflagsFromI64 { .. } => self.assert("bitflags.from_i64"), + Witx { instr } => match instr { - I32FromBitflags { .. } => self.assert("i32.from_bitflags"), - BitflagsFromI32 { .. } => self.assert("bitflags.from_i32"), - I64FromBitflags { .. } => self.assert("i64.from_bitflags"), - BitflagsFromI64 { .. } => self.assert("bitflags.from_i64"), PointerFromI32 { .. } => self.assert("pointer.from_i32"), ConstPointerFromI32 { .. } => self.assert("const_pointer.from_i32"), ResultLift => self.assert("result.lift"), diff --git a/tools/witx/tests/witxt/abi-next.witxt b/tools/witx/tests/witxt/abi-next.witxt index 073b211b6..2bb15c092 100644 --- a/tools/witx/tests/witxt/abi-next.witxt +++ b/tools/witx/tests/witxt/abi-next.witxt @@ -798,3 +798,69 @@ return ) ) + +;; bitflags +(assert_abi + (witx + (typename $a (flags $a $b)) + (module $x (export "f" (func (param $a $a) (result $b $a)))) + ) + (wasm (param i32) (result i32)) + (declared_import + get-arg0 + i32.from_bitflags arg0 + call.wasm + bitflags.from_i32 ret0 + return + ) + (defined_import + get-arg0 + bitflags.from_i32 arg0 + call.interface + i32.from_bitflags ret0 + return + ) +) +(assert_abi + (witx + (typename $a (flags + $f0 $f1 $f2 $f3 $f4 $f5 $f6 $f7 + $f8 $f9 $f10 $f11 $f12 $f13 $f14 $f15 + $f16 $f17 $f18 $f19 $f20 $f21 $f22 $f23 + $f24 $f25 $f26 $f27 $f28 $f29 $f30 $f31 + $f32 + )) + (module $x (export "f" (func (param $a $a) (result $b $a)))) + ) + (wasm (param i64) (result i64)) +) +(assert_abi + (witx + (typename $a (flags $a $b)) + (module $x (export "f" (func + (param $a (list $a)) + (result $b (list $a)) + ))) + ) + (wasm (param i32 i32 i32)) + (declared_import + get-arg0 + block.push + iter-elem + iter-base-pointer + i32.from_bitflags + i32.store8 offset=0 + block.finish + list.lower arg0 + call.wasm rp0 + i32.load offset=0 rp0 + i32.load offset=8 rp0 + block.push + iter-base-pointer + i32.load8u offset=0 + i32.from_bitflags + block.finish + list.lift + return + ) +) From eefa46cee4d7353fca41b8e9c773e90ee2c55ff6 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 25 Feb 2021 09:34:36 -0800 Subject: [PATCH 08/22] Support `@witx tag` in the Next ABI * Automatically size enums based on how many cases they have * Read/write the tag appropriate tag size --- phases/ephemeral/docs.md | 190 ++++++++++---------- phases/old/snapshot_0/docs.md | 184 +++++++++---------- phases/snapshot/docs.md | 184 +++++++++---------- tools/witx/src/abi.rs | 62 ++++--- tools/witx/src/ast.rs | 10 ++ tools/witx/src/docs/ast.rs | 8 +- tools/witx/src/validate.rs | 30 ++-- tools/witx/tests/witxt/abi-next.witxt | 12 +- tools/witx/tests/witxt/representation.witxt | 4 +- 9 files changed, 349 insertions(+), 335 deletions(-) diff --git a/phases/ephemeral/docs.md b/phases/ephemeral/docs.md index 7fa4c11de..d604dfd49 100644 --- a/phases/ephemeral/docs.md +++ b/phases/ephemeral/docs.md @@ -1222,9 +1222,9 @@ The size of the array should match that returned by [`sizes_get`](#sizes_get) - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1245,7 +1245,7 @@ data, or an error. ###### Variant Layout - size: 12 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: `(size, size)` @@ -1284,7 +1284,7 @@ The resolution of the clock. ###### Variant Layout - size: 16 - align: 8 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`timestamp`](#timestamp) @@ -1311,7 +1311,7 @@ The time value of the clock. ###### Variant Layout - size: 16 - align: 8 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`timestamp`](#timestamp) @@ -1337,9 +1337,9 @@ The sizes of the buffers should match that returned by [`sizes_get`](#sizes_get) - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1360,7 +1360,7 @@ environment variable data. ###### Variant Layout - size: 12 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: `(size, size)` @@ -1402,9 +1402,9 @@ The advice. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1430,9 +1430,9 @@ The length of the area that is allocated. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1452,9 +1452,9 @@ Note: This is similar to [`close`](#close) in POSIX. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1474,9 +1474,9 @@ Note: This is similar to `fdatasync` in POSIX. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1499,7 +1499,7 @@ The buffer where the file descriptor's attributes are stored. ###### Variant Layout - size: 32 - align: 8 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`fdstat`](#fdstat) @@ -1522,9 +1522,9 @@ The desired values of the file descriptor flags. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1549,9 +1549,9 @@ The desired rights of the file descriptor. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1573,7 +1573,7 @@ The buffer where the file's attributes are stored. ###### Variant Layout - size: 72 - align: 8 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`filestat`](#filestat) @@ -1596,9 +1596,9 @@ The desired file size. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1627,9 +1627,9 @@ A bitmask indicating which timestamps to adjust. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1662,9 +1662,9 @@ The permissions associated with the file. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1693,7 +1693,7 @@ The number of bytes read. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -1715,7 +1715,7 @@ The buffer where the description is stored. ###### Variant Layout - size: 12 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`prestat`](#prestat) @@ -1739,9 +1739,9 @@ A buffer into which to write the preopened directory name. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1774,7 +1774,7 @@ The number of bytes written. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -1800,7 +1800,7 @@ The number of bytes read. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -1838,7 +1838,7 @@ The number of bytes stored in the read buffer. If less than the size of the read ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -1867,9 +1867,9 @@ The file descriptor to overwrite. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1898,7 +1898,7 @@ The new offset of the file descriptor, relative to the start of the file. ###### Variant Layout - size: 16 - align: 8 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`filesize`](#filesize) @@ -1918,9 +1918,9 @@ Note: This is similar to `fsync` in POSIX. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1943,7 +1943,7 @@ The current offset of the file descriptor, relative to the start of the file. ###### Variant Layout - size: 16 - align: 8 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`filesize`](#filesize) @@ -1973,7 +1973,7 @@ The number of bytes written. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -2000,9 +2000,9 @@ The path at which to create the directory. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2031,7 +2031,7 @@ The buffer where the file's attributes are stored. ###### Variant Layout - size: 72 - align: 8 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`filestat`](#filestat) @@ -2066,9 +2066,9 @@ A bitmask indicating which timestamps to adjust. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2107,9 +2107,9 @@ The permissions to associate with the file. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2141,9 +2141,9 @@ The destination path at which to create the hard link. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2197,7 +2197,7 @@ The file descriptor of the file that has been opened. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`fd`](#fd) @@ -2228,7 +2228,7 @@ The number of bytes placed in the buffer. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -2252,9 +2252,9 @@ The path to a directory to remove. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2283,9 +2283,9 @@ The destination path to which to rename the file or directory. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2311,9 +2311,9 @@ The destination path at which to create the symbolic link. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2337,9 +2337,9 @@ The path to a file to unlink. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2374,7 +2374,7 @@ The number of events stored. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -2421,9 +2421,9 @@ The buffer to fill with random data. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2444,9 +2444,9 @@ Note: This is similar to [`yield`](#yield) in POSIX. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2480,7 +2480,7 @@ Number of bytes stored in ri_data and message flags. ###### Variant Layout - size: 12 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: `(size, roflags)` @@ -2519,7 +2519,7 @@ Number of bytes transmitted. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -2542,9 +2542,9 @@ Which channels on the socket to shut down. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` diff --git a/phases/old/snapshot_0/docs.md b/phases/old/snapshot_0/docs.md index 707c836b2..63ecd4889 100644 --- a/phases/old/snapshot_0/docs.md +++ b/phases/old/snapshot_0/docs.md @@ -1281,9 +1281,9 @@ The size of the array should match that returned by [`args_sizes_get`](#args_siz - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1304,7 +1304,7 @@ data, or an error. ###### Variant Layout - size: 12 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: `(size, size)` @@ -1335,9 +1335,9 @@ The sizes of the buffers should match that returned by [`environ_sizes_get`](#en - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1358,7 +1358,7 @@ environment variable data. ###### Variant Layout - size: 12 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: `(size, size)` @@ -1393,7 +1393,7 @@ The resolution of the clock, or an error if one happened. ###### Variant Layout - size: 16 - align: 8 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`timestamp`](#timestamp) @@ -1420,7 +1420,7 @@ The time value of the clock. ###### Variant Layout - size: 16 - align: 8 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`timestamp`](#timestamp) @@ -1449,9 +1449,9 @@ The advice. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1477,9 +1477,9 @@ The length of the area that is allocated. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1499,9 +1499,9 @@ Note: This is similar to `close` in POSIX. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1521,9 +1521,9 @@ Note: This is similar to `fdatasync` in POSIX. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1546,7 +1546,7 @@ The buffer where the file descriptor's attributes are stored. ###### Variant Layout - size: 32 - align: 8 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`fdstat`](#fdstat) @@ -1569,9 +1569,9 @@ The desired values of the file descriptor flags. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1596,9 +1596,9 @@ The desired rights of the file descriptor. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1620,7 +1620,7 @@ The buffer where the file's attributes are stored. ###### Variant Layout - size: 64 - align: 8 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`filestat`](#filestat) @@ -1643,9 +1643,9 @@ The desired file size. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1674,9 +1674,9 @@ A bitmask indicating which timestamps to adjust. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1705,7 +1705,7 @@ The number of bytes read. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -1727,7 +1727,7 @@ The buffer where the description is stored. ###### Variant Layout - size: 12 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`prestat`](#prestat) @@ -1751,9 +1751,9 @@ A buffer into which to write the preopened directory name. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1782,7 +1782,7 @@ The number of bytes written. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -1808,7 +1808,7 @@ The number of bytes read. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -1846,7 +1846,7 @@ The number of bytes stored in the read buffer. If less than the size of the read ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -1875,9 +1875,9 @@ The file descriptor to overwrite. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1906,7 +1906,7 @@ The new offset of the file descriptor, relative to the start of the file. ###### Variant Layout - size: 16 - align: 8 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`filesize`](#filesize) @@ -1926,9 +1926,9 @@ Note: This is similar to `fsync` in POSIX. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1951,7 +1951,7 @@ The current offset of the file descriptor, relative to the start of the file. ###### Variant Layout - size: 16 - align: 8 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`filesize`](#filesize) @@ -1976,7 +1976,7 @@ List of scatter/gather vectors from which to retrieve data. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -1999,9 +1999,9 @@ The path at which to create the directory. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2030,7 +2030,7 @@ The buffer where the file's attributes are stored. ###### Variant Layout - size: 64 - align: 8 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`filestat`](#filestat) @@ -2065,9 +2065,9 @@ A bitmask indicating which timestamps to adjust. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2099,9 +2099,9 @@ The destination path at which to create the hard link. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2152,7 +2152,7 @@ The file descriptor of the file that has been opened. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`fd`](#fd) @@ -2183,7 +2183,7 @@ The number of bytes placed in the buffer. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -2207,9 +2207,9 @@ The path to a directory to remove. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2238,9 +2238,9 @@ The destination path to which to rename the file or directory. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2266,9 +2266,9 @@ The destination path at which to create the symbolic link. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2292,9 +2292,9 @@ The path to a file to unlink. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2323,7 +2323,7 @@ The number of events stored. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -2357,9 +2357,9 @@ The signal condition to trigger. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2377,9 +2377,9 @@ Note: This is similar to [`sched_yield`](#sched_yield) in POSIX. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2406,9 +2406,9 @@ The buffer to fill with random data. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2438,7 +2438,7 @@ Number of bytes stored in ri_data and message flags. ###### Variant Layout - size: 12 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: `(size, roflags)` @@ -2477,7 +2477,7 @@ Number of bytes transmitted. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -2500,9 +2500,9 @@ Which channels on the socket to shut down. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` diff --git a/phases/snapshot/docs.md b/phases/snapshot/docs.md index c55a661b1..0bf03b167 100644 --- a/phases/snapshot/docs.md +++ b/phases/snapshot/docs.md @@ -1278,9 +1278,9 @@ The size of the array should match that returned by [`args_sizes_get`](#args_siz - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1301,7 +1301,7 @@ data, or an error. ###### Variant Layout - size: 12 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: `(size, size)` @@ -1332,9 +1332,9 @@ The sizes of the buffers should match that returned by [`environ_sizes_get`](#en - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1355,7 +1355,7 @@ environment variable data. ###### Variant Layout - size: 12 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: `(size, size)` @@ -1390,7 +1390,7 @@ The resolution of the clock, or an error if one happened. ###### Variant Layout - size: 16 - align: 8 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`timestamp`](#timestamp) @@ -1417,7 +1417,7 @@ The time value of the clock. ###### Variant Layout - size: 16 - align: 8 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`timestamp`](#timestamp) @@ -1446,9 +1446,9 @@ The advice. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1474,9 +1474,9 @@ The length of the area that is allocated. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1496,9 +1496,9 @@ Note: This is similar to `close` in POSIX. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1518,9 +1518,9 @@ Note: This is similar to `fdatasync` in POSIX. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1543,7 +1543,7 @@ The buffer where the file descriptor's attributes are stored. ###### Variant Layout - size: 32 - align: 8 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`fdstat`](#fdstat) @@ -1566,9 +1566,9 @@ The desired values of the file descriptor flags. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1593,9 +1593,9 @@ The desired rights of the file descriptor. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1617,7 +1617,7 @@ The buffer where the file's attributes are stored. ###### Variant Layout - size: 72 - align: 8 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`filestat`](#filestat) @@ -1640,9 +1640,9 @@ The desired file size. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1671,9 +1671,9 @@ A bitmask indicating which timestamps to adjust. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1702,7 +1702,7 @@ The number of bytes read. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -1724,7 +1724,7 @@ The buffer where the description is stored. ###### Variant Layout - size: 12 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`prestat`](#prestat) @@ -1748,9 +1748,9 @@ A buffer into which to write the preopened directory name. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1779,7 +1779,7 @@ The number of bytes written. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -1805,7 +1805,7 @@ The number of bytes read. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -1843,7 +1843,7 @@ The number of bytes stored in the read buffer. If less than the size of the read ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -1872,9 +1872,9 @@ The file descriptor to overwrite. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1903,7 +1903,7 @@ The new offset of the file descriptor, relative to the start of the file. ###### Variant Layout - size: 16 - align: 8 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`filesize`](#filesize) @@ -1923,9 +1923,9 @@ Note: This is similar to `fsync` in POSIX. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -1948,7 +1948,7 @@ The current offset of the file descriptor, relative to the start of the file. ###### Variant Layout - size: 16 - align: 8 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`filesize`](#filesize) @@ -1973,7 +1973,7 @@ List of scatter/gather vectors from which to retrieve data. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -1996,9 +1996,9 @@ The path at which to create the directory. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2027,7 +2027,7 @@ The buffer where the file's attributes are stored. ###### Variant Layout - size: 72 - align: 8 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`filestat`](#filestat) @@ -2062,9 +2062,9 @@ A bitmask indicating which timestamps to adjust. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2096,9 +2096,9 @@ The destination path at which to create the hard link. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2149,7 +2149,7 @@ The file descriptor of the file that has been opened. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`fd`](#fd) @@ -2180,7 +2180,7 @@ The number of bytes placed in the buffer. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -2204,9 +2204,9 @@ The path to a directory to remove. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2235,9 +2235,9 @@ The destination path to which to rename the file or directory. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2263,9 +2263,9 @@ The destination path at which to create the symbolic link. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2289,9 +2289,9 @@ The path to a file to unlink. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2320,7 +2320,7 @@ The number of events stored. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -2354,9 +2354,9 @@ The signal condition to trigger. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2374,9 +2374,9 @@ Note: This is similar to [`sched_yield`](#sched_yield) in POSIX. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2403,9 +2403,9 @@ The buffer to fill with random data. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` @@ -2435,7 +2435,7 @@ Number of bytes stored in ri_data and message flags. ###### Variant Layout - size: 12 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: `(size, roflags)` @@ -2474,7 +2474,7 @@ Number of bytes transmitted. ###### Variant Layout - size: 8 - align: 4 -- tag_size: 4 +- tag_size: 1 ###### Variant cases - `ok`: [`size`](#size) @@ -2497,9 +2497,9 @@ Which channels on the socket to shut down. - `error`: `Result<(), errno>` ###### Variant Layout -- size: 8 -- align: 4 -- tag_size: 4 +- size: 4 +- align: 2 +- tag_size: 1 ###### Variant cases - `ok` diff --git a/tools/witx/src/abi.rs b/tools/witx/src/abi.rs index 7d9ec127e..77a08ede4 100644 --- a/tools/witx/src/abi.rs +++ b/tools/witx/src/abi.rs @@ -752,9 +752,6 @@ fn validate_no_witx(ty: &Type) -> Result<(), String> { Ok(()) } Type::Variant(v) => { - if v.tag_repr != IntRepr::U32 { - return Err("cannot use `(@witx tag)` in this ABI".to_string()); - } for case in v.cases.iter() { if let Some(ty) = &case.tref { validate_no_witx(ty.type_())?; @@ -1782,26 +1779,12 @@ impl<'a, B: Bindgen> Generator<'a, B> { // memory. Some(repr) => { let name = ty.name().unwrap(); - let store = match repr { - IntRepr::U64 => { - self.emit(&I64FromBitflags { ty: r, name }); - I64Store { offset } - } - IntRepr::U32 => { - self.emit(&I32FromBitflags { ty: r, name }); - I32Store { offset } - } - IntRepr::U16 => { - self.emit(&I32FromBitflags { ty: r, name }); - I32Store16 { offset } - } - IntRepr::U8 => { - self.emit(&I32FromBitflags { ty: r, name }); - I32Store8 { offset } - } - }; + self.emit(&match repr { + IntRepr::U64 => I64FromBitflags { ty: r, name }, + _ => I32FromBitflags { ty: r, name }, + }); self.stack.push(addr); - self.emit(&store); + self.store_intrepr(offset, repr); } // Decompose the record into its components and then write all @@ -1836,7 +1819,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.bindgen.push_block(); self.emit(&I32Const { val: i as i32 }); self.stack.push(addr.clone()); - self.emit(&I32Store { offset }); + self.store_intrepr(offset, v.tag_repr); if let Some(ty) = &case.tref { self.emit(&VariantPayload); self.write_to_memory(ty, addr.clone(), payload_offset); @@ -1898,15 +1881,12 @@ impl<'a, B: Bindgen> Generator<'a, B> { // memory. Some(repr) => { let name = ty.name().unwrap(); - let (load, cast) = match repr { - IntRepr::U64 => (I64Load { offset }, I64FromBitflags { ty: r, name }), - IntRepr::U32 => (I32Load { offset }, I32FromBitflags { ty: r, name }), - IntRepr::U16 => (I32Load16U { offset }, I32FromBitflags { ty: r, name }), - IntRepr::U8 => (I32Load8U { offset }, I32FromBitflags { ty: r, name }), - }; self.stack.push(addr); - self.emit(&load); - self.emit(&cast); + self.load_intrepr(offset, repr); + self.emit(&match repr { + IntRepr::U64 => I64FromBitflags { ty: r, name }, + _ => I32FromBitflags { ty: r, name }, + }); } // Read and lift each field individually, adjusting the offset // as we go along, then aggregate all the fields into the @@ -1932,7 +1912,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { // from the corresponding offset if one is available. Type::Variant(v) => { self.stack.push(addr.clone()); - self.emit(&I32Load { offset }); + self.load_intrepr(offset, v.tag_repr); let payload_offset = offset + (v.payload_offset() as i32); for case in v.cases.iter() { self.bindgen.push_block(); @@ -1948,6 +1928,24 @@ impl<'a, B: Bindgen> Generator<'a, B> { } } } + + fn load_intrepr(&mut self, offset: i32, repr: IntRepr) { + self.emit(&match repr { + IntRepr::U64 => Instruction::I64Load { offset }, + IntRepr::U32 => Instruction::I32Load { offset }, + IntRepr::U16 => Instruction::I32Load16U { offset }, + IntRepr::U8 => Instruction::I32Load8U { offset }, + }); + } + + fn store_intrepr(&mut self, offset: i32, repr: IntRepr) { + self.emit(&match repr { + IntRepr::U64 => Instruction::I64Store { offset }, + IntRepr::U32 => Instruction::I32Store { offset }, + IntRepr::U16 => Instruction::I32Store16 { offset }, + IntRepr::U8 => Instruction::I32Store8 { offset }, + }); + } } fn push_wasm(ty: &Type, result: &mut Vec) { diff --git a/tools/witx/src/ast.rs b/tools/witx/src/ast.rs index 296d2ce10..b5fd281f1 100644 --- a/tools/witx/src/ast.rs +++ b/tools/witx/src/ast.rs @@ -437,6 +437,16 @@ pub struct Variant { } impl Variant { + pub fn infer_repr(cases: usize) -> IntRepr { + match cases { + n if n < u8::max_value() as usize => IntRepr::U8, + n if n < u16::max_value() as usize => IntRepr::U16, + n if n < u32::max_value() as usize => IntRepr::U32, + n if n < u64::max_value() as usize => IntRepr::U64, + _ => panic!("too many cases to fit in a repr"), + } + } + /// If this variant looks like an `expected` shorthand, return the ok/err /// types associated with this result. /// diff --git a/tools/witx/src/docs/ast.rs b/tools/witx/src/docs/ast.rs index a295a6756..20379dcb3 100644 --- a/tools/witx/src/docs/ast.rs +++ b/tools/witx/src/docs/ast.rs @@ -114,16 +114,16 @@ impl ToMarkdown for RecordDatatype { } else { name.to_owned() }; - let (div, offset_desc) = if self.bitflags_repr().is_some() { - (4, "Bit") + let offset_desc = if self.bitflags_repr().is_some() { + "Bit" } else { - (1, "Offset") + "Offset" }; let n = node.new_child(MdNamedType::new( MdHeading::new_bullet(), id.as_str(), name, - format!("{}\n{}: {}\n", &member.docs, offset_desc, offset / div).as_str(), + format!("{}\n{}: {}\n", &member.docs, offset_desc, offset).as_str(), )); member.tref.generate(n.clone()); } diff --git a/tools/witx/src/validate.rs b/tools/witx/src/validate.rs index 76e90bf54..9fcfb5e26 100644 --- a/tools/witx/src/validate.rs +++ b/tools/witx/src/validate.rs @@ -150,7 +150,7 @@ impl DocValidation { entries: HashMap::new(), constant_scopes: HashMap::new(), bool_ty: TypeRef::Value(Rc::new(Type::Variant(Variant { - tag_repr: IntRepr::U32, + tag_repr: IntRepr::U8, cases: vec![ Case { name: Id::new("false"), @@ -343,7 +343,7 @@ impl DocValidationScope<'_> { let mut enum_scope = IdentValidation::new(); let tag_repr = match &syntax.repr { Some(repr) => self.validate_int_repr(repr, span)?, - None => IntRepr::U32, + None => Variant::infer_repr(syntax.members.len()), }; let cases = syntax .members @@ -400,7 +400,7 @@ impl DocValidationScope<'_> { None => None, }; Ok(Variant { - tag_repr: IntRepr::U32, + tag_repr: IntRepr::U8, cases: vec![ Case { name: Id::new("ok"), @@ -498,7 +498,10 @@ impl DocValidationScope<'_> { }) }) .collect::, _>>()?; - Ok(Variant { tag_repr, cases }) + Ok(Variant { + tag_repr: tag_repr.unwrap_or_else(|| Variant::infer_repr(cases.len())), + cases, + }) } fn validate_variant( @@ -565,17 +568,20 @@ impl DocValidationScope<'_> { cases.sort_by_key(|c| name_pos[&&c.name]); } - Ok(Variant { tag_repr, cases }) + Ok(Variant { + tag_repr: tag_repr.unwrap_or_else(|| Variant::infer_repr(cases.len())), + cases, + }) } fn union_tag_repr( &self, tag: &Option>>, span: wast::Span, - ) -> Result<(IntRepr, Option>), ValidationError> { + ) -> Result<(Option, Option>), ValidationError> { let ty = match tag { Some(tag) => self.validate_datatype(tag, false, span)?, - None => return Ok((IntRepr::U32, None)), + None => return Ok((None, None)), }; match &**ty.type_() { Type::Variant(e) => { @@ -589,12 +595,12 @@ impl DocValidationScope<'_> { } names.push(c.name.clone()); } - return Ok((e.tag_repr, Some(names))); + return Ok((Some(e.tag_repr), Some(names))); } - Type::Builtin(BuiltinType::U8 { .. }) => return Ok((IntRepr::U8, None)), - Type::Builtin(BuiltinType::U16) => return Ok((IntRepr::U16, None)), - Type::Builtin(BuiltinType::U32 { .. }) => return Ok((IntRepr::U32, None)), - Type::Builtin(BuiltinType::U64) => return Ok((IntRepr::U64, None)), + Type::Builtin(BuiltinType::U8 { .. }) => return Ok((Some(IntRepr::U8), None)), + Type::Builtin(BuiltinType::U16) => return Ok((Some(IntRepr::U16), None)), + Type::Builtin(BuiltinType::U32 { .. }) => return Ok((Some(IntRepr::U32), None)), + Type::Builtin(BuiltinType::U64) => return Ok((Some(IntRepr::U64), None)), _ => {} } diff --git a/tools/witx/tests/witxt/abi-next.witxt b/tools/witx/tests/witxt/abi-next.witxt index 2bb15c092..b53eb93c2 100644 --- a/tools/witx/tests/witxt/abi-next.witxt +++ b/tools/witx/tests/witxt/abi-next.witxt @@ -719,11 +719,11 @@ iter-base-pointer block.push i32.const 0 - i32.store offset=0 + i32.store8 offset=0 block.finish block.push i32.const 1 - i32.store offset=0 + i32.store8 offset=0 block.finish variant-lower block.finish @@ -736,7 +736,7 @@ get-arg1 block.push iter-base-pointer - i32.load offset=0 + i32.load8u offset=0 block.push block.finish block.push @@ -762,11 +762,11 @@ record-lower block.push i32.const 0 - i32.store offset=0 + i32.store8 offset=0 block.finish block.push i32.const 1 - i32.store offset=0 + i32.store8 offset=0 block.finish variant-lower field.x list.canon_lower field.y @@ -782,7 +782,7 @@ get-arg1 block.push iter-base-pointer - i32.load offset=0 + i32.load8u offset=0 block.push block.finish block.push diff --git a/tools/witx/tests/witxt/representation.witxt b/tools/witx/tests/witxt/representation.witxt index 53f71f196..cd6ff6dc6 100644 --- a/tools/witx/tests/witxt/representation.witxt +++ b/tools/witx/tests/witxt/representation.witxt @@ -31,9 +31,9 @@ ;; enums (witx $a - (typename $a (enum $b $c))) + (typename $a (enum (@witx tag u32) $b $c))) (witx $b - (typename $b (enum $b $c $d))) + (typename $b (enum (@witx tag u32) $b $c $d))) (assert_representable superset $a "a" $b "b") (assert_representable noteq $b "b" $a "a") From 08814348ec1e9e3be04bc4e76c7664a5534e3c7d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 25 Feb 2021 11:32:17 -0800 Subject: [PATCH 09/22] Trim to minimize the witx ABI --- tools/witx/src/abi.rs | 241 ++++++++++++-------------- tools/witx/tests/witxt.rs | 12 +- tools/witx/tests/witxt/abi-next.witxt | 2 +- tools/witx/tests/witxt/abi.witxt | 121 ++++++++++--- 4 files changed, 205 insertions(+), 171 deletions(-) diff --git a/tools/witx/src/abi.rs b/tools/witx/src/abi.rs index 77a08ede4..0072a1630 100644 --- a/tools/witx/src/abi.rs +++ b/tools/witx/src/abi.rs @@ -512,11 +512,13 @@ def_instruction! { } : [1] => [1], /// Converts a native wasm `i32` to a language-specific record-of-bools. BitflagsFromI32 { + repr: IntRepr, ty: &'a RecordDatatype, name: &'a NamedType, } : [1] => [1], /// Converts a native wasm `i64` to a language-specific record-of-bools. BitflagsFromI64 { + repr: IntRepr, ty: &'a RecordDatatype, name: &'a NamedType, } : [1] => [1], @@ -602,69 +604,16 @@ def_instruction! { /// Takes the value off the top of the stack and writes it into linear /// memory. Pushes the address in linear memory as an `i32`. AddrOf : [1] => [1], + /// Converts a language-specific pointer value to a wasm `i32`. I32FromPointer : [1] => [1], /// Converts a language-specific pointer value to a wasm `i32`. I32FromConstPointer : [1] => [1], - /// Pops two `i32` values from the stack and creates a list from them of - /// the specified type. The first operand is the pointer in linear - /// memory to the start of the list and the second operand is the - /// length. - ListFromPointerLength { ty: &'a TypeRef } : [2] => [1], - /// Pushes the pointer/length of a list as two `i32` parameters. - ListPointerLength : [1] => [2], - /// Converts a native wasm `i32` to a language-specific pointer. PointerFromI32 { ty: &'a TypeRef }: [1] => [1], /// Converts a native wasm `i32` to a language-specific pointer. ConstPointerFromI32 { ty: &'a TypeRef } : [1] => [1], - /// Loads the interface types value from an `i32` pointer popped from - /// the stack. - Load { ty: &'a NamedType } : [1] => [1], - /// Stores an interface types value into linear memory. The first - /// operand is the value to store and the second operand is the pointer - /// in linear memory to store it at. - Store { ty: &'a NamedType } : [2] => [0], - /// Pops a native wasm `i32` from the stack, as well as two blocks - /// internally from the code generator. - /// - /// If the value is 0 then the first "ok" block value should be used. - /// If the value is anything else then the second "err" block value - /// should be used, and the value is used as the error enum. - /// - /// Note that this is a special instruction matching the current ABI of - /// WASI and intentionally differs from the type-level grammar of - /// interface types results. - ResultLift : [1] => [1], - /// Pops a native interface value from the stack as well as two blocks - /// internally from the code generator. - /// - /// A `match` is performed on the value popped and the corresponding - /// block for ok/err is used depending on value. This pushes a single - /// `i32` onto the stack representing the error code for this result. - /// - /// Note that like `ResultLift` this is specialized to the current WASI - /// ABI. - ResultLower { - ok: Option<&'a TypeRef>, - err: Option<&'a TypeRef>, - } : [1] => [1], - /// Converts a native wasm `i32` to an interface type `enum` value. - /// - /// It's guaranteed that the interface type integer value is within - /// range for this enum's type. Additionally `ty` is guaranteed to be - /// enum-like as a `Variant` where all `case` arms have no associated - /// type with them. The purpose of this instruction is to convert a - /// native wasm integer into the enum type for the interface. - EnumLift { ty: &'a NamedType } : [1] => [1], - /// Converts an interface types enum value into a wasm `i32`. - EnumLower { ty: &'a NamedType } : [1] => [1], - /// Creates a tuple from the top `n` elements on the stack, pushing the - /// tuple onto the stack. - TupleLift { amt: usize } : [*amt] => [1], - /// Splits a tuple at the top of the stack into its `n` components, - /// pushing them all onto the stack. - TupleLower { amt: usize } : [1] => [*amt], + /// This is a special instruction specifically for the original ABI of /// WASI. The raw return `i32` of a function is re-pushed onto the /// stack for reuse. @@ -969,6 +918,7 @@ impl InterfaceFunc { /// Each mode may have a slightly different codegen for some types, so /// [`InterfaceFunc::call`] takes this as a parameter to know in what context /// the invocation is happening within. +#[derive(Debug, PartialEq)] pub enum CallMode { /// A defined export is being called. /// @@ -1067,7 +1017,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { // Batch-lift all result values now that all the function's return // values are on the stack. - self.lift_all(&func.results, true); + self.lift_all(&func.results); self.emit(&Instruction::Return { amt: func.results.len(), @@ -1101,7 +1051,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { // Once everything is on the stack we can lift all arguments // one-by-one into their interface-types equivalent. - self.lift_all(&func.params, false); + self.lift_all(&func.params); // ... and that allows us to call the interface types function self.emit(&Instruction::CallInterface { @@ -1126,7 +1076,11 @@ impl<'a, B: Bindgen> Generator<'a, B> { } } - assert!(self.stack.is_empty()); + assert!( + self.stack.is_empty(), + "stack has {} items remaining", + self.stack.len() + ); } fn load_retptr(&mut self, types: &[WasmType]) { @@ -1148,7 +1102,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { /// /// Inserts instructions necesesary to lift those types into their /// interface types equivalent. - fn lift_all(&mut self, tys: &[InterfaceFuncParam], is_return: bool) { + fn lift_all(&mut self, tys: &[InterfaceFuncParam]) { let mut temp = Vec::new(); let operands = tys .iter() @@ -1172,7 +1126,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { .collect::>(); for (operands, ty) in operands.into_iter().rev().zip(tys) { self.stack.extend(operands); - self.lift(&ty.tref, is_return); + self.lift(&ty.tref); } } @@ -1241,6 +1195,10 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.stack.extend(self.results.drain(..)); } + fn push_block(&mut self) { + self.bindgen.push_block(); + } + fn finish_block(&mut self, size: usize) { self.operands.clear(); assert!( @@ -1329,7 +1287,10 @@ impl<'a, B: Bindgen> Generator<'a, B> { } } Type::List(element) => match self.abi { - Abi::Preview1 => self.witx(&ListPointerLength), + Abi::Preview1 => self.emit(&ListCanonLower { + element, + malloc: None, + }), Abi::Next => { // Lowering parameters calling a declared import means we // don't need to pass ownership, but we pass ownership in @@ -1345,7 +1306,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { malloc: if owned { Some(malloc) } else { None }, }); } else { - self.bindgen.push_block(); + self.push_block(); self.emit(&IterElem); self.emit(&IterBasePointer); let addr = self.stack.pop().unwrap(); @@ -1386,41 +1347,29 @@ impl<'a, B: Bindgen> Generator<'a, B> { } } - Type::Variant(v) if self.abi == Abi::Preview1 => { - // Enum-like variants are simply lowered to their discriminant. - if v.is_enum() { - return self.witx(&EnumLower { - ty: ty.name().unwrap(), - }); - } - - // If this variant is in the return position then it's special, - // otherwise it's an argument and we just pass the address. - let retptr = match retptr { - Some(ptr) => ptr, - None => return self.witx(&AddrOf), - }; - - // For the return position we emit some blocks to lower the - // ok/err payloads which means that in the ok branch we're - // storing to out-params and in the err branch we're simply - // lowering the error enum. - // - // Note that this is all very specific to the current WASI ABI. + // Variants in the return position of an import must be a Result in + // the preview1 ABI and they're a bit special about where all the + // pieces are. + Type::Variant(v) + if self.abi == Abi::Preview1 + && self.mode == CallMode::DefinedImport + && !v.is_enum() => + { + let retptr = retptr.unwrap(); let (ok, err) = v.as_expected().unwrap(); - self.bindgen.push_block(); + self.push_block(); if let Some(ok) = ok { self.emit(&VariantPayload); let store = |me: &mut Self, ty: &TypeRef, n| { me.emit(&GetArg { nth: *retptr + n }); - me.witx(&Store { - ty: ty.name().unwrap(), - }); + let addr = me.stack.pop().unwrap(); + me.write_to_memory(ty, addr, 0); }; match &**ok.type_() { Type::Record(r) if r.is_tuple() => { - self.witx(&TupleLower { - amt: r.members.len(), + self.emit(&RecordLower { + ty: r, + name: ty.name(), }); // Note that `rev()` is used here due to the order // that tuples are pushed onto the stack and how we @@ -1432,16 +1381,30 @@ impl<'a, B: Bindgen> Generator<'a, B> { _ => store(self, ok, 0), } }; - self.finish_block(0); + self.emit(&I32Const { val: 0 }); + self.finish_block(1); - self.bindgen.push_block(); + self.push_block(); if let Some(ty) = err { self.emit(&VariantPayload); self.lower(ty, None); } - self.finish_block(err.is_some() as usize); + self.finish_block(1); - self.witx(&ResultLower { ok, err }); + self.emit(&VariantLower { + ty: v, + name: ty.name(), + nresults: 1, + }); + } + + // Variant arguments in the Preview1 ABI are all passed by pointer + Type::Variant(v) + if self.abi == Abi::Preview1 + && self.mode == CallMode::DeclaredImport + && !v.is_enum() => + { + self.witx(&AddrOf) } Type::Variant(v) => { @@ -1450,7 +1413,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { let mut casts = Vec::new(); push_wasm(ty.type_(), &mut results); for (i, case) in v.cases.iter().enumerate() { - self.bindgen.push_block(); + self.push_block(); self.emit(&I32Const { val: i as i32 }); let mut pushed = 1; if let Some(ty) = &case.tref { @@ -1548,7 +1511,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { // Note that in general everything in this function is the opposite of the // `lower` function above. This is intentional and should be kept this way! - fn lift(&mut self, ty: &TypeRef, is_return: bool) { + fn lift(&mut self, ty: &TypeRef) { use Instruction::*; use WitxInstruction::*; @@ -1587,7 +1550,10 @@ impl<'a, B: Bindgen> Generator<'a, B> { } } Type::List(element) => match self.abi { - Abi::Preview1 => self.witx(&ListFromPointerLength { ty: element }), + Abi::Preview1 => self.emit(&ListCanonLift { + element, + free: None, + }), Abi::Next => { // Lifting the arguments of a defined import means that, if // possible, the caller still retains ownership and we don't @@ -1603,7 +1569,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { free: if owned { Some(free) } else { None }, }); } else { - self.bindgen.push_block(); + self.push_block(); self.emit(&IterBasePointer); let addr = self.stack.pop().unwrap(); self.read_from_memory(element, addr, 0); @@ -1616,14 +1582,14 @@ impl<'a, B: Bindgen> Generator<'a, B> { if let Some(repr) = r.bitflags_repr() { let name = ty.name().unwrap(); match repr { - IntRepr::U64 => return self.emit(&BitflagsFromI64 { ty: r, name }), - _ => return self.emit(&BitflagsFromI32 { ty: r, name }), + IntRepr::U64 => return self.emit(&BitflagsFromI64 { repr, ty: r, name }), + _ => return self.emit(&BitflagsFromI32 { repr, ty: r, name }), } } match self.abi { Abi::Preview1 => { - let ty = ty.name().unwrap(); - self.witx(&Load { ty }) + let addr = self.stack.pop().unwrap(); + self.read_from_memory(ty, addr, 0); } Abi::Next => { let mut temp = Vec::new(); @@ -1636,7 +1602,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { temp.truncate(0); push_wasm(member.tref.type_(), &mut temp); self.stack.extend(args.drain(..temp.len())); - self.lift(&member.tref, false); + self.lift(&member.tref); } self.emit(&RecordLift { ty: r, @@ -1646,35 +1612,30 @@ impl<'a, B: Bindgen> Generator<'a, B> { } } - Type::Variant(v) if self.abi == Abi::Preview1 => { - if v.is_enum() { - return self.witx(&EnumLift { - ty: ty.name().unwrap(), - }); - } else if !is_return { - return self.witx(&Load { - ty: ty.name().unwrap(), - }); - } - + // Variants in the return position of an import must be a Result in + // the preview1 ABI and they're a bit special about where all the + // pieces are. + Type::Variant(v) + if self.abi == Abi::Preview1 + && self.mode == CallMode::DeclaredImport + && !v.is_enum() => + { let (ok, err) = v.as_expected().unwrap(); - self.bindgen.push_block(); + self.push_block(); if let Some(ok) = ok { let mut n = 0; let mut load = |ty: &TypeRef| { - self.stack.push(self.return_pointers[n].clone()); + self.read_from_memory(ty, self.return_pointers[n].clone(), 0); n += 1; - self.witx(&Load { - ty: ty.name().unwrap(), - }); }; match &**ok.type_() { Type::Record(r) if r.is_tuple() => { for member in r.members.iter() { load(&member.tref); } - self.witx(&TupleLift { - amt: r.members.len(), + self.emit(&RecordLift { + ty: r, + name: ok.name(), }); } _ => load(ok), @@ -1682,14 +1643,28 @@ impl<'a, B: Bindgen> Generator<'a, B> { } self.finish_block(ok.is_some() as usize); - self.bindgen.push_block(); + self.push_block(); if let Some(ty) = err { self.witx(&ReuseReturn); - self.lift(ty, false); + self.lift(ty); } self.finish_block(err.is_some() as usize); - self.witx(&ResultLift); + self.emit(&VariantLift { + ty: v, + name: ty.name(), + }); + } + + // Variant arguments in the Preview1 ABI are all passed by pointer, + // so we read them here. + Type::Variant(v) + if self.abi == Abi::Preview1 + && self.mode == CallMode::DefinedImport + && !v.is_enum() => + { + let addr = self.stack.pop().unwrap(); + self.read_from_memory(ty, addr, 0) } Type::Variant(v) => { @@ -1699,10 +1674,10 @@ impl<'a, B: Bindgen> Generator<'a, B> { push_wasm(ty.type_(), &mut params); let block_inputs = self .stack - .drain(self.stack.len() - params.len() + 1..) + .drain(self.stack.len() + 1 - params.len()..) .collect::>(); for case in v.cases.iter() { - self.bindgen.push_block(); + self.push_block(); if let Some(ty) = &case.tref { // Push only the values we need for this variant onto // the stack. @@ -1722,7 +1697,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { } // Then recursively lift this variant's payload. - self.lift(ty, false); + self.lift(ty); } self.finish_block(case.tref.is_some() as usize); } @@ -1816,7 +1791,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { Type::Variant(v) => { let payload_offset = offset + (v.payload_offset() as i32); for (i, case) in v.cases.iter().enumerate() { - self.bindgen.push_block(); + self.push_block(); self.emit(&I32Const { val: i as i32 }); self.stack.push(addr.clone()); self.store_intrepr(offset, v.tag_repr); @@ -1856,14 +1831,14 @@ impl<'a, B: Bindgen> Generator<'a, B> { BuiltinType::F32 => self.emit(&F32Load { offset }), BuiltinType::F64 => self.emit(&F64Load { offset }), } - self.lift(ty, false); + self.lift(ty); } // These types are all easily lifted from an `i32` Type::Pointer(_) | Type::ConstPointer(_) | Type::Handle(_) => { self.stack.push(addr); self.emit(&I32Load { offset }); - self.lift(ty, false); + self.lift(ty); } // Read the pointer/len and then perform the standard lifting @@ -1873,7 +1848,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&I32Load { offset }); self.stack.push(addr); self.emit(&I32Load { offset: offset + 4 }); - self.lift(ty, false); + self.lift(ty); } Type::Record(r) => match r.bitflags_repr() { @@ -1884,8 +1859,8 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.stack.push(addr); self.load_intrepr(offset, repr); self.emit(&match repr { - IntRepr::U64 => I64FromBitflags { ty: r, name }, - _ => I32FromBitflags { ty: r, name }, + IntRepr::U64 => BitflagsFromI64 { repr, ty: r, name }, + _ => BitflagsFromI32 { repr, ty: r, name }, }); } // Read and lift each field individually, adjusting the offset @@ -1915,7 +1890,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.load_intrepr(offset, v.tag_repr); let payload_offset = offset + (v.payload_offset() as i32); for case in v.cases.iter() { - self.bindgen.push_block(); + self.push_block(); if let Some(ty) = &case.tref { self.read_from_memory(ty, addr.clone(), payload_offset); } diff --git a/tools/witx/tests/witxt.rs b/tools/witx/tests/witxt.rs index 8c641e2f6..5c8575409 100644 --- a/tools/witx/tests/witxt.rs +++ b/tools/witx/tests/witxt.rs @@ -338,7 +338,7 @@ impl AbiBindgen<'_> { } } -#[derive(Clone)] +#[derive(Clone, Debug)] enum Operand { Op(usize), Ret(usize), @@ -482,17 +482,7 @@ impl witx::Bindgen for AbiBindgen<'_> { Witx { instr } => match instr { PointerFromI32 { .. } => self.assert("pointer.from_i32"), ConstPointerFromI32 { .. } => self.assert("const_pointer.from_i32"), - ResultLift => self.assert("result.lift"), - ResultLower { .. } => self.assert("result.lower"), - EnumLift { .. } => self.assert("enum.lift"), - EnumLower { .. } => self.assert("enum.lower"), - TupleLift { .. } => self.assert("tuple.lift"), - TupleLower { .. } => self.assert("tuple.lower"), ReuseReturn => self.assert("reuse_return"), - ListFromPointerLength { .. } => self.assert("list.from_pointer_length"), - ListPointerLength => self.assert("list.pointer_length"), - Load { .. } => self.assert("load"), - Store { .. } => self.assert("store"), I32FromPointer => self.assert("i32.from_pointer"), I32FromConstPointer => self.assert("i32.from_const_pointer"), AddrOf => self.assert("addr-of"), diff --git a/tools/witx/tests/witxt/abi-next.witxt b/tools/witx/tests/witxt/abi-next.witxt index b53eb93c2..3e2db28e7 100644 --- a/tools/witx/tests/witxt/abi-next.witxt +++ b/tools/witx/tests/witxt/abi-next.witxt @@ -858,7 +858,7 @@ block.push iter-base-pointer i32.load8u offset=0 - i32.from_bitflags + bitflags.from_i32 block.finish list.lift return diff --git a/tools/witx/tests/witxt/abi.witxt b/tools/witx/tests/witxt/abi.witxt index defa4d568..3e0b323a4 100644 --- a/tools/witx/tests/witxt/abi.witxt +++ b/tools/witx/tests/witxt/abi.witxt @@ -133,7 +133,13 @@ ) (wasm (param i32)) (declared_import get-arg0 addr-of arg0 call.wasm return) - (defined_import get-arg0 load arg0 call.interface return) + (defined_import + get-arg0 + i32.load8u offset=0 arg0 + u8.from_i32 + record-lift + call.interface + return) ) ;; handle parameter @@ -153,11 +159,11 @@ (module $x (@interface func (export "f") (param $p (list u8)))) ) (wasm (param i32 i32)) - (declared_import get-arg0 list.pointer_length arg0 call.wasm return) + (declared_import get-arg0 list.canon_lower arg0 call.wasm return) (defined_import get-arg0 get-arg1 - list.from_pointer_length arg0 arg1 + list.canon_lift arg0 arg1 call.interface return) ) @@ -169,8 +175,21 @@ (module $x (@interface func (export "f") (param $p $a))) ) (wasm (param i32)) - (declared_import get-arg0 enum.lower arg0 call.wasm return) - (defined_import get-arg0 enum.lift arg0 call.interface return) + (declared_import + get-arg0 + block.push + i32.const 0 + block.finish + variant-lower arg0 + call.wasm + return) + (defined_import + get-arg0 + block.push + block.finish + variant-lift arg0 + call.interface + return) ) (assert_abi (witx @@ -179,7 +198,16 @@ ) (wasm (param i32)) (declared_import get-arg0 addr-of arg0 call.wasm return) - (defined_import get-arg0 load arg0 call.interface return) + (defined_import + get-arg0 + i32.load8u offset=0 arg0 + block.push + f32.load offset=4 arg0 + if32.from_f32 + block.finish + variant-lift + call.interface + return) ) ;; scalar returns @@ -370,10 +398,14 @@ ;; err block, we lift the return value as the num block.push reuse_return - enum.lift + block.push + block.finish + block.push + block.finish + variant-lift block.finish ;; consumes 2 blocks and uses the return value of the call to discriminate - result.lift ret0 + variant-lift ret0 return) (defined_import @@ -381,16 +413,23 @@ ;; ok block, nothing happens block.push + i32.const 0 block.finish ;; err block, lift the enum block.push variant-payload - enum.lower + block.push + i32.const 0 + block.finish + block.push + i32.const 1 + block.finish + variant-lower block.finish ;; consume the 2 blocks and lower based on the call - result.lower ret0 + variant-lower ret0 return) ) @@ -409,15 +448,20 @@ ;; ok block, load the return pointer and have it be the result for the `Ok` block.push - load rp0 + i32.load offset=0 rp0 + u32.from_i32 block.finish block.push reuse_return - enum.lift + block.push + block.finish + block.push + block.finish + variant-lift block.finish - result.lift ret0 + variant-lift ret0 return) (defined_import @@ -427,15 +471,23 @@ block.push variant-payload get-arg0 - store arg0 + i32.from_u32 + i32.store offset=0 arg0 + i32.const 0 block.finish block.push variant-payload - enum.lower + block.push + i32.const 0 + block.finish + block.push + i32.const 1 + block.finish + variant-lower block.finish - result.lower ret0 + variant-lower ret0 return) ) @@ -454,17 +506,24 @@ call.wasm rp0 rp1 block.push - load rp0 - load rp1 - tuple.lift + i32.load offset=0 rp0 + u32.from_i32 + i32.load offset=0 rp1 + u32.from_i32 + record-lift + record-lift block.finish block.push reuse_return - enum.lift + block.push + block.finish + block.push + block.finish + variant-lift block.finish - result.lift ret0 + variant-lift ret0 return) (defined_import @@ -474,18 +533,28 @@ ;; tuple block.push variant-payload - tuple.lower + record-lower get-arg1 - store arg1 + record-lower field.1 + i32.from_u32 field.a + i32.store offset=0 arg1 get-arg0 - store arg0 + i32.from_u32 field.0 + i32.store offset=0 arg0 + i32.const 0 block.finish block.push variant-payload - enum.lower + block.push + i32.const 0 + block.finish + block.push + i32.const 1 + block.finish + variant-lower block.finish - result.lower ret0 + variant-lower ret0 return) ) From a6835becb3b30dbdfed48fa9f29c848ed0f5e15f Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 25 Feb 2021 11:33:57 -0800 Subject: [PATCH 10/22] Don't support export modes with `call` and the preview1 abi --- tools/witx/src/abi.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tools/witx/src/abi.rs b/tools/witx/src/abi.rs index 0072a1630..3776c5f68 100644 --- a/tools/witx/src/abi.rs +++ b/tools/witx/src/abi.rs @@ -909,6 +909,16 @@ impl InterfaceFunc { /// and it will also automatically convert the results of the WASI function /// back to a language-specific value. pub fn call(&self, module: &Id, mode: CallMode, bindgen: &mut impl Bindgen) { + if Abi::Preview1 == self.abi { + match mode { + // The Preview1 ABI only works with WASI which is only intended + // for use with these modes. + CallMode::DefinedImport | CallMode::DeclaredImport => {} + _ => { + panic!("the preview1 ABI only supports import modes"); + } + } + } Generator::new(self.abi, mode, bindgen).call(module, self); } } From a5b09acd6d4ea53a76e35208caf6174d272d2b8a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 25 Feb 2021 15:26:38 -0800 Subject: [PATCH 11/22] Add some initial fuzzing --- tools/witx/fuzz/.gitignore | 4 + tools/witx/fuzz/Cargo.toml | 26 +++ tools/witx/fuzz/fuzz_targets/abi.rs | 225 ++++++++++++++++++++++++++ tools/witx/fuzz/fuzz_targets/parse.rs | 10 ++ tools/witx/src/layout.rs | 4 +- 5 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 tools/witx/fuzz/.gitignore create mode 100644 tools/witx/fuzz/Cargo.toml create mode 100644 tools/witx/fuzz/fuzz_targets/abi.rs create mode 100644 tools/witx/fuzz/fuzz_targets/parse.rs diff --git a/tools/witx/fuzz/.gitignore b/tools/witx/fuzz/.gitignore new file mode 100644 index 000000000..572e03bdf --- /dev/null +++ b/tools/witx/fuzz/.gitignore @@ -0,0 +1,4 @@ + +target +corpus +artifacts diff --git a/tools/witx/fuzz/Cargo.toml b/tools/witx/fuzz/Cargo.toml new file mode 100644 index 000000000..095330f2b --- /dev/null +++ b/tools/witx/fuzz/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "witx-fuzz" +version = "0.0.0" +authors = ["Automatically generated"] +publish = false +edition = "2018" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +arbitrary = "1.0" +libfuzzer-sys = "0.3" +witx = { path = ".." } + +[[bin]] +name = "abi" +path = "fuzz_targets/abi.rs" +test = false +doc = false + +[[bin]] +name = "parse" +path = "fuzz_targets/parse.rs" +test = false +doc = false diff --git a/tools/witx/fuzz/fuzz_targets/abi.rs b/tools/witx/fuzz/fuzz_targets/abi.rs new file mode 100644 index 000000000..a9080c39a --- /dev/null +++ b/tools/witx/fuzz/fuzz_targets/abi.rs @@ -0,0 +1,225 @@ +#![no_main] + +use arbitrary::{Result, Unstructured}; +use libfuzzer_sys::fuzz_target; +use std::rc::Rc; +use witx::*; + +fuzz_target!(|data: &[u8]| { + drop(fuzz(&mut Unstructured::new(data))); +}); + +fn fuzz(u: &mut Unstructured<'_>) -> Result<()> { + let results = params("r", u)?; + let params = params("p", u)?; + let func = InterfaceFunc { + abi: abi(u)?, + docs: String::new(), + name: Id::new("foo"), + noreturn: results.len() == 0 && u.arbitrary()?, + params, + results, + }; + + if func.abi.validate(&func.params, &func.results).is_ok() { + func.call(&Id::new("bar"), call_mode(u)?, &mut Bindgen); + } + + Ok(()) +} + +fn abi(_: &mut Unstructured<'_>) -> Result { + Ok(Abi::Next) +} + +fn call_mode(u: &mut Unstructured<'_>) -> Result { + match u.int_in_range(0..=3)? { + 0 => Ok(CallMode::DefinedExport), + 1 => Ok(CallMode::DefinedImport), + 2 => Ok(CallMode::DeclaredExport), + _ => Ok(CallMode::DeclaredImport), + } +} + +fn params(prefix: &str, u: &mut Unstructured<'_>) -> Result> { + let len = u.int_in_range(0..=20)?; + (0..len).map(|i| param(prefix, i, u)).collect() +} + +fn param(prefix: &str, i: usize, u: &mut Unstructured<'_>) -> Result { + Ok(InterfaceFuncParam { + docs: String::new(), + name: Id::new(format!("{}{}", prefix, i)), + tref: tref(&mut 100, u)?, + }) +} + +fn tref(fuel: &mut usize, u: &mut Unstructured<'_>) -> Result { + let ty = if *fuel == 0 { + Type::Builtin(BuiltinType::U64) + } else { + match u.int_in_range(0..=6)? { + 0 => Type::Builtin(builtin(u)?), + 1 => Type::Handle(HandleDatatype {}), + 2 => { + *fuel -= 1; + Type::List(tref(fuel, u)?) + } + 3 => { + *fuel -= 1; + Type::Pointer(tref(fuel, u)?) + } + 4 => { + *fuel -= 1; + Type::ConstPointer(tref(fuel, u)?) + } + 5 => { + let (kind, max) = record_kind(u)?; + let nfields = u.int_in_range(0..=(*fuel).min(20).min(max))?; + *fuel -= nfields; + let members = (0..nfields) + .map(|i| member(i, fuel, &kind, u)) + .collect::>>()?; + Type::Record(RecordDatatype { kind, members }) + } + _ => { + let tag_repr = int_repr(u)?; + let ncases = u.int_in_range(0..=(*fuel).min(20))?; + *fuel -= ncases; + let cases = (0..ncases) + .map(|i| case(i, fuel, u)) + .collect::>>()?; + Type::Variant(Variant { tag_repr, cases }) + } + } + }; + Ok(TypeRef::Name(Rc::new(NamedType { + name: Id::new("ty"), + tref: TypeRef::Value(Rc::new(ty)), + docs: String::new(), + }))) +} + +fn member( + i: usize, + fuel: &mut usize, + kind: &RecordKind, + u: &mut Unstructured<'_>, +) -> Result { + Ok(match kind { + RecordKind::Bitflags(_) => RecordMember { + name: Id::new(format!("f{}", i)), + docs: String::new(), + tref: TypeRef::Value(Rc::new(Type::Variant(Variant { + tag_repr: IntRepr::U8, + cases: vec![ + Case { + name: Id::new("false"), + tref: None, + docs: String::new(), + }, + Case { + name: Id::new("true"), + tref: None, + docs: String::new(), + }, + ], + }))), + }, + RecordKind::Tuple => RecordMember { + name: Id::new(format!("{}", i)), + docs: String::new(), + tref: tref(fuel, u)?, + }, + RecordKind::Other => RecordMember { + name: Id::new(format!("m{}", i)), + docs: String::new(), + tref: tref(fuel, u)?, + }, + }) +} + +fn case(i: usize, fuel: &mut usize, u: &mut Unstructured<'_>) -> Result { + Ok(Case { + name: Id::new(format!("m{}", i)), + docs: String::new(), + tref: if u.arbitrary()? { + Some(tref(fuel, u)?) + } else { + None + }, + }) +} + +fn record_kind(u: &mut Unstructured<'_>) -> Result<(RecordKind, usize)> { + Ok(match u.int_in_range(0..=10)? { + 0 => (RecordKind::Tuple, usize::max_value()), + 1 => { + let repr = int_repr(u)?; + let max = match repr { + IntRepr::U8 => 8, + IntRepr::U16 => 16, + IntRepr::U32 => 32, + IntRepr::U64 => 64, + }; + (RecordKind::Bitflags(repr), max) + } + _ => (RecordKind::Other, usize::max_value()), + }) +} + +fn int_repr(u: &mut Unstructured<'_>) -> Result { + Ok(match u.int_in_range(0..=3)? { + 0 => IntRepr::U8, + 1 => IntRepr::U16, + 2 => IntRepr::U32, + _ => IntRepr::U64, + }) +} + +fn builtin(u: &mut Unstructured<'_>) -> Result { + Ok(match u.int_in_range(0..=12)? { + 0 => BuiltinType::Char, + 1 => BuiltinType::U8 { lang_c_char: false }, + 2 => BuiltinType::U8 { lang_c_char: true }, + 3 => BuiltinType::U16, + 4 => BuiltinType::U32 { + lang_ptr_size: false, + }, + 5 => BuiltinType::U32 { + lang_ptr_size: true, + }, + 6 => BuiltinType::U64, + 7 => BuiltinType::S8, + 8 => BuiltinType::S16, + 9 => BuiltinType::S32, + 10 => BuiltinType::S64, + 11 => BuiltinType::F32, + _ => BuiltinType::F64, + }) +} + +struct Bindgen; + +impl witx::Bindgen for Bindgen { + type Operand = (); + + fn emit( + &mut self, + inst: &Instruction<'_>, + _operands: &mut Vec, + results: &mut Vec, + ) { + for _ in 0..inst.results_len() { + results.push(()); + } + } + fn allocate_typed_space(&mut self, _ty: &NamedType) -> Self::Operand { + () + } + fn allocate_i64_array(&mut self, _amt: usize) -> Self::Operand { + () + } + fn push_block(&mut self) {} + fn finish_block(&mut self, _operand: &mut Vec) {} +} diff --git a/tools/witx/fuzz/fuzz_targets/parse.rs b/tools/witx/fuzz/fuzz_targets/parse.rs new file mode 100644 index 000000000..c9ab07823 --- /dev/null +++ b/tools/witx/fuzz/fuzz_targets/parse.rs @@ -0,0 +1,10 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use std::str; + +fuzz_target!(|data: &[u8]| { + if let Ok(s) = str::from_utf8(data) { + drop(witx::parse(s)); + } +}); diff --git a/tools/witx/src/layout.rs b/tools/witx/src/layout.rs index 6812f01ae..3eb79c128 100644 --- a/tools/witx/src/layout.rs +++ b/tools/witx/src/layout.rs @@ -9,7 +9,7 @@ pub struct SizeAlign { impl SizeAlign { fn zero() -> SizeAlign { - SizeAlign { size: 0, align: 0 } + SizeAlign { size: 0, align: 1 } } fn append_field(&mut self, other: &SizeAlign) { self.align = self.align.max(other.align); @@ -137,7 +137,7 @@ impl Layout for RecordDatatype { impl Layout for Variant { fn mem_size_align(&self) -> SizeAlign { - let mut max = SizeAlign { size: 0, align: 0 }; + let mut max = SizeAlign::zero(); for case in self.cases.iter() { let mut size = self.tag_repr.mem_size_align(); if let Some(payload) = &case.tref { From 9726d024fff8ad1877f8114716695d2ee945ec7e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 26 Feb 2021 08:30:48 -0800 Subject: [PATCH 12/22] Add an option shorthand --- tools/witx/src/ast.rs | 23 ++++++++++++++++++++ tools/witx/src/parser.rs | 17 +++++++++++++++ tools/witx/src/validate.rs | 30 ++++++++++++++++++++++++-- tools/witx/tests/witxt/shorthand.witxt | 6 ++++++ 4 files changed, 74 insertions(+), 2 deletions(-) diff --git a/tools/witx/src/ast.rs b/tools/witx/src/ast.rs index b5fd281f1..47a683fa2 100644 --- a/tools/witx/src/ast.rs +++ b/tools/witx/src/ast.rs @@ -447,6 +447,29 @@ impl Variant { } } + /// If this variant looks like an `option` shorthand, return the type + /// associated with option. + /// + /// Only matches variants fo the form: + /// + /// ```text + /// (variant + /// (case "none") + /// (case "some" ty)) + /// ``` + pub fn as_option(&self) -> Option<&TypeRef> { + if self.cases.len() != 2 { + return None; + } + if self.cases[0].name != "none" || self.cases[0].tref.is_some() { + return None; + } + if self.cases[1].name != "some" { + return None; + } + self.cases[1].tref.as_ref() + } + /// If this variant looks like an `expected` shorthand, return the ok/err /// types associated with this result. /// diff --git a/tools/witx/src/parser.rs b/tools/witx/src/parser.rs index ee7fd46bc..2cb9c92d2 100644 --- a/tools/witx/src/parser.rs +++ b/tools/witx/src/parser.rs @@ -53,6 +53,7 @@ mod kw { wast::custom_keyword!(usize); wast::custom_keyword!(variant); wast::custom_keyword!(bool_ = "bool"); + wast::custom_keyword!(option); } mod annotation { @@ -283,6 +284,7 @@ pub enum TypedefSyntax<'a> { Enum(EnumSyntax<'a>), Tuple(TupleSyntax<'a>), Expected(ExpectedSyntax<'a>), + Option(OptionSyntax<'a>), Flags(FlagsSyntax<'a>), Record(RecordSyntax<'a>), Union(UnionSyntax<'a>), @@ -319,6 +321,8 @@ impl<'a> Parse<'a> for TypedefSyntax<'a> { Ok(TypedefSyntax::Tuple(parser.parse()?)) } else if l.peek::() { Ok(TypedefSyntax::Expected(parser.parse()?)) + } else if l.peek::() { + Ok(TypedefSyntax::Option(parser.parse()?)) } else if l.peek::() { Ok(TypedefSyntax::Flags(parser.parse()?)) } else if l.peek::() { @@ -433,6 +437,19 @@ impl<'a> Parse<'a> for ExpectedSyntax<'a> { } } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct OptionSyntax<'a> { + pub ty: Box>, +} + +impl<'a> Parse<'a> for OptionSyntax<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + let ty = Box::new(parser.parse()?); + Ok(OptionSyntax { ty }) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct ConstSyntax<'a> { pub ty: wast::Id<'a>, diff --git a/tools/witx/src/validate.rs b/tools/witx/src/validate.rs index 9fcfb5e26..37f5ac8f5 100644 --- a/tools/witx/src/validate.rs +++ b/tools/witx/src/validate.rs @@ -2,8 +2,8 @@ use crate::{ io::{Filesystem, WitxIo}, parser::{ CommentSyntax, DeclSyntax, Documented, EnumSyntax, ExpectedSyntax, FlagsSyntax, - HandleSyntax, ImportTypeSyntax, ModuleDeclSyntax, RecordSyntax, TupleSyntax, TypedefSyntax, - UnionSyntax, VariantSyntax, + HandleSyntax, ImportTypeSyntax, ModuleDeclSyntax, OptionSyntax, RecordSyntax, TupleSyntax, + TypedefSyntax, UnionSyntax, VariantSyntax, }, BuiltinType, Case, Constant, Definition, Document, Entry, HandleDatatype, Id, IntRepr, InterfaceFunc, InterfaceFuncParam, Location, Module, ModuleDefinition, ModuleEntry, @@ -309,6 +309,9 @@ impl DocValidationScope<'_> { TypedefSyntax::Expected(syntax) => { Type::Variant(self.validate_expected(&syntax, span)?) } + TypedefSyntax::Option(syntax) => { + Type::Variant(self.validate_option(&syntax, span)?) + } TypedefSyntax::Flags(syntax) => Type::Record(self.validate_flags(&syntax, span)?), TypedefSyntax::Record(syntax) => Type::Record(self.validate_record(&syntax, span)?), TypedefSyntax::Union(syntax) => Type::Variant(self.validate_union(&syntax, span)?), @@ -416,6 +419,29 @@ impl DocValidationScope<'_> { }) } + fn validate_option( + &self, + syntax: &OptionSyntax, + span: wast::Span, + ) -> Result { + let tref = self.validate_datatype(&syntax.ty, false, span)?; + Ok(Variant { + tag_repr: IntRepr::U8, + cases: vec![ + Case { + name: Id::new("none"), + tref: None, + docs: String::new(), + }, + Case { + name: Id::new("some"), + tref: Some(tref), + docs: String::new(), + }, + ], + }) + } + fn validate_flags( &self, syntax: &FlagsSyntax, diff --git a/tools/witx/tests/witxt/shorthand.witxt b/tools/witx/tests/witxt/shorthand.witxt index 960615f3c..d830132c2 100644 --- a/tools/witx/tests/witxt/shorthand.witxt +++ b/tools/witx/tests/witxt/shorthand.witxt @@ -57,3 +57,9 @@ (witx $b (typename $a (variant (case $0 u32) (case $1 u64)))) (assert_representable eq $a "a" $b "a") + +(witx $a + (typename $a (option u32))) +(witx $b + (typename $a (variant (case $none) (case $some u32)))) +(assert_representable eq $a "a" $b "a") From 439b4f8ad0a8785cd43f86bdd6acf6cbb8d26d46 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 3 Mar 2021 14:14:27 -0800 Subject: [PATCH 13/22] Move a helper function to a method on `Type` Allows code generators to use this in their own calculations if necessary. --- tools/witx/src/abi.rs | 26 ++------------------------ tools/witx/src/ast.rs | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/tools/witx/src/abi.rs b/tools/witx/src/abi.rs index 3776c5f68..1f1d90c53 100644 --- a/tools/witx/src/abi.rs +++ b/tools/witx/src/abi.rs @@ -1310,7 +1310,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { _ => true, }; let malloc = String::from("witx_malloc"); - if type_all_bits_valid(element) || is_char(element) { + if element.type_().all_bits_valid() || is_char(element) { self.emit(&ListCanonLower { element, malloc: if owned { Some(malloc) } else { None }, @@ -1573,7 +1573,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { _ => true, }; let free = String::from("witx_free"); - if type_all_bits_valid(element) || is_char(element) { + if element.type_().all_bits_valid() || is_char(element) { self.emit(&ListCanonLift { element, free: if owned { Some(free) } else { None }, @@ -2030,28 +2030,6 @@ fn cast(from: WasmType, to: WasmType) -> Bitcast { } } -fn type_all_bits_valid(ty: &TypeRef) -> bool { - match &**ty.type_() { - Type::Record(r) => r.members.iter().all(|t| type_all_bits_valid(&t.tref)), - - Type::Builtin(BuiltinType::Char) | Type::Variant(_) | Type::Handle(_) | Type::List(_) => { - false - } - - Type::Builtin(BuiltinType::U8 { .. }) - | Type::Builtin(BuiltinType::S8) - | Type::Builtin(BuiltinType::U16) - | Type::Builtin(BuiltinType::S16) - | Type::Builtin(BuiltinType::U32 { .. }) - | Type::Builtin(BuiltinType::S32) - | Type::Builtin(BuiltinType::U64) - | Type::Builtin(BuiltinType::S64) - | Type::Builtin(BuiltinType::F32) - | Type::Builtin(BuiltinType::F64) - | Type::Pointer(_) - | Type::ConstPointer(_) => true, - } -} fn is_char(ty: &TypeRef) -> bool { match &**ty.type_() { Type::Builtin(BuiltinType::Char) => true, diff --git a/tools/witx/src/ast.rs b/tools/witx/src/ast.rs index 47a683fa2..70f3fe345 100644 --- a/tools/witx/src/ast.rs +++ b/tools/witx/src/ast.rs @@ -254,6 +254,35 @@ impl Type { Builtin(_) => "builtin", } } + + /// Returns whether the in-memory representation of this type will always be + /// valid regardless of the value of all the bits in memory. + /// + /// This is only true for numerical types, pointers, and records of these + /// values. This is used for canonical lifting/lowering of lists. + pub fn all_bits_valid(&self) -> bool { + match self { + Type::Record(r) => r.members.iter().all(|t| t.tref.type_().all_bits_valid()), + + Type::Builtin(BuiltinType::Char) + | Type::Variant(_) + | Type::Handle(_) + | Type::List(_) => false, + + Type::Builtin(BuiltinType::U8 { .. }) + | Type::Builtin(BuiltinType::S8) + | Type::Builtin(BuiltinType::U16) + | Type::Builtin(BuiltinType::S16) + | Type::Builtin(BuiltinType::U32 { .. }) + | Type::Builtin(BuiltinType::S32) + | Type::Builtin(BuiltinType::U64) + | Type::Builtin(BuiltinType::S64) + | Type::Builtin(BuiltinType::F32) + | Type::Builtin(BuiltinType::F64) + | Type::Pointer(_) + | Type::ConstPointer(_) => true, + } + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] From a7fcb5f405a35e30bbf680305478c4faee95c72a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 8 Mar 2021 09:28:50 -0800 Subject: [PATCH 14/22] Tweak memory ownership in list lifting/lowering --- tools/witx/src/abi.rs | 65 +++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 40 deletions(-) diff --git a/tools/witx/src/abi.rs b/tools/witx/src/abi.rs index 1f1d90c53..48a4f44ec 100644 --- a/tools/witx/src/abi.rs +++ b/tools/witx/src/abi.rs @@ -402,12 +402,13 @@ def_instruction! { /// memory (e.g. the host has raw access or wasm-to-wasm communication /// would copy the list). /// - /// Note that the adapter is not responsible for cleaning up this list. - /// It's either owned by the caller (`malloc` is `None`) or the - /// receiving end is responsible for cleanup (`malloc` is `Some`) + /// If `malloc` is `Some` then the adapter is not responsible for + /// cleaning up this list because the other end is receiving the + /// allocation. If `malloc` is `None` then the adapter is responsible + /// for cleaning up any temporary allocation it created, if any. ListCanonLower { element: &'a TypeRef, - malloc: Option, + malloc: Option<&'a str>, } : [1] => [2], /// Lowers a list where the element's layout in the native language is @@ -419,20 +420,13 @@ def_instruction! { /// which is used as the iteration body of writing each element of the /// list consumed. /// - /// The `malloc` function is expected to be used within the - /// wasm module to allocate memory. The `owned` flag is set similar to - /// the ownership of `ListCanonLower`, meaning it's always `true` except - /// for when the lowering is happening for a wasm module calling an - /// imported function. - /// - /// Note, though, that the `owned` flag is only describing the interface - /// types value we're lowering from. This instruction is expected to - /// always create an owned allocation as part of lowering. Lifting on - /// the other side will always deallocate the list passed. + /// The `malloc` field here behaves the same way as `ListCanonLower`. + /// It's only set to `None` when a wasm module calls a declared import. + /// Otherwise lowering in other contexts requires allocating memory for + /// the receiver to own. ListLower { element: &'a TypeRef, - malloc: String, - owned: bool, + malloc: Option<&'a str>, } : [1] => [2], /// Lifts a list which has a canonical representation into an interface @@ -450,10 +444,13 @@ def_instruction! { /// be deallocated. /// /// The `free` field is set to `Some` in similar situations as described - /// by `ListCanonLower`. + /// by `ListCanonLower`. If `free` is `Some` then the memory must be + /// deallocated after the lifted list is done being consumed. If it is + /// `None` then the receiver of the lifted list does not own the memory + /// and must leave the memory as-is. ListCanonLift { element: &'a TypeRef, - free: Option, + free: Option<&'a str>, } : [2] => [1], /// Lifts a list which into an interface types value. @@ -467,7 +464,7 @@ def_instruction! { /// read each individual element from the list. ListLift { element: &'a TypeRef, - free: String, + free: Option<&'a str>, } : [2] => [1], /// Pushes an operand onto the stack representing the list item from @@ -1305,16 +1302,12 @@ impl<'a, B: Bindgen> Generator<'a, B> { // Lowering parameters calling a declared import means we // don't need to pass ownership, but we pass ownership in // all other cases. - let owned = match self.mode { - CallMode::DeclaredImport => false, - _ => true, + let malloc = match self.mode { + CallMode::DeclaredImport => None, + _ => Some("witx_malloc"), }; - let malloc = String::from("witx_malloc"); if element.type_().all_bits_valid() || is_char(element) { - self.emit(&ListCanonLower { - element, - malloc: if owned { Some(malloc) } else { None }, - }); + self.emit(&ListCanonLower { element, malloc }); } else { self.push_block(); self.emit(&IterElem); @@ -1322,11 +1315,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { let addr = self.stack.pop().unwrap(); self.write_to_memory(element, addr, 0); self.finish_block(0); - self.emit(&ListLower { - element, - malloc, - owned, - }); + self.emit(&ListLower { element, malloc }); } } }, @@ -1568,16 +1557,12 @@ impl<'a, B: Bindgen> Generator<'a, B> { // Lifting the arguments of a defined import means that, if // possible, the caller still retains ownership and we don't // free anything. - let owned = match self.mode { - CallMode::DefinedImport => false, - _ => true, + let free = match self.mode { + CallMode::DefinedImport => None, + _ => Some("witx_free"), }; - let free = String::from("witx_free"); if element.type_().all_bits_valid() || is_char(element) { - self.emit(&ListCanonLift { - element, - free: if owned { Some(free) } else { None }, - }); + self.emit(&ListCanonLift { element, free }); } else { self.push_block(); self.emit(&IterBasePointer); From 98a294d1caaa59b5f75e42928e20891787b61fa7 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 8 Mar 2021 09:33:57 -0800 Subject: [PATCH 15/22] Bump witx to 0.10.0 --- tools/witx/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/witx/Cargo.toml b/tools/witx/Cargo.toml index bd4bfc076..e29707f4c 100644 --- a/tools/witx/Cargo.toml +++ b/tools/witx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "witx" -version = "0.9.0" +version = "0.10.0" description = "Parse and validate witx file format" homepage = "https://github.com/WebAssembly/WASI" repository = "https://github.com/WebAssembly/WASI" From 2beb50f9e114762a550a80594cfe453a3a66a54c Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 15 Mar 2021 11:55:25 -0700 Subject: [PATCH 16/22] Initial support for `in-buffer` and `out-buffer` types --- tools/witx/src/abi.rs | 275 ++++++++++++++++++++------ tools/witx/src/ast.rs | 14 ++ tools/witx/src/docs/ast.rs | 12 +- tools/witx/src/layout.rs | 89 +++++---- tools/witx/src/parser.rs | 25 +++ tools/witx/src/render.rs | 1 + tools/witx/src/validate.rs | 20 +- tools/witx/tests/witxt.rs | 122 ++++++++---- tools/witx/tests/witxt/abi-next.witxt | 211 ++++++++++++++++++++ 9 files changed, 621 insertions(+), 148 deletions(-) diff --git a/tools/witx/src/abi.rs b/tools/witx/src/abi.rs index 48a4f44ec..5d88543d9 100644 --- a/tools/witx/src/abi.rs +++ b/tools/witx/src/abi.rs @@ -21,8 +21,8 @@ //! generators will need to implement the various instructions to support APIs. use crate::{ - BuiltinType, Id, IntRepr, InterfaceFunc, InterfaceFuncParam, NamedType, RecordDatatype, Type, - TypeRef, Variant, + Buffer, BuiltinType, Id, IntRepr, InterfaceFunc, InterfaceFuncParam, NamedType, RecordDatatype, + Type, TypeRef, Variant, }; use std::mem; @@ -479,6 +479,19 @@ def_instruction! { /// This is used for both lifting and lowering lists. IterBasePointer : [0] => [1], + // buffers + + /// Pops a buffer value, pushes the pointer/length of where it points + /// to in memory. + BufferLowerPtrLen { buffer: &'a Buffer } : [1] => [2], + /// Pops a buffer value, pushes an integer handle for the buffer. + BufferLowerHandle { buffer: &'a Buffer } : [1] => [1], + /// Pops a ptr/len, pushes a buffer wrapping that ptr/len of the memory + /// from the origin module. + BufferLiftPtrLen { buffer: &'a Buffer } : [2] => [1], + /// Pops an i32, pushes a buffer wrapping that i32 handle. + BufferLiftHandle { buffer: &'a Buffer } : [1] => [1], + // records /// Pops a record value off the stack, decomposes the record to all of @@ -628,16 +641,17 @@ impl Abi { params: &[InterfaceFuncParam], results: &[InterfaceFuncParam], ) -> Result<(), String> { + for ty in params.iter() { + self.validate_ty(ty.tref.type_(), true)?; + } + for ty in results.iter() { + self.validate_ty(ty.tref.type_(), false)?; + } match self { Abi::Preview1 => { // validated below... } - Abi::Next => { - for ty in params.iter().chain(results) { - validate_no_witx(ty.tref.type_())?; - } - return Ok(()); - } + Abi::Next => return Ok(()), } assert_eq!(*self, Abi::Preview1); match results.len() { @@ -681,45 +695,69 @@ impl Abi { } } Type::Record(r) if r.bitflags_repr().is_some() => {} - Type::Record(_) | Type::List(_) => return Err("invalid return type".to_string()), + Type::Record(_) | Type::List(_) | Type::Buffer(_) => { + return Err("invalid return type".to_string()) + } }, _ => return Err("more than one result".to_string()), } Ok(()) } -} -fn validate_no_witx(ty: &Type) -> Result<(), String> { - match ty { - Type::Record(r) => { - for r in r.members.iter() { - validate_no_witx(r.tref.type_())?; + fn validate_ty(&self, ty: &Type, param: bool) -> Result<(), String> { + match ty { + Type::Record(r) => { + for r in r.members.iter() { + self.validate_ty(r.tref.type_(), param)?; + } + Ok(()) } - Ok(()) - } - Type::Variant(v) => { - for case in v.cases.iter() { - if let Some(ty) = &case.tref { - validate_no_witx(ty.type_())?; + Type::Variant(v) => { + for case in v.cases.iter() { + if let Some(ty) = &case.tref { + self.validate_ty(ty.type_(), param)?; + } } + Ok(()) } - Ok(()) - } - Type::Handle(_) => Ok(()), - Type::List(t) => validate_no_witx(t.type_()), - Type::Pointer(_) => return Err("cannot use `(@witx pointer)` in this ABI".to_string()), - Type::ConstPointer(_) => { - return Err("cannot use `(@witx const_pointer)` in this ABI".to_string()) - } - Type::Builtin(BuiltinType::U8 { lang_c_char: true }) => { - return Err("cannot use `(@witx char8)` in this ABI".to_string()); - } - Type::Builtin(BuiltinType::U32 { - lang_ptr_size: true, - }) => { - return Err("cannot use `(@witx usize)` in this ABI".to_string()); + Type::Handle(_) => Ok(()), + Type::List(t) => self.validate_ty(t.type_(), param), + Type::Pointer(t) => { + if let Abi::Next = self { + return Err("cannot use `(@witx pointer)` in this ABI".to_string()); + } + self.validate_ty(t.type_(), param) + } + Type::ConstPointer(t) => { + if let Abi::Next = self { + return Err("cannot use `(@witx const_pointer)` in this ABI".to_string()); + } + self.validate_ty(t.type_(), param) + } + Type::Builtin(BuiltinType::U8 { lang_c_char: true }) => { + if let Abi::Next = self { + return Err("cannot use `(@witx char8)` in this ABI".to_string()); + } + Ok(()) + } + Type::Builtin(BuiltinType::U32 { + lang_ptr_size: true, + }) => { + if let Abi::Next = self { + return Err("cannot use `(@witx usize)` in this ABI".to_string()); + } + Ok(()) + } + Type::Buffer(t) => { + if !param { + return Err("cannot use buffers in the result position".to_string()); + } + // If this is an output buffer then validate `t` as if it were a + // result because the callee can't give us buffers back. + self.validate_ty(t.tref.type_(), param && !t.out) + } + Type::Builtin(_) => Ok(()), } - Type::Builtin(_) => Ok(()), } } @@ -807,7 +845,7 @@ impl InterfaceFunc { /// /// The first entry returned is the list of parameters and the second entry /// is the list of results for the wasm function signature. - pub fn wasm_signature(&self) -> WasmSignature { + pub fn wasm_signature(&self, mode: CallMode) -> WasmSignature { let mut params = Vec::new(); let mut results = Vec::new(); for param in self.params.iter() { @@ -816,19 +854,20 @@ impl InterfaceFunc { | Type::Pointer(_) | Type::ConstPointer(_) | Type::Handle(_) - | Type::List(_) => { - push_wasm(param.tref.type_(), &mut params); + | Type::List(_) + | Type::Buffer(_) => { + push_wasm(mode, param.tref.type_(), &mut params); } ty @ Type::Variant(_) => match self.abi { Abi::Preview1 => params.push(WasmType::I32), - Abi::Next => push_wasm(ty, &mut params), + Abi::Next => push_wasm(mode, ty, &mut params), }, Type::Record(r) => match self.abi { Abi::Preview1 => match r.bitflags_repr() { Some(repr) => params.push(WasmType::from(repr)), None => params.push(WasmType::I32), }, - Abi::Next => push_wasm(param.tref.type_(), &mut params), + Abi::Next => push_wasm(mode, param.tref.type_(), &mut params), }, } } @@ -840,15 +879,16 @@ impl InterfaceFunc { | Type::Pointer(_) | Type::ConstPointer(_) | Type::Record(_) - | Type::Handle(_) => { - push_wasm(param.tref.type_(), &mut results); + | Type::Handle(_) + | Type::Buffer(_) => { + push_wasm(mode, param.tref.type_(), &mut results); } Type::Variant(v) => { match self.abi { Abi::Preview1 => {} // handled below Abi::Next => { - push_wasm(param.tref.type_(), &mut results); + push_wasm(mode, param.tref.type_(), &mut results); continue; } } @@ -925,7 +965,7 @@ impl InterfaceFunc { /// Each mode may have a slightly different codegen for some types, so /// [`InterfaceFunc::call`] takes this as a parameter to know in what context /// the invocation is happening within. -#[derive(Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq)] pub enum CallMode { /// A defined export is being called. /// @@ -963,6 +1003,22 @@ pub enum CallMode { DeclaredImport, } +impl CallMode { + pub fn export(&self) -> bool { + match self { + CallMode::DefinedExport | CallMode::DeclaredExport => true, + CallMode::DefinedImport | CallMode::DeclaredImport => false, + } + } + + pub fn defined(&self) -> bool { + match self { + CallMode::DefinedExport | CallMode::DefinedImport => true, + CallMode::DeclaredExport | CallMode::DeclaredImport => false, + } + } +} + struct Generator<'a, B: Bindgen> { abi: Abi, mode: CallMode, @@ -987,7 +1043,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { } fn call(&mut self, module: &Id, func: &InterfaceFunc) { - let sig = func.wasm_signature(); + let sig = func.wasm_signature(self.mode); match self.mode { CallMode::DeclaredExport | CallMode::DeclaredImport => { @@ -1037,7 +1093,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { // directly due to various conversions and return pointers, so // we need to somewhat manually calculate all the arguments // which are converted as interface types arguments below. - let sig = func.wasm_signature(); + let sig = func.wasm_signature(self.mode); let nargs = match self.abi { Abi::Preview1 => { func.params.len() @@ -1122,7 +1178,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { }, Abi::Next => { temp.truncate(0); - push_wasm(ty.tref.type_(), &mut temp); + push_wasm(self.mode, ty.tref.type_(), &mut temp); temp.len() } }; @@ -1319,6 +1375,24 @@ impl<'a, B: Bindgen> Generator<'a, B> { } } }, + Type::Buffer(buffer) => { + self.translate_buffer(buffer); + match self.mode { + // When calling an imported function we're passing a raw view + // into memory, and the adapter will convert it into something + // else if necessary. + CallMode::DeclaredImport => self.emit(&BufferLowerPtrLen { buffer }), + + // When calling an exported function we're passing a handle to + // the caller's memory, and this part of the adapter is + // responsible for converting it into something that's a handle. + CallMode::DeclaredExport => self.emit(&BufferLowerHandle { buffer }), + + // Buffers are only used in the parameter position, which means + // lowering a buffer should never happen in these contexts. + CallMode::DefinedImport | CallMode::DefinedExport => unreachable!(), + } + } Type::Record(r) => { if let Some(repr) = r.bitflags_repr() { let name = ty.name().unwrap(); @@ -1410,7 +1484,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { let mut results = Vec::new(); let mut temp = Vec::new(); let mut casts = Vec::new(); - push_wasm(ty.type_(), &mut results); + push_wasm(self.mode, ty.type_(), &mut results); for (i, case) in v.cases.iter().enumerate() { self.push_block(); self.emit(&I32Const { val: i as i32 }); @@ -1425,7 +1499,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { // pushed, and record how many. If we pushed too few // then we'll need to push some zeros after this. temp.truncate(0); - push_wasm(ty.type_(), &mut temp); + push_wasm(self.mode, ty.type_(), &mut temp); pushed += temp.len(); // For all the types pushed we may need to insert some @@ -1573,6 +1647,23 @@ impl<'a, B: Bindgen> Generator<'a, B> { } } }, + Type::Buffer(buffer) => { + self.translate_buffer(buffer); + match self.mode { + // When calling a defined imported function then we're coming + // from a pointer/length, and the embedding context will figure + // out what to do with that pointer/length. + CallMode::DefinedImport => self.emit(&BufferLiftPtrLen { buffer }), + + // When calling an exported function we're given a handle to the + // buffer, which is then interpreted in the calling context. + CallMode::DefinedExport => self.emit(&BufferLiftHandle { buffer }), + + // Buffers are only used in the parameter position, which means + // lifting a buffer should never happen in these contexts. + CallMode::DeclaredImport | CallMode::DeclaredExport => unreachable!(), + } + } Type::Record(r) => { if let Some(repr) = r.bitflags_repr() { let name = ty.name().unwrap(); @@ -1588,14 +1679,14 @@ impl<'a, B: Bindgen> Generator<'a, B> { } Abi::Next => { let mut temp = Vec::new(); - push_wasm(ty.type_(), &mut temp); + push_wasm(self.mode, ty.type_(), &mut temp); let mut args = self .stack .drain(self.stack.len() - temp.len()..) .collect::>(); for member in r.members.iter() { temp.truncate(0); - push_wasm(member.tref.type_(), &mut temp); + push_wasm(self.mode, member.tref.type_(), &mut temp); self.stack.extend(args.drain(..temp.len())); self.lift(&member.tref); } @@ -1666,7 +1757,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { let mut params = Vec::new(); let mut temp = Vec::new(); let mut casts = Vec::new(); - push_wasm(ty.type_(), &mut params); + push_wasm(self.mode, ty.type_(), &mut params); let block_inputs = self .stack .drain(self.stack.len() + 1 - params.len()..) @@ -1677,7 +1768,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { // Push only the values we need for this variant onto // the stack. temp.truncate(0); - push_wasm(ty.type_(), &mut temp); + push_wasm(self.mode, ty.type_(), &mut temp); self.stack .extend(block_inputs[..temp.len()].iter().cloned()); @@ -1744,6 +1835,19 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.emit(&I32Store { offset }); } + // Lower the buffer to its raw values, and then write the values + // into memory, which may be more than one value depending on our + // call mode. + Type::Buffer(_) => { + self.lower(ty, None); + if let CallMode::DeclaredImport = self.mode { + self.stack.push(addr.clone()); + self.emit(&I32Store { offset: offset + 4 }); + } + self.stack.push(addr); + self.emit(&I32Store { offset }); + } + Type::Record(r) => match r.bitflags_repr() { // Bitflags just have their appropriate width written into // memory. @@ -1768,7 +1872,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { .stack .drain(self.stack.len() - r.members.len()..) .collect::>(); - for (layout, field) in r.member_layout().iter().zip(fields) { + for (layout, field) in r.member_layout(self.mode.export()).iter().zip(fields) { self.stack.push(field); self.write_to_memory( &layout.member.tref, @@ -1784,7 +1888,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { // payload we write the payload after the discriminant, aligned up // to the type's alignment. Type::Variant(v) => { - let payload_offset = offset + (v.payload_offset() as i32); + let payload_offset = offset + (v.payload_offset(self.mode.export()) as i32); for (i, case) in v.cases.iter().enumerate() { self.push_block(); self.emit(&I32Const { val: i as i32 }); @@ -1846,6 +1950,18 @@ impl<'a, B: Bindgen> Generator<'a, B> { self.lift(ty); } + // Read the requisite number of values from memory and then lift as + // appropriate. + Type::Buffer(_) => { + self.stack.push(addr.clone()); + self.emit(&I32Load { offset }); + if let CallMode::DefinedImport = self.mode { + self.stack.push(addr); + self.emit(&I32Load { offset: offset + 4 }); + } + self.lift(ty); + } + Type::Record(r) => match r.bitflags_repr() { // Bitflags just have their appropriate size read from // memory. @@ -1862,7 +1978,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { // as we go along, then aggregate all the fields into the // record. None => { - for layout in r.member_layout() { + for layout in r.member_layout(self.mode.export()) { self.read_from_memory( &layout.member.tref, addr.clone(), @@ -1883,7 +1999,7 @@ impl<'a, B: Bindgen> Generator<'a, B> { Type::Variant(v) => { self.stack.push(addr.clone()); self.load_intrepr(offset, v.tag_repr); - let payload_offset = offset + (v.payload_offset() as i32); + let payload_offset = offset + (v.payload_offset(self.mode.export()) as i32); for case in v.cases.iter() { self.push_block(); if let Some(ty) = &case.tref { @@ -1916,9 +2032,36 @@ impl<'a, B: Bindgen> Generator<'a, B> { IntRepr::U8 => Instruction::I32Store8 { offset }, }); } + + fn translate_buffer(&mut self, buffer: &Buffer) { + let do_write = match self.mode { + // For declared items, input/output is defined in the context of + // what the callee will do. The callee will read input buffers, + // meaning we write to them, and write to ouptut buffers, meaning + // we'll read from them. + CallMode::DeclaredImport | CallMode::DeclaredExport => !buffer.out, + + // Defined item mirror declared imports because buffers are + // defined from the caller's perspective, so we don't invert the + // `out` setting like above. + CallMode::DefinedImport | CallMode::DefinedExport => buffer.out, + }; + self.emit(&Instruction::IterBasePointer); + let addr = self.stack.pop().unwrap(); + if do_write { + self.push_block(); + self.emit(&Instruction::VariantPayload); + self.write_to_memory(&buffer.tref, addr, 0); + self.finish_block(0); + } else { + self.push_block(); + self.read_from_memory(&buffer.tref, addr, 0); + self.finish_block(1); + } + } } -fn push_wasm(ty: &Type, result: &mut Vec) { +fn push_wasm(mode: CallMode, ty: &Type, result: &mut Vec) { match ty { Type::Builtin(BuiltinType::S8) | Type::Builtin(BuiltinType::U8 { .. }) @@ -1941,7 +2084,7 @@ fn push_wasm(ty: &Type, result: &mut Vec) { Some(repr) => result.push(repr.into()), None => { for member in r.members.iter() { - push_wasm(member.tref.type_(), result); + push_wasm(mode, member.tref.type_(), result); } } }, @@ -1951,6 +2094,14 @@ fn push_wasm(ty: &Type, result: &mut Vec) { result.push(WasmType::I32); } + Type::Buffer(_) => { + result.push(WasmType::I32); + match mode { + CallMode::DefinedExport | CallMode::DeclaredExport => {} + CallMode::DefinedImport | CallMode::DeclaredImport => result.push(WasmType::I32), + } + } + Type::Variant(v) => { result.push(v.tag_repr.into()); let start = result.len(); @@ -1967,7 +2118,7 @@ fn push_wasm(ty: &Type, result: &mut Vec) { Some(ty) => ty, None => continue, }; - push_wasm(ty.type_(), &mut temp); + push_wasm(mode, ty.type_(), &mut temp); for (i, ty) in temp.drain(..).enumerate() { match result.get_mut(start + i) { diff --git a/tools/witx/src/ast.rs b/tools/witx/src/ast.rs index 70f3fe345..fdb390663 100644 --- a/tools/witx/src/ast.rs +++ b/tools/witx/src/ast.rs @@ -236,6 +236,8 @@ pub enum Type { /// A `witx`-specific type representing a raw const pointer into linear /// memory ConstPointer(TypeRef), + /// A buffer type representing a window in memory + Buffer(Buffer), /// A builtin base-case type. Builtin(BuiltinType), } @@ -251,6 +253,7 @@ impl Type { List(_) => "list", Pointer(_) => "pointer", ConstPointer(_) => "constpointer", + Buffer(_) => "buffer", Builtin(_) => "builtin", } } @@ -267,6 +270,7 @@ impl Type { Type::Builtin(BuiltinType::Char) | Type::Variant(_) | Type::Handle(_) + | Type::Buffer(_) | Type::List(_) => false, Type::Builtin(BuiltinType::U8 { .. }) @@ -695,3 +699,13 @@ pub struct Constant { pub value: u64, pub docs: String, } + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Buffer { + /// Whether or not this is an `out` buffer (`true`) or an `in` buffer + /// (`false`) + pub out: bool, + + /// The type of items this buffer contains + pub tref: TypeRef, +} diff --git a/tools/witx/src/docs/ast.rs b/tools/witx/src/docs/ast.rs index 20379dcb3..722907e15 100644 --- a/tools/witx/src/docs/ast.rs +++ b/tools/witx/src/docs/ast.rs @@ -33,8 +33,8 @@ impl ToMarkdown for Document { format!( "{}\nSize: {}\n\nAlignment: {}\n", &d.docs, - &d.mem_size(), - &d.mem_align() + &d.mem_size(false), + &d.mem_align(false) ) .as_str(), )); @@ -95,6 +95,7 @@ impl ToMarkdown for Type { Self::List(_) => {} Self::Pointer(_) => {} Self::ConstPointer(_) => {} + Self::Buffer(_) => {} Self::Builtin(_) => {} } } @@ -105,7 +106,7 @@ impl ToMarkdown for RecordDatatype { let heading = heading_from_node(&node, 1); node.new_child(MdSection::new(heading, "Record members")); - for member_layout in &self.member_layout() { + for member_layout in &self.member_layout(false) { let member = member_layout.member; let offset = member_layout.offset; let name = member.name.as_str(); @@ -139,7 +140,7 @@ impl ToMarkdown for Variant { let heading = heading_from_node(&node, 1); node.new_child(MdSection::new(heading, "Variant Layout")); - let whole = self.mem_size_align(); + let whole = self.mem_size_align(false); node.new_child(MdSection::new( MdHeading::new_bullet(), format!("size: {}", whole.size), @@ -149,7 +150,7 @@ impl ToMarkdown for Variant { format!("align: {}", whole.align), )); - let tag = self.tag_repr.mem_size_align(); + let tag = self.tag_repr.mem_size_align(false); node.new_child(MdSection::new( MdHeading::new_bullet(), format!("tag_size: {}", tag.size), @@ -346,6 +347,7 @@ impl TypeRef { format!("Variant") } } + Type::Buffer(_) => format!("buffer"), }, } } diff --git a/tools/witx/src/layout.rs b/tools/witx/src/layout.rs index 3eb79c128..43d772a9a 100644 --- a/tools/witx/src/layout.rs +++ b/tools/witx/src/layout.rs @@ -19,23 +19,23 @@ impl SizeAlign { } pub trait Layout { - fn mem_size_align(&self) -> SizeAlign; - fn mem_size(&self) -> usize { - self.mem_size_align().size + fn mem_size_align(&self, export: bool) -> SizeAlign; + fn mem_size(&self, export: bool) -> usize { + self.mem_size_align(export).size } - fn mem_align(&self) -> usize { - self.mem_size_align().align + fn mem_align(&self, export: bool) -> usize { + self.mem_size_align(export).align } } impl TypeRef { - fn layout(&self, cache: &mut HashMap) -> SizeAlign { + fn layout(&self, export: bool, cache: &mut HashMap) -> SizeAlign { if let Some(hit) = cache.get(self) { return *hit; } let layout = match &self { - TypeRef::Name(nt) => nt.layout(cache), - TypeRef::Value(v) => v.layout(cache), + TypeRef::Name(nt) => nt.layout(export, cache), + TypeRef::Value(v) => v.layout(export, cache), }; cache.insert(self.clone(), layout); layout @@ -43,50 +43,54 @@ impl TypeRef { } impl Layout for TypeRef { - fn mem_size_align(&self) -> SizeAlign { + fn mem_size_align(&self, export: bool) -> SizeAlign { let mut cache = HashMap::new(); - self.layout(&mut cache) + self.layout(export, &mut cache) } } impl NamedType { - fn layout(&self, cache: &mut HashMap) -> SizeAlign { - self.tref.layout(cache) + fn layout(&self, export: bool, cache: &mut HashMap) -> SizeAlign { + self.tref.layout(export, cache) } } impl Layout for NamedType { - fn mem_size_align(&self) -> SizeAlign { + fn mem_size_align(&self, export: bool) -> SizeAlign { let mut cache = HashMap::new(); - self.layout(&mut cache) + self.layout(export, &mut cache) } } impl Type { - fn layout(&self, cache: &mut HashMap) -> SizeAlign { + fn layout(&self, export: bool, cache: &mut HashMap) -> SizeAlign { match &self { Type::Record(s) => match s.bitflags_repr() { - Some(repr) => repr.mem_size_align(), - None => s.layout(cache), + Some(repr) => repr.mem_size_align(export), + None => s.layout(export, cache), }, - Type::Variant(s) => s.mem_size_align(), - Type::Handle(h) => h.mem_size_align(), + Type::Variant(s) => s.mem_size_align(export), + Type::Handle(h) => h.mem_size_align(export), Type::List { .. } => SizeAlign { size: 8, align: 4 }, // Pointer and Length - Type::Pointer { .. } | Type::ConstPointer { .. } => BuiltinType::S32.mem_size_align(), - Type::Builtin(b) => b.mem_size_align(), + Type::Pointer { .. } | Type::ConstPointer { .. } => { + BuiltinType::S32.mem_size_align(export) + } + Type::Buffer(_) if export => SizeAlign { size: 4, align: 4 }, + Type::Buffer(_) => SizeAlign { size: 8, align: 4 }, + Type::Builtin(b) => b.mem_size_align(export), } } } impl Layout for Type { - fn mem_size_align(&self) -> SizeAlign { + fn mem_size_align(&self, export: bool) -> SizeAlign { let mut cache = HashMap::new(); - self.layout(&mut cache) + self.layout(export, &mut cache) } } impl Layout for IntRepr { - fn mem_size_align(&self) -> SizeAlign { - self.to_builtin().mem_size_align() + fn mem_size_align(&self, export: bool) -> SizeAlign { + self.to_builtin().mem_size_align(export) } } @@ -96,18 +100,19 @@ pub struct RecordMemberLayout<'a> { } impl RecordDatatype { - pub fn member_layout(&self) -> Vec { - self.member_layout_(&mut HashMap::new()).1 + pub fn member_layout(&self, export: bool) -> Vec { + self.member_layout_(export, &mut HashMap::new()).1 } fn member_layout_( &self, + export: bool, cache: &mut HashMap, ) -> (SizeAlign, Vec) { let mut members = Vec::new(); let mut sa = SizeAlign::zero(); for m in self.members.iter() { - let member = m.tref.layout(cache); + let member = m.tref.layout(export, cache); sa.append_field(&member); members.push(RecordMemberLayout { member: m, @@ -118,30 +123,30 @@ impl RecordDatatype { (sa, members) } - fn layout(&self, cache: &mut HashMap) -> SizeAlign { - self.member_layout_(cache).0 + fn layout(&self, export: bool, cache: &mut HashMap) -> SizeAlign { + self.member_layout_(export, cache).0 } } impl Layout for RecordDatatype { - fn mem_size_align(&self) -> SizeAlign { + fn mem_size_align(&self, export: bool) -> SizeAlign { match self.bitflags_repr() { - Some(repr) => repr.mem_size_align(), + Some(repr) => repr.mem_size_align(export), None => { let mut cache = HashMap::new(); - self.layout(&mut cache) + self.layout(export, &mut cache) } } } } impl Layout for Variant { - fn mem_size_align(&self) -> SizeAlign { + fn mem_size_align(&self, export: bool) -> SizeAlign { let mut max = SizeAlign::zero(); for case in self.cases.iter() { - let mut size = self.tag_repr.mem_size_align(); + let mut size = self.tag_repr.mem_size_align(export); if let Some(payload) = &case.tref { - size.append_field(&payload.mem_size_align()); + size.append_field(&payload.mem_size_align(export)); } size.size = align_to(size.size, size.align); max.size = max.size.max(size.size); @@ -152,11 +157,11 @@ impl Layout for Variant { } impl Variant { - pub fn payload_offset(&self) -> usize { - let mut offset = self.tag_repr.mem_size_align().size; + pub fn payload_offset(&self, export: bool) -> usize { + let mut offset = self.tag_repr.mem_size_align(export).size; for case in self.cases.iter() { if let Some(payload) = &case.tref { - offset = offset.max(align_to(offset, payload.mem_size_align().align)); + offset = offset.max(align_to(offset, payload.mem_size_align(export).align)); } } offset @@ -198,13 +203,13 @@ mod test { } impl Layout for HandleDatatype { - fn mem_size_align(&self) -> SizeAlign { - BuiltinType::S32.mem_size_align() + fn mem_size_align(&self, export: bool) -> SizeAlign { + BuiltinType::S32.mem_size_align(export) } } impl Layout for BuiltinType { - fn mem_size_align(&self) -> SizeAlign { + fn mem_size_align(&self, _export: bool) -> SizeAlign { match self { BuiltinType::U8 { .. } | BuiltinType::S8 => SizeAlign { size: 1, align: 1 }, BuiltinType::U16 | BuiltinType::S16 => SizeAlign { size: 2, align: 2 }, diff --git a/tools/witx/src/parser.rs b/tools/witx/src/parser.rs index 2cb9c92d2..45797a727 100644 --- a/tools/witx/src/parser.rs +++ b/tools/witx/src/parser.rs @@ -54,6 +54,8 @@ mod kw { wast::custom_keyword!(variant); wast::custom_keyword!(bool_ = "bool"); wast::custom_keyword!(option); + wast::custom_keyword!(in_buffer = "in-buffer"); + wast::custom_keyword!(out_buffer = "out-buffer"); } mod annotation { @@ -293,6 +295,7 @@ pub enum TypedefSyntax<'a> { List(Box>), Pointer(Box>), ConstPointer(Box>), + Buffer(BufferSyntax<'a>), Builtin(BuiltinType), Ident(wast::Id<'a>), String, @@ -333,6 +336,8 @@ impl<'a> Parse<'a> for TypedefSyntax<'a> { Ok(TypedefSyntax::Variant(parser.parse()?)) } else if l.peek::() { Ok(TypedefSyntax::Handle(parser.parse()?)) + } else if l.peek::() || l.peek::() { + Ok(TypedefSyntax::Buffer(parser.parse()?)) } else if l.peek::() { parser.parse::()?; Ok(TypedefSyntax::List(Box::new(parser.parse()?))) @@ -611,6 +616,26 @@ impl<'a> Parse<'a> for HandleSyntax { } } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BufferSyntax<'a> { + pub out: bool, + pub ty: Box>, +} + +impl<'a> Parse<'a> for BufferSyntax<'a> { + fn parse(parser: Parser<'a>) -> Result { + let out = if parser.peek::() { + parser.parse::()?; + false + } else { + parser.parse::()?; + true + }; + let ty = Box::new(parser.parse()?); + Ok(BufferSyntax { out, ty }) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct ModuleSyntax<'a> { pub name: wast::Id<'a>, diff --git a/tools/witx/src/render.rs b/tools/witx/src/render.rs index a740e282c..fdfcf4d2b 100644 --- a/tools/witx/src/render.rs +++ b/tools/witx/src/render.rs @@ -136,6 +136,7 @@ impl Type { SExpr::word("const_pointer"), p.to_sexpr(), ]), + Type::Buffer(_) => unimplemented!(), Type::Builtin(b) => b.to_sexpr(), } } diff --git a/tools/witx/src/validate.rs b/tools/witx/src/validate.rs index 37f5ac8f5..59d6a4e68 100644 --- a/tools/witx/src/validate.rs +++ b/tools/witx/src/validate.rs @@ -1,11 +1,11 @@ use crate::{ io::{Filesystem, WitxIo}, parser::{ - CommentSyntax, DeclSyntax, Documented, EnumSyntax, ExpectedSyntax, FlagsSyntax, - HandleSyntax, ImportTypeSyntax, ModuleDeclSyntax, OptionSyntax, RecordSyntax, TupleSyntax, - TypedefSyntax, UnionSyntax, VariantSyntax, + BufferSyntax, CommentSyntax, DeclSyntax, Documented, EnumSyntax, ExpectedSyntax, + FlagsSyntax, HandleSyntax, ImportTypeSyntax, ModuleDeclSyntax, OptionSyntax, RecordSyntax, + TupleSyntax, TypedefSyntax, UnionSyntax, VariantSyntax, }, - BuiltinType, Case, Constant, Definition, Document, Entry, HandleDatatype, Id, IntRepr, + Buffer, BuiltinType, Case, Constant, Definition, Document, Entry, HandleDatatype, Id, IntRepr, InterfaceFunc, InterfaceFuncParam, Location, Module, ModuleDefinition, ModuleEntry, ModuleImport, ModuleImportVariant, NamedType, RecordDatatype, RecordKind, RecordMember, Type, TypeRef, Variant, @@ -328,6 +328,7 @@ impl DocValidationScope<'_> { TypedefSyntax::ConstPointer(syntax) => { Type::ConstPointer(self.validate_datatype(syntax, false, span)?) } + TypedefSyntax::Buffer(syntax) => Type::Buffer(self.validate_buffer(syntax, span)?), TypedefSyntax::Builtin(builtin) => Type::Builtin(*builtin), TypedefSyntax::String => { Type::List(TypeRef::Value(Rc::new(Type::Builtin(BuiltinType::Char)))) @@ -646,6 +647,17 @@ impl DocValidationScope<'_> { Ok(HandleDatatype {}) } + fn validate_buffer( + &self, + syntax: &BufferSyntax, + span: wast::Span, + ) -> Result { + Ok(Buffer { + out: syntax.out, + tref: self.validate_datatype(&syntax.ty, false, span)?, + }) + } + fn validate_int_repr( &self, type_: &BuiltinType, diff --git a/tools/witx/tests/witxt.rs b/tools/witx/tests/witxt.rs index 5c8575409..6486211e5 100644 --- a/tools/witx/tests/witxt.rs +++ b/tools/witx/tests/witxt.rs @@ -13,7 +13,7 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::str; use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; -use wast::parser::{self, Parse, ParseBuffer, Parser}; +use wast::parser::{self, Cursor, Parse, ParseBuffer, Parser, Peek}; use witx::{Documentation, Instruction, Representable, WasmType}; fn main() { @@ -194,24 +194,25 @@ impl WitxtRunner<'_> { } WitxtDirective::AssertAbi { witx, - wasm_signature: (wasm_params, wasm_results), + wasm_signatures, abis, .. } => { let doc = witx.document(contents, test)?; let module = doc.modules().next().ok_or_else(|| anyhow!("no modules"))?; let func = module.funcs().next().ok_or_else(|| anyhow!("no funcs"))?; - - let sig = func.wasm_signature(); - if sig.params != wasm_params { - bail!("expected params {:?}, found {:?}", wasm_params, sig.params); - } - if sig.results != wasm_results { - bail!( - "expected results {:?}, found {:?}", - wasm_results, - sig.results - ); + for (mode, wasm_params, wasm_results) in wasm_signatures.iter() { + let sig = func.wasm_signature(*mode); + if sig.params != *wasm_params { + bail!("expected params {:?}, found {:?}", wasm_params, sig.params); + } + if sig.results != *wasm_results { + bail!( + "expected results {:?}, found {:?}", + wasm_results, + sig.results + ); + } } for abi in abis { @@ -479,6 +480,11 @@ impl witx::Bindgen for AbiBindgen<'_> { I64FromBitflags { .. } => self.assert("i64.from_bitflags"), BitflagsFromI64 { .. } => self.assert("bitflags.from_i64"), + BufferLowerPtrLen { .. } => self.assert("buffer.lower_ptr_len"), + BufferLowerHandle { .. } => self.assert("buffer.lower_handle"), + BufferLiftPtrLen { .. } => self.assert("buffer.lift_ptr_len"), + BufferLiftHandle { .. } => self.assert("buffer.lift_handle"), + Witx { instr } => match instr { PointerFromI32 { .. } => self.assert("pointer.from_i32"), ConstPointerFromI32 { .. } => self.assert("const_pointer.from_i32"), @@ -570,7 +576,7 @@ enum WitxtDirective<'a> { AssertAbi { span: wast::Span, witx: Witx<'a>, - wasm_signature: (Vec, Vec), + wasm_signatures: Vec<(witx::CallMode, Vec, Vec)>, abis: Vec>, }, } @@ -611,30 +617,40 @@ impl<'a> Parse<'a> for WitxtDirective<'a> { Ok(WitxtDirective::AssertAbi { span, witx: parser.parens(|p| p.parse())?, - wasm_signature: parser.parens(|p| { - p.parse::()?; - let mut params = Vec::new(); - let mut results = Vec::new(); - if p.peek2::() { - p.parens(|p| { - p.parse::()?; - while !p.is_empty() { - params.push(parse_wasmtype(p)?); + wasm_signatures: { + let mut signatures = Vec::new(); + while parser.peek2::() { + signatures.push(parser.parens(|p| { + p.parse::()?; + let mode = p + .parse::>()? + .map(|p| p.0) + .unwrap_or(witx::CallMode::DeclaredImport); + let mut params = Vec::new(); + let mut results = Vec::new(); + if p.peek2::() { + p.parens(|p| { + p.parse::()?; + while !p.is_empty() { + params.push(parse_wasmtype(p)?); + } + Ok(()) + })?; } - Ok(()) - })?; - } - if p.peek2::() { - p.parens(|p| { - p.parse::()?; - while !p.is_empty() { - results.push(parse_wasmtype(p)?); + if p.peek2::() { + p.parens(|p| { + p.parse::()?; + while !p.is_empty() { + results.push(parse_wasmtype(p)?); + } + Ok(()) + })?; } - Ok(()) - })?; + Ok((mode, params, results)) + })?); } - Ok((params, results)) - })?, + signatures + }, abis: { let mut abis = Vec::new(); while !parser.is_empty() { @@ -791,3 +807,39 @@ impl<'a> Parse<'a> for Abi<'a> { Ok(Abi { mode, instrs }) } } + +struct CallMode(witx::CallMode); + +impl<'a> Parse<'a> for CallMode { + fn parse(parser: Parser<'a>) -> parser::Result { + let mut l = parser.lookahead1(); + Ok(CallMode(if l.peek::() { + parser.parse::()?; + witx::CallMode::DeclaredImport + } else if l.peek::() { + parser.parse::()?; + witx::CallMode::DeclaredExport + } else if l.peek::() { + parser.parse::()?; + witx::CallMode::DefinedImport + } else if l.peek::() { + parser.parse::()?; + witx::CallMode::DefinedExport + } else { + return Err(l.error()); + })) + } +} + +impl Peek for CallMode { + fn peek(cursor: Cursor<'_>) -> bool { + kw::declared_import::peek(cursor) + || kw::declared_export::peek(cursor) + || kw::defined_import::peek(cursor) + || kw::defined_export::peek(cursor) + } + + fn display() -> &'static str { + "call mode" + } +} diff --git a/tools/witx/tests/witxt/abi-next.witxt b/tools/witx/tests/witxt/abi-next.witxt index 3e2db28e7..94b31576d 100644 --- a/tools/witx/tests/witxt/abi-next.witxt +++ b/tools/witx/tests/witxt/abi-next.witxt @@ -864,3 +864,214 @@ return ) ) + +;; buffers +(assert_abi + (witx + (module $x (export "f" (func (param $a (in-buffer u8))))) + ) + (wasm declared_import (param i32 i32)) + (wasm defined_import (param i32 i32)) + (wasm declared_export (param i32)) + (wasm defined_export (param i32)) + + (declared_import + get-arg0 + + ;; definition of how to write to the buffer to-be-read by the callee + iter-base-pointer + block.push + variant-payload + i32.from_u8 + i32.store8 offset=0 + block.finish + + buffer.lower_ptr_len arg0 + call.wasm + return) + + (defined_import + get-arg0 + get-arg1 + + ;; definition of how to read to the buffer from the caller + iter-base-pointer + block.push + i32.load8u offset=0 + u8.from_i32 + block.finish + + buffer.lift_ptr_len arg0 arg1 + call.interface + return) + + (declared_export + get-arg0 + + ;; definition of how to write to the buffer to-be-read by the callee + iter-base-pointer + block.push + variant-payload + i32.from_u8 + i32.store8 offset=0 + block.finish + + buffer.lower_handle arg0 + call.wasm + return) + + (defined_export + get-arg0 + + ;; definition of how to read to the buffer from the caller + iter-base-pointer + block.push + i32.load8u offset=0 + u8.from_i32 + block.finish + + buffer.lift_handle arg0 + call.interface + return) +) +(assert_abi + (witx + (module $x (export "f" (func (param $a (out-buffer u8))))) + ) + (wasm declared_import (param i32 i32)) + (wasm defined_import (param i32 i32)) + (wasm declared_export (param i32)) + (wasm defined_export (param i32)) + + (declared_import + get-arg0 + + iter-base-pointer + block.push + i32.load8u offset=0 + u8.from_i32 + block.finish + + buffer.lower_ptr_len arg0 + call.wasm + return) + + (defined_import + get-arg0 + get-arg1 + + ;; definition of how to read to the buffer from the caller + iter-base-pointer + block.push + variant-payload + i32.from_u8 + i32.store8 offset=0 + block.finish + + buffer.lift_ptr_len arg0 arg1 + call.interface + return) +) + +(assert_invalid + (witx + (module $x (export "f" (func (result $a (in-buffer u8))))) + ) + "cannot use buffers in the result position" +) +(assert_invalid + (witx + (module $x (export "f" (func (result $a (out-buffer u8))))) + ) + "cannot use buffers in the result position" +) +(assert_invalid + (witx + (module $x (export "f" (func (param $a (out-buffer (in-buffer u8)))))) + ) + "cannot use buffers in the result position" +) +(assert_abi + (witx + (module $x (export "f" (func (param $a (list (in-buffer u8)))))) + ) + (wasm declared_import (param i32 i32)) + (wasm declared_export (param i32 i32)) + (declared_import + get-arg0 + block.push + iter-elem + iter-base-pointer + + iter-base-pointer + block.push + variant-payload + i32.from_u8 + i32.store8 offset=0 + block.finish + buffer.lower_ptr_len + + i32.store offset=4 + i32.store offset=0 + block.finish + list.lower arg0 + call.wasm + return) + + (defined_import + get-arg0 + get-arg1 + block.push + iter-base-pointer + i32.load offset=0 + i32.load offset=4 + + iter-base-pointer + block.push + i32.load8u offset=0 + u8.from_i32 + block.finish + buffer.lift_ptr_len + block.finish + list.lift arg0 arg1 + call.interface + return) + + (declared_export + get-arg0 + block.push + iter-elem + iter-base-pointer + + iter-base-pointer + block.push + variant-payload + i32.from_u8 + i32.store8 offset=0 + block.finish + buffer.lower_handle + + i32.store offset=0 + block.finish + list.lower arg0 + call.wasm + return) + + (defined_export + get-arg0 + get-arg1 + block.push + iter-base-pointer + i32.load offset=0 + + iter-base-pointer + block.push + i32.load8u offset=0 + u8.from_i32 + block.finish + buffer.lift_handle + block.finish + list.lift arg0 arg1 + call.interface + return) +) From 59e67ae8b820b834101b748c1b6efb3f6765f8eb Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Tue, 20 Apr 2021 10:14:34 -0700 Subject: [PATCH 17/22] Move typename, resource, and const declarations inside of module syntax. --- phases/ephemeral/witx/typenames.witx | 4 + phases/old/snapshot_0/witx/typenames.witx | 4 + phases/snapshot/witx/typenames.witx | 3 + tools/witx/src/parser.rs | 23 ++- tools/witx/src/toplevel.rs | 12 +- tools/witx/tests/witxt/abi-next.witxt | 178 +++++++++++------- tools/witx/tests/witxt/abi.witxt | 129 ++++++++----- tools/witx/tests/witxt/anonymous.witxt | 24 +-- tools/witx/tests/witxt/multimodule.witxt | 4 +- .../tests/witxt/multimodule/redefine_a.witx | 2 +- .../tests/witxt/multimodule/structured.witx | 6 +- .../witx/tests/witxt/multimodule/type_a.witx | 2 +- .../witx/tests/witxt/multimodule/type_b.witx | 2 +- .../witx/tests/witxt/multimodule/type_c.witx | 2 +- tools/witx/tests/witxt/resources.witxt | 22 +-- tools/witx/tests/witxt/resources/multi.witx | 16 +- tools/witx/tests/witxt/resources/other.witx | 6 +- tools/witx/tests/witxt/shorthand.witxt | 44 ++--- tools/witx/tests/witxt/simple.witxt | 8 +- tools/witx/tests/witxt/union.witxt | 94 +++++---- 20 files changed, 353 insertions(+), 232 deletions(-) diff --git a/phases/ephemeral/witx/typenames.witx b/phases/ephemeral/witx/typenames.witx index 12b60bc1f..f812a1556 100644 --- a/phases/ephemeral/witx/typenames.witx +++ b/phases/ephemeral/witx/typenames.witx @@ -5,6 +5,8 @@ ;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md) ;; for an explanation of what that means. +(module $typenames + ;;; An array size. ;;; ;;; Note: This is similar to `size_t` in POSIX. @@ -707,3 +709,5 @@ $prestat_dir ) ) + +) diff --git a/phases/old/snapshot_0/witx/typenames.witx b/phases/old/snapshot_0/witx/typenames.witx index a57a6cdd0..ddfaf2e2e 100644 --- a/phases/old/snapshot_0/witx/typenames.witx +++ b/phases/old/snapshot_0/witx/typenames.witx @@ -5,6 +5,8 @@ ;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/main/docs/witx.md) ;; for an explanation of what that means. +(module $typenames + (typename $size u32) ;;; Non-negative file size or length of a region within a file. @@ -745,3 +747,5 @@ $prestat_dir ) ) + +) diff --git a/phases/snapshot/witx/typenames.witx b/phases/snapshot/witx/typenames.witx index b50ff3474..20ee6ced7 100644 --- a/phases/snapshot/witx/typenames.witx +++ b/phases/snapshot/witx/typenames.witx @@ -5,6 +5,8 @@ ;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md) ;; for an explanation of what that means. +(module $typenames + (typename $size u32) ;;; Non-negative file size or length of a region within a file. @@ -747,3 +749,4 @@ ) ) +) diff --git a/tools/witx/src/parser.rs b/tools/witx/src/parser.rs index 2d7419955..f2b8b7136 100644 --- a/tools/witx/src/parser.rs +++ b/tools/witx/src/parser.rs @@ -229,11 +229,7 @@ impl<'a> Parse<'a> for TopLevelModule<'a> { let mut comments = parser.parse()?; loop { - if parser.peek2::() - || parser.peek2::() - || parser.peek2::() - || parser.peek2::() - { + if parser.peek2::() { decls.push(Documented { comments, item: parser.parens(|p| p.parse())?, @@ -249,10 +245,19 @@ impl<'a> Parse<'a> for TopLevelModule<'a> { p.parse::()?; module_name = p.parse()?; while !p.is_empty() { - functions.push(Documented { - comments: parser.parse()?, - item: p.parens(|p| p.parse())?, - }); + if parser.peek2::() + || parser.peek2::() + || parser.peek2::() { + decls.push(Documented { + comments: parser.parse()?, + item: parser.parens(|p| p.parse())?, + }); + } else { + functions.push(Documented { + comments: parser.parse()?, + item: p.parens(|p| p.parse())?, + }); + } } Ok(()) })?; diff --git a/tools/witx/src/toplevel.rs b/tools/witx/src/toplevel.rs index 57d50755e..7e49b665f 100644 --- a/tools/witx/src/toplevel.rs +++ b/tools/witx/src/toplevel.rs @@ -99,7 +99,7 @@ mod test { fn one_use() { parse_witx_with( "/a", - &MockFs::new(&[("/a", "(use $x from $b)"), ("/b.witx", "(typename $x u8)")]), + &MockFs::new(&[("/a", "(use $x from $b)"), ("/b.witx", "(module $x (typename $x u8))")]), ) .unwrap(); } @@ -110,8 +110,8 @@ mod test { "/a", &MockFs::new(&[ ("/a", "(use $b_float $c_int from $b)"), - ("/b.witx", "(use $c_int from $c)\n(typename $b_float f64)"), - ("/c.witx", "(typename $c_int u32)"), + ("/b.witx", "(use $c_int from $c)\n(module $x (typename $b_float f64))"), + ("/c.witx", "(module $x (typename $c_int u32))"), ]), ) .expect("parse"); @@ -136,13 +136,13 @@ mod test { ("/a", "(use $b_char from $b)\n(use $c_char from $c)"), ( "/b.witx", - "(use $d_char from $d) (typename $b_char $d_char)", + "(use $d_char from $d) (module $x (typename $b_char $d_char))", ), ( "/c.witx", - "(use $d_char from $d) (typename $c_char $d_char)", + "(use $d_char from $d) (module $x (typename $c_char $d_char))", ), - ("/d.witx", "(typename $d_char u8)"), + ("/d.witx", "(module $x (typename $d_char u8))"), ]), ) .expect("parse"); diff --git a/tools/witx/tests/witxt/abi-next.witxt b/tools/witx/tests/witxt/abi-next.witxt index 59c7a3892..ce0245ffb 100644 --- a/tools/witx/tests/witxt/abi-next.witxt +++ b/tools/witx/tests/witxt/abi-next.witxt @@ -76,9 +76,11 @@ ;; handle argument (assert_abi (witx - (resource $y) - (typename $y (handle $y)) - (module $x (export "f" (func (param $p $y)))) + (module $x + (resource $y) + (typename $y (handle $y)) + (export "f" (func (param $p $y))) + ) ) (wasm (param i32)) (declared_import get-arg0 i32.from_borrowed_handle arg0 call.wasm return) @@ -88,8 +90,10 @@ ;; record arguments (assert_abi (witx - (typename $y (record)) - (module $x (export "f" (func (param $p $y)))) + (module $x + (typename $y (record)) + (export "f" (func (param $p $y))) + ) ) (wasm) (declared_import get-arg0 record-lower arg0 call.wasm return) @@ -98,8 +102,10 @@ (assert_abi (witx - (typename $y (record (field $a u32))) - (module $x (export "f" (func (param $p $y)))) + (module $x + (typename $y (record (field $a u32))) + (export "f" (func (param $p $y))) + ) ) (wasm (param i32)) (declared_import @@ -117,11 +123,13 @@ ) (assert_abi (witx - (typename $y (record - (field $a u32) - (field $b s64) - )) - (module $x (export "f" (func (param $p $y)))) + (module $x + (typename $y (record + (field $a u32) + (field $b s64) + )) + (export "f" (func (param $p $y))) + ) ) (wasm (param i32 i64)) (declared_import @@ -142,14 +150,16 @@ ) (assert_abi (witx - (typename $empty (record)) - (typename $tuple (tuple f64 u32)) - (typename $y (record - (field $a u32) - (field $b $empty) - (field $c $tuple) - )) - (module $x (export "f" (func (param $p $y)))) + (module $x + (typename $empty (record)) + (typename $tuple (tuple f64 u32)) + (typename $y (record + (field $a u32) + (field $b $empty) + (field $c $tuple) + )) + (export "f" (func (param $p $y))) + ) ) (wasm (param i32 f64 i32)) (declared_import @@ -179,8 +189,10 @@ ;; variant arguments (assert_abi (witx - (typename $y (enum $a $b)) - (module $x (export "f" (func (param $p $y)))) + (module $x + (typename $y (enum $a $b)) + (export "f" (func (param $p $y))) + ) ) (wasm (param i32)) (declared_import @@ -207,8 +219,10 @@ (assert_abi (witx - (typename $y (union u32 f32)) - (module $x (export "f" (func (param $p $y)))) + (module $x + (typename $y (union u32 f32)) + (export "f" (func (param $p $y))) + ) ) (wasm (param i32 i32)) (declared_import @@ -244,8 +258,10 @@ (assert_abi (witx - (typename $y (variant (case $none) (case $some f64))) - (module $x (export "f" (func (param $p $y)))) + (module $x + (typename $y (variant (case $none) (case $some f64))) + (export "f" (func (param $p $y))) + ) ) (wasm (param i32 f64)) (declared_import @@ -314,20 +330,22 @@ ;; flavorful parameters (assert_abi (witx - (typename $a (union (list u8) f32)) - (typename $b (tuple f64 bool)) - (typename $c (record - (field $a $a) - (field $b $b) - )) - - (typename $d (tuple f32 f64)) - (typename $e (variant - (case $a) - (case $b $d) - (case $c) - )) - (module $x (export "f" (func + (module $x + (typename $a (union (list u8) f32)) + (typename $b (tuple f64 bool)) + (typename $c (record + (field $a $a) + (field $b $b) + )) + + (typename $d (tuple f32 f64)) + (typename $e (variant + (case $a) + (case $b $d) + (case $c) + )) + + (export "f" (func (param $p1 $c) (param $p2 s32) (param $p3 $e) @@ -515,9 +533,11 @@ ;; handle result (assert_abi (witx - (resource $y) - (typename $y (handle $y)) - (module $x (export "f" (func (result $p $y)))) + (module $x + (resource $y) + (typename $y (handle $y)) + (export "f" (func (result $p $y))) + ) ) (wasm (result i32)) (declared_import call.wasm handle.owned_from_i32 ret0 return) @@ -527,8 +547,10 @@ ;; record results (assert_abi (witx - (typename $y (record)) - (module $x (export "f" (func (result $p $y)))) + (module $x + (typename $y (record)) + (export "f" (func (result $p $y))) + ) ) (wasm) (declared_import call.wasm record-lift return) @@ -536,8 +558,10 @@ ) (assert_abi (witx - (typename $y (record (field $a s32))) - (module $x (export "f" (func (result $p $y)))) + (module $x + (typename $y (record (field $a s32))) + (export "f" (func (result $p $y))) + ) ) (wasm (result i32)) (declared_import call.wasm s32.from_i32 ret0 record-lift return) @@ -549,8 +573,10 @@ ) (assert_abi (witx - (typename $y (record (field $a s32) (field $b u32))) - (module $x (export "f" (func (result $p $y)))) + (module $x + (typename $y (record (field $a s32) (field $b u32))) + (export "f" (func (result $p $y))) + ) ) (wasm (param i32)) (declared_import @@ -574,8 +600,10 @@ ;; variant results (assert_abi (witx - (typename $y (enum $a $b)) - (module $x (export "f" (func (result $p $y)))) + (module $x + (typename $y (enum $a $b)) + (export "f" (func (result $p $y))) + ) ) (wasm (result i32)) (declared_import @@ -600,8 +628,10 @@ (assert_abi (witx - (typename $y (variant (case $a f32) (case $b))) - (module $x (export "f" (func (result $p $y)))) + (module $x + (typename $y (variant (case $a f32) (case $b))) + (export "f" (func (result $p $y))) + ) ) (wasm (param i32)) (declared_import @@ -752,8 +782,10 @@ ) (assert_abi (witx - (typename $a (record (field $x bool) (field $y (list u8)))) - (module $x (export "f" (func (param $a (list $a))))) + (module $x + (typename $a (record (field $x bool) (field $y (list u8)))) + (export "f" (func (param $a (list $a)))) + ) ) (wasm (param i32 i32)) (declared_import @@ -804,8 +836,10 @@ ;; bitflags (assert_abi (witx - (typename $a (flags $a $b)) - (module $x (export "f" (func (param $a $a) (result $b $a)))) + (module $x + (typename $a (flags $a $b)) + (export "f" (func (param $a $a) (result $b $a))) + ) ) (wasm (param i32) (result i32)) (declared_import @@ -825,24 +859,28 @@ ) (assert_abi (witx - (typename $a (flags - $f0 $f1 $f2 $f3 $f4 $f5 $f6 $f7 - $f8 $f9 $f10 $f11 $f12 $f13 $f14 $f15 - $f16 $f17 $f18 $f19 $f20 $f21 $f22 $f23 - $f24 $f25 $f26 $f27 $f28 $f29 $f30 $f31 - $f32 - )) - (module $x (export "f" (func (param $a $a) (result $b $a)))) + (module $x + (typename $a (flags + $f0 $f1 $f2 $f3 $f4 $f5 $f6 $f7 + $f8 $f9 $f10 $f11 $f12 $f13 $f14 $f15 + $f16 $f17 $f18 $f19 $f20 $f21 $f22 $f23 + $f24 $f25 $f26 $f27 $f28 $f29 $f30 $f31 + $f32 + )) + (export "f" (func (param $a $a) (result $b $a))) + ) ) (wasm (param i64) (result i64)) ) (assert_abi (witx - (typename $a (flags $a $b)) - (module $x (export "f" (func - (param $a (list $a)) - (result $b (list $a)) - ))) + (module $x + (typename $a (flags $a $b)) + (export "f" (func + (param $a (list $a)) + (result $b (list $a)) + )) + ) ) (wasm (param i32 i32 i32)) (declared_import diff --git a/tools/witx/tests/witxt/abi.witxt b/tools/witx/tests/witxt/abi.witxt index 93cc4986f..0bd97d4a7 100644 --- a/tools/witx/tests/witxt/abi.witxt +++ b/tools/witx/tests/witxt/abi.witxt @@ -100,8 +100,10 @@ ;; flags parameter (assert_abi (witx - (typename $a (flags $x $y)) - (module $x (@interface func (export "f") (param $p $a))) + (module $x + (typename $a (flags $x $y)) + (@interface func (export "f") (param $p $a)) + ) ) (wasm (param i32)) (declared_import get-arg0 i32.from_bitflags arg0 call.wasm return) @@ -109,8 +111,10 @@ ) (assert_abi (witx - (typename $a (flags (@witx repr u64) $x $y)) - (module $x (@interface func (export "f") (param $p $a))) + (module $x + (typename $a (flags (@witx repr u64) $x $y)) + (@interface func (export "f") (param $p $a)) + ) ) (wasm (param i64)) (declared_import get-arg0 i64.from_bitflags arg0 call.wasm return) @@ -118,8 +122,10 @@ ) (assert_abi (witx - (typename $a (record (field $x bool))) - (module $x (@interface func (export "f") (param $p $a))) + (module $x + (typename $a (record (field $x bool))) + (@interface func (export "f") (param $p $a)) + ) ) (wasm (param i32)) (declared_import get-arg0 i32.from_bitflags arg0 call.wasm return) @@ -128,8 +134,10 @@ ;; struct parameter (assert_abi (witx - (typename $a (record (field $x u8))) - (module $x (@interface func (export "f") (param $p $a))) + (module $x + (typename $a (record (field $x u8))) + (@interface func (export "f") (param $p $a)) + ) ) (wasm (param i32)) (declared_import get-arg0 addr-of arg0 call.wasm return) @@ -145,9 +153,11 @@ ;; handle parameter (assert_abi (witx - (resource $a) - (typename $a (handle $a)) - (module $x (@interface func (export "f") (param $p $a))) + (module $x + (resource $a) + (typename $a (handle $a)) + (@interface func (export "f") (param $p $a)) + ) ) (wasm (param i32)) (declared_import get-arg0 i32.from_borrowed_handle arg0 call.wasm return) @@ -157,7 +167,8 @@ ;; list parameter (assert_abi (witx - (module $x (@interface func (export "f") (param $p (list u8)))) + (module $x + (@interface func (export "f") (param $p (list u8)))) ) (wasm (param i32 i32)) (declared_import get-arg0 list.canon_lower arg0 call.wasm return) @@ -172,8 +183,10 @@ ;; variant parameter -- some not allowed at this time (assert_abi (witx - (typename $a (enum $b)) - (module $x (@interface func (export "f") (param $p $a))) + (module $x + (typename $a (enum $b)) + (@interface func (export "f") (param $p $a)) + ) ) (wasm (param i32)) (declared_import @@ -194,8 +207,10 @@ ) (assert_abi (witx - (typename $a (union f32)) - (module $x (@interface func (export "f") (param $p $a))) + (module $x + (typename $a (union f32)) + (@interface func (export "f") (param $p $a)) + ) ) (wasm (param i32)) (declared_import get-arg0 addr-of arg0 call.wasm return) @@ -306,8 +321,10 @@ ;; flags ret0 return (assert_abi (witx - (typename $a (flags $x $y)) - (module $x (@interface func (export "f") (result $p $a))) + (module $x + (typename $a (flags $x $y)) + (@interface func (export "f") (result $p $a)) + ) ) (wasm (result i32)) (declared_import call.wasm bitflags.from_i32 ret0 return) @@ -315,8 +332,10 @@ ) (assert_abi (witx - (typename $a (flags (@witx repr u64) $x $y)) - (module $x (@interface func (export "f") (result $p $a))) + (module $x + (typename $a (flags (@witx repr u64) $x $y)) + (@interface func (export "f") (result $p $a)) + ) ) (wasm (result i64)) (declared_import call.wasm bitflags.from_i64 ret0 return) @@ -326,9 +345,11 @@ ;; handle ret0 return (assert_abi (witx - (resource $a) - (typename $a (handle $a)) - (module $x (@interface func (export "f") (result $p $a))) + (module $x + (resource $a) + (typename $a (handle $a)) + (@interface func (export "f") (result $p $a)) + ) ) (wasm (result i32)) (declared_import call.wasm handle.owned_from_i32 ret0 return) @@ -338,8 +359,10 @@ ;; struct return -- not supported (assert_invalid (witx - (typename $a (record (field $x u8))) - (module $x (@interface func (export "f") (result $p $a))) + (module $x + (typename $a (record (field $x u8))) + (@interface func (export "f") (result $p $a)) + ) ) "ABI error: invalid return type" ) @@ -355,30 +378,38 @@ ;; variant return -- only some allowed (assert_invalid (witx - (typename $a (enum $b)) - (module $x (@interface func (export "f") (result $p $a))) + (module $x + (typename $a (enum $b)) + (@interface func (export "f") (result $p $a)) + ) ) "ABI error: invalid return type" ) (assert_invalid (witx - (typename $a (union s32 f32)) - (module $x (@interface func (export "f") (result $p $a))) + (module $x + (typename $a (union s32 f32)) + (@interface func (export "f") (result $p $a)) + ) ) "ABI error: invalid return type" ) (assert_invalid (witx - (typename $a (expected (error f32))) - (module $x (@interface func (export "f") (result $p $a))) + (module $x + (typename $a (expected (error f32))) + (@interface func (export "f") (result $p $a)) + ) ) "ABI error: only named types are allowed in results" ) (assert_invalid (witx - (typename $errno (enum $success $bad)) - (typename $a (expected f32 (error $errno))) - (module $x (@interface func (export "f") (result $p $a))) + (module $x + (typename $errno (enum $success $bad)) + (typename $a (expected f32 (error $errno))) + (@interface func (export "f") (result $p $a)) + ) ) "ABI error: only named types are allowed in results" ) @@ -386,9 +417,11 @@ ;; Result<(), $errno> (assert_abi (witx - (typename $errno (enum $success $bad)) - (typename $a (expected (error $errno))) - (module $x (@interface func (export "f") (result $p $a))) + (module $x + (typename $errno (enum $success $bad)) + (typename $a (expected (error $errno))) + (@interface func (export "f") (result $p $a)) + ) ) (wasm (result i32)) @@ -438,10 +471,12 @@ ;; Result<$ty, $errno> (assert_abi (witx - (typename $errno (enum $success $bad)) - (typename $size u32) - (typename $a (expected $size (error $errno))) - (module $x (@interface func (export "f") (result $p $a))) + (module $x + (typename $errno (enum $success $bad)) + (typename $size u32) + (typename $a (expected $size (error $errno))) + (@interface func (export "f") (result $p $a)) + ) ) (wasm (param i32) (result i32)) @@ -496,11 +531,13 @@ ;; Result<($a, $b), $errno> (assert_abi (witx - (typename $errno (enum $success $bad)) - (typename $size u32) - (typename $other (record (field $a $size))) - (module $x (@interface func (export "f") - (result $p (expected (tuple $size $other) (error $errno))))) + (module $x + (typename $errno (enum $success $bad)) + (typename $size u32) + (typename $other (record (field $a $size))) + (@interface func (export "f") + (result $p (expected (tuple $size $other) (error $errno)))) + ) ) (wasm (param i32 i32) (result i32)) diff --git a/tools/witx/tests/witxt/anonymous.witxt b/tools/witx/tests/witxt/anonymous.witxt index 4ff479712..2130e9cb2 100644 --- a/tools/witx/tests/witxt/anonymous.witxt +++ b/tools/witx/tests/witxt/anonymous.witxt @@ -3,55 +3,55 @@ (assert_invalid (witx - (typename $a (@witx pointer (record (field $b u8)))) + (module $x (typename $a (@witx pointer (record (field $b u8))))) ) "Anonymous structured types") (assert_invalid (witx - (typename $a (@witx pointer (union))) + (module $x (typename $a (@witx pointer (union)))) ) "Anonymous structured types") (assert_invalid (witx - (typename $a (@witx pointer (enum $b))) + (module $x (typename $a (@witx pointer (enum $b)))) ) "Anonymous structured types") (assert_invalid (witx - (typename $a (@witx pointer (flags $b))) + (module $x (typename $a (@witx pointer (flags $b)))) ) "Anonymous structured types") (assert_invalid - (witx + (witx (module $x (resource $x) (typename $a (@witx pointer (handle $x))) - ) + )) "Anonymous structured types") (assert_invalid (witx - (typename $a (record (field $b (record (field $c u8))))) + (module $x (typename $a (record (field $b (record (field $c u8)))))) ) "Anonymous structured types") (assert_invalid - (witx + (witx (module $x (typename $tag (enum $c)) (typename $a (record (field $b (union)))) - ) + )) "Anonymous structured types") ;; pointers don't count for anonymous indirections (witx - (typename $a (@witx pointer u8))) + (module $x (typename $a (@witx pointer u8)))) (witx - (typename $a (@witx pointer (@witx const_pointer u8)))) + (module $x (typename $a (@witx pointer (@witx const_pointer u8))))) (witx - (typename $a (record (field $b (@witx pointer u8))))) + (module $x (typename $a (record (field $b (@witx pointer u8)))))) diff --git a/tools/witx/tests/witxt/multimodule.witxt b/tools/witx/tests/witxt/multimodule.witxt index bffa18962..bc5b4f71a 100644 --- a/tools/witx/tests/witxt/multimodule.witxt +++ b/tools/witx/tests/witxt/multimodule.witxt @@ -2,11 +2,11 @@ (witx $b (load "multimodule/type_b.witx")) (witx $c (load "multimodule/type_c.witx")) -(witx $reference +(witx $reference (module $x (typename $a u32) (typename $b (record (field $member_a $a))) (typename $c (record (field $first_a $a) (field $second_a $a))) -) +)) (assert_eq $reference "a" $b "a") (assert_eq $reference "a" $c "a") diff --git a/tools/witx/tests/witxt/multimodule/redefine_a.witx b/tools/witx/tests/witxt/multimodule/redefine_a.witx index 76f04ffdb..f5b33f333 100644 --- a/tools/witx/tests/witxt/multimodule/redefine_a.witx +++ b/tools/witx/tests/witxt/multimodule/redefine_a.witx @@ -1,2 +1,2 @@ (use $a from $type_a) -(typename $a u8) +(module $x (typename $a u8)) diff --git a/tools/witx/tests/witxt/multimodule/structured.witx b/tools/witx/tests/witxt/multimodule/structured.witx index 5b9cf5dc7..748319849 100644 --- a/tools/witx/tests/witxt/multimodule/structured.witx +++ b/tools/witx/tests/witxt/multimodule/structured.witx @@ -1,2 +1,4 @@ -(typename $a u32) -(typename $foo (tuple $a)) +(module $x + (typename $a u32) + (typename $foo (tuple $a)) +) diff --git a/tools/witx/tests/witxt/multimodule/type_a.witx b/tools/witx/tests/witxt/multimodule/type_a.witx index 5499ff3db..79e9d15dd 100644 --- a/tools/witx/tests/witxt/multimodule/type_a.witx +++ b/tools/witx/tests/witxt/multimodule/type_a.witx @@ -1 +1 @@ -(typename $a u32) +(module $x (typename $a u32)) diff --git a/tools/witx/tests/witxt/multimodule/type_b.witx b/tools/witx/tests/witxt/multimodule/type_b.witx index ad695706a..668aecfd6 100644 --- a/tools/witx/tests/witxt/multimodule/type_b.witx +++ b/tools/witx/tests/witxt/multimodule/type_b.witx @@ -1,2 +1,2 @@ (use $a from $type_a) -(typename $b (record (field $member_a $a))) +(module $x (typename $b (record (field $member_a $a)))) diff --git a/tools/witx/tests/witxt/multimodule/type_c.witx b/tools/witx/tests/witxt/multimodule/type_c.witx index 5b7af399b..92e98de01 100644 --- a/tools/witx/tests/witxt/multimodule/type_c.witx +++ b/tools/witx/tests/witxt/multimodule/type_c.witx @@ -1,2 +1,2 @@ (use $a from $type_a) -(typename $c (record (field $first_a $a) (field $second_a $a))) +(module $x (typename $c (record (field $first_a $a) (field $second_a $a)))) diff --git a/tools/witx/tests/witxt/resources.witxt b/tools/witx/tests/witxt/resources.witxt index fb991903a..8d832aca5 100644 --- a/tools/witx/tests/witxt/resources.witxt +++ b/tools/witx/tests/witxt/resources.witxt @@ -1,37 +1,37 @@ (assert_invalid - (witx (typename $x (handle $y))) + (witx (module $b (typename $x (handle $y)))) "Unknown name `y`") (assert_invalid - (witx + (witx (module $b (typename $y u32) - (typename $x (handle $y))) + (typename $x (handle $y)))) "Unknown name `y`") (assert_invalid - (witx + (witx (module $b (resource $x) - (resource $x)) + (resource $x))) "Redefinition of name `x`") -(witx +(witx (module $b (resource $x) - (typename $x (handle $x))) + (typename $x (handle $x)))) -(witx $a +(witx $a (module $b (resource $x) (typename $x (handle $x)) (typename $y (handle $x)) -) +)) (assert_eq $a "x" $a "y") -(witx $a +(witx $a (module $b (resource $x) (resource $y) (typename $x (handle $x)) (typename $y (handle $y)) -) +)) (assert_ne $a "x" $a "y") diff --git a/tools/witx/tests/witxt/resources/multi.witx b/tools/witx/tests/witxt/resources/multi.witx index 105fef364..25ddf6f54 100644 --- a/tools/witx/tests/witxt/resources/multi.witx +++ b/tools/witx/tests/witxt/resources/multi.witx @@ -1,11 +1,13 @@ (use ($x as $other_x) ($y as $other_y) from $other) -;; this resource named `y` shouldn't be the same as another resource named `y` -;; defined elsewhere. -(resource $y) +(module $b + ;; this resource named `y` shouldn't be the same as another resource named `y` + ;; defined elsewhere. + (resource $y) -(typename $x1 (handle $other_x)) -(typename $x2 (handle $other_x)) + (typename $x1 (handle $other_x)) + (typename $x2 (handle $other_x)) -(typename $y1 (handle $other_y)) -(typename $y2 (handle $y)) + (typename $y1 (handle $other_y)) + (typename $y2 (handle $y)) +) diff --git a/tools/witx/tests/witxt/resources/other.witx b/tools/witx/tests/witxt/resources/other.witx index 7de20bc3d..1bba62fb9 100644 --- a/tools/witx/tests/witxt/resources/other.witx +++ b/tools/witx/tests/witxt/resources/other.witx @@ -1,2 +1,4 @@ -(resource $x) -(resource $y) +(module $b + (resource $x) + (resource $y) +) diff --git a/tools/witx/tests/witxt/shorthand.witxt b/tools/witx/tests/witxt/shorthand.witxt index d7118ccd3..4a18375f4 100644 --- a/tools/witx/tests/witxt/shorthand.witxt +++ b/tools/witx/tests/witxt/shorthand.witxt @@ -1,65 +1,65 @@ (witx $a - (typename $a bool)) + (module $b (typename $a bool))) (witx $b - (typename $a (variant (case $false) (case $true)))) + (module $b (typename $a (variant (case $false) (case $true))))) (assert_eq $a "a" $b "a") (witx $a - (typename $a (expected (error)))) + (module $b (typename $a (expected (error))))) (witx $b - (typename $a (variant (case $ok) (case $err)))) + (module $b (typename $a (variant (case $ok) (case $err))))) (assert_eq $a "a" $b "a") (witx $a - (typename $a (expected (error u32)))) + (module $b (typename $a (expected (error u32))))) (witx $b - (typename $a (variant (case $ok) (case $err u32)))) + (module $b (typename $a (variant (case $ok) (case $err u32))))) (assert_eq $a "a" $b "a") (witx $a - (typename $a (expected u32 (error)))) + (module $b (typename $a (expected u32 (error))))) (witx $b - (typename $a (variant (case $ok u32) (case $err)))) + (module $b (typename $a (variant (case $ok u32) (case $err))))) (assert_eq $a "a" $b "a") (witx $a - (typename $a (expected u32 (error u64)))) + (module $b (typename $a (expected u32 (error u64))))) (witx $b - (typename $a (variant (case $ok u32) (case $err u64)))) + (module $b (typename $a (variant (case $ok u32) (case $err u64))))) (assert_eq $a "a" $b "a") (witx $a - (typename $a (flags $a $b))) + (module $b (typename $a (flags $a $b)))) (witx $b - (typename $a (record (field $a bool) (field $b bool)))) + (module $b (typename $a (record (field $a bool) (field $b bool))))) (assert_eq $a "a" $b "a") (witx $a - (typename $a (enum $a $b))) + (module $b (typename $a (enum $a $b)))) (witx $b - (typename $a (variant (case $a) (case $b)))) + (module $b (typename $a (variant (case $a) (case $b))))) (assert_eq $a "a" $b "a") (witx $a - (typename $a string)) + (module $b (typename $a string))) (witx $b - (typename $a (list char))) + (module $b (typename $a (list char)))) (assert_eq $a "a" $b "a") (witx $a - (typename $a (tuple u32 u64))) + (module $b (typename $a (tuple u32 u64)))) (witx $b - (typename $a (record (field $0 u32) (field $1 u64)))) + (module $b (typename $a (record (field $0 u32) (field $1 u64))))) (assert_eq $a "a" $b "a") (witx $a - (typename $a (union u32 u64))) + (module $b (typename $a (union u32 u64)))) (witx $b - (typename $a (variant (case $0 u32) (case $1 u64)))) + (module $b (typename $a (variant (case $0 u32) (case $1 u64))))) (assert_eq $a "a" $b "a") (witx $a - (typename $a (option u32))) + (module $b (typename $a (option u32)))) (witx $b - (typename $a (variant (case $none) (case $some u32)))) + (module $b (typename $a (variant (case $none) (case $some u32))))) (assert_eq $a "a" $b "a") diff --git a/tools/witx/tests/witxt/simple.witxt b/tools/witx/tests/witxt/simple.witxt index af2403043..d7c204a47 100644 --- a/tools/witx/tests/witxt/simple.witxt +++ b/tools/witx/tests/witxt/simple.witxt @@ -1,12 +1,14 @@ (witx) (witx - (typename $x u32) + (module $a (typename $x u32)) ) (assert_invalid (witx - (typename $x u32) - (typename $x u32) + (module $a + (typename $x u32) + (typename $x u32) + ) ) "Redefinition of name `x`") diff --git a/tools/witx/tests/witxt/union.witxt b/tools/witx/tests/witxt/union.witxt index 58d2ce9b4..7d1a4cab8 100644 --- a/tools/witx/tests/witxt/union.witxt +++ b/tools/witx/tests/witxt/union.witxt @@ -1,87 +1,107 @@ (witx - (typename $u (union u8)) + (module $a (typename $u (union u8))) ) (witx - (typename $tag (enum (@witx tag u8) $c)) - (typename $u (union (@witx tag $tag) u8)) + (module $a + (typename $tag (enum (@witx tag u8) $c)) + (typename $u (union (@witx tag $tag) u8)) + ) ) (witx - (typename $tag (enum $a $b)) - (typename $u (variant (@witx tag $tag) (case $a) (case $b u16))) + (module $a + (typename $tag (enum $a $b)) + (typename $u (variant (@witx tag $tag) (case $a) (case $b u16))) + ) ) (witx - (typename $tag (enum $a $b)) - (typename $u (variant (@witx tag $tag) (case $a) (case $b))) + (module $a + (typename $tag (enum $a $b)) + (typename $u (variant (@witx tag $tag) (case $a) (case $b))) + ) ) (witx - (typename $u - (union - u8 - u16 - u32 - u64 - s8 - s16 - s32 - s64 - f32 - f64 - (@witx usize) - (@witx char8) + (module $a + (typename $u + (union + u8 + u16 + u32 + u64 + s8 + s16 + s32 + s64 + f32 + f64 + (@witx usize) + (@witx char8) + ) ) ) ) (assert_invalid - (witx (typename $u (union (@witx tag $tag) u8 u16))) + (witx (module $a (typename $u (union (@witx tag $tag) u8 u16)))) "Unknown name `tag`" ) (assert_invalid (witx - (typename $tag string) - (typename $u (union (@witx tag $tag) u8 u16)) + (module $a + (typename $tag string) + (typename $u (union (@witx tag $tag) u8 u16)) + ) ) "Wrong kind of name `tag`: expected enum or builtin, got list" ) (assert_invalid (witx - (typename $tag (enum $c)) - (typename $u (variant (@witx tag $tag) (case $b u8))) + (module $a + (typename $tag (enum $c)) + (typename $u (variant (@witx tag $tag) (case $b u8))) + ) ) "Invalid union field `b`: does not correspond to variant in tag `tag`" ) (assert_invalid (witx - (typename $tag (enum $c)) - (typename $u (union (@witx tag $tag) f32 u8)) + (module $a + (typename $tag (enum $c)) + (typename $u (union (@witx tag $tag) f32 u8)) + ) ) "Union expected 1 variants, found 2" ) (assert_invalid (witx - (typename $tag (enum $c $d)) - (typename $u (union (@witx tag $tag) f32)) + (module $a + (typename $tag (enum $c $d)) + (typename $u (union (@witx tag $tag) f32)) + ) ) "Union expected 2 variants, found 1" ) (witx $d1 - (typename $tag (enum $a $b)) - (typename $u (union (@witx tag $tag) u8 u16)) + (module $a + (typename $tag (enum $a $b)) + (typename $u (union (@witx tag $tag) u8 u16)) + ) ) (witx $d2 - (typename $tag (enum $a $b)) - (typename $u (variant (@witx tag $tag) (case $b u16) (case $a u8))) + (module $a + (typename $tag (enum $a $b)) + (typename $u (variant (@witx tag $tag) (case $b u16) (case $a u8))) + ) ) ;; These two unions should be represented the same: @@ -90,8 +110,10 @@ ;; Tag order doesnt matter for validation, but does for equality (witx $d3 - (typename $tag (enum $b $a)) - (typename $u (union (@witx tag $tag) u16 u8)) + (module $a + (typename $tag (enum $b $a)) + (typename $u (union (@witx tag $tag) u16 u8)) + ) ) (assert_ne $d3 "u" $d1 "u") From 5651d5293e4ecc17f37123105e7a2e5eef0848e8 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Tue, 20 Apr 2021 11:38:04 -0700 Subject: [PATCH 18/22] rustfmt --- tools/witx/src/parser.rs | 3 ++- tools/witx/src/toplevel.rs | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/tools/witx/src/parser.rs b/tools/witx/src/parser.rs index f2b8b7136..7a16b6be0 100644 --- a/tools/witx/src/parser.rs +++ b/tools/witx/src/parser.rs @@ -247,7 +247,8 @@ impl<'a> Parse<'a> for TopLevelModule<'a> { while !p.is_empty() { if parser.peek2::() || parser.peek2::() - || parser.peek2::() { + || parser.peek2::() + { decls.push(Documented { comments: parser.parse()?, item: parser.parens(|p| p.parse())?, diff --git a/tools/witx/src/toplevel.rs b/tools/witx/src/toplevel.rs index 7e49b665f..15c6a8a6c 100644 --- a/tools/witx/src/toplevel.rs +++ b/tools/witx/src/toplevel.rs @@ -99,7 +99,10 @@ mod test { fn one_use() { parse_witx_with( "/a", - &MockFs::new(&[("/a", "(use $x from $b)"), ("/b.witx", "(module $x (typename $x u8))")]), + &MockFs::new(&[ + ("/a", "(use $x from $b)"), + ("/b.witx", "(module $x (typename $x u8))"), + ]), ) .unwrap(); } @@ -110,7 +113,10 @@ mod test { "/a", &MockFs::new(&[ ("/a", "(use $b_float $c_int from $b)"), - ("/b.witx", "(use $c_int from $c)\n(module $x (typename $b_float f64))"), + ( + "/b.witx", + "(use $c_int from $c)\n(module $x (typename $b_float f64))", + ), ("/c.witx", "(module $x (typename $c_int u32))"), ]), ) From 9013e18b5d33e442387ca9b103833b00f750198b Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 3 May 2021 13:05:54 -0700 Subject: [PATCH 19/22] Use the parsed module name where applicable. Use the parsed module name for inline modules, and validate that it matches the filename for file modules. --- tools/witx/src/toplevel.rs | 32 +++++++++++++++---- tools/witx/src/validate.rs | 13 ++++++-- tools/witx/tests/witxt.rs | 9 +++++- .../tests/witxt/multimodule/redefine_a.witx | 2 +- .../tests/witxt/multimodule/structured.witx | 2 +- .../witx/tests/witxt/multimodule/type_a.witx | 2 +- .../witx/tests/witxt/multimodule/type_b.witx | 2 +- .../witx/tests/witxt/multimodule/type_c.witx | 2 +- tools/witx/tests/witxt/resources/multi.witx | 2 +- tools/witx/tests/witxt/resources/other.witx | 2 +- tools/witx/tests/witxt/simple.witxt | 4 ++- 11 files changed, 53 insertions(+), 19 deletions(-) diff --git a/tools/witx/src/toplevel.rs b/tools/witx/src/toplevel.rs index 15c6a8a6c..21a4843f4 100644 --- a/tools/witx/src/toplevel.rs +++ b/tools/witx/src/toplevel.rs @@ -36,7 +36,6 @@ fn _parse( } let input = io.fgets(&canon_path)?; - let mut validator = ModuleValidation::new(&input, path); let adjust_err = |mut error: wast::Error| { error.set_path(&path); @@ -46,6 +45,25 @@ fn _parse( let buf = wast::parser::ParseBuffer::new(&input).map_err(adjust_err)?; let doc = wast::parser::parse::(&buf).map_err(adjust_err)?; + let file_name = path.file_stem().unwrap().to_str().unwrap(); + let name = match doc.module_name { + Some(name) => name.name(), + None => file_name, + }; + let mut validator = ModuleValidation::new(&input, name, path); + + if let Some(name) = doc.module_name { + if file_name != name.name() { + let location = validator.location(name.span()); + return Err(ValidationError::ModuleNameMismatch { + location, + module_name: name.name().to_owned(), + file_name: file_name.to_owned(), + } + .into()); + } + } + let mut submodules = HashMap::new(); for t in doc.decls { match t.item { @@ -101,7 +119,7 @@ mod test { "/a", &MockFs::new(&[ ("/a", "(use $x from $b)"), - ("/b.witx", "(module $x (typename $x u8))"), + ("/b.witx", "(module $b (typename $x u8))"), ]), ) .unwrap(); @@ -115,9 +133,9 @@ mod test { ("/a", "(use $b_float $c_int from $b)"), ( "/b.witx", - "(use $c_int from $c)\n(module $x (typename $b_float f64))", + "(use $c_int from $c)\n(module $b (typename $b_float f64))", ), - ("/c.witx", "(module $x (typename $c_int u32))"), + ("/c.witx", "(module $c (typename $c_int u32))"), ]), ) .expect("parse"); @@ -142,13 +160,13 @@ mod test { ("/a", "(use $b_char from $b)\n(use $c_char from $c)"), ( "/b.witx", - "(use $d_char from $d) (module $x (typename $b_char $d_char))", + "(use $d_char from $d) (module $b (typename $b_char $d_char))", ), ( "/c.witx", - "(use $d_char from $d) (module $x (typename $c_char $d_char))", + "(use $d_char from $d) (module $c (typename $c_char $d_char))", ), - ("/d.witx", "(module $x (typename $d_char u8))"), + ("/d.witx", "(module $d (typename $d_char u8))"), ]), ) .expect("parse"); diff --git a/tools/witx/src/validate.rs b/tools/witx/src/validate.rs index 5b2631318..de2518d80 100644 --- a/tools/witx/src/validate.rs +++ b/tools/witx/src/validate.rs @@ -60,6 +60,12 @@ pub enum ValidationError { }, #[error("Variant has zero cases")] ZeroCaseVariant { location: Location }, + #[error("Module name `{module_name}` doesn't match file name `{file_name}`")] + ModuleNameMismatch { + location: Location, + module_name: String, + file_name: String, + }, } impl ValidationError { @@ -76,7 +82,8 @@ impl ValidationError { | ZeroCaseVariant { location } | InvalidUnionField { location, .. } | InvalidUnionTag { location, .. } - | CyclicModule { location } => { + | CyclicModule { location } + | ModuleNameMismatch { location, .. } => { format!("{}\n{}", location.highlight_source_with(witxio), &self) } NameAlreadyExists { @@ -144,8 +151,8 @@ pub struct ModuleValidation<'a> { } impl<'a> ModuleValidation<'a> { - pub fn new(text: &'a str, path: &'a Path) -> Self { - let name = Id::new(path.file_stem().unwrap().to_str().unwrap()); + pub fn new(text: &'a str, name: &'a str, path: &'a Path) -> Self { + let name = Id::new(name); let module_id = ModuleId(Rc::new(path.to_path_buf())); Self { module: Module::new(name, module_id), diff --git a/tools/witx/tests/witxt.rs b/tools/witx/tests/witxt.rs index cfd57893d..7d26d6f38 100644 --- a/tools/witx/tests/witxt.rs +++ b/tools/witx/tests/witxt.rs @@ -659,7 +659,14 @@ impl Witx<'_> { match &self.def { WitxDef::Inline(doc) => { - let mut validator = witx::ModuleValidation::new(contents, file); + if doc.module_name.is_none() { + dbg!(file); + } + let module_name = doc + .module_name + .expect("inline modules should have a name") + .name(); + let mut validator = witx::ModuleValidation::new(contents, module_name, file); for t in doc.decls.iter() { match &t.item { TopLevelSyntax::Decl(d) => validator diff --git a/tools/witx/tests/witxt/multimodule/redefine_a.witx b/tools/witx/tests/witxt/multimodule/redefine_a.witx index f5b33f333..51a87d563 100644 --- a/tools/witx/tests/witxt/multimodule/redefine_a.witx +++ b/tools/witx/tests/witxt/multimodule/redefine_a.witx @@ -1,2 +1,2 @@ (use $a from $type_a) -(module $x (typename $a u8)) +(module $redefine_a (typename $a u8)) diff --git a/tools/witx/tests/witxt/multimodule/structured.witx b/tools/witx/tests/witxt/multimodule/structured.witx index 748319849..e29d162f2 100644 --- a/tools/witx/tests/witxt/multimodule/structured.witx +++ b/tools/witx/tests/witxt/multimodule/structured.witx @@ -1,4 +1,4 @@ -(module $x +(module $structured (typename $a u32) (typename $foo (tuple $a)) ) diff --git a/tools/witx/tests/witxt/multimodule/type_a.witx b/tools/witx/tests/witxt/multimodule/type_a.witx index 79e9d15dd..514b6cb89 100644 --- a/tools/witx/tests/witxt/multimodule/type_a.witx +++ b/tools/witx/tests/witxt/multimodule/type_a.witx @@ -1 +1 @@ -(module $x (typename $a u32)) +(module $type_a (typename $a u32)) diff --git a/tools/witx/tests/witxt/multimodule/type_b.witx b/tools/witx/tests/witxt/multimodule/type_b.witx index 668aecfd6..c461b9ca7 100644 --- a/tools/witx/tests/witxt/multimodule/type_b.witx +++ b/tools/witx/tests/witxt/multimodule/type_b.witx @@ -1,2 +1,2 @@ (use $a from $type_a) -(module $x (typename $b (record (field $member_a $a)))) +(module $type_b (typename $b (record (field $member_a $a)))) diff --git a/tools/witx/tests/witxt/multimodule/type_c.witx b/tools/witx/tests/witxt/multimodule/type_c.witx index 92e98de01..db60eb0c7 100644 --- a/tools/witx/tests/witxt/multimodule/type_c.witx +++ b/tools/witx/tests/witxt/multimodule/type_c.witx @@ -1,2 +1,2 @@ (use $a from $type_a) -(module $x (typename $c (record (field $first_a $a) (field $second_a $a)))) +(module $type_c (typename $c (record (field $first_a $a) (field $second_a $a)))) diff --git a/tools/witx/tests/witxt/resources/multi.witx b/tools/witx/tests/witxt/resources/multi.witx index 25ddf6f54..f1d2edd12 100644 --- a/tools/witx/tests/witxt/resources/multi.witx +++ b/tools/witx/tests/witxt/resources/multi.witx @@ -1,6 +1,6 @@ (use ($x as $other_x) ($y as $other_y) from $other) -(module $b +(module $multi ;; this resource named `y` shouldn't be the same as another resource named `y` ;; defined elsewhere. (resource $y) diff --git a/tools/witx/tests/witxt/resources/other.witx b/tools/witx/tests/witxt/resources/other.witx index 1bba62fb9..2c723e09c 100644 --- a/tools/witx/tests/witxt/resources/other.witx +++ b/tools/witx/tests/witxt/resources/other.witx @@ -1,4 +1,4 @@ -(module $b +(module $other (resource $x) (resource $y) ) diff --git a/tools/witx/tests/witxt/simple.witxt b/tools/witx/tests/witxt/simple.witxt index d7c204a47..e3f9a9153 100644 --- a/tools/witx/tests/witxt/simple.witxt +++ b/tools/witx/tests/witxt/simple.witxt @@ -1,4 +1,6 @@ -(witx) +(witx + (module $a) +) (witx (module $a (typename $x u32)) From b53b089059f8b4aa08980551b264bd81803e1ec4 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Mon, 3 May 2021 17:15:13 -0700 Subject: [PATCH 20/22] Don't error if the filename "-" doesn't match the module name. --- tools/witx/src/toplevel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/witx/src/toplevel.rs b/tools/witx/src/toplevel.rs index 21a4843f4..5e6e0b803 100644 --- a/tools/witx/src/toplevel.rs +++ b/tools/witx/src/toplevel.rs @@ -53,7 +53,7 @@ fn _parse( let mut validator = ModuleValidation::new(&input, name, path); if let Some(name) = doc.module_name { - if file_name != name.name() { + if file_name != "-" && file_name != name.name() { let location = validator.location(name.span()); return Err(ValidationError::ModuleNameMismatch { location, From d31ad1bc9c0ee39da201bec9edc3bf3af6e76952 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Tue, 4 May 2021 10:16:48 -0700 Subject: [PATCH 21/22] Move `use` syntax inside of modules. And in witxt files, use the module name instead of giving `(witx ...)` its own name. --- .../ephemeral/witx/wasi_ephemeral_args.witx | 4 +- .../ephemeral/witx/wasi_ephemeral_clock.witx | 4 +- .../witx/wasi_ephemeral_environ.witx | 4 +- phases/ephemeral/witx/wasi_ephemeral_fd.witx | 4 +- .../ephemeral/witx/wasi_ephemeral_path.witx | 4 +- .../ephemeral/witx/wasi_ephemeral_poll.witx | 4 +- .../ephemeral/witx/wasi_ephemeral_proc.witx | 4 +- .../ephemeral/witx/wasi_ephemeral_random.witx | 4 +- .../ephemeral/witx/wasi_ephemeral_sched.witx | 4 +- .../ephemeral/witx/wasi_ephemeral_sock.witx | 4 +- phases/old/snapshot_0/witx/wasi_unstable.witx | 4 +- .../snapshot/witx/wasi_snapshot_preview1.witx | 4 +- tools/witx/cli/src/main.rs | 4 +- tools/witx/src/parser.rs | 20 ++--- tools/witx/src/toplevel.rs | 83 ++++++++++--------- tools/witx/tests/witxt.rs | 25 +++--- tools/witx/tests/witxt/multimodule.witxt | 16 ++-- .../tests/witxt/multimodule/redefine_a.witx | 6 +- .../witx/tests/witxt/multimodule/type_b.witx | 6 +- .../witx/tests/witxt/multimodule/type_c.witx | 6 +- .../witxt/multimodule/use_of_structured.witx | 4 +- tools/witx/tests/witxt/resources.witxt | 10 +-- tools/witx/tests/witxt/resources/multi.witx | 4 +- tools/witx/tests/witxt/shorthand.witxt | 66 +++++++-------- tools/witx/tests/witxt/snapshot_0.witx | 1 + tools/witx/tests/witxt/snapshot_preview1.witx | 1 + tools/witx/tests/witxt/union.witxt | 12 +-- tools/witx/tests/witxt/wasi.witxt | 4 - 28 files changed, 161 insertions(+), 155 deletions(-) create mode 100644 tools/witx/tests/witxt/snapshot_0.witx create mode 100644 tools/witx/tests/witxt/snapshot_preview1.witx diff --git a/phases/ephemeral/witx/wasi_ephemeral_args.witx b/phases/ephemeral/witx/wasi_ephemeral_args.witx index 92a38512d..6b6004c55 100644 --- a/phases/ephemeral/witx/wasi_ephemeral_args.witx +++ b/phases/ephemeral/witx/wasi_ephemeral_args.witx @@ -3,9 +3,9 @@ ;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md) ;; for an explanation of what that means. -(use $errno $size from $typenames) - (module $wasi_ephemeral_args + (use $errno $size from $typenames) + ;;; Read command-line argument data. ;;; The size of the array should match that returned by `sizes_get`. ;;; Each argument is expected to be `\0` terminated. diff --git a/phases/ephemeral/witx/wasi_ephemeral_clock.witx b/phases/ephemeral/witx/wasi_ephemeral_clock.witx index e7fb07e2f..33c64df23 100644 --- a/phases/ephemeral/witx/wasi_ephemeral_clock.witx +++ b/phases/ephemeral/witx/wasi_ephemeral_clock.witx @@ -5,9 +5,9 @@ ;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md) ;; for an explanation of what that means. -(use $clockid $timestamp $errno from $typenames) - (module $wasi_ephemeral_clock + (use $clockid $timestamp $errno from $typenames) + ;;; Return the resolution of a clock. ;;; Implementations are required to provide a non-zero value for supported clocks. For unsupported clocks, ;;; return `errno::inval`. diff --git a/phases/ephemeral/witx/wasi_ephemeral_environ.witx b/phases/ephemeral/witx/wasi_ephemeral_environ.witx index 2bb758a06..e6005e998 100644 --- a/phases/ephemeral/witx/wasi_ephemeral_environ.witx +++ b/phases/ephemeral/witx/wasi_ephemeral_environ.witx @@ -3,9 +3,9 @@ ;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md) ;; for an explanation of what that means. -(use $errno $size from $typenames) - (module $wasi_ephemeral_environ + (use $errno $size from $typenames) + ;;; Read environment variable data. ;;; The sizes of the buffers should match that returned by `sizes_get`. ;;; Key/value pairs are expected to be joined with `=`s, and terminated with `\0`s. diff --git a/phases/ephemeral/witx/wasi_ephemeral_fd.witx b/phases/ephemeral/witx/wasi_ephemeral_fd.witx index 84327febc..1c2e59e9a 100644 --- a/phases/ephemeral/witx/wasi_ephemeral_fd.witx +++ b/phases/ephemeral/witx/wasi_ephemeral_fd.witx @@ -5,9 +5,9 @@ ;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md) ;; for an explanation of what that means. -(use * from $typenames) - (module $wasi_ephemeral_fd + (use * from $typenames) + ;;; Provide file advisory information on a file descriptor. ;;; Note: This is similar to `posix_fadvise` in POSIX. (@interface func (export "advise") diff --git a/phases/ephemeral/witx/wasi_ephemeral_path.witx b/phases/ephemeral/witx/wasi_ephemeral_path.witx index a76b0d721..797e82ea0 100644 --- a/phases/ephemeral/witx/wasi_ephemeral_path.witx +++ b/phases/ephemeral/witx/wasi_ephemeral_path.witx @@ -5,9 +5,9 @@ ;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md) ;; for an explanation of what that means. -(use * from $typenames) - (module $wasi_ephemeral_path + (use * from $typenames) + ;;; Create a directory. ;;; Note: This is similar to `mkdirat` in POSIX. (@interface func (export "create_directory") diff --git a/phases/ephemeral/witx/wasi_ephemeral_poll.witx b/phases/ephemeral/witx/wasi_ephemeral_poll.witx index d3e878a4b..5a97e66b8 100644 --- a/phases/ephemeral/witx/wasi_ephemeral_poll.witx +++ b/phases/ephemeral/witx/wasi_ephemeral_poll.witx @@ -5,9 +5,9 @@ ;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md) ;; for an explanation of what that means. -(use $event $subscription $size $errno from $typenames) - (module $wasi_ephemeral_poll + (use $event $subscription $size $errno from $typenames) + ;;; Concurrently poll for the occurrence of a set of events. ;;; ;;; If `nsubscriptions` is 0, returns `errno::inval`. diff --git a/phases/ephemeral/witx/wasi_ephemeral_proc.witx b/phases/ephemeral/witx/wasi_ephemeral_proc.witx index 74f2d8961..d7411b615 100644 --- a/phases/ephemeral/witx/wasi_ephemeral_proc.witx +++ b/phases/ephemeral/witx/wasi_ephemeral_proc.witx @@ -5,9 +5,9 @@ ;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md) ;; for an explanation of what that means. -(use $exitcode from $typenames) - (module $wasi_ephemeral_proc + (use $exitcode from $typenames) + ;;; Terminate the process normally. An exit code of `$exitcode::success` ;;; reports successful completion of the program. An exit code of ;;; `$exitcode::failure` or any other value less than 126 reports a diff --git a/phases/ephemeral/witx/wasi_ephemeral_random.witx b/phases/ephemeral/witx/wasi_ephemeral_random.witx index 29149daea..47abf11a9 100644 --- a/phases/ephemeral/witx/wasi_ephemeral_random.witx +++ b/phases/ephemeral/witx/wasi_ephemeral_random.witx @@ -5,9 +5,9 @@ ;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md) ;; for an explanation of what that means. -(use $errno $size from $typenames) - (module $wasi_ephemeral_random + (use $errno $size from $typenames) + ;;; Write high-quality random data into a buffer. ;;; This function blocks when the implementation is unable to immediately ;;; provide sufficient high-quality random data. diff --git a/phases/ephemeral/witx/wasi_ephemeral_sched.witx b/phases/ephemeral/witx/wasi_ephemeral_sched.witx index a558e61d0..7477f832e 100644 --- a/phases/ephemeral/witx/wasi_ephemeral_sched.witx +++ b/phases/ephemeral/witx/wasi_ephemeral_sched.witx @@ -5,9 +5,9 @@ ;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md) ;; for an explanation of what that means. -(use $errno from $typenames) - (module $wasi_ephemeral_sched + (use $errno from $typenames) + ;;; Temporarily yield execution of the calling thread. ;;; Note: This is similar to `yield` in POSIX. (@interface func (export "yield") diff --git a/phases/ephemeral/witx/wasi_ephemeral_sock.witx b/phases/ephemeral/witx/wasi_ephemeral_sock.witx index ce6803781..1af9826b8 100644 --- a/phases/ephemeral/witx/wasi_ephemeral_sock.witx +++ b/phases/ephemeral/witx/wasi_ephemeral_sock.witx @@ -5,9 +5,9 @@ ;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md) ;; for an explanation of what that means. -(use * from $typenames) - (module $wasi_ephemeral_sock + (use * from $typenames) + ;;; Receive a message from a socket. ;;; Note: This is similar to `recv` in POSIX, though it also supports reading ;;; the data into multiple buffers in the manner of `readv`. diff --git a/phases/old/snapshot_0/witx/wasi_unstable.witx b/phases/old/snapshot_0/witx/wasi_unstable.witx index ca73b30de..9d77ffe45 100644 --- a/phases/old/snapshot_0/witx/wasi_unstable.witx +++ b/phases/old/snapshot_0/witx/wasi_unstable.witx @@ -6,12 +6,12 @@ ;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md) ;; for an explanation of what that means. -(use * from $typenames) - ;;; This API predated the convention of naming modules with a `wasi_unstable_` ;;; prefix and a version number. It is preserved here for compatibility, but ;;; we shouldn't follow this pattern in new APIs. (module $wasi_unstable + (use * from $typenames) + ;;; Read command-line argument data. ;;; The size of the array should match that returned by `args_sizes_get`. ;;; Each argument is expected to be `\0` terminated. diff --git a/phases/snapshot/witx/wasi_snapshot_preview1.witx b/phases/snapshot/witx/wasi_snapshot_preview1.witx index fba51a693..9f098479f 100644 --- a/phases/snapshot/witx/wasi_snapshot_preview1.witx +++ b/phases/snapshot/witx/wasi_snapshot_preview1.witx @@ -6,9 +6,9 @@ ;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md) ;; for an explanation of what that means. -(use * from $typenames) - (module $wasi_snapshot_preview1 + (use * from $typenames) + ;;; Read command-line argument data. ;;; The size of the array should match that returned by `args_sizes_get`. ;;; Each argument is expected to be `\0` terminated. diff --git a/tools/witx/cli/src/main.rs b/tools/witx/cli/src/main.rs index eb380897e..8c7eaecc0 100644 --- a/tools/witx/cli/src/main.rs +++ b/tools/witx/cli/src/main.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use std::collections::HashMap; use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; @@ -56,9 +57,10 @@ pub fn main() { check, output, } => { + let mut universe = HashMap::new(); let modules = input .iter() - .map(|i| load_witx(i, "input", verbose)) + .map(|i| load_witx(i, &mut universe, "input", verbose)) .collect::>(); let docs = witx::document(&modules); if check { diff --git a/tools/witx/src/parser.rs b/tools/witx/src/parser.rs index 7a16b6be0..a388f51c8 100644 --- a/tools/witx/src/parser.rs +++ b/tools/witx/src/parser.rs @@ -227,25 +227,17 @@ impl<'a> Parse<'a> for TopLevelModule<'a> { let mut functions = Vec::new(); let mut module_name = None; - let mut comments = parser.parse()?; - loop { - if parser.peek2::() { - decls.push(Documented { - comments, - item: parser.parens(|p| p.parse())?, - }); - comments = parser.parse()?; - } else { - break; - } - } - if parser.peek2::() { parser.parens(|p| { p.parse::()?; module_name = p.parse()?; while !p.is_empty() { - if parser.peek2::() + if parser.peek2::() { + decls.push(Documented { + comments: parser.parse()?, + item: parser.parens(|p| p.parse())?, + }); + } else if parser.peek2::() || parser.peek2::() || parser.peek2::() { diff --git a/tools/witx/src/toplevel.rs b/tools/witx/src/toplevel.rs index 5e6e0b803..56cd69f15 100644 --- a/tools/witx/src/toplevel.rs +++ b/tools/witx/src/toplevel.rs @@ -110,35 +110,32 @@ mod test { #[test] fn empty() { - parse_witx_with("/a", &MockFs::new(&[("/a", ";; empty")])).expect("parse"); + parse_witx_with("/a.witx", &MockFs::new(&[("/a.witx", ";; empty")])).expect("parse"); } #[test] fn one_use() { - parse_witx_with( - "/a", - &MockFs::new(&[ - ("/a", "(use $x from $b)"), - ("/b.witx", "(module $b (typename $x u8))"), - ]), - ) - .unwrap(); + let fs = &MockFs::new(&[ + ("/a.witx", "(module $a (use $x from $b))"), + ("/b.witx", "(module $b (typename $x u8))"), + ]); + parse_witx_with("/b.witx", fs).unwrap(); + parse_witx_with("/a.witx", fs).unwrap(); } #[test] fn multi_use() { - let doc = parse_witx_with( - "/a", - &MockFs::new(&[ - ("/a", "(use $b_float $c_int from $b)"), - ( - "/b.witx", - "(use $c_int from $c)\n(module $b (typename $b_float f64))", - ), - ("/c.witx", "(module $c (typename $c_int u32))"), - ]), - ) - .expect("parse"); + let fs = &MockFs::new(&[ + ("/a.witx", "(module $a (use $b_float $c_int from $b))"), + ( + "/b.witx", + "(module $b (use $c_int from $c) (typename $b_float f64))", + ), + ("/c.witx", "(module $c (typename $c_int u32))"), + ]); + parse_witx_with("/c.witx", fs).expect("parse"); + parse_witx_with("/b.witx", fs).expect("parse"); + let doc = parse_witx_with("/a.witx", fs).expect("parse"); let b_float = doc.typename(&Id::new("b_float")).unwrap(); assert_eq!(**b_float.type_(), Type::Builtin(BuiltinType::F64)); @@ -154,22 +151,25 @@ mod test { #[test] fn diamond_dependency() { - let doc = parse_witx_with( - "/a", - &MockFs::new(&[ - ("/a", "(use $b_char from $b)\n(use $c_char from $c)"), - ( - "/b.witx", - "(use $d_char from $d) (module $b (typename $b_char $d_char))", - ), - ( - "/c.witx", - "(use $d_char from $d) (module $c (typename $c_char $d_char))", - ), - ("/d.witx", "(module $d (typename $d_char u8))"), - ]), - ) - .expect("parse"); + let fs = &MockFs::new(&[ + ( + "/a.witx", + "(module $a\n(use $b_char from $b)\n(use $c_char from $c)\n)", + ), + ( + "/b.witx", + "(module $b (use $d_char from $d) (typename $b_char $d_char))", + ), + ( + "/c.witx", + "(module $c (use $d_char from $d) (typename $c_char $d_char))", + ), + ("/d.witx", "(module $d (typename $d_char u8))"), + ]); + parse_witx_with("/d.witx", fs).expect("parse"); + parse_witx_with("/c.witx", fs).expect("parse"); + parse_witx_with("/b.witx", fs).expect("parse"); + let doc = parse_witx_with("/a.witx", fs).expect("parse"); let b_char = doc.typename(&Id::new("b_char")).unwrap(); assert_eq!( @@ -181,9 +181,12 @@ mod test { #[test] fn use_not_found() { - match parse_witx_with("/a", &MockFs::new(&[("/a", "(use $x from $b)")])) - .err() - .unwrap() + match parse_witx_with( + "/a.witx", + &MockFs::new(&[("/a.witx", "(module $a (use $x from $b))")]), + ) + .err() + .unwrap() { WitxError::Io(path, _error) => assert_eq!(path, PathBuf::from("/b.witx")), e => panic!("wrong error: {:?}", e), diff --git a/tools/witx/tests/witxt.rs b/tools/witx/tests/witxt.rs index 7d26d6f38..b6d5aa331 100644 --- a/tools/witx/tests/witxt.rs +++ b/tools/witx/tests/witxt.rs @@ -149,14 +149,12 @@ impl WitxtRunner<'_> { self.bump_ntests(); match directive { WitxtDirective::Witx(witx) => { - let doc = witx.module(contents, test)?; + let doc = witx.module(contents, test, &mut self.modules)?; self.assert_md(&doc)?; - if let Some(name) = witx.id { - self.modules.insert(name.name().to_string(), doc); - } + self.modules.insert(doc.name().as_str().to_string(), doc); } WitxtDirective::AssertInvalid { witx, message, .. } => { - let err = match witx.module(contents, test) { + let err = match witx.module(contents, test, &mut self.modules) { Ok(_) => bail!("witx was valid when it shouldn't be"), Err(e) => format!("{:?}", anyhow::Error::from(e)), }; @@ -191,7 +189,7 @@ impl WitxtRunner<'_> { abis, .. } => { - let module = witx.module(contents, test)?; + let module = witx.module(contents, test, &mut self.modules)?; let func = module.funcs().next().ok_or_else(|| anyhow!("no funcs"))?; for (mode, wasm_params, wasm_results) in wasm_signatures.iter() { let sig = func.wasm_signature(*mode); @@ -644,7 +642,6 @@ fn parse_wasmtype(p: Parser<'_>) -> parser::Result { struct Witx<'a> { span: wast::Span, - id: Option>, def: WitxDef<'a>, } @@ -654,7 +651,12 @@ enum WitxDef<'a> { } impl Witx<'_> { - fn module(&self, contents: &str, file: &Path) -> Result { + fn module( + &self, + contents: &str, + file: &Path, + modules: &mut HashMap, + ) -> Result { use witx::parser::TopLevelSyntax; match &self.def { @@ -680,7 +682,9 @@ impl Witx<'_> { .validate_function(&f.item, &f.comments) .map_err(|e| anyhow::anyhow!("{}", e.report()))?; } - Ok(validator.into_module()) + let module = validator.into_module(); + modules.insert(module_name.to_owned(), module.clone()); + Ok(module) } WitxDef::Fs(path) => { let parent = file.parent().unwrap(); @@ -694,7 +698,6 @@ impl Witx<'_> { impl<'a> Parse<'a> for Witx<'a> { fn parse(parser: Parser<'a>) -> parser::Result { let span = parser.parse::()?.0; - let id = parser.parse()?; let def = if parser.peek2::() { parser.parens(|p| { @@ -704,7 +707,7 @@ impl<'a> Parse<'a> for Witx<'a> { } else { WitxDef::Inline(parser.parse()?) }; - Ok(Witx { id, span, def }) + Ok(Witx { span, def }) } } diff --git a/tools/witx/tests/witxt/multimodule.witxt b/tools/witx/tests/witxt/multimodule.witxt index bc5b4f71a..059a0ddb5 100644 --- a/tools/witx/tests/witxt/multimodule.witxt +++ b/tools/witx/tests/witxt/multimodule.witxt @@ -1,20 +1,20 @@ ;; B uses A, and C uses A. -(witx $b (load "multimodule/type_b.witx")) -(witx $c (load "multimodule/type_c.witx")) +(witx (load "multimodule/type_b.witx")) +(witx (load "multimodule/type_c.witx")) -(witx $reference (module $x +(witx (module $reference (typename $a u32) (typename $b (record (field $member_a $a))) (typename $c (record (field $first_a $a) (field $second_a $a))) )) -(assert_eq $reference "a" $b "a") -(assert_eq $reference "a" $c "a") -(assert_eq $reference "b" $b "b") -(assert_eq $reference "c" $c "c") +(assert_eq $reference "a" $type_b "a") +(assert_eq $reference "a" $type_c "a") +(assert_eq $reference "b" $type_b "b") +(assert_eq $reference "c" $type_c "c") (assert_invalid (witx (load "multimodule/redefine_a.witx")) "Redefinition of name `a`") -(witx $c (load "multimodule/use_of_structured.witx")) +(witx (load "multimodule/use_of_structured.witx")) diff --git a/tools/witx/tests/witxt/multimodule/redefine_a.witx b/tools/witx/tests/witxt/multimodule/redefine_a.witx index 51a87d563..447a84405 100644 --- a/tools/witx/tests/witxt/multimodule/redefine_a.witx +++ b/tools/witx/tests/witxt/multimodule/redefine_a.witx @@ -1,2 +1,4 @@ -(use $a from $type_a) -(module $redefine_a (typename $a u8)) +(module $redefine_a + (use $a from $type_a) + (typename $a u8) +) diff --git a/tools/witx/tests/witxt/multimodule/type_b.witx b/tools/witx/tests/witxt/multimodule/type_b.witx index c461b9ca7..dc2b3a3c3 100644 --- a/tools/witx/tests/witxt/multimodule/type_b.witx +++ b/tools/witx/tests/witxt/multimodule/type_b.witx @@ -1,2 +1,4 @@ -(use $a from $type_a) -(module $type_b (typename $b (record (field $member_a $a)))) +(module $type_b + (use $a from $type_a) + (typename $b (record (field $member_a $a))) +) diff --git a/tools/witx/tests/witxt/multimodule/type_c.witx b/tools/witx/tests/witxt/multimodule/type_c.witx index db60eb0c7..578524a94 100644 --- a/tools/witx/tests/witxt/multimodule/type_c.witx +++ b/tools/witx/tests/witxt/multimodule/type_c.witx @@ -1,2 +1,4 @@ -(use $a from $type_a) -(module $type_c (typename $c (record (field $first_a $a) (field $second_a $a)))) +(module $type_c + (use $a from $type_a) + (typename $c (record (field $first_a $a) (field $second_a $a))) +) diff --git a/tools/witx/tests/witxt/multimodule/use_of_structured.witx b/tools/witx/tests/witxt/multimodule/use_of_structured.witx index 467e8ef36..2eaa90ed5 100644 --- a/tools/witx/tests/witxt/multimodule/use_of_structured.witx +++ b/tools/witx/tests/witxt/multimodule/use_of_structured.witx @@ -1 +1,3 @@ -(use $foo from $structured) +(module $use_of_structured + (use $foo from $structured) +) diff --git a/tools/witx/tests/witxt/resources.witxt b/tools/witx/tests/witxt/resources.witxt index 8d832aca5..ee1c3eb60 100644 --- a/tools/witx/tests/witxt/resources.witxt +++ b/tools/witx/tests/witxt/resources.witxt @@ -18,7 +18,7 @@ (resource $x) (typename $x (handle $x)))) -(witx $a (module $b +(witx (module $a (resource $x) (typename $x (handle $x)) (typename $y (handle $x)) @@ -26,7 +26,7 @@ (assert_eq $a "x" $a "y") -(witx $a (module $b +(witx (module $a (resource $x) (resource $y) (typename $x (handle $x)) @@ -35,7 +35,7 @@ (assert_ne $a "x" $a "y") -(witx $a (load "resources/multi.witx")) +(witx (load "resources/multi.witx")) -(assert_eq $a "x1" $a "x2") -(assert_ne $a "y1" $a "y2") +(assert_eq $multi "x1" $multi "x2") +(assert_ne $multi "y1" $multi "y2") diff --git a/tools/witx/tests/witxt/resources/multi.witx b/tools/witx/tests/witxt/resources/multi.witx index f1d2edd12..ae851276d 100644 --- a/tools/witx/tests/witxt/resources/multi.witx +++ b/tools/witx/tests/witxt/resources/multi.witx @@ -1,6 +1,6 @@ -(use ($x as $other_x) ($y as $other_y) from $other) - (module $multi + (use ($x as $other_x) ($y as $other_y) from $other) + ;; this resource named `y` shouldn't be the same as another resource named `y` ;; defined elsewhere. (resource $y) diff --git a/tools/witx/tests/witxt/shorthand.witxt b/tools/witx/tests/witxt/shorthand.witxt index 4a18375f4..2d6756ab7 100644 --- a/tools/witx/tests/witxt/shorthand.witxt +++ b/tools/witx/tests/witxt/shorthand.witxt @@ -1,65 +1,65 @@ -(witx $a - (module $b (typename $a bool))) -(witx $b +(witx + (module $a (typename $a bool))) +(witx (module $b (typename $a (variant (case $false) (case $true))))) (assert_eq $a "a" $b "a") -(witx $a - (module $b (typename $a (expected (error))))) -(witx $b +(witx + (module $a (typename $a (expected (error))))) +(witx (module $b (typename $a (variant (case $ok) (case $err))))) (assert_eq $a "a" $b "a") -(witx $a - (module $b (typename $a (expected (error u32))))) -(witx $b +(witx + (module $a (typename $a (expected (error u32))))) +(witx (module $b (typename $a (variant (case $ok) (case $err u32))))) (assert_eq $a "a" $b "a") -(witx $a - (module $b (typename $a (expected u32 (error))))) -(witx $b +(witx + (module $a (typename $a (expected u32 (error))))) +(witx (module $b (typename $a (variant (case $ok u32) (case $err))))) (assert_eq $a "a" $b "a") -(witx $a - (module $b (typename $a (expected u32 (error u64))))) -(witx $b +(witx + (module $a (typename $a (expected u32 (error u64))))) +(witx (module $b (typename $a (variant (case $ok u32) (case $err u64))))) (assert_eq $a "a" $b "a") -(witx $a - (module $b (typename $a (flags $a $b)))) -(witx $b +(witx + (module $a (typename $a (flags $a $b)))) +(witx (module $b (typename $a (record (field $a bool) (field $b bool))))) (assert_eq $a "a" $b "a") -(witx $a - (module $b (typename $a (enum $a $b)))) -(witx $b +(witx + (module $a (typename $a (enum $a $b)))) +(witx (module $b (typename $a (variant (case $a) (case $b))))) (assert_eq $a "a" $b "a") -(witx $a - (module $b (typename $a string))) -(witx $b +(witx + (module $a (typename $a string))) +(witx (module $b (typename $a (list char)))) (assert_eq $a "a" $b "a") -(witx $a - (module $b (typename $a (tuple u32 u64)))) -(witx $b +(witx + (module $a (typename $a (tuple u32 u64)))) +(witx (module $b (typename $a (record (field $0 u32) (field $1 u64))))) (assert_eq $a "a" $b "a") -(witx $a - (module $b (typename $a (union u32 u64)))) -(witx $b +(witx + (module $a (typename $a (union u32 u64)))) +(witx (module $b (typename $a (variant (case $0 u32) (case $1 u64))))) (assert_eq $a "a" $b "a") -(witx $a - (module $b (typename $a (option u32)))) -(witx $b +(witx + (module $a (typename $a (option u32)))) +(witx (module $b (typename $a (variant (case $none) (case $some u32))))) (assert_eq $a "a" $b "a") diff --git a/tools/witx/tests/witxt/snapshot_0.witx b/tools/witx/tests/witxt/snapshot_0.witx new file mode 100644 index 000000000..baca3db9d --- /dev/null +++ b/tools/witx/tests/witxt/snapshot_0.witx @@ -0,0 +1 @@ +(witx (load "../../../../phases/old/snapshot_0/witx/wasi_unstable.witx")) diff --git a/tools/witx/tests/witxt/snapshot_preview1.witx b/tools/witx/tests/witxt/snapshot_preview1.witx new file mode 100644 index 000000000..63c2575ba --- /dev/null +++ b/tools/witx/tests/witxt/snapshot_preview1.witx @@ -0,0 +1 @@ +(witx (load "../../../../phases/snapshot/witx/wasi_snapshot_preview1.witx")) diff --git a/tools/witx/tests/witxt/union.witxt b/tools/witx/tests/witxt/union.witxt index 7d1a4cab8..59b39ff4a 100644 --- a/tools/witx/tests/witxt/union.witxt +++ b/tools/witx/tests/witxt/union.witxt @@ -90,15 +90,15 @@ "Union expected 2 variants, found 1" ) -(witx $d1 - (module $a +(witx + (module $d1 (typename $tag (enum $a $b)) (typename $u (union (@witx tag $tag) u8 u16)) ) ) -(witx $d2 - (module $a +(witx + (module $d2 (typename $tag (enum $a $b)) (typename $u (variant (@witx tag $tag) (case $b u16) (case $a u8))) ) @@ -109,8 +109,8 @@ (assert_eq $d2 "u" $d1 "u") ;; Tag order doesnt matter for validation, but does for equality -(witx $d3 - (module $a +(witx + (module $d3 (typename $tag (enum $b $a)) (typename $u (union (@witx tag $tag) u16 u8)) ) diff --git a/tools/witx/tests/witxt/wasi.witxt b/tools/witx/tests/witxt/wasi.witxt index b046a5821..11b3a4231 100644 --- a/tools/witx/tests/witxt/wasi.witxt +++ b/tools/witx/tests/witxt/wasi.witxt @@ -1,14 +1,10 @@ ;; Ensure that all current witx definitions in this repository load, parse, ;; roundtrip, and are documentable. -(witx (load "../../../../phases/old/snapshot_0/witx/wasi_unstable.witx")) -(witx (load "../../../../phases/snapshot/witx/wasi_snapshot_preview1.witx")) - (witx (load "../../../../phases/ephemeral/witx/wasi_ephemeral_args.witx")) (witx (load "../../../../phases/ephemeral/witx/wasi_ephemeral_clock.witx")) (witx (load "../../../../phases/ephemeral/witx/wasi_ephemeral_environ.witx")) (witx (load "../../../../phases/ephemeral/witx/wasi_ephemeral_fd.witx")) -(witx (load "../../../../phases/ephemeral/witx/wasi_ephemeral_fd.witx")) (witx (load "../../../../phases/ephemeral/witx/wasi_ephemeral_path.witx")) (witx (load "../../../../phases/ephemeral/witx/wasi_ephemeral_poll.witx")) (witx (load "../../../../phases/ephemeral/witx/wasi_ephemeral_proc.witx")) From 85a887ba1f7f4392d7229cdd847f5323fc0e847b Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 5 May 2021 08:06:15 -0700 Subject: [PATCH 22/22] Say "core wasm" instead of "native wasm". --- tools/witx/src/abi.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/tools/witx/src/abi.rs b/tools/witx/src/abi.rs index 98080a413..4506fd81d 100644 --- a/tools/witx/src/abi.rs +++ b/tools/witx/src/abi.rs @@ -248,43 +248,43 @@ def_instruction! { /// representation of `f64`. F64FromIf64 : [1] => [1], - /// Converts a native wasm `i32` to an interface type `s8`. + /// Converts a core wasm `i32` to an interface type `s8`. /// /// This will truncate the upper bits of the `i32`. S8FromI32 : [1] => [1], - /// Converts a native wasm `i32` to an interface type `u8`. + /// Converts a core wasm `i32` to an interface type `u8`. /// /// This will truncate the upper bits of the `i32`. U8FromI32 : [1] => [1], - /// Converts a native wasm `i32` to an interface type `s16`. + /// Converts a core wasm `i32` to an interface type `s16`. /// /// This will truncate the upper bits of the `i32`. S16FromI32 : [1] => [1], - /// Converts a native wasm `i32` to an interface type `u16`. + /// Converts a core wasm `i32` to an interface type `u16`. /// /// This will truncate the upper bits of the `i32`. U16FromI32 : [1] => [1], - /// Converts a native wasm `i32` to an interface type `s32`. + /// Converts a core wasm `i32` to an interface type `s32`. S32FromI32 : [1] => [1], - /// Converts a native wasm `i32` to an interface type `u32`. + /// Converts a core wasm `i32` to an interface type `u32`. U32FromI32 : [1] => [1], - /// Converts a native wasm `i64` to an interface type `s64`. + /// Converts a core wasm `i64` to an interface type `s64`. S64FromI64 : [1] => [1], - /// Converts a native wasm `i64` to an interface type `u64`. + /// Converts a core wasm `i64` to an interface type `u64`. U64FromI64 : [1] => [1], - /// Converts a native wasm `i32` to an interface type `char`. + /// Converts a core wasm `i32` to an interface type `char`. /// /// It's safe to assume that the `i32` is indeed a valid unicode code point. CharFromI32 : [1] => [1], - /// Converts a native wasm `f32` to an interface type `f32`. + /// Converts a core wasm `f32` to an interface type `f32`. If32FromF32 : [1] => [1], - /// Converts a native wasm `f64` to an interface type `f64`. + /// Converts a core wasm `f64` to an interface type `f64`. If64FromF64 : [1] => [1], - /// Converts a native wasm `i32` to a language-specific C `char`. + /// Converts a core wasm `i32` to a language-specific C `char`. /// /// This will truncate the upper bits of the `i32`. Char8FromI32 : [1] => [1], - /// Converts a native wasm `i32` to a language-specific `usize`. + /// Converts a core wasm `i32` to a language-specific `usize`. UsizeFromI32 : [1] => [1], // Handles @@ -348,7 +348,7 @@ def_instruction! { /// question was defined. I32FromOwnedHandle { ty: &'a NamedType } : [1] => [1], - /// Converts a native wasm `i32` into an owned handle value. + /// Converts a core wasm `i32` into an owned handle value. /// /// This is the converse of `I32FromOwnedHandle` and is used in similar /// situations: @@ -367,7 +367,7 @@ def_instruction! { /// just yet. HandleOwnedFromI32 { ty: &'a NamedType } : [1] => [1], - /// Converts a native wasm `i32` into a borrowedhandle value. + /// Converts a core wasm `i32` into a borrowedhandle value. /// /// This is the converse of `I32FromBorrowedHandle` and is used in similar /// situations: @@ -520,13 +520,13 @@ def_instruction! { ty: &'a RecordDatatype, name: &'a NamedType, } : [1] => [1], - /// Converts a native wasm `i32` to a language-specific record-of-bools. + /// Converts a core wasm `i32` to a language-specific record-of-bools. BitflagsFromI32 { repr: IntRepr, ty: &'a RecordDatatype, name: &'a NamedType, } : [1] => [1], - /// Converts a native wasm `i64` to a language-specific record-of-bools. + /// Converts a core wasm `i64` to a language-specific record-of-bools. BitflagsFromI64 { repr: IntRepr, ty: &'a RecordDatatype, @@ -619,9 +619,9 @@ def_instruction! { I32FromPointer : [1] => [1], /// Converts a language-specific pointer value to a wasm `i32`. I32FromConstPointer : [1] => [1], - /// Converts a native wasm `i32` to a language-specific pointer. + /// Converts a core wasm `i32` to a language-specific pointer. PointerFromI32 { ty: &'a TypeRef }: [1] => [1], - /// Converts a native wasm `i32` to a language-specific pointer. + /// Converts a core wasm `i32` to a language-specific pointer. ConstPointerFromI32 { ty: &'a TypeRef } : [1] => [1], /// This is a special instruction specifically for the original ABI of