From b9d14ef161c3d8760f1e46bd5617af43e791f976 Mon Sep 17 00:00:00 2001 From: katelyn martin Date: Mon, 21 Jun 2021 16:57:59 -0400 Subject: [PATCH 1/2] Revert "Enable more than one `handle` type in `*.witx` (#421)" This reverts commit 834679b8bf465070f98d8689587477e739ca21bc. --- phases/ephemeral/witx/typenames.witx | 3 +- phases/old/snapshot_0/witx/typenames.witx | 3 +- phases/snapshot/witx/typenames.witx | 3 +- tools/witx/src/ast.rs | 53 +----------- tools/witx/src/parser.rs | 58 ++----------- tools/witx/src/toplevel.rs | 15 ++++ tools/witx/src/validate.rs | 92 ++++----------------- tools/witx/tests/witxt.rs | 10 +-- tools/witx/tests/witxt/abi.witxt | 6 +- tools/witx/tests/witxt/anonymous.witxt | 3 +- tools/witx/tests/witxt/resources.witxt | 41 --------- tools/witx/tests/witxt/resources/multi.witx | 11 --- tools/witx/tests/witxt/resources/other.witx | 2 - 13 files changed, 48 insertions(+), 252 deletions(-) delete mode 100644 tools/witx/tests/witxt/resources.witxt delete mode 100644 tools/witx/tests/witxt/resources/multi.witx delete mode 100644 tools/witx/tests/witxt/resources/other.witx diff --git a/phases/ephemeral/witx/typenames.witx b/phases/ephemeral/witx/typenames.witx index 12b60bc1f..bbd6489df 100644 --- a/phases/ephemeral/witx/typenames.witx +++ b/phases/ephemeral/witx/typenames.witx @@ -281,9 +281,8 @@ ) ) -(resource $fd) ;;; A file descriptor handle. -(typename $fd (handle $fd)) +(typename $fd (handle)) ;;; A region of memory for scatter/gather reads. (typename $iovec diff --git a/phases/old/snapshot_0/witx/typenames.witx b/phases/old/snapshot_0/witx/typenames.witx index a57a6cdd0..f4ba78802 100644 --- a/phases/old/snapshot_0/witx/typenames.witx +++ b/phases/old/snapshot_0/witx/typenames.witx @@ -273,9 +273,8 @@ ) ) -(resource $fd) ;;; A file descriptor handle. -(typename $fd (handle $fd)) +(typename $fd (handle)) ;;; A region of memory for scatter/gather reads. (typename $iovec diff --git a/phases/snapshot/witx/typenames.witx b/phases/snapshot/witx/typenames.witx index b50ff3474..311b42233 100644 --- a/phases/snapshot/witx/typenames.witx +++ b/phases/snapshot/witx/typenames.witx @@ -273,9 +273,8 @@ ) ) -(resource $fd) ;;; A file descriptor handle. -(typename $fd (handle $fd)) +(typename $fd (handle)) ;;; A region of memory for scatter/gather reads. (typename $iovec diff --git a/tools/witx/src/ast.rs b/tools/witx/src/ast.rs index b36bb8a25..1f042244e 100644 --- a/tools/witx/src/ast.rs +++ b/tools/witx/src/ast.rs @@ -45,9 +45,6 @@ pub struct Module { types: Vec>, type_map: HashMap>, - resources: Vec>, - resource_map: HashMap>, - funcs: Vec>, func_map: HashMap>, @@ -64,8 +61,6 @@ impl Module { module_id, types: Default::default(), type_map: Default::default(), - resources: Default::default(), - resource_map: Default::default(), funcs: Default::default(), func_map: Default::default(), constants: Default::default(), @@ -85,14 +80,6 @@ impl Module { self.types.push(ty); } - pub(crate) fn push_resource(&mut self, r: Rc) { - assert!(self - .resource_map - .insert(r.name.clone(), r.clone()) - .is_none()); - self.resources.push(r); - } - pub(crate) fn push_func(&mut self, func: Rc) { assert!(self .func_map @@ -113,14 +100,6 @@ impl Module { self.types.iter() } - pub fn resource(&self, name: &Id) -> Option> { - self.resource_map.get(name).cloned() - } - - pub fn resources<'a>(&'a self) -> impl Iterator> + 'a { - self.resources.iter() - } - /// All of the (unique) types used as "err" variant of results returned from /// functions. pub fn error_types<'a>(&'a self) -> impl Iterator + 'a { @@ -520,37 +499,11 @@ impl Case { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Resource { - /// The local name within the module this resource is defined within. This - /// may differ from the id of the resource itself. - pub name: Id, - /// The unique id assigned to this resource. - pub resource_id: ResourceId, - /// Documentation in the defining module, if any. - pub docs: String, -} - -/// A unique id used to determine whether two handles are nominally referring -/// to the same resource. -/// -/// An id is composed of the definition location (a module id) and the original -/// name within that module. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ResourceId { - pub name: Id, - pub module_id: ModuleId, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct HandleDatatype { - /// The resource that this handle references, used for determining if two - /// handle types are nominally equal to one another. - pub resource_id: ResourceId, -} +pub struct HandleDatatype {} impl HandleDatatype { - pub fn type_equal(&self, other: &HandleDatatype) -> bool { - self.resource_id == other.resource_id + pub fn type_equal(&self, _other: &HandleDatatype) -> bool { + true } } diff --git a/tools/witx/src/parser.rs b/tools/witx/src/parser.rs index 6633b35f3..97dde4d78 100644 --- a/tools/witx/src/parser.rs +++ b/tools/witx/src/parser.rs @@ -34,13 +34,11 @@ mod kw { wast::custom_keyword!(noreturn); wast::custom_keyword!(pointer); wast::custom_keyword!(record); - wast::custom_keyword!(r#as = "as"); wast::custom_keyword!(r#const = "const"); wast::custom_keyword!(r#enum = "enum"); wast::custom_keyword!(r#union = "union"); wast::custom_keyword!(r#use = "use"); wast::custom_keyword!(repr); - wast::custom_keyword!(resource); wast::custom_keyword!(s16); wast::custom_keyword!(s32); wast::custom_keyword!(s64); @@ -229,7 +227,6 @@ impl<'a> Parse<'a> for TopLevelModule<'a> { if parser.peek2::() || parser.peek2::() || parser.peek2::() - || parser.peek2::() { decls.push(Documented { comments, @@ -282,7 +279,6 @@ impl<'a> Parse<'a> for TopLevelSyntax<'a> { #[derive(Debug, Clone)] pub enum DeclSyntax<'a> { Typename(TypenameSyntax<'a>), - Resource(ResourceSyntax<'a>), Const(Documented<'a, ConstSyntax<'a>>), } @@ -293,8 +289,6 @@ impl<'a> Parse<'a> for DeclSyntax<'a> { Ok(DeclSyntax::Typename(parser.parse()?)) } else if l.peek::() { Ok(DeclSyntax::Const(parser.parse()?)) - } else if l.peek::() { - Ok(DeclSyntax::Resource(parser.parse()?)) } else { Err(l.error()) } @@ -319,7 +313,7 @@ impl<'a> Parse<'a> for UseSyntax<'a> { #[derive(Debug, Clone, PartialEq, Eq)] pub enum UsedNames<'a> { - List(Vec>), + List(Vec>), All(wast::Span), } @@ -339,32 +333,6 @@ impl<'a> Parse<'a> for UsedNames<'a> { } } -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct UseName<'a> { - pub other_name: wast::Id<'a>, - pub our_name: wast::Id<'a>, -} - -impl<'a> Parse<'a> for UseName<'a> { - fn parse(parser: Parser<'a>) -> Result { - let (other_name, our_name) = if parser.peek::() { - let name = parser.parse()?; - (name, name) - } else { - parser.parens(|p| { - let other_name = p.parse()?; - p.parse::()?; - let our_name = p.parse()?; - Ok((other_name, our_name)) - })? - }; - Ok(UseName { - other_name, - our_name, - }) - } -} - #[derive(Debug, Clone, PartialEq, Eq)] pub struct TypenameSyntax<'a> { pub ident: wast::Id<'a>, @@ -389,7 +357,7 @@ pub enum TypedefSyntax<'a> { Record(RecordSyntax<'a>), Union(UnionSyntax<'a>), Variant(VariantSyntax<'a>), - Handle(HandleSyntax<'a>), + Handle(HandleSyntax), List(Box>), Pointer(Box>), ConstPointer(Box>), @@ -553,19 +521,6 @@ impl<'a> Parse<'a> for ConstSyntax<'a> { } } -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ResourceSyntax<'a> { - pub ident: wast::Id<'a>, -} - -impl<'a> Parse<'a> for ResourceSyntax<'a> { - fn parse(parser: Parser<'a>) -> Result { - parser.parse::()?; - let ident = parser.parse()?; - Ok(ResourceSyntax { ident }) - } -} - #[derive(Debug, Clone, PartialEq, Eq)] pub struct FlagsSyntax<'a> { pub repr: Option, @@ -701,15 +656,12 @@ impl<'a> Parse<'a> for CaseSyntax<'a> { } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct HandleSyntax<'a> { - pub resource: wast::Id<'a>, -} +pub struct HandleSyntax {} -impl<'a> Parse<'a> for HandleSyntax<'a> { +impl<'a> Parse<'a> for HandleSyntax { fn parse(parser: Parser<'a>) -> Result { parser.parse::()?; - let resource = parser.parse()?; - Ok(HandleSyntax { resource }) + Ok(HandleSyntax {}) } } diff --git a/tools/witx/src/toplevel.rs b/tools/witx/src/toplevel.rs index 57d50755e..9a5f10392 100644 --- a/tools/witx/src/toplevel.rs +++ b/tools/witx/src/toplevel.rs @@ -165,4 +165,19 @@ mod test { e => panic!("wrong error: {:?}", e), } } + + #[test] + fn use_invalid() { + match parse_witx_with("/a", &MockFs::new(&[("/a", "(use bbbbbbb)")])) + .err() + .unwrap() + { + WitxError::Parse(e) => { + let err = e.to_string(); + assert!(err.contains("expected an identifier"), "bad error: {}", err); + assert!(err.contains("/a:1:6")); + } + e => panic!("wrong error: {:?}", e), + } + } } diff --git a/tools/witx/src/validate.rs b/tools/witx/src/validate.rs index cc7b67a5a..c8ad706cd 100644 --- a/tools/witx/src/validate.rs +++ b/tools/witx/src/validate.rs @@ -6,8 +6,7 @@ use crate::{ VariantSyntax, }, Abi, BuiltinType, Case, Constant, Function, HandleDatatype, Id, IntRepr, Location, Module, - ModuleId, NamedType, Param, RecordDatatype, RecordKind, RecordMember, Resource, ResourceId, - Type, TypeRef, Variant, + ModuleId, NamedType, Param, RecordDatatype, RecordKind, RecordMember, Type, TypeRef, Variant, }; use std::collections::{HashMap, HashSet}; use std::path::Path; @@ -132,7 +131,6 @@ impl IdentValidation { pub struct ModuleValidation<'a> { module: Module, type_ns: IdentValidation, - resource_ns: IdentValidation, func_ns: IdentValidation, constant_ns: HashMap, bool_ty: TypeRef, @@ -147,7 +145,6 @@ impl<'a> ModuleValidation<'a> { Self { module: Module::new(name, module_id), type_ns: IdentValidation::new(), - resource_ns: IdentValidation::new(), func_ns: IdentValidation::new(), constant_ns: HashMap::new(), bool_ty: TypeRef::Value(Rc::new(Type::Variant(Variant { @@ -196,59 +193,22 @@ impl<'a> ModuleValidation<'a> { self.type_ns.introduce(ty.name.as_str(), loc)?; self.module.push_type(ty.clone()); } - for r in module.resources() { - let loc = self.location(span); - self.resource_ns.introduce(r.name.as_str(), loc)?; - self.module.push_resource(r.clone()); - } } UsedNames::List(names) => { for name in names { - let mut used = false; - let id = Id::new(name.other_name.name()); - let other_loc = self.location(name.other_name.span()); - let our_loc = self.location(name.our_name.span()); - - if let Some(ty) = module.typename(&id) { - let id = self - .type_ns - .introduce(name.our_name.name(), our_loc.clone())?; - let ty = if name.other_name.name() == name.our_name.name() { - ty - } else { - Rc::new(NamedType { - name: id, - module: self.module.module_id().clone(), - tref: TypeRef::Name(ty), - docs: String::new(), - }) - }; - self.module.push_type(ty); - used = true; - } - - if let Some(r) = module.resource(&id) { - let id = self.resource_ns.introduce(name.our_name.name(), our_loc)?; - let r = if name.other_name.name() == name.our_name.name() { - r - } else { - Rc::new(Resource { - name: id, - resource_id: r.resource_id.clone(), - docs: String::new(), - }) - }; - self.module.push_resource(r); - used = true; - } - - if !used { - return Err(ValidationError::UnknownName { - name: name.other_name.name().to_string(), - location: other_loc, + let loc = self.location(name.span()); + let id = self.type_ns.introduce(name.name(), loc)?; + let ty = match module.typename(&id) { + Some(ty) => ty, + None => { + return Err(ValidationError::UnknownName { + name: name.name().to_string(), + location: self.location(name.span()), + } + .into()); } - .into()); - } + }; + self.module.push_type(ty); } } } @@ -268,28 +228,13 @@ impl<'a> ModuleValidation<'a> { let tref = self.validate_datatype(&decl.def, true, decl.ident.span())?; self.module.push_type(Rc::new(NamedType { - name, + name: name.clone(), module: self.module.module_id().clone(), tref, docs, })); } - DeclSyntax::Resource(decl) => { - let loc = self.location(decl.ident.span()); - let name = self.resource_ns.introduce(decl.ident.name(), loc)?; - let docs = comments.docs(); - - self.module.push_resource(Rc::new(Resource { - name: name.clone(), - resource_id: ResourceId { - name, - module_id: self.module.module_id().clone(), - }, - docs, - })); - } - DeclSyntax::Const(syntax) => { let ty = Id::new(syntax.item.ty.name()); let loc = self.location(syntax.item.name.span()); @@ -686,15 +631,10 @@ impl<'a> ModuleValidation<'a> { fn validate_handle( &self, - syntax: &HandleSyntax, + _syntax: &HandleSyntax, _span: wast::Span, ) -> Result { - let loc = self.location(syntax.resource.span()); - let name = self.resource_ns.get(syntax.resource.name(), loc)?; - let resource = self.module.resource(&name).unwrap(); - Ok(HandleDatatype { - resource_id: resource.resource_id.clone(), - }) + Ok(HandleDatatype {}) } fn validate_int_repr( diff --git a/tools/witx/tests/witxt.rs b/tools/witx/tests/witxt.rs index c74e34679..753cac444 100644 --- a/tools/witx/tests/witxt.rs +++ b/tools/witx/tests/witxt.rs @@ -530,23 +530,19 @@ impl Witx<'_> { let mut validator = witx::ModuleValidation::new(contents, file); for t in doc.decls.iter() { match &t.item { - TopLevelSyntax::Decl(d) => validator - .validate_decl(&d, &t.comments) - .map_err(|e| anyhow::anyhow!("{}", e.report()))?, + TopLevelSyntax::Decl(d) => validator.validate_decl(&d, &t.comments)?, TopLevelSyntax::Use(_) => unimplemented!(), } } for f in doc.functions.iter() { - validator - .validate_function(&f.item, &f.comments) - .map_err(|e| anyhow::anyhow!("{}", e.report()))?; + validator.validate_function(&f.item, &f.comments)?; } Ok(validator.into_module()) } WitxDef::Fs(path) => { let parent = file.parent().unwrap(); let path = parent.join(path); - Ok(witx::load(&path).map_err(|e| anyhow::anyhow!("{}", e.report()))?) + Ok(witx::load(&path)?) } } } diff --git a/tools/witx/tests/witxt/abi.witxt b/tools/witx/tests/witxt/abi.witxt index 913e6edb2..ccdad7bf4 100644 --- a/tools/witx/tests/witxt/abi.witxt +++ b/tools/witx/tests/witxt/abi.witxt @@ -131,8 +131,7 @@ ;; handle parameter (assert_abi (witx - (resource $a) - (typename $a (handle $a)) + (typename $a (handle)) (module $x (@interface func (export "f") (param $p $a))) ) (wasm (param i32)) @@ -285,8 +284,7 @@ ;; handle return (assert_abi (witx - (resource $a) - (typename $a (handle $a)) + (typename $a (handle)) (module $x (@interface func (export "f") (result $p $a))) ) (wasm (result i32)) diff --git a/tools/witx/tests/witxt/anonymous.witxt b/tools/witx/tests/witxt/anonymous.witxt index 4ff479712..0ed3bc0d2 100644 --- a/tools/witx/tests/witxt/anonymous.witxt +++ b/tools/witx/tests/witxt/anonymous.witxt @@ -27,8 +27,7 @@ (assert_invalid (witx - (resource $x) - (typename $a (@witx pointer (handle $x))) + (typename $a (@witx pointer (handle))) ) "Anonymous structured types") diff --git a/tools/witx/tests/witxt/resources.witxt b/tools/witx/tests/witxt/resources.witxt deleted file mode 100644 index fb991903a..000000000 --- a/tools/witx/tests/witxt/resources.witxt +++ /dev/null @@ -1,41 +0,0 @@ -(assert_invalid - (witx (typename $x (handle $y))) - "Unknown name `y`") - -(assert_invalid - (witx - (typename $y u32) - (typename $x (handle $y))) - "Unknown name `y`") - -(assert_invalid - (witx - (resource $x) - (resource $x)) - "Redefinition of name `x`") - -(witx - (resource $x) - (typename $x (handle $x))) - -(witx $a - (resource $x) - (typename $x (handle $x)) - (typename $y (handle $x)) -) - -(assert_eq $a "x" $a "y") - -(witx $a - (resource $x) - (resource $y) - (typename $x (handle $x)) - (typename $y (handle $y)) -) - -(assert_ne $a "x" $a "y") - -(witx $a (load "resources/multi.witx")) - -(assert_eq $a "x1" $a "x2") -(assert_ne $a "y1" $a "y2") diff --git a/tools/witx/tests/witxt/resources/multi.witx b/tools/witx/tests/witxt/resources/multi.witx deleted file mode 100644 index 105fef364..000000000 --- a/tools/witx/tests/witxt/resources/multi.witx +++ /dev/null @@ -1,11 +0,0 @@ -(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) - -(typename $x1 (handle $other_x)) -(typename $x2 (handle $other_x)) - -(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 deleted file mode 100644 index 7de20bc3d..000000000 --- a/tools/witx/tests/witxt/resources/other.witx +++ /dev/null @@ -1,2 +0,0 @@ -(resource $x) -(resource $y) From f544485874bcfc74c25b7d4ea1e275c8737ae436 Mon Sep 17 00:00:00 2001 From: katelyn martin Date: Mon, 21 Jun 2021 16:58:10 -0400 Subject: [PATCH 2/2] Revert "Implement a new `use` syntax (#415)" This reverts commit fc3da39c0225416ac5b3178a13763efa4e87486b. --- phases/ephemeral/docs.md | 1080 +++++++++-------- .../ephemeral/witx/wasi_ephemeral_args.witx | 5 +- .../ephemeral/witx/wasi_ephemeral_clock.witx | 5 +- .../witx/wasi_ephemeral_environ.witx | 5 +- phases/ephemeral/witx/wasi_ephemeral_fd.witx | 5 +- .../ephemeral/witx/wasi_ephemeral_path.witx | 5 +- .../ephemeral/witx/wasi_ephemeral_poll.witx | 5 +- .../ephemeral/witx/wasi_ephemeral_proc.witx | 2 +- .../ephemeral/witx/wasi_ephemeral_random.witx | 5 +- .../ephemeral/witx/wasi_ephemeral_sched.witx | 2 +- .../ephemeral/witx/wasi_ephemeral_sock.witx | 5 +- phases/old/snapshot_0/docs.md | 1048 ++++++++-------- phases/old/snapshot_0/witx/wasi_unstable.witx | 5 +- phases/snapshot/docs.md | 1018 ++++++++-------- .../snapshot/witx/wasi_snapshot_preview1.witx | 5 +- tools/witx/cli/src/main.rs | 102 +- tools/witx/src/abi.rs | 24 +- tools/witx/src/ast.rs | 382 +++--- tools/witx/src/docs/ast.rs | 332 +++-- tools/witx/src/docs/mod.rs | 59 +- tools/witx/src/lib.rs | 20 +- tools/witx/src/parser.rs | 236 ++-- tools/witx/src/polyfill.rs | 255 ++++ tools/witx/src/render.rs | 320 +++++ tools/witx/src/representation.rs | 161 +++ tools/witx/src/toplevel.rs | 145 +-- tools/witx/src/validate.rs | 310 +++-- tools/witx/tests/witxt.rs | 185 ++- tools/witx/tests/witxt/multimodule.witxt | 20 +- .../tests/witxt/multimodule/redefine_a.witx | 1 - .../tests/witxt/multimodule/structured.witx | 2 - .../witx/tests/witxt/multimodule/type_b.witx | 2 +- .../witx/tests/witxt/multimodule/type_c.witx | 2 +- .../witxt/multimodule/use_of_structured.witx | 1 - tools/witx/tests/witxt/representation.witxt | 60 + tools/witx/tests/witxt/shorthand.witxt | 20 +- tools/witx/tests/witxt/union.witxt | 8 +- tools/witx/tests/witxt/wasi.witxt | 35 +- 38 files changed, 3585 insertions(+), 2297 deletions(-) create mode 100644 tools/witx/src/polyfill.rs create mode 100644 tools/witx/src/render.rs create mode 100644 tools/witx/src/representation.rs delete mode 100644 tools/witx/tests/witxt/multimodule/structured.witx delete mode 100644 tools/witx/tests/witxt/multimodule/use_of_structured.witx create mode 100644 tools/witx/tests/witxt/representation.witxt diff --git a/phases/ephemeral/docs.md b/phases/ephemeral/docs.md index 54f001760..f29d401c4 100644 --- a/phases/ephemeral/docs.md +++ b/phases/ephemeral/docs.md @@ -1,53 +1,26 @@ # Types -## `advice`: `Variant` -File or memory access pattern advisory information. - -Size: 1 - -Alignment: 1 - -### Variant cases -- `normal` -The application has no advice to give on its behavior with respect to the specified data. - -- `sequential` -The application expects to access the specified data sequentially from lower offsets to higher offsets. - -- `random` -The application expects to access the specified data in a random order. - -- `willneed` -The application expects to access the specified data in the near future. - -- `dontneed` -The application expects that it will not access the specified data in the near future. - -- `noreuse` -The application expects to access the specified data once and then not reuse it thereafter. +## `size`: `usize` +An array size. -## `ciovec`: `Record` -A region of memory for scatter/gather writes. +Note: This is similar to `size_t` in POSIX. -Size: 8 +Size: 4 Alignment: 4 -### Record members -- `buf`: `ConstPointer` -The address of the buffer to be written. - -Offset: 0 +## `filesize`: `u64` +Non-negative file size or length of a region within a file. -- `buf_len`: [`size`](#size) -The length of the buffer to be written. +Size: 8 -Offset: 4 +Alignment: 8 -## `ciovec_array`: `List` +## `timestamp`: `u64` +Timestamp in nanoseconds. Size: 8 -Alignment: 4 +Alignment: 8 ## `clockid`: `Variant` Identifiers for clocks. @@ -67,56 +40,6 @@ real time, whose value cannot be adjusted and which cannot have negative clock jumps. The epoch of this clock is undefined. The absolute time value of this clock therefore has no meaning. -## `device`: `u64` -Identifier for a device containing a file system. Can be used in combination -with [`inode`](#inode) to uniquely identify a file or directory in the filesystem. - -Size: 8 - -Alignment: 8 - -## `dircookie`: `u64` -A reference to the offset of a directory entry. - -Size: 8 - -Alignment: 8 - -## `dirent`: `Record` -A directory entry. - -Size: 24 - -Alignment: 8 - -### Record members -- `d_next`: [`dircookie`](#dircookie) -The offset of the next directory entry stored in this directory. - -Offset: 0 - -- `d_ino`: [`inode`](#inode) -The serial number of the file referred to by this directory entry. - -Offset: 8 - -- `d_type`: [`filetype`](#filetype) -The type of the file referred to by this directory entry. - -Offset: 16 - -- `d_namlen`: [`dirnamlen`](#dirnamlen) -The length of the name of the directory entry. - -Offset: 20 - -## `dirnamlen`: `u32` -The type for the [`dirent::d_namlen`](#dirent.d_namlen) field of [`dirent`](#dirent). - -Size: 4 - -Alignment: 4 - ## `errno`: `Variant` Error codes returned by functions. Not all of these error codes are returned by the functions provided by this @@ -359,245 +282,289 @@ Cross-device link. - `notcapable` Extension: Capabilities insufficient. -## `event`: `Record` -An event that occurred. +## `rights`: `Record` +File descriptor rights, determining which actions may be performed. -Size: 40 +Size: 8 Alignment: 8 ### Record members -- `userdata`: [`userdata`](#userdata) -User-provided value that got attached to [`subscription::userdata`](#subscription.userdata). +- `fd_datasync`: `bool` +The right to invoke `fd_datasync`. +If `path_open` is set, includes the right to invoke +`path_open` with [`fdflags::dsync`](#fdflags.dsync). -Offset: 0 +Bit: 0 -- `error`: [`errno`](#errno) -If non-zero, an error that occurred while processing the subscription request. +- `fd_read`: `bool` +The right to invoke `fd_read` and `sock_recv`. +If [`rights::fd_seek`](#rights.fd_seek) is set, includes the right to invoke `fd_pread`. -Offset: 8 +Bit: 1 -- `u`: [`event_u`](#event_u) -The type of the event that occurred, and the contents of the event +- `fd_seek`: `bool` +The right to invoke `fd_seek`. This flag implies [`rights::fd_tell`](#rights.fd_tell). -Offset: 16 +Bit: 2 -## `event_fd_readwrite`: `Record` -The contents of an [`event`](#event) when type is [`eventtype::fd_read`](#eventtype.fd_read) or -[`eventtype::fd_write`](#eventtype.fd_write). +- `fd_fdstat_set_flags`: `bool` +The right to invoke `fd_fdstat_set_flags`. -Size: 16 +Bit: 3 -Alignment: 8 +- `fd_sync`: `bool` +The right to invoke `fd_sync`. +If `path_open` is set, includes the right to invoke +`path_open` with [`fdflags::rsync`](#fdflags.rsync) and [`fdflags::dsync`](#fdflags.dsync). -### Record members -- `nbytes`: [`filesize`](#filesize) -The number of bytes available for reading or writing. +Bit: 4 -Offset: 0 +- `fd_tell`: `bool` +The right to invoke `fd_seek` in such a way that the file offset +remains unaltered (i.e., [`whence::cur`](#whence.cur) with offset zero), or to +invoke `fd_tell`. -- `flags`: [`eventrwflags`](#eventrwflags) -The state of the file descriptor. +Bit: 5 -Offset: 8 +- `fd_write`: `bool` +The right to invoke `fd_write` and `sock_send`. +If [`rights::fd_seek`](#rights.fd_seek) is set, includes the right to invoke `fd_pwrite`. -## `event_u`: `Variant` -The contents of an [`event`](#event). +Bit: 6 -Size: 24 +- `fd_advise`: `bool` +The right to invoke `fd_advise`. -Alignment: 8 +Bit: 7 -### Variant Layout -- size: 24 -- align: 8 -- tag_size: 1 -### Variant cases -- `clock` +- `fd_allocate`: `bool` +The right to invoke `fd_allocate`. -- `fd_read`: [`event_fd_readwrite`](#event_fd_readwrite) +Bit: 8 -- `fd_write`: [`event_fd_readwrite`](#event_fd_readwrite) +- `path_create_directory`: `bool` +The right to invoke `path_create_directory`. -## `eventrwflags`: `Record` -The state of the file descriptor subscribed to with -[`eventtype::fd_read`](#eventtype.fd_read) or [`eventtype::fd_write`](#eventtype.fd_write). +Bit: 9 -Size: 2 +- `path_create_file`: `bool` +If `path_open` is set, the right to invoke `path_open` with [`oflags::create`](#oflags.create). -Alignment: 2 +Bit: 10 -### Record members -- `fd_readwrite_hangup`: `bool` -The peer of this socket has closed or disconnected. +- `path_link_source`: `bool` +The right to invoke `path_link` with the file descriptor as the +source directory. -Bit: 0 +Bit: 11 -## `eventtype`: `Variant` -Type of a subscription to an event or its occurrence. +- `path_link_target`: `bool` +The right to invoke `path_link` with the file descriptor as the +target directory. -Size: 1 +Bit: 12 -Alignment: 1 +- `path_open`: `bool` +The right to invoke `path_open`. -### Variant cases -- `clock` -The time value of clock [`subscription_clock::id`](#subscription_clock.id) has -reached timestamp [`subscription_clock::timeout`](#subscription_clock.timeout). +Bit: 13 -- `fd_read` -File descriptor [`subscription_fd_readwrite::fd`](#subscription_fd_readwrite.fd) has data -available for reading. This event always triggers for regular files. +- `fd_readdir`: `bool` +The right to invoke `fd_readdir`. -- `fd_write` -File descriptor [`subscription_fd_readwrite::fd`](#subscription_fd_readwrite.fd) has capacity -available for writing. This event always triggers for regular files. +Bit: 14 -## `exitcode`: `u8` -Exit code generated by a program when exiting. +- `path_readlink`: `bool` +The right to invoke `path_readlink`. -Size: 1 +Bit: 15 -Alignment: 1 +- `path_rename_source`: `bool` +The right to invoke `path_rename` with the file descriptor as the source directory. -## `fd`: `Handle` -A file descriptor handle. +Bit: 16 -Size: 4 +- `path_rename_target`: `bool` +The right to invoke `path_rename` with the file descriptor as the target directory. -Alignment: 4 +Bit: 17 -### Supertypes -## `fdflags`: `Record` -File descriptor flags. +- `path_filestat_get`: `bool` +The right to invoke `path_filestat_get`. -Size: 2 +Bit: 18 -Alignment: 2 +- `path_filestat_set_size`: `bool` +The right to change a file's size. +If `path_open` is set, includes the right to invoke `path_open` with [`oflags::trunc`](#oflags.trunc). +Note: there is no function named `path_filestat_set_size`. This follows POSIX design, +which only has `ftruncate` and does not provide `ftruncateat`. +While such function would be desirable from the API design perspective, there are virtually +no use cases for it since no code written for POSIX systems would use it. +Moreover, implementing it would require multiple syscalls, leading to inferior performance. -### Record members -- `append`: `bool` -Append mode: Data written to the file is always appended to the file's end. +Bit: 19 -Bit: 0 +- `path_filestat_set_times`: `bool` +The right to invoke `path_filestat_set_times`. -- `dsync`: `bool` -Write according to synchronized I/O data integrity completion. Only the data stored in the file is synchronized. +Bit: 20 -Bit: 1 +- `path_permissions_set`: `bool` +The right to invoke `path_permissions_set`. -- `nonblock`: `bool` -Non-blocking mode. +Bit: 21 -Bit: 2 +- `fd_filestat_get`: `bool` +The right to invoke `fd_filestat_get`. -- `rsync`: `bool` -Synchronized read I/O operations. +Bit: 22 -Bit: 3 +- `fd_filestat_set_size`: `bool` +The right to invoke `fd_filestat_set_size`. -- `sync`: `bool` -Write according to synchronized I/O file integrity completion. In -addition to synchronizing the data stored in the file, the implementation -may also synchronously update the file's metadata. +Bit: 23 -Bit: 4 +- `fd_filestat_set_times`: `bool` +The right to invoke `fd_filestat_set_times`. -## `fdstat`: `Record` -File descriptor attributes. +Bit: 24 -Size: 24 +- `fd_permissions_set`: `bool` +The right to invoke `fd_permissions_set`. -Alignment: 8 +Bit: 25 -### Record members -- `fs_filetype`: [`filetype`](#filetype) -File type. +- `path_symlink`: `bool` +The right to invoke `path_symlink`. -Offset: 0 +Bit: 26 -- `fs_flags`: [`fdflags`](#fdflags) -File descriptor flags. +- `path_remove_directory`: `bool` +The right to invoke `path_remove_directory`. -Offset: 2 +Bit: 27 -- `fs_rights_base`: [`rights`](#rights) -Rights that apply to this file descriptor. +- `path_unlink_file`: `bool` +The right to invoke `path_unlink_file`. -Offset: 8 +Bit: 28 -- `fs_rights_inheriting`: [`rights`](#rights) -Maximum set of rights that may be installed on new file descriptors that -are created through this file descriptor, e.g., through `path_open`. +- `poll_fd_readwrite`: `bool` +If [`rights::fd_read`](#rights.fd_read) is set, includes the right to invoke `poll_oneoff` to subscribe to [`eventtype::fd_read`](#eventtype.fd_read). +If [`rights::fd_write`](#rights.fd_write) is set, includes the right to invoke `poll_oneoff` to subscribe to [`eventtype::fd_write`](#eventtype.fd_write). -Offset: 16 +Bit: 29 -## `filedelta`: `s64` -Relative offset within a file. +- `sock_shutdown`: `bool` +The right to invoke `sock_shutdown`. -Size: 8 +Bit: 30 -Alignment: 8 +## `fd`: `Handle` +A file descriptor handle. -## `filesize`: `u64` -Non-negative file size or length of a region within a file. +Size: 4 + +Alignment: 4 + +### Supertypes +## `iovec`: `Record` +A region of memory for scatter/gather reads. Size: 8 -Alignment: 8 +Alignment: 4 -## `filestat`: `Record` -File attributes. +### Record members +- `buf`: `Pointer` +The address of the buffer to be filled. -Size: 64 +Offset: 0 -Alignment: 8 +- `buf_len`: [`size`](#size) +The length of the buffer to be filled. + +Offset: 4 + +## `ciovec`: `Record` +A region of memory for scatter/gather writes. + +Size: 8 + +Alignment: 4 ### Record members -- `dev`: [`device`](#device) -Device ID of device containing the file. +- `buf`: `ConstPointer` +The address of the buffer to be written. Offset: 0 -- `ino`: [`inode`](#inode) -File serial number. +- `buf_len`: [`size`](#size) +The length of the buffer to be written. -Offset: 8 +Offset: 4 -- `filetype`: [`filetype`](#filetype) -File type. +## `iovec_array`: `List` -Offset: 16 +Size: 8 -- `permissions`: [`permissions`](#permissions) -File permissions. +Alignment: 4 -Offset: 17 +## `ciovec_array`: `List` -- `nlink`: [`linkcount`](#linkcount) -Number of hard links to the file. +Size: 8 -Offset: 24 +Alignment: 4 -- `size`: [`filesize`](#filesize) -For regular files, the file size in bytes. For symbolic links, the length in bytes of the pathname contained in the symbolic link. +## `filedelta`: `s64` +Relative offset within a file. -Offset: 32 +Size: 8 -- `atim`: [`timestamp`](#timestamp) -Last data access timestamp. +Alignment: 8 -Offset: 40 +## `whence`: `Variant` +The position relative to which to set the offset of the file descriptor. -- `mtim`: [`timestamp`](#timestamp) -Last data modification timestamp. +Size: 1 -Offset: 48 +Alignment: 1 -- `ctim`: [`timestamp`](#timestamp) -Last file status change timestamp. +### Variant cases +- `set` +Seek relative to start-of-file. -Offset: 56 +- `cur` +Seek relative to current position. + +- `end` +Seek relative to end-of-file. + +## `dircookie`: `u64` +A reference to the offset of a directory entry. + +Size: 8 + +Alignment: 8 + +### Constants +- `start` + +## `dirnamlen`: `u32` +The type for the [`dirent::d_namlen`](#dirent.d_namlen) field of [`dirent`](#dirent). + +Size: 4 + +Alignment: 4 + +## `inode`: `u64` +File serial number that is unique within its file system. + +Size: 8 + +Alignment: 8 ## `filetype`: `Variant` The type of a file descriptor or file. @@ -634,71 +601,159 @@ The file refers to a symbolic link inode. - `fifo` The file descriptor or file refers to a FIFO. -## `fstflags`: `Record` -Which file time attributes to adjust. +## `dirent`: `Record` +A directory entry. + +Size: 24 + +Alignment: 8 + +### Record members +- `d_next`: [`dircookie`](#dircookie) +The offset of the next directory entry stored in this directory. + +Offset: 0 + +- `d_ino`: [`inode`](#inode) +The serial number of the file referred to by this directory entry. + +Offset: 8 + +- `d_type`: [`filetype`](#filetype) +The type of the file referred to by this directory entry. + +Offset: 16 + +- `d_namlen`: [`dirnamlen`](#dirnamlen) +The length of the name of the directory entry. + +Offset: 20 + +## `advice`: `Variant` +File or memory access pattern advisory information. + +Size: 1 + +Alignment: 1 + +### Variant cases +- `normal` +The application has no advice to give on its behavior with respect to the specified data. + +- `sequential` +The application expects to access the specified data sequentially from lower offsets to higher offsets. + +- `random` +The application expects to access the specified data in a random order. + +- `willneed` +The application expects to access the specified data in the near future. + +- `dontneed` +The application expects that it will not access the specified data in the near future. + +- `noreuse` +The application expects to access the specified data once and then not reuse it thereafter. + +## `fdflags`: `Record` +File descriptor flags. Size: 2 Alignment: 2 ### Record members -- `atim`: `bool` -Adjust the last data access timestamp to the value stored in [`filestat::atim`](#filestat.atim). +- `append`: `bool` +Append mode: Data written to the file is always appended to the file's end. Bit: 0 -- `atim_now`: `bool` -Adjust the last data access timestamp to the time of clock [`clockid::realtime`](#clockid.realtime). +- `dsync`: `bool` +Write according to synchronized I/O data integrity completion. Only the data stored in the file is synchronized. Bit: 1 -- `mtim`: `bool` -Adjust the last data modification timestamp to the value stored in [`filestat::mtim`](#filestat.mtim). +- `nonblock`: `bool` +Non-blocking mode. Bit: 2 -- `mtim_now`: `bool` -Adjust the last data modification timestamp to the time of clock [`clockid::realtime`](#clockid.realtime). +- `rsync`: `bool` +Synchronized read I/O operations. Bit: 3 -## `inode`: `u64` -File serial number that is unique within its file system. +- `sync`: `bool` +Write according to synchronized I/O file integrity completion. In +addition to synchronizing the data stored in the file, the implementation +may also synchronously update the file's metadata. -Size: 8 +Bit: 4 + +## `fdstat`: `Record` +File descriptor attributes. + +Size: 24 Alignment: 8 -## `iovec`: `Record` -A region of memory for scatter/gather reads. +### Record members +- `fs_filetype`: [`filetype`](#filetype) +File type. + +Offset: 0 + +- `fs_flags`: [`fdflags`](#fdflags) +File descriptor flags. + +Offset: 2 + +- `fs_rights_base`: [`rights`](#rights) +Rights that apply to this file descriptor. + +Offset: 8 + +- `fs_rights_inheriting`: [`rights`](#rights) +Maximum set of rights that may be installed on new file descriptors that +are created through this file descriptor, e.g., through `path_open`. + +Offset: 16 + +## `device`: `u64` +Identifier for a device containing a file system. Can be used in combination +with [`inode`](#inode) to uniquely identify a file or directory in the filesystem. Size: 8 -Alignment: 4 +Alignment: 8 -### Record members -- `buf`: `Pointer` -The address of the buffer to be filled. +## `fstflags`: `Record` +Which file time attributes to adjust. -Offset: 0 +Size: 2 -- `buf_len`: [`size`](#size) -The length of the buffer to be filled. +Alignment: 2 -Offset: 4 +### Record members +- `atim`: `bool` +Adjust the last data access timestamp to the value stored in [`filestat::atim`](#filestat.atim). -## `iovec_array`: `List` +Bit: 0 -Size: 8 +- `atim_now`: `bool` +Adjust the last data access timestamp to the time of clock [`clockid::realtime`](#clockid.realtime). -Alignment: 4 +Bit: 1 -## `linkcount`: `u64` -Number of hard links to an inode. +- `mtim`: `bool` +Adjust the last data modification timestamp to the value stored in [`filestat::mtim`](#filestat.mtim). -Size: 8 +Bit: 2 -Alignment: 8 +- `mtim_now`: `bool` +Adjust the last data modification timestamp to the time of clock [`clockid::realtime`](#clockid.realtime). + +Bit: 3 ## `lookupflags`: `Record` Flags determining the method of how paths are resolved. @@ -741,6 +796,13 @@ Truncate file to size 0. Bit: 3 +## `linkcount`: `u64` +Number of hard links to an inode. + +Size: 8 + +Alignment: 8 + ## `permissions`: `Record` File permissions. This represents the permissions associated with a file in a filesystem, and don't fully reflect all the conditions @@ -783,290 +845,160 @@ to other "users". Bit: 3 -## `preopentype`: `Variant` -Identifiers for preopened capabilities. - -Size: 1 - -Alignment: 1 - -### Variant cases -- `dir` -A pre-opened directory. - -## `prestat`: `Variant` -Information about a pre-opened capability. - -Size: 8 - -Alignment: 4 - -### Variant Layout -- size: 8 -- align: 4 -- tag_size: 1 -### Variant cases -- `dir`: [`prestat_dir`](#prestat_dir) -When type is [`preopentype::dir`](#preopentype.dir): - -## `prestat_dir`: `Record` -The contents of a [`prestat`](#prestat) when its type is [`preopentype::dir`](#preopentype.dir). - -Size: 4 - -Alignment: 4 - -### Record members -- `pr_name_len`: [`size`](#size) -The length of the directory name for use with `fd_prestat_dir_name`. - -Offset: 0 - -## `riflags`: `Record` -Flags provided to `sock_recv`. - -Size: 2 - -Alignment: 2 - -### Record members -- `recv_peek`: `bool` -Returns the message without removing it from the socket's receive queue. - -Bit: 0 - -- `recv_waitall`: `bool` -On byte-stream sockets, block until the full amount of data can be returned. - -Bit: 1 - -## `rights`: `Record` -File descriptor rights, determining which actions may be performed. +## `filestat`: `Record` +File attributes. -Size: 8 +Size: 64 Alignment: 8 ### Record members -- `fd_datasync`: `bool` -The right to invoke `fd_datasync`. -If `path_open` is set, includes the right to invoke -`path_open` with [`fdflags::dsync`](#fdflags.dsync). - -Bit: 0 - -- `fd_read`: `bool` -The right to invoke `fd_read` and `sock_recv`. -If [`rights::fd_seek`](#rights.fd_seek) is set, includes the right to invoke `fd_pread`. - -Bit: 1 - -- `fd_seek`: `bool` -The right to invoke `fd_seek`. This flag implies [`rights::fd_tell`](#rights.fd_tell). - -Bit: 2 - -- `fd_fdstat_set_flags`: `bool` -The right to invoke `fd_fdstat_set_flags`. - -Bit: 3 - -- `fd_sync`: `bool` -The right to invoke `fd_sync`. -If `path_open` is set, includes the right to invoke -`path_open` with [`fdflags::rsync`](#fdflags.rsync) and [`fdflags::dsync`](#fdflags.dsync). - -Bit: 4 - -- `fd_tell`: `bool` -The right to invoke `fd_seek` in such a way that the file offset -remains unaltered (i.e., [`whence::cur`](#whence.cur) with offset zero), or to -invoke `fd_tell`. - -Bit: 5 - -- `fd_write`: `bool` -The right to invoke `fd_write` and `sock_send`. -If [`rights::fd_seek`](#rights.fd_seek) is set, includes the right to invoke `fd_pwrite`. - -Bit: 6 - -- `fd_advise`: `bool` -The right to invoke `fd_advise`. - -Bit: 7 - -- `fd_allocate`: `bool` -The right to invoke `fd_allocate`. - -Bit: 8 - -- `path_create_directory`: `bool` -The right to invoke `path_create_directory`. - -Bit: 9 - -- `path_create_file`: `bool` -If `path_open` is set, the right to invoke `path_open` with [`oflags::create`](#oflags.create). - -Bit: 10 - -- `path_link_source`: `bool` -The right to invoke `path_link` with the file descriptor as the -source directory. - -Bit: 11 - -- `path_link_target`: `bool` -The right to invoke `path_link` with the file descriptor as the -target directory. - -Bit: 12 - -- `path_open`: `bool` -The right to invoke `path_open`. - -Bit: 13 - -- `fd_readdir`: `bool` -The right to invoke `fd_readdir`. - -Bit: 14 - -- `path_readlink`: `bool` -The right to invoke `path_readlink`. - -Bit: 15 - -- `path_rename_source`: `bool` -The right to invoke `path_rename` with the file descriptor as the source directory. - -Bit: 16 - -- `path_rename_target`: `bool` -The right to invoke `path_rename` with the file descriptor as the target directory. - -Bit: 17 +- `dev`: [`device`](#device) +Device ID of device containing the file. -- `path_filestat_get`: `bool` -The right to invoke `path_filestat_get`. +Offset: 0 -Bit: 18 +- `ino`: [`inode`](#inode) +File serial number. -- `path_filestat_set_size`: `bool` -The right to change a file's size. -If `path_open` is set, includes the right to invoke `path_open` with [`oflags::trunc`](#oflags.trunc). -Note: there is no function named `path_filestat_set_size`. This follows POSIX design, -which only has `ftruncate` and does not provide `ftruncateat`. -While such function would be desirable from the API design perspective, there are virtually -no use cases for it since no code written for POSIX systems would use it. -Moreover, implementing it would require multiple syscalls, leading to inferior performance. +Offset: 8 -Bit: 19 +- `filetype`: [`filetype`](#filetype) +File type. -- `path_filestat_set_times`: `bool` -The right to invoke `path_filestat_set_times`. +Offset: 16 -Bit: 20 +- `permissions`: [`permissions`](#permissions) +File permissions. -- `path_permissions_set`: `bool` -The right to invoke `path_permissions_set`. +Offset: 17 -Bit: 21 +- `nlink`: [`linkcount`](#linkcount) +Number of hard links to the file. -- `fd_filestat_get`: `bool` -The right to invoke `fd_filestat_get`. +Offset: 24 -Bit: 22 +- `size`: [`filesize`](#filesize) +For regular files, the file size in bytes. For symbolic links, the length in bytes of the pathname contained in the symbolic link. -- `fd_filestat_set_size`: `bool` -The right to invoke `fd_filestat_set_size`. +Offset: 32 -Bit: 23 +- `atim`: [`timestamp`](#timestamp) +Last data access timestamp. -- `fd_filestat_set_times`: `bool` -The right to invoke `fd_filestat_set_times`. +Offset: 40 -Bit: 24 +- `mtim`: [`timestamp`](#timestamp) +Last data modification timestamp. -- `fd_permissions_set`: `bool` -The right to invoke `fd_permissions_set`. +Offset: 48 -Bit: 25 +- `ctim`: [`timestamp`](#timestamp) +Last file status change timestamp. -- `path_symlink`: `bool` -The right to invoke `path_symlink`. +Offset: 56 -Bit: 26 +## `userdata`: `u64` +User-provided value that may be attached to objects that is retained when +extracted from the implementation. -- `path_remove_directory`: `bool` -The right to invoke `path_remove_directory`. +Size: 8 -Bit: 27 +Alignment: 8 -- `path_unlink_file`: `bool` -The right to invoke `path_unlink_file`. +## `eventtype`: `Variant` +Type of a subscription to an event or its occurrence. -Bit: 28 +Size: 1 -- `poll_fd_readwrite`: `bool` -If [`rights::fd_read`](#rights.fd_read) is set, includes the right to invoke `poll_oneoff` to subscribe to [`eventtype::fd_read`](#eventtype.fd_read). -If [`rights::fd_write`](#rights.fd_write) is set, includes the right to invoke `poll_oneoff` to subscribe to [`eventtype::fd_write`](#eventtype.fd_write). +Alignment: 1 -Bit: 29 +### Variant cases +- `clock` +The time value of clock [`subscription_clock::id`](#subscription_clock.id) has +reached timestamp [`subscription_clock::timeout`](#subscription_clock.timeout). -- `sock_shutdown`: `bool` -The right to invoke `sock_shutdown`. +- `fd_read` +File descriptor [`subscription_fd_readwrite::fd`](#subscription_fd_readwrite.fd) has data +available for reading. This event always triggers for regular files. -Bit: 30 +- `fd_write` +File descriptor [`subscription_fd_readwrite::fd`](#subscription_fd_readwrite.fd) has capacity +available for writing. This event always triggers for regular files. -## `roflags`: `Record` -Flags returned by `sock_recv`. +## `eventrwflags`: `Record` +The state of the file descriptor subscribed to with +[`eventtype::fd_read`](#eventtype.fd_read) or [`eventtype::fd_write`](#eventtype.fd_write). Size: 2 Alignment: 2 ### Record members -- `recv_data_truncated`: `bool` -Returned by `sock_recv`: Message data has been truncated. +- `fd_readwrite_hangup`: `bool` +The peer of this socket has closed or disconnected. Bit: 0 -## `sdflags`: `Record` -Which channels on a socket to shut down. +## `event_fd_readwrite`: `Record` +The contents of an [`event`](#event) when type is [`eventtype::fd_read`](#eventtype.fd_read) or +[`eventtype::fd_write`](#eventtype.fd_write). -Size: 1 +Size: 16 -Alignment: 1 +Alignment: 8 ### Record members -- `rd`: `bool` -Disables further receive operations. +- `nbytes`: [`filesize`](#filesize) +The number of bytes available for reading or writing. -Bit: 0 +Offset: 0 -- `wr`: `bool` -Disables further send operations. +- `flags`: [`eventrwflags`](#eventrwflags) +The state of the file descriptor. -Bit: 1 +Offset: 8 -## `siflags`: `u16` -Flags provided to `sock_send`. As there are currently no flags -defined, it must be set to zero. +## `event_u`: `Variant` +The contents of an [`event`](#event). -Size: 2 +Size: 24 -Alignment: 2 +Alignment: 8 -## `size`: `usize` -An array size. +### Variant Layout +- size: 24 +- align: 8 +- tag_size: 1 +### Variant cases +- `clock` -Note: This is similar to `size_t` in POSIX. +- `fd_read`: [`event_fd_readwrite`](#event_fd_readwrite) -Size: 4 +- `fd_write`: [`event_fd_readwrite`](#event_fd_readwrite) -Alignment: 4 +## `event`: `Record` +An event that occurred. + +Size: 40 + +Alignment: 8 + +### Record members +- `userdata`: [`userdata`](#userdata) +User-provided value that got attached to [`subscription::userdata`](#subscription.userdata). + +Offset: 0 + +- `error`: [`errno`](#errno) +If non-zero, an error that occurred while processing the subscription request. + +Offset: 8 + +- `u`: [`event_u`](#event_u) +The type of the event that occurred, and the contents of the event + +Offset: 16 ## `subclockflags`: `Record` Flags determining how to interpret the timestamp provided in @@ -1086,25 +1018,6 @@ current time value of clock [`subscription_clock::id`](#subscription_clock.id). Bit: 0 -## `subscription`: `Record` -Subscription to an event. - -Size: 48 - -Alignment: 8 - -### Record members -- `userdata`: [`userdata`](#userdata) -User-provided value that is attached to the subscription in the -implementation and returned through [`event::userdata`](#event.userdata). - -Offset: 0 - -- `u`: [`subscription_u`](#subscription_u) -The type of the event to which to subscribe, and the contents of the subscription. - -Offset: 8 - ## `subscription_clock`: `Record` The contents of a [`subscription`](#subscription) when type is [`eventtype::clock`](#eventtype.clock). @@ -1166,40 +1079,137 @@ Alignment: 8 - `fd_write`: [`subscription_fd_readwrite`](#subscription_fd_readwrite) -## `timestamp`: `u64` -Timestamp in nanoseconds. +## `subscription`: `Record` +Subscription to an event. -Size: 8 +Size: 48 Alignment: 8 -## `userdata`: `u64` -User-provided value that may be attached to objects that is retained when -extracted from the implementation. +### Record members +- `userdata`: [`userdata`](#userdata) +User-provided value that is attached to the subscription in the +implementation and returned through [`event::userdata`](#event.userdata). -Size: 8 +Offset: 0 -Alignment: 8 +- `u`: [`subscription_u`](#subscription_u) +The type of the event to which to subscribe, and the contents of the subscription. -## `whence`: `Variant` -The position relative to which to set the offset of the file descriptor. +Offset: 8 + +## `exitcode`: `u8` +Exit code generated by a program when exiting. + +Size: 1 + +Alignment: 1 + +### Constants +- `success` + +- `failure` + +## `riflags`: `Record` +Flags provided to `sock_recv`. + +Size: 2 + +Alignment: 2 + +### Record members +- `recv_peek`: `bool` +Returns the message without removing it from the socket's receive queue. + +Bit: 0 + +- `recv_waitall`: `bool` +On byte-stream sockets, block until the full amount of data can be returned. + +Bit: 1 + +## `roflags`: `Record` +Flags returned by `sock_recv`. + +Size: 2 + +Alignment: 2 + +### Record members +- `recv_data_truncated`: `bool` +Returned by `sock_recv`: Message data has been truncated. + +Bit: 0 + +## `siflags`: `u16` +Flags provided to `sock_send`. As there are currently no flags +defined, it must be set to zero. + +Size: 2 + +Alignment: 2 + +## `sdflags`: `Record` +Which channels on a socket to shut down. + +Size: 1 + +Alignment: 1 + +### Record members +- `rd`: `bool` +Disables further receive operations. + +Bit: 0 + +- `wr`: `bool` +Disables further send operations. + +Bit: 1 + +## `preopentype`: `Variant` +Identifiers for preopened capabilities. Size: 1 Alignment: 1 ### Variant cases -- `set` -Seek relative to start-of-file. +- `dir` +A pre-opened directory. -- `cur` -Seek relative to current position. +## `prestat_dir`: `Record` +The contents of a [`prestat`](#prestat) when its type is [`preopentype::dir`](#preopentype.dir). -- `end` -Seek relative to end-of-file. +Size: 4 + +Alignment: 4 + +### Record members +- `pr_name_len`: [`size`](#size) +The length of the directory name for use with `fd_prestat_dir_name`. + +Offset: 0 + +## `prestat`: `Variant` +Information about a pre-opened capability. + +Size: 8 + +Alignment: 4 + +### Variant Layout +- size: 8 +- align: 4 +- tag_size: 1 +### Variant cases +- `dir`: [`prestat_dir`](#prestat_dir) +When type is [`preopentype::dir`](#preopentype.dir): # Modules ## wasi_ephemeral_args +### Imports +#### Memory ### Functions --- @@ -1257,6 +1267,8 @@ Offset: 4 - `err`: [`errno`](#errno) ## wasi_ephemeral_clock +### Imports +#### Memory ### Functions --- @@ -1312,6 +1324,8 @@ The time value of the clock. - `err`: [`errno`](#errno) ## wasi_ephemeral_environ +### Imports +#### Memory ### Functions --- @@ -1369,6 +1383,8 @@ Offset: 4 - `err`: [`errno`](#errno) ## wasi_ephemeral_fd +### Imports +#### Memory ### Functions --- @@ -1971,6 +1987,8 @@ The number of bytes written. - `err`: [`errno`](#errno) ## wasi_ephemeral_path +### Imports +#### Memory ### Functions --- @@ -2335,6 +2353,8 @@ The path to a file to unlink. - `err`: [`errno`](#errno) ## wasi_ephemeral_poll +### Imports +#### Memory ### Functions --- @@ -2368,6 +2388,7 @@ The number of events stored. - `err`: [`errno`](#errno) ## wasi_ephemeral_proc +### Imports ### Functions --- @@ -2386,6 +2407,8 @@ The exit code returned by the process. ##### Results ## wasi_ephemeral_random +### Imports +#### Memory ### Functions --- @@ -2417,6 +2440,7 @@ The buffer to fill with random data. - `err`: [`errno`](#errno) ## wasi_ephemeral_sched +### Imports ### Functions --- @@ -2439,6 +2463,8 @@ Note: This is similar to [`yield`](#yield) in POSIX. - `err`: [`errno`](#errno) ## wasi_ephemeral_sock +### Imports +#### Memory ### Functions --- diff --git a/phases/ephemeral/witx/wasi_ephemeral_args.witx b/phases/ephemeral/witx/wasi_ephemeral_args.witx index 92a38512d..bb956fc53 100644 --- a/phases/ephemeral/witx/wasi_ephemeral_args.witx +++ b/phases/ephemeral/witx/wasi_ephemeral_args.witx @@ -3,9 +3,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 $errno $size from $typenames) +(use "typenames.witx") (module $wasi_ephemeral_args + ;;; Linear memory to be accessed by WASI functions that need it. + (import "memory" (memory)) + ;;; 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..a980529f8 100644 --- a/phases/ephemeral/witx/wasi_ephemeral_clock.witx +++ b/phases/ephemeral/witx/wasi_ephemeral_clock.witx @@ -5,9 +5,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 $clockid $timestamp $errno from $typenames) +(use "typenames.witx") (module $wasi_ephemeral_clock + ;;; Linear memory to be accessed by WASI functions that need it. + (import "memory" (memory)) + ;;; 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..ef1c20b22 100644 --- a/phases/ephemeral/witx/wasi_ephemeral_environ.witx +++ b/phases/ephemeral/witx/wasi_ephemeral_environ.witx @@ -3,9 +3,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 $errno $size from $typenames) +(use "typenames.witx") (module $wasi_ephemeral_environ + ;;; Linear memory to be accessed by WASI functions that need it. + (import "memory" (memory)) + ;;; 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..979e9a333 100644 --- a/phases/ephemeral/witx/wasi_ephemeral_fd.witx +++ b/phases/ephemeral/witx/wasi_ephemeral_fd.witx @@ -5,9 +5,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) +(use "typenames.witx") (module $wasi_ephemeral_fd + ;;; Linear memory to be accessed by WASI functions that need it. + (import "memory" (memory)) + ;;; 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..2901decb7 100644 --- a/phases/ephemeral/witx/wasi_ephemeral_path.witx +++ b/phases/ephemeral/witx/wasi_ephemeral_path.witx @@ -5,9 +5,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) +(use "typenames.witx") (module $wasi_ephemeral_path + ;;; Linear memory to be accessed by WASI functions that need it. + (import "memory" (memory)) + ;;; 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..08da5f03a 100644 --- a/phases/ephemeral/witx/wasi_ephemeral_poll.witx +++ b/phases/ephemeral/witx/wasi_ephemeral_poll.witx @@ -5,9 +5,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 $event $subscription $size $errno from $typenames) +(use "typenames.witx") (module $wasi_ephemeral_poll + ;;; Linear memory to be accessed by WASI functions that need it. + (import "memory" (memory)) + ;;; 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..0bd57682d 100644 --- a/phases/ephemeral/witx/wasi_ephemeral_proc.witx +++ b/phases/ephemeral/witx/wasi_ephemeral_proc.witx @@ -5,7 +5,7 @@ ;; 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) +(use "typenames.witx") (module $wasi_ephemeral_proc ;;; Terminate the process normally. An exit code of `$exitcode::success` diff --git a/phases/ephemeral/witx/wasi_ephemeral_random.witx b/phases/ephemeral/witx/wasi_ephemeral_random.witx index 29149daea..4dc8e1a47 100644 --- a/phases/ephemeral/witx/wasi_ephemeral_random.witx +++ b/phases/ephemeral/witx/wasi_ephemeral_random.witx @@ -5,9 +5,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 $errno $size from $typenames) +(use "typenames.witx") (module $wasi_ephemeral_random + ;;; Linear memory to be accessed by WASI functions that need it. + (import "memory" (memory)) + ;;; 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..3b70856dc 100644 --- a/phases/ephemeral/witx/wasi_ephemeral_sched.witx +++ b/phases/ephemeral/witx/wasi_ephemeral_sched.witx @@ -5,7 +5,7 @@ ;; 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) +(use "typenames.witx") (module $wasi_ephemeral_sched ;;; Temporarily yield execution of the calling thread. diff --git a/phases/ephemeral/witx/wasi_ephemeral_sock.witx b/phases/ephemeral/witx/wasi_ephemeral_sock.witx index ce6803781..a05b02ea6 100644 --- a/phases/ephemeral/witx/wasi_ephemeral_sock.witx +++ b/phases/ephemeral/witx/wasi_ephemeral_sock.witx @@ -5,9 +5,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) +(use "typenames.witx") (module $wasi_ephemeral_sock + ;;; Linear memory to be accessed by WASI functions that need it. + (import "memory" (memory)) + ;;; 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/docs.md b/phases/old/snapshot_0/docs.md index 2585a0db8..634e304b3 100644 --- a/phases/old/snapshot_0/docs.md +++ b/phases/old/snapshot_0/docs.md @@ -1,53 +1,23 @@ # Types -## `advice`: `Variant` -File or memory access pattern advisory information. - -Size: 1 - -Alignment: 1 - -### Variant cases -- `normal` -The application has no advice to give on its behavior with respect to the specified data. - -- `sequential` -The application expects to access the specified data sequentially from lower offsets to higher offsets. - -- `random` -The application expects to access the specified data in a random order. - -- `willneed` -The application expects to access the specified data in the near future. - -- `dontneed` -The application expects that it will not access the specified data in the near future. - -- `noreuse` -The application expects to access the specified data once and then not reuse it thereafter. - -## `ciovec`: `Record` -A region of memory for scatter/gather writes. +## `size`: `u32` -Size: 8 +Size: 4 Alignment: 4 -### Record members -- `buf`: `ConstPointer` -The address of the buffer to be written. - -Offset: 0 +## `filesize`: `u64` +Non-negative file size or length of a region within a file. -- `buf_len`: [`size`](#size) -The length of the buffer to be written. +Size: 8 -Offset: 4 +Alignment: 8 -## `ciovec_array`: `List` +## `timestamp`: `u64` +Timestamp in nanoseconds. Size: 8 -Alignment: 4 +Alignment: 8 ## `clockid`: `Variant` Identifiers for clocks. @@ -73,56 +43,6 @@ The CPU-time clock associated with the current process. - `thread_cputime_id` The CPU-time clock associated with the current thread. -## `device`: `u64` -Identifier for a device containing a file system. Can be used in combination -with [`inode`](#inode) to uniquely identify a file or directory in the filesystem. - -Size: 8 - -Alignment: 8 - -## `dircookie`: `u64` -A reference to the offset of a directory entry. - -Size: 8 - -Alignment: 8 - -## `dirent`: `Record` -A directory entry. - -Size: 24 - -Alignment: 8 - -### Record members -- `d_next`: [`dircookie`](#dircookie) -The offset of the next directory entry stored in this directory. - -Offset: 0 - -- `d_ino`: [`inode`](#inode) -The serial number of the file referred to by this directory entry. - -Offset: 8 - -- `d_namlen`: [`dirnamlen`](#dirnamlen) -The length of the name of the directory entry. - -Offset: 16 - -- `d_type`: [`filetype`](#filetype) -The type of the file referred to by this directory entry. - -Offset: 20 - -## `dirnamlen`: `u32` -The type for the [`dirent::d_namlen`](#dirent.d_namlen) field of [`dirent`](#dirent) struct. - -Size: 4 - -Alignment: 4 - ## `errno`: `Variant` Error codes returned by functions. Not all of these error codes are returned by the functions provided by this @@ -365,228 +285,271 @@ Cross-device link. - `notcapable` Extension: Capabilities insufficient. -## `event`: `Record` -An event that occurred. +## `rights`: `Record` +File descriptor rights, determining which actions may be performed. -Size: 32 +Size: 8 Alignment: 8 ### Record members -- `userdata`: [`userdata`](#userdata) -User-provided value that got attached to [`subscription::userdata`](#subscription.userdata). +- `fd_datasync`: `bool` +The right to invoke [`fd_datasync`](#fd_datasync). +If [`rights::path_open`](#rights.path_open) is set, includes the right to invoke +[`path_open`](#path_open) with [`fdflags::dsync`](#fdflags.dsync). -Offset: 0 +Bit: 0 -- `error`: [`errno`](#errno) -If non-zero, an error that occurred while processing the subscription request. +- `fd_read`: `bool` +The right to invoke [`fd_read`](#fd_read) and [`sock_recv`](#sock_recv). +If [`rights::fd_seek`](#rights.fd_seek) is set, includes the right to invoke [`fd_pread`](#fd_pread). -Offset: 8 +Bit: 1 -- `type`: [`eventtype`](#eventtype) -The type of event that occured +- `fd_seek`: `bool` +The right to invoke [`fd_seek`](#fd_seek). This flag implies [`rights::fd_tell`](#rights.fd_tell). -Offset: 10 +Bit: 2 -- `fd_readwrite`: [`event_fd_readwrite`](#event_fd_readwrite) -The contents of the event, if it is an [`eventtype::fd_read`](#eventtype.fd_read) or -[`eventtype::fd_write`](#eventtype.fd_write). [`eventtype::clock`](#eventtype.clock) events ignore this field. +- `fd_fdstat_set_flags`: `bool` +The right to invoke [`fd_fdstat_set_flags`](#fd_fdstat_set_flags). -Offset: 16 +Bit: 3 -## `event_fd_readwrite`: `Record` -The contents of an [`event`](#event) for the [`eventtype::fd_read`](#eventtype.fd_read) and -[`eventtype::fd_write`](#eventtype.fd_write) variants +- `fd_sync`: `bool` +The right to invoke [`fd_sync`](#fd_sync). +If [`rights::path_open`](#rights.path_open) is set, includes the right to invoke +[`path_open`](#path_open) with [`fdflags::rsync`](#fdflags.rsync) and [`fdflags::dsync`](#fdflags.dsync). -Size: 16 +Bit: 4 -Alignment: 8 +- `fd_tell`: `bool` +The right to invoke [`fd_seek`](#fd_seek) in such a way that the file offset +remains unaltered (i.e., [`whence::cur`](#whence.cur) with offset zero), or to +invoke [`fd_tell`](#fd_tell). -### Record members -- `nbytes`: [`filesize`](#filesize) -The number of bytes available for reading or writing. +Bit: 5 -Offset: 0 +- `fd_write`: `bool` +The right to invoke [`fd_write`](#fd_write) and [`sock_send`](#sock_send). +If [`rights::fd_seek`](#rights.fd_seek) is set, includes the right to invoke [`fd_pwrite`](#fd_pwrite). -- `flags`: [`eventrwflags`](#eventrwflags) -The state of the file descriptor. +Bit: 6 -Offset: 8 +- `fd_advise`: `bool` +The right to invoke [`fd_advise`](#fd_advise). -## `eventrwflags`: `Record` -The state of the file descriptor subscribed to with -[`eventtype::fd_read`](#eventtype.fd_read) or [`eventtype::fd_write`](#eventtype.fd_write). +Bit: 7 -Size: 2 +- `fd_allocate`: `bool` +The right to invoke [`fd_allocate`](#fd_allocate). -Alignment: 2 +Bit: 8 -### Record members -- `fd_readwrite_hangup`: `bool` -The peer of this socket has closed or disconnected. +- `path_create_directory`: `bool` +The right to invoke [`path_create_directory`](#path_create_directory). -Bit: 0 +Bit: 9 -## `eventtype`: `Variant` -Type of a subscription to an event or its occurrence. +- `path_create_file`: `bool` +If [`rights::path_open`](#rights.path_open) is set, the right to invoke [`path_open`](#path_open) with [`oflags::creat`](#oflags.creat). -Size: 1 +Bit: 10 -Alignment: 1 +- `path_link_source`: `bool` +The right to invoke [`path_link`](#path_link) with the file descriptor as the +source directory. -### Variant cases -- `clock` -The time value of clock [`subscription_clock::id`](#subscription_clock.id) has -reached timestamp [`subscription_clock::timeout`](#subscription_clock.timeout). +Bit: 11 -- `fd_read` -File descriptor [`subscription_fd_readwrite::file_descriptor`](#subscription_fd_readwrite.file_descriptor) has data -available for reading. This event always triggers for regular files. +- `path_link_target`: `bool` +The right to invoke [`path_link`](#path_link) with the file descriptor as the +target directory. -- `fd_write` -File descriptor [`subscription_fd_readwrite::file_descriptor`](#subscription_fd_readwrite.file_descriptor) has capacity -available for writing. This event always triggers for regular files. +Bit: 12 -## `exitcode`: `u32` -Exit code generated by a process when exiting. +- `path_open`: `bool` +The right to invoke [`path_open`](#path_open). -Size: 4 +Bit: 13 -Alignment: 4 +- `fd_readdir`: `bool` +The right to invoke [`fd_readdir`](#fd_readdir). -## `fd`: `Handle` -A file descriptor handle. +Bit: 14 -Size: 4 +- `path_readlink`: `bool` +The right to invoke [`path_readlink`](#path_readlink). -Alignment: 4 +Bit: 15 -### Supertypes -## `fdflags`: `Record` -File descriptor flags. +- `path_rename_source`: `bool` +The right to invoke [`path_rename`](#path_rename) with the file descriptor as the source directory. -Size: 2 +Bit: 16 -Alignment: 2 +- `path_rename_target`: `bool` +The right to invoke [`path_rename`](#path_rename) with the file descriptor as the target directory. -### Record members -- `append`: `bool` -Append mode: Data written to the file is always appended to the file's end. +Bit: 17 -Bit: 0 +- `path_filestat_get`: `bool` +The right to invoke [`path_filestat_get`](#path_filestat_get). -- `dsync`: `bool` -Write according to synchronized I/O data integrity completion. Only the data stored in the file is synchronized. +Bit: 18 -Bit: 1 +- `path_filestat_set_size`: `bool` +The right to change a file's size (there is no `path_filestat_set_size`). +If [`rights::path_open`](#rights.path_open) is set, includes the right to invoke [`path_open`](#path_open) with [`oflags::trunc`](#oflags.trunc). -- `nonblock`: `bool` -Non-blocking mode. +Bit: 19 -Bit: 2 +- `path_filestat_set_times`: `bool` +The right to invoke [`path_filestat_set_times`](#path_filestat_set_times). -- `rsync`: `bool` -Synchronized read I/O operations. +Bit: 20 -Bit: 3 +- `fd_filestat_get`: `bool` +The right to invoke [`fd_filestat_get`](#fd_filestat_get). -- `sync`: `bool` -Write according to synchronized I/O file integrity completion. In -addition to synchronizing the data stored in the file, the implementation -may also synchronously update the file's metadata. +Bit: 21 -Bit: 4 +- `fd_filestat_set_size`: `bool` +The right to invoke [`fd_filestat_set_size`](#fd_filestat_set_size). -## `fdstat`: `Record` -File descriptor attributes. +Bit: 22 -Size: 24 +- `fd_filestat_set_times`: `bool` +The right to invoke [`fd_filestat_set_times`](#fd_filestat_set_times). -Alignment: 8 +Bit: 23 + +- `path_symlink`: `bool` +The right to invoke [`path_symlink`](#path_symlink). + +Bit: 24 + +- `path_remove_directory`: `bool` +The right to invoke [`path_remove_directory`](#path_remove_directory). + +Bit: 25 + +- `path_unlink_file`: `bool` +The right to invoke [`path_unlink_file`](#path_unlink_file). + +Bit: 26 + +- `poll_fd_readwrite`: `bool` +If [`rights::fd_read`](#rights.fd_read) is set, includes the right to invoke [`poll_oneoff`](#poll_oneoff) to subscribe to [`eventtype::fd_read`](#eventtype.fd_read). +If [`rights::fd_write`](#rights.fd_write) is set, includes the right to invoke [`poll_oneoff`](#poll_oneoff) to subscribe to [`eventtype::fd_write`](#eventtype.fd_write). + +Bit: 27 + +- `sock_shutdown`: `bool` +The right to invoke [`sock_shutdown`](#sock_shutdown). + +Bit: 28 + +## `fd`: `Handle` +A file descriptor handle. + +Size: 4 + +Alignment: 4 + +### Supertypes +## `iovec`: `Record` +A region of memory for scatter/gather reads. + +Size: 8 + +Alignment: 4 ### Record members -- `fs_filetype`: [`filetype`](#filetype) -File type. +- `buf`: `Pointer` +The address of the buffer to be filled. Offset: 0 -- `fs_flags`: [`fdflags`](#fdflags) -File descriptor flags. +- `buf_len`: [`size`](#size) +The length of the buffer to be filled. -Offset: 2 +Offset: 4 -- `fs_rights_base`: [`rights`](#rights) -Rights that apply to this file descriptor. +## `ciovec`: `Record` +A region of memory for scatter/gather writes. -Offset: 8 +Size: 8 -- `fs_rights_inheriting`: [`rights`](#rights) -Maximum set of rights that may be installed on new file descriptors that -are created through this file descriptor, e.g., through [`path_open`](#path_open). +Alignment: 4 -Offset: 16 +### Record members +- `buf`: `ConstPointer` +The address of the buffer to be written. -## `filedelta`: `s64` -Relative offset within a file. +Offset: 0 + +- `buf_len`: [`size`](#size) +The length of the buffer to be written. + +Offset: 4 + +## `iovec_array`: `List` Size: 8 -Alignment: 8 +Alignment: 4 -## `filesize`: `u64` -Non-negative file size or length of a region within a file. +## `ciovec_array`: `List` Size: 8 -Alignment: 8 +Alignment: 4 -## `filestat`: `Record` -File attributes. +## `filedelta`: `s64` +Relative offset within a file. -Size: 56 +Size: 8 Alignment: 8 -### Record members -- `dev`: [`device`](#device) -Device ID of device containing the file. - -Offset: 0 +## `whence`: `Variant` +The position relative to which to set the offset of the file descriptor. -- `ino`: [`inode`](#inode) -File serial number. +Size: 1 -Offset: 8 +Alignment: 1 -- `filetype`: [`filetype`](#filetype) -File type. +### Variant cases +- `cur` +Seek relative to current position. -Offset: 16 +- `end` +Seek relative to end-of-file. -- `nlink`: [`linkcount`](#linkcount) -Number of hard links to the file. +- `set` +Seek relative to start-of-file. -Offset: 20 +## `dircookie`: `u64` +A reference to the offset of a directory entry. -- `size`: [`filesize`](#filesize) -For regular files, the file size in bytes. For symbolic links, the length in bytes of the pathname contained in the symbolic link. +Size: 8 -Offset: 24 +Alignment: 8 -- `atim`: [`timestamp`](#timestamp) -Last data access timestamp. +## `dirnamlen`: `u32` +The type for the [`dirent::d_namlen`](#dirent.d_namlen) field of [`dirent`](#dirent) struct. -Offset: 32 +Size: 4 -- `mtim`: [`timestamp`](#timestamp) -Last data modification timestamp. +Alignment: 4 -Offset: 40 +## `inode`: `u64` +File serial number that is unique within its file system. -- `ctim`: [`timestamp`](#timestamp) -Last file status change timestamp. +Size: 8 -Offset: 48 +Alignment: 8 ## `filetype`: `Variant` The type of a file descriptor or file. @@ -617,8 +580,134 @@ The file descriptor or file refers to a datagram socket. - `socket_stream` The file descriptor or file refers to a byte-stream socket. -- `symbolic_link` -The file refers to a symbolic link inode. +- `symbolic_link` +The file refers to a symbolic link inode. + +## `dirent`: `Record` +A directory entry. + +Size: 24 + +Alignment: 8 + +### Record members +- `d_next`: [`dircookie`](#dircookie) +The offset of the next directory entry stored in this directory. + +Offset: 0 + +- `d_ino`: [`inode`](#inode) +The serial number of the file referred to by this directory entry. + +Offset: 8 + +- `d_namlen`: [`dirnamlen`](#dirnamlen) +The length of the name of the directory entry. + +Offset: 16 + +- `d_type`: [`filetype`](#filetype) +The type of the file referred to by this directory entry. + +Offset: 20 + +## `advice`: `Variant` +File or memory access pattern advisory information. + +Size: 1 + +Alignment: 1 + +### Variant cases +- `normal` +The application has no advice to give on its behavior with respect to the specified data. + +- `sequential` +The application expects to access the specified data sequentially from lower offsets to higher offsets. + +- `random` +The application expects to access the specified data in a random order. + +- `willneed` +The application expects to access the specified data in the near future. + +- `dontneed` +The application expects that it will not access the specified data in the near future. + +- `noreuse` +The application expects to access the specified data once and then not reuse it thereafter. + +## `fdflags`: `Record` +File descriptor flags. + +Size: 2 + +Alignment: 2 + +### Record members +- `append`: `bool` +Append mode: Data written to the file is always appended to the file's end. + +Bit: 0 + +- `dsync`: `bool` +Write according to synchronized I/O data integrity completion. Only the data stored in the file is synchronized. + +Bit: 1 + +- `nonblock`: `bool` +Non-blocking mode. + +Bit: 2 + +- `rsync`: `bool` +Synchronized read I/O operations. + +Bit: 3 + +- `sync`: `bool` +Write according to synchronized I/O file integrity completion. In +addition to synchronizing the data stored in the file, the implementation +may also synchronously update the file's metadata. + +Bit: 4 + +## `fdstat`: `Record` +File descriptor attributes. + +Size: 24 + +Alignment: 8 + +### Record members +- `fs_filetype`: [`filetype`](#filetype) +File type. + +Offset: 0 + +- `fs_flags`: [`fdflags`](#fdflags) +File descriptor flags. + +Offset: 2 + +- `fs_rights_base`: [`rights`](#rights) +Rights that apply to this file descriptor. + +Offset: 8 + +- `fs_rights_inheriting`: [`rights`](#rights) +Maximum set of rights that may be installed on new file descriptors that +are created through this file descriptor, e.g., through [`path_open`](#path_open). + +Offset: 16 + +## `device`: `u64` +Identifier for a device containing a file system. Can be used in combination +with [`inode`](#inode) to uniquely identify a file or directory in the filesystem. + +Size: 8 + +Alignment: 8 ## `fstflags`: `Record` Which file time attributes to adjust. @@ -648,44 +737,6 @@ Adjust the last data modification timestamp to the time of clock [`clockid::real Bit: 3 -## `inode`: `u64` -File serial number that is unique within its file system. - -Size: 8 - -Alignment: 8 - -## `iovec`: `Record` -A region of memory for scatter/gather reads. - -Size: 8 - -Alignment: 4 - -### Record members -- `buf`: `Pointer` -The address of the buffer to be filled. - -Offset: 0 - -- `buf_len`: [`size`](#size) -The length of the buffer to be filled. - -Offset: 4 - -## `iovec_array`: `List` - -Size: 8 - -Alignment: 4 - -## `linkcount`: `u32` -Number of hard links to an inode. - -Size: 4 - -Alignment: 4 - ## `lookupflags`: `Record` Flags determining the method of how paths are resolved. @@ -727,265 +778,260 @@ Truncate file to size 0. Bit: 3 -## `preopentype`: `Variant` -Identifiers for preopened capabilities. - -Size: 1 - -Alignment: 1 - -### Variant cases -- `dir` -A pre-opened directory. - -## `prestat`: `Variant` -Information about a pre-opened capability. +## `linkcount`: `u32` +Number of hard links to an inode. -Size: 8 +Size: 4 Alignment: 4 -### Variant Layout -- size: 8 -- align: 4 -- tag_size: 1 -### Variant cases -- `dir`: [`prestat_dir`](#prestat_dir) - -## `prestat_dir`: `Record` -The contents of a $prestat when type is [`preopentype::dir`](#preopentype.dir). +## `filestat`: `Record` +File attributes. -Size: 4 +Size: 56 -Alignment: 4 +Alignment: 8 ### Record members -- `pr_name_len`: [`size`](#size) -The length of the directory name for use with [`fd_prestat_dir_name`](#fd_prestat_dir_name). +- `dev`: [`device`](#device) +Device ID of device containing the file. Offset: 0 -## `riflags`: `Record` -Flags provided to [`sock_recv`](#sock_recv). +- `ino`: [`inode`](#inode) +File serial number. -Size: 2 +Offset: 8 -Alignment: 2 +- `filetype`: [`filetype`](#filetype) +File type. -### Record members -- `recv_peek`: `bool` -Returns the message without removing it from the socket's receive queue. +Offset: 16 -Bit: 0 +- `nlink`: [`linkcount`](#linkcount) +Number of hard links to the file. -- `recv_waitall`: `bool` -On byte-stream sockets, block until the full amount of data can be returned. +Offset: 20 -Bit: 1 +- `size`: [`filesize`](#filesize) +For regular files, the file size in bytes. For symbolic links, the length in bytes of the pathname contained in the symbolic link. -## `rights`: `Record` -File descriptor rights, determining which actions may be performed. +Offset: 24 -Size: 8 +- `atim`: [`timestamp`](#timestamp) +Last data access timestamp. -Alignment: 8 +Offset: 32 -### Record members -- `fd_datasync`: `bool` -The right to invoke [`fd_datasync`](#fd_datasync). -If [`rights::path_open`](#rights.path_open) is set, includes the right to invoke -[`path_open`](#path_open) with [`fdflags::dsync`](#fdflags.dsync). +- `mtim`: [`timestamp`](#timestamp) +Last data modification timestamp. -Bit: 0 +Offset: 40 -- `fd_read`: `bool` -The right to invoke [`fd_read`](#fd_read) and [`sock_recv`](#sock_recv). -If [`rights::fd_seek`](#rights.fd_seek) is set, includes the right to invoke [`fd_pread`](#fd_pread). +- `ctim`: [`timestamp`](#timestamp) +Last file status change timestamp. -Bit: 1 +Offset: 48 -- `fd_seek`: `bool` -The right to invoke [`fd_seek`](#fd_seek). This flag implies [`rights::fd_tell`](#rights.fd_tell). +## `userdata`: `u64` +User-provided value that may be attached to objects that is retained when +extracted from the implementation. -Bit: 2 +Size: 8 -- `fd_fdstat_set_flags`: `bool` -The right to invoke [`fd_fdstat_set_flags`](#fd_fdstat_set_flags). +Alignment: 8 -Bit: 3 +## `eventtype`: `Variant` +Type of a subscription to an event or its occurrence. -- `fd_sync`: `bool` -The right to invoke [`fd_sync`](#fd_sync). -If [`rights::path_open`](#rights.path_open) is set, includes the right to invoke -[`path_open`](#path_open) with [`fdflags::rsync`](#fdflags.rsync) and [`fdflags::dsync`](#fdflags.dsync). +Size: 1 -Bit: 4 +Alignment: 1 -- `fd_tell`: `bool` -The right to invoke [`fd_seek`](#fd_seek) in such a way that the file offset -remains unaltered (i.e., [`whence::cur`](#whence.cur) with offset zero), or to -invoke [`fd_tell`](#fd_tell). +### Variant cases +- `clock` +The time value of clock [`subscription_clock::id`](#subscription_clock.id) has +reached timestamp [`subscription_clock::timeout`](#subscription_clock.timeout). -Bit: 5 +- `fd_read` +File descriptor [`subscription_fd_readwrite::file_descriptor`](#subscription_fd_readwrite.file_descriptor) has data +available for reading. This event always triggers for regular files. -- `fd_write`: `bool` -The right to invoke [`fd_write`](#fd_write) and [`sock_send`](#sock_send). -If [`rights::fd_seek`](#rights.fd_seek) is set, includes the right to invoke [`fd_pwrite`](#fd_pwrite). +- `fd_write` +File descriptor [`subscription_fd_readwrite::file_descriptor`](#subscription_fd_readwrite.file_descriptor) has capacity +available for writing. This event always triggers for regular files. -Bit: 6 +## `eventrwflags`: `Record` +The state of the file descriptor subscribed to with +[`eventtype::fd_read`](#eventtype.fd_read) or [`eventtype::fd_write`](#eventtype.fd_write). -- `fd_advise`: `bool` -The right to invoke [`fd_advise`](#fd_advise). +Size: 2 -Bit: 7 +Alignment: 2 -- `fd_allocate`: `bool` -The right to invoke [`fd_allocate`](#fd_allocate). +### Record members +- `fd_readwrite_hangup`: `bool` +The peer of this socket has closed or disconnected. -Bit: 8 +Bit: 0 -- `path_create_directory`: `bool` -The right to invoke [`path_create_directory`](#path_create_directory). +## `event_fd_readwrite`: `Record` +The contents of an [`event`](#event) for the [`eventtype::fd_read`](#eventtype.fd_read) and +[`eventtype::fd_write`](#eventtype.fd_write) variants -Bit: 9 +Size: 16 -- `path_create_file`: `bool` -If [`rights::path_open`](#rights.path_open) is set, the right to invoke [`path_open`](#path_open) with [`oflags::creat`](#oflags.creat). +Alignment: 8 -Bit: 10 +### Record members +- `nbytes`: [`filesize`](#filesize) +The number of bytes available for reading or writing. -- `path_link_source`: `bool` -The right to invoke [`path_link`](#path_link) with the file descriptor as the -source directory. +Offset: 0 -Bit: 11 +- `flags`: [`eventrwflags`](#eventrwflags) +The state of the file descriptor. -- `path_link_target`: `bool` -The right to invoke [`path_link`](#path_link) with the file descriptor as the -target directory. +Offset: 8 -Bit: 12 +## `event`: `Record` +An event that occurred. -- `path_open`: `bool` -The right to invoke [`path_open`](#path_open). +Size: 32 -Bit: 13 +Alignment: 8 -- `fd_readdir`: `bool` -The right to invoke [`fd_readdir`](#fd_readdir). +### Record members +- `userdata`: [`userdata`](#userdata) +User-provided value that got attached to [`subscription::userdata`](#subscription.userdata). -Bit: 14 +Offset: 0 -- `path_readlink`: `bool` -The right to invoke [`path_readlink`](#path_readlink). +- `error`: [`errno`](#errno) +If non-zero, an error that occurred while processing the subscription request. -Bit: 15 +Offset: 8 -- `path_rename_source`: `bool` -The right to invoke [`path_rename`](#path_rename) with the file descriptor as the source directory. +- `type`: [`eventtype`](#eventtype) +The type of event that occured -Bit: 16 +Offset: 10 -- `path_rename_target`: `bool` -The right to invoke [`path_rename`](#path_rename) with the file descriptor as the target directory. +- `fd_readwrite`: [`event_fd_readwrite`](#event_fd_readwrite) +The contents of the event, if it is an [`eventtype::fd_read`](#eventtype.fd_read) or +[`eventtype::fd_write`](#eventtype.fd_write). [`eventtype::clock`](#eventtype.clock) events ignore this field. -Bit: 17 +Offset: 16 -- `path_filestat_get`: `bool` -The right to invoke [`path_filestat_get`](#path_filestat_get). +## `subclockflags`: `Record` +Flags determining how to interpret the timestamp provided in +[`subscription_clock::timeout`](#subscription_clock.timeout). -Bit: 18 +Size: 2 -- `path_filestat_set_size`: `bool` -The right to change a file's size (there is no `path_filestat_set_size`). -If [`rights::path_open`](#rights.path_open) is set, includes the right to invoke [`path_open`](#path_open) with [`oflags::trunc`](#oflags.trunc). +Alignment: 2 + +### Record members +- `subscription_clock_abstime`: `bool` +If set, treat the timestamp provided in +[`subscription_clock::timeout`](#subscription_clock.timeout) as an absolute timestamp of clock +[`subscription_clock::id`](#subscription_clock.id). If clear, treat the timestamp +provided in [`subscription_clock::timeout`](#subscription_clock.timeout) relative to the +current time value of clock [`subscription_clock::id`](#subscription_clock.id). + +Bit: 0 -Bit: 19 +## `subscription_clock`: `Record` +The contents of a [`subscription`](#subscription) when type is [`eventtype::clock`](#eventtype.clock). -- `path_filestat_set_times`: `bool` -The right to invoke [`path_filestat_set_times`](#path_filestat_set_times). +Size: 40 -Bit: 20 +Alignment: 8 -- `fd_filestat_get`: `bool` -The right to invoke [`fd_filestat_get`](#fd_filestat_get). +### Record members +- `identifier`: [`userdata`](#userdata) +The user-defined unique identifier of the clock. -Bit: 21 +Offset: 0 -- `fd_filestat_set_size`: `bool` -The right to invoke [`fd_filestat_set_size`](#fd_filestat_set_size). +- `id`: [`clockid`](#clockid) +The clock against which to compare the timestamp. -Bit: 22 +Offset: 8 -- `fd_filestat_set_times`: `bool` -The right to invoke [`fd_filestat_set_times`](#fd_filestat_set_times). +- `timeout`: [`timestamp`](#timestamp) +The absolute or relative timestamp. -Bit: 23 +Offset: 16 -- `path_symlink`: `bool` -The right to invoke [`path_symlink`](#path_symlink). +- `precision`: [`timestamp`](#timestamp) +The amount of time that the implementation may wait additionally +to coalesce with other events. -Bit: 24 +Offset: 24 -- `path_remove_directory`: `bool` -The right to invoke [`path_remove_directory`](#path_remove_directory). +- `flags`: [`subclockflags`](#subclockflags) +Flags specifying whether the timeout is absolute or relative -Bit: 25 +Offset: 32 -- `path_unlink_file`: `bool` -The right to invoke [`path_unlink_file`](#path_unlink_file). +## `subscription_fd_readwrite`: `Record` +The contents of a [`subscription`](#subscription) when the variant is +[`eventtype::fd_read`](#eventtype.fd_read) or [`eventtype::fd_write`](#eventtype.fd_write). -Bit: 26 +Size: 4 -- `poll_fd_readwrite`: `bool` -If [`rights::fd_read`](#rights.fd_read) is set, includes the right to invoke [`poll_oneoff`](#poll_oneoff) to subscribe to [`eventtype::fd_read`](#eventtype.fd_read). -If [`rights::fd_write`](#rights.fd_write) is set, includes the right to invoke [`poll_oneoff`](#poll_oneoff) to subscribe to [`eventtype::fd_write`](#eventtype.fd_write). +Alignment: 4 -Bit: 27 +### Record members +- `file_descriptor`: [`fd`](#fd) +The file descriptor on which to wait for it to become ready for reading or writing. -- `sock_shutdown`: `bool` -The right to invoke [`sock_shutdown`](#sock_shutdown). +Offset: 0 -Bit: 28 +## `subscription_u`: `Variant` +The contents of a [`subscription`](#subscription). -## `roflags`: `Record` -Flags returned by [`sock_recv`](#sock_recv). +Size: 48 -Size: 2 +Alignment: 8 -Alignment: 2 +### Variant Layout +- size: 48 +- align: 8 +- tag_size: 1 +### Variant cases +- `clock`: [`subscription_clock`](#subscription_clock) -### Record members -- `recv_data_truncated`: `bool` -Returned by [`sock_recv`](#sock_recv): Message data has been truncated. +- `fd_read`: [`subscription_fd_readwrite`](#subscription_fd_readwrite) -Bit: 0 +- `fd_write`: [`subscription_fd_readwrite`](#subscription_fd_readwrite) -## `sdflags`: `Record` -Which channels on a socket to shut down. +## `subscription`: `Record` +Subscription to an event. -Size: 1 +Size: 56 -Alignment: 1 +Alignment: 8 ### Record members -- `rd`: `bool` -Disables further receive operations. +- `userdata`: [`userdata`](#userdata) +User-provided value that is attached to the subscription in the +implementation and returned through [`event::userdata`](#event.userdata). -Bit: 0 +Offset: 0 -- `wr`: `bool` -Disables further send operations. +- `u`: [`subscription_u`](#subscription_u) +The type of the event to which to subscribe. -Bit: 1 +Offset: 8 -## `siflags`: `u16` -Flags provided to [`sock_send`](#sock_send). As there are currently no flags -defined, it must be set to zero. +## `exitcode`: `u32` +Exit code generated by a process when exiting. -Size: 2 +Size: 4 -Alignment: 2 +Alignment: 4 ## `signal`: `Variant` Signal condition. @@ -1119,149 +1165,105 @@ Action: Terminates the process. Bad system call. Action: Terminates the process. -## `size`: `u32` - -Size: 4 - -Alignment: 4 - -## `subclockflags`: `Record` -Flags determining how to interpret the timestamp provided in -[`subscription_clock::timeout`](#subscription_clock.timeout). +## `riflags`: `Record` +Flags provided to [`sock_recv`](#sock_recv). Size: 2 Alignment: 2 ### Record members -- `subscription_clock_abstime`: `bool` -If set, treat the timestamp provided in -[`subscription_clock::timeout`](#subscription_clock.timeout) as an absolute timestamp of clock -[`subscription_clock::id`](#subscription_clock.id). If clear, treat the timestamp -provided in [`subscription_clock::timeout`](#subscription_clock.timeout) relative to the -current time value of clock [`subscription_clock::id`](#subscription_clock.id). +- `recv_peek`: `bool` +Returns the message without removing it from the socket's receive queue. Bit: 0 -## `subscription`: `Record` -Subscription to an event. +- `recv_waitall`: `bool` +On byte-stream sockets, block until the full amount of data can be returned. -Size: 56 +Bit: 1 -Alignment: 8 +## `roflags`: `Record` +Flags returned by [`sock_recv`](#sock_recv). -### Record members -- `userdata`: [`userdata`](#userdata) -User-provided value that is attached to the subscription in the -implementation and returned through [`event::userdata`](#event.userdata). +Size: 2 -Offset: 0 +Alignment: 2 -- `u`: [`subscription_u`](#subscription_u) -The type of the event to which to subscribe. +### Record members +- `recv_data_truncated`: `bool` +Returned by [`sock_recv`](#sock_recv): Message data has been truncated. -Offset: 8 +Bit: 0 -## `subscription_clock`: `Record` -The contents of a [`subscription`](#subscription) when type is [`eventtype::clock`](#eventtype.clock). +## `siflags`: `u16` +Flags provided to [`sock_send`](#sock_send). As there are currently no flags +defined, it must be set to zero. -Size: 40 +Size: 2 -Alignment: 8 +Alignment: 2 -### Record members -- `identifier`: [`userdata`](#userdata) -The user-defined unique identifier of the clock. +## `sdflags`: `Record` +Which channels on a socket to shut down. -Offset: 0 +Size: 1 -- `id`: [`clockid`](#clockid) -The clock against which to compare the timestamp. +Alignment: 1 -Offset: 8 +### Record members +- `rd`: `bool` +Disables further receive operations. -- `timeout`: [`timestamp`](#timestamp) -The absolute or relative timestamp. +Bit: 0 -Offset: 16 +- `wr`: `bool` +Disables further send operations. -- `precision`: [`timestamp`](#timestamp) -The amount of time that the implementation may wait additionally -to coalesce with other events. +Bit: 1 -Offset: 24 +## `preopentype`: `Variant` +Identifiers for preopened capabilities. -- `flags`: [`subclockflags`](#subclockflags) -Flags specifying whether the timeout is absolute or relative +Size: 1 -Offset: 32 +Alignment: 1 -## `subscription_fd_readwrite`: `Record` -The contents of a [`subscription`](#subscription) when the variant is -[`eventtype::fd_read`](#eventtype.fd_read) or [`eventtype::fd_write`](#eventtype.fd_write). +### Variant cases +- `dir` +A pre-opened directory. + +## `prestat_dir`: `Record` +The contents of a $prestat when type is [`preopentype::dir`](#preopentype.dir). Size: 4 Alignment: 4 ### Record members -- `file_descriptor`: [`fd`](#fd) -The file descriptor on which to wait for it to become ready for reading or writing. +- `pr_name_len`: [`size`](#size) +The length of the directory name for use with [`fd_prestat_dir_name`](#fd_prestat_dir_name). Offset: 0 -## `subscription_u`: `Variant` -The contents of a [`subscription`](#subscription). +## `prestat`: `Variant` +Information about a pre-opened capability. -Size: 48 +Size: 8 -Alignment: 8 +Alignment: 4 ### Variant Layout -- size: 48 -- align: 8 +- size: 8 +- align: 4 - tag_size: 1 ### Variant cases -- `clock`: [`subscription_clock`](#subscription_clock) - -- `fd_read`: [`subscription_fd_readwrite`](#subscription_fd_readwrite) - -- `fd_write`: [`subscription_fd_readwrite`](#subscription_fd_readwrite) - -## `timestamp`: `u64` -Timestamp in nanoseconds. - -Size: 8 - -Alignment: 8 - -## `userdata`: `u64` -User-provided value that may be attached to objects that is retained when -extracted from the implementation. - -Size: 8 - -Alignment: 8 - -## `whence`: `Variant` -The position relative to which to set the offset of the file descriptor. - -Size: 1 - -Alignment: 1 - -### Variant cases -- `cur` -Seek relative to current position. - -- `end` -Seek relative to end-of-file. - -- `set` -Seek relative to start-of-file. +- `dir`: [`prestat_dir`](#prestat_dir) # Modules ## wasi_unstable +### Imports +#### Memory ### Functions --- diff --git a/phases/old/snapshot_0/witx/wasi_unstable.witx b/phases/old/snapshot_0/witx/wasi_unstable.witx index ca73b30de..dbece2641 100644 --- a/phases/old/snapshot_0/witx/wasi_unstable.witx +++ b/phases/old/snapshot_0/witx/wasi_unstable.witx @@ -6,12 +6,15 @@ ;; 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) +(use "typenames.witx") ;;; 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 + ;;; Linear memory to be accessed by WASI functions that need it. + (import "memory" (memory)) + ;;; 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/docs.md b/phases/snapshot/docs.md index 08f88573d..44c3c35f2 100644 --- a/phases/snapshot/docs.md +++ b/phases/snapshot/docs.md @@ -1,53 +1,23 @@ # Types -## `advice`: `Variant` -File or memory access pattern advisory information. - -Size: 1 - -Alignment: 1 - -### Variant cases -- `normal` -The application has no advice to give on its behavior with respect to the specified data. - -- `sequential` -The application expects to access the specified data sequentially from lower offsets to higher offsets. - -- `random` -The application expects to access the specified data in a random order. - -- `willneed` -The application expects to access the specified data in the near future. - -- `dontneed` -The application expects that it will not access the specified data in the near future. - -- `noreuse` -The application expects to access the specified data once and then not reuse it thereafter. - -## `ciovec`: `Record` -A region of memory for scatter/gather writes. +## `size`: `u32` -Size: 8 +Size: 4 Alignment: 4 -### Record members -- `buf`: `ConstPointer` -The address of the buffer to be written. - -Offset: 0 +## `filesize`: `u64` +Non-negative file size or length of a region within a file. -- `buf_len`: [`size`](#size) -The length of the buffer to be written. +Size: 8 -Offset: 4 +Alignment: 8 -## `ciovec_array`: `List` +## `timestamp`: `u64` +Timestamp in nanoseconds. Size: 8 -Alignment: 4 +Alignment: 8 ## `clockid`: `Variant` Identifiers for clocks. @@ -73,58 +43,6 @@ The CPU-time clock associated with the current process. - `thread_cputime_id` The CPU-time clock associated with the current thread. -## `device`: `u64` -Identifier for a device containing a file system. Can be used in combination -with [`inode`](#inode) to uniquely identify a file or directory in the filesystem. - -Size: 8 - -Alignment: 8 - -## `dircookie`: `u64` -A reference to the offset of a directory entry. - -The value 0 signifies the start of the directory. - -Size: 8 - -Alignment: 8 - -## `dirent`: `Record` -A directory entry. - -Size: 24 - -Alignment: 8 - -### Record members -- `d_next`: [`dircookie`](#dircookie) -The offset of the next directory entry stored in this directory. - -Offset: 0 - -- `d_ino`: [`inode`](#inode) -The serial number of the file referred to by this directory entry. - -Offset: 8 - -- `d_namlen`: [`dirnamlen`](#dirnamlen) -The length of the name of the directory entry. - -Offset: 16 - -- `d_type`: [`filetype`](#filetype) -The type of the file referred to by this directory entry. - -Offset: 20 - -## `dirnamlen`: `u32` -The type for the [`dirent::d_namlen`](#dirent.d_namlen) field of [`dirent`](#dirent) struct. - -Size: 4 - -Alignment: 4 - ## `errno`: `Variant` Error codes returned by functions. Not all of these error codes are returned by the functions provided by this @@ -367,228 +285,273 @@ Cross-device link. - `notcapable` Extension: Capabilities insufficient. -## `event`: `Record` -An event that occurred. +## `rights`: `Record` +File descriptor rights, determining which actions may be performed. -Size: 32 +Size: 8 Alignment: 8 ### Record members -- `userdata`: [`userdata`](#userdata) -User-provided value that got attached to [`subscription::userdata`](#subscription.userdata). +- `fd_datasync`: `bool` +The right to invoke [`fd_datasync`](#fd_datasync). +If [`path_open`](#path_open) is set, includes the right to invoke +[`path_open`](#path_open) with [`fdflags::dsync`](#fdflags.dsync). -Offset: 0 +Bit: 0 -- `error`: [`errno`](#errno) -If non-zero, an error that occurred while processing the subscription request. +- `fd_read`: `bool` +The right to invoke [`fd_read`](#fd_read) and [`sock_recv`](#sock_recv). +If [`rights::fd_seek`](#rights.fd_seek) is set, includes the right to invoke [`fd_pread`](#fd_pread). -Offset: 8 +Bit: 1 -- `type`: [`eventtype`](#eventtype) -The type of event that occured +- `fd_seek`: `bool` +The right to invoke [`fd_seek`](#fd_seek). This flag implies [`rights::fd_tell`](#rights.fd_tell). -Offset: 10 +Bit: 2 -- `fd_readwrite`: [`event_fd_readwrite`](#event_fd_readwrite) -The contents of the event, if it is an [`eventtype::fd_read`](#eventtype.fd_read) or -[`eventtype::fd_write`](#eventtype.fd_write). [`eventtype::clock`](#eventtype.clock) events ignore this field. +- `fd_fdstat_set_flags`: `bool` +The right to invoke [`fd_fdstat_set_flags`](#fd_fdstat_set_flags). -Offset: 16 +Bit: 3 -## `event_fd_readwrite`: `Record` -The contents of an [`event`](#event) when type is [`eventtype::fd_read`](#eventtype.fd_read) or -[`eventtype::fd_write`](#eventtype.fd_write). +- `fd_sync`: `bool` +The right to invoke [`fd_sync`](#fd_sync). +If [`path_open`](#path_open) is set, includes the right to invoke +[`path_open`](#path_open) with [`fdflags::rsync`](#fdflags.rsync) and [`fdflags::dsync`](#fdflags.dsync). -Size: 16 +Bit: 4 -Alignment: 8 +- `fd_tell`: `bool` +The right to invoke [`fd_seek`](#fd_seek) in such a way that the file offset +remains unaltered (i.e., [`whence::cur`](#whence.cur) with offset zero), or to +invoke [`fd_tell`](#fd_tell). -### Record members -- `nbytes`: [`filesize`](#filesize) -The number of bytes available for reading or writing. +Bit: 5 -Offset: 0 +- `fd_write`: `bool` +The right to invoke [`fd_write`](#fd_write) and [`sock_send`](#sock_send). +If [`rights::fd_seek`](#rights.fd_seek) is set, includes the right to invoke [`fd_pwrite`](#fd_pwrite). -- `flags`: [`eventrwflags`](#eventrwflags) -The state of the file descriptor. +Bit: 6 -Offset: 8 +- `fd_advise`: `bool` +The right to invoke [`fd_advise`](#fd_advise). -## `eventrwflags`: `Record` -The state of the file descriptor subscribed to with -[`eventtype::fd_read`](#eventtype.fd_read) or [`eventtype::fd_write`](#eventtype.fd_write). +Bit: 7 -Size: 2 +- `fd_allocate`: `bool` +The right to invoke [`fd_allocate`](#fd_allocate). -Alignment: 2 +Bit: 8 -### Record members -- `fd_readwrite_hangup`: `bool` -The peer of this socket has closed or disconnected. +- `path_create_directory`: `bool` +The right to invoke [`path_create_directory`](#path_create_directory). -Bit: 0 +Bit: 9 -## `eventtype`: `Variant` -Type of a subscription to an event or its occurrence. +- `path_create_file`: `bool` +If [`path_open`](#path_open) is set, the right to invoke [`path_open`](#path_open) with [`oflags::creat`](#oflags.creat). -Size: 1 +Bit: 10 -Alignment: 1 +- `path_link_source`: `bool` +The right to invoke [`path_link`](#path_link) with the file descriptor as the +source directory. -### Variant cases -- `clock` -The time value of clock [`subscription_clock::id`](#subscription_clock.id) has -reached timestamp [`subscription_clock::timeout`](#subscription_clock.timeout). +Bit: 11 -- `fd_read` -File descriptor [`subscription_fd_readwrite::file_descriptor`](#subscription_fd_readwrite.file_descriptor) has data -available for reading. This event always triggers for regular files. +- `path_link_target`: `bool` +The right to invoke [`path_link`](#path_link) with the file descriptor as the +target directory. -- `fd_write` -File descriptor [`subscription_fd_readwrite::file_descriptor`](#subscription_fd_readwrite.file_descriptor) has capacity -available for writing. This event always triggers for regular files. +Bit: 12 -## `exitcode`: `u32` -Exit code generated by a process when exiting. +- `path_open`: `bool` +The right to invoke [`path_open`](#path_open). -Size: 4 +Bit: 13 -Alignment: 4 +- `fd_readdir`: `bool` +The right to invoke [`fd_readdir`](#fd_readdir). -## `fd`: `Handle` -A file descriptor handle. +Bit: 14 -Size: 4 +- `path_readlink`: `bool` +The right to invoke [`path_readlink`](#path_readlink). -Alignment: 4 +Bit: 15 -### Supertypes -## `fdflags`: `Record` -File descriptor flags. +- `path_rename_source`: `bool` +The right to invoke [`path_rename`](#path_rename) with the file descriptor as the source directory. -Size: 2 +Bit: 16 -Alignment: 2 +- `path_rename_target`: `bool` +The right to invoke [`path_rename`](#path_rename) with the file descriptor as the target directory. -### Record members -- `append`: `bool` -Append mode: Data written to the file is always appended to the file's end. +Bit: 17 -Bit: 0 +- `path_filestat_get`: `bool` +The right to invoke [`path_filestat_get`](#path_filestat_get). -- `dsync`: `bool` -Write according to synchronized I/O data integrity completion. Only the data stored in the file is synchronized. +Bit: 18 -Bit: 1 +- `path_filestat_set_size`: `bool` +The right to change a file's size (there is no `path_filestat_set_size`). +If [`path_open`](#path_open) is set, includes the right to invoke [`path_open`](#path_open) with [`oflags::trunc`](#oflags.trunc). -- `nonblock`: `bool` -Non-blocking mode. +Bit: 19 -Bit: 2 +- `path_filestat_set_times`: `bool` +The right to invoke [`path_filestat_set_times`](#path_filestat_set_times). -- `rsync`: `bool` -Synchronized read I/O operations. +Bit: 20 -Bit: 3 +- `fd_filestat_get`: `bool` +The right to invoke [`fd_filestat_get`](#fd_filestat_get). -- `sync`: `bool` -Write according to synchronized I/O file integrity completion. In -addition to synchronizing the data stored in the file, the implementation -may also synchronously update the file's metadata. +Bit: 21 -Bit: 4 +- `fd_filestat_set_size`: `bool` +The right to invoke [`fd_filestat_set_size`](#fd_filestat_set_size). -## `fdstat`: `Record` -File descriptor attributes. +Bit: 22 -Size: 24 +- `fd_filestat_set_times`: `bool` +The right to invoke [`fd_filestat_set_times`](#fd_filestat_set_times). -Alignment: 8 +Bit: 23 + +- `path_symlink`: `bool` +The right to invoke [`path_symlink`](#path_symlink). + +Bit: 24 + +- `path_remove_directory`: `bool` +The right to invoke [`path_remove_directory`](#path_remove_directory). + +Bit: 25 + +- `path_unlink_file`: `bool` +The right to invoke [`path_unlink_file`](#path_unlink_file). + +Bit: 26 + +- `poll_fd_readwrite`: `bool` +If [`rights::fd_read`](#rights.fd_read) is set, includes the right to invoke [`poll_oneoff`](#poll_oneoff) to subscribe to [`eventtype::fd_read`](#eventtype.fd_read). +If [`rights::fd_write`](#rights.fd_write) is set, includes the right to invoke [`poll_oneoff`](#poll_oneoff) to subscribe to [`eventtype::fd_write`](#eventtype.fd_write). + +Bit: 27 + +- `sock_shutdown`: `bool` +The right to invoke [`sock_shutdown`](#sock_shutdown). + +Bit: 28 + +## `fd`: `Handle` +A file descriptor handle. + +Size: 4 + +Alignment: 4 + +### Supertypes +## `iovec`: `Record` +A region of memory for scatter/gather reads. + +Size: 8 + +Alignment: 4 ### Record members -- `fs_filetype`: [`filetype`](#filetype) -File type. +- `buf`: `Pointer` +The address of the buffer to be filled. Offset: 0 -- `fs_flags`: [`fdflags`](#fdflags) -File descriptor flags. +- `buf_len`: [`size`](#size) +The length of the buffer to be filled. -Offset: 2 +Offset: 4 -- `fs_rights_base`: [`rights`](#rights) -Rights that apply to this file descriptor. +## `ciovec`: `Record` +A region of memory for scatter/gather writes. -Offset: 8 +Size: 8 -- `fs_rights_inheriting`: [`rights`](#rights) -Maximum set of rights that may be installed on new file descriptors that -are created through this file descriptor, e.g., through [`path_open`](#path_open). +Alignment: 4 -Offset: 16 +### Record members +- `buf`: `ConstPointer` +The address of the buffer to be written. -## `filedelta`: `s64` -Relative offset within a file. +Offset: 0 + +- `buf_len`: [`size`](#size) +The length of the buffer to be written. + +Offset: 4 + +## `iovec_array`: `List` Size: 8 -Alignment: 8 +Alignment: 4 -## `filesize`: `u64` -Non-negative file size or length of a region within a file. +## `ciovec_array`: `List` Size: 8 -Alignment: 8 +Alignment: 4 -## `filestat`: `Record` -File attributes. +## `filedelta`: `s64` +Relative offset within a file. -Size: 64 +Size: 8 Alignment: 8 -### Record members -- `dev`: [`device`](#device) -Device ID of device containing the file. +## `whence`: `Variant` +The position relative to which to set the offset of the file descriptor. -Offset: 0 +Size: 1 -- `ino`: [`inode`](#inode) -File serial number. +Alignment: 1 -Offset: 8 +### Variant cases +- `set` +Seek relative to start-of-file. -- `filetype`: [`filetype`](#filetype) -File type. +- `cur` +Seek relative to current position. -Offset: 16 +- `end` +Seek relative to end-of-file. -- `nlink`: [`linkcount`](#linkcount) -Number of hard links to the file. +## `dircookie`: `u64` +A reference to the offset of a directory entry. -Offset: 24 +The value 0 signifies the start of the directory. -- `size`: [`filesize`](#filesize) -For regular files, the file size in bytes. For symbolic links, the length in bytes of the pathname contained in the symbolic link. +Size: 8 -Offset: 32 +Alignment: 8 -- `atim`: [`timestamp`](#timestamp) -Last data access timestamp. +## `dirnamlen`: `u32` +The type for the [`dirent::d_namlen`](#dirent.d_namlen) field of [`dirent`](#dirent) struct. -Offset: 40 +Size: 4 -- `mtim`: [`timestamp`](#timestamp) -Last data modification timestamp. +Alignment: 4 -Offset: 48 +## `inode`: `u64` +File serial number that is unique within its file system. -- `ctim`: [`timestamp`](#timestamp) -Last file status change timestamp. +Size: 8 -Offset: 56 +Alignment: 8 ## `filetype`: `Variant` The type of a file descriptor or file. @@ -622,72 +585,160 @@ The file descriptor or file refers to a byte-stream socket. - `symbolic_link` The file refers to a symbolic link inode. -## `fstflags`: `Record` -Which file time attributes to adjust. +## `dirent`: `Record` +A directory entry. + +Size: 24 + +Alignment: 8 + +### Record members +- `d_next`: [`dircookie`](#dircookie) +The offset of the next directory entry stored in this directory. + +Offset: 0 + +- `d_ino`: [`inode`](#inode) +The serial number of the file referred to by this directory entry. + +Offset: 8 + +- `d_namlen`: [`dirnamlen`](#dirnamlen) +The length of the name of the directory entry. + +Offset: 16 + +- `d_type`: [`filetype`](#filetype) +The type of the file referred to by this directory entry. + +Offset: 20 + +## `advice`: `Variant` +File or memory access pattern advisory information. + +Size: 1 + +Alignment: 1 + +### Variant cases +- `normal` +The application has no advice to give on its behavior with respect to the specified data. + +- `sequential` +The application expects to access the specified data sequentially from lower offsets to higher offsets. + +- `random` +The application expects to access the specified data in a random order. + +- `willneed` +The application expects to access the specified data in the near future. + +- `dontneed` +The application expects that it will not access the specified data in the near future. + +- `noreuse` +The application expects to access the specified data once and then not reuse it thereafter. + +## `fdflags`: `Record` +File descriptor flags. Size: 2 Alignment: 2 ### Record members -- `atim`: `bool` -Adjust the last data access timestamp to the value stored in [`filestat::atim`](#filestat.atim). +- `append`: `bool` +Append mode: Data written to the file is always appended to the file's end. Bit: 0 -- `atim_now`: `bool` -Adjust the last data access timestamp to the time of clock [`clockid::realtime`](#clockid.realtime). +- `dsync`: `bool` +Write according to synchronized I/O data integrity completion. Only the data stored in the file is synchronized. Bit: 1 -- `mtim`: `bool` -Adjust the last data modification timestamp to the value stored in [`filestat::mtim`](#filestat.mtim). +- `nonblock`: `bool` +Non-blocking mode. Bit: 2 -- `mtim_now`: `bool` -Adjust the last data modification timestamp to the time of clock [`clockid::realtime`](#clockid.realtime). +- `rsync`: `bool` +Synchronized read I/O operations. Bit: 3 -## `inode`: `u64` -File serial number that is unique within its file system. - -Size: 8 +- `sync`: `bool` +Write according to synchronized I/O file integrity completion. In +addition to synchronizing the data stored in the file, the implementation +may also synchronously update the file's metadata. -Alignment: 8 +Bit: 4 -## `iovec`: `Record` -A region of memory for scatter/gather reads. +## `fdstat`: `Record` +File descriptor attributes. -Size: 8 +Size: 24 -Alignment: 4 +Alignment: 8 ### Record members -- `buf`: `Pointer` -The address of the buffer to be filled. +- `fs_filetype`: [`filetype`](#filetype) +File type. Offset: 0 -- `buf_len`: [`size`](#size) -The length of the buffer to be filled. +- `fs_flags`: [`fdflags`](#fdflags) +File descriptor flags. -Offset: 4 +Offset: 2 -## `iovec_array`: `List` +- `fs_rights_base`: [`rights`](#rights) +Rights that apply to this file descriptor. -Size: 8 +Offset: 8 -Alignment: 4 +- `fs_rights_inheriting`: [`rights`](#rights) +Maximum set of rights that may be installed on new file descriptors that +are created through this file descriptor, e.g., through [`path_open`](#path_open). -## `linkcount`: `u64` -Number of hard links to an inode. +Offset: 16 + +## `device`: `u64` +Identifier for a device containing a file system. Can be used in combination +with [`inode`](#inode) to uniquely identify a file or directory in the filesystem. Size: 8 Alignment: 8 +## `fstflags`: `Record` +Which file time attributes to adjust. + +Size: 2 + +Alignment: 2 + +### Record members +- `atim`: `bool` +Adjust the last data access timestamp to the value stored in [`filestat::atim`](#filestat.atim). + +Bit: 0 + +- `atim_now`: `bool` +Adjust the last data access timestamp to the time of clock [`clockid::realtime`](#clockid.realtime). + +Bit: 1 + +- `mtim`: `bool` +Adjust the last data modification timestamp to the value stored in [`filestat::mtim`](#filestat.mtim). + +Bit: 2 + +- `mtim_now`: `bool` +Adjust the last data modification timestamp to the time of clock [`clockid::realtime`](#clockid.realtime). + +Bit: 3 + ## `lookupflags`: `Record` Flags determining the method of how paths are resolved. @@ -729,265 +780,255 @@ Truncate file to size 0. Bit: 3 -## `preopentype`: `Variant` -Identifiers for preopened capabilities. - -Size: 1 - -Alignment: 1 - -### Variant cases -- `dir` -A pre-opened directory. - -## `prestat`: `Variant` -Information about a pre-opened capability. +## `linkcount`: `u64` +Number of hard links to an inode. Size: 8 -Alignment: 4 - -### Variant Layout -- size: 8 -- align: 4 -- tag_size: 1 -### Variant cases -- `dir`: [`prestat_dir`](#prestat_dir) +Alignment: 8 -## `prestat_dir`: `Record` -The contents of a $prestat when type is [`preopentype::dir`](#preopentype.dir). +## `filestat`: `Record` +File attributes. -Size: 4 +Size: 64 -Alignment: 4 +Alignment: 8 ### Record members -- `pr_name_len`: [`size`](#size) -The length of the directory name for use with [`fd_prestat_dir_name`](#fd_prestat_dir_name). +- `dev`: [`device`](#device) +Device ID of device containing the file. Offset: 0 -## `riflags`: `Record` -Flags provided to [`sock_recv`](#sock_recv). +- `ino`: [`inode`](#inode) +File serial number. -Size: 2 +Offset: 8 -Alignment: 2 +- `filetype`: [`filetype`](#filetype) +File type. -### Record members -- `recv_peek`: `bool` -Returns the message without removing it from the socket's receive queue. +Offset: 16 -Bit: 0 +- `nlink`: [`linkcount`](#linkcount) +Number of hard links to the file. -- `recv_waitall`: `bool` -On byte-stream sockets, block until the full amount of data can be returned. +Offset: 24 -Bit: 1 +- `size`: [`filesize`](#filesize) +For regular files, the file size in bytes. For symbolic links, the length in bytes of the pathname contained in the symbolic link. -## `rights`: `Record` -File descriptor rights, determining which actions may be performed. +Offset: 32 -Size: 8 +- `atim`: [`timestamp`](#timestamp) +Last data access timestamp. -Alignment: 8 +Offset: 40 -### Record members -- `fd_datasync`: `bool` -The right to invoke [`fd_datasync`](#fd_datasync). -If [`path_open`](#path_open) is set, includes the right to invoke -[`path_open`](#path_open) with [`fdflags::dsync`](#fdflags.dsync). +- `mtim`: [`timestamp`](#timestamp) +Last data modification timestamp. -Bit: 0 +Offset: 48 -- `fd_read`: `bool` -The right to invoke [`fd_read`](#fd_read) and [`sock_recv`](#sock_recv). -If [`rights::fd_seek`](#rights.fd_seek) is set, includes the right to invoke [`fd_pread`](#fd_pread). +- `ctim`: [`timestamp`](#timestamp) +Last file status change timestamp. -Bit: 1 +Offset: 56 -- `fd_seek`: `bool` -The right to invoke [`fd_seek`](#fd_seek). This flag implies [`rights::fd_tell`](#rights.fd_tell). +## `userdata`: `u64` +User-provided value that may be attached to objects that is retained when +extracted from the implementation. -Bit: 2 +Size: 8 -- `fd_fdstat_set_flags`: `bool` -The right to invoke [`fd_fdstat_set_flags`](#fd_fdstat_set_flags). +Alignment: 8 -Bit: 3 +## `eventtype`: `Variant` +Type of a subscription to an event or its occurrence. -- `fd_sync`: `bool` -The right to invoke [`fd_sync`](#fd_sync). -If [`path_open`](#path_open) is set, includes the right to invoke -[`path_open`](#path_open) with [`fdflags::rsync`](#fdflags.rsync) and [`fdflags::dsync`](#fdflags.dsync). +Size: 1 -Bit: 4 +Alignment: 1 -- `fd_tell`: `bool` -The right to invoke [`fd_seek`](#fd_seek) in such a way that the file offset -remains unaltered (i.e., [`whence::cur`](#whence.cur) with offset zero), or to -invoke [`fd_tell`](#fd_tell). +### Variant cases +- `clock` +The time value of clock [`subscription_clock::id`](#subscription_clock.id) has +reached timestamp [`subscription_clock::timeout`](#subscription_clock.timeout). -Bit: 5 +- `fd_read` +File descriptor [`subscription_fd_readwrite::file_descriptor`](#subscription_fd_readwrite.file_descriptor) has data +available for reading. This event always triggers for regular files. -- `fd_write`: `bool` -The right to invoke [`fd_write`](#fd_write) and [`sock_send`](#sock_send). -If [`rights::fd_seek`](#rights.fd_seek) is set, includes the right to invoke [`fd_pwrite`](#fd_pwrite). +- `fd_write` +File descriptor [`subscription_fd_readwrite::file_descriptor`](#subscription_fd_readwrite.file_descriptor) has capacity +available for writing. This event always triggers for regular files. -Bit: 6 +## `eventrwflags`: `Record` +The state of the file descriptor subscribed to with +[`eventtype::fd_read`](#eventtype.fd_read) or [`eventtype::fd_write`](#eventtype.fd_write). -- `fd_advise`: `bool` -The right to invoke [`fd_advise`](#fd_advise). +Size: 2 -Bit: 7 +Alignment: 2 -- `fd_allocate`: `bool` -The right to invoke [`fd_allocate`](#fd_allocate). +### Record members +- `fd_readwrite_hangup`: `bool` +The peer of this socket has closed or disconnected. -Bit: 8 +Bit: 0 -- `path_create_directory`: `bool` -The right to invoke [`path_create_directory`](#path_create_directory). +## `event_fd_readwrite`: `Record` +The contents of an [`event`](#event) when type is [`eventtype::fd_read`](#eventtype.fd_read) or +[`eventtype::fd_write`](#eventtype.fd_write). -Bit: 9 +Size: 16 -- `path_create_file`: `bool` -If [`path_open`](#path_open) is set, the right to invoke [`path_open`](#path_open) with [`oflags::creat`](#oflags.creat). +Alignment: 8 -Bit: 10 +### Record members +- `nbytes`: [`filesize`](#filesize) +The number of bytes available for reading or writing. -- `path_link_source`: `bool` -The right to invoke [`path_link`](#path_link) with the file descriptor as the -source directory. +Offset: 0 -Bit: 11 +- `flags`: [`eventrwflags`](#eventrwflags) +The state of the file descriptor. -- `path_link_target`: `bool` -The right to invoke [`path_link`](#path_link) with the file descriptor as the -target directory. +Offset: 8 -Bit: 12 +## `event`: `Record` +An event that occurred. -- `path_open`: `bool` -The right to invoke [`path_open`](#path_open). +Size: 32 -Bit: 13 +Alignment: 8 -- `fd_readdir`: `bool` -The right to invoke [`fd_readdir`](#fd_readdir). +### Record members +- `userdata`: [`userdata`](#userdata) +User-provided value that got attached to [`subscription::userdata`](#subscription.userdata). -Bit: 14 +Offset: 0 -- `path_readlink`: `bool` -The right to invoke [`path_readlink`](#path_readlink). +- `error`: [`errno`](#errno) +If non-zero, an error that occurred while processing the subscription request. -Bit: 15 +Offset: 8 -- `path_rename_source`: `bool` -The right to invoke [`path_rename`](#path_rename) with the file descriptor as the source directory. +- `type`: [`eventtype`](#eventtype) +The type of event that occured -Bit: 16 +Offset: 10 -- `path_rename_target`: `bool` -The right to invoke [`path_rename`](#path_rename) with the file descriptor as the target directory. +- `fd_readwrite`: [`event_fd_readwrite`](#event_fd_readwrite) +The contents of the event, if it is an [`eventtype::fd_read`](#eventtype.fd_read) or +[`eventtype::fd_write`](#eventtype.fd_write). [`eventtype::clock`](#eventtype.clock) events ignore this field. -Bit: 17 +Offset: 16 -- `path_filestat_get`: `bool` -The right to invoke [`path_filestat_get`](#path_filestat_get). +## `subclockflags`: `Record` +Flags determining how to interpret the timestamp provided in +[`subscription_clock::timeout`](#subscription_clock.timeout). -Bit: 18 +Size: 2 -- `path_filestat_set_size`: `bool` -The right to change a file's size (there is no `path_filestat_set_size`). -If [`path_open`](#path_open) is set, includes the right to invoke [`path_open`](#path_open) with [`oflags::trunc`](#oflags.trunc). +Alignment: 2 -Bit: 19 +### Record members +- `subscription_clock_abstime`: `bool` +If set, treat the timestamp provided in +[`subscription_clock::timeout`](#subscription_clock.timeout) as an absolute timestamp of clock +[`subscription_clock::id`](#subscription_clock.id). If clear, treat the timestamp +provided in [`subscription_clock::timeout`](#subscription_clock.timeout) relative to the +current time value of clock [`subscription_clock::id`](#subscription_clock.id). -- `path_filestat_set_times`: `bool` -The right to invoke [`path_filestat_set_times`](#path_filestat_set_times). +Bit: 0 -Bit: 20 +## `subscription_clock`: `Record` +The contents of a [`subscription`](#subscription) when type is [`eventtype::clock`](#eventtype.clock). -- `fd_filestat_get`: `bool` -The right to invoke [`fd_filestat_get`](#fd_filestat_get). +Size: 32 -Bit: 21 +Alignment: 8 -- `fd_filestat_set_size`: `bool` -The right to invoke [`fd_filestat_set_size`](#fd_filestat_set_size). +### Record members +- `id`: [`clockid`](#clockid) +The clock against which to compare the timestamp. -Bit: 22 +Offset: 0 -- `fd_filestat_set_times`: `bool` -The right to invoke [`fd_filestat_set_times`](#fd_filestat_set_times). +- `timeout`: [`timestamp`](#timestamp) +The absolute or relative timestamp. -Bit: 23 +Offset: 8 -- `path_symlink`: `bool` -The right to invoke [`path_symlink`](#path_symlink). +- `precision`: [`timestamp`](#timestamp) +The amount of time that the implementation may wait additionally +to coalesce with other events. -Bit: 24 +Offset: 16 -- `path_remove_directory`: `bool` -The right to invoke [`path_remove_directory`](#path_remove_directory). +- `flags`: [`subclockflags`](#subclockflags) +Flags specifying whether the timeout is absolute or relative -Bit: 25 +Offset: 24 -- `path_unlink_file`: `bool` -The right to invoke [`path_unlink_file`](#path_unlink_file). +## `subscription_fd_readwrite`: `Record` +The contents of a [`subscription`](#subscription) when type is type is +[`eventtype::fd_read`](#eventtype.fd_read) or [`eventtype::fd_write`](#eventtype.fd_write). -Bit: 26 +Size: 4 -- `poll_fd_readwrite`: `bool` -If [`rights::fd_read`](#rights.fd_read) is set, includes the right to invoke [`poll_oneoff`](#poll_oneoff) to subscribe to [`eventtype::fd_read`](#eventtype.fd_read). -If [`rights::fd_write`](#rights.fd_write) is set, includes the right to invoke [`poll_oneoff`](#poll_oneoff) to subscribe to [`eventtype::fd_write`](#eventtype.fd_write). +Alignment: 4 -Bit: 27 +### Record members +- `file_descriptor`: [`fd`](#fd) +The file descriptor on which to wait for it to become ready for reading or writing. -- `sock_shutdown`: `bool` -The right to invoke [`sock_shutdown`](#sock_shutdown). +Offset: 0 -Bit: 28 +## `subscription_u`: `Variant` +The contents of a [`subscription`](#subscription). -## `roflags`: `Record` -Flags returned by [`sock_recv`](#sock_recv). +Size: 40 -Size: 2 +Alignment: 8 -Alignment: 2 +### Variant Layout +- size: 40 +- align: 8 +- tag_size: 1 +### Variant cases +- `clock`: [`subscription_clock`](#subscription_clock) -### Record members -- `recv_data_truncated`: `bool` -Returned by [`sock_recv`](#sock_recv): Message data has been truncated. +- `fd_read`: [`subscription_fd_readwrite`](#subscription_fd_readwrite) -Bit: 0 +- `fd_write`: [`subscription_fd_readwrite`](#subscription_fd_readwrite) -## `sdflags`: `Record` -Which channels on a socket to shut down. +## `subscription`: `Record` +Subscription to an event. -Size: 1 +Size: 48 -Alignment: 1 +Alignment: 8 ### Record members -- `rd`: `bool` -Disables further receive operations. +- `userdata`: [`userdata`](#userdata) +User-provided value that is attached to the subscription in the +implementation and returned through [`event::userdata`](#event.userdata). -Bit: 0 +Offset: 0 -- `wr`: `bool` -Disables further send operations. +- `u`: [`subscription_u`](#subscription_u) +The type of the event to which to subscribe, and its contents -Bit: 1 +Offset: 8 -## `siflags`: `u16` -Flags provided to [`sock_send`](#sock_send). As there are currently no flags -defined, it must be set to zero. +## `exitcode`: `u32` +Exit code generated by a process when exiting. -Size: 2 +Size: 4 -Alignment: 2 +Alignment: 4 ## `signal`: `Variant` Signal condition. @@ -1121,144 +1162,105 @@ Action: Terminates the process. Bad system call. Action: Terminates the process. -## `size`: `u32` - -Size: 4 - -Alignment: 4 - -## `subclockflags`: `Record` -Flags determining how to interpret the timestamp provided in -[`subscription_clock::timeout`](#subscription_clock.timeout). +## `riflags`: `Record` +Flags provided to [`sock_recv`](#sock_recv). Size: 2 Alignment: 2 ### Record members -- `subscription_clock_abstime`: `bool` -If set, treat the timestamp provided in -[`subscription_clock::timeout`](#subscription_clock.timeout) as an absolute timestamp of clock -[`subscription_clock::id`](#subscription_clock.id). If clear, treat the timestamp -provided in [`subscription_clock::timeout`](#subscription_clock.timeout) relative to the -current time value of clock [`subscription_clock::id`](#subscription_clock.id). +- `recv_peek`: `bool` +Returns the message without removing it from the socket's receive queue. Bit: 0 -## `subscription`: `Record` -Subscription to an event. +- `recv_waitall`: `bool` +On byte-stream sockets, block until the full amount of data can be returned. -Size: 48 +Bit: 1 -Alignment: 8 +## `roflags`: `Record` +Flags returned by [`sock_recv`](#sock_recv). + +Size: 2 + +Alignment: 2 ### Record members -- `userdata`: [`userdata`](#userdata) -User-provided value that is attached to the subscription in the -implementation and returned through [`event::userdata`](#event.userdata). +- `recv_data_truncated`: `bool` +Returned by [`sock_recv`](#sock_recv): Message data has been truncated. -Offset: 0 +Bit: 0 -- `u`: [`subscription_u`](#subscription_u) -The type of the event to which to subscribe, and its contents +## `siflags`: `u16` +Flags provided to [`sock_send`](#sock_send). As there are currently no flags +defined, it must be set to zero. -Offset: 8 +Size: 2 -## `subscription_clock`: `Record` -The contents of a [`subscription`](#subscription) when type is [`eventtype::clock`](#eventtype.clock). +Alignment: 2 -Size: 32 +## `sdflags`: `Record` +Which channels on a socket to shut down. -Alignment: 8 +Size: 1 + +Alignment: 1 ### Record members -- `id`: [`clockid`](#clockid) -The clock against which to compare the timestamp. +- `rd`: `bool` +Disables further receive operations. -Offset: 0 +Bit: 0 -- `timeout`: [`timestamp`](#timestamp) -The absolute or relative timestamp. +- `wr`: `bool` +Disables further send operations. -Offset: 8 +Bit: 1 -- `precision`: [`timestamp`](#timestamp) -The amount of time that the implementation may wait additionally -to coalesce with other events. +## `preopentype`: `Variant` +Identifiers for preopened capabilities. -Offset: 16 +Size: 1 -- `flags`: [`subclockflags`](#subclockflags) -Flags specifying whether the timeout is absolute or relative +Alignment: 1 -Offset: 24 +### Variant cases +- `dir` +A pre-opened directory. -## `subscription_fd_readwrite`: `Record` -The contents of a [`subscription`](#subscription) when type is type is -[`eventtype::fd_read`](#eventtype.fd_read) or [`eventtype::fd_write`](#eventtype.fd_write). +## `prestat_dir`: `Record` +The contents of a $prestat when type is [`preopentype::dir`](#preopentype.dir). Size: 4 Alignment: 4 ### Record members -- `file_descriptor`: [`fd`](#fd) -The file descriptor on which to wait for it to become ready for reading or writing. +- `pr_name_len`: [`size`](#size) +The length of the directory name for use with [`fd_prestat_dir_name`](#fd_prestat_dir_name). Offset: 0 -## `subscription_u`: `Variant` -The contents of a [`subscription`](#subscription). +## `prestat`: `Variant` +Information about a pre-opened capability. -Size: 40 +Size: 8 -Alignment: 8 +Alignment: 4 ### Variant Layout -- size: 40 -- align: 8 +- size: 8 +- align: 4 - tag_size: 1 ### Variant cases -- `clock`: [`subscription_clock`](#subscription_clock) - -- `fd_read`: [`subscription_fd_readwrite`](#subscription_fd_readwrite) - -- `fd_write`: [`subscription_fd_readwrite`](#subscription_fd_readwrite) - -## `timestamp`: `u64` -Timestamp in nanoseconds. - -Size: 8 - -Alignment: 8 - -## `userdata`: `u64` -User-provided value that may be attached to objects that is retained when -extracted from the implementation. - -Size: 8 - -Alignment: 8 - -## `whence`: `Variant` -The position relative to which to set the offset of the file descriptor. - -Size: 1 - -Alignment: 1 - -### Variant cases -- `set` -Seek relative to start-of-file. - -- `cur` -Seek relative to current position. - -- `end` -Seek relative to end-of-file. +- `dir`: [`prestat_dir`](#prestat_dir) # Modules ## wasi_snapshot_preview1 +### Imports +#### Memory ### Functions --- diff --git a/phases/snapshot/witx/wasi_snapshot_preview1.witx b/phases/snapshot/witx/wasi_snapshot_preview1.witx index fba51a693..efb2c797f 100644 --- a/phases/snapshot/witx/wasi_snapshot_preview1.witx +++ b/phases/snapshot/witx/wasi_snapshot_preview1.witx @@ -6,9 +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) +(use "typenames.witx") (module $wasi_snapshot_preview1 + ;;; Linear memory to be accessed by WASI functions that need it. + (import "memory" (memory)) + ;;; 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..bdb1c6a9c 100644 --- a/tools/witx/cli/src/main.rs +++ b/tools/witx/cli/src/main.rs @@ -1,10 +1,10 @@ -use anyhow::Result; +use anyhow::{anyhow, bail, Result}; use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; use std::process; use structopt::{clap::AppSettings, StructOpt}; -use witx::{load, Module}; +use witx::{load, Document, Documentation}; /// Validate and process witx files #[derive(StructOpt, Debug)] @@ -43,6 +43,36 @@ enum Command { )] output: Option, }, + /// Examine differences between interfaces + Polyfill { + /// Path to root of witx document + #[structopt( + required = true, + number_of_values = 1, + value_name = "INPUT", + parse(from_os_str) + )] + input: Vec, + /// Path to root of witx document describing interface to polyfill + #[structopt( + required = true, + number_of_values = 1, + value_name = "OLDER_INTERFACE", + parse(from_os_str) + )] + older_interface: Vec, + /// Module to examine (use newname=oldname syntax if name is different + /// between new and old interfaces) + #[structopt( + short = "m", + long = "module_mapping", + required = true, + number_of_values = 1, + value_name = "NEWNAME=OLDNAME", + parse(try_from_str = parse_module_mapping) + )] + module_mapping: Vec<(String, String)>, + }, } pub fn main() { @@ -56,14 +86,10 @@ pub fn main() { check, output, } => { - let modules = input - .iter() - .map(|i| load_witx(i, "input", verbose)) - .collect::>(); - let docs = witx::document(&modules); + let doc = load_witx(&input, "input", verbose); if check { let output = output.expect("output argument required in docs --check mode"); - if diff_against_filesystem(&docs, &output).is_err() { + if diff_against_filesystem(&doc.to_md(), &output).is_err() { println!("Docs in tree are out-of-date with witx files. Re-run this executable with the following arguments to to re-generate:"); println!( "> witx docs {} --output {}", @@ -77,16 +103,42 @@ pub fn main() { } } else { if let Some(output) = output { - write_docs(&docs, output) + write_docs(&doc, output) } else { - println!("{}", docs); + println!("{}", doc.to_md()) } } } + Command::Polyfill { + input, + older_interface, + module_mapping, + } => { + use std::{collections::HashMap, iter::FromIterator}; + use witx::polyfill::Polyfill; + + let doc = load_witx(&input, "input", verbose); + let older_doc = load_witx(&older_interface, "older_interface", verbose); + let module_mapping = HashMap::from_iter(module_mapping.into_iter()); + let polyfill = match Polyfill::new(&doc, &older_doc, &module_mapping) { + Ok(polyfill) => polyfill, + Err(e) => { + eprintln!("couldn't calculate polyfill"); + if verbose { + println!("{:?}", e); + } + process::exit(1); + } + }; + println!("{}", polyfill.to_md()); + if verbose { + println!("{:?}", polyfill); + } + } } } -fn load_witx(input: &Path, field_name: &str, verbose: bool) -> Module { +fn load_witx(input: &[PathBuf], field_name: &str, verbose: bool) -> Document { match load(input) { Ok(doc) => { if verbose { @@ -104,9 +156,33 @@ fn load_witx(input: &Path, field_name: &str, verbose: bool) -> Module { } } -fn write_docs>(docs: &str, path: P) { +fn write_docs>(document: &Document, path: P) { let mut file = File::create(path.as_ref()).expect("create output file"); - file.write_all(docs.as_bytes()).expect("write output file"); + file.write_all(document.to_md().as_bytes()) + .expect("write output file"); +} + +fn parse_module_mapping(m: &str) -> Result<(String, String)> { + let s: Vec<_> = m.split('=').collect(); + let (n, o) = match s.len() { + 1 => { + let mname = s + .get(0) + .ok_or(anyhow!("module name cannot be an empty string"))?; + (mname, mname) + } + 2 => { + let newname = s + .get(0) + .ok_or(anyhow!("new module name cannot be an empty string"))?; + let oldname = s + .get(1) + .ok_or(anyhow!("old module name cannot be an empty string"))?; + (newname, oldname) + } + _ => bail!("invalid module mapping: '{}'", m), + }; + Ok((n.to_string(), o.to_string())) } fn dos2unix(s: &str) -> String { diff --git a/tools/witx/src/abi.rs b/tools/witx/src/abi.rs index 65bee67e1..0e1ca9cb4 100644 --- a/tools/witx/src/abi.rs +++ b/tools/witx/src/abi.rs @@ -11,8 +11,8 @@ //! per-language) into this wasm API. This module is intended to assist with //! this definition. //! -//! Contained within are two primary functions, [`Function::call_wasm`] and -//! [`Function::call_interface`]. These functions implement the two ways to +//! Contained within are two primary functions, [`InterfaceFunc::call_wasm`] and +//! [`InterfaceFunc::call_interface`]. These functions implement the two ways to //! interact with an interface types function, namely calling the raw wasm //! version and calling the high-level version with interface types. These two //! functions are fed a structure that implements [`Bindgen`]. An instance of @@ -20,7 +20,9 @@ //! of how to convert to and from wasm types and interface types. Code //! generators will need to implement the various instructions to support APIs. -use crate::{BuiltinType, Function, Id, IntRepr, NamedType, Param, Type, TypeRef}; +use crate::{ + BuiltinType, Id, IntRepr, InterfaceFunc, InterfaceFuncParam, NamedType, Type, TypeRef, +}; /// Enumerates wasm types used by interface types when lowering/lifting. #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -196,7 +198,7 @@ def_instruction! { /// called rather than a raw wasm function. CallInterface { module: &'a str, - func: &'a Function, + func: &'a InterfaceFunc, } : [func.params.len()] => [func.results.len()], /// Converts a native wasm `i32` to an interface type `s8`. @@ -320,7 +322,11 @@ impl Abi { /// /// Returns an error string if they're not representable or returns `Ok` if /// they're indeed representable. - pub fn validate(&self, _params: &[Param], results: &[Param]) -> Result<(), String> { + pub fn validate( + &self, + _params: &[InterfaceFuncParam], + results: &[InterfaceFuncParam], + ) -> Result<(), String> { assert_eq!(*self, Abi::Preview1); match results.len() { 0 => {} @@ -438,7 +444,7 @@ pub trait Bindgen { fn finish_block(&mut self, operand: Option); } -impl Function { +impl InterfaceFunc { /// Get the WebAssembly type signature for this interface function /// /// The first entry returned is the list of parameters and the second entry @@ -557,7 +563,7 @@ impl Function { .call_wasm(module, self); } - /// This is the dual of [`Function::call_wasm`], except that instead of + /// 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) { @@ -580,7 +586,7 @@ struct Generator<'a, B: Bindgen> { } impl Generator<'_, B> { - fn call_wasm(&mut self, module: &Id, func: &Function) { + 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() { @@ -613,7 +619,7 @@ impl Generator<'_, B> { }); } - fn call_interface(&mut self, module: &Id, func: &Function) { + 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 diff --git a/tools/witx/src/ast.rs b/tools/witx/src/ast.rs index 1f042244e..ee985d302 100644 --- a/tools/witx/src/ast.rs +++ b/tools/witx/src/ast.rs @@ -1,8 +1,8 @@ use crate::Abi; use std::collections::{HashMap, HashSet}; -use std::rc::Rc; +use std::rc::{Rc, Weak}; -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Id(String); impl Id { @@ -39,100 +39,133 @@ impl From<&str> for Id { } #[derive(Debug, Clone)] -pub struct Module { - name: Id, - module_id: ModuleId, - types: Vec>, - type_map: HashMap>, - - funcs: Vec>, - func_map: HashMap>, - - constants: Vec, +pub struct Document { + definitions: Vec, + entries: HashMap, } -#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] -pub struct ModuleId(pub(crate) Rc); - -impl Module { - pub(crate) fn new(name: Id, module_id: ModuleId) -> Module { - Module { - name, - module_id, - types: Default::default(), - type_map: Default::default(), - funcs: Default::default(), - func_map: Default::default(), - constants: Default::default(), +impl Document { + pub(crate) fn new(definitions: Vec, entries: HashMap) -> Self { + Document { + definitions, + entries, } } - - pub fn name(&self) -> &Id { - &self.name - } - - pub fn module_id(&self) -> &ModuleId { - &self.module_id - } - - pub(crate) fn push_type(&mut self, ty: Rc) { - assert!(self.type_map.insert(ty.name.clone(), ty.clone()).is_none()); - self.types.push(ty); - } - - pub(crate) fn push_func(&mut self, func: Rc) { - assert!(self - .func_map - .insert(func.name.clone(), func.clone()) - .is_none()); - self.funcs.push(func); - } - - pub(crate) fn push_constant(&mut self, constant: Constant) { - self.constants.push(constant); - } - pub fn typename(&self, name: &Id) -> Option> { - self.type_map.get(name).cloned() + self.entries.get(name).and_then(|e| match e { + Entry::Typename(nt) => Some(nt.upgrade().expect("always possible to upgrade entry")), + _ => None, + }) } - - pub fn typenames<'a>(&'a self) -> impl Iterator> + 'a { - self.types.iter() + pub fn typenames<'a>(&'a self) -> impl Iterator> + 'a { + self.definitions.iter().filter_map(|d| match d { + Definition::Typename(nt) => Some(nt.clone()), + _ => None, + }) } - /// All of the (unique) types used as "err" variant of results returned from /// functions. pub fn error_types<'a>(&'a self) -> impl Iterator + 'a { let errors: HashSet = self - .funcs() - .filter_map(|f| { - if f.results.len() == 1 { - Some(f.results[0].tref.type_().clone()) - } else { - None - } + .modules() + .flat_map(|m| { + m.funcs() + .filter_map(|f| { + if f.results.len() == 1 { + Some(f.results[0].tref.type_().clone()) + } else { + None + } + }) + .filter_map(|t| match &*t { + Type::Variant(v) => { + let (_ok, err) = v.as_expected()?; + Some(err?.clone()) + } + _ => None, + }) + .collect::>() }) - .filter_map(|t| match &*t { - Type::Variant(v) => { - let (_ok, err) = v.as_expected()?; - Some(err?.clone()) - } - _ => None, - }) - .collect::>(); + .collect(); errors.into_iter() } + pub fn module(&self, name: &Id) -> Option> { + self.entries.get(&name).and_then(|e| match e { + Entry::Module(m) => Some(m.upgrade().expect("always possible to upgrade entry")), + _ => None, + }) + } + pub fn modules<'a>(&'a self) -> impl Iterator> + 'a { + self.definitions.iter().filter_map(|d| match d { + Definition::Module(m) => Some(m.clone()), + _ => None, + }) + } - pub fn func(&self, name: &Id) -> Option> { - self.func_map.get(&name).cloned() + pub fn constants<'a>(&'a self) -> impl Iterator + 'a { + self.definitions.iter().filter_map(|d| match d { + Definition::Constant(c) => Some(c), + _ => None, + }) } +} - pub fn funcs<'a>(&'a self) -> impl Iterator> + 'a { - self.funcs.iter().cloned() +impl PartialEq for Document { + fn eq(&self, rhs: &Document) -> bool { + // For equality, we don't care about the ordering of definitions, + // so we only need to check that the entries map is equal + self.entries == rhs.entries } +} +impl Eq for Document {} - pub fn constants<'a>(&'a self) -> impl Iterator + 'a { - self.constants.iter() +impl std::hash::Hash for Document { + fn hash(&self, state: &mut H) { + std::hash::Hash::hash(&self.definitions, state); + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Definition { + Typename(Rc), + Module(Rc), + Constant(Constant), +} + +#[derive(Debug, Clone)] +pub enum Entry { + Typename(Weak), + Module(Weak), +} + +impl Entry { + pub fn kind(&self) -> &'static str { + match self { + Entry::Typename { .. } => "typename", + Entry::Module { .. } => "module", + } + } +} + +impl PartialEq for Entry { + fn eq(&self, rhs: &Entry) -> bool { + match (self, rhs) { + (Entry::Typename(t), Entry::Typename(t_rhs)) => { + t.upgrade() + .expect("possible to upgrade entry when part of document") + == t_rhs + .upgrade() + .expect("possible to upgrade entry when part of document") + } + (Entry::Module(m), Entry::Module(m_rhs)) => { + m.upgrade() + .expect("possible to upgrade entry when part of document") + == m_rhs + .upgrade() + .expect("possible to upgrade entry when part of document") + } + _ => false, + } } } @@ -156,16 +189,11 @@ impl TypeRef { TypeRef::Value(_) => false, } } - - pub fn type_equal(&self, other: &TypeRef) -> bool { - self.type_().type_equal(other.type_()) - } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct NamedType { pub name: Id, - pub module: ModuleId, pub tref: TypeRef, pub docs: String, } @@ -219,39 +247,6 @@ impl Type { Builtin(_) => "builtin", } } - - pub fn type_equal(&self, other: &Type) -> bool { - match self { - Type::Record(a) => match other { - Type::Record(b) => a.type_equal(b), - _ => false, - }, - Type::Variant(a) => match other { - Type::Variant(b) => a.type_equal(b), - _ => false, - }, - Type::Handle(a) => match other { - Type::Handle(b) => a.type_equal(b), - _ => false, - }, - Type::List(a) => match other { - Type::List(b) => a.type_equal(b), - _ => false, - }, - Type::Pointer(a) => match other { - Type::Pointer(b) => a.type_equal(b), - _ => false, - }, - Type::ConstPointer(a) => match other { - Type::ConstPointer(b) => a.type_equal(b), - _ => false, - }, - Type::Builtin(a) => match other { - Type::Builtin(b) => a == b, - _ => false, - }, - } - } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -375,25 +370,6 @@ impl RecordDatatype { _ => None, } } - - pub fn type_equal(&self, other: &RecordDatatype) -> bool { - // Note that eventually we'll probably want to ignore ABI-style - // differences where the fields are reordered but have the same types. - // That's more of a subtyping-style check, however, and would require a - // bit more infrastructure so we just go for strict equality for now. - self.members.len() == other.members.len() - && self - .members - .iter() - .zip(&other.members) - .all(|(a, b)| a.type_equal(b)) - } -} - -impl RecordMember { - pub fn type_equal(&self, other: &RecordMember) -> bool { - self.name == other.name && self.tref.type_equal(&other.tref) - } } /// A type which represents how values can be one of a set of possible cases. @@ -461,18 +437,6 @@ impl Variant { pub fn is_enum(&self) -> bool { self.cases.iter().all(|c| c.tref.is_none()) } - - pub fn type_equal(&self, other: &Variant) -> bool { - // See the comment in `RecordDatatype::type_equal` for why strict - // positional equality is required here - self.tag_repr == other.tag_repr - && self.cases.len() == other.cases.len() - && self - .cases - .iter() - .zip(&other.cases) - .all(|(a, b)| a.type_equal(b)) - } } /// One of a number of possible types that a `Variant` can take. @@ -487,38 +451,132 @@ pub struct Case { pub docs: String, } -impl Case { - pub fn type_equal(&self, other: &Case) -> bool { - self.name == other.name - && match (&self.tref, &other.tref) { - (Some(a), Some(b)) => a.type_equal(b), - (None, None) => true, - _ => false, - } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct HandleDatatype {} + +#[derive(Debug, Clone)] +pub struct Module { + pub name: Id, + definitions: Vec, + entries: HashMap, + pub docs: String, +} + +impl Module { + pub(crate) fn new( + name: Id, + definitions: Vec, + entries: HashMap, + docs: String, + ) -> Self { + Module { + name, + definitions, + entries, + docs, + } + } + pub fn import(&self, name: &Id) -> Option> { + self.entries.get(name).and_then(|e| match e { + ModuleEntry::Import(d) => Some(d.upgrade().expect("always possible to upgrade entry")), + _ => None, + }) + } + pub fn imports<'a>(&'a self) -> impl Iterator> + 'a { + self.definitions.iter().filter_map(|d| match d { + ModuleDefinition::Import(d) => Some(d.clone()), + _ => None, + }) + } + pub fn func(&self, name: &Id) -> Option> { + self.entries.get(name).and_then(|e| match e { + ModuleEntry::Func(d) => Some(d.upgrade().expect("always possible to upgrade entry")), + _ => None, + }) + } + pub fn funcs<'a>(&'a self) -> impl Iterator> + 'a { + self.definitions.iter().filter_map(|d| match d { + ModuleDefinition::Func(d) => Some(d.clone()), + _ => None, + }) + } +} + +impl PartialEq for Module { + fn eq(&self, rhs: &Module) -> bool { + // For equality, we don't care about the ordering of definitions, + // so we only need to check that the entries map is equal + self.name == rhs.name && self.entries == rhs.entries && self.docs == rhs.docs + } +} +impl Eq for Module {} + +impl std::hash::Hash for Module { + fn hash(&self, state: &mut H) { + std::hash::Hash::hash(&self.name, state); + std::hash::Hash::hash(&self.definitions, state); + std::hash::Hash::hash(&self.docs, state); } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct HandleDatatype {} +pub enum ModuleDefinition { + Import(Rc), + Func(Rc), +} -impl HandleDatatype { - pub fn type_equal(&self, _other: &HandleDatatype) -> bool { - true +#[derive(Debug, Clone)] +pub enum ModuleEntry { + Import(Weak), + Func(Weak), +} + +impl PartialEq for ModuleEntry { + fn eq(&self, rhs: &ModuleEntry) -> bool { + match (self, rhs) { + (ModuleEntry::Import(i), ModuleEntry::Import(i_rhs)) => { + i.upgrade() + .expect("always possible to upgrade moduleentry when part of module") + == i_rhs + .upgrade() + .expect("always possible to upgrade moduleentry when part of module") + } + (ModuleEntry::Func(i), ModuleEntry::Func(i_rhs)) => { + i.upgrade() + .expect("always possible to upgrade moduleentry when part of module") + == i_rhs + .upgrade() + .expect("always possible to upgrade moduleentry when part of module") + } + _ => false, + } } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Function { +pub struct ModuleImport { + pub name: Id, + pub variant: ModuleImportVariant, + pub docs: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum ModuleImportVariant { + Memory, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct InterfaceFunc { pub abi: Abi, pub name: Id, - pub params: Vec, - pub results: Vec, + pub params: Vec, + pub results: Vec, pub noreturn: bool, pub docs: String, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Param { +pub struct InterfaceFuncParam { pub name: Id, pub tref: TypeRef, pub docs: String, diff --git a/tools/witx/src/docs/ast.rs b/tools/witx/src/docs/ast.rs index 4bca0b02b..a295a6756 100644 --- a/tools/witx/src/docs/ast.rs +++ b/tools/witx/src/docs/ast.rs @@ -1,115 +1,67 @@ -use super::md::{MdFunc, MdHeading, MdNamedType, MdNodeRef, MdSection, ToMarkdown}; -use crate::{ast::*, layout::Layout}; -use std::collections::{btree_map::Entry, BTreeMap, HashMap}; +use super::{ + md::{MdFunc, MdHeading, MdNamedType, MdNodeRef, MdSection, ToMarkdown}, + Documentation, +}; +use crate::{ + ast::*, + layout::Layout, + polyfill::{FuncPolyfill, ModulePolyfill, ParamPolyfill, Polyfill, TypePolyfill}, + RepEquality, +}; +use std::collections::HashMap; fn heading_from_node(node: &MdNodeRef, levels_down: usize) -> MdHeading { MdHeading::new_header(node.borrow().ancestors().len() + levels_down) } -pub(super) fn modules(node: MdNodeRef, all_modules: &[&Module]) { - // Generate a set, transitively, of all types/constants used by these modules. - let mut all_types = BTreeMap::new(); - let mut constants_by_name = HashMap::new(); - for module in all_modules { - for ty in module.typenames() { - all_types.insert((&ty.name, &ty.module), &**ty); - add_types(&mut all_types, &ty.tref); - } +impl ToMarkdown for Document { + fn generate(&self, node: MdNodeRef) { + let heading = heading_from_node(&node, 1); + let types = node.new_child(MdSection::new(heading, "Types")); - for c in module.constants() { - constants_by_name - .entry((&c.ty, module.module_id())) - .or_insert(Vec::new()) - .push(c); + let mut constants_by_name = HashMap::new(); + for c in self.constants() { + constants_by_name.entry(&c.ty).or_insert(Vec::new()).push(c); } - } - - // Then render the information for all of the types... - - let heading = heading_from_node(&node, 1); - let types = node.new_child(MdSection::new(heading, "Types")); - for (key, d) in all_types { - let name = d.name.as_str(); - let child = types.new_child(MdNamedType::new( - heading.new_level_down(), - name, - name, - format!( - "{}\nSize: {}\n\nAlignment: {}\n", - &d.docs, - &d.mem_size(), - &d.mem_align() - ) - .as_str(), - )); - if let Some(constants) = constants_by_name.remove(&key) { - let heading = heading_from_node(&child, 1); - child.new_child(MdSection::new(heading, "Constants")); - for constant in constants { - child.new_child(MdNamedType::new( - MdHeading::new_bullet(), - format!("{}.{}", name, constant.name.as_str()).as_str(), - constant.name.as_str(), - &constant.docs, - )); - } - } - d.generate(child.clone()); - } - - let modules = node.new_child(MdSection::new(heading, "Modules")); - for d in all_modules { - let heading = heading.new_level_down(); - let mut content = MdSection::new(heading, d.name().as_str()); - content.id = Some(d.name().as_str().to_owned()); - let child = modules.new_child(content); - - // d.generate(child.clone()); - let heading = heading.new_level_down(); - let funcs = child - .clone() - .new_child(MdSection::new(heading, "Functions")); - for func in d.funcs() { - let name = func.name.as_str(); - let child = funcs.new_child(MdFunc::new( + for d in self.typenames() { + let name = d.name.as_str(); + let child = types.new_child(MdNamedType::new( heading.new_level_down(), name, name, - &func.docs, + format!( + "{}\nSize: {}\n\nAlignment: {}\n", + &d.docs, + &d.mem_size(), + &d.mem_align() + ) + .as_str(), )); - func.generate(child.clone()); - } - } - - fn add_types<'a>(types: &mut BTreeMap<(&'a Id, &'a ModuleId), &'a NamedType>, ty: &'a TypeRef) { - let ty = match ty { - TypeRef::Name(name) => { - match types.entry((&name.name, &name.module)) { - Entry::Occupied(_) => return, - Entry::Vacant(v) => { - v.insert(name); - } + if let Some(constants) = constants_by_name.remove(&d.name) { + let heading = heading_from_node(&child, 1); + child.new_child(MdSection::new(heading, "Constants")); + for constant in constants { + child.new_child(MdNamedType::new( + MdHeading::new_bullet(), + format!("{}.{}", name, constant.name.as_str()).as_str(), + constant.name.as_str(), + &constant.docs, + )); } - return add_types(types, &name.tref); } - TypeRef::Value(ty) => ty, - }; + d.generate(child.clone()); + } - match &**ty { - Type::Record(r) => { - for member in r.members.iter() { - add_types(types, &member.tref); - } - } - Type::Variant(v) => { - for ty in v.cases.iter().filter_map(|c| c.tref.as_ref()) { - add_types(types, ty); - } - } - Type::List(t) | Type::ConstPointer(t) | Type::Pointer(t) => add_types(types, t), - Type::Handle(_) | Type::Builtin(_) => {} + let modules = node.new_child(MdSection::new(heading, "Modules")); + for d in self.modules() { + let mut content = MdSection::new(heading.new_level_down(), d.name.as_str()); + content.id = Some(d.name.as_str().to_owned()); + let child = modules.new_child(content); + d.generate(child.clone()); } + + assert!(constants_by_name.is_empty()); } } @@ -235,7 +187,40 @@ impl ToMarkdown for HandleDatatype { } } -impl ToMarkdown for Function { +impl ToMarkdown for Module { + fn generate(&self, node: MdNodeRef) { + let heading = heading_from_node(&node, 1); + let imports = node.new_child(MdSection::new(heading, "Imports")); + for import in self.imports() { + let child = imports.new_child(MdSection::new(heading.new_level_down(), "")); + import.generate(child.clone()); + } + + let funcs = node.new_child(MdSection::new(heading, "Functions")); + for func in self.funcs() { + let name = func.name.as_str(); + let child = funcs.new_child(MdFunc::new( + heading.new_level_down(), + name, + name, + &func.docs, + )); + func.generate(child.clone()); + } + } +} + +impl ToMarkdown for ModuleImport { + fn generate(&self, node: MdNodeRef) { + match self.variant { + ModuleImportVariant::Memory => { + node.content_ref_mut::().title = "Memory".to_owned(); + } + } + } +} + +impl ToMarkdown for InterfaceFunc { fn generate(&self, node: MdNodeRef) { let heading = heading_from_node(&node, 1); node.new_child(MdSection::new(heading, "Params")); @@ -282,7 +267,7 @@ impl ToMarkdown for Function { } } -impl ToMarkdown for Param { +impl ToMarkdown for InterfaceFuncParam { fn generate(&self, node: MdNodeRef) { self.tref.generate(node.clone()); node.content_ref_mut::().docs = self.docs.clone(); @@ -365,3 +350,154 @@ impl TypeRef { } } } + +// TODO +// Generate Markdown tree for the polyfill +impl Documentation for Polyfill { + fn to_md(&self) -> String { + let module_docs = self + .modules + .iter() + .map(|m| m.to_md()) + .collect::>() + .join("\n"); + let type_docs = self + .type_polyfills() + .iter() + .filter_map(|t| { + if t.repeq() == RepEquality::Eq { + None + } else { + Some(t.to_md()) + } + }) + .collect::>() + .join("\n"); + format!( + "# Modules\n{}\n# Type Conversions\n{}\n", + module_docs, type_docs + ) + } +} + +impl Documentation for ModulePolyfill { + fn to_md(&self) -> String { + format!( + "## `{}` in terms of `{}`\n{}", + self.new.name.as_str(), + self.old.name.as_str(), + self.funcs + .iter() + .map(|f| f.to_md()) + .collect::>() + .join("\n"), + ) + } +} + +impl Documentation for FuncPolyfill { + fn to_md(&self) -> String { + if self.full_compat() { + format!("* `{}`: full compatibility", self.new.name.as_str()) + } else { + let name = if self.new.name != self.old.name { + format!( + "* `{}` => `{}`", + self.old.name.as_str(), + self.new.name.as_str() + ) + } else { + format!("* `{}`", self.new.name.as_str()) + }; + let mut contents = Vec::new(); + for p in self.mapped_params.iter() { + contents.push(if !p.full_compat() { + format!("param {}", p.to_md()) + } else { + format!("param `{}`: compatible", p.new.name.as_str()) + }) + } + for u in self.unknown_params.iter() { + contents.push(format!( + "{} param `{}`: no corresponding result!", + u.which(), + u.param().name.as_str() + )) + } + for r in self.mapped_results.iter() { + contents.push(if !r.full_compat() { + format!("result {}", r.to_md()) + } else { + format!("result `{}`: compatible", r.new.name.as_str()) + }) + } + for u in self.unknown_results.iter() { + contents.push(format!( + "{} result `{}`: no corresponding result!", + u.which(), + u.param().name.as_str() + )) + } + let contents = if contents.is_empty() { + String::new() + } else { + format!(":\n - {}", contents.join("\n - ")) + }; + format!("{}{}", name, contents) + } + } +} + +impl Documentation for ParamPolyfill { + fn to_md(&self) -> String { + let name = if self.new.name != self.old.name { + format!( + "`{}` => `{}`", + self.old.name.as_str(), + self.new.name.as_str() + ) + } else { + format!("`{}`", self.new.name.as_str()) + }; + let repr = match self.repeq() { + RepEquality::Eq => "compatible types".to_string(), + RepEquality::Superset => format!( + "`{}` is superset-compatible with `{}`", + self.old.tref.type_name(), + self.new.tref.type_name() + ), + RepEquality::NotEq => format!( + "`{}` is incompatible with new `{}`", + self.old.tref.type_name(), + self.new.tref.type_name() + ), + }; + format!("{}: {}", name, repr) + } +} + +impl Documentation for TypePolyfill { + fn to_md(&self) -> String { + fn repeq_name(r: RepEquality) -> &'static str { + match r { + RepEquality::Eq => ": compatible", + RepEquality::Superset => ": superset", + RepEquality::NotEq => "", + } + } + match self { + TypePolyfill::OldToNew(o, n) => format!( + "* old `{}` => new `{}`{}", + o.type_name(), + n.type_name(), + repeq_name(self.repeq()) + ), + TypePolyfill::NewToOld(n, o) => format!( + "* new `{}` => old `{}`{}", + n.type_name(), + o.type_name(), + repeq_name(self.repeq()) + ), + } + } +} diff --git a/tools/witx/src/docs/mod.rs b/tools/witx/src/docs/mod.rs index 00f71cc2f..8f082cba5 100644 --- a/tools/witx/src/docs/mod.rs +++ b/tools/witx/src/docs/mod.rs @@ -1,13 +1,18 @@ mod ast; mod md; -use crate::Module; -use md::{MdNodeRef, MdRoot}; +use crate::ast::Document; +use md::{MdNodeRef, MdRoot, ToMarkdown}; use std::{ collections::{hash_map, HashSet}, iter::FromIterator, }; +/// Enables generating Markdown formatted content. +pub trait Documentation { + fn to_md(&self) -> String; +} + /// Helper function which given input `text` and a `HashSet` of existing links converts /// any slice of the form '`{link}`' into either /// 1. "[`{link}`](#{md_link})" where `md_link` is `link` with "::" replaced with "." @@ -54,33 +59,29 @@ fn parse_links>(text: S, existing_links: &HashSet) -> Stri parsed_text } -pub fn document<'a>(modules: impl IntoIterator) -> String { - let modules = modules.into_iter().collect::>(); - _document(&modules) -} - -fn _document(modules: &[&Module]) -> String { - let root = MdNodeRef::new(MdRoot::default()); - ast::modules(root.clone(), &modules); - - // Get all children of the `root` element. - let children = root.borrow().children(); - // Gather all existing links in the document into a set. - let existing_links: HashSet = HashSet::from_iter( - children - .iter() - .filter_map(|x| x.any_ref().id().map(String::from)), - ); - // Traverse each docs section of each child, and parse links - // logging a warning in case the generated is invalid. - for child in children { - let docs_with_links = child - .any_ref() - .docs() - .map(|docs| parse_links(docs, &existing_links)); - if let Some(docs) = docs_with_links { - child.any_ref_mut().set_docs(&docs); +impl Documentation for Document { + fn to_md(&self) -> String { + let root = MdNodeRef::new(MdRoot::default()); + self.generate(root.clone()); + // Get all children of the `root` element. + let children = root.borrow().children(); + // Gather all existing links in the document into a set. + let existing_links: HashSet = HashSet::from_iter( + children + .iter() + .filter_map(|x| x.any_ref().id().map(String::from)), + ); + // Traverse each docs section of each child, and parse links + // logging a warning in case the generated is invalid. + for child in children { + let docs_with_links = child + .any_ref() + .docs() + .map(|docs| parse_links(docs, &existing_links)); + if let Some(docs) = docs_with_links { + child.any_ref_mut().set_docs(&docs); + } } + format!("{}", root) } - format!("{}", root) } diff --git a/tools/witx/src/lib.rs b/tools/witx/src/lib.rs index ebdf9158a..a0eba6a17 100644 --- a/tools/witx/src/lib.rs +++ b/tools/witx/src/lib.rs @@ -10,6 +10,12 @@ mod io; mod layout; /// Witx syntax parsing from SExprs pub mod parser; +/// Calculate required polyfill between interfaces +pub mod polyfill; +/// Render ast to text +mod render; +/// Representational equality of types +mod representation; /// Resolve toplevel `use` declarations across files mod toplevel; /// Validate declarations into ast @@ -17,23 +23,25 @@ mod validate; pub use abi::*; pub use ast::*; -pub use docs::document; +pub use docs::Documentation; pub use io::{Filesystem, MockFs, WitxIo}; pub use layout::{Layout, RecordMemberLayout, SizeAlign}; -pub use validate::{ModuleValidation, ValidationError}; +pub use render::SExpr; +pub use representation::{RepEquality, Representable}; +pub use validate::{DocValidation, ValidationError}; use std::path::{Path, PathBuf}; use thiserror::Error; /// Load a witx document from the filesystem -pub fn load(path: impl AsRef) -> Result { - toplevel::parse_witx(path) +pub fn load>(paths: &[P]) -> Result { + toplevel::parse_witx(paths) } /// Parse a witx document from a str. `(use ...)` directives are not permitted. -pub fn parse(source: &str) -> Result { +pub fn parse(source: &str) -> Result { let mockfs = MockFs::new(&[("-", source)]); - toplevel::parse_witx_with("-", &mockfs) + toplevel::parse_witx_with(&[Path::new("-")], &mockfs) } /// Location in the source text diff --git a/tools/witx/src/parser.rs b/tools/witx/src/parser.rs index 97dde4d78..33cf745ff 100644 --- a/tools/witx/src/parser.rs +++ b/tools/witx/src/parser.rs @@ -28,7 +28,6 @@ mod kw { wast::custom_keyword!(error); wast::custom_keyword!(expected); wast::custom_keyword!(flags); - wast::custom_keyword!(from); wast::custom_keyword!(handle); wast::custom_keyword!(list); wast::custom_keyword!(noreturn); @@ -200,92 +199,61 @@ pub struct Documented<'a, T> { impl<'a, T: Parse<'a>> Parse<'a> for Documented<'a, T> { fn parse(parser: Parser<'a>) -> Result { + let _r1 = parser.register_annotation("witx"); + let _r1 = parser.register_annotation("interface"); let comments = parser.parse()?; let item = parser.parse()?; Ok(Documented { comments, item }) } } -#[derive(Debug, Clone)] -pub struct TopLevelModule<'a> { - pub decls: Vec>>, - pub module_name: Option>, - pub functions: Vec>>, +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TopLevelDocument<'a> { + pub items: Vec>>, } -impl<'a> Parse<'a> for TopLevelModule<'a> { +impl<'a> Parse<'a> for TopLevelDocument<'a> { fn parse(parser: Parser<'a>) -> Result { - let _r1 = parser.register_annotation("witx"); - let _r2 = parser.register_annotation("interface"); - - let mut decls = Vec::new(); - let mut functions = Vec::new(); - let mut module_name = None; - - let mut comments = parser.parse()?; - loop { - if parser.peek2::() - || parser.peek2::() - || 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() { - functions.push(Documented { - comments: parser.parse()?, - item: p.parens(|p| p.parse())?, - }); - } - Ok(()) - })?; + let mut items = Vec::new(); + while !parser.is_empty() { + items.push(parser.parse()?); } - - Ok(TopLevelModule { - decls, - module_name, - functions, - }) + Ok(TopLevelDocument { items }) } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum TopLevelSyntax<'a> { Decl(DeclSyntax<'a>), - Use(UseSyntax<'a>), + Use(&'a str), } impl<'a> Parse<'a> for TopLevelSyntax<'a> { fn parse(parser: Parser<'a>) -> Result { - if parser.peek::() { - Ok(TopLevelSyntax::Use(parser.parse()?)) - } else { - Ok(TopLevelSyntax::Decl(parser.parse()?)) - } + parser.parens(|p| { + if p.peek::() { + p.parse::()?; + Ok(TopLevelSyntax::Use(p.parse()?)) + } else { + Ok(TopLevelSyntax::Decl(p.parse()?)) + } + }) } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum DeclSyntax<'a> { Typename(TypenameSyntax<'a>), + Module(ModuleSyntax<'a>), Const(Documented<'a, ConstSyntax<'a>>), } impl<'a> Parse<'a> for DeclSyntax<'a> { fn parse(parser: Parser<'a>) -> Result { let mut l = parser.lookahead1(); - if l.peek::() { + if l.peek::() { + Ok(DeclSyntax::Module(parser.parse()?)) + } else if l.peek::() { Ok(DeclSyntax::Typename(parser.parse()?)) } else if l.peek::() { Ok(DeclSyntax::Const(parser.parse()?)) @@ -295,44 +263,6 @@ impl<'a> Parse<'a> for DeclSyntax<'a> { } } -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct UseSyntax<'a> { - pub names: UsedNames<'a>, - pub from: wast::Id<'a>, -} - -impl<'a> Parse<'a> for UseSyntax<'a> { - fn parse(parser: Parser<'a>) -> Result { - parser.parse::()?; - let names = parser.parse()?; - parser.parse::()?; - let from = parser.parse()?; - Ok(UseSyntax { names, from }) - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum UsedNames<'a> { - List(Vec>), - All(wast::Span), -} - -impl<'a> Parse<'a> for UsedNames<'a> { - fn parse(parser: Parser<'a>) -> Result { - wast::custom_reserved!(star = "*"); - if parser.peek::() { - let t = parser.parse::()?; - return Ok(UsedNames::All(t.0)); - } - let mut names = Vec::new(); - names.push(parser.parse()?); - while !parser.peek::() { - names.push(parser.parse()?); - } - Ok(UsedNames::List(names)) - } -} - #[derive(Debug, Clone, PartialEq, Eq)] pub struct TypenameSyntax<'a> { pub ident: wast::Id<'a>, @@ -665,8 +595,87 @@ impl<'a> Parse<'a> for HandleSyntax { } } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ModuleSyntax<'a> { + pub name: wast::Id<'a>, + pub decls: Vec>>, +} + +impl<'a> Parse<'a> for ModuleSyntax<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + let name = parser.parse()?; + let mut decls = Vec::new(); + while !parser.is_empty() { + decls.push(parser.parse()?); + } + Ok(ModuleSyntax { name, decls }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ModuleDeclSyntax<'a> { + Import(ModuleImportSyntax<'a>), + Func(InterfaceFuncSyntax<'a>), +} + +impl<'a> Parse<'a> for ModuleDeclSyntax<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parens(|p| { + let mut l = p.lookahead1(); + if l.peek::() { + Ok(ModuleDeclSyntax::Import(p.parse()?)) + } else if l.peek::() { + Ok(ModuleDeclSyntax::Func(p.parse()?)) + } else { + Err(l.error()) + } + }) + } +} + +#[derive(Debug, Clone)] +pub struct ModuleImportSyntax<'a> { + pub name: &'a str, + pub name_loc: wast::Span, + pub type_: ImportTypeSyntax, +} + +impl<'a> Parse<'a> for ModuleImportSyntax<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parse::()?; + let name_loc = parser.cur_span(); + Ok(ModuleImportSyntax { + name: parser.parse()?, + name_loc, + type_: parser.parens(|p| p.parse())?, + }) + } +} + +impl PartialEq for ModuleImportSyntax<'_> { + fn eq(&self, other: &ModuleImportSyntax<'_>) -> bool { + // skip the `name_loc` field + self.name == other.name && self.type_ == other.type_ + } +} + +impl Eq for ModuleImportSyntax<'_> {} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ImportTypeSyntax { + Memory, +} + +impl Parse<'_> for ImportTypeSyntax { + fn parse(parser: Parser<'_>) -> Result { + parser.parse::()?; + Ok(ImportTypeSyntax::Memory) + } +} + #[derive(Debug, Clone)] -pub struct FunctionSyntax<'a> { +pub struct InterfaceFuncSyntax<'a> { pub export: &'a str, pub export_loc: wast::Span, pub params: Vec>>, @@ -674,7 +683,7 @@ pub struct FunctionSyntax<'a> { pub noreturn: bool, } -impl<'a> Parse<'a> for FunctionSyntax<'a> { +impl<'a> Parse<'a> for InterfaceFuncSyntax<'a> { fn parse(parser: Parser<'a>) -> Result { parser.parse::()?; parser.parse::()?; @@ -689,27 +698,27 @@ impl<'a> Parse<'a> for FunctionSyntax<'a> { let mut noreturn = false; while !parser.is_empty() { - let func_field = parser.parse::>()?; + let func_field = parser.parse::>()?; match func_field.item { - FunctionField::Param(item) => { + InterfaceFuncField::Param(item) => { params.push(Documented { comments: func_field.comments, item, }); } - FunctionField::Result(item) => { + InterfaceFuncField::Result(item) => { results.push(Documented { comments: func_field.comments, item, }); } - FunctionField::Noreturn => { + InterfaceFuncField::Noreturn => { noreturn = true; } } } - Ok(FunctionSyntax { + Ok(InterfaceFuncSyntax { export, export_loc, params, @@ -719,25 +728,24 @@ impl<'a> Parse<'a> for FunctionSyntax<'a> { } } -enum FunctionField<'a> { +enum InterfaceFuncField<'a> { Param(FieldSyntax<'a>), Result(FieldSyntax<'a>), Noreturn, } - -impl<'a> Parse<'a> for FunctionField<'a> { +impl<'a> Parse<'a> for InterfaceFuncField<'a> { fn parse(parser: Parser<'a>) -> Result { parser.parens(|p| { let mut l = p.lookahead1(); if l.peek::() { parser.parse::()?; - Ok(FunctionField::Param(FieldSyntax { + Ok(InterfaceFuncField::Param(FieldSyntax { name: parser.parse()?, type_: parser.parse()?, })) } else if l.peek::() { parser.parse::()?; - Ok(FunctionField::Result(FieldSyntax { + Ok(InterfaceFuncField::Result(FieldSyntax { name: parser.parse()?, type_: parser.parse()?, })) @@ -746,7 +754,7 @@ impl<'a> Parse<'a> for FunctionField<'a> { let mut l = parser.lookahead1(); if l.peek::() { parser.parse::()?; - Ok(FunctionField::Noreturn) + Ok(InterfaceFuncField::Noreturn) } else { Err(l.error()) } @@ -756,3 +764,15 @@ impl<'a> Parse<'a> for FunctionField<'a> { }) } } + +impl PartialEq for InterfaceFuncSyntax<'_> { + fn eq(&self, other: &InterfaceFuncSyntax<'_>) -> bool { + // skip the `export_loc` field + self.export == other.export + && self.params == other.params + && self.results == other.results + && self.noreturn == other.noreturn + } +} + +impl Eq for InterfaceFuncSyntax<'_> {} diff --git a/tools/witx/src/polyfill.rs b/tools/witx/src/polyfill.rs new file mode 100644 index 000000000..c779cfa4e --- /dev/null +++ b/tools/witx/src/polyfill.rs @@ -0,0 +1,255 @@ +use crate::{ + Document, Id, InterfaceFunc, InterfaceFuncParam, Module, RepEquality, Representable, Type, + TypeRef, +}; +use std::collections::{HashMap, HashSet}; +use std::rc::Rc; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum PolyfillError { + #[error("Module not present: {name:?}")] + ModuleNotPresent { name: Id }, + #[error("Function not present: {name:?}")] + FuncNotPresent { module: Id, name: Id }, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Polyfill { + pub modules: Vec, +} + +impl Polyfill { + pub fn new( + new: &Document, + old: &Document, + module_mapping: &HashMap, // Will need a more sophisticated mapping - what about function names, argument names? + ) -> Result { + let mut modules = Vec::new(); + for (newname, oldname) in module_mapping { + let newname = Id::new(newname); + let oldname = Id::new(oldname); + let newmod = new + .module(&newname) + .ok_or_else(|| PolyfillError::ModuleNotPresent { name: newname })?; + let oldmod = old + .module(&oldname) + .ok_or_else(|| PolyfillError::ModuleNotPresent { name: oldname })?; + modules.push(ModulePolyfill::new(newmod, oldmod)?); + } + Ok(Polyfill { modules }) + } + + pub fn type_polyfills(&self) -> HashSet { + self.modules + .iter() + .map(|m| m.type_polyfills()) + .flatten() + .collect() + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ModulePolyfill { + pub new: Rc, + pub old: Rc, + pub funcs: Vec, +} + +impl ModulePolyfill { + pub fn new(new: Rc, old: Rc) -> Result { + let mut funcs = Vec::new(); + for oldfunc in old.funcs() { + let newfunc = new + .func(&oldfunc.name) + .ok_or_else(|| PolyfillError::FuncNotPresent { + module: new.name.clone(), + name: oldfunc.name.clone(), + })?; + funcs.push(FuncPolyfill::new(newfunc, oldfunc)); + } + Ok(ModulePolyfill { new, old, funcs }) + } + pub fn type_polyfills(&self) -> HashSet { + self.funcs + .iter() + .map(|f| f.type_polyfills()) + .flatten() + .collect() + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct FuncPolyfill { + pub new: Rc, + pub old: Rc, + pub mapped_params: Vec, + pub unknown_params: Vec, + pub mapped_results: Vec, + pub unknown_results: Vec, +} + +impl FuncPolyfill { + pub fn new(new: Rc, old: Rc) -> FuncPolyfill { + let mut mapped_params = Vec::new(); + let mut unknown_params = Vec::new(); + + // Old function is called. Need to map each of its parameters to the new function: + for old_param in old.params.iter() { + if let Some(new_param) = new.params.iter().find(|p| p.name == old_param.name) { + mapped_params.push(ParamPolyfill::param(new_param.clone(), old_param.clone())) + } else { + unknown_params.push(ParamUnknown::Old(old_param.clone())); + } + } + // Are any new params not covered by the old params? + // This search is O(n^2), but n ought to be small. + for new_param in new.params.iter() { + if mapped_params + .iter() + .find(|m| m.new.name == new_param.name) + .is_none() + { + unknown_params.push(ParamUnknown::New(new_param.clone())); + } + } + + let mut mapped_results = Vec::new(); + let mut unknown_results = Vec::new(); + + // New function has returned. Need to map each of its results to the old function: + for new_result in new.results.iter() { + if let Some(old_result) = old.results.iter().find(|p| p.name == new_result.name) { + mapped_results.push(ParamPolyfill::result( + new_result.clone(), + old_result.clone(), + )) + } else { + unknown_results.push(ParamUnknown::New(new_result.clone())); + } + } + + // Are any old results not covered by the new results? + for old_result in old.results.iter() { + if mapped_results + .iter() + .find(|m| m.old.name == old_result.name) + .is_none() + { + unknown_results.push(ParamUnknown::Old(old_result.clone())); + } + } + + FuncPolyfill { + new, + old, + mapped_params, + unknown_params, + mapped_results, + unknown_results, + } + } + + pub fn full_compat(&self) -> bool { + self.new.name == self.old.name + && self.mapped_params.iter().all(|p| p.full_compat()) + && self.unknown_params.is_empty() + && self.mapped_results.iter().all(|p| p.full_compat()) + && self.unknown_results.is_empty() + } + + pub fn type_polyfills(&self) -> HashSet { + self.mapped_params + .iter() + .map(|p| p.type_polyfill.clone()) + .chain(self.mapped_results.iter().map(|p| p.type_polyfill.clone())) + .collect() + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ParamPolyfill { + pub new: InterfaceFuncParam, + pub old: InterfaceFuncParam, + pub type_polyfill: TypePolyfill, +} + +impl ParamPolyfill { + fn common_denominator(a: TypeRef, b: TypeRef) -> (TypeRef, TypeRef) { + match (&a, &b) { + (TypeRef::Value(va), TypeRef::Value(vb)) => match (&**va, &**vb) { + (Type::List(a), Type::List(b)) => (a.clone(), b.clone()), + (Type::Pointer(a), Type::Pointer(b)) => (a.clone(), b.clone()), + (Type::ConstPointer(a), Type::ConstPointer(b)) => (a.clone(), b.clone()), + _ => (a, b), + }, + _ => (a, b), + } + } + + pub fn param(new: InterfaceFuncParam, old: InterfaceFuncParam) -> Self { + let (told, tnew) = Self::common_denominator(old.tref.clone(), new.tref.clone()); + // Call new param type with old param: + let type_polyfill = TypePolyfill::OldToNew(told, tnew); + ParamPolyfill { + new, + old, + type_polyfill, + } + } + + pub fn result(new: InterfaceFuncParam, old: InterfaceFuncParam) -> Self { + let (told, tnew) = Self::common_denominator(old.tref.clone(), new.tref.clone()); + // Return old result type from new result: + let type_polyfill = TypePolyfill::NewToOld(tnew, told); + ParamPolyfill { + new, + old, + type_polyfill, + } + } + + pub fn full_compat(&self) -> bool { + self.new.name == self.old.name && self.repeq() == RepEquality::Eq + } + + pub fn repeq(&self) -> RepEquality { + self.type_polyfill.repeq() + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum ParamUnknown { + Old(InterfaceFuncParam), + New(InterfaceFuncParam), +} + +impl ParamUnknown { + pub fn which(&self) -> &'static str { + match self { + ParamUnknown::Old { .. } => "old", + ParamUnknown::New { .. } => "new", + } + } + pub fn param(&self) -> &InterfaceFuncParam { + match self { + ParamUnknown::Old(p) => &p, + ParamUnknown::New(p) => &p, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum TypePolyfill { + NewToOld(TypeRef, TypeRef), + OldToNew(TypeRef, TypeRef), +} + +impl TypePolyfill { + pub fn repeq(&self) -> RepEquality { + match self { + TypePolyfill::NewToOld(new, old) => old.type_().representable(&new.type_()), + TypePolyfill::OldToNew(old, new) => new.type_().representable(&old.type_()), + } + } +} diff --git a/tools/witx/src/render.rs b/tools/witx/src/render.rs new file mode 100644 index 000000000..a740e282c --- /dev/null +++ b/tools/witx/src/render.rs @@ -0,0 +1,320 @@ +use crate::ast::*; +use std::fmt; + +impl fmt::Display for Document { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for d in self.typenames() { + write!(f, "{}\n", d.to_sexpr())?; + } + for m in self.modules() { + write!(f, "{}\n", m.to_sexpr())?; + } + Ok(()) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum SExpr { + Vec(Vec), + Word(String), + Ident(String), + Quote(String), + /// Short for Annotation + Annot(String), + /// Doc comment + Docs(String, Box), +} + +impl fmt::Display for SExpr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + SExpr::Vec(vs) => { + write!(f, "(")?; + let mut vss = Vec::new(); + for v in vs { + vss.push(format!("{}", v)); + } + f.write_str(&vss.join(" "))?; + write!(f, ")") + } + SExpr::Word(w) => write!(f, "{}", w), + SExpr::Ident(i) => write!(f, "${}", i), + SExpr::Quote(q) => write!(f, "\"{}\"", q), + SExpr::Annot(a) => write!(f, "@{}", a), + SExpr::Docs(d, s) => write!(f, "(;; {} ;) {}", d, s), + } + } +} + +impl SExpr { + pub fn word(s: &str) -> SExpr { + SExpr::Word(s.to_string()) + } + pub fn ident(s: &str) -> SExpr { + SExpr::Ident(s.to_string()) + } + pub fn quote(s: &str) -> SExpr { + SExpr::Quote(s.to_string()) + } + pub fn annot(s: &str) -> SExpr { + SExpr::Annot(s.to_string()) + } + pub fn docs(d: &str, s: SExpr) -> SExpr { + if d.is_empty() { + s + } else { + SExpr::Docs(d.to_string(), Box::new(s)) + } + } +} + +impl Id { + pub fn to_sexpr(&self) -> SExpr { + SExpr::ident(self.as_str()) + } +} + +impl BuiltinType { + pub fn to_sexpr(&self) -> SExpr { + match self { + BuiltinType::Char => SExpr::word("char"), + BuiltinType::U8 { lang_c_char: true } => { + SExpr::Vec(vec![SExpr::annot("witx"), SExpr::word("char8")]) + } + BuiltinType::U8 { lang_c_char: false } => SExpr::word("u8"), + BuiltinType::U16 => SExpr::word("u16"), + BuiltinType::U32 { + lang_ptr_size: false, + } => SExpr::word("u32"), + BuiltinType::U32 { + lang_ptr_size: true, + } => SExpr::Vec(vec![SExpr::annot("witx"), SExpr::word("usize")]), + BuiltinType::U64 => SExpr::word("u64"), + BuiltinType::S8 => SExpr::word("s8"), + BuiltinType::S16 => SExpr::word("s16"), + BuiltinType::S32 => SExpr::word("s32"), + BuiltinType::S64 => SExpr::word("s64"), + BuiltinType::F32 => SExpr::word("f32"), + BuiltinType::F64 => SExpr::word("f64"), + } + } +} + +impl NamedType { + pub fn to_sexpr(&self) -> SExpr { + let body = self.tref.to_sexpr(); + SExpr::docs( + &self.docs, + SExpr::Vec(vec![SExpr::word("typename"), self.name.to_sexpr(), body]), + ) + } +} + +impl TypeRef { + pub fn to_sexpr(&self) -> SExpr { + match self { + TypeRef::Name(n) => n.name.to_sexpr(), + TypeRef::Value(v) => v.to_sexpr(), + } + } +} + +impl Type { + pub fn to_sexpr(&self) -> SExpr { + match self { + Type::Record(a) => a.to_sexpr(), + Type::Variant(a) => a.to_sexpr(), + Type::Handle(a) => a.to_sexpr(), + Type::List(a) => SExpr::Vec(vec![SExpr::word("list"), a.to_sexpr()]), + Type::Pointer(p) => SExpr::Vec(vec![ + SExpr::annot("witx"), + SExpr::word("pointer"), + p.to_sexpr(), + ]), + Type::ConstPointer(p) => SExpr::Vec(vec![ + SExpr::annot("witx"), + SExpr::word("const_pointer"), + p.to_sexpr(), + ]), + Type::Builtin(b) => b.to_sexpr(), + } + } +} + +impl RecordDatatype { + pub fn to_sexpr(&self) -> SExpr { + match self.kind { + RecordKind::Tuple => { + let mut tuple = vec![SExpr::word("tuple")]; + for m in self.members.iter() { + tuple.push(SExpr::docs(&m.docs, m.tref.to_sexpr())); + } + SExpr::Vec(tuple) + } + RecordKind::Bitflags(repr) => { + let mut flags = vec![SExpr::word("flags")]; + flags.push(SExpr::Vec(vec![ + SExpr::word("@witx"), + SExpr::word("repr"), + repr.to_sexpr(), + ])); + flags.extend( + self.members + .iter() + .map(|m| SExpr::docs(&m.docs, m.name.to_sexpr())), + ); + SExpr::Vec(flags) + } + RecordKind::Other => { + let header = vec![SExpr::word("record")]; + let members = self + .members + .iter() + .map(|m| { + SExpr::docs( + &m.docs, + SExpr::Vec(vec![ + SExpr::word("field"), + m.name.to_sexpr(), + m.tref.to_sexpr(), + ]), + ) + }) + .collect::>(); + SExpr::Vec([header, members].concat()) + } + } + } +} + +impl Variant { + pub fn to_sexpr(&self) -> SExpr { + let mut list = Vec::new(); + if self.is_bool() { + return SExpr::word("bool"); + } else if self.is_enum() { + list.push(SExpr::word("enum")); + list.push(SExpr::Vec(vec![ + SExpr::word("@witx"), + SExpr::word("tag"), + self.tag_repr.to_sexpr(), + ])); + for case in self.cases.iter() { + list.push(SExpr::docs(&case.docs, case.name.to_sexpr())); + } + } else { + list.push(SExpr::word("variant")); + list.push(SExpr::Vec(vec![ + SExpr::word("@witx"), + SExpr::word("tag"), + self.tag_repr.to_sexpr(), + ])); + for case in self.cases.iter() { + let mut case_expr = vec![SExpr::word("case"), case.name.to_sexpr()]; + if let Some(ty) = &case.tref { + case_expr.push(ty.to_sexpr()); + } + list.push(SExpr::docs(&case.docs, SExpr::Vec(case_expr))); + } + } + SExpr::Vec(list) + } +} + +impl HandleDatatype { + pub fn to_sexpr(&self) -> SExpr { + SExpr::Vec(vec![SExpr::word("handle")]) + } +} + +impl IntRepr { + pub fn to_sexpr(&self) -> SExpr { + match self { + IntRepr::U8 => SExpr::word("u8"), + IntRepr::U16 => SExpr::word("u16"), + IntRepr::U32 => SExpr::word("u32"), + IntRepr::U64 => SExpr::word("u64"), + } + } +} + +impl Module { + pub fn to_sexpr(&self) -> SExpr { + let header = vec![SExpr::word("module"), self.name.to_sexpr()]; + let definitions = self + .imports() + .map(|i| i.to_sexpr()) + .chain(self.funcs().map(|f| f.to_sexpr())) + .collect::>(); + SExpr::docs(&self.docs, SExpr::Vec([header, definitions].concat())) + } +} + +impl ModuleImport { + pub fn to_sexpr(&self) -> SExpr { + let variant = match self.variant { + ModuleImportVariant::Memory => SExpr::Vec(vec![SExpr::word("memory")]), + }; + SExpr::docs( + &self.docs, + SExpr::Vec(vec![ + SExpr::word("import"), + SExpr::quote(self.name.as_str()), + variant, + ]), + ) + } +} + +impl InterfaceFunc { + pub fn to_sexpr(&self) -> SExpr { + let header = vec![ + SExpr::annot("interface"), + SExpr::word("func"), + SExpr::Vec(vec![ + SExpr::word("export"), + SExpr::quote(self.name.as_str()), + ]), + ]; + let params = self + .params + .iter() + .map(|f| { + SExpr::docs( + &f.docs, + SExpr::Vec(vec![ + SExpr::word("param"), + f.name.to_sexpr(), + f.tref.to_sexpr(), + ]), + ) + }) + .collect(); + let results = self + .results + .iter() + .map(|f| { + SExpr::docs( + &f.docs, + SExpr::Vec(vec![ + SExpr::word("result"), + f.name.to_sexpr(), + f.tref.to_sexpr(), + ]), + ) + }) + .collect(); + let attrs = if self.noreturn { + vec![SExpr::Vec(vec![ + SExpr::annot("witx"), + SExpr::word("noreturn"), + ])] + } else { + vec![] + }; + SExpr::docs( + &self.docs, + SExpr::Vec([header, params, results, attrs].concat()), + ) + } +} diff --git a/tools/witx/src/representation.rs b/tools/witx/src/representation.rs new file mode 100644 index 000000000..589b64e39 --- /dev/null +++ b/tools/witx/src/representation.rs @@ -0,0 +1,161 @@ +use crate::{BuiltinType, IntRepr, NamedType, RecordDatatype, Type, TypeRef, Variant}; +use std::collections::HashMap; + +// A lattice. Eq + Eq = Eq, SuperSet + any = NotEq, NotEq + any = NotEq. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum RepEquality { + Eq, + Superset, + NotEq, +} + +impl RepEquality { + pub fn join(&self, rhs: &Self) -> Self { + match (self, rhs) { + (RepEquality::Eq, RepEquality::Eq) => RepEquality::Eq, + _ => RepEquality::NotEq, + } + } +} + +pub trait Representable { + fn representable(&self, by: &Self) -> RepEquality; +} + +impl Representable for BuiltinType { + fn representable(&self, by: &Self) -> RepEquality { + // An unsigned integer can be used to represent an unsigned integer of smaller width. + // Otherwise, types must be equal. + if self == by { + return RepEquality::Eq; + } + match self { + BuiltinType::U8 { .. } => match by { + BuiltinType::U64 | BuiltinType::U32 { .. } | BuiltinType::U16 => { + RepEquality::Superset + } + _ => RepEquality::NotEq, + }, + BuiltinType::U16 => match by { + BuiltinType::U64 | BuiltinType::U32 { .. } => RepEquality::Superset, + _ => RepEquality::NotEq, + }, + BuiltinType::U32 { .. } => match by { + BuiltinType::U64 => RepEquality::Superset, + _ => RepEquality::NotEq, + }, + _ => RepEquality::NotEq, + } + } +} + +impl Representable for IntRepr { + fn representable(&self, by: &Self) -> RepEquality { + if self == by { + return RepEquality::Eq; + } + // An unsigned integer can be used to represent an unsigned integer of smaller width. + match self { + IntRepr::U16 => match by { + IntRepr::U32 | IntRepr::U64 => RepEquality::Superset, + _ => RepEquality::NotEq, + }, + IntRepr::U32 => match by { + IntRepr::U64 => RepEquality::Superset, + _ => RepEquality::NotEq, + }, + _ => RepEquality::NotEq, + } + } +} + +impl Representable for Variant { + fn representable(&self, by: &Self) -> RepEquality { + let mut superset = false; + // Integer representation must be compatible + match self.tag_repr.representable(&by.tag_repr) { + RepEquality::NotEq => return RepEquality::NotEq, + RepEquality::Eq => {} + RepEquality::Superset => superset = true, + } + let other_by_name = by + .cases + .iter() + .enumerate() + .map(|(i, c)| (&c.name, (c, i))) + .collect::>(); + // For each variant in self, must have variant of same name in by: + for (i, v) in self.cases.iter().enumerate() { + let other_ty = match other_by_name.get(&v.name) { + Some((_, j)) if i != *j => return RepEquality::NotEq, + Some((other, _)) => &other.tref, + None => return RepEquality::NotEq, + }; + match (&v.tref, other_ty) { + (Some(me), Some(other)) => match me.representable(other) { + RepEquality::NotEq => return RepEquality::NotEq, + RepEquality::Eq => {} + RepEquality::Superset => superset = true, + }, + // We added fields, that's not ok + (Some(_), None) => return RepEquality::NotEq, + // Fields were deleted, that's ok + (None, Some(_)) => superset = true, + (None, None) => {} + } + } + if superset || self.cases.len() < by.cases.len() { + RepEquality::Superset + } else { + RepEquality::Eq + } + } +} + +impl Representable for RecordDatatype { + fn representable(&self, by: &Self) -> RepEquality { + // Records must have exact structural equality - same members, must + // be Eq, in the same order. + // We would require require a more expressive RepEquality enum to describe which members + // might be supersets. + if self.members.len() != by.members.len() { + return RepEquality::NotEq; + } + for (m, bym) in self.members.iter().zip(by.members.iter()) { + if m.name != bym.name { + return RepEquality::NotEq; + } + if m.tref.type_().representable(&*bym.tref.type_()) != RepEquality::Eq { + return RepEquality::NotEq; + } + } + RepEquality::Eq + } +} + +impl Representable for TypeRef { + fn representable(&self, by: &Self) -> RepEquality { + self.type_().representable(&*by.type_()) + } +} + +impl Representable for NamedType { + fn representable(&self, by: &Self) -> RepEquality { + self.tref.representable(&by.tref) + } +} + +impl Representable for Type { + fn representable(&self, by: &Self) -> RepEquality { + match (&self, &by) { + (Type::Variant(s), Type::Variant(b)) => s.representable(b), + (Type::Record(s), Type::Record(b)) => s.representable(b), + (Type::Handle(_), Type::Handle(_)) => RepEquality::Eq, // Handles are nominal, not structural + (Type::List(s), Type::List(b)) => s.representable(b), + (Type::Pointer(s), Type::Pointer(b)) => s.representable(b), + (Type::ConstPointer(s), Type::ConstPointer(b)) => s.representable(b), + (Type::Builtin(s), Type::Builtin(b)) => s.representable(b), + _ => RepEquality::NotEq, + } + } +} diff --git a/tools/witx/src/toplevel.rs b/tools/witx/src/toplevel.rs index 9a5f10392..257e6edd8 100644 --- a/tools/witx/src/toplevel.rs +++ b/tools/witx/src/toplevel.rs @@ -1,42 +1,53 @@ +use crate::ast::{Definition, Document}; use crate::io::{Filesystem, WitxIo}; -use crate::parser::{TopLevelModule, TopLevelSyntax}; -use crate::validate::{ModuleValidation, ValidationError}; -use crate::{Location, Module, WitxError}; -use std::collections::{hash_map::Entry, HashMap}; +use crate::parser::{TopLevelDocument, TopLevelSyntax}; +use crate::validate::DocValidation; +use crate::WitxError; +use std::collections::HashSet; use std::path::{Path, PathBuf}; -pub fn parse_witx(i: impl AsRef) -> Result { - parse_witx_with(i, Filesystem) +pub fn parse_witx(i: &[impl AsRef]) -> Result { + let paths = i.iter().map(|p| p.as_ref()).collect::>(); + _parse_witx_with(&paths, &Filesystem) } -pub fn parse_witx_with(i: impl AsRef, witxio: impl WitxIo) -> Result { - _parse(i.as_ref(), &witxio, &mut Default::default(), None) +pub fn parse_witx_with(i: &[impl AsRef], witxio: impl WitxIo) -> Result { + let paths = i.iter().map(|p| p.as_ref()).collect::>(); + _parse_witx_with(&paths, &witxio) } -fn _parse( +fn _parse_witx_with(paths: &[&Path], io: &dyn WitxIo) -> Result { + let mut validator = DocValidation::new(); + let mut definitions = Vec::new(); + let mut parsed = HashSet::new(); + for path in paths { + let root = path.parent().unwrap_or(Path::new(".")); + + parse_file( + path.file_name().unwrap().as_ref(), + io, + root, + &mut validator, + &mut definitions, + &mut parsed, + )?; + } + Ok(validator.into_document(definitions)) +} + +fn parse_file( path: &Path, io: &dyn WitxIo, - parsed: &mut HashMap>, - parent_location: Option, -) -> Result { - let canon_path = io.canonicalize(path)?; - match parsed.entry(canon_path.clone()) { - Entry::Occupied(e) => match e.get() { - Some(doc) => return Ok(doc.clone()), - None => { - return Err(ValidationError::CyclicModule { - location: parent_location.unwrap(), - } - .into()) - } - }, - Entry::Vacant(v) => { - v.insert(None); - } + root: &Path, + validator: &mut DocValidation, + definitions: &mut Vec, + parsed: &mut HashSet, +) -> Result<(), WitxError> { + let path = io.canonicalize(&root.join(path))?; + if !parsed.insert(path.clone()) { + return Ok(()); } - - let input = io.fgets(&canon_path)?; - let mut validator = ModuleValidation::new(&input, path); + let input = io.fgets(&path)?; let adjust_err = |mut error: wast::Error| { error.set_path(&path); @@ -44,44 +55,23 @@ fn _parse( WitxError::Parse(error) }; let buf = wast::parser::ParseBuffer::new(&input).map_err(adjust_err)?; - let doc = wast::parser::parse::(&buf).map_err(adjust_err)?; + let doc = wast::parser::parse::(&buf).map_err(adjust_err)?; - let mut submodules = HashMap::new(); - for t in doc.decls { + for t in doc.items { match t.item { TopLevelSyntax::Decl(d) => { validator - .validate_decl(&d, &t.comments) + .scope(&input, &path) + .validate_decl(&d, &t.comments, definitions) .map_err(WitxError::Validation)?; } TopLevelSyntax::Use(u) => { - let path = path - .parent() - .unwrap() - .join(u.from.name()) - .with_extension("witx"); - - let module = match submodules.entry(u.from.name()) { - Entry::Occupied(e) => e.into_mut(), - Entry::Vacant(v) => { - let location = validator.location(u.from.span()); - let doc = _parse(&path, io, parsed, Some(location))?; - v.insert(doc) - } - }; - validator.validate_use(u, module)?; + parse_file(u.as_ref(), io, root, validator, definitions, parsed)?; } } } - for f in doc.functions { - validator - .validate_function(&f.item, &f.comments) - .map_err(WitxError::Validation)?; - } - let doc = validator.into_module(); - parsed.insert(canon_path, Some(doc.clone())); - Ok(doc) + Ok(()) } #[cfg(test)] @@ -92,14 +82,14 @@ mod test { #[test] fn empty() { - parse_witx_with("/a", &MockFs::new(&[("/a", ";; empty")])).expect("parse"); + parse_witx_with(&[Path::new("/a")], &MockFs::new(&[("/a", ";; empty")])).expect("parse"); } #[test] fn one_use() { parse_witx_with( - "/a", - &MockFs::new(&[("/a", "(use $x from $b)"), ("/b.witx", "(typename $x u8)")]), + &[Path::new("/a")], + &MockFs::new(&[("/a", "(use \"b\")"), ("/b", ";; empty")]), ) .unwrap(); } @@ -107,11 +97,11 @@ mod test { #[test] fn multi_use() { let doc = parse_witx_with( - "/a", + &[Path::new("/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)"), + ("/a", "(use \"b\")"), + ("/b", "(use \"c\")\n(typename $b_float f64)"), + ("/c", "(typename $c_int u32)"), ]), ) .expect("parse"); @@ -131,50 +121,43 @@ mod test { #[test] fn diamond_dependency() { let doc = parse_witx_with( - "/a", + &[Path::new("/a")], &MockFs::new(&[ - ("/a", "(use $b_char from $b)\n(use $c_char from $c)"), - ( - "/b.witx", - "(use $d_char from $d) (typename $b_char $d_char)", - ), - ( - "/c.witx", - "(use $d_char from $d) (typename $c_char $d_char)", - ), - ("/d.witx", "(typename $d_char u8)"), + ("/a", "(use \"b\")\n(use \"c\")"), + ("/b", "(use \"d\")"), + ("/c", "(use \"d\")"), + ("/d", "(typename $d_char u8)"), ]), ) .expect("parse"); - let b_char = doc.typename(&Id::new("b_char")).unwrap(); + let d_char = doc.typename(&Id::new("d_char")).unwrap(); assert_eq!( - **b_char.type_(), + **d_char.type_(), Type::Builtin(BuiltinType::U8 { lang_c_char: false }) ); - assert!(doc.typename(&Id::new("d_char")).is_none()); } #[test] fn use_not_found() { - match parse_witx_with("/a", &MockFs::new(&[("/a", "(use $x from $b)")])) + match parse_witx_with(&[Path::new("/a")], &MockFs::new(&[("/a", "(use \"b\")")])) .err() .unwrap() { - WitxError::Io(path, _error) => assert_eq!(path, PathBuf::from("/b.witx")), + WitxError::Io(path, _error) => assert_eq!(path, PathBuf::from("/b")), e => panic!("wrong error: {:?}", e), } } #[test] fn use_invalid() { - match parse_witx_with("/a", &MockFs::new(&[("/a", "(use bbbbbbb)")])) + match parse_witx_with(&[Path::new("/a")], &MockFs::new(&[("/a", "(use bbbbbbb)")])) .err() .unwrap() { WitxError::Parse(e) => { let err = e.to_string(); - assert!(err.contains("expected an identifier"), "bad error: {}", err); + assert!(err.contains("expected a string"), "bad error: {}", err); assert!(err.contains("/a:1:6")); } e => panic!("wrong error: {:?}", e), diff --git a/tools/witx/src/validate.rs b/tools/witx/src/validate.rs index c8ad706cd..cf4d0ad7f 100644 --- a/tools/witx/src/validate.rs +++ b/tools/witx/src/validate.rs @@ -1,12 +1,14 @@ use crate::{ io::{Filesystem, WitxIo}, parser::{ - CommentSyntax, DeclSyntax, EnumSyntax, ExpectedSyntax, FlagsSyntax, FunctionSyntax, - HandleSyntax, RecordSyntax, TupleSyntax, TypedefSyntax, UnionSyntax, UseSyntax, UsedNames, - VariantSyntax, + CommentSyntax, DeclSyntax, Documented, EnumSyntax, ExpectedSyntax, FlagsSyntax, + HandleSyntax, ImportTypeSyntax, ModuleDeclSyntax, RecordSyntax, TupleSyntax, TypedefSyntax, + UnionSyntax, VariantSyntax, }, - Abi, BuiltinType, Case, Constant, Function, HandleDatatype, Id, IntRepr, Location, Module, - ModuleId, NamedType, Param, RecordDatatype, RecordKind, RecordMember, Type, TypeRef, Variant, + Abi, BuiltinType, Case, Constant, Definition, Document, Entry, HandleDatatype, Id, IntRepr, + InterfaceFunc, InterfaceFuncParam, Location, Module, ModuleDefinition, ModuleEntry, + ModuleImport, ModuleImportVariant, NamedType, RecordDatatype, RecordKind, RecordMember, Type, + TypeRef, Variant, }; use std::collections::{HashMap, HashSet}; use std::path::Path; @@ -17,8 +19,6 @@ use thiserror::Error; pub enum ValidationError { #[error("Unknown name `{name}`")] UnknownName { name: String, location: Location }, - #[error("module definition cycle detected")] - CyclicModule { location: Location }, #[error("Redefinition of name `{name}`")] NameAlreadyExists { name: String, @@ -71,8 +71,7 @@ impl ValidationError { | AnonymousRecord { location, .. } | UnionSizeMismatch { location, .. } | InvalidUnionField { location, .. } - | InvalidUnionTag { location, .. } - | CyclicModule { location } => { + | InvalidUnionTag { location, .. } => { format!("{}\n{}", location.highlight_source_with(witxio), &self) } NameAlreadyExists { @@ -128,25 +127,25 @@ impl IdentValidation { } } -pub struct ModuleValidation<'a> { - module: Module, - type_ns: IdentValidation, - func_ns: IdentValidation, - constant_ns: HashMap, +pub struct DocValidation { + scope: IdentValidation, + entries: HashMap, + constant_scopes: HashMap, bool_ty: TypeRef, +} + +pub struct DocValidationScope<'a> { + doc: &'a mut DocValidation, text: &'a str, path: &'a Path, } -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()); - let module_id = ModuleId(Rc::new(path.to_path_buf())); +impl DocValidation { + pub fn new() -> Self { Self { - module: Module::new(name, module_id), - type_ns: IdentValidation::new(), - func_ns: IdentValidation::new(), - constant_ns: HashMap::new(), + scope: IdentValidation::new(), + entries: HashMap::new(), + constant_scopes: HashMap::new(), bool_ty: TypeRef::Value(Rc::new(Type::Variant(Variant { tag_repr: IntRepr::U32, cases: vec![ @@ -162,16 +161,24 @@ impl<'a> ModuleValidation<'a> { }, ], }))), + } + } + + pub fn scope<'a>(&'a mut self, text: &'a str, path: &'a Path) -> DocValidationScope<'a> { + DocValidationScope { + doc: self, text, path, } } - pub fn into_module(self) -> Module { - self.module + pub fn into_document(self, defs: Vec) -> Document { + Document::new(defs, self.entries) } +} - pub fn location(&self, span: wast::Span) -> Location { +impl DocValidationScope<'_> { + fn location(&self, span: wast::Span) -> Location { // Wast Span gives 0-indexed lines and columns. Location is 1-indexed. let (line, column) = span.linecol_in(self.text); Location { @@ -181,132 +188,82 @@ impl<'a> ModuleValidation<'a> { } } - pub fn validate_use( - &mut self, - use_: UseSyntax<'_>, - module: &Module, - ) -> Result<(), ValidationError> { - match use_.names { - UsedNames::All(span) => { - for ty in module.typenames() { - let loc = self.location(span); - self.type_ns.introduce(ty.name.as_str(), loc)?; - self.module.push_type(ty.clone()); - } - } - UsedNames::List(names) => { - for name in names { - let loc = self.location(name.span()); - let id = self.type_ns.introduce(name.name(), loc)?; - let ty = match module.typename(&id) { - Some(ty) => ty, - None => { - return Err(ValidationError::UnknownName { - name: name.name().to_string(), - location: self.location(name.span()), - } - .into()); - } - }; - self.module.push_type(ty); - } - } - } - Ok(()) + fn introduce(&mut self, name: &wast::Id<'_>) -> Result { + let loc = self.location(name.span()); + self.doc.scope.introduce(name.name(), loc) + } + + fn get(&self, name: &wast::Id<'_>) -> Result { + let loc = self.location(name.span()); + self.doc.scope.get(name.name(), loc) } pub fn validate_decl( &mut self, decl: &DeclSyntax, comments: &CommentSyntax, + definitions: &mut Vec, ) -> Result<(), ValidationError> { match decl { DeclSyntax::Typename(decl) => { - let loc = self.location(decl.ident.span()); - let name = self.type_ns.introduce(decl.ident.name(), loc)?; + let name = self.introduce(&decl.ident)?; let docs = comments.docs(); let tref = self.validate_datatype(&decl.def, true, decl.ident.span())?; - self.module.push_type(Rc::new(NamedType { + let rc_datatype = Rc::new(NamedType { name: name.clone(), - module: self.module.module_id().clone(), tref, docs, - })); + }); + self.doc + .entries + .insert(name.clone(), Entry::Typename(Rc::downgrade(&rc_datatype))); + definitions.push(Definition::Typename(rc_datatype)); + } + + DeclSyntax::Module(syntax) => { + let name = self.introduce(&syntax.name)?; + let mut module_validator = ModuleValidation::new(self); + let decls = syntax + .decls + .iter() + .map(|d| module_validator.validate_decl(&d)) + .collect::, _>>()?; + + let rc_module = Rc::new(Module::new( + name.clone(), + decls, + module_validator.entries, + comments.docs(), + )); + self.doc + .entries + .insert(name, Entry::Module(Rc::downgrade(&rc_module))); + definitions.push(Definition::Module(rc_module)); } DeclSyntax::Const(syntax) => { let ty = Id::new(syntax.item.ty.name()); let loc = self.location(syntax.item.name.span()); let scope = self - .constant_ns + .doc + .constant_scopes .entry(ty.clone()) .or_insert_with(IdentValidation::new); let name = scope.introduce(syntax.item.name.name(), loc)?; // TODO: validate `ty` is a integer datatype that `syntax.value` // fits within. - self.module.push_constant(Constant { + definitions.push(Definition::Constant(Constant { ty, name, value: syntax.item.value, docs: syntax.comments.docs(), - }); + })); } } Ok(()) } - pub fn validate_function( - &mut self, - syntax: &FunctionSyntax, - comments: &CommentSyntax, - ) -> Result<(), ValidationError> { - let loc = self.location(syntax.export_loc); - let name = self.func_ns.introduce(syntax.export, loc)?; - let mut argnames = IdentValidation::new(); - let params = syntax - .params - .iter() - .map(|f| { - Ok(Param { - name: argnames - .introduce(f.item.name.name(), self.location(f.item.name.span()))?, - tref: self.validate_datatype(&f.item.type_, false, f.item.name.span())?, - docs: f.comments.docs(), - }) - }) - .collect::, _>>()?; - let results = syntax - .results - .iter() - .map(|f| { - let tref = self.validate_datatype(&f.item.type_, false, f.item.name.span())?; - Ok(Param { - name: argnames - .introduce(f.item.name.name(), self.location(f.item.name.span()))?, - tref, - docs: f.comments.docs(), - }) - }) - .collect::, _>>()?; - let noreturn = syntax.noreturn; - let abi = Abi::Preview1; - abi.validate(¶ms, &results) - .map_err(|reason| ValidationError::Abi { - reason, - location: self.location(syntax.export_loc), - })?; - self.module.push_func(Rc::new(Function { - abi, - name: name.clone(), - params, - results, - noreturn, - docs: comments.docs(), - })); - Ok(()) - } - fn validate_datatype( &self, syntax: &TypedefSyntax, @@ -315,10 +272,22 @@ impl<'a> ModuleValidation<'a> { ) -> Result { match syntax { TypedefSyntax::Ident(syntax) => { - let loc = self.location(syntax.span()); - let i = self.type_ns.get(syntax.name(), loc)?; - let ty = self.module.typename(&i).unwrap(); - Ok(TypeRef::Name(ty)) + let i = self.get(syntax)?; + match self.doc.entries.get(&i) { + Some(Entry::Typename(weak_ref)) => Ok(TypeRef::Name( + weak_ref.upgrade().expect("weak backref to defined type"), + )), + Some(e) => Err(ValidationError::WrongKindName { + name: i.as_str().to_string(), + location: self.location(syntax.span()), + expected: "datatype", + got: e.kind(), + }), + None => Err(ValidationError::Recursive { + name: i.as_str().to_string(), + location: self.location(syntax.span()), + }), + } } TypedefSyntax::Enum { .. } | TypedefSyntax::Flags { .. } @@ -357,7 +326,7 @@ impl<'a> ModuleValidation<'a> { TypedefSyntax::String => { Type::List(TypeRef::Value(Rc::new(Type::Builtin(BuiltinType::Char)))) } - TypedefSyntax::Bool => return Ok(self.bool_ty.clone()), + TypedefSyntax::Bool => return Ok(self.doc.bool_ty.clone()), TypedefSyntax::Ident { .. } => unreachable!(), }))), } @@ -461,7 +430,7 @@ impl<'a> ModuleValidation<'a> { members.push(RecordMember { name, docs, - tref: self.bool_ty.clone(), + tref: self.doc.bool_ty.clone(), }); } Ok(RecordDatatype { @@ -654,3 +623,100 @@ impl<'a> ModuleValidation<'a> { } } } + +struct ModuleValidation<'a> { + doc: &'a DocValidationScope<'a>, + scope: IdentValidation, + pub entries: HashMap, +} + +impl<'a> ModuleValidation<'a> { + fn new(doc: &'a DocValidationScope<'a>) -> Self { + Self { + doc, + scope: IdentValidation::new(), + entries: HashMap::new(), + } + } + + fn validate_decl( + &mut self, + decl: &Documented, + ) -> Result { + match &decl.item { + ModuleDeclSyntax::Import(syntax) => { + let loc = self.doc.location(syntax.name_loc); + let name = self.scope.introduce(syntax.name, loc)?; + let variant = match syntax.type_ { + ImportTypeSyntax::Memory => ModuleImportVariant::Memory, + }; + let rc_import = Rc::new(ModuleImport { + name: name.clone(), + variant, + docs: decl.comments.docs(), + }); + self.entries + .insert(name, ModuleEntry::Import(Rc::downgrade(&rc_import))); + Ok(ModuleDefinition::Import(rc_import)) + } + ModuleDeclSyntax::Func(syntax) => { + let loc = self.doc.location(syntax.export_loc); + let name = self.scope.introduce(syntax.export, loc)?; + let mut argnames = IdentValidation::new(); + let params = syntax + .params + .iter() + .map(|f| { + Ok(InterfaceFuncParam { + name: argnames.introduce( + f.item.name.name(), + self.doc.location(f.item.name.span()), + )?, + tref: self.doc.validate_datatype( + &f.item.type_, + false, + f.item.name.span(), + )?, + docs: f.comments.docs(), + }) + }) + .collect::, _>>()?; + let results = syntax + .results + .iter() + .map(|f| { + let tref = + self.doc + .validate_datatype(&f.item.type_, false, f.item.name.span())?; + Ok(InterfaceFuncParam { + name: argnames.introduce( + f.item.name.name(), + self.doc.location(f.item.name.span()), + )?, + tref, + docs: f.comments.docs(), + }) + }) + .collect::, _>>()?; + let noreturn = syntax.noreturn; + let abi = Abi::Preview1; + abi.validate(¶ms, &results) + .map_err(|reason| ValidationError::Abi { + reason, + location: self.doc.location(syntax.export_loc), + })?; + let rc_func = Rc::new(InterfaceFunc { + abi, + name: name.clone(), + params, + results, + noreturn, + docs: decl.comments.docs(), + }); + self.entries + .insert(name, ModuleEntry::Func(Rc::downgrade(&rc_func))); + Ok(ModuleDefinition::Func(rc_func)) + } + } + } +} diff --git a/tools/witx/tests/witxt.rs b/tools/witx/tests/witxt.rs index 753cac444..7c002fe1b 100644 --- a/tools/witx/tests/witxt.rs +++ b/tools/witx/tests/witxt.rs @@ -14,7 +14,7 @@ use std::path::{Path, PathBuf}; use std::str; use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; use wast::parser::{self, Parse, ParseBuffer, Parser}; -use witx::{Instruction, WasmType}; +use witx::{Documentation, Instruction, Representable, WasmType}; fn main() { let tests = find_tests(); @@ -43,7 +43,7 @@ fn main() { .filter_map(|(test, contents)| { WitxtRunner { ntests: &ntests, - modules: HashMap::new(), + documents: HashMap::new(), } .run(test, contents) .err() @@ -91,7 +91,7 @@ fn find_tests() -> Vec { struct WitxtRunner<'a> { ntests: &'a AtomicUsize, - modules: HashMap, + documents: HashMap, } impl WitxtRunner<'_> { @@ -149,14 +149,16 @@ impl WitxtRunner<'_> { self.bump_ntests(); match directive { WitxtDirective::Witx(witx) => { - let doc = witx.module(contents, test)?; + let doc = witx.document(contents, test)?; + self.assert_roundtrip(&doc) + .context("failed to round-trip the document")?; self.assert_md(&doc)?; if let Some(name) = witx.id { - self.modules.insert(name.name().to_string(), doc); + self.documents.insert(name.name().to_string(), doc); } } WitxtDirective::AssertInvalid { witx, message, .. } => { - let err = match witx.module(contents, test) { + let err = match witx.document(contents, test) { Ok(_) => bail!("witx was valid when it shouldn't be"), Err(e) => format!("{:?}", anyhow::Error::from(e)), }; @@ -164,15 +166,15 @@ impl WitxtRunner<'_> { bail!("expected error {:?}\nfound error {}", message, err); } } - WitxtDirective::AssertEq { eq, t1, t2, .. } => { + WitxtDirective::AssertRepresentable { repr, t1, t2, .. } => { let (t1m, t1t) = t1; let (t2m, t2t) = t2; let t1d = self - .modules + .documents .get(t1m.name()) .ok_or_else(|| anyhow!("no document named {:?}", t1m.name()))?; let t2d = self - .modules + .documents .get(t2m.name()) .ok_or_else(|| anyhow!("no document named {:?}", t2m.name()))?; let t1 = t1d @@ -181,8 +183,13 @@ impl WitxtRunner<'_> { let t2 = t2d .typename(&witx::Id::new(t2t)) .ok_or_else(|| anyhow!("no type named {:?}", t2t))?; - if t1.tref.type_equal(&t2.tref) != eq { - bail!("failed comparing {:?} and {:?}", t1, t2); + match (repr, t1.type_().representable(&t2.type_())) { + (RepEquality::Eq, witx::RepEquality::Eq) + | (RepEquality::Superset, witx::RepEquality::Superset) + | (RepEquality::NotEq, witx::RepEquality::NotEq) => {} + (a, b) => { + bail!("expected {:?} representation, got {:?}", a, b); + } } } WitxtDirective::AssertAbi { @@ -192,8 +199,9 @@ impl WitxtRunner<'_> { wasm_signature: (wasm_params, wasm_results), .. } => { - let m = witx.module(contents, test)?; - let func = m.funcs().next().ok_or_else(|| anyhow!("no funcs"))?; + 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 (params, results) = func.wasm_signature(); if params != wasm_params { @@ -208,19 +216,61 @@ impl WitxtRunner<'_> { err: None, contents, }; - func.call_wasm(m.name(), &mut check); + func.call_wasm(&module.name, &mut check); check.check()?; check.abi = interface.instrs.iter(); - func.call_interface(m.name(), &mut check); + func.call_interface(&module.name, &mut check); check.check()?; } } Ok(()) } - fn assert_md(&self, m: &witx::Module) -> Result<()> { + fn assert_roundtrip(&self, doc: &witx::Document) -> Result<()> { self.bump_ntests(); - witx::document(Some(m)); + let back_to_sexprs = format!("{}", doc); + let doc2 = witx::parse(&back_to_sexprs)?; + if *doc == doc2 { + return Ok(()); + } + + // Try to get a more specific error message that isn't thousands of + // lines long of debug representations. + for type_ in doc.typenames() { + let type2 = match doc2.typename(&type_.name) { + Some(t) => t, + None => bail!("doc2 missing datatype"), + }; + if type_ != type2 { + bail!("types are not equal\n{:?}\n !=\n{:?}", type_, type2); + } + } + for mod_ in doc.modules() { + let mod2 = match doc2.module(&mod_.name) { + Some(m) => m, + None => bail!("doc2 missing module"), + }; + for import in mod_.imports() { + let import2 = match mod2.import(&import.name) { + Some(i) => i, + None => bail!("mod2 missing import"), + }; + assert_eq!(import, import2); + } + for func in mod_.funcs() { + let func2 = match mod2.func(&func.name) { + Some(f) => f, + None => bail!("mod2 missing func"), + }; + assert_eq!(func, func2); + } + } + bail!("{:?} != {:?}", doc, doc2) + } + + fn assert_md(&self, doc: &witx::Document) -> Result<()> { + self.bump_ntests(); + doc.to_md(); Ok(()) } @@ -354,11 +404,13 @@ impl witx::Bindgen for AbiBindgen<'_> { mod kw { wast::custom_keyword!(assert_invalid); - wast::custom_keyword!(assert_eq); - wast::custom_keyword!(assert_ne); + wast::custom_keyword!(assert_representable); wast::custom_keyword!(assert_abi); wast::custom_keyword!(witx); + wast::custom_keyword!(eq); + wast::custom_keyword!(noteq); wast::custom_keyword!(load); + wast::custom_keyword!(superset); wast::custom_keyword!(call_wasm); wast::custom_keyword!(call_interface); wast::custom_keyword!(param); @@ -376,8 +428,6 @@ struct Witxt<'a> { impl<'a> Parse<'a> for Witxt<'a> { fn parse(parser: Parser<'a>) -> parser::Result { - let _r1 = parser.register_annotation("interface"); - let _r2 = parser.register_annotation("witx"); let mut directives = Vec::new(); while !parser.is_empty() { directives.push(parser.parens(|p| p.parse())?); @@ -393,9 +443,9 @@ enum WitxtDirective<'a> { witx: Witx<'a>, message: &'a str, }, - AssertEq { + AssertRepresentable { span: wast::Span, - eq: bool, + repr: RepEquality, t1: (wast::Id<'a>, &'a str), t2: (wast::Id<'a>, &'a str), }, @@ -414,7 +464,7 @@ impl WitxtDirective<'_> { WitxtDirective::Witx(w) => w.span, WitxtDirective::AssertInvalid { span, .. } | WitxtDirective::AssertAbi { span, .. } - | WitxtDirective::AssertEq { span, .. } => *span, + | WitxtDirective::AssertRepresentable { span, .. } => *span, } } } @@ -431,19 +481,11 @@ impl<'a> Parse<'a> for WitxtDirective<'a> { witx: parser.parens(|p| p.parse())?, message: parser.parse()?, }) - } else if l.peek::() { - let span = parser.parse::()?.0; - Ok(WitxtDirective::AssertEq { + } else if l.peek::() { + let span = parser.parse::()?.0; + Ok(WitxtDirective::AssertRepresentable { span, - eq: true, - t1: (parser.parse()?, parser.parse()?), - t2: (parser.parse()?, parser.parse()?), - }) - } else if l.peek::() { - let span = parser.parse::()?.0; - Ok(WitxtDirective::AssertEq { - span, - eq: false, + repr: parser.parse()?, t1: (parser.parse()?, parser.parse()?), t2: (parser.parse()?, parser.parse()?), }) @@ -517,32 +559,28 @@ struct Witx<'a> { } enum WitxDef<'a> { - Fs(&'a str), - Inline(witx::parser::TopLevelModule<'a>), + Fs(Vec<&'a str>), + Inline(Vec>>), } impl Witx<'_> { - fn module(&self, contents: &str, file: &Path) -> Result { - use witx::parser::TopLevelSyntax; - + fn document(&self, contents: &str, file: &Path) -> Result { match &self.def { - WitxDef::Inline(doc) => { - let mut validator = witx::ModuleValidation::new(contents, file); - for t in doc.decls.iter() { - match &t.item { - TopLevelSyntax::Decl(d) => validator.validate_decl(&d, &t.comments)?, - TopLevelSyntax::Use(_) => unimplemented!(), - } - } - for f in doc.functions.iter() { - validator.validate_function(&f.item, &f.comments)?; + WitxDef::Inline(decls) => { + let mut validator = witx::DocValidation::new(); + let mut definitions = Vec::new(); + for decl in decls { + validator + .scope(contents, file) + .validate_decl(&decl.item, &decl.comments, &mut definitions) + .map_err(witx::WitxError::Validation)?; } - Ok(validator.into_module()) + Ok(validator.into_document(definitions)) } - WitxDef::Fs(path) => { + WitxDef::Fs(paths) => { let parent = file.parent().unwrap(); - let path = parent.join(path); - Ok(witx::load(&path)?) + let paths = paths.iter().map(|p| parent.join(p)).collect::>(); + Ok(witx::load(&paths)?) } } } @@ -556,15 +594,48 @@ impl<'a> Parse<'a> for Witx<'a> { let def = if parser.peek2::() { parser.parens(|p| { p.parse::()?; - Ok(WitxDef::Fs(p.parse()?)) + let mut paths = Vec::new(); + while !p.is_empty() { + paths.push(p.parse()?); + } + Ok(WitxDef::Fs(paths)) })? } else { - WitxDef::Inline(parser.parse()?) + let mut decls = Vec::new(); + while !parser.is_empty() { + decls.push(parser.parens(|p| p.parse())?); + } + WitxDef::Inline(decls) }; Ok(Witx { id, span, def }) } } +#[derive(Debug)] +enum RepEquality { + Eq, + NotEq, + Superset, +} + +impl<'a> Parse<'a> for RepEquality { + fn parse(parser: Parser<'a>) -> parser::Result { + let mut l = parser.lookahead1(); + if l.peek::() { + parser.parse::()?; + Ok(RepEquality::Eq) + } else if l.peek::() { + parser.parse::()?; + Ok(RepEquality::NotEq) + } else if l.peek::() { + parser.parse::()?; + Ok(RepEquality::Superset) + } else { + Err(l.error()) + } + } +} + struct Abi<'a> { instrs: Vec<(wast::Span, &'a str)>, } diff --git a/tools/witx/tests/witxt/multimodule.witxt b/tools/witx/tests/witxt/multimodule.witxt index bffa18962..d8b6559ac 100644 --- a/tools/witx/tests/witxt/multimodule.witxt +++ b/tools/witx/tests/witxt/multimodule.witxt @@ -1,6 +1,7 @@ ;; B uses A, and C uses A. -(witx $b (load "multimodule/type_b.witx")) -(witx $c (load "multimodule/type_c.witx")) +(witx $multi + (load "multimodule/type_b.witx" "multimodule/type_c.witx") +) (witx $reference (typename $a u32) @@ -8,13 +9,14 @@ (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_representable eq $reference "a" $multi "a") +(assert_representable eq $reference "b" $multi "b") +(assert_representable eq $reference "c" $multi "c") (assert_invalid - (witx (load "multimodule/redefine_a.witx")) + (witx + (load + "multimodule/type_a.witx" + "multimodule/redefine_a.witx") + ) "Redefinition of name `a`") - -(witx $c (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 76f04ffdb..e5d99d82c 100644 --- a/tools/witx/tests/witxt/multimodule/redefine_a.witx +++ b/tools/witx/tests/witxt/multimodule/redefine_a.witx @@ -1,2 +1 @@ -(use $a from $type_a) (typename $a u8) diff --git a/tools/witx/tests/witxt/multimodule/structured.witx b/tools/witx/tests/witxt/multimodule/structured.witx deleted file mode 100644 index 5b9cf5dc7..000000000 --- a/tools/witx/tests/witxt/multimodule/structured.witx +++ /dev/null @@ -1,2 +0,0 @@ -(typename $a u32) -(typename $foo (tuple $a)) diff --git a/tools/witx/tests/witxt/multimodule/type_b.witx b/tools/witx/tests/witxt/multimodule/type_b.witx index ad695706a..fe169d3ec 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) +(use "type_a.witx") (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..f42aac2d0 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) +(use "type_a.witx") (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 deleted file mode 100644 index 467e8ef36..000000000 --- a/tools/witx/tests/witxt/multimodule/use_of_structured.witx +++ /dev/null @@ -1 +0,0 @@ -(use $foo from $structured) diff --git a/tools/witx/tests/witxt/representation.witxt b/tools/witx/tests/witxt/representation.witxt new file mode 100644 index 000000000..53f71f196 --- /dev/null +++ b/tools/witx/tests/witxt/representation.witxt @@ -0,0 +1,60 @@ +;; type names don't matter +(witx $a + (typename $a (flags (@witx repr u8) $b $c))) +(witx $b + (typename $b (flags (@witx repr u8) $b $c))) + +(assert_representable eq $a "a" $b "b") +(assert_representable eq $b "b" $a "a") + +(; TODO: perhaps add assertions eventually for document-level representability? +;; flags +(witx $a + (typename $a (flags (@witx bitflags u8) $b $c))) +(witx $b + (typename $b (flags (@witx bitflags u8) $b $c $d))) + +(assert_representable noteq $b "b" $a "a") +(assert_representable superset $a "a" $b "b") + +(witx $c + (typename $c (flags (@witx bitflags u8) $b $e))) +(assert_representable noteq $a "a" $c "c") +(assert_representable noteq $c "c" $a "a") + +(witx $d + (typename $d (flags (@witx bitflags u16) $b $c))) +(assert_representable noteq $a "a" $d "d") +(assert_representable superset $d "d" $a "a") +(assert_representable superset $d "d" $b "b") +;) + +;; enums +(witx $a + (typename $a (enum $b $c))) +(witx $b + (typename $b (enum $b $c $d))) +(assert_representable superset $a "a" $b "b") +(assert_representable noteq $b "b" $a "a") + +(witx $c + (typename $c (enum (@witx tag u16) $b $c))) +(assert_representable superset $c "c" $a "a") +(assert_representable superset $c "c" $b "b") + +;; unions +(witx $a + (typename $tag (enum $b $c)) + (typename $a (union (@witx tag $tag) u32 f32))) +(witx $b + (typename $tag (enum $b $c $d)) + (typename $b (union (@witx tag $tag) u32 f32 f64))) +(assert_representable superset $a "a" $b "b") +(assert_representable noteq $b "b" $a "a") + +(witx $c + (typename $tag (enum $b $c)) + (typename $c (variant (@witx tag $tag) (case $c f32) (case $b u32)))) +(assert_representable eq $a "a" $c "c") +(assert_representable eq $c "c" $a "a") +(assert_representable superset $c "c" $b "b") diff --git a/tools/witx/tests/witxt/shorthand.witxt b/tools/witx/tests/witxt/shorthand.witxt index 7379701b1..960615f3c 100644 --- a/tools/witx/tests/witxt/shorthand.witxt +++ b/tools/witx/tests/witxt/shorthand.witxt @@ -2,58 +2,58 @@ (typename $a bool)) (witx $b (typename $a (variant (case $false) (case $true)))) -(assert_eq $a "a" $b "a") +(assert_representable eq $a "a" $b "a") (witx $a (typename $a (expected (error)))) (witx $b (typename $a (variant (case $ok) (case $err)))) -(assert_eq $a "a" $b "a") +(assert_representable eq $a "a" $b "a") (witx $a (typename $a (expected (error u32)))) (witx $b (typename $a (variant (case $ok) (case $err u32)))) -(assert_eq $a "a" $b "a") +(assert_representable eq $a "a" $b "a") (witx $a (typename $a (expected u32 (error)))) (witx $b (typename $a (variant (case $ok u32) (case $err)))) -(assert_eq $a "a" $b "a") +(assert_representable eq $a "a" $b "a") (witx $a (typename $a (expected u32 (error u64)))) (witx $b (typename $a (variant (case $ok u32) (case $err u64)))) -(assert_eq $a "a" $b "a") +(assert_representable eq $a "a" $b "a") (witx $a (typename $a (flags $a $b))) (witx $b (typename $a (record (field $a bool) (field $b bool)))) -(assert_eq $a "a" $b "a") +(assert_representable eq $a "a" $b "a") (witx $a (typename $a (enum $a $b))) (witx $b (typename $a (variant (case $a) (case $b)))) -(assert_eq $a "a" $b "a") +(assert_representable eq $a "a" $b "a") (witx $a (typename $a string)) (witx $b (typename $a (list char))) -(assert_eq $a "a" $b "a") +(assert_representable eq $a "a" $b "a") (witx $a (typename $a (tuple u32 u64))) (witx $b (typename $a (record (field $0 u32) (field $1 u64)))) -(assert_eq $a "a" $b "a") +(assert_representable eq $a "a" $b "a") (witx $a (typename $a (union u32 u64))) (witx $b (typename $a (variant (case $0 u32) (case $1 u64)))) -(assert_eq $a "a" $b "a") +(assert_representable eq $a "a" $b "a") diff --git a/tools/witx/tests/witxt/union.witxt b/tools/witx/tests/witxt/union.witxt index 58d2ce9b4..92be3dcd9 100644 --- a/tools/witx/tests/witxt/union.witxt +++ b/tools/witx/tests/witxt/union.witxt @@ -85,13 +85,13 @@ ) ;; These two unions should be represented the same: -(assert_eq $d1 "u" $d2 "u") -(assert_eq $d2 "u" $d1 "u") +(assert_representable eq $d1 "u" $d2 "u") +(assert_representable eq $d2 "u" $d1 "u") -;; Tag order doesnt matter for validation, but does for equality +;; Tag order doesnt matter for validation, but does for rep equality (witx $d3 (typename $tag (enum $b $a)) (typename $u (union (@witx tag $tag) u16 u8)) ) -(assert_ne $d3 "u" $d1 "u") +(assert_representable noteq $d3 "u" $d1 "u") diff --git a/tools/witx/tests/witxt/wasi.witxt b/tools/witx/tests/witxt/wasi.witxt index b046a5821..f654a7ee3 100644 --- a/tools/witx/tests/witxt/wasi.witxt +++ b/tools/witx/tests/witxt/wasi.witxt @@ -1,17 +1,26 @@ ;; 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/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")) -(witx (load "../../../../phases/ephemeral/witx/wasi_ephemeral_random.witx")) -(witx (load "../../../../phases/ephemeral/witx/wasi_ephemeral_sched.witx")) -(witx (load "../../../../phases/ephemeral/witx/wasi_ephemeral_sock.witx")) +(witx + (load + "../../../../phases/ephemeral/witx/wasi_ephemeral_args.witx" + "../../../../phases/ephemeral/witx/wasi_ephemeral_clock.witx" + "../../../../phases/ephemeral/witx/wasi_ephemeral_environ.witx" + "../../../../phases/ephemeral/witx/wasi_ephemeral_fd.witx" + "../../../../phases/ephemeral/witx/wasi_ephemeral_path.witx" + "../../../../phases/ephemeral/witx/wasi_ephemeral_poll.witx" + "../../../../phases/ephemeral/witx/wasi_ephemeral_proc.witx" + "../../../../phases/ephemeral/witx/wasi_ephemeral_random.witx" + "../../../../phases/ephemeral/witx/wasi_ephemeral_sched.witx" + "../../../../phases/ephemeral/witx/wasi_ephemeral_sock.witx" + ) +) +;; should be singularly-loadable as well +(witx + (load + "../../../../phases/ephemeral/witx/wasi_ephemeral_fd.witx"))