diff --git a/phases/ephemeral/docs.md b/phases/ephemeral/docs.md
index 54f00176..2f8d4aa7 100644
--- a/phases/ephemeral/docs.md
+++ b/phases/ephemeral/docs.md
@@ -1218,9 +1218,9 @@ Each argument is expected to be `\0` terminated.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1241,7 +1241,7 @@ data, or an error.
###### Variant Layout
- size: 12
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: `(size, size)`
@@ -1278,7 +1278,7 @@ The resolution of the clock.
###### Variant Layout
- size: 16
- align: 8
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`timestamp`](#timestamp)
@@ -1305,7 +1305,7 @@ The time value of the clock.
###### Variant Layout
- size: 16
- align: 8
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`timestamp`](#timestamp)
@@ -1330,9 +1330,9 @@ Key/value pairs are expected to be joined with `=`s, and terminated with `\0`s.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1353,7 +1353,7 @@ environment variable data.
###### Variant Layout
- size: 12
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: `(size, size)`
@@ -1393,9 +1393,9 @@ The advice.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1421,9 +1421,9 @@ The length of the area that is allocated.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1443,9 +1443,9 @@ Note: This is similar to [`close`](#close) in POSIX.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1465,9 +1465,9 @@ Note: This is similar to `fdatasync` in POSIX.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1490,7 +1490,7 @@ The buffer where the file descriptor's attributes are stored.
###### Variant Layout
- size: 32
- align: 8
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`fdstat`](#fdstat)
@@ -1513,9 +1513,9 @@ The desired values of the file descriptor flags.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1540,9 +1540,9 @@ The desired rights of the file descriptor.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1564,7 +1564,7 @@ The buffer where the file's attributes are stored.
###### Variant Layout
- size: 72
- align: 8
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`filestat`](#filestat)
@@ -1587,9 +1587,9 @@ The desired file size.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1618,9 +1618,9 @@ A bitmask indicating which timestamps to adjust.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1653,9 +1653,9 @@ The permissions associated with the file.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1684,7 +1684,7 @@ The number of bytes read.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -1706,7 +1706,7 @@ The buffer where the description is stored.
###### Variant Layout
- size: 12
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`prestat`](#prestat)
@@ -1730,9 +1730,9 @@ A buffer into which to write the preopened directory name.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1765,7 +1765,7 @@ The number of bytes written.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -1791,7 +1791,7 @@ The number of bytes read.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -1829,7 +1829,7 @@ The number of bytes stored in the read buffer. If less than the size of the read
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -1858,9 +1858,9 @@ The file descriptor to overwrite.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1889,7 +1889,7 @@ The new offset of the file descriptor, relative to the start of the file.
###### Variant Layout
- size: 16
- align: 8
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`filesize`](#filesize)
@@ -1909,9 +1909,9 @@ Note: This is similar to `fsync` in POSIX.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1934,7 +1934,7 @@ The current offset of the file descriptor, relative to the start of the file.
###### Variant Layout
- size: 16
- align: 8
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`filesize`](#filesize)
@@ -1964,7 +1964,7 @@ The number of bytes written.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -1989,9 +1989,9 @@ The path at which to create the directory.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2020,7 +2020,7 @@ The buffer where the file's attributes are stored.
###### Variant Layout
- size: 72
- align: 8
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`filestat`](#filestat)
@@ -2055,9 +2055,9 @@ A bitmask indicating which timestamps to adjust.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2096,9 +2096,9 @@ The permissions to associate with the file.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2130,9 +2130,9 @@ The destination path at which to create the hard link.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2186,7 +2186,7 @@ The file descriptor of the file that has been opened.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`fd`](#fd)
@@ -2217,7 +2217,7 @@ The number of bytes placed in the buffer.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -2241,9 +2241,9 @@ The path to a directory to remove.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2272,9 +2272,9 @@ The destination path to which to rename the file or directory.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2300,9 +2300,9 @@ The destination path at which to create the symbolic link.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2326,9 +2326,9 @@ The path to a file to unlink.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2361,7 +2361,7 @@ The number of events stored.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -2408,9 +2408,9 @@ The buffer to fill with random data.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2430,9 +2430,9 @@ Note: This is similar to [`yield`](#yield) in POSIX.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2464,7 +2464,7 @@ Number of bytes stored in ri_data and message flags.
###### Variant Layout
- size: 12
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: `(size, roflags)`
@@ -2503,7 +2503,7 @@ Number of bytes transmitted.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -2526,9 +2526,9 @@ Which channels on the socket to shut down.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
diff --git a/phases/ephemeral/witx/typenames.witx b/phases/ephemeral/witx/typenames.witx
index 12b60bc1..f812a155 100644
--- a/phases/ephemeral/witx/typenames.witx
+++ b/phases/ephemeral/witx/typenames.witx
@@ -5,6 +5,8 @@
;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md)
;; for an explanation of what that means.
+(module $typenames
+
;;; An array size.
;;;
;;; Note: This is similar to `size_t` in POSIX.
@@ -707,3 +709,5 @@
$prestat_dir
)
)
+
+)
diff --git a/phases/ephemeral/witx/wasi_ephemeral_args.witx b/phases/ephemeral/witx/wasi_ephemeral_args.witx
index 92a38512..6b6004c5 100644
--- a/phases/ephemeral/witx/wasi_ephemeral_args.witx
+++ b/phases/ephemeral/witx/wasi_ephemeral_args.witx
@@ -3,9 +3,9 @@
;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md)
;; for an explanation of what that means.
-(use $errno $size from $typenames)
-
(module $wasi_ephemeral_args
+ (use $errno $size from $typenames)
+
;;; Read command-line argument data.
;;; The size of the array should match that returned by `sizes_get`.
;;; Each argument is expected to be `\0` terminated.
diff --git a/phases/ephemeral/witx/wasi_ephemeral_clock.witx b/phases/ephemeral/witx/wasi_ephemeral_clock.witx
index e7fb07e2..33c64df2 100644
--- a/phases/ephemeral/witx/wasi_ephemeral_clock.witx
+++ b/phases/ephemeral/witx/wasi_ephemeral_clock.witx
@@ -5,9 +5,9 @@
;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md)
;; for an explanation of what that means.
-(use $clockid $timestamp $errno from $typenames)
-
(module $wasi_ephemeral_clock
+ (use $clockid $timestamp $errno from $typenames)
+
;;; Return the resolution of a clock.
;;; Implementations are required to provide a non-zero value for supported clocks. For unsupported clocks,
;;; return `errno::inval`.
diff --git a/phases/ephemeral/witx/wasi_ephemeral_environ.witx b/phases/ephemeral/witx/wasi_ephemeral_environ.witx
index 2bb758a0..e6005e99 100644
--- a/phases/ephemeral/witx/wasi_ephemeral_environ.witx
+++ b/phases/ephemeral/witx/wasi_ephemeral_environ.witx
@@ -3,9 +3,9 @@
;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md)
;; for an explanation of what that means.
-(use $errno $size from $typenames)
-
(module $wasi_ephemeral_environ
+ (use $errno $size from $typenames)
+
;;; Read environment variable data.
;;; The sizes of the buffers should match that returned by `sizes_get`.
;;; Key/value pairs are expected to be joined with `=`s, and terminated with `\0`s.
diff --git a/phases/ephemeral/witx/wasi_ephemeral_fd.witx b/phases/ephemeral/witx/wasi_ephemeral_fd.witx
index 84327feb..1c2e59e9 100644
--- a/phases/ephemeral/witx/wasi_ephemeral_fd.witx
+++ b/phases/ephemeral/witx/wasi_ephemeral_fd.witx
@@ -5,9 +5,9 @@
;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md)
;; for an explanation of what that means.
-(use * from $typenames)
-
(module $wasi_ephemeral_fd
+ (use * from $typenames)
+
;;; Provide file advisory information on a file descriptor.
;;; Note: This is similar to `posix_fadvise` in POSIX.
(@interface func (export "advise")
diff --git a/phases/ephemeral/witx/wasi_ephemeral_path.witx b/phases/ephemeral/witx/wasi_ephemeral_path.witx
index a76b0d72..797e82ea 100644
--- a/phases/ephemeral/witx/wasi_ephemeral_path.witx
+++ b/phases/ephemeral/witx/wasi_ephemeral_path.witx
@@ -5,9 +5,9 @@
;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md)
;; for an explanation of what that means.
-(use * from $typenames)
-
(module $wasi_ephemeral_path
+ (use * from $typenames)
+
;;; Create a directory.
;;; Note: This is similar to `mkdirat` in POSIX.
(@interface func (export "create_directory")
diff --git a/phases/ephemeral/witx/wasi_ephemeral_poll.witx b/phases/ephemeral/witx/wasi_ephemeral_poll.witx
index d3e878a4..5a97e66b 100644
--- a/phases/ephemeral/witx/wasi_ephemeral_poll.witx
+++ b/phases/ephemeral/witx/wasi_ephemeral_poll.witx
@@ -5,9 +5,9 @@
;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md)
;; for an explanation of what that means.
-(use $event $subscription $size $errno from $typenames)
-
(module $wasi_ephemeral_poll
+ (use $event $subscription $size $errno from $typenames)
+
;;; Concurrently poll for the occurrence of a set of events.
;;;
;;; If `nsubscriptions` is 0, returns `errno::inval`.
diff --git a/phases/ephemeral/witx/wasi_ephemeral_proc.witx b/phases/ephemeral/witx/wasi_ephemeral_proc.witx
index 74f2d896..d7411b61 100644
--- a/phases/ephemeral/witx/wasi_ephemeral_proc.witx
+++ b/phases/ephemeral/witx/wasi_ephemeral_proc.witx
@@ -5,9 +5,9 @@
;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md)
;; for an explanation of what that means.
-(use $exitcode from $typenames)
-
(module $wasi_ephemeral_proc
+ (use $exitcode from $typenames)
+
;;; Terminate the process normally. An exit code of `$exitcode::success`
;;; reports successful completion of the program. An exit code of
;;; `$exitcode::failure` or any other value less than 126 reports a
diff --git a/phases/ephemeral/witx/wasi_ephemeral_random.witx b/phases/ephemeral/witx/wasi_ephemeral_random.witx
index 29149dae..47abf11a 100644
--- a/phases/ephemeral/witx/wasi_ephemeral_random.witx
+++ b/phases/ephemeral/witx/wasi_ephemeral_random.witx
@@ -5,9 +5,9 @@
;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md)
;; for an explanation of what that means.
-(use $errno $size from $typenames)
-
(module $wasi_ephemeral_random
+ (use $errno $size from $typenames)
+
;;; Write high-quality random data into a buffer.
;;; This function blocks when the implementation is unable to immediately
;;; provide sufficient high-quality random data.
diff --git a/phases/ephemeral/witx/wasi_ephemeral_sched.witx b/phases/ephemeral/witx/wasi_ephemeral_sched.witx
index a558e61d..7477f832 100644
--- a/phases/ephemeral/witx/wasi_ephemeral_sched.witx
+++ b/phases/ephemeral/witx/wasi_ephemeral_sched.witx
@@ -5,9 +5,9 @@
;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md)
;; for an explanation of what that means.
-(use $errno from $typenames)
-
(module $wasi_ephemeral_sched
+ (use $errno from $typenames)
+
;;; Temporarily yield execution of the calling thread.
;;; Note: This is similar to `yield` in POSIX.
(@interface func (export "yield")
diff --git a/phases/ephemeral/witx/wasi_ephemeral_sock.witx b/phases/ephemeral/witx/wasi_ephemeral_sock.witx
index ce680378..1af9826b 100644
--- a/phases/ephemeral/witx/wasi_ephemeral_sock.witx
+++ b/phases/ephemeral/witx/wasi_ephemeral_sock.witx
@@ -5,9 +5,9 @@
;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md)
;; for an explanation of what that means.
-(use * from $typenames)
-
(module $wasi_ephemeral_sock
+ (use * from $typenames)
+
;;; Receive a message from a socket.
;;; Note: This is similar to `recv` in POSIX, though it also supports reading
;;; the data into multiple buffers in the manner of `readv`.
diff --git a/phases/old/snapshot_0/docs.md b/phases/old/snapshot_0/docs.md
index 2585a0db..ab9eee24 100644
--- a/phases/old/snapshot_0/docs.md
+++ b/phases/old/snapshot_0/docs.md
@@ -1280,9 +1280,9 @@ Each argument is expected to be `\0` terminated.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1303,7 +1303,7 @@ data, or an error.
###### Variant Layout
- size: 12
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: `(size, size)`
@@ -1335,9 +1335,9 @@ Key/value pairs are expected to be joined with `=`s, and terminated with `\0`s.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1358,7 +1358,7 @@ environment variable data.
###### Variant Layout
- size: 12
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: `(size, size)`
@@ -1393,7 +1393,7 @@ The resolution of the clock, or an error if one happened.
###### Variant Layout
- size: 16
- align: 8
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`timestamp`](#timestamp)
@@ -1420,7 +1420,7 @@ The time value of the clock.
###### Variant Layout
- size: 16
- align: 8
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`timestamp`](#timestamp)
@@ -1449,9 +1449,9 @@ The advice.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1477,9 +1477,9 @@ The length of the area that is allocated.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1499,9 +1499,9 @@ Note: This is similar to `close` in POSIX.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1521,9 +1521,9 @@ Note: This is similar to `fdatasync` in POSIX.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1546,7 +1546,7 @@ The buffer where the file descriptor's attributes are stored.
###### Variant Layout
- size: 32
- align: 8
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`fdstat`](#fdstat)
@@ -1569,9 +1569,9 @@ The desired values of the file descriptor flags.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1596,9 +1596,9 @@ The desired rights of the file descriptor.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1620,7 +1620,7 @@ The buffer where the file's attributes are stored.
###### Variant Layout
- size: 64
- align: 8
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`filestat`](#filestat)
@@ -1643,9 +1643,9 @@ The desired file size.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1674,9 +1674,9 @@ A bitmask indicating which timestamps to adjust.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1705,7 +1705,7 @@ The number of bytes read.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -1727,7 +1727,7 @@ The buffer where the description is stored.
###### Variant Layout
- size: 12
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`prestat`](#prestat)
@@ -1751,9 +1751,9 @@ A buffer into which to write the preopened directory name.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1782,7 +1782,7 @@ The number of bytes written.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -1808,7 +1808,7 @@ The number of bytes read.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -1846,7 +1846,7 @@ The number of bytes stored in the read buffer. If less than the size of the read
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -1875,9 +1875,9 @@ The file descriptor to overwrite.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1906,7 +1906,7 @@ The new offset of the file descriptor, relative to the start of the file.
###### Variant Layout
- size: 16
- align: 8
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`filesize`](#filesize)
@@ -1926,9 +1926,9 @@ Note: This is similar to `fsync` in POSIX.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1951,7 +1951,7 @@ The current offset of the file descriptor, relative to the start of the file.
###### Variant Layout
- size: 16
- align: 8
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`filesize`](#filesize)
@@ -1976,7 +1976,7 @@ List of scatter/gather vectors from which to retrieve data.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -1999,9 +1999,9 @@ The path at which to create the directory.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2030,7 +2030,7 @@ The buffer where the file's attributes are stored.
###### Variant Layout
- size: 64
- align: 8
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`filestat`](#filestat)
@@ -2065,9 +2065,9 @@ A bitmask indicating which timestamps to adjust.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2099,9 +2099,9 @@ The destination path at which to create the hard link.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2152,7 +2152,7 @@ The file descriptor of the file that has been opened.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`fd`](#fd)
@@ -2183,7 +2183,7 @@ The number of bytes placed in the buffer.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -2207,9 +2207,9 @@ The path to a directory to remove.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2238,9 +2238,9 @@ The destination path to which to rename the file or directory.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2266,9 +2266,9 @@ The destination path at which to create the symbolic link.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2292,9 +2292,9 @@ The path to a file to unlink.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2323,7 +2323,7 @@ The number of events stored.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -2357,9 +2357,9 @@ The signal condition to trigger.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2377,9 +2377,9 @@ Note: This is similar to [`sched_yield`](#sched_yield) in POSIX.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2406,9 +2406,9 @@ The buffer to fill with random data.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2438,7 +2438,7 @@ Number of bytes stored in ri_data and message flags.
###### Variant Layout
- size: 12
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: `(size, roflags)`
@@ -2477,7 +2477,7 @@ Number of bytes transmitted.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -2500,9 +2500,9 @@ Which channels on the socket to shut down.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
diff --git a/phases/old/snapshot_0/witx/typenames.witx b/phases/old/snapshot_0/witx/typenames.witx
index a57a6cdd..ddfaf2e2 100644
--- a/phases/old/snapshot_0/witx/typenames.witx
+++ b/phases/old/snapshot_0/witx/typenames.witx
@@ -5,6 +5,8 @@
;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/main/docs/witx.md)
;; for an explanation of what that means.
+(module $typenames
+
(typename $size u32)
;;; Non-negative file size or length of a region within a file.
@@ -745,3 +747,5 @@
$prestat_dir
)
)
+
+)
diff --git a/phases/old/snapshot_0/witx/wasi_unstable.witx b/phases/old/snapshot_0/witx/wasi_unstable.witx
index ca73b30d..9d77ffe4 100644
--- a/phases/old/snapshot_0/witx/wasi_unstable.witx
+++ b/phases/old/snapshot_0/witx/wasi_unstable.witx
@@ -6,12 +6,12 @@
;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md)
;; for an explanation of what that means.
-(use * from $typenames)
-
;;; This API predated the convention of naming modules with a `wasi_unstable_`
;;; prefix and a version number. It is preserved here for compatibility, but
;;; we shouldn't follow this pattern in new APIs.
(module $wasi_unstable
+ (use * from $typenames)
+
;;; Read command-line argument data.
;;; The size of the array should match that returned by `args_sizes_get`.
;;; Each argument is expected to be `\0` terminated.
diff --git a/phases/snapshot/docs.md b/phases/snapshot/docs.md
index 08f88573..082a8160 100644
--- a/phases/snapshot/docs.md
+++ b/phases/snapshot/docs.md
@@ -1277,9 +1277,9 @@ Each argument is expected to be `\0` terminated.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1300,7 +1300,7 @@ data, or an error.
###### Variant Layout
- size: 12
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: `(size, size)`
@@ -1332,9 +1332,9 @@ Key/value pairs are expected to be joined with `=`s, and terminated with `\0`s.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1355,7 +1355,7 @@ environment variable data.
###### Variant Layout
- size: 12
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: `(size, size)`
@@ -1390,7 +1390,7 @@ The resolution of the clock, or an error if one happened.
###### Variant Layout
- size: 16
- align: 8
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`timestamp`](#timestamp)
@@ -1417,7 +1417,7 @@ The time value of the clock.
###### Variant Layout
- size: 16
- align: 8
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`timestamp`](#timestamp)
@@ -1446,9 +1446,9 @@ The advice.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1474,9 +1474,9 @@ The length of the area that is allocated.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1496,9 +1496,9 @@ Note: This is similar to `close` in POSIX.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1518,9 +1518,9 @@ Note: This is similar to `fdatasync` in POSIX.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1543,7 +1543,7 @@ The buffer where the file descriptor's attributes are stored.
###### Variant Layout
- size: 32
- align: 8
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`fdstat`](#fdstat)
@@ -1566,9 +1566,9 @@ The desired values of the file descriptor flags.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1593,9 +1593,9 @@ The desired rights of the file descriptor.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1617,7 +1617,7 @@ The buffer where the file's attributes are stored.
###### Variant Layout
- size: 72
- align: 8
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`filestat`](#filestat)
@@ -1640,9 +1640,9 @@ The desired file size.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1671,9 +1671,9 @@ A bitmask indicating which timestamps to adjust.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1702,7 +1702,7 @@ The number of bytes read.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -1724,7 +1724,7 @@ The buffer where the description is stored.
###### Variant Layout
- size: 12
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`prestat`](#prestat)
@@ -1748,9 +1748,9 @@ A buffer into which to write the preopened directory name.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1779,7 +1779,7 @@ The number of bytes written.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -1805,7 +1805,7 @@ The number of bytes read.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -1843,7 +1843,7 @@ The number of bytes stored in the read buffer. If less than the size of the read
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -1872,9 +1872,9 @@ The file descriptor to overwrite.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1903,7 +1903,7 @@ The new offset of the file descriptor, relative to the start of the file.
###### Variant Layout
- size: 16
- align: 8
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`filesize`](#filesize)
@@ -1923,9 +1923,9 @@ Note: This is similar to `fsync` in POSIX.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -1948,7 +1948,7 @@ The current offset of the file descriptor, relative to the start of the file.
###### Variant Layout
- size: 16
- align: 8
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`filesize`](#filesize)
@@ -1973,7 +1973,7 @@ List of scatter/gather vectors from which to retrieve data.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -1996,9 +1996,9 @@ The path at which to create the directory.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2027,7 +2027,7 @@ The buffer where the file's attributes are stored.
###### Variant Layout
- size: 72
- align: 8
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`filestat`](#filestat)
@@ -2062,9 +2062,9 @@ A bitmask indicating which timestamps to adjust.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2096,9 +2096,9 @@ The destination path at which to create the hard link.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2149,7 +2149,7 @@ The file descriptor of the file that has been opened.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`fd`](#fd)
@@ -2180,7 +2180,7 @@ The number of bytes placed in the buffer.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -2204,9 +2204,9 @@ The path to a directory to remove.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2235,9 +2235,9 @@ The destination path to which to rename the file or directory.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2263,9 +2263,9 @@ The destination path at which to create the symbolic link.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2289,9 +2289,9 @@ The path to a file to unlink.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2320,7 +2320,7 @@ The number of events stored.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -2354,9 +2354,9 @@ The signal condition to trigger.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2374,9 +2374,9 @@ Note: This is similar to [`sched_yield`](#sched_yield) in POSIX.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2403,9 +2403,9 @@ The buffer to fill with random data.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
@@ -2435,7 +2435,7 @@ Number of bytes stored in ri_data and message flags.
###### Variant Layout
- size: 12
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: `(size, roflags)`
@@ -2474,7 +2474,7 @@ Number of bytes transmitted.
###### Variant Layout
- size: 8
- align: 4
-- tag_size: 4
+- tag_size: 1
###### Variant cases
- `ok`: [`size`](#size)
@@ -2497,9 +2497,9 @@ Which channels on the socket to shut down.
- `error`: `Result<(), errno>`
###### Variant Layout
-- size: 8
-- align: 4
-- tag_size: 4
+- size: 4
+- align: 2
+- tag_size: 1
###### Variant cases
- `ok`
diff --git a/phases/snapshot/witx/typenames.witx b/phases/snapshot/witx/typenames.witx
index b50ff347..20ee6ced 100644
--- a/phases/snapshot/witx/typenames.witx
+++ b/phases/snapshot/witx/typenames.witx
@@ -5,6 +5,8 @@
;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md)
;; for an explanation of what that means.
+(module $typenames
+
(typename $size u32)
;;; Non-negative file size or length of a region within a file.
@@ -747,3 +749,4 @@
)
)
+)
diff --git a/phases/snapshot/witx/wasi_snapshot_preview1.witx b/phases/snapshot/witx/wasi_snapshot_preview1.witx
index fba51a69..9f098479 100644
--- a/phases/snapshot/witx/wasi_snapshot_preview1.witx
+++ b/phases/snapshot/witx/wasi_snapshot_preview1.witx
@@ -6,9 +6,9 @@
;; This is a `witx` file. See [here](https://github.com/WebAssembly/WASI/tree/master/docs/witx.md)
;; for an explanation of what that means.
-(use * from $typenames)
-
(module $wasi_snapshot_preview1
+ (use * from $typenames)
+
;;; Read command-line argument data.
;;; The size of the array should match that returned by `args_sizes_get`.
;;; Each argument is expected to be `\0` terminated.
diff --git a/tools/witx/Cargo.toml b/tools/witx/Cargo.toml
index 1dd39a8b..012d1500 100644
--- a/tools/witx/Cargo.toml
+++ b/tools/witx/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "witx"
-version = "0.9.0"
+version = "0.10.0"
description = "Parse and validate witx file format"
homepage = "https://github.com/WebAssembly/WASI"
repository = "https://github.com/WebAssembly/WASI"
diff --git a/tools/witx/cli/Cargo.toml b/tools/witx/cli/Cargo.toml
index 6f45ef3d..2f6ffed5 100644
--- a/tools/witx/cli/Cargo.toml
+++ b/tools/witx/cli/Cargo.toml
@@ -15,7 +15,7 @@ name = "witx"
path = "src/main.rs"
[dependencies]
-witx = { path = "../", version = "0.9.0" }
+witx = { path = "../", version = "0.10.0" }
anyhow = "1"
log = "0.4"
thiserror = "1.0"
diff --git a/tools/witx/cli/src/main.rs b/tools/witx/cli/src/main.rs
index eb380897..8c7eaecc 100644
--- a/tools/witx/cli/src/main.rs
+++ b/tools/witx/cli/src/main.rs
@@ -1,4 +1,5 @@
use anyhow::Result;
+use std::collections::HashMap;
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
@@ -56,9 +57,10 @@ pub fn main() {
check,
output,
} => {
+ let mut universe = HashMap::new();
let modules = input
.iter()
- .map(|i| load_witx(i, "input", verbose))
+ .map(|i| load_witx(i, &mut universe, "input", verbose))
.collect::>();
let docs = witx::document(&modules);
if check {
diff --git a/tools/witx/src/abi.rs b/tools/witx/src/abi.rs
index 65bee67e..4506fd81 100644
--- a/tools/witx/src/abi.rs
+++ b/tools/witx/src/abi.rs
@@ -20,7 +20,23 @@
//! 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::{
+ Buffer, BuiltinType, Function, Id, IntRepr, NamedType, Param, RecordDatatype, Type, TypeRef,
+ Variant,
+};
+use std::mem;
+
+/// A raw WebAssembly signature with params and results.
+#[derive(Debug)]
+pub struct WasmSignature {
+ /// The WebAssembly parameters of this function.
+ pub params: Vec,
+ /// The WebAssembly results of this function.
+ pub results: Vec,
+ /// The raw types, if needed, returned through return pointer located in
+ /// `params`.
+ pub retptr: Option>,
+}
/// Enumerates wasm types used by interface types when lowering/lifting.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@@ -55,6 +71,9 @@ pub enum Abi {
/// Note that this ABI is limited notably in its return values where it can
/// only return 0 results or one `Result` lookalike.
Preview1,
+
+ /// TODO
+ Next,
}
// Helper macro for defining instructions without having to have tons of
@@ -62,7 +81,7 @@ pub enum Abi {
macro_rules! def_instruction {
(
$( #[$enum_attr:meta] )*
- pub enum Instruction<'a> {
+ pub enum $name:ident<'a> {
$(
$( #[$attr:meta] )*
$variant:ident $( {
@@ -74,7 +93,7 @@ macro_rules! def_instruction {
}
) => {
$( #[$enum_attr] )*
- pub enum Instruction<'a> {
+ pub enum $name<'a> {
$(
$( #[$attr] )*
$variant $( {
@@ -85,7 +104,7 @@ macro_rules! def_instruction {
)*
}
- impl Instruction<'_> {
+ impl $name<'_> {
/// How many operands does this instruction pop from the stack?
#[allow(unused_variables)]
pub fn operands_len(&self) -> usize {
@@ -124,9 +143,75 @@ def_instruction! {
/// Depending on the context this may refer to wasm parameters or
/// interface types parameters.
GetArg { nth: usize } : [0] => [1],
- /// Takes the value off the top of the stack and writes it into linear
- /// memory. Pushes the address in linear memory as an `i32`.
- AddrOf : [1] => [1],
+
+ // Integer const/manipulation instructions
+
+ /// Pushes the constant `val` onto the stack.
+ I32Const { val: i32 } : [0] => [1],
+ /// Casts the top N items on the stack using the `Bitcast` enum
+ /// provided. Consumes the same number of operands that this produces.
+ Bitcasts { casts: &'a [Bitcast] } : [casts.len()] => [casts.len()],
+ /// Pushes a number of constant zeros for each wasm type on the stack.
+ ConstZero { tys: &'a [WasmType] } : [0] => [tys.len()],
+
+ // Memory load/store instructions
+
+ /// Pops an `i32` from the stack and loads a little-endian `i32` from
+ /// it, using the specified constant offset.
+ I32Load { offset: i32 } : [1] => [1],
+ /// Pops an `i32` from the stack and loads a little-endian `i8` from
+ /// it, using the specified constant offset. The value loaded is the
+ /// zero-extended to 32-bits
+ I32Load8U { offset: i32 } : [1] => [1],
+ /// Pops an `i32` from the stack and loads a little-endian `i8` from
+ /// it, using the specified constant offset. The value loaded is the
+ /// sign-extended to 32-bits
+ I32Load8S { offset: i32 } : [1] => [1],
+ /// Pops an `i32` from the stack and loads a little-endian `i16` from
+ /// it, using the specified constant offset. The value loaded is the
+ /// zero-extended to 32-bits
+ I32Load16U { offset: i32 } : [1] => [1],
+ /// Pops an `i32` from the stack and loads a little-endian `i16` from
+ /// it, using the specified constant offset. The value loaded is the
+ /// sign-extended to 32-bits
+ I32Load16S { offset: i32 } : [1] => [1],
+ /// Pops an `i32` from the stack and loads a little-endian `i64` from
+ /// it, using the specified constant offset.
+ I64Load { offset: i32 } : [1] => [1],
+ /// Pops an `i32` from the stack and loads a little-endian `f32` from
+ /// it, using the specified constant offset.
+ F32Load { offset: i32 } : [1] => [1],
+ /// Pops an `i32` from the stack and loads a little-endian `f64` from
+ /// it, using the specified constant offset.
+ F64Load { offset: i32 } : [1] => [1],
+
+ /// Pops an `i32` address from the stack and then an `i32` value.
+ /// Stores the value in little-endian at the pointer specified plus the
+ /// constant `offset`.
+ I32Store { offset: i32 } : [2] => [0],
+ /// Pops an `i32` address from the stack and then an `i32` value.
+ /// Stores the low 8 bits of the value in little-endian at the pointer
+ /// specified plus the constant `offset`.
+ I32Store8 { offset: i32 } : [2] => [0],
+ /// Pops an `i32` address from the stack and then an `i32` value.
+ /// Stores the low 16 bits of the value in little-endian at the pointer
+ /// specified plus the constant `offset`.
+ I32Store16 { offset: i32 } : [2] => [0],
+ /// Pops an `i32` address from the stack and then an `i64` value.
+ /// Stores the value in little-endian at the pointer specified plus the
+ /// constant `offset`.
+ I64Store { offset: i32 } : [2] => [0],
+ /// Pops an `i32` address from the stack and then an `f32` value.
+ /// Stores the value in little-endian at the pointer specified plus the
+ /// constant `offset`.
+ F32Store { offset: i32 } : [2] => [0],
+ /// Pops an `i32` address from the stack and then an `f64` value.
+ /// Stores the value in little-endian at the pointer specified plus the
+ /// constant `offset`.
+ F64Store { offset: i32 } : [2] => [0],
+
+ // Scalar lifting/lowering
+
/// Converts an interface type `char` value to a 32-bit integer
/// representing the unicode scalar value.
I32FromChar : [1] => [1],
@@ -138,8 +223,6 @@ def_instruction! {
I32FromU32 : [1] => [1],
/// Converts an interface type `s32` value to a wasm `i32`.
I32FromS32 : [1] => [1],
- /// Converts a language-specific `usize` value to a wasm `i32`.
- I32FromUsize : [1] => [1],
/// Converts an interface type `u16` value to a wasm `i32`.
I32FromU16 : [1] => [1],
/// Converts an interface type `s16` value to a wasm `i32`.
@@ -148,28 +231,10 @@ def_instruction! {
I32FromU8 : [1] => [1],
/// Converts an interface type `s8` value to a wasm `i32`.
I32FromS8 : [1] => [1],
+ /// Converts a language-specific `usize` value to a wasm `i32`.
+ I32FromUsize : [1] => [1],
/// Converts a language-specific C `char` value to a wasm `i32`.
I32FromChar8 : [1] => [1],
- /// Converts a language-specific pointer value to a wasm `i32`.
- I32FromPointer : [1] => [1],
- /// Converts a language-specific pointer value to a wasm `i32`.
- I32FromConstPointer : [1] => [1],
- /// Converts a language-specific handle value to a wasm `i32`.
- I32FromHandle { ty: &'a NamedType } : [1] => [1],
- /// Converts a language-specific record-of-bools to the packed
- /// representation as an `i32`.
- I32FromBitflags { ty: &'a NamedType } : [1] => [1],
- /// Converts a language-specific record-of-bools to the packed
- /// representation as an `i64`.
- I64FromBitflags { ty: &'a NamedType } : [1] => [1],
- /// Converts an interface type list into its pointer/length, pushing
- /// them both on the stack.
- ListPointerLength : [1] => [2],
- /// Pops two `i32` values from the stack and creates a list from them of
- /// the specified type. The first operand is the pointer in linear
- /// memory to the start of the list and the second operand is the
- /// length.
- ListFromPointerLength { ty: &'a TypeRef } : [2] => [1],
/// Conversion an interface type `f32` value to a wasm `f32`.
///
/// This may be a noop for some implementations, but it's here in case the
@@ -183,135 +248,386 @@ def_instruction! {
/// representation of `f64`.
F64FromIf64 : [1] => [1],
- /// Represents a call to a raw WebAssembly API. The module/name are
- /// provided inline as well as the types if necessary.
- CallWasm {
- module: &'a str,
- name: &'a str,
- params: &'a [WasmType],
- results: &'a [WasmType],
- } : [params.len()] => [results.len()],
-
- /// Same as `CallWasm`, except the dual where an interface is being
- /// called rather than a raw wasm function.
- CallInterface {
- module: &'a str,
- func: &'a Function,
- } : [func.params.len()] => [func.results.len()],
-
- /// Converts a native wasm `i32` to an interface type `s8`.
+ /// Converts a core wasm `i32` to an interface type `s8`.
///
/// This will truncate the upper bits of the `i32`.
S8FromI32 : [1] => [1],
- /// Converts a native wasm `i32` to an interface type `u8`.
+ /// Converts a core wasm `i32` to an interface type `u8`.
///
/// This will truncate the upper bits of the `i32`.
U8FromI32 : [1] => [1],
- /// Converts a native wasm `i32` to an interface type `s16`.
+ /// Converts a core wasm `i32` to an interface type `s16`.
///
/// This will truncate the upper bits of the `i32`.
S16FromI32 : [1] => [1],
- /// Converts a native wasm `i32` to an interface type `u16`.
+ /// Converts a core wasm `i32` to an interface type `u16`.
///
/// This will truncate the upper bits of the `i32`.
U16FromI32 : [1] => [1],
- /// Converts a native wasm `i32` to an interface type `s32`.
+ /// Converts a core wasm `i32` to an interface type `s32`.
S32FromI32 : [1] => [1],
- /// Converts a native wasm `i32` to an interface type `u32`.
+ /// Converts a core wasm `i32` to an interface type `u32`.
U32FromI32 : [1] => [1],
- /// Converts a native wasm `i64` to an interface type `s64`.
+ /// Converts a core wasm `i64` to an interface type `s64`.
S64FromI64 : [1] => [1],
- /// Converts a native wasm `i64` to an interface type `u64`.
+ /// Converts a core wasm `i64` to an interface type `u64`.
U64FromI64 : [1] => [1],
- /// Converts a native wasm `i32` to an interface type `char`.
+ /// Converts a core wasm `i32` to an interface type `char`.
///
/// It's safe to assume that the `i32` is indeed a valid unicode code point.
CharFromI32 : [1] => [1],
- /// Converts a native wasm `i32` to a language-specific C `char`.
+ /// Converts a core wasm `f32` to an interface type `f32`.
+ If32FromF32 : [1] => [1],
+ /// Converts a core wasm `f64` to an interface type `f64`.
+ If64FromF64 : [1] => [1],
+ /// Converts a core wasm `i32` to a language-specific C `char`.
///
/// This will truncate the upper bits of the `i32`.
Char8FromI32 : [1] => [1],
- /// Converts a native wasm `i32` to a language-specific `usize`.
+ /// Converts a core wasm `i32` to a language-specific `usize`.
UsizeFromI32 : [1] => [1],
- /// Converts a native wasm `f32` to an interface type `f32`.
- If32FromF32 : [1] => [1],
- /// Converts a native wasm `f64` to an interface type `f64`.
- If64FromF64 : [1] => [1],
- /// Converts a native wasm `i32` to an interface type `handle`.
- HandleFromI32 { ty: &'a NamedType } : [1] => [1],
- /// Converts a native wasm `i32` to a language-specific pointer.
+
+ // Handles
+
+ /// Converts a "borrowed" handle into a wasm `i32` value.
+ ///
+ /// A "borrowed" handle in this case means one where ownership is not
+ /// being relinquished. This is only used for lowering interface types
+ /// parameters.
+ ///
+ /// Situations that this is used are:
+ ///
+ /// * A wasm exported function receives, as a parameter, handles defined
+ /// by the wasm module itself. This is effectively proof of ownership
+ /// by an external caller (be it host or wasm module) and the
+ /// ownership of the handle still lies with the caller. The wasm
+ /// module is only receiving a reference to the resource.
+ ///
+ /// * A wasm module is calling an import with a handle defined by the
+ /// import's module. Sort of the converse of the previous case this
+ /// means that the wasm module is handing out a reference to a
+ /// resource that it owns. The type in the wasm module, for example,
+ /// needs to reflect this.
+ ///
+ /// This instruction is not used for return values in either
+ /// export/import positions.
+ I32FromBorrowedHandle { ty: &'a NamedType } : [1] => [1],
+
+ /// Converts an "owned" handle into a wasm `i32` value.
+ ///
+ /// This conversion is used for handle values which are crossing a
+ /// module boundary for perhaps the first time. Some example cases of
+ /// when this conversion is used are:
+ ///
+ /// * When a host defines a function to be imported, returned handles
+ /// use this instruction. Handles being returned to wasm a granting a
+ /// capability, which means that this new capability is typically
+ /// wrapped up in a new integer descriptor.
+ ///
+ /// * When a wasm module calls an imported function with a type defined
+ /// by itself, then it's granting a capability to the callee. This
+ /// means that the wasm module's type is being granted for the first
+ /// time, possibly, so it needs to be an owned value that's consumed.
+ /// Note that this doesn't actually happen with `*.witx` today due to
+ /// the lack of handle type imports.
+ ///
+ /// * When a wasm module export returns a handle defined within the
+ /// module, then it's similar to calling an imported function with
+ /// that handle. The capability is being granted to the caller of the
+ /// export, so the owned value is wrapped up in an `i32`.
+ ///
+ /// * When a host is calling a wasm module with a capability defined by
+ /// the host, its' similar to the host import returning a capability.
+ /// This would be granting the wasm module with the capability so an
+ /// owned version with a fresh handle is passed to the wasm module.
+ /// Note that this doesn't happen today with `*.witx` due to the lack
+ /// of handle type imports.
+ ///
+ /// Basically this instruction is used for handle->wasm conversions
+ /// depending on the calling context and where the handle type in
+ /// question was defined.
+ I32FromOwnedHandle { ty: &'a NamedType } : [1] => [1],
+
+ /// Converts a core wasm `i32` into an owned handle value.
+ ///
+ /// This is the converse of `I32FromOwnedHandle` and is used in similar
+ /// situations:
+ ///
+ /// * A host definition of an import receives a handle defined in the
+ /// module itself.
+ /// * A wasm module calling an import receives a handle defined by the
+ /// import.
+ /// * A wasm module's export receives a handle defined by an external
+ /// module.
+ /// * A host calling a wasm export receives a handle defined in the
+ /// module.
+ ///
+ /// Note that like `I32FromOwnedHandle` the first and third bullets
+ /// above don't happen today because witx can't express type imports
+ /// just yet.
+ HandleOwnedFromI32 { ty: &'a NamedType } : [1] => [1],
+
+ /// Converts a core wasm `i32` into a borrowedhandle value.
+ ///
+ /// This is the converse of `I32FromBorrowedHandle` and is used in similar
+ /// situations:
+ ///
+ /// * An exported wasm function receives, as a parameter, a handle that
+ /// is defined by the wasm module.
+ /// * An host-defined imported function is receiving a handle, as a
+ /// parameter, that is defined by the host itself.
+ HandleBorrowedFromI32 { ty: &'a NamedType } : [1] => [1],
+
+ // lists
+
+ /// Lowers a list where the element's layout in the native language is
+ /// expected to match the canonical ABI definition of interface types.
+ ///
+ /// Pops a list value from the stack and pushes the pointer/length onto
+ /// the stack. If `malloc` is set to `Some` then this is expected to
+ /// *consume* the list which means that the data needs to be copied. An
+ /// allocation/copy is expected when:
+ ///
+ /// * A host is calling a wasm export with a list (it needs to copy the
+ /// list in to the callee's module, allocating space with `malloc`)
+ /// * A wasm export is returning a list (it's expected to use `malloc`
+ /// to give ownership of the list to the caller.
+ /// * A host is returning a list in a import definition, meaning that
+ /// space needs to be allocated in the caller with `malloc`).
+ ///
+ /// A copy does not happen (e.g. `malloc` is `None`) when:
+ ///
+ /// * A wasm module calls an import with the list. In this situation
+ /// it's expected the caller will know how to access this module's
+ /// memory (e.g. the host has raw access or wasm-to-wasm communication
+ /// would copy the list).
+ ///
+ /// If `malloc` is `Some` then the adapter is not responsible for
+ /// cleaning up this list because the other end is receiving the
+ /// allocation. If `malloc` is `None` then the adapter is responsible
+ /// for cleaning up any temporary allocation it created, if any.
+ ListCanonLower {
+ element: &'a TypeRef,
+ malloc: Option<&'a str>,
+ } : [1] => [2],
+
+ /// Lowers a list where the element's layout in the native language is
+ /// not expected to match the canonical ABI definition of interface
+ /// types.
+ ///
+ /// Pops a list value from the stack and pushes the pointer/length onto
+ /// the stack. This operation also pops a block from the block stack
+ /// which is used as the iteration body of writing each element of the
+ /// list consumed.
+ ///
+ /// The `malloc` field here behaves the same way as `ListCanonLower`.
+ /// It's only set to `None` when a wasm module calls a declared import.
+ /// Otherwise lowering in other contexts requires allocating memory for
+ /// the receiver to own.
+ ListLower {
+ element: &'a TypeRef,
+ malloc: Option<&'a str>,
+ } : [1] => [2],
+
+ /// Lifts a list which has a canonical representation into an interface
+ /// types value.
+ ///
+ /// The term "canonical" representation here means that the
+ /// representation of the interface types value in the native language
+ /// exactly matches the canonical ABI definition of the type.
+ ///
+ /// This will consume two `i32` values from the stack, a pointer and a
+ /// length, and then produces an interface value list. If the `free`
+ /// field is set to `Some` then the pointer/length should be considered
+ /// an owned allocation and need to be deallocated by the receiver. If
+ /// it is set to `None` then a view is provided but it does not need to
+ /// be deallocated.
+ ///
+ /// The `free` field is set to `Some` in similar situations as described
+ /// by `ListCanonLower`. If `free` is `Some` then the memory must be
+ /// deallocated after the lifted list is done being consumed. If it is
+ /// `None` then the receiver of the lifted list does not own the memory
+ /// and must leave the memory as-is.
+ ListCanonLift {
+ element: &'a TypeRef,
+ free: Option<&'a str>,
+ } : [2] => [1],
+
+ /// Lifts a list which into an interface types value.
+ ///
+ /// This will consume two `i32` values from the stack, a pointer and a
+ /// length, and then produces an interface value list. Note that the
+ /// pointer/length popped are **owned** and need to be deallocated with
+ /// the wasm `free` function when the list is no longer needed.
+ ///
+ /// This will also pop a block from the block stack which is how to
+ /// read each individual element from the list.
+ ListLift {
+ element: &'a TypeRef,
+ free: Option<&'a str>,
+ } : [2] => [1],
+
+ /// Pushes an operand onto the stack representing the list item from
+ /// each iteration of the list.
+ ///
+ /// This is only used inside of blocks related to lowering lists.
+ IterElem : [0] => [1],
+
+ /// Pushes an operand onto the stack representing the base pointer of
+ /// the next element in a list.
+ ///
+ /// This is used for both lifting and lowering lists.
+ IterBasePointer : [0] => [1],
+
+ // buffers
+
+ /// Pops a buffer value, pushes the pointer/length of where it points
+ /// to in memory.
+ BufferLowerPtrLen { buffer: &'a Buffer } : [1] => [2],
+ /// Pops a buffer value, pushes an integer handle for the buffer.
+ BufferLowerHandle { buffer: &'a Buffer } : [1] => [1],
+ /// Pops a ptr/len, pushes a buffer wrapping that ptr/len of the memory
+ /// from the origin module.
+ BufferLiftPtrLen { buffer: &'a Buffer } : [2] => [1],
+ /// Pops an i32, pushes a buffer wrapping that i32 handle.
+ BufferLiftHandle { buffer: &'a Buffer } : [1] => [1],
+
+ // records
+
+ /// Pops a record value off the stack, decomposes the record to all of
+ /// its fields, and then pushes the fields onto the stack.
+ RecordLower {
+ ty: &'a RecordDatatype,
+ name: Option<&'a NamedType>,
+ } : [1] => [ty.members.len()],
+
+ /// Pops all fields for a record off the stack and then composes them
+ /// into a record.
+ RecordLift {
+ ty: &'a RecordDatatype,
+ name: Option<&'a NamedType>,
+ } : [ty.members.len()] => [1],
+
+ /// Converts a language-specific record-of-bools to the packed
+ /// representation as an `i32`.
+ I32FromBitflags {
+ ty: &'a RecordDatatype,
+ name: &'a NamedType,
+ } : [1] => [1],
+ /// Converts a language-specific record-of-bools to the packed
+ /// representation as an `i64`.
+ I64FromBitflags {
+ ty: &'a RecordDatatype,
+ name: &'a NamedType,
+ } : [1] => [1],
+ /// Converts a core wasm `i32` to a language-specific record-of-bools.
+ BitflagsFromI32 {
+ repr: IntRepr,
+ ty: &'a RecordDatatype,
+ name: &'a NamedType,
+ } : [1] => [1],
+ /// Converts a core wasm `i64` to a language-specific record-of-bools.
+ BitflagsFromI64 {
+ repr: IntRepr,
+ ty: &'a RecordDatatype,
+ name: &'a NamedType,
+ } : [1] => [1],
+
+ // variants
+
+ /// This is a special instruction used at the entry of blocks used as
+ /// part of `ResultLower`, representing that the payload of that variant
+ /// being matched on should be pushed onto the stack.
+ VariantPayload : [0] => [1],
+
+ /// Pops a variant off the stack as well as `ty.cases.len()` blocks
+ /// from the code generator. Uses each of those blocks and the value
+ /// from the stack to produce `nresults` of items.
+ VariantLower {
+ ty: &'a Variant,
+ name: Option<&'a NamedType>,
+ nresults: usize,
+ } : [1] => [*nresults],
+
+ /// Pops an `i32` off the stack as well as `ty.cases.len()` blocks
+ /// from the code generator. Uses each of those blocks and the value
+ /// from the stack to produce a final variant.
+ VariantLift {
+ ty: &'a Variant,
+ name: Option<&'a NamedType>,
+ } : [1] => [1],
+
+ // calling/control flow
+
+ /// Represents a call to a raw WebAssembly API. The module/name are
+ /// provided inline as well as the types if necessary.
+ CallWasm {
+ module: &'a str,
+ name: &'a str,
+ params: &'a [WasmType],
+ results: &'a [WasmType],
+ } : [params.len()] => [results.len()],
+
+ /// Same as `CallWasm`, except the dual where an interface is being
+ /// called rather than a raw wasm function.
+ CallInterface {
+ module: &'a str,
+ func: &'a Function,
+ } : [func.params.len()] => [func.results.len()],
+
+ /// Returns `amt` values on the stack. This is always the last
+ /// instruction.
+ Return { amt: usize } : [*amt] => [0],
+
+ // ...
+
+ /// An instruction from an extended instruction set that's specific to
+ /// `*.witx` and the "Preview1" ABI.
+ Witx {
+ instr: &'a WitxInstruction<'a>,
+ } : [instr.operands_len()] => [instr.results_len()],
+ }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum Bitcast {
+ // Upcasts
+ F32ToF64,
+ F32ToI32,
+ F64ToI64,
+ I32ToI64,
+ F32ToI64,
+
+ // Downcasts
+ F64ToF32,
+ I32ToF32,
+ I64ToF64,
+ I64ToI32,
+ I64ToF32,
+
+ None,
+}
+
+def_instruction! {
+ #[derive(Debug)]
+ pub enum WitxInstruction<'a> {
+ /// Takes the value off the top of the stack and writes it into linear
+ /// memory. Pushes the address in linear memory as an `i32`.
+ AddrOf : [1] => [1],
+
+ /// Converts a language-specific pointer value to a wasm `i32`.
+ I32FromPointer : [1] => [1],
+ /// Converts a language-specific pointer value to a wasm `i32`.
+ I32FromConstPointer : [1] => [1],
+ /// Converts a core wasm `i32` to a language-specific pointer.
PointerFromI32 { ty: &'a TypeRef }: [1] => [1],
- /// Converts a native wasm `i32` to a language-specific pointer.
+ /// Converts a core wasm `i32` to a language-specific pointer.
ConstPointerFromI32 { ty: &'a TypeRef } : [1] => [1],
- /// Converts a native wasm `i32` to a language-specific record-of-bools.
- BitflagsFromI32 { ty: &'a NamedType } : [1] => [1],
- /// Converts a native wasm `i64` to a language-specific record-of-bools.
- BitflagsFromI64 { ty: &'a NamedType } : [1] => [1],
- /// Acquires the return pointer `n` and pushes an `i32` on the stack.
- ///
- /// Implementations of [`Bindgen`] may have [`Bindgen::allocate_space`]
- /// called to reserve space in memory for the result of a computation to
- /// get written. This instruction acquires a pointer to the space
- /// reserved in `allocate_space`.
- ReturnPointerGet { n: usize } : [0] => [1],
- /// Loads the interface types value from an `i32` pointer popped from
- /// the stack.
- Load { ty: &'a NamedType } : [1] => [1],
- /// Stores an interface types value into linear memory. The first
- /// operand is the value to store and the second operand is the pointer
- /// in linear memory to store it at.
- Store { ty: &'a NamedType } : [2] => [0],
- /// Pops a native wasm `i32` from the stack, as well as two blocks
- /// internally from the code generator.
- ///
- /// If the value is 0 then the first "ok" block value should be used.
- /// If the value is anything else then the second "err" block value
- /// should be used, and the value is used as the error enum.
- ///
- /// Note that this is a special instruction matching the current ABI of
- /// WASI and intentionally differs from the type-level grammar of
- /// interface types results.
- ResultLift : [1] => [1],
- /// Pops a native interface value from the stack as well as two blocks
- /// internally from the code generator.
- ///
- /// A `match` is performed on the value popped and the corresponding
- /// block for ok/err is used depending on value. This pushes a single
- /// `i32` onto the stack representing the error code for this result.
- ///
- /// Note that like `ResultLift` this is specialized to the current WASI
- /// ABI.
- ResultLower {
- ok: Option<&'a TypeRef>,
- err: Option<&'a TypeRef>,
- } : [1] => [1],
- /// Converts a native wasm `i32` to an interface type `enum` value.
- ///
- /// It's guaranteed that the interface type integer value is within
- /// range for this enum's type. Additionally `ty` is guaranteed to be
- /// enum-like as a `Variant` where all `case` arms have no associated
- /// type with them. The purpose of this instruction is to convert a
- /// native wasm integer into the enum type for the interface.
- EnumLift { ty: &'a NamedType } : [1] => [1],
- /// Converts an interface types enum value into a wasm `i32`.
- EnumLower { ty: &'a NamedType } : [1] => [1],
- /// Creates a tuple from the top `n` elements on the stack, pushing the
- /// tuple onto the stack.
- TupleLift { amt: usize } : [*amt] => [1],
- /// Splits a tuple at the top of the stack into its `n` components,
- /// pushing them all onto the stack.
- TupleLower { amt: usize } : [1] => [*amt],
+
/// This is a special instruction specifically for the original ABI of
/// WASI. The raw return `i32` of a function is re-pushed onto the
/// stack for reuse.
ReuseReturn : [0] => [1],
- /// Returns `amt` values on the stack. This is always the last
- /// instruction.
- Return { amt: usize } : [*amt] => [0],
- /// This is a special instruction used at the entry of blocks used as
- /// part of `ResultLower`, representing that the payload of that variant
- /// being matched on should be pushed onto the stack.
- VariantPayload : [0] => [1],
}
}
@@ -320,7 +636,19 @@ 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: &[Param], results: &[Param]) -> Result<(), String> {
+ for ty in params.iter() {
+ self.validate_ty(ty.tref.type_(), true)?;
+ }
+ for ty in results.iter() {
+ self.validate_ty(ty.tref.type_(), false)?;
+ }
+ match self {
+ Abi::Preview1 => {
+ // validated below...
+ }
+ Abi::Next => return Ok(()),
+ }
assert_eq!(*self, Abi::Preview1);
match results.len() {
0 => {}
@@ -363,12 +691,70 @@ impl Abi {
}
}
Type::Record(r) if r.bitflags_repr().is_some() => {}
- Type::Record(_) | Type::List(_) => return Err("invalid return type".to_string()),
+ Type::Record(_) | Type::List(_) | Type::Buffer(_) => {
+ return Err("invalid return type".to_string())
+ }
},
_ => return Err("more than one result".to_string()),
}
Ok(())
}
+
+ fn validate_ty(&self, ty: &Type, param: bool) -> Result<(), String> {
+ match ty {
+ Type::Record(r) => {
+ for r in r.members.iter() {
+ self.validate_ty(r.tref.type_(), param)?;
+ }
+ Ok(())
+ }
+ Type::Variant(v) => {
+ for case in v.cases.iter() {
+ if let Some(ty) = &case.tref {
+ self.validate_ty(ty.type_(), param)?;
+ }
+ }
+ Ok(())
+ }
+ Type::Handle(_) => Ok(()),
+ Type::List(t) => self.validate_ty(t.type_(), param),
+ Type::Pointer(t) => {
+ if let Abi::Next = self {
+ return Err("cannot use `(@witx pointer)` in this ABI".to_string());
+ }
+ self.validate_ty(t.type_(), param)
+ }
+ Type::ConstPointer(t) => {
+ if let Abi::Next = self {
+ return Err("cannot use `(@witx const_pointer)` in this ABI".to_string());
+ }
+ self.validate_ty(t.type_(), param)
+ }
+ Type::Builtin(BuiltinType::U8 { lang_c_char: true }) => {
+ if let Abi::Next = self {
+ return Err("cannot use `(@witx char8)` in this ABI".to_string());
+ }
+ Ok(())
+ }
+ Type::Builtin(BuiltinType::U32 {
+ lang_ptr_size: true,
+ }) => {
+ if let Abi::Next = self {
+ return Err("cannot use `(@witx usize)` in this ABI".to_string());
+ }
+ Ok(())
+ }
+ Type::Buffer(t) => {
+ if !param {
+ return Err("cannot use buffers in the result position".to_string());
+ }
+ // If this is an output buffer then validate `t` as if it were a
+ // result because the callee can't give us buffers back.
+ self.validate_ty(t.tref.type_(), param && !t.out)
+ }
+ Type::Builtin(_) => Ok(()),
+ }
+ }
}
/// Trait for language implementors to use to generate glue code between native
@@ -387,7 +773,7 @@ pub trait Bindgen {
/// The intermediate type for fragments of code for this type.
///
/// For most languages `String` is a suitable intermediate type.
- type Operand;
+ type Operand: Clone;
/// Emit code to implement the given instruction.
///
@@ -404,12 +790,24 @@ pub trait Bindgen {
results: &mut Vec,
);
- /// Allocates temporary space in linear memory indexed by `slot` with enough
- /// space to store `ty`.
+ /// Allocates temporary space in linear memory for the type `ty`.
///
/// This is called when calling some wasm functions where a return pointer
- /// is needed.
- fn allocate_space(&mut self, slot: usize, ty: &NamedType);
+ /// is needed. Only used for the `Abi::Preview1` ABI.
+ ///
+ /// Returns an `Operand` which has type `i32` and is the base of the typed
+ /// allocation in memory.
+ fn allocate_typed_space(&mut self, ty: &NamedType) -> Self::Operand;
+
+ /// Allocates temporary space in linear memory for a fixed number of `i64`
+ /// values.
+ ///
+ /// This is only called in the `Abi::Next` ABI for when a function would
+ /// otherwise have multiple results.
+ ///
+ /// Returns an `Operand` which has type `i32` and points to the base of the
+ /// fixed-size-array allocation.
+ fn allocate_i64_array(&mut self, amt: usize) -> Self::Operand;
/// Enters a new block of code to generate code for.
///
@@ -435,7 +833,7 @@ pub trait Bindgen {
/// block before `push_block` was called. This must also save the results
/// of the current block internally for instructions like `ResultLift` to
/// use later.
- fn finish_block(&mut self, operand: Option);
+ fn finish_block(&mut self, operand: &mut Vec);
}
impl Function {
@@ -443,70 +841,53 @@ impl Function {
///
/// The first entry returned is the list of parameters and the second entry
/// is the list of results for the wasm function signature.
- pub fn wasm_signature(&self) -> (Vec, Vec) {
- assert_eq!(self.abi, Abi::Preview1);
+ pub fn wasm_signature(&self, mode: CallMode) -> WasmSignature {
let mut params = Vec::new();
let mut results = Vec::new();
for param in self.params.iter() {
match &**param.tref.type_() {
- Type::Builtin(BuiltinType::S8)
- | Type::Builtin(BuiltinType::U8 { .. })
- | Type::Builtin(BuiltinType::S16)
- | Type::Builtin(BuiltinType::U16)
- | Type::Builtin(BuiltinType::S32)
- | Type::Builtin(BuiltinType::U32 { .. })
- | Type::Builtin(BuiltinType::Char)
+ Type::Builtin(_)
| Type::Pointer(_)
| Type::ConstPointer(_)
| Type::Handle(_)
- | Type::Variant(_) => params.push(WasmType::I32),
-
- Type::Record(r) => match r.bitflags_repr() {
- Some(repr) => params.push(WasmType::from(repr)),
- None => params.push(WasmType::I32),
- },
-
- Type::Builtin(BuiltinType::S64) | Type::Builtin(BuiltinType::U64) => {
- params.push(WasmType::I64)
- }
-
- Type::Builtin(BuiltinType::F32) => params.push(WasmType::F32),
- Type::Builtin(BuiltinType::F64) => params.push(WasmType::F64),
-
- Type::List(_) => {
- params.push(WasmType::I32);
- params.push(WasmType::I32);
+ | Type::List(_)
+ | Type::Buffer(_) => {
+ push_wasm(mode, param.tref.type_(), &mut params);
}
+ ty @ Type::Variant(_) => match self.abi {
+ Abi::Preview1 => params.push(WasmType::I32),
+ Abi::Next => push_wasm(mode, ty, &mut params),
+ },
+ Type::Record(r) => match self.abi {
+ Abi::Preview1 => match r.bitflags_repr() {
+ Some(repr) => params.push(WasmType::from(repr)),
+ None => params.push(WasmType::I32),
+ },
+ Abi::Next => push_wasm(mode, param.tref.type_(), &mut params),
+ },
}
}
for param in self.results.iter() {
match &**param.tref.type_() {
- Type::Builtin(BuiltinType::S8)
- | Type::Builtin(BuiltinType::U8 { .. })
- | Type::Builtin(BuiltinType::S16)
- | Type::Builtin(BuiltinType::U16)
- | Type::Builtin(BuiltinType::S32)
- | Type::Builtin(BuiltinType::U32 { .. })
- | Type::Builtin(BuiltinType::Char)
+ Type::List(_)
+ | Type::Builtin(_)
| Type::Pointer(_)
| Type::ConstPointer(_)
- | Type::Handle(_) => results.push(WasmType::I32),
-
- Type::Builtin(BuiltinType::S64) | Type::Builtin(BuiltinType::U64) => {
- results.push(WasmType::I64)
+ | Type::Record(_)
+ | Type::Handle(_)
+ | Type::Buffer(_) => {
+ push_wasm(mode, param.tref.type_(), &mut results);
}
- Type::Builtin(BuiltinType::F32) => results.push(WasmType::F32),
- Type::Builtin(BuiltinType::F64) => results.push(WasmType::F64),
-
- Type::Record(r) => match r.bitflags_repr() {
- Some(repr) => results.push(WasmType::from(repr)),
- None => unreachable!(),
- },
- Type::List(_) => unreachable!(),
-
Type::Variant(v) => {
+ match self.abi {
+ Abi::Preview1 => {} // handled below
+ Abi::Next => {
+ push_wasm(mode, param.tref.type_(), &mut results);
+ continue;
+ }
+ }
results.push(match v.tag_repr {
IntRepr::U64 => WasmType::I64,
IntRepr::U32 | IntRepr::U16 | IntRepr::U8 => WasmType::I32,
@@ -528,7 +909,21 @@ impl Function {
}
}
}
- (params, results)
+
+ // Rust/C don't support multi-value well right now, so if a function
+ // would have multiple results then instead truncate it to have 0
+ // results and instead insert a return pointer.
+ let mut retptr = None;
+ if results.len() > 1 {
+ params.push(WasmType::I32);
+ retptr = Some(mem::take(&mut results));
+ }
+
+ WasmSignature {
+ params,
+ results,
+ retptr,
+ }
}
/// Generates an abstract sequence of instructions which represents this
@@ -546,104 +941,289 @@ impl Function {
/// language-specific values into the wasm types to call a WASI function,
/// and it will also automatically convert the results of the WASI function
/// back to a language-specific value.
- pub fn call_wasm(&self, module: &Id, bindgen: &mut impl Bindgen) {
- assert_eq!(self.abi, Abi::Preview1);
- Generator {
- bindgen,
- operands: vec![],
- results: vec![],
- stack: vec![],
+ pub fn call(&self, module: &Id, mode: CallMode, bindgen: &mut impl Bindgen) {
+ if Abi::Preview1 == self.abi {
+ match mode {
+ // The Preview1 ABI only works with WASI which is only intended
+ // for use with these modes.
+ CallMode::DefinedImport | CallMode::DeclaredImport => {}
+ _ => {
+ panic!("the preview1 ABI only supports import modes");
+ }
+ }
}
- .call_wasm(module, self);
+ Generator::new(self.abi, mode, bindgen).call(module, self);
}
+}
- /// This is the dual of [`Function::call_wasm`], except that instead of
- /// calling a wasm signature it generates code to come from a wasm signature
- /// and call an interface types signature.
- pub fn call_interface(&self, module: &Id, bindgen: &mut impl Bindgen) {
- assert_eq!(self.abi, Abi::Preview1);
- Generator {
- bindgen,
- operands: vec![],
- results: vec![],
- stack: vec![],
+/// Modes of calling a WebAssembly or host function.
+///
+/// Each mode may have a slightly different codegen for some types, so
+/// [`Function::call`] takes this as a parameter to know in what context
+/// the invocation is happening within.
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum CallMode {
+ /// A defined export is being called.
+ ///
+ /// This typically means that a code generator is generating a shim function
+ /// to get exported from a WebAssembly module and the shim is calling a
+ /// native language function defined within the wasm module. In this mode
+ /// arguments are being lifted from wasm types to interface types, and
+ /// results are being lowered.
+ DefinedExport,
+
+ /// A declared export is being called.
+ ///
+ /// This typically means that a code generator is generating calls to a
+ /// WebAssembly module, for example a JS host calling a WebAssembly
+ /// instance. In this mode the native language's arguments are being lowered
+ /// to wasm values and the results of the function are lifted back into the
+ /// native language.
+ DeclaredExport,
+
+ /// A defined import is being called.
+ ///
+ /// This is typically used for code generators that are creating bindings in
+ /// a host for a function imported by WebAssembly. In this mode a function
+ /// with a wasm signature is generated and that function's wasm arguments
+ /// are lifted into the native language's arguments. The results are then
+ /// lowered back into wasm arguments to return.
+ DefinedImport,
+
+ /// A declared import is being called
+ ///
+ /// This is typically used for code generators that are calling an imported
+ /// function from within a wasm module. In this mode native language
+ /// arguments are lowered to wasm values, and the results of the import are
+ /// lifted back into the native language.
+ DeclaredImport,
+}
+
+impl CallMode {
+ pub fn export(&self) -> bool {
+ match self {
+ CallMode::DefinedExport | CallMode::DeclaredExport => true,
+ CallMode::DefinedImport | CallMode::DeclaredImport => false,
+ }
+ }
+
+ pub fn defined(&self) -> bool {
+ match self {
+ CallMode::DefinedExport | CallMode::DefinedImport => true,
+ CallMode::DeclaredExport | CallMode::DeclaredImport => false,
}
- .call_interface(module, self);
}
}
struct Generator<'a, B: Bindgen> {
+ abi: Abi,
+ mode: CallMode,
bindgen: &'a mut B,
operands: Vec,
results: Vec,
stack: Vec,
+ return_pointers: Vec,
}
-impl Generator<'_, B> {
- fn call_wasm(&mut self, module: &Id, func: &Function) {
- // Translate all parameters which are interface values by lowering them
- // to their wasm types.
- for (nth, param) in func.params.iter().enumerate() {
- self.emit(&Instruction::GetArg { nth });
- self.lower(¶m.tref, None);
+impl<'a, B: Bindgen> Generator<'a, B> {
+ fn new(abi: Abi, mode: CallMode, bindgen: &'a mut B) -> Generator<'a, B> {
+ Generator {
+ abi,
+ mode,
+ bindgen,
+ operands: Vec::new(),
+ results: Vec::new(),
+ stack: Vec::new(),
+ return_pointers: Vec::new(),
}
+ }
- // If necessary for our ABI, insert return pointers for any returned
- // values through a result.
- assert!(func.results.len() < 2);
- if let Some(result) = func.results.get(0) {
- self.prep_return_pointer(&result.tref.type_());
+ fn call(&mut self, module: &Id, func: &Function) {
+ let sig = func.wasm_signature(self.mode);
+
+ match self.mode {
+ CallMode::DeclaredExport | CallMode::DeclaredImport => {
+ // Push all parameters for this function onto the stack, and
+ // then batch-lower everything all at once.
+ for nth in 0..func.params.len() {
+ self.emit(&Instruction::GetArg { nth });
+ }
+ self.lower_all(&func.params, None);
+
+ // If necessary we may need to prepare a return pointer for this
+ // ABI. The `Preview1` ABI has most return values returned
+ // through pointers, and the `Next` ABI returns more-than-one
+ // values through a return pointer.
+ self.prep_return_pointer(&sig, &func.results);
+
+ // Now that all the wasm args are prepared we can call the
+ // actual wasm function.
+ assert_eq!(self.stack.len(), sig.params.len());
+ self.emit(&Instruction::CallWasm {
+ module: module.as_str(),
+ name: func.name.as_str(),
+ params: &sig.params,
+ results: &sig.results,
+ });
+
+ // In the `Next` ABI we model multiple return values by going
+ // through memory. Remove that indirection here by loading
+ // everything to simulate the function having many return values
+ // in our stack discipline.
+ if let Some(actual) = &sig.retptr {
+ self.load_retptr(actual);
+ }
+
+ // Batch-lift all result values now that all the function's return
+ // values are on the stack.
+ self.lift_all(&func.results);
+
+ self.emit(&Instruction::Return {
+ amt: func.results.len(),
+ });
+ }
+
+ CallMode::DefinedExport | CallMode::DefinedImport => {
+ // Use `GetArg` to push all relevant arguments onto the stack.
+ // Note that we can't use the signature of this function
+ // directly due to various conversions and return pointers, so
+ // we need to somewhat manually calculate all the arguments
+ // which are converted as interface types arguments below.
+ let sig = func.wasm_signature(self.mode);
+ let nargs = match self.abi {
+ Abi::Preview1 => {
+ func.params.len()
+ + func
+ .params
+ .iter()
+ .filter(|t| match &**t.tref.type_() {
+ Type::List(_) => true,
+ _ => false,
+ })
+ .count()
+ }
+ Abi::Next => sig.params.len() - sig.retptr.is_some() as usize,
+ };
+ for nth in 0..nargs {
+ self.emit(&Instruction::GetArg { nth });
+ }
+
+ // Once everything is on the stack we can lift all arguments
+ // one-by-one into their interface-types equivalent.
+ self.lift_all(&func.params);
+
+ // ... and that allows us to call the interface types function
+ self.emit(&Instruction::CallInterface {
+ module: module.as_str(),
+ func,
+ });
+
+ // ... and at the end we lower everything back into return
+ // values.
+ self.lower_all(&func.results, Some(nargs));
+
+ // Our ABI dictates that a list of returned types are returned
+ // through memories, so after we've got all the values on the
+ // stack perform all of the stores here.
+ if let Some(tys) = &sig.retptr {
+ self.store_retptr(tys, sig.params.len() - 1);
+ }
+
+ self.emit(&Instruction::Return {
+ amt: sig.results.len(),
+ });
+ }
}
- let (params, results) = func.wasm_signature();
- self.emit(&Instruction::CallWasm {
- module: module.as_str(),
- name: func.name.as_str(),
- params: ¶ms,
- results: &results,
- });
+ assert!(
+ self.stack.is_empty(),
+ "stack has {} items remaining",
+ self.stack.len()
+ );
+ }
- // Lift the return value if one is present.
- if let Some(result) = func.results.get(0) {
- self.lift(&result.tref, true);
+ fn load_retptr(&mut self, types: &[WasmType]) {
+ assert_eq!(self.return_pointers.len(), 1);
+ for (i, ty) in types.iter().enumerate() {
+ self.stack.push(self.return_pointers[0].clone());
+ let offset = (i * 8) as i32;
+ match ty {
+ WasmType::I32 => self.emit(&Instruction::I32Load { offset }),
+ WasmType::I64 => self.emit(&Instruction::I64Load { offset }),
+ WasmType::F32 => self.emit(&Instruction::F32Load { offset }),
+ WasmType::F64 => self.emit(&Instruction::F64Load { offset }),
+ }
}
+ }
- self.emit(&Instruction::Return {
- amt: func.results.len(),
- });
+ /// Assumes that the wasm values to create `tys` are all located on the
+ /// stack.
+ ///
+ /// Inserts instructions necesesary to lift those types into their
+ /// interface types equivalent.
+ fn lift_all(&mut self, tys: &[Param]) {
+ let mut temp = Vec::new();
+ let operands = tys
+ .iter()
+ .rev()
+ .map(|ty| {
+ let ntys = match self.abi {
+ Abi::Preview1 => match &**ty.tref.type_() {
+ Type::List(_) => 2,
+ _ => 1,
+ },
+ Abi::Next => {
+ temp.truncate(0);
+ push_wasm(self.mode, ty.tref.type_(), &mut temp);
+ temp.len()
+ }
+ };
+ self.stack
+ .drain(self.stack.len() - ntys..)
+ .collect::>()
+ })
+ .collect::>();
+ for (operands, ty) in operands.into_iter().rev().zip(tys) {
+ self.stack.extend(operands);
+ self.lift(&ty.tref);
+ }
}
- fn call_interface(&mut self, module: &Id, func: &Function) {
- // Lift all wasm parameters into interface types first.
- //
- // Note that consuming arguments is somewhat janky right now by manually
- // giving lists a second argument for their length. In the future we'll
- // probably want to refactor the `lift` function to internally know how
- // to consume arguments.
- let mut nth = 0;
- for param in func.params.iter() {
- self.emit(&Instruction::GetArg { nth });
- nth += 1;
- if let Type::List(_) = &**param.tref.type_() {
- self.emit(&Instruction::GetArg { nth });
- nth += 1;
- }
- self.lift(¶m.tref, false);
- }
-
- self.emit(&Instruction::CallInterface {
- module: module.as_str(),
- func,
- });
+ /// Assumes that the value for `tys` is already on the stack, and then
+ /// converts all of those values into their wasm types by lowering each
+ /// argument in-order.
+ fn lower_all(&mut self, tys: &[Param], mut nargs: Option) {
+ let operands = self
+ .stack
+ .drain(self.stack.len() - tys.len()..)
+ .collect::>();
+ for (operand, ty) in operands.into_iter().zip(tys) {
+ self.stack.push(operand);
+ self.lower(&ty.tref, nargs.as_mut());
+ }
+ }
- // Like above the current ABI only has at most one result, so lower it
- // here if necessary.
- if let Some(result) = func.results.get(0) {
- self.lower(&result.tref, Some(&mut nth));
+ /// Assumes `types.len()` values are on the stack and stores them all into
+ /// the return pointer of this function, specified in the last argument.
+ ///
+ /// This is only used with `Abi::Next`.
+ fn store_retptr(&mut self, types: &[WasmType], retptr_arg: usize) {
+ self.emit(&Instruction::GetArg { nth: retptr_arg });
+ let retptr = self.stack.pop().unwrap();
+ for (i, ty) in types.iter().enumerate().rev() {
+ self.stack.push(retptr.clone());
+ let offset = (i * 8) as i32;
+ match ty {
+ WasmType::I32 => self.emit(&Instruction::I32Store { offset }),
+ WasmType::I64 => self.emit(&Instruction::I64Store { offset }),
+ WasmType::F32 => self.emit(&Instruction::F32Store { offset }),
+ WasmType::F64 => self.emit(&Instruction::F64Store { offset }),
+ }
}
+ }
- let (_params, results) = func.wasm_signature();
- self.emit(&Instruction::Return { amt: results.len() });
+ fn witx(&mut self, instr: &WitxInstruction<'_>) {
+ self.emit(&Instruction::Witx { instr });
}
fn emit(&mut self, inst: &Instruction<'_>) {
@@ -674,8 +1254,25 @@ impl Generator<'_, B> {
self.stack.extend(self.results.drain(..));
}
+ fn push_block(&mut self) {
+ self.bindgen.push_block();
+ }
+
+ fn finish_block(&mut self, size: usize) {
+ self.operands.clear();
+ assert!(
+ size <= self.stack.len(),
+ "not enough operands on stack for finishing block",
+ );
+ self.operands
+ .extend(self.stack.drain((self.stack.len() - size)..));
+ self.bindgen.finish_block(&mut self.operands);
+ }
+
fn lower(&mut self, ty: &TypeRef, retptr: Option<&mut usize>) {
use Instruction::*;
+ use WitxInstruction::*;
+
match &**ty.type_() {
Type::Builtin(BuiltinType::S8) => self.emit(&I32FromS8),
Type::Builtin(BuiltinType::U8 { lang_c_char: true }) => self.emit(&I32FromChar8),
@@ -692,64 +1289,156 @@ impl Generator<'_, B> {
Type::Builtin(BuiltinType::S64) => self.emit(&I64FromS64),
Type::Builtin(BuiltinType::U64) => self.emit(&I64FromU64),
Type::Builtin(BuiltinType::Char) => self.emit(&I32FromChar),
- Type::Pointer(_) => self.emit(&I32FromPointer),
- Type::ConstPointer(_) => self.emit(&I32FromConstPointer),
- Type::Handle(_) => self.emit(&I32FromHandle {
- ty: match ty {
- TypeRef::Name(ty) => ty,
- _ => unreachable!(),
- },
- }),
- Type::Record(r) => {
- let ty = match ty {
- TypeRef::Name(ty) => ty,
- _ => unreachable!(),
+ Type::Builtin(BuiltinType::F32) => self.emit(&F32FromIf32),
+ Type::Builtin(BuiltinType::F64) => self.emit(&F64FromIf64),
+ Type::Pointer(_) => self.witx(&I32FromPointer),
+ Type::ConstPointer(_) => self.witx(&I32FromConstPointer),
+ Type::Handle(_) => {
+ let ty = ty.name().unwrap();
+ let borrowed = match self.mode {
+ // There's one of three possible situations we're in:
+ //
+ // * The handle is defined by the wasm module itself. This
+ // is the only actual possible scenario today due to how
+ // witx is defined. In this situation the handle is owned
+ // by the host and "proof of ownership" is being offered
+ // and there's no need to relinquish ownership.
+ //
+ // * The handle is defined by the host, and it's passing it
+ // to a wasm module. This should use an owned conversion.
+ // This isn't expressible in today's `*.witx` format.
+ //
+ // * The handle is defined by neither the host or the wasm
+ // mdoule. This means that the host is passing a
+ // capability from another wasm module into this one,
+ // meaning it's doing so by reference since the host is
+ // retaining access to its own
+ //
+ // Note, again, only the first bullet here is possible
+ // today, hence the hardcoded `true` value. We'll need to
+ // refactor `witx` to expose the other possibilities.
+ CallMode::DeclaredExport => true,
+
+ // Like above there's one of three possibilities. This means
+ // a wasm module is calling an imported function with a
+ // handle, and the handle is owned by either the wasm
+ // module, the imported function we're calling, or neither.
+ //
+ // An owned value is only used when the wasm module passes
+ // its own handle to the import. Otherwise a borrowed value
+ // is always used because the wasm module retains ownership
+ // of the original handle since it's a reference to
+ // something owned elsewhere.
+ //
+ // Today the only expressible case is that an imported
+ // function receives a handle that it itself defined, so
+ // it's always borrowed.
+ CallMode::DeclaredImport => true,
+
+ // This means that a return value is being lowered, which is
+ // never borrowed.
+ CallMode::DefinedExport | CallMode::DefinedImport => false,
};
- match r.bitflags_repr() {
- Some(IntRepr::U64) => self.emit(&I64FromBitflags { ty }),
- Some(_) => self.emit(&I32FromBitflags { ty }),
- None => self.emit(&AddrOf),
+ if borrowed {
+ self.emit(&I32FromBorrowedHandle { ty });
+ } else {
+ self.emit(&I32FromOwnedHandle { ty });
}
}
- Type::Variant(v) => {
- // Enum-like variants are simply lowered to their discriminant.
- if v.is_enum() {
- return self.emit(&EnumLower {
- ty: match ty {
- TypeRef::Name(n) => n,
- _ => unreachable!(),
- },
- });
+ Type::List(element) => match self.abi {
+ Abi::Preview1 => self.emit(&ListCanonLower {
+ element,
+ malloc: None,
+ }),
+ Abi::Next => {
+ // Lowering parameters calling a declared import means we
+ // don't need to pass ownership, but we pass ownership in
+ // all other cases.
+ let malloc = match self.mode {
+ CallMode::DeclaredImport => None,
+ _ => Some("witx_malloc"),
+ };
+ if element.type_().all_bits_valid() || is_char(element) {
+ self.emit(&ListCanonLower { element, malloc });
+ } else {
+ self.push_block();
+ self.emit(&IterElem);
+ self.emit(&IterBasePointer);
+ let addr = self.stack.pop().unwrap();
+ self.write_to_memory(element, addr, 0);
+ self.finish_block(0);
+ self.emit(&ListLower { element, malloc });
+ }
}
+ },
+ Type::Buffer(buffer) => {
+ self.translate_buffer(buffer);
+ match self.mode {
+ // When calling an imported function we're passing a raw view
+ // into memory, and the adapter will convert it into something
+ // else if necessary.
+ CallMode::DeclaredImport => self.emit(&BufferLowerPtrLen { buffer }),
- // If this variant is in the return position then it's special,
- // otherwise it's an argument and we just pass the address.
- let retptr = match retptr {
- Some(ptr) => ptr,
- None => return self.emit(&AddrOf),
- };
+ // When calling an exported function we're passing a handle to
+ // the caller's memory, and this part of the adapter is
+ // responsible for converting it into something that's a handle.
+ CallMode::DeclaredExport => self.emit(&BufferLowerHandle { buffer }),
+
+ // Buffers are only used in the parameter position, which means
+ // lowering a buffer should never happen in these contexts.
+ CallMode::DefinedImport | CallMode::DefinedExport => unreachable!(),
+ }
+ }
+ Type::Record(r) => {
+ if let Some(repr) = r.bitflags_repr() {
+ let name = ty.name().unwrap();
+ match repr {
+ IntRepr::U64 => return self.emit(&I64FromBitflags { ty: r, name }),
+ _ => return self.emit(&I32FromBitflags { ty: r, name }),
+ }
+ }
+ match self.abi {
+ Abi::Preview1 => self.witx(&AddrOf),
+ Abi::Next => {
+ self.emit(&RecordLower {
+ ty: r,
+ name: ty.name(),
+ });
+ let fields = self
+ .stack
+ .drain(self.stack.len() - r.members.len()..)
+ .collect::>();
+ for (member, field) in r.members.iter().zip(fields) {
+ self.stack.push(field);
+ self.lower(&member.tref, None);
+ }
+ }
+ }
+ }
- // For the return position we emit some blocks to lower the
- // ok/err payloads which means that in the ok branch we're
- // storing to out-params and in the err branch we're simply
- // lowering the error enum.
- //
- // Note that this is all very specific to the current WASI ABI.
+ // Variants in the return position of an import must be a Result in
+ // the preview1 ABI and they're a bit special about where all the
+ // pieces are.
+ Type::Variant(v)
+ if self.abi == Abi::Preview1
+ && self.mode == CallMode::DefinedImport
+ && !v.is_enum() =>
+ {
+ let retptr = retptr.unwrap();
let (ok, err) = v.as_expected().unwrap();
- self.bindgen.push_block();
+ self.push_block();
if let Some(ok) = ok {
self.emit(&VariantPayload);
let store = |me: &mut Self, ty: &TypeRef, n| {
me.emit(&GetArg { nth: *retptr + n });
- match ty {
- TypeRef::Name(ty) => me.emit(&Store { ty }),
- _ => unreachable!(),
- }
+ let addr = me.stack.pop().unwrap();
+ me.write_to_memory(ty, addr, 0);
};
match &**ok.type_() {
Type::Record(r) if r.is_tuple() => {
- self.emit(&TupleLower {
- amt: r.members.len(),
+ self.emit(&RecordLower {
+ ty: r,
+ name: ty.name(),
});
// Note that `rev()` is used here due to the order
// that tuples are pushed onto the stack and how we
@@ -761,63 +1450,140 @@ impl Generator<'_, B> {
_ => store(self, ok, 0),
}
};
- self.bindgen.finish_block(None);
+ self.emit(&I32Const { val: 0 });
+ self.finish_block(1);
- self.bindgen.push_block();
- let err_expr = if let Some(ty) = err {
+ self.push_block();
+ if let Some(ty) = err {
self.emit(&VariantPayload);
self.lower(ty, None);
- Some(self.stack.pop().unwrap())
- } else {
- None
- };
- self.bindgen.finish_block(err_expr);
+ }
+ self.finish_block(1);
- self.emit(&ResultLower { ok, err });
+ self.emit(&VariantLower {
+ ty: v,
+ name: ty.name(),
+ nresults: 1,
+ });
+ }
+
+ // Variant arguments in the Preview1 ABI are all passed by pointer
+ Type::Variant(v)
+ if self.abi == Abi::Preview1
+ && self.mode == CallMode::DeclaredImport
+ && !v.is_enum() =>
+ {
+ self.witx(&AddrOf)
+ }
+
+ Type::Variant(v) => {
+ let mut results = Vec::new();
+ let mut temp = Vec::new();
+ let mut casts = Vec::new();
+ push_wasm(self.mode, ty.type_(), &mut results);
+ for (i, case) in v.cases.iter().enumerate() {
+ self.push_block();
+ self.emit(&I32Const { val: i as i32 });
+ let mut pushed = 1;
+ if let Some(ty) = &case.tref {
+ // Using the payload of this block we lower the type to
+ // raw wasm values.
+ self.emit(&VariantPayload);
+ self.lower(ty, None);
+
+ // Determine the types of all the wasm values we just
+ // pushed, and record how many. If we pushed too few
+ // then we'll need to push some zeros after this.
+ temp.truncate(0);
+ push_wasm(self.mode, ty.type_(), &mut temp);
+ pushed += temp.len();
+
+ // For all the types pushed we may need to insert some
+ // bitcasts. This will go through and cast everything
+ // to the right type to ensure all blocks produce the
+ // same set of results.
+ casts.truncate(0);
+ for (actual, expected) in temp.iter().zip(&results[1..]) {
+ casts.push(cast(*actual, *expected));
+ }
+ if casts.iter().any(|c| *c != Bitcast::None) {
+ self.emit(&Bitcasts { casts: &casts });
+ }
+ }
+
+ // If we haven't pushed enough items in this block to match
+ // what other variants are pushing then we need to push
+ // some zeros.
+ if pushed < results.len() {
+ self.emit(&ConstZero {
+ tys: &results[pushed..],
+ });
+ }
+ self.finish_block(results.len());
+ }
+ self.emit(&VariantLower {
+ ty: v,
+ nresults: results.len(),
+ name: ty.name(),
+ });
}
- Type::Builtin(BuiltinType::F32) => self.emit(&F32FromIf32),
- Type::Builtin(BuiltinType::F64) => self.emit(&F64FromIf64),
- Type::List(_) => self.emit(&ListPointerLength),
}
}
- fn prep_return_pointer(&mut self, ty: &Type) {
- // Return pointers are only needed for `Result`...
- let variant = match ty {
- Type::Variant(v) => v,
- _ => return,
- };
- // ... and only if `T` is actually present in `Result`
- let ok = match &variant.cases[0].tref {
- Some(t) => t,
- None => return,
- };
+ fn prep_return_pointer(&mut self, sig: &WasmSignature, results: &[Param]) {
+ match self.abi {
+ Abi::Preview1 => {
+ assert!(results.len() < 2);
+ let ty = match results.get(0) {
+ Some(ty) => ty.tref.type_(),
+ None => return,
+ };
+ // Return pointers are only needed for `Result`...
+ let variant = match &**ty {
+ Type::Variant(v) => v,
+ _ => return,
+ };
+ // ... and only if `T` is actually present in `Result`
+ let ok = match &variant.cases[0].tref {
+ Some(t) => t,
+ None => return,
+ };
- // Tuples have each individual item in a separate return pointer while
- // all other types go through a singular return pointer.
- let mut n = 0;
- let mut prep = |ty: &TypeRef| {
- match ty {
- TypeRef::Name(ty) => self.bindgen.allocate_space(n, ty),
- _ => unreachable!(),
+ // Tuples have each individual item in a separate return pointer while
+ // all other types go through a singular return pointer.
+ let mut prep = |ty: &TypeRef| {
+ let ptr = self.bindgen.allocate_typed_space(ty.name().unwrap());
+ self.return_pointers.push(ptr.clone());
+ self.stack.push(ptr);
+ };
+ match &**ok.type_() {
+ Type::Record(r) if r.is_tuple() => {
+ for member in r.members.iter() {
+ prep(&member.tref);
+ }
+ }
+ _ => prep(ok),
+ }
}
- self.emit(&Instruction::ReturnPointerGet { n });
- n += 1;
- };
- match &**ok.type_() {
- Type::Record(r) if r.is_tuple() => {
- for member in r.members.iter() {
- prep(&member.tref);
+ // If a return pointer was automatically injected into this function
+ // then we need to allocate a proper amount of space for it and then
+ // add it to the stack to get passed to the callee.
+ Abi::Next => {
+ if let Some(results) = &sig.retptr {
+ let ptr = self.bindgen.allocate_i64_array(results.len());
+ self.return_pointers.push(ptr.clone());
+ self.stack.push(ptr.clone());
}
}
- _ => prep(ok),
}
}
// Note that in general everything in this function is the opposite of the
// `lower` function above. This is intentional and should be kept this way!
- fn lift(&mut self, ty: &TypeRef, is_return: bool) {
+ fn lift(&mut self, ty: &TypeRef) {
use Instruction::*;
+ use WitxInstruction::*;
+
match &**ty.type_() {
Type::Builtin(BuiltinType::S8) => self.emit(&S8FromI32),
Type::Builtin(BuiltinType::U8 { lang_c_char: true }) => self.emit(&Char8FromI32),
@@ -836,84 +1602,569 @@ impl Generator<'_, B> {
Type::Builtin(BuiltinType::Char) => self.emit(&CharFromI32),
Type::Builtin(BuiltinType::F32) => self.emit(&If32FromF32),
Type::Builtin(BuiltinType::F64) => self.emit(&If64FromF64),
- Type::Pointer(ty) => self.emit(&PointerFromI32 { ty }),
- Type::ConstPointer(ty) => self.emit(&ConstPointerFromI32 { ty }),
- Type::Handle(_) => self.emit(&HandleFromI32 {
- ty: match ty {
- TypeRef::Name(ty) => ty,
- _ => unreachable!(),
- },
- }),
- Type::Variant(v) => {
- if v.is_enum() {
- return self.emit(&EnumLift {
- ty: match ty {
- TypeRef::Name(n) => n,
- _ => unreachable!(),
- },
- });
- } else if !is_return {
- return self.emit(&Load {
- ty: match ty {
- TypeRef::Name(n) => n,
- _ => unreachable!(),
- },
- });
+ Type::Pointer(ty) => self.witx(&PointerFromI32 { ty }),
+ Type::ConstPointer(ty) => self.witx(&ConstPointerFromI32 { ty }),
+ Type::Handle(_) => {
+ let ty = ty.name().unwrap();
+ // For more information on these values see the comments in
+ // `lower` above.
+ let borrowed = match self.mode {
+ CallMode::DefinedExport | CallMode::DefinedImport => true,
+ CallMode::DeclaredExport | CallMode::DeclaredImport => false,
+ };
+ if borrowed {
+ self.emit(&HandleBorrowedFromI32 { ty });
+ } else {
+ self.emit(&HandleOwnedFromI32 { ty });
+ }
+ }
+ Type::List(element) => match self.abi {
+ Abi::Preview1 => self.emit(&ListCanonLift {
+ element,
+ free: None,
+ }),
+ Abi::Next => {
+ // Lifting the arguments of a defined import means that, if
+ // possible, the caller still retains ownership and we don't
+ // free anything.
+ let free = match self.mode {
+ CallMode::DefinedImport => None,
+ _ => Some("witx_free"),
+ };
+ if element.type_().all_bits_valid() || is_char(element) {
+ self.emit(&ListCanonLift { element, free });
+ } else {
+ self.push_block();
+ self.emit(&IterBasePointer);
+ let addr = self.stack.pop().unwrap();
+ self.read_from_memory(element, addr, 0);
+ self.finish_block(1);
+ self.emit(&ListLift { element, free });
+ }
+ }
+ },
+ Type::Buffer(buffer) => {
+ self.translate_buffer(buffer);
+ match self.mode {
+ // When calling a defined imported function then we're coming
+ // from a pointer/length, and the embedding context will figure
+ // out what to do with that pointer/length.
+ CallMode::DefinedImport => self.emit(&BufferLiftPtrLen { buffer }),
+
+ // When calling an exported function we're given a handle to the
+ // buffer, which is then interpreted in the calling context.
+ CallMode::DefinedExport => self.emit(&BufferLiftHandle { buffer }),
+
+ // Buffers are only used in the parameter position, which means
+ // lifting a buffer should never happen in these contexts.
+ CallMode::DeclaredImport | CallMode::DeclaredExport => unreachable!(),
+ }
+ }
+ Type::Record(r) => {
+ if let Some(repr) = r.bitflags_repr() {
+ let name = ty.name().unwrap();
+ match repr {
+ IntRepr::U64 => return self.emit(&BitflagsFromI64 { repr, ty: r, name }),
+ _ => return self.emit(&BitflagsFromI32 { repr, ty: r, name }),
+ }
}
+ match self.abi {
+ Abi::Preview1 => {
+ let addr = self.stack.pop().unwrap();
+ self.read_from_memory(ty, addr, 0);
+ }
+ Abi::Next => {
+ let mut temp = Vec::new();
+ push_wasm(self.mode, ty.type_(), &mut temp);
+ let mut args = self
+ .stack
+ .drain(self.stack.len() - temp.len()..)
+ .collect::>();
+ for member in r.members.iter() {
+ temp.truncate(0);
+ push_wasm(self.mode, member.tref.type_(), &mut temp);
+ self.stack.extend(args.drain(..temp.len()));
+ self.lift(&member.tref);
+ }
+ self.emit(&RecordLift {
+ ty: r,
+ name: ty.name(),
+ });
+ }
+ }
+ }
+ // Variants in the return position of an import must be a Result in
+ // the preview1 ABI and they're a bit special about where all the
+ // pieces are.
+ Type::Variant(v)
+ if self.abi == Abi::Preview1
+ && self.mode == CallMode::DeclaredImport
+ && !v.is_enum() =>
+ {
let (ok, err) = v.as_expected().unwrap();
- self.bindgen.push_block();
- let ok_expr = if let Some(ok) = ok {
+ self.push_block();
+ if let Some(ok) = ok {
let mut n = 0;
let mut load = |ty: &TypeRef| {
- self.emit(&ReturnPointerGet { n });
+ self.read_from_memory(ty, self.return_pointers[n].clone(), 0);
n += 1;
- match ty {
- TypeRef::Name(ty) => self.emit(&Load { ty }),
- _ => unreachable!(),
- }
};
match &**ok.type_() {
Type::Record(r) if r.is_tuple() => {
for member in r.members.iter() {
load(&member.tref);
}
- self.emit(&TupleLift {
- amt: r.members.len(),
+ self.emit(&RecordLift {
+ ty: r,
+ name: ok.name(),
});
}
_ => load(ok),
}
- Some(self.stack.pop().unwrap())
- } else {
- None
- };
- self.bindgen.finish_block(ok_expr);
+ }
+ self.finish_block(ok.is_some() as usize);
- self.bindgen.push_block();
- let err_expr = if let Some(ty) = err {
- self.emit(&ReuseReturn);
- self.lift(ty, false);
- Some(self.stack.pop().unwrap())
- } else {
- None
- };
- self.bindgen.finish_block(err_expr);
+ self.push_block();
+ if let Some(ty) = err {
+ self.witx(&ReuseReturn);
+ self.lift(ty);
+ }
+ self.finish_block(err.is_some() as usize);
- self.emit(&ResultLift);
+ self.emit(&VariantLift {
+ ty: v,
+ name: ty.name(),
+ });
}
- Type::Record(r) => {
- let ty = match ty {
- TypeRef::Name(ty) => ty,
- _ => unreachable!(),
+
+ // Variant arguments in the Preview1 ABI are all passed by pointer,
+ // so we read them here.
+ Type::Variant(v)
+ if self.abi == Abi::Preview1
+ && self.mode == CallMode::DefinedImport
+ && !v.is_enum() =>
+ {
+ let addr = self.stack.pop().unwrap();
+ self.read_from_memory(ty, addr, 0)
+ }
+
+ Type::Variant(v) => {
+ let mut params = Vec::new();
+ let mut temp = Vec::new();
+ let mut casts = Vec::new();
+ push_wasm(self.mode, ty.type_(), &mut params);
+ let block_inputs = self
+ .stack
+ .drain(self.stack.len() + 1 - params.len()..)
+ .collect::>();
+ for case in v.cases.iter() {
+ self.push_block();
+ if let Some(ty) = &case.tref {
+ // Push only the values we need for this variant onto
+ // the stack.
+ temp.truncate(0);
+ push_wasm(self.mode, ty.type_(), &mut temp);
+ self.stack
+ .extend(block_inputs[..temp.len()].iter().cloned());
+
+ // Cast all the types we have on the stack to the actual
+ // types needed for this variant, if necessary.
+ casts.truncate(0);
+ for (actual, expected) in temp.iter().zip(¶ms[1..]) {
+ casts.push(cast(*expected, *actual));
+ }
+ if casts.iter().any(|c| *c != Bitcast::None) {
+ self.emit(&Bitcasts { casts: &casts });
+ }
+
+ // Then recursively lift this variant's payload.
+ self.lift(ty);
+ }
+ self.finish_block(case.tref.is_some() as usize);
+ }
+ self.emit(&VariantLift {
+ ty: v,
+ name: ty.name(),
+ });
+ }
+ }
+ }
+
+ fn write_to_memory(&mut self, ty: &TypeRef, addr: B::Operand, offset: i32) {
+ use Instruction::*;
+
+ match &**ty.type_() {
+ // Builtin types need different flavors of storage instructions
+ // depending on the size of the value written.
+ Type::Builtin(b) => {
+ self.lower(ty, None);
+ self.stack.push(addr);
+ match b {
+ BuiltinType::S8 | BuiltinType::U8 { .. } => self.emit(&I32Store8 { offset }),
+ BuiltinType::S16 | BuiltinType::U16 => self.emit(&I32Store16 { offset }),
+ BuiltinType::S32 | BuiltinType::U32 { .. } | BuiltinType::Char => {
+ self.emit(&I32Store { offset })
+ }
+ BuiltinType::S64 | BuiltinType::U64 => self.emit(&I64Store { offset }),
+ BuiltinType::F32 => self.emit(&F32Store { offset }),
+ BuiltinType::F64 => self.emit(&F64Store { offset }),
+ }
+ }
+
+ // Lowering all these types produces an `i32` which we can easily
+ // store into memory.
+ Type::Pointer(_) | Type::ConstPointer(_) | Type::Handle(_) => {
+ self.lower(ty, None);
+ self.stack.push(addr);
+ self.emit(&I32Store { offset })
+ }
+
+ // After lowering the list there's two i32 values on the stack
+ // which we write into memory, writing the pointer into the low address
+ // and the length into the high address.
+ Type::List(_) => {
+ self.lower(ty, None);
+ self.stack.push(addr.clone());
+ self.emit(&I32Store { offset: offset + 4 });
+ self.stack.push(addr);
+ self.emit(&I32Store { offset });
+ }
+
+ // Lower the buffer to its raw values, and then write the values
+ // into memory, which may be more than one value depending on our
+ // call mode.
+ Type::Buffer(_) => {
+ self.lower(ty, None);
+ if let CallMode::DeclaredImport = self.mode {
+ self.stack.push(addr.clone());
+ self.emit(&I32Store { offset: offset + 4 });
+ }
+ self.stack.push(addr);
+ self.emit(&I32Store { offset });
+ }
+
+ Type::Record(r) => match r.bitflags_repr() {
+ // Bitflags just have their appropriate width written into
+ // memory.
+ Some(repr) => {
+ let name = ty.name().unwrap();
+ self.emit(&match repr {
+ IntRepr::U64 => I64FromBitflags { ty: r, name },
+ _ => I32FromBitflags { ty: r, name },
+ });
+ self.stack.push(addr);
+ self.store_intrepr(offset, repr);
+ }
+
+ // Decompose the record into its components and then write all
+ // the components into memory one-by-one.
+ None => {
+ self.emit(&RecordLower {
+ ty: r,
+ name: ty.name(),
+ });
+ let fields = self
+ .stack
+ .drain(self.stack.len() - r.members.len()..)
+ .collect::>();
+ for (layout, field) in r.member_layout(self.mode.export()).iter().zip(fields) {
+ self.stack.push(field);
+ self.write_to_memory(
+ &layout.member.tref,
+ addr.clone(),
+ offset + (layout.offset as i32),
+ );
+ }
+ }
+ },
+
+ // Each case will get its own block, and the first item in each
+ // case is writing the discriminant. After that if we have a
+ // payload we write the payload after the discriminant, aligned up
+ // to the type's alignment.
+ Type::Variant(v) => {
+ let payload_offset = offset + (v.payload_offset(self.mode.export()) as i32);
+ for (i, case) in v.cases.iter().enumerate() {
+ self.push_block();
+ self.emit(&I32Const { val: i as i32 });
+ self.stack.push(addr.clone());
+ self.store_intrepr(offset, v.tag_repr);
+ if let Some(ty) = &case.tref {
+ self.emit(&VariantPayload);
+ self.write_to_memory(ty, addr.clone(), payload_offset);
+ }
+ self.finish_block(0);
+ }
+ self.emit(&VariantLower {
+ ty: v,
+ nresults: 0,
+ name: ty.name(),
+ });
+ }
+ }
+ }
+
+ fn read_from_memory(&mut self, ty: &TypeRef, addr: B::Operand, offset: i32) {
+ use Instruction::*;
+
+ match &**ty.type_() {
+ // Builtin types need different flavors of load instructions
+ // depending on the size of the value written, but then they're all
+ // lifted the same way.
+ Type::Builtin(b) => {
+ self.stack.push(addr);
+ match b {
+ BuiltinType::S8 => self.emit(&I32Load8S { offset }),
+ BuiltinType::U8 { .. } => self.emit(&I32Load8U { offset }),
+ BuiltinType::S16 => self.emit(&I32Load16S { offset }),
+ BuiltinType::U16 => self.emit(&I32Load16U { offset }),
+ BuiltinType::S32 | BuiltinType::U32 { .. } | BuiltinType::Char => {
+ self.emit(&I32Load { offset })
+ }
+ BuiltinType::S64 | BuiltinType::U64 => self.emit(&I64Load { offset }),
+ BuiltinType::F32 => self.emit(&F32Load { offset }),
+ BuiltinType::F64 => self.emit(&F64Load { offset }),
+ }
+ self.lift(ty);
+ }
+
+ // These types are all easily lifted from an `i32`
+ Type::Pointer(_) | Type::ConstPointer(_) | Type::Handle(_) => {
+ self.stack.push(addr);
+ self.emit(&I32Load { offset });
+ self.lift(ty);
+ }
+
+ // Read the pointer/len and then perform the standard lifting
+ // proceses.
+ Type::List(_) => {
+ self.stack.push(addr.clone());
+ self.emit(&I32Load { offset });
+ self.stack.push(addr);
+ self.emit(&I32Load { offset: offset + 4 });
+ self.lift(ty);
+ }
+
+ // Read the requisite number of values from memory and then lift as
+ // appropriate.
+ Type::Buffer(_) => {
+ self.stack.push(addr.clone());
+ self.emit(&I32Load { offset });
+ if let CallMode::DefinedImport = self.mode {
+ self.stack.push(addr);
+ self.emit(&I32Load { offset: offset + 4 });
+ }
+ self.lift(ty);
+ }
+
+ Type::Record(r) => match r.bitflags_repr() {
+ // Bitflags just have their appropriate size read from
+ // memory.
+ Some(repr) => {
+ let name = ty.name().unwrap();
+ self.stack.push(addr);
+ self.load_intrepr(offset, repr);
+ self.emit(&match repr {
+ IntRepr::U64 => BitflagsFromI64 { repr, ty: r, name },
+ _ => BitflagsFromI32 { repr, ty: r, name },
+ });
+ }
+ // Read and lift each field individually, adjusting the offset
+ // as we go along, then aggregate all the fields into the
+ // record.
+ None => {
+ for layout in r.member_layout(self.mode.export()) {
+ self.read_from_memory(
+ &layout.member.tref,
+ addr.clone(),
+ offset + (layout.offset as i32),
+ );
+ }
+ self.emit(&RecordLift {
+ ty: r,
+ name: ty.name(),
+ });
+ }
+ },
+
+ // Each case will get its own block, and we'll dispatch to the
+ // right block based on the `i32.load` we initially perform. Each
+ // individual block is pretty simple and just reads the payload type
+ // from the corresponding offset if one is available.
+ Type::Variant(v) => {
+ self.stack.push(addr.clone());
+ self.load_intrepr(offset, v.tag_repr);
+ let payload_offset = offset + (v.payload_offset(self.mode.export()) as i32);
+ for case in v.cases.iter() {
+ self.push_block();
+ if let Some(ty) = &case.tref {
+ self.read_from_memory(ty, addr.clone(), payload_offset);
+ }
+ self.finish_block(case.tref.is_some() as usize);
+ }
+ self.emit(&VariantLift {
+ ty: v,
+ name: ty.name(),
+ });
+ }
+ }
+ }
+
+ fn load_intrepr(&mut self, offset: i32, repr: IntRepr) {
+ self.emit(&match repr {
+ IntRepr::U64 => Instruction::I64Load { offset },
+ IntRepr::U32 => Instruction::I32Load { offset },
+ IntRepr::U16 => Instruction::I32Load16U { offset },
+ IntRepr::U8 => Instruction::I32Load8U { offset },
+ });
+ }
+
+ fn store_intrepr(&mut self, offset: i32, repr: IntRepr) {
+ self.emit(&match repr {
+ IntRepr::U64 => Instruction::I64Store { offset },
+ IntRepr::U32 => Instruction::I32Store { offset },
+ IntRepr::U16 => Instruction::I32Store16 { offset },
+ IntRepr::U8 => Instruction::I32Store8 { offset },
+ });
+ }
+
+ fn translate_buffer(&mut self, buffer: &Buffer) {
+ let do_write = match self.mode {
+ // For declared items, input/output is defined in the context of
+ // what the callee will do. The callee will read input buffers,
+ // meaning we write to them, and write to ouptut buffers, meaning
+ // we'll read from them.
+ CallMode::DeclaredImport | CallMode::DeclaredExport => !buffer.out,
+
+ // Defined item mirror declared imports because buffers are
+ // defined from the caller's perspective, so we don't invert the
+ // `out` setting like above.
+ CallMode::DefinedImport | CallMode::DefinedExport => buffer.out,
+ };
+ self.emit(&Instruction::IterBasePointer);
+ let addr = self.stack.pop().unwrap();
+ if do_write {
+ self.push_block();
+ self.emit(&Instruction::VariantPayload);
+ self.write_to_memory(&buffer.tref, addr, 0);
+ self.finish_block(0);
+ } else {
+ self.push_block();
+ self.read_from_memory(&buffer.tref, addr, 0);
+ self.finish_block(1);
+ }
+ }
+}
+
+fn push_wasm(mode: CallMode, ty: &Type, result: &mut Vec) {
+ match ty {
+ Type::Builtin(BuiltinType::S8)
+ | Type::Builtin(BuiltinType::U8 { .. })
+ | Type::Builtin(BuiltinType::S16)
+ | Type::Builtin(BuiltinType::U16)
+ | Type::Builtin(BuiltinType::S32)
+ | Type::Builtin(BuiltinType::U32 { .. })
+ | Type::Builtin(BuiltinType::Char)
+ | Type::Pointer(_)
+ | Type::ConstPointer(_)
+ | Type::Handle(_) => result.push(WasmType::I32),
+
+ Type::Builtin(BuiltinType::U64) | Type::Builtin(BuiltinType::S64) => {
+ result.push(WasmType::I64)
+ }
+ Type::Builtin(BuiltinType::F32) => result.push(WasmType::F32),
+ Type::Builtin(BuiltinType::F64) => result.push(WasmType::F64),
+
+ Type::Record(r) => match r.bitflags_repr() {
+ Some(repr) => result.push(repr.into()),
+ None => {
+ for member in r.members.iter() {
+ push_wasm(mode, member.tref.type_(), result);
+ }
+ }
+ },
+
+ Type::List(_) => {
+ result.push(WasmType::I32);
+ result.push(WasmType::I32);
+ }
+
+ Type::Buffer(_) => {
+ result.push(WasmType::I32);
+ match mode {
+ CallMode::DefinedExport | CallMode::DeclaredExport => {}
+ CallMode::DefinedImport | CallMode::DeclaredImport => result.push(WasmType::I32),
+ }
+ }
+
+ Type::Variant(v) => {
+ result.push(v.tag_repr.into());
+ let start = result.len();
+ let mut temp = Vec::new();
+
+ // Push each case's type onto a temporary vector, and then merge
+ // that vector into our final list starting at `start`. Note
+ // that this requires some degree of "unification" so we can
+ // handle things like `Result` where that turns into
+ // `[i32 i32]` where the second `i32` might be the `f32`
+ // bitcasted.
+ for case in v.cases.iter() {
+ let ty = match &case.tref {
+ Some(ty) => ty,
+ None => continue,
};
- match r.bitflags_repr() {
- Some(IntRepr::U64) => self.emit(&BitflagsFromI64 { ty }),
- Some(_) => self.emit(&BitflagsFromI32 { ty }),
- None => self.emit(&Load { ty }),
+ push_wasm(mode, ty.type_(), &mut temp);
+
+ for (i, ty) in temp.drain(..).enumerate() {
+ match result.get_mut(start + i) {
+ Some(prev) => *prev = unify(*prev, ty),
+ None => result.push(ty),
+ }
}
}
- Type::List(ty) => self.emit(&ListFromPointerLength { ty }),
}
}
}
+
+fn unify(a: WasmType, b: WasmType) -> WasmType {
+ use WasmType::*;
+
+ match (a, b) {
+ (I64, _) | (_, I64) | (I32, F64) | (F64, I32) => I64,
+
+ (I32, I32) | (I32, F32) | (F32, I32) => I32,
+
+ (F32, F32) => F32,
+ (F64, F64) | (F32, F64) | (F64, F32) => F64,
+ }
+}
+
+fn cast(from: WasmType, to: WasmType) -> Bitcast {
+ use WasmType::*;
+
+ match (from, to) {
+ (I32, I32) | (I64, I64) | (F32, F32) | (F64, F64) => Bitcast::None,
+
+ (I32, I64) => Bitcast::I32ToI64,
+ (F32, F64) => Bitcast::F32ToF64,
+ (F32, I32) => Bitcast::F32ToI32,
+ (F64, I64) => Bitcast::F64ToI64,
+
+ (I64, I32) => Bitcast::I64ToI32,
+ (F64, F32) => Bitcast::F64ToF32,
+ (I32, F32) => Bitcast::I32ToF32,
+ (I64, F64) => Bitcast::I64ToF64,
+
+ (F32, I64) => Bitcast::F32ToI64,
+ (I64, F32) => Bitcast::I64ToF32,
+ (F64, I32) | (I32, F64) => unreachable!(),
+ }
+}
+
+fn is_char(ty: &TypeRef) -> bool {
+ match &**ty.type_() {
+ Type::Builtin(BuiltinType::Char) => true,
+ _ => false,
+ }
+}
diff --git a/tools/witx/src/ast.rs b/tools/witx/src/ast.rs
index b36bb8a2..08b285a9 100644
--- a/tools/witx/src/ast.rs
+++ b/tools/witx/src/ast.rs
@@ -171,6 +171,13 @@ impl TypeRef {
}
}
+ pub fn name(&self) -> Option<&NamedType> {
+ match self {
+ TypeRef::Name(n) => Some(n),
+ TypeRef::Value(_) => None,
+ }
+ }
+
pub fn named(&self) -> bool {
match self {
TypeRef::Name(_) => true,
@@ -222,6 +229,8 @@ pub enum Type {
/// A `witx`-specific type representing a raw const pointer into linear
/// memory
ConstPointer(TypeRef),
+ /// A buffer type representing a window in memory
+ Buffer(Buffer),
/// A builtin base-case type.
Builtin(BuiltinType),
}
@@ -237,10 +246,41 @@ impl Type {
List(_) => "list",
Pointer(_) => "pointer",
ConstPointer(_) => "constpointer",
+ Buffer(_) => "buffer",
Builtin(_) => "builtin",
}
}
+ /// Returns whether the in-memory representation of this type will always be
+ /// valid regardless of the value of all the bits in memory.
+ ///
+ /// This is only true for numerical types, pointers, and records of these
+ /// values. This is used for canonical lifting/lowering of lists.
+ pub fn all_bits_valid(&self) -> bool {
+ match self {
+ Type::Record(r) => r.members.iter().all(|t| t.tref.type_().all_bits_valid()),
+
+ Type::Builtin(BuiltinType::Char)
+ | Type::Variant(_)
+ | Type::Handle(_)
+ | Type::Buffer(_)
+ | Type::List(_) => false,
+
+ Type::Builtin(BuiltinType::U8 { .. })
+ | Type::Builtin(BuiltinType::S8)
+ | Type::Builtin(BuiltinType::U16)
+ | Type::Builtin(BuiltinType::S16)
+ | Type::Builtin(BuiltinType::U32 { .. })
+ | Type::Builtin(BuiltinType::S32)
+ | Type::Builtin(BuiltinType::U64)
+ | Type::Builtin(BuiltinType::S64)
+ | Type::Builtin(BuiltinType::F32)
+ | Type::Builtin(BuiltinType::F64)
+ | Type::Pointer(_)
+ | Type::ConstPointer(_) => true,
+ }
+ }
+
pub fn type_equal(&self, other: &Type) -> bool {
match self {
Type::Record(a) => match other {
@@ -271,6 +311,10 @@ impl Type {
Type::Builtin(b) => a == b,
_ => false,
},
+ Type::Buffer(a) => match other {
+ Type::Buffer(b) => a.type_equal(b),
+ _ => false,
+ },
}
}
}
@@ -417,6 +461,43 @@ impl RecordMember {
}
}
+impl RecordKind {
+ pub fn infer(members: &[RecordMember]) -> RecordKind {
+ if members.len() == 0 {
+ return RecordKind::Other;
+ }
+
+ // Structs-of-bools are classified to get represented as bitflags.
+ if members.iter().all(|t| is_bool(&t.tref)) {
+ match members.len() {
+ n if n <= 8 => return RecordKind::Bitflags(IntRepr::U8),
+ n if n <= 16 => return RecordKind::Bitflags(IntRepr::U16),
+ n if n <= 32 => return RecordKind::Bitflags(IntRepr::U32),
+ n if n <= 64 => return RecordKind::Bitflags(IntRepr::U64),
+ _ => {}
+ }
+ }
+
+ // Members with consecutive integer names get represented as tuples.
+ if members
+ .iter()
+ .enumerate()
+ .all(|(i, m)| m.name.as_str().parse().ok() == Some(i))
+ {
+ return RecordKind::Tuple;
+ }
+
+ return RecordKind::Other;
+
+ fn is_bool(t: &TypeRef) -> bool {
+ match &**t.type_() {
+ Type::Variant(v) => v.is_bool(),
+ _ => false,
+ }
+ }
+ }
+}
+
/// A type which represents how values can be one of a set of possible cases.
///
/// This type maps to an `enum` in languages like Rust, but doesn't have an
@@ -438,6 +519,39 @@ pub struct Variant {
}
impl Variant {
+ pub fn infer_repr(cases: usize) -> IntRepr {
+ match cases {
+ n if n < u8::max_value() as usize => IntRepr::U8,
+ n if n < u16::max_value() as usize => IntRepr::U16,
+ n if n < u32::max_value() as usize => IntRepr::U32,
+ n if n < u64::max_value() as usize => IntRepr::U64,
+ _ => panic!("too many cases to fit in a repr"),
+ }
+ }
+
+ /// If this variant looks like an `option` shorthand, return the type
+ /// associated with option.
+ ///
+ /// Only matches variants fo the form:
+ ///
+ /// ```text
+ /// (variant
+ /// (case "none")
+ /// (case "some" ty))
+ /// ```
+ pub fn as_option(&self) -> Option<&TypeRef> {
+ if self.cases.len() != 2 {
+ return None;
+ }
+ if self.cases[0].name != "none" || self.cases[0].tref.is_some() {
+ return None;
+ }
+ if self.cases[1].name != "some" {
+ return None;
+ }
+ self.cases[1].tref.as_ref()
+ }
+
/// If this variant looks like an `expected` shorthand, return the ok/err
/// types associated with this result.
///
@@ -578,3 +692,19 @@ pub struct Constant {
pub value: u64,
pub docs: String,
}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Buffer {
+ /// Whether or not this is an `out` buffer (`true`) or an `in` buffer
+ /// (`false`)
+ pub out: bool,
+
+ /// The type of items this buffer contains
+ pub tref: TypeRef,
+}
+
+impl Buffer {
+ pub fn type_equal(&self, other: &Buffer) -> bool {
+ self.out == other.out && self.tref.type_equal(&other.tref)
+ }
+}
diff --git a/tools/witx/src/docs/ast.rs b/tools/witx/src/docs/ast.rs
index 4bca0b02..9a39563d 100644
--- a/tools/witx/src/docs/ast.rs
+++ b/tools/witx/src/docs/ast.rs
@@ -38,8 +38,8 @@ pub(super) fn modules(node: MdNodeRef, all_modules: &[&Module]) {
format!(
"{}\nSize: {}\n\nAlignment: {}\n",
&d.docs,
- &d.mem_size(),
- &d.mem_align()
+ &d.mem_size(false),
+ &d.mem_align(false)
)
.as_str(),
));
@@ -108,6 +108,7 @@ pub(super) fn modules(node: MdNodeRef, all_modules: &[&Module]) {
}
}
Type::List(t) | Type::ConstPointer(t) | Type::Pointer(t) => add_types(types, t),
+ Type::Buffer(b) => add_types(types, &b.tref),
Type::Handle(_) | Type::Builtin(_) => {}
}
}
@@ -143,6 +144,7 @@ impl ToMarkdown for Type {
Self::List(_) => {}
Self::Pointer(_) => {}
Self::ConstPointer(_) => {}
+ Self::Buffer(_) => {}
Self::Builtin(_) => {}
}
}
@@ -153,7 +155,7 @@ impl ToMarkdown for RecordDatatype {
let heading = heading_from_node(&node, 1);
node.new_child(MdSection::new(heading, "Record members"));
- for member_layout in &self.member_layout() {
+ for member_layout in &self.member_layout(false) {
let member = member_layout.member;
let offset = member_layout.offset;
let name = member.name.as_str();
@@ -162,16 +164,16 @@ impl ToMarkdown for RecordDatatype {
} else {
name.to_owned()
};
- let (div, offset_desc) = if self.bitflags_repr().is_some() {
- (4, "Bit")
+ let offset_desc = if self.bitflags_repr().is_some() {
+ "Bit"
} else {
- (1, "Offset")
+ "Offset"
};
let n = node.new_child(MdNamedType::new(
MdHeading::new_bullet(),
id.as_str(),
name,
- format!("{}\n{}: {}\n", &member.docs, offset_desc, offset / div).as_str(),
+ format!("{}\n{}: {}\n", &member.docs, offset_desc, offset).as_str(),
));
member.tref.generate(n.clone());
}
@@ -187,7 +189,7 @@ impl ToMarkdown for Variant {
let heading = heading_from_node(&node, 1);
node.new_child(MdSection::new(heading, "Variant Layout"));
- let whole = self.mem_size_align();
+ let whole = self.mem_size_align(false);
node.new_child(MdSection::new(
MdHeading::new_bullet(),
format!("size: {}", whole.size),
@@ -197,7 +199,7 @@ impl ToMarkdown for Variant {
format!("align: {}", whole.align),
));
- let tag = self.tag_repr.mem_size_align();
+ let tag = self.tag_repr.mem_size_align(false);
node.new_child(MdSection::new(
MdHeading::new_bullet(),
format!("tag_size: {}", tag.size),
@@ -361,6 +363,7 @@ impl TypeRef {
format!("Variant")
}
}
+ Type::Buffer(_) => format!("buffer"),
},
}
}
diff --git a/tools/witx/src/layout.rs b/tools/witx/src/layout.rs
index 6812f01a..43d772a9 100644
--- a/tools/witx/src/layout.rs
+++ b/tools/witx/src/layout.rs
@@ -9,7 +9,7 @@ pub struct SizeAlign {
impl SizeAlign {
fn zero() -> SizeAlign {
- SizeAlign { size: 0, align: 0 }
+ SizeAlign { size: 0, align: 1 }
}
fn append_field(&mut self, other: &SizeAlign) {
self.align = self.align.max(other.align);
@@ -19,23 +19,23 @@ impl SizeAlign {
}
pub trait Layout {
- fn mem_size_align(&self) -> SizeAlign;
- fn mem_size(&self) -> usize {
- self.mem_size_align().size
+ fn mem_size_align(&self, export: bool) -> SizeAlign;
+ fn mem_size(&self, export: bool) -> usize {
+ self.mem_size_align(export).size
}
- fn mem_align(&self) -> usize {
- self.mem_size_align().align
+ fn mem_align(&self, export: bool) -> usize {
+ self.mem_size_align(export).align
}
}
impl TypeRef {
- fn layout(&self, cache: &mut HashMap) -> SizeAlign {
+ fn layout(&self, export: bool, cache: &mut HashMap) -> SizeAlign {
if let Some(hit) = cache.get(self) {
return *hit;
}
let layout = match &self {
- TypeRef::Name(nt) => nt.layout(cache),
- TypeRef::Value(v) => v.layout(cache),
+ TypeRef::Name(nt) => nt.layout(export, cache),
+ TypeRef::Value(v) => v.layout(export, cache),
};
cache.insert(self.clone(), layout);
layout
@@ -43,50 +43,54 @@ impl TypeRef {
}
impl Layout for TypeRef {
- fn mem_size_align(&self) -> SizeAlign {
+ fn mem_size_align(&self, export: bool) -> SizeAlign {
let mut cache = HashMap::new();
- self.layout(&mut cache)
+ self.layout(export, &mut cache)
}
}
impl NamedType {
- fn layout(&self, cache: &mut HashMap) -> SizeAlign {
- self.tref.layout(cache)
+ fn layout(&self, export: bool, cache: &mut HashMap) -> SizeAlign {
+ self.tref.layout(export, cache)
}
}
impl Layout for NamedType {
- fn mem_size_align(&self) -> SizeAlign {
+ fn mem_size_align(&self, export: bool) -> SizeAlign {
let mut cache = HashMap::new();
- self.layout(&mut cache)
+ self.layout(export, &mut cache)
}
}
impl Type {
- fn layout(&self, cache: &mut HashMap) -> SizeAlign {
+ fn layout(&self, export: bool, cache: &mut HashMap) -> SizeAlign {
match &self {
Type::Record(s) => match s.bitflags_repr() {
- Some(repr) => repr.mem_size_align(),
- None => s.layout(cache),
+ Some(repr) => repr.mem_size_align(export),
+ None => s.layout(export, cache),
},
- Type::Variant(s) => s.mem_size_align(),
- Type::Handle(h) => h.mem_size_align(),
+ Type::Variant(s) => s.mem_size_align(export),
+ Type::Handle(h) => h.mem_size_align(export),
Type::List { .. } => SizeAlign { size: 8, align: 4 }, // Pointer and Length
- Type::Pointer { .. } | Type::ConstPointer { .. } => BuiltinType::S32.mem_size_align(),
- Type::Builtin(b) => b.mem_size_align(),
+ Type::Pointer { .. } | Type::ConstPointer { .. } => {
+ BuiltinType::S32.mem_size_align(export)
+ }
+ Type::Buffer(_) if export => SizeAlign { size: 4, align: 4 },
+ Type::Buffer(_) => SizeAlign { size: 8, align: 4 },
+ Type::Builtin(b) => b.mem_size_align(export),
}
}
}
impl Layout for Type {
- fn mem_size_align(&self) -> SizeAlign {
+ fn mem_size_align(&self, export: bool) -> SizeAlign {
let mut cache = HashMap::new();
- self.layout(&mut cache)
+ self.layout(export, &mut cache)
}
}
impl Layout for IntRepr {
- fn mem_size_align(&self) -> SizeAlign {
- self.to_builtin().mem_size_align()
+ fn mem_size_align(&self, export: bool) -> SizeAlign {
+ self.to_builtin().mem_size_align(export)
}
}
@@ -96,18 +100,19 @@ pub struct RecordMemberLayout<'a> {
}
impl RecordDatatype {
- pub fn member_layout(&self) -> Vec {
- self.member_layout_(&mut HashMap::new()).1
+ pub fn member_layout(&self, export: bool) -> Vec {
+ self.member_layout_(export, &mut HashMap::new()).1
}
fn member_layout_(
&self,
+ export: bool,
cache: &mut HashMap,
) -> (SizeAlign, Vec) {
let mut members = Vec::new();
let mut sa = SizeAlign::zero();
for m in self.members.iter() {
- let member = m.tref.layout(cache);
+ let member = m.tref.layout(export, cache);
sa.append_field(&member);
members.push(RecordMemberLayout {
member: m,
@@ -118,30 +123,30 @@ impl RecordDatatype {
(sa, members)
}
- fn layout(&self, cache: &mut HashMap) -> SizeAlign {
- self.member_layout_(cache).0
+ fn layout(&self, export: bool, cache: &mut HashMap) -> SizeAlign {
+ self.member_layout_(export, cache).0
}
}
impl Layout for RecordDatatype {
- fn mem_size_align(&self) -> SizeAlign {
+ fn mem_size_align(&self, export: bool) -> SizeAlign {
match self.bitflags_repr() {
- Some(repr) => repr.mem_size_align(),
+ Some(repr) => repr.mem_size_align(export),
None => {
let mut cache = HashMap::new();
- self.layout(&mut cache)
+ self.layout(export, &mut cache)
}
}
}
}
impl Layout for Variant {
- fn mem_size_align(&self) -> SizeAlign {
- let mut max = SizeAlign { size: 0, align: 0 };
+ fn mem_size_align(&self, export: bool) -> SizeAlign {
+ let mut max = SizeAlign::zero();
for case in self.cases.iter() {
- let mut size = self.tag_repr.mem_size_align();
+ let mut size = self.tag_repr.mem_size_align(export);
if let Some(payload) = &case.tref {
- size.append_field(&payload.mem_size_align());
+ size.append_field(&payload.mem_size_align(export));
}
size.size = align_to(size.size, size.align);
max.size = max.size.max(size.size);
@@ -152,11 +157,11 @@ impl Layout for Variant {
}
impl Variant {
- pub fn payload_offset(&self) -> usize {
- let mut offset = self.tag_repr.mem_size_align().size;
+ pub fn payload_offset(&self, export: bool) -> usize {
+ let mut offset = self.tag_repr.mem_size_align(export).size;
for case in self.cases.iter() {
if let Some(payload) = &case.tref {
- offset = offset.max(align_to(offset, payload.mem_size_align().align));
+ offset = offset.max(align_to(offset, payload.mem_size_align(export).align));
}
}
offset
@@ -198,13 +203,13 @@ mod test {
}
impl Layout for HandleDatatype {
- fn mem_size_align(&self) -> SizeAlign {
- BuiltinType::S32.mem_size_align()
+ fn mem_size_align(&self, export: bool) -> SizeAlign {
+ BuiltinType::S32.mem_size_align(export)
}
}
impl Layout for BuiltinType {
- fn mem_size_align(&self) -> SizeAlign {
+ fn mem_size_align(&self, _export: bool) -> SizeAlign {
match self {
BuiltinType::U8 { .. } | BuiltinType::S8 => SizeAlign { size: 1, align: 1 },
BuiltinType::U16 | BuiltinType::S16 => SizeAlign { size: 2, align: 2 },
diff --git a/tools/witx/src/parser.rs b/tools/witx/src/parser.rs
index 6633b35f..a388f51c 100644
--- a/tools/witx/src/parser.rs
+++ b/tools/witx/src/parser.rs
@@ -1,4 +1,4 @@
-use crate::BuiltinType;
+use crate::{Abi, BuiltinType};
use wast::parser::{Parse, Parser, Peek, Result};
///! Parser turns s-expressions into unvalidated syntax constructs.
@@ -56,6 +56,9 @@ mod kw {
wast::custom_keyword!(usize);
wast::custom_keyword!(variant);
wast::custom_keyword!(bool_ = "bool");
+ wast::custom_keyword!(option);
+ wast::custom_keyword!(in_buffer = "in-buffer");
+ wast::custom_keyword!(out_buffer = "out-buffer");
}
mod annotation {
@@ -224,32 +227,30 @@ impl<'a> Parse<'a> for TopLevelModule<'a> {
let mut functions = Vec::new();
let mut module_name = None;
- let mut comments = parser.parse()?;
- loop {
- if parser.peek2::()
- || 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())?,
- });
+ if parser.peek2::() {
+ decls.push(Documented {
+ comments: parser.parse()?,
+ item: parser.parens(|p| p.parse())?,
+ });
+ } else if parser.peek2::()
+ || parser.peek2::()
+ || parser.peek2::()
+ {
+ decls.push(Documented {
+ comments: parser.parse()?,
+ item: parser.parens(|p| p.parse())?,
+ });
+ } else {
+ functions.push(Documented {
+ comments: parser.parse()?,
+ item: p.parens(|p| p.parse())?,
+ });
+ }
}
Ok(())
})?;
@@ -385,6 +386,7 @@ pub enum TypedefSyntax<'a> {
Enum(EnumSyntax<'a>),
Tuple(TupleSyntax<'a>),
Expected(ExpectedSyntax<'a>),
+ Option(OptionSyntax<'a>),
Flags(FlagsSyntax<'a>),
Record(RecordSyntax<'a>),
Union(UnionSyntax<'a>),
@@ -393,6 +395,7 @@ pub enum TypedefSyntax<'a> {
List(Box>),
Pointer(Box>),
ConstPointer(Box>),
+ Buffer(BufferSyntax<'a>),
Builtin(BuiltinType),
Ident(wast::Id<'a>),
String,
@@ -421,6 +424,8 @@ impl<'a> Parse<'a> for TypedefSyntax<'a> {
Ok(TypedefSyntax::Tuple(parser.parse()?))
} else if l.peek::() {
Ok(TypedefSyntax::Expected(parser.parse()?))
+ } else if l.peek::() {
+ Ok(TypedefSyntax::Option(parser.parse()?))
} else if l.peek::() {
Ok(TypedefSyntax::Flags(parser.parse()?))
} else if l.peek::() {
@@ -431,6 +436,8 @@ impl<'a> Parse<'a> for TypedefSyntax<'a> {
Ok(TypedefSyntax::Variant(parser.parse()?))
} else if l.peek::() {
Ok(TypedefSyntax::Handle(parser.parse()?))
+ } else if l.peek::() || l.peek::() {
+ Ok(TypedefSyntax::Buffer(parser.parse()?))
} else if l.peek::() {
parser.parse::()?;
Ok(TypedefSyntax::List(Box::new(parser.parse()?)))
@@ -535,6 +542,19 @@ impl<'a> Parse<'a> for ExpectedSyntax<'a> {
}
}
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct OptionSyntax<'a> {
+ pub ty: Box>,
+}
+
+impl<'a> Parse<'a> for OptionSyntax<'a> {
+ fn parse(parser: Parser<'a>) -> Result {
+ parser.parse::()?;
+ let ty = Box::new(parser.parse()?);
+ Ok(OptionSyntax { ty })
+ }
+}
+
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConstSyntax<'a> {
pub ty: wast::Id<'a>,
@@ -601,7 +621,6 @@ impl<'a> Parse<'a> for RecordSyntax<'a> {
fn parse(parser: Parser<'a>) -> Result {
parser.parse::()?;
let mut fields = Vec::new();
- fields.push(parser.parse()?);
while !parser.is_empty() {
fields.push(parser.parse()?);
}
@@ -700,6 +719,26 @@ impl<'a> Parse<'a> for CaseSyntax<'a> {
}
}
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct BufferSyntax<'a> {
+ pub out: bool,
+ pub ty: Box>,
+}
+
+impl<'a> Parse<'a> for BufferSyntax<'a> {
+ fn parse(parser: Parser<'a>) -> Result {
+ let out = if parser.peek::() {
+ parser.parse::()?;
+ false
+ } else {
+ parser.parse::()?;
+ true
+ };
+ let ty = Box::new(parser.parse()?);
+ Ok(BufferSyntax { out, ty })
+ }
+}
+
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HandleSyntax<'a> {
pub resource: wast::Id<'a>,
@@ -715,6 +754,7 @@ impl<'a> Parse<'a> for HandleSyntax<'a> {
#[derive(Debug, Clone)]
pub struct FunctionSyntax<'a> {
+ pub abi: Abi,
pub export: &'a str,
pub export_loc: wast::Span,
pub params: Vec>>,
@@ -722,16 +762,14 @@ pub struct FunctionSyntax<'a> {
pub noreturn: bool,
}
-impl<'a> Parse<'a> for FunctionSyntax<'a> {
- fn parse(parser: Parser<'a>) -> Result {
- parser.parse::()?;
- parser.parse::()?;
-
- let (export_loc, export) = parser.parens(|p| {
- p.parse::()?;
- Ok((p.cur_span(), p.parse()?))
- })?;
-
+impl<'a> FunctionSyntax<'a> {
+ fn parse_func_params(
+ parser: Parser<'a>,
+ ) -> Result<(
+ Vec>>,
+ Vec>>,
+ bool,
+ )> {
let mut params = Vec::new();
let mut results = Vec::new();
let mut noreturn = false;
@@ -756,14 +794,49 @@ impl<'a> Parse<'a> for FunctionSyntax<'a> {
}
}
}
+ Ok((params, results, noreturn))
+ }
+}
- Ok(FunctionSyntax {
- export,
- export_loc,
- params,
- results,
- noreturn,
- })
+impl<'a> Parse<'a> for FunctionSyntax<'a> {
+ fn parse(parser: Parser<'a>) -> Result {
+ if parser.parse::