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::>()?.is_some() { + let export_loc = parser.cur_span(); + let export = parser.parse()?; + + let (params, results, noreturn) = parser.parens(|p| { + p.parse::()?; + FunctionSyntax::parse_func_params(parser) + })?; + + Ok(FunctionSyntax { + abi: Abi::Next, + export, + export_loc, + params, + results, + noreturn, + }) + } else { + parser.parse::()?; + parser.parse::()?; + + let (export_loc, export) = parser.parens(|p| { + p.parse::()?; + Ok((p.cur_span(), p.parse()?)) + })?; + + let (params, results, noreturn) = FunctionSyntax::parse_func_params(parser)?; + + Ok(FunctionSyntax { + abi: Abi::Preview1, + export, + export_loc, + params, + results, + noreturn, + }) + } } } diff --git a/tools/witx/src/toplevel.rs b/tools/witx/src/toplevel.rs index 57d50755..56cd69f1 100644 --- a/tools/witx/src/toplevel.rs +++ b/tools/witx/src/toplevel.rs @@ -36,7 +36,6 @@ fn _parse( } let input = io.fgets(&canon_path)?; - let mut validator = ModuleValidation::new(&input, path); let adjust_err = |mut error: wast::Error| { error.set_path(&path); @@ -46,6 +45,25 @@ fn _parse( let buf = wast::parser::ParseBuffer::new(&input).map_err(adjust_err)?; let doc = wast::parser::parse::(&buf).map_err(adjust_err)?; + let file_name = path.file_stem().unwrap().to_str().unwrap(); + let name = match doc.module_name { + Some(name) => name.name(), + None => file_name, + }; + let mut validator = ModuleValidation::new(&input, name, path); + + if let Some(name) = doc.module_name { + if file_name != "-" && file_name != name.name() { + let location = validator.location(name.span()); + return Err(ValidationError::ModuleNameMismatch { + location, + module_name: name.name().to_owned(), + file_name: file_name.to_owned(), + } + .into()); + } + } + let mut submodules = HashMap::new(); for t in doc.decls { match t.item { @@ -92,29 +110,32 @@ mod test { #[test] fn empty() { - parse_witx_with("/a", &MockFs::new(&[("/a", ";; empty")])).expect("parse"); + parse_witx_with("/a.witx", &MockFs::new(&[("/a.witx", ";; empty")])).expect("parse"); } #[test] fn one_use() { - parse_witx_with( - "/a", - &MockFs::new(&[("/a", "(use $x from $b)"), ("/b.witx", "(typename $x u8)")]), - ) - .unwrap(); + let fs = &MockFs::new(&[ + ("/a.witx", "(module $a (use $x from $b))"), + ("/b.witx", "(module $b (typename $x u8))"), + ]); + parse_witx_with("/b.witx", fs).unwrap(); + parse_witx_with("/a.witx", fs).unwrap(); } #[test] fn multi_use() { - let doc = parse_witx_with( - "/a", - &MockFs::new(&[ - ("/a", "(use $b_float $c_int from $b)"), - ("/b.witx", "(use $c_int from $c)\n(typename $b_float f64)"), - ("/c.witx", "(typename $c_int u32)"), - ]), - ) - .expect("parse"); + let fs = &MockFs::new(&[ + ("/a.witx", "(module $a (use $b_float $c_int from $b))"), + ( + "/b.witx", + "(module $b (use $c_int from $c) (typename $b_float f64))", + ), + ("/c.witx", "(module $c (typename $c_int u32))"), + ]); + parse_witx_with("/c.witx", fs).expect("parse"); + parse_witx_with("/b.witx", fs).expect("parse"); + let doc = parse_witx_with("/a.witx", fs).expect("parse"); let b_float = doc.typename(&Id::new("b_float")).unwrap(); assert_eq!(**b_float.type_(), Type::Builtin(BuiltinType::F64)); @@ -130,22 +151,25 @@ mod test { #[test] fn diamond_dependency() { - let doc = parse_witx_with( - "/a", - &MockFs::new(&[ - ("/a", "(use $b_char from $b)\n(use $c_char from $c)"), - ( - "/b.witx", - "(use $d_char from $d) (typename $b_char $d_char)", - ), - ( - "/c.witx", - "(use $d_char from $d) (typename $c_char $d_char)", - ), - ("/d.witx", "(typename $d_char u8)"), - ]), - ) - .expect("parse"); + let fs = &MockFs::new(&[ + ( + "/a.witx", + "(module $a\n(use $b_char from $b)\n(use $c_char from $c)\n)", + ), + ( + "/b.witx", + "(module $b (use $d_char from $d) (typename $b_char $d_char))", + ), + ( + "/c.witx", + "(module $c (use $d_char from $d) (typename $c_char $d_char))", + ), + ("/d.witx", "(module $d (typename $d_char u8))"), + ]); + parse_witx_with("/d.witx", fs).expect("parse"); + parse_witx_with("/c.witx", fs).expect("parse"); + parse_witx_with("/b.witx", fs).expect("parse"); + let doc = parse_witx_with("/a.witx", fs).expect("parse"); let b_char = doc.typename(&Id::new("b_char")).unwrap(); assert_eq!( @@ -157,9 +181,12 @@ mod test { #[test] fn use_not_found() { - match parse_witx_with("/a", &MockFs::new(&[("/a", "(use $x from $b)")])) - .err() - .unwrap() + match parse_witx_with( + "/a.witx", + &MockFs::new(&[("/a.witx", "(module $a (use $x from $b))")]), + ) + .err() + .unwrap() { WitxError::Io(path, _error) => assert_eq!(path, PathBuf::from("/b.witx")), e => panic!("wrong error: {:?}", e), diff --git a/tools/witx/src/validate.rs b/tools/witx/src/validate.rs index cc7b67a5..de2518d8 100644 --- a/tools/witx/src/validate.rs +++ b/tools/witx/src/validate.rs @@ -1,11 +1,11 @@ use crate::{ io::{Filesystem, WitxIo}, parser::{ - CommentSyntax, DeclSyntax, EnumSyntax, ExpectedSyntax, FlagsSyntax, FunctionSyntax, - HandleSyntax, RecordSyntax, TupleSyntax, TypedefSyntax, UnionSyntax, UseSyntax, UsedNames, - VariantSyntax, + BufferSyntax, CommentSyntax, DeclSyntax, EnumSyntax, ExpectedSyntax, FlagsSyntax, + FunctionSyntax, HandleSyntax, OptionSyntax, RecordSyntax, TupleSyntax, TypedefSyntax, + UnionSyntax, UseSyntax, UsedNames, VariantSyntax, }, - Abi, BuiltinType, Case, Constant, Function, HandleDatatype, Id, IntRepr, Location, Module, + Buffer, BuiltinType, Case, Constant, Function, HandleDatatype, Id, IntRepr, Location, Module, ModuleId, NamedType, Param, RecordDatatype, RecordKind, RecordMember, Resource, ResourceId, Type, TypeRef, Variant, }; @@ -58,6 +58,14 @@ pub enum ValidationError { reason: String, location: Location, }, + #[error("Variant has zero cases")] + ZeroCaseVariant { location: Location }, + #[error("Module name `{module_name}` doesn't match file name `{file_name}`")] + ModuleNameMismatch { + location: Location, + module_name: String, + file_name: String, + }, } impl ValidationError { @@ -71,9 +79,11 @@ impl ValidationError { | Abi { location, .. } | AnonymousRecord { location, .. } | UnionSizeMismatch { location, .. } + | ZeroCaseVariant { location } | InvalidUnionField { location, .. } | InvalidUnionTag { location, .. } - | CyclicModule { location } => { + | CyclicModule { location } + | ModuleNameMismatch { location, .. } => { format!("{}\n{}", location.highlight_source_with(witxio), &self) } NameAlreadyExists { @@ -141,8 +151,8 @@ pub struct ModuleValidation<'a> { } impl<'a> ModuleValidation<'a> { - pub fn new(text: &'a str, path: &'a Path) -> Self { - let name = Id::new(path.file_stem().unwrap().to_str().unwrap()); + pub fn new(text: &'a str, name: &'a str, path: &'a Path) -> Self { + let name = Id::new(name); let module_id = ModuleId(Rc::new(path.to_path_buf())); Self { module: Module::new(name, module_id), @@ -151,7 +161,7 @@ impl<'a> ModuleValidation<'a> { func_ns: IdentValidation::new(), constant_ns: HashMap::new(), bool_ty: TypeRef::Value(Rc::new(Type::Variant(Variant { - tag_repr: IntRepr::U32, + tag_repr: IntRepr::U8, cases: vec![ Case { name: Id::new("false"), @@ -345,14 +355,15 @@ impl<'a> ModuleValidation<'a> { }) .collect::, _>>()?; let noreturn = syntax.noreturn; - let abi = Abi::Preview1; - abi.validate(¶ms, &results) + syntax + .abi + .validate(¶ms, &results) .map_err(|reason| ValidationError::Abi { reason, location: self.location(syntax.export_loc), })?; self.module.push_func(Rc::new(Function { - abi, + abi: syntax.abi, name: name.clone(), params, results, @@ -392,6 +403,9 @@ impl<'a> ModuleValidation<'a> { TypedefSyntax::Expected(syntax) => { Type::Variant(self.validate_expected(&syntax, span)?) } + TypedefSyntax::Option(syntax) => { + Type::Variant(self.validate_option(&syntax, span)?) + } TypedefSyntax::Flags(syntax) => Type::Record(self.validate_flags(&syntax, span)?), TypedefSyntax::Record(syntax) => Type::Record(self.validate_record(&syntax, span)?), TypedefSyntax::Union(syntax) => Type::Variant(self.validate_union(&syntax, span)?), @@ -408,6 +422,7 @@ impl<'a> ModuleValidation<'a> { TypedefSyntax::ConstPointer(syntax) => { Type::ConstPointer(self.validate_datatype(syntax, false, span)?) } + TypedefSyntax::Buffer(syntax) => Type::Buffer(self.validate_buffer(syntax, span)?), TypedefSyntax::Builtin(builtin) => Type::Builtin(*builtin), TypedefSyntax::String => { Type::List(TypeRef::Value(Rc::new(Type::Builtin(BuiltinType::Char)))) @@ -426,7 +441,7 @@ impl<'a> ModuleValidation<'a> { let mut enum_scope = IdentValidation::new(); let tag_repr = match &syntax.repr { Some(repr) => self.validate_int_repr(repr, span)?, - None => IntRepr::U32, + None => Variant::infer_repr(syntax.members.len()), }; let cases = syntax .members @@ -483,7 +498,7 @@ impl<'a> ModuleValidation<'a> { None => None, }; Ok(Variant { - tag_repr: IntRepr::U32, + tag_repr: IntRepr::U8, cases: vec![ Case { name: Id::new("ok"), @@ -499,14 +514,37 @@ impl<'a> ModuleValidation<'a> { }) } + fn validate_option( + &self, + syntax: &OptionSyntax, + span: wast::Span, + ) -> Result { + let tref = self.validate_datatype(&syntax.ty, false, span)?; + Ok(Variant { + tag_repr: IntRepr::U8, + cases: vec![ + Case { + name: Id::new("none"), + tref: None, + docs: String::new(), + }, + Case { + name: Id::new("some"), + tref: Some(tref), + docs: String::new(), + }, + ], + }) + } + fn validate_flags( &self, syntax: &FlagsSyntax, span: wast::Span, ) -> Result { let repr = match syntax.repr { - Some(ty) => self.validate_int_repr(&ty, span)?, - None => IntRepr::U32, + Some(ty) => Some(RecordKind::Bitflags(self.validate_int_repr(&ty, span)?)), + None => None, }; let mut flags_scope = IdentValidation::new(); let mut members = Vec::new(); @@ -520,7 +558,7 @@ impl<'a> ModuleValidation<'a> { }); } Ok(RecordDatatype { - kind: RecordKind::Bitflags(repr), + kind: repr.unwrap_or_else(|| RecordKind::infer(&members)), members, }) } @@ -544,7 +582,7 @@ impl<'a> ModuleValidation<'a> { .collect::, _>>()?; Ok(RecordDatatype { - kind: RecordKind::Other, + kind: RecordKind::infer(&members), members, }) } @@ -581,7 +619,10 @@ impl<'a> ModuleValidation<'a> { }) }) .collect::, _>>()?; - Ok(Variant { tag_repr, cases }) + Ok(Variant { + tag_repr: tag_repr.unwrap_or_else(|| Variant::infer_repr(cases.len())), + cases, + }) } fn validate_variant( @@ -589,6 +630,11 @@ impl<'a> ModuleValidation<'a> { syntax: &VariantSyntax, span: wast::Span, ) -> Result { + if syntax.cases.len() == 0 { + return Err(ValidationError::ZeroCaseVariant { + location: self.location(span), + }); + } let (tag_repr, names) = self.union_tag_repr(&syntax.tag, span)?; if let Some(names) = &names { @@ -643,17 +689,20 @@ impl<'a> ModuleValidation<'a> { cases.sort_by_key(|c| name_pos[&&c.name]); } - Ok(Variant { tag_repr, cases }) + Ok(Variant { + tag_repr: tag_repr.unwrap_or_else(|| Variant::infer_repr(cases.len())), + cases, + }) } fn union_tag_repr( &self, tag: &Option>>, span: wast::Span, - ) -> Result<(IntRepr, Option>), ValidationError> { + ) -> Result<(Option, Option>), ValidationError> { let ty = match tag { Some(tag) => self.validate_datatype(tag, false, span)?, - None => return Ok((IntRepr::U32, None)), + None => return Ok((None, None)), }; match &**ty.type_() { Type::Variant(e) => { @@ -667,12 +716,12 @@ impl<'a> ModuleValidation<'a> { } names.push(c.name.clone()); } - return Ok((e.tag_repr, Some(names))); + return Ok((Some(e.tag_repr), Some(names))); } - Type::Builtin(BuiltinType::U8 { .. }) => return Ok((IntRepr::U8, None)), - Type::Builtin(BuiltinType::U16) => return Ok((IntRepr::U16, None)), - Type::Builtin(BuiltinType::U32 { .. }) => return Ok((IntRepr::U32, None)), - Type::Builtin(BuiltinType::U64) => return Ok((IntRepr::U64, None)), + Type::Builtin(BuiltinType::U8 { .. }) => return Ok((Some(IntRepr::U8), None)), + Type::Builtin(BuiltinType::U16) => return Ok((Some(IntRepr::U16), None)), + Type::Builtin(BuiltinType::U32 { .. }) => return Ok((Some(IntRepr::U32), None)), + Type::Builtin(BuiltinType::U64) => return Ok((Some(IntRepr::U64), None)), _ => {} } @@ -697,6 +746,17 @@ impl<'a> ModuleValidation<'a> { }) } + fn validate_buffer( + &self, + syntax: &BufferSyntax, + span: wast::Span, + ) -> Result { + Ok(Buffer { + out: syntax.out, + tref: self.validate_datatype(&syntax.ty, false, span)?, + }) + } + fn validate_int_repr( &self, type_: &BuiltinType, diff --git a/tools/witx/tests/witxt.rs b/tools/witx/tests/witxt.rs index c74e3467..b6d5aa33 100644 --- a/tools/witx/tests/witxt.rs +++ b/tools/witx/tests/witxt.rs @@ -13,7 +13,7 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::str; use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; -use wast::parser::{self, Parse, ParseBuffer, Parser}; +use wast::parser::{self, Cursor, Parse, ParseBuffer, Parser, Peek}; use witx::{Instruction, WasmType}; fn main() { @@ -149,14 +149,12 @@ impl WitxtRunner<'_> { self.bump_ntests(); match directive { WitxtDirective::Witx(witx) => { - let doc = witx.module(contents, test)?; + let doc = witx.module(contents, test, &mut self.modules)?; self.assert_md(&doc)?; - if let Some(name) = witx.id { - self.modules.insert(name.name().to_string(), doc); - } + self.modules.insert(doc.name().as_str().to_string(), doc); } WitxtDirective::AssertInvalid { witx, message, .. } => { - let err = match witx.module(contents, test) { + let err = match witx.module(contents, test, &mut self.modules) { Ok(_) => bail!("witx was valid when it shouldn't be"), Err(e) => format!("{:?}", anyhow::Error::from(e)), }; @@ -187,32 +185,36 @@ impl WitxtRunner<'_> { } WitxtDirective::AssertAbi { witx, - wasm, - interface, - wasm_signature: (wasm_params, wasm_results), + wasm_signatures, + abis, .. } => { - let m = witx.module(contents, test)?; - let func = m.funcs().next().ok_or_else(|| anyhow!("no funcs"))?; - - let (params, results) = func.wasm_signature(); - if params != wasm_params { - bail!("expected params {:?}, found {:?}", wasm_params, params); - } - if results != wasm_results { - bail!("expected results {:?}, found {:?}", wasm_results, results); + let module = witx.module(contents, test, &mut self.modules)?; + let func = module.funcs().next().ok_or_else(|| anyhow!("no funcs"))?; + for (mode, wasm_params, wasm_results) in wasm_signatures.iter() { + let sig = func.wasm_signature(*mode); + if sig.params != *wasm_params { + bail!("expected params {:?}, found {:?}", wasm_params, sig.params); + } + if sig.results != *wasm_results { + bail!( + "expected results {:?}, found {:?}", + wasm_results, + sig.results + ); + } } - let mut check = AbiBindgen { - abi: wasm.instrs.iter(), - err: None, - contents, - }; - func.call_wasm(m.name(), &mut check); - check.check()?; - check.abi = interface.instrs.iter(); - func.call_interface(m.name(), &mut check); - check.check()?; + for abi in abis { + let mut check = AbiBindgen { + abi: abi.instrs.iter(), + err: None, + contents, + next_rp: 0, + }; + func.call(module.name(), abi.mode, &mut check); + check.check()?; + } } } Ok(()) @@ -233,6 +235,7 @@ struct AbiBindgen<'a> { abi: std::slice::Iter<'a, (wast::Span, &'a str)>, err: Option, contents: &'a str, + next_rp: usize, } impl AbiBindgen<'_> { @@ -252,11 +255,11 @@ impl AbiBindgen<'_> { Some((span, s)) => { let (line, col) = span.linecol_in(self.contents); self.err = Some(anyhow!( - "line {}:{} - expected `{}` found `{}`", + "line {}:{} - expected `{}` but adapter produced `{}`", line + 1, col + 1, - name, s, + name, )); } None => { @@ -267,20 +270,47 @@ impl AbiBindgen<'_> { } } } + + fn assert_op(&mut self, op: &Operand) { + match op { + Operand::Op(n) => self.assert(&format!("arg{}", n)), + Operand::Ret(n) => self.assert(&format!("ret{}", n)), + Operand::Field(n) => self.assert(&format!("field.{}", n)), + Operand::Rp(n) => self.assert(&format!("rp{}", n)), + Operand::InstResult => {} + } + } + + fn assert_mem(&mut self, inst: &str, offset: i32) { + self.assert(inst); + self.assert(&format!("offset={}", offset)); + } +} + +#[derive(Clone, Debug)] +enum Operand { + Op(usize), + Ret(usize), + Field(String), + Rp(usize), + InstResult, } impl witx::Bindgen for AbiBindgen<'_> { - type Operand = (); + type Operand = Operand; fn emit( &mut self, inst: &Instruction<'_>, - _operands: &mut Vec, + operands: &mut Vec, results: &mut Vec, ) { use witx::Instruction::*; + use witx::WitxInstruction::*; match inst { - GetArg { nth } => self.assert(&format!("get-arg{}", nth)), - AddrOf => self.assert("addr-of"), + GetArg { nth } => { + self.assert(&format!("get-arg{}", nth)); + results.push(Operand::Op(*nth)); + } I32FromChar => self.assert("i32.from_char"), I64FromU64 => self.assert("i64.from_u64"), I64FromS64 => self.assert("i64.from_s64"), @@ -292,15 +322,25 @@ impl witx::Bindgen for AbiBindgen<'_> { I32FromU8 => self.assert("i32.from_u8"), I32FromS8 => self.assert("i32.from_s8"), I32FromChar8 => self.assert("i32.from_char8"), - I32FromPointer => self.assert("i32.from_pointer"), - I32FromConstPointer => self.assert("i32.from_const_pointer"), - I32FromHandle { .. } => self.assert("i32.from_handle"), - ListPointerLength => self.assert("list.pointer_length"), - ListFromPointerLength { .. } => self.assert("list.from_pointer_length"), + I32FromBorrowedHandle { .. } => self.assert("i32.from_borrowed_handle"), + I32FromOwnedHandle { .. } => self.assert("i32.from_owned_handle"), F32FromIf32 => self.assert("f32.from_if32"), F64FromIf64 => self.assert("f64.from_if64"), - CallWasm { .. } => self.assert("call.wasm"), - CallInterface { .. } => self.assert("call.interface"), + CallWasm { + results: call_results, + .. + } => { + self.assert("call.wasm"); + for i in 0..call_results.len() { + results.push(Operand::Ret(i)); + } + } + CallInterface { func, .. } => { + self.assert("call.interface"); + for i in 0..func.results.len() { + results.push(Operand::Ret(i)); + } + } S8FromI32 => self.assert("s8.from_i32"), U8FromI32 => self.assert("u8.from_i32"), S16FromI32 => self.assert("s16.from_i32"), @@ -314,40 +354,118 @@ impl witx::Bindgen for AbiBindgen<'_> { UsizeFromI32 => self.assert("usize.from_i32"), If32FromF32 => self.assert("if32.from_f32"), If64FromF64 => self.assert("if64.from_f64"), - HandleFromI32 { .. } => self.assert("handle.from_i32"), - PointerFromI32 { .. } => self.assert("pointer.from_i32"), - ConstPointerFromI32 { .. } => self.assert("const_pointer.from_i32"), - ReturnPointerGet { n } => self.assert(&format!("return_pointer.get{}", n)), - ResultLift => self.assert("result.lift"), - ResultLower { .. } => self.assert("result.lower"), - EnumLift { .. } => self.assert("enum.lift"), - EnumLower { .. } => self.assert("enum.lower"), - TupleLift { .. } => self.assert("tuple.lift"), - TupleLower { .. } => self.assert("tuple.lower"), - ReuseReturn => self.assert("reuse_return"), - Load { .. } => self.assert("load"), - Store { .. } => self.assert("store"), + HandleBorrowedFromI32 { .. } => self.assert("handle.borrowed_from_i32"), + HandleOwnedFromI32 { .. } => self.assert("handle.owned_from_i32"), Return { .. } => self.assert("return"), VariantPayload => self.assert("variant-payload"), + RecordLift { .. } => self.assert("record-lift"), + RecordLower { ty, .. } => { + self.assert("record-lower"); + for member in ty.members.iter() { + results.push(Operand::Field(member.name.as_str().to_string())); + } + } + + I32Const { val } => { + self.assert("i32.const"); + self.assert(&val.to_string()); + } + VariantLower { .. } => self.assert("variant-lower"), + VariantLift { .. } => self.assert("variant-lift"), + Bitcasts { casts } => { + for (cast, operand) in casts.iter().zip(operands.drain(..)) { + match cast { + witx::Bitcast::None => self.assert("nocast"), + witx::Bitcast::I32ToI64 => self.assert("i32-to-i64"), + witx::Bitcast::F32ToF64 => self.assert("f32-to-f64"), + witx::Bitcast::F32ToI32 => self.assert("f32-to-i32"), + witx::Bitcast::F64ToI64 => self.assert("f64-to-i64"), + witx::Bitcast::I64ToI32 => self.assert("i64-to-i32"), + witx::Bitcast::F64ToF32 => self.assert("f64-to-f32"), + witx::Bitcast::I32ToF32 => self.assert("i32-to-f32"), + witx::Bitcast::I64ToF64 => self.assert("i64-to-f64"), + witx::Bitcast::F32ToI64 => self.assert("f32-to-i64"), + witx::Bitcast::I64ToF32 => self.assert("i64-to-f32"), + } + self.assert_op(&operand); + } + } + ConstZero { tys } => { + for ty in tys.iter() { + match ty { + witx::WasmType::I32 => self.assert("i32.const"), + witx::WasmType::I64 => self.assert("i64.const"), + witx::WasmType::F32 => self.assert("f32.const"), + witx::WasmType::F64 => self.assert("f64.const"), + } + self.assert("0"); + } + } + I32Load { offset } => self.assert_mem("i32.load", *offset), + I32Load8U { offset } => self.assert_mem("i32.load8u", *offset), + I32Load8S { offset } => self.assert_mem("i32.load8s", *offset), + I32Load16U { offset } => self.assert_mem("i32.load16u", *offset), + I32Load16S { offset } => self.assert_mem("i32.load16s", *offset), + I64Load { offset } => self.assert_mem("i64.load", *offset), + F32Load { offset } => self.assert_mem("f32.load", *offset), + F64Load { offset } => self.assert_mem("f64.load", *offset), + I32Store { offset } => self.assert_mem("i32.store", *offset), + I32Store8 { offset } => self.assert_mem("i32.store8", *offset), + I32Store16 { offset } => self.assert_mem("i32.store16", *offset), + I64Store { offset } => self.assert_mem("i64.store", *offset), + F32Store { offset } => self.assert_mem("f32.store", *offset), + F64Store { offset } => self.assert_mem("f64.store", *offset), + + ListCanonLower { .. } => self.assert("list.canon_lower"), + ListLower { .. } => self.assert("list.lower"), + ListCanonLift { .. } => self.assert("list.canon_lift"), + ListLift { .. } => self.assert("list.lift"), + IterElem => self.assert("iter-elem"), + IterBasePointer => self.assert("iter-base-pointer"), + I32FromBitflags { .. } => self.assert("i32.from_bitflags"), BitflagsFromI32 { .. } => self.assert("bitflags.from_i32"), I64FromBitflags { .. } => self.assert("i64.from_bitflags"), BitflagsFromI64 { .. } => self.assert("bitflags.from_i64"), + + BufferLowerPtrLen { .. } => self.assert("buffer.lower_ptr_len"), + BufferLowerHandle { .. } => self.assert("buffer.lower_handle"), + BufferLiftPtrLen { .. } => self.assert("buffer.lift_ptr_len"), + BufferLiftHandle { .. } => self.assert("buffer.lift_handle"), + + Witx { instr } => match instr { + PointerFromI32 { .. } => self.assert("pointer.from_i32"), + ConstPointerFromI32 { .. } => self.assert("const_pointer.from_i32"), + ReuseReturn => self.assert("reuse_return"), + I32FromPointer => self.assert("i32.from_pointer"), + I32FromConstPointer => self.assert("i32.from_const_pointer"), + AddrOf => self.assert("addr-of"), + }, + } + + for op in operands.iter() { + self.assert_op(op); } - for _ in 0..inst.results_len() { - results.push(()); + + while results.len() < inst.results_len() { + results.push(Operand::InstResult); } } - fn allocate_space(&mut self, _: usize, _: &witx::NamedType) { - self.assert("allocate-space"); + fn allocate_typed_space(&mut self, _: &witx::NamedType) -> Self::Operand { + self.next_rp += 1; + Operand::Rp(self.next_rp - 1) + } + + fn allocate_i64_array(&mut self, _: usize) -> Self::Operand { + Operand::Rp(0) } fn push_block(&mut self) { self.assert("block.push"); } - fn finish_block(&mut self, _operand: Option) { + fn finish_block(&mut self, _operands: &mut Vec) { self.assert("block.finish"); } } @@ -368,6 +486,10 @@ mod kw { wast::custom_keyword!(i64); wast::custom_keyword!(f32); wast::custom_keyword!(f64); + wast::custom_keyword!(declared_import); + wast::custom_keyword!(declared_export); + wast::custom_keyword!(defined_import); + wast::custom_keyword!(defined_export); } struct Witxt<'a> { @@ -402,9 +524,8 @@ enum WitxtDirective<'a> { AssertAbi { span: wast::Span, witx: Witx<'a>, - wasm_signature: (Vec, Vec), - wasm: Abi<'a>, - interface: Abi<'a>, + wasm_signatures: Vec<(witx::CallMode, Vec, Vec)>, + abis: Vec>, }, } @@ -452,38 +573,47 @@ impl<'a> Parse<'a> for WitxtDirective<'a> { Ok(WitxtDirective::AssertAbi { span, witx: parser.parens(|p| p.parse())?, - wasm_signature: parser.parens(|p| { - p.parse::()?; - let mut params = Vec::new(); - let mut results = Vec::new(); - if p.peek2::() { - p.parens(|p| { - p.parse::()?; - while !p.is_empty() { - params.push(parse_wasmtype(p)?); + wasm_signatures: { + let mut signatures = Vec::new(); + while parser.peek2::() { + signatures.push(parser.parens(|p| { + p.parse::()?; + let mode = p + .parse::>()? + .map(|p| p.0) + .unwrap_or(witx::CallMode::DeclaredImport); + let mut params = Vec::new(); + let mut results = Vec::new(); + if p.peek2::() { + p.parens(|p| { + p.parse::()?; + while !p.is_empty() { + params.push(parse_wasmtype(p)?); + } + Ok(()) + })?; } - Ok(()) - })?; - } - if p.peek2::() { - p.parens(|p| { - p.parse::()?; - while !p.is_empty() { - results.push(parse_wasmtype(p)?); + if p.peek2::() { + p.parens(|p| { + p.parse::()?; + while !p.is_empty() { + results.push(parse_wasmtype(p)?); + } + Ok(()) + })?; } - Ok(()) - })?; + Ok((mode, params, results)) + })?); + } + signatures + }, + abis: { + let mut abis = Vec::new(); + while !parser.is_empty() { + abis.push(parser.parens(|p| p.parse())?) } - Ok((params, results)) - })?, - wasm: parser.parens(|p| { - p.parse::()?; - p.parse() - })?, - interface: parser.parens(|p| { - p.parse::()?; - p.parse() - })?, + abis + }, }) } else { Err(l.error()) @@ -512,7 +642,6 @@ fn parse_wasmtype(p: Parser<'_>) -> parser::Result { struct Witx<'a> { span: wast::Span, - id: Option>, def: WitxDef<'a>, } @@ -522,12 +651,24 @@ enum WitxDef<'a> { } impl Witx<'_> { - fn module(&self, contents: &str, file: &Path) -> Result { + fn module( + &self, + contents: &str, + file: &Path, + modules: &mut HashMap, + ) -> Result { use witx::parser::TopLevelSyntax; match &self.def { WitxDef::Inline(doc) => { - let mut validator = witx::ModuleValidation::new(contents, file); + if doc.module_name.is_none() { + dbg!(file); + } + let module_name = doc + .module_name + .expect("inline modules should have a name") + .name(); + let mut validator = witx::ModuleValidation::new(contents, module_name, file); for t in doc.decls.iter() { match &t.item { TopLevelSyntax::Decl(d) => validator @@ -541,7 +682,9 @@ impl Witx<'_> { .validate_function(&f.item, &f.comments) .map_err(|e| anyhow::anyhow!("{}", e.report()))?; } - Ok(validator.into_module()) + let module = validator.into_module(); + modules.insert(module_name.to_owned(), module.clone()); + Ok(module) } WitxDef::Fs(path) => { let parent = file.parent().unwrap(); @@ -555,7 +698,6 @@ impl Witx<'_> { impl<'a> Parse<'a> for Witx<'a> { fn parse(parser: Parser<'a>) -> parser::Result { let span = parser.parse::()?.0; - let id = parser.parse()?; let def = if parser.peek2::() { parser.parens(|p| { @@ -565,25 +707,82 @@ impl<'a> Parse<'a> for Witx<'a> { } else { WitxDef::Inline(parser.parse()?) }; - Ok(Witx { id, span, def }) + Ok(Witx { span, def }) } } struct Abi<'a> { + mode: witx::CallMode, instrs: Vec<(wast::Span, &'a str)>, } impl<'a> Parse<'a> for Abi<'a> { fn parse(parser: Parser<'a>) -> parser::Result { + let mut l = parser.lookahead1(); + let mode = if l.peek::() { + parser.parse::()?; + witx::CallMode::DeclaredImport + } else if l.peek::() { + parser.parse::()?; + witx::CallMode::DeclaredExport + } else if l.peek::() { + parser.parse::()?; + witx::CallMode::DefinedImport + } else if l.peek::() { + parser.parse::()?; + witx::CallMode::DefinedExport + } else { + return Err(l.error()); + }; let mut instrs = Vec::new(); while !parser.is_empty() { instrs.push(parser.step(|cursor| { - let (kw, next) = cursor - .keyword() - .ok_or_else(|| cursor.error("expected keyword"))?; + let (kw, next) = match cursor.keyword() { + Some(pair) => pair, + None => match cursor.integer() { + Some((i, next)) => (i.src(), next), + None => return Err(cursor.error("expected keyword or integer")), + }, + }; Ok(((cursor.cur_span(), kw), next)) })?); } - Ok(Abi { instrs }) + Ok(Abi { mode, instrs }) + } +} + +struct CallMode(witx::CallMode); + +impl<'a> Parse<'a> for CallMode { + fn parse(parser: Parser<'a>) -> parser::Result { + let mut l = parser.lookahead1(); + Ok(CallMode(if l.peek::() { + parser.parse::()?; + witx::CallMode::DeclaredImport + } else if l.peek::() { + parser.parse::()?; + witx::CallMode::DeclaredExport + } else if l.peek::() { + parser.parse::()?; + witx::CallMode::DefinedImport + } else if l.peek::() { + parser.parse::()?; + witx::CallMode::DefinedExport + } else { + return Err(l.error()); + })) + } +} + +impl Peek for CallMode { + fn peek(cursor: Cursor<'_>) -> bool { + kw::declared_import::peek(cursor) + || kw::declared_export::peek(cursor) + || kw::defined_import::peek(cursor) + || kw::defined_export::peek(cursor) + } + + fn display() -> &'static str { + "call mode" } } diff --git a/tools/witx/tests/witxt/abi-next.witxt b/tools/witx/tests/witxt/abi-next.witxt new file mode 100644 index 00000000..ce0245ff --- /dev/null +++ b/tools/witx/tests/witxt/abi-next.witxt @@ -0,0 +1,1117 @@ +(assert_abi + (witx (module $x (export "f" (func)))) + (wasm) + (declared_import call.wasm return) + (defined_import call.interface return) +) + +;; scalar arguments +(assert_abi + (witx (module $x (export "f" (func (param $p u8))))) + (wasm (param i32)) + (declared_import get-arg0 i32.from_u8 arg0 call.wasm return) + (defined_import get-arg0 u8.from_i32 arg0 call.interface return) +) +(assert_abi + (witx (module $x (export "f" (func (param $p s8))))) + (wasm (param i32)) + (declared_import get-arg0 i32.from_s8 arg0 call.wasm return) + (defined_import get-arg0 s8.from_i32 arg0 call.interface return) +) +(assert_abi + (witx (module $x (export "f" (func (param $p u16))))) + (wasm (param i32)) + (declared_import get-arg0 i32.from_u16 arg0 call.wasm return) + (defined_import get-arg0 u16.from_i32 arg0 call.interface return) +) +(assert_abi + (witx (module $x (export "f" (func (param $p s16))))) + (wasm (param i32)) + (declared_import get-arg0 i32.from_s16 arg0 call.wasm return) + (defined_import get-arg0 s16.from_i32 arg0 call.interface return) +) +(assert_abi + (witx (module $x (export "f" (func (param $p u32))))) + (wasm (param i32)) + (declared_import get-arg0 i32.from_u32 arg0 call.wasm return) + (defined_import get-arg0 u32.from_i32 arg0 call.interface return) +) +(assert_abi + (witx (module $x (export "f" (func (param $p s32))))) + (wasm (param i32)) + (declared_import get-arg0 i32.from_s32 arg0 call.wasm return) + (defined_import get-arg0 s32.from_i32 arg0 call.interface return) +) +(assert_abi + (witx (module $x (export "f" (func (param $p u64))))) + (wasm (param i64)) + (declared_import get-arg0 i64.from_u64 arg0 call.wasm return) + (defined_import get-arg0 u64.from_i64 arg0 call.interface return) +) +(assert_abi + (witx (module $x (export "f" (func (param $p s64))))) + (wasm (param i64)) + (declared_import get-arg0 i64.from_s64 arg0 call.wasm return) + (defined_import get-arg0 s64.from_i64 arg0 call.interface return) +) +(assert_abi + (witx (module $x (export "f" (func (param $p f32))))) + (wasm (param f32)) + (declared_import get-arg0 f32.from_if32 arg0 call.wasm return) + (defined_import get-arg0 if32.from_f32 arg0 call.interface return) +) +(assert_abi + (witx (module $x (export "f" (func (param $p f64))))) + (wasm (param f64)) + (declared_import get-arg0 f64.from_if64 arg0 call.wasm return) + (defined_import get-arg0 if64.from_f64 arg0 call.interface return) +) +(assert_abi + (witx (module $x (export "f" (func (param $p char))))) + (wasm (param i32)) + (declared_import get-arg0 i32.from_char arg0 call.wasm return) + (defined_import get-arg0 char.from_i32 arg0 call.interface return) +) + +;; handle argument +(assert_abi + (witx + (module $x + (resource $y) + (typename $y (handle $y)) + (export "f" (func (param $p $y))) + ) + ) + (wasm (param i32)) + (declared_import get-arg0 i32.from_borrowed_handle arg0 call.wasm return) + (defined_import get-arg0 handle.borrowed_from_i32 arg0 call.interface return) +) + +;; record arguments +(assert_abi + (witx + (module $x + (typename $y (record)) + (export "f" (func (param $p $y))) + ) + ) + (wasm) + (declared_import get-arg0 record-lower arg0 call.wasm return) + (defined_import record-lift call.interface return) +) + +(assert_abi + (witx + (module $x + (typename $y (record (field $a u32))) + (export "f" (func (param $p $y))) + ) + ) + (wasm (param i32)) + (declared_import + get-arg0 + record-lower arg0 + i32.from_u32 field.a + call.wasm + return) + (defined_import + get-arg0 + u32.from_i32 arg0 + record-lift + call.interface + return) +) +(assert_abi + (witx + (module $x + (typename $y (record + (field $a u32) + (field $b s64) + )) + (export "f" (func (param $p $y))) + ) + ) + (wasm (param i32 i64)) + (declared_import + get-arg0 + record-lower arg0 + i32.from_u32 field.a + i64.from_s64 field.b + call.wasm + return) + (defined_import + get-arg0 + get-arg1 + u32.from_i32 arg0 + s64.from_i64 arg1 + record-lift + call.interface + return) +) +(assert_abi + (witx + (module $x + (typename $empty (record)) + (typename $tuple (tuple f64 u32)) + (typename $y (record + (field $a u32) + (field $b $empty) + (field $c $tuple) + )) + (export "f" (func (param $p $y))) + ) + ) + (wasm (param i32 f64 i32)) + (declared_import + get-arg0 + record-lower arg0 + i32.from_u32 field.a + record-lower field.b + record-lower field.c + f64.from_if64 field.0 + i32.from_u32 field.1 + call.wasm + return) + (defined_import + get-arg0 + get-arg1 + get-arg2 + u32.from_i32 arg0 + record-lift ;; empty struct lift + if64.from_f64 arg1 + u32.from_i32 arg2 + record-lift ;; tuple lift + record-lift ;; final struct lift + call.interface + return) +) + +;; variant arguments +(assert_abi + (witx + (module $x + (typename $y (enum $a $b)) + (export "f" (func (param $p $y))) + ) + ) + (wasm (param i32)) + (declared_import + get-arg0 + block.push + i32.const 0 + block.finish + block.push + i32.const 1 + block.finish + variant-lower arg0 + call.wasm + return) + (defined_import + get-arg0 + block.push + block.finish + block.push + block.finish + variant-lift arg0 + call.interface + return) +) + +(assert_abi + (witx + (module $x + (typename $y (union u32 f32)) + (export "f" (func (param $p $y))) + ) + ) + (wasm (param i32 i32)) + (declared_import + get-arg0 + block.push + i32.const 0 + variant-payload + i32.from_u32 + block.finish + block.push + i32.const 1 + variant-payload + f32.from_if32 + f32-to-i32 + block.finish + variant-lower arg0 + call.wasm + return) + (defined_import + get-arg0 + get-arg1 + block.push + u32.from_i32 arg1 + block.finish + block.push + i32-to-f32 arg1 + if32.from_f32 + block.finish + variant-lift arg0 + call.interface + return) +) + +(assert_abi + (witx + (module $x + (typename $y (variant (case $none) (case $some f64))) + (export "f" (func (param $p $y))) + ) + ) + (wasm (param i32 f64)) + (declared_import + get-arg0 + block.push + i32.const 0 + f64.const 0 + block.finish + block.push + i32.const 1 + variant-payload + f64.from_if64 + block.finish + variant-lower arg0 + call.wasm + return) + (defined_import + get-arg0 + get-arg1 + block.push + block.finish + block.push + if64.from_f64 arg1 + block.finish + variant-lift arg0 + call.interface + return) +) + +;; list arguments +(assert_abi + (witx + (module $x (export "f" (func (param $p (list u8))))) + ) + (wasm (param i32 i32)) + (declared_import + get-arg0 + list.canon_lower arg0 + call.wasm + return) + (defined_import + get-arg0 + get-arg1 + list.canon_lift arg0 arg1 + call.interface + return) +) +(assert_abi + (witx + (module $x (export "f" (func (param $p (list char))))) + ) + (wasm (param i32 i32)) + (declared_import + get-arg0 + list.canon_lower arg0 + call.wasm + return) + (defined_import + get-arg0 + get-arg1 + list.canon_lift arg0 arg1 + call.interface + return) +) + +;; flavorful parameters +(assert_abi + (witx + (module $x + (typename $a (union (list u8) f32)) + (typename $b (tuple f64 bool)) + (typename $c (record + (field $a $a) + (field $b $b) + )) + + (typename $d (tuple f32 f64)) + (typename $e (variant + (case $a) + (case $b $d) + (case $c) + )) + + (export "f" (func + (param $p1 $c) + (param $p2 s32) + (param $p3 $e) + ))) + ) + (wasm (param i32 i32 i32 f64 i32 i32 i32 f32 f64)) + (declared_import + get-arg0 + get-arg1 + get-arg2 + + ;; conversion of $p1 + record-lower arg0 + ;; conversion of $c.a + block.push + i32.const 0 + variant-payload + list.canon_lower + block.finish + block.push + i32.const 1 + variant-payload + f32.from_if32 + f32-to-i32 + i32.const 0 + block.finish + variant-lower field.a + + ;; conversion of $c.b + record-lower field.b + f64.from_if64 field.0 + block.push + i32.const 0 + block.finish + block.push + i32.const 1 + block.finish + variant-lower field.1 + + ;; conversion of $p2 + i32.from_s32 arg1 + + ;; conversion of $p3 + block.push + i32.const 0 + f32.const 0 + f64.const 0 + block.finish + block.push + i32.const 1 + variant-payload + record-lower + f32.from_if32 field.0 + f64.from_if64 field.1 + block.finish + block.push + i32.const 2 + f32.const 0 + f64.const 0 + block.finish + variant-lower arg2 + call.wasm + return) + + (defined_import + get-arg0 + get-arg1 + get-arg2 + get-arg3 + get-arg4 + get-arg5 + get-arg6 + get-arg7 + get-arg8 + + ;; decoding p1 + ;; decode $a + block.push + list.canon_lift arg1 arg2 + block.finish + block.push + i32-to-f32 arg1 + if32.from_f32 + block.finish + variant-lift arg0 ;; lift into $a + + if64.from_f64 arg3 ;; decode $b field 0 + block.push + block.finish + block.push + block.finish + variant-lift arg4 ;; decode $b field 1's boolean + record-lift ;; lift into $b + + record-lift ;; lift into $c + + ;; decoding $p2 + s32.from_i32 arg5 + + ;; decoding $p3 + block.push + block.finish + block.push + if32.from_f32 arg7 + if64.from_f64 arg8 + record-lift ;; lift into $d + block.finish + block.push + block.finish + variant-lift arg6 ;; lift into $e + + call.interface + + return) +) + +;; scalar results +(assert_abi + (witx (module $x (export "f" (func (result $p u8))))) + (wasm (result i32)) + (declared_import call.wasm u8.from_i32 ret0 return) + (defined_import call.interface i32.from_u8 ret0 return) +) +(assert_abi + (witx (module $x (export "f" (func (result $p s8))))) + (wasm (result i32)) + (declared_import call.wasm s8.from_i32 ret0 return) + (defined_import call.interface i32.from_s8 ret0 return) +) +(assert_abi + (witx (module $x (export "f" (func (result $p u16))))) + (wasm (result i32)) + (declared_import call.wasm u16.from_i32 ret0 return) + (defined_import call.interface i32.from_u16 ret0 return) +) +(assert_abi + (witx (module $x (export "f" (func (result $p s16))))) + (wasm (result i32)) + (declared_import call.wasm s16.from_i32 ret0 return) + (defined_import call.interface i32.from_s16 ret0 return) +) +(assert_abi + (witx (module $x (export "f" (func (result $p u32))))) + (wasm (result i32)) + (declared_import call.wasm u32.from_i32 ret0 return) + (defined_import call.interface i32.from_u32 ret0 return) +) +(assert_abi + (witx (module $x (export "f" (func (result $p s32))))) + (wasm (result i32)) + (declared_import call.wasm s32.from_i32 ret0 return) + (defined_import call.interface i32.from_s32 ret0 return) +) +(assert_abi + (witx (module $x (export "f" (func (result $p u64))))) + (wasm (result i64)) + (declared_import call.wasm u64.from_i64 ret0 return) + (defined_import call.interface i64.from_u64 ret0 return) +) +(assert_abi + (witx (module $x (export "f" (func (result $p s64))))) + (wasm (result i64)) + (declared_import call.wasm s64.from_i64 ret0 return) + (defined_import call.interface i64.from_s64 ret0 return) +) +(assert_abi + (witx (module $x (export "f" (func (result $p f32))))) + (wasm (result f32)) + (declared_import call.wasm if32.from_f32 ret0 return) + (defined_import call.interface f32.from_if32 ret0 return) +) +(assert_abi + (witx (module $x (export "f" (func (result $p f64))))) + (wasm (result f64)) + (declared_import call.wasm if64.from_f64 ret0 return) + (defined_import call.interface f64.from_if64 ret0 return) +) +(assert_abi + (witx (module $x (export "f" (func (result $p char))))) + (wasm (result i32)) + (declared_import call.wasm char.from_i32 ret0 return) + (defined_import call.interface i32.from_char ret0 return) +) + +;; handle result +(assert_abi + (witx + (module $x + (resource $y) + (typename $y (handle $y)) + (export "f" (func (result $p $y))) + ) + ) + (wasm (result i32)) + (declared_import call.wasm handle.owned_from_i32 ret0 return) + (defined_import call.interface i32.from_owned_handle ret0 return) +) + +;; record results +(assert_abi + (witx + (module $x + (typename $y (record)) + (export "f" (func (result $p $y))) + ) + ) + (wasm) + (declared_import call.wasm record-lift return) + (defined_import call.interface record-lower ret0 return) +) +(assert_abi + (witx + (module $x + (typename $y (record (field $a s32))) + (export "f" (func (result $p $y))) + ) + ) + (wasm (result i32)) + (declared_import call.wasm s32.from_i32 ret0 record-lift return) + (defined_import + call.interface + record-lower ret0 + i32.from_s32 field.a + return) +) +(assert_abi + (witx + (module $x + (typename $y (record (field $a s32) (field $b u32))) + (export "f" (func (result $p $y))) + ) + ) + (wasm (param i32)) + (declared_import + call.wasm rp0 + i32.load offset=0 rp0 + i32.load offset=8 rp0 + s32.from_i32 + u32.from_i32 + record-lift return) + (defined_import + call.interface + record-lower ret0 + i32.from_s32 field.a + i32.from_u32 field.b + get-arg0 + i32.store offset=8 arg0 + i32.store offset=0 arg0 + return) +) + +;; variant results +(assert_abi + (witx + (module $x + (typename $y (enum $a $b)) + (export "f" (func (result $p $y))) + ) + ) + (wasm (result i32)) + (declared_import + call.wasm + block.push + block.finish + block.push + block.finish + variant-lift ret0 + return) + (defined_import + call.interface + block.push + i32.const 0 + block.finish + block.push + i32.const 1 + block.finish + variant-lower ret0 + return) +) + +(assert_abi + (witx + (module $x + (typename $y (variant (case $a f32) (case $b))) + (export "f" (func (result $p $y))) + ) + ) + (wasm (param i32)) + (declared_import + call.wasm rp0 + i32.load offset=0 rp0 + f32.load offset=8 rp0 + block.push + if32.from_f32 + block.finish + block.push + block.finish + variant-lift + return) + (defined_import + call.interface + block.push + i32.const 0 + variant-payload + f32.from_if32 + block.finish + block.push + i32.const 1 + f32.const 0 + block.finish + variant-lower ret0 + get-arg0 + f32.store offset=8 arg0 + i32.store offset=0 arg0 + return) +) + +;; multiple results +(assert_abi + (witx + (module $x (export "f" (func (result $a s32) (result $b f64) (result $c bool)))) + ) + (wasm (param i32)) + (declared_import + call.wasm rp0 + i32.load offset=0 rp0 + f64.load offset=8 rp0 + i32.load offset=16 rp0 + + s32.from_i32 + if64.from_f64 + block.push + block.finish + block.push + block.finish + variant-lift + return + ) + (defined_import + call.interface + i32.from_s32 ret0 + f64.from_if64 ret1 + block.push + i32.const 0 + block.finish + block.push + i32.const 1 + block.finish + variant-lower ret2 + + get-arg0 + + i32.store offset=16 arg0 + f64.store offset=8 arg0 + i32.store offset=0 arg0 + return + ) +) + +;; invalid +(assert_invalid + (witx (module $x (export "f" (func (result $x (@witx char8)))))) + "cannot use `(@witx char8)" +) +(assert_invalid + (witx (module $x (export "f" (func (result $x (@witx pointer u8)))))) + "cannot use `(@witx pointer)" +) +(assert_invalid + (witx (module $x (export "f" (func (result $x (@witx const_pointer u8)))))) + "cannot use `(@witx const_pointer)" +) + +;; fancy list parameters +(assert_abi + (witx + (module $x (export "f" (func (param $a (list (tuple u32 u32)))))) + ) + (wasm (param i32 i32)) + (declared_import + get-arg0 + list.canon_lower arg0 + call.wasm + return + ) + (defined_import + get-arg0 + get-arg1 + list.canon_lift arg0 arg1 + call.interface + return + ) +) +(assert_abi + (witx + (module $x (export "f" (func (param $a (list bool))))) + ) + (wasm (param i32 i32)) + (declared_import + get-arg0 + block.push + iter-elem + iter-base-pointer + block.push + i32.const 0 + i32.store8 offset=0 + block.finish + block.push + i32.const 1 + i32.store8 offset=0 + block.finish + variant-lower + block.finish + list.lower arg0 + call.wasm + return + ) + (defined_import + get-arg0 + get-arg1 + block.push + iter-base-pointer + i32.load8u offset=0 + block.push + block.finish + block.push + block.finish + variant-lift + block.finish + list.lift arg0 arg1 + call.interface + return + ) +) +(assert_abi + (witx + (module $x + (typename $a (record (field $x bool) (field $y (list u8)))) + (export "f" (func (param $a (list $a)))) + ) + ) + (wasm (param i32 i32)) + (declared_import + get-arg0 + block.push + iter-elem + iter-base-pointer + record-lower + block.push + i32.const 0 + i32.store8 offset=0 + block.finish + block.push + i32.const 1 + i32.store8 offset=0 + block.finish + variant-lower field.x + list.canon_lower field.y + i32.store offset=8 + i32.store offset=4 + block.finish + list.lower arg0 + call.wasm + return + ) + (defined_import + get-arg0 + get-arg1 + block.push + iter-base-pointer + i32.load8u offset=0 + block.push + block.finish + block.push + block.finish + variant-lift + i32.load offset=4 + i32.load offset=8 + list.canon_lift + record-lift + block.finish + list.lift arg0 arg1 + call.interface + return + ) +) + +;; bitflags +(assert_abi + (witx + (module $x + (typename $a (flags $a $b)) + (export "f" (func (param $a $a) (result $b $a))) + ) + ) + (wasm (param i32) (result i32)) + (declared_import + get-arg0 + i32.from_bitflags arg0 + call.wasm + bitflags.from_i32 ret0 + return + ) + (defined_import + get-arg0 + bitflags.from_i32 arg0 + call.interface + i32.from_bitflags ret0 + return + ) +) +(assert_abi + (witx + (module $x + (typename $a (flags + $f0 $f1 $f2 $f3 $f4 $f5 $f6 $f7 + $f8 $f9 $f10 $f11 $f12 $f13 $f14 $f15 + $f16 $f17 $f18 $f19 $f20 $f21 $f22 $f23 + $f24 $f25 $f26 $f27 $f28 $f29 $f30 $f31 + $f32 + )) + (export "f" (func (param $a $a) (result $b $a))) + ) + ) + (wasm (param i64) (result i64)) +) +(assert_abi + (witx + (module $x + (typename $a (flags $a $b)) + (export "f" (func + (param $a (list $a)) + (result $b (list $a)) + )) + ) + ) + (wasm (param i32 i32 i32)) + (declared_import + get-arg0 + block.push + iter-elem + iter-base-pointer + i32.from_bitflags + i32.store8 offset=0 + block.finish + list.lower arg0 + call.wasm rp0 + i32.load offset=0 rp0 + i32.load offset=8 rp0 + block.push + iter-base-pointer + i32.load8u offset=0 + bitflags.from_i32 + block.finish + list.lift + return + ) +) + +;; buffers +(assert_abi + (witx + (module $x (export "f" (func (param $a (in-buffer u8))))) + ) + (wasm declared_import (param i32 i32)) + (wasm defined_import (param i32 i32)) + (wasm declared_export (param i32)) + (wasm defined_export (param i32)) + + (declared_import + get-arg0 + + ;; definition of how to write to the buffer to-be-read by the callee + iter-base-pointer + block.push + variant-payload + i32.from_u8 + i32.store8 offset=0 + block.finish + + buffer.lower_ptr_len arg0 + call.wasm + return) + + (defined_import + get-arg0 + get-arg1 + + ;; definition of how to read to the buffer from the caller + iter-base-pointer + block.push + i32.load8u offset=0 + u8.from_i32 + block.finish + + buffer.lift_ptr_len arg0 arg1 + call.interface + return) + + (declared_export + get-arg0 + + ;; definition of how to write to the buffer to-be-read by the callee + iter-base-pointer + block.push + variant-payload + i32.from_u8 + i32.store8 offset=0 + block.finish + + buffer.lower_handle arg0 + call.wasm + return) + + (defined_export + get-arg0 + + ;; definition of how to read to the buffer from the caller + iter-base-pointer + block.push + i32.load8u offset=0 + u8.from_i32 + block.finish + + buffer.lift_handle arg0 + call.interface + return) +) +(assert_abi + (witx + (module $x (export "f" (func (param $a (out-buffer u8))))) + ) + (wasm declared_import (param i32 i32)) + (wasm defined_import (param i32 i32)) + (wasm declared_export (param i32)) + (wasm defined_export (param i32)) + + (declared_import + get-arg0 + + iter-base-pointer + block.push + i32.load8u offset=0 + u8.from_i32 + block.finish + + buffer.lower_ptr_len arg0 + call.wasm + return) + + (defined_import + get-arg0 + get-arg1 + + ;; definition of how to read to the buffer from the caller + iter-base-pointer + block.push + variant-payload + i32.from_u8 + i32.store8 offset=0 + block.finish + + buffer.lift_ptr_len arg0 arg1 + call.interface + return) +) + +(assert_invalid + (witx + (module $x (export "f" (func (result $a (in-buffer u8))))) + ) + "cannot use buffers in the result position" +) +(assert_invalid + (witx + (module $x (export "f" (func (result $a (out-buffer u8))))) + ) + "cannot use buffers in the result position" +) +(assert_invalid + (witx + (module $x (export "f" (func (param $a (out-buffer (in-buffer u8)))))) + ) + "cannot use buffers in the result position" +) +(assert_abi + (witx + (module $x (export "f" (func (param $a (list (in-buffer u8)))))) + ) + (wasm declared_import (param i32 i32)) + (wasm declared_export (param i32 i32)) + (declared_import + get-arg0 + block.push + iter-elem + iter-base-pointer + + iter-base-pointer + block.push + variant-payload + i32.from_u8 + i32.store8 offset=0 + block.finish + buffer.lower_ptr_len + + i32.store offset=4 + i32.store offset=0 + block.finish + list.lower arg0 + call.wasm + return) + + (defined_import + get-arg0 + get-arg1 + block.push + iter-base-pointer + i32.load offset=0 + i32.load offset=4 + + iter-base-pointer + block.push + i32.load8u offset=0 + u8.from_i32 + block.finish + buffer.lift_ptr_len + block.finish + list.lift arg0 arg1 + call.interface + return) + + (declared_export + get-arg0 + block.push + iter-elem + iter-base-pointer + + iter-base-pointer + block.push + variant-payload + i32.from_u8 + i32.store8 offset=0 + block.finish + buffer.lower_handle + + i32.store offset=0 + block.finish + list.lower arg0 + call.wasm + return) + + (defined_export + get-arg0 + get-arg1 + block.push + iter-base-pointer + i32.load offset=0 + + iter-base-pointer + block.push + i32.load8u offset=0 + u8.from_i32 + block.finish + buffer.lift_handle + block.finish + list.lift arg0 arg1 + call.interface + return) +) diff --git a/tools/witx/tests/witxt/abi.witxt b/tools/witx/tests/witxt/abi.witxt index 913e6edb..0bd97d4a 100644 --- a/tools/witx/tests/witxt/abi.witxt +++ b/tools/witx/tests/witxt/abi.witxt @@ -1,304 +1,368 @@ (assert_abi (witx (module $x (@interface func (export "f")))) (wasm) - (call_wasm call.wasm return) - (call_interface call.interface return) + (declared_import call.wasm return) + (defined_import call.interface return) ) ;; scalar arguments (assert_abi (witx (module $x (@interface func (export "f") (param $p u8)))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_u8 call.wasm return) - (call_interface get-arg0 u8.from_i32 call.interface return) + (declared_import get-arg0 i32.from_u8 arg0 call.wasm return) + (defined_import get-arg0 u8.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p s8)))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_s8 call.wasm return) - (call_interface get-arg0 s8.from_i32 call.interface return) + (declared_import get-arg0 i32.from_s8 arg0 call.wasm return) + (defined_import get-arg0 s8.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p u16)))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_u16 call.wasm return) - (call_interface get-arg0 u16.from_i32 call.interface return) + (declared_import get-arg0 i32.from_u16 arg0 call.wasm return) + (defined_import get-arg0 u16.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p s16)))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_s16 call.wasm return) - (call_interface get-arg0 s16.from_i32 call.interface return) + (declared_import get-arg0 i32.from_s16 arg0 call.wasm return) + (defined_import get-arg0 s16.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p u32)))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_u32 call.wasm return) - (call_interface get-arg0 u32.from_i32 call.interface return) + (declared_import get-arg0 i32.from_u32 arg0 call.wasm return) + (defined_import get-arg0 u32.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p s32)))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_s32 call.wasm return) - (call_interface get-arg0 s32.from_i32 call.interface return) + (declared_import get-arg0 i32.from_s32 arg0 call.wasm return) + (defined_import get-arg0 s32.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p u64)))) (wasm (param i64)) - (call_wasm get-arg0 i64.from_u64 call.wasm return) - (call_interface get-arg0 u64.from_i64 call.interface return) + (declared_import get-arg0 i64.from_u64 arg0 call.wasm return) + (defined_import get-arg0 u64.from_i64 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p s64)))) (wasm (param i64)) - (call_wasm get-arg0 i64.from_s64 call.wasm return) - (call_interface get-arg0 s64.from_i64 call.interface return) + (declared_import get-arg0 i64.from_s64 arg0 call.wasm return) + (defined_import get-arg0 s64.from_i64 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p f32)))) (wasm (param f32)) - (call_wasm get-arg0 f32.from_if32 call.wasm return) - (call_interface get-arg0 if32.from_f32 call.interface return) + (declared_import get-arg0 f32.from_if32 arg0 call.wasm return) + (defined_import get-arg0 if32.from_f32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p f64)))) (wasm (param f64)) - (call_wasm get-arg0 f64.from_if64 call.wasm return) - (call_interface get-arg0 if64.from_f64 call.interface return) + (declared_import get-arg0 f64.from_if64 arg0 call.wasm return) + (defined_import get-arg0 if64.from_f64 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p (@witx usize))))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_usize call.wasm return) - (call_interface get-arg0 usize.from_i32 call.interface return) + (declared_import get-arg0 i32.from_usize arg0 call.wasm return) + (defined_import get-arg0 usize.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p (@witx char8))))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_char8 call.wasm return) - (call_interface get-arg0 char8.from_i32 call.interface return) + (declared_import get-arg0 i32.from_char8 arg0 call.wasm return) + (defined_import get-arg0 char8.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p char)))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_char call.wasm return) - (call_interface get-arg0 char.from_i32 call.interface return) + (declared_import get-arg0 i32.from_char arg0 call.wasm return) + (defined_import get-arg0 char.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p (@witx pointer u8))))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_pointer call.wasm return) - (call_interface get-arg0 pointer.from_i32 call.interface return) + (declared_import get-arg0 i32.from_pointer arg0 call.wasm return) + (defined_import get-arg0 pointer.from_i32 arg0 call.interface return) ) (assert_abi (witx (module $x (@interface func (export "f") (param $p (@witx const_pointer u8))))) (wasm (param i32)) - (call_wasm get-arg0 i32.from_const_pointer call.wasm return) - (call_interface get-arg0 const_pointer.from_i32 call.interface return) + (declared_import get-arg0 i32.from_const_pointer arg0 call.wasm return) + (defined_import get-arg0 const_pointer.from_i32 arg0 call.interface return) ) ;; flags parameter (assert_abi (witx - (typename $a (flags $x $y)) - (module $x (@interface func (export "f") (param $p $a))) + (module $x + (typename $a (flags $x $y)) + (@interface func (export "f") (param $p $a)) + ) ) (wasm (param i32)) - (call_wasm get-arg0 i32.from_bitflags call.wasm return) - (call_interface get-arg0 bitflags.from_i32 call.interface return) + (declared_import get-arg0 i32.from_bitflags arg0 call.wasm return) + (defined_import get-arg0 bitflags.from_i32 arg0 call.interface return) ) (assert_abi (witx - (typename $a (flags (@witx repr u64) $x $y)) - (module $x (@interface func (export "f") (param $p $a))) + (module $x + (typename $a (flags (@witx repr u64) $x $y)) + (@interface func (export "f") (param $p $a)) + ) ) (wasm (param i64)) - (call_wasm get-arg0 i64.from_bitflags call.wasm return) - (call_interface get-arg0 bitflags.from_i64 call.interface return) + (declared_import get-arg0 i64.from_bitflags arg0 call.wasm return) + (defined_import get-arg0 bitflags.from_i64 arg0 call.interface return) +) +(assert_abi + (witx + (module $x + (typename $a (record (field $x bool))) + (@interface func (export "f") (param $p $a)) + ) + ) + (wasm (param i32)) + (declared_import get-arg0 i32.from_bitflags arg0 call.wasm return) ) ;; struct parameter (assert_abi (witx - (typename $a (record (field $x u8))) - (module $x (@interface func (export "f") (param $p $a))) + (module $x + (typename $a (record (field $x u8))) + (@interface func (export "f") (param $p $a)) + ) ) (wasm (param i32)) - (call_wasm get-arg0 addr-of call.wasm return) - (call_interface get-arg0 load call.interface return) + (declared_import get-arg0 addr-of arg0 call.wasm return) + (defined_import + get-arg0 + i32.load8u offset=0 arg0 + u8.from_i32 + record-lift + call.interface + return) ) ;; handle parameter (assert_abi (witx - (resource $a) - (typename $a (handle $a)) - (module $x (@interface func (export "f") (param $p $a))) + (module $x + (resource $a) + (typename $a (handle $a)) + (@interface func (export "f") (param $p $a)) + ) ) (wasm (param i32)) - (call_wasm get-arg0 i32.from_handle call.wasm return) - (call_interface get-arg0 handle.from_i32 call.interface return) + (declared_import get-arg0 i32.from_borrowed_handle arg0 call.wasm return) + (defined_import get-arg0 handle.borrowed_from_i32 arg0 call.interface return) ) ;; list parameter (assert_abi (witx - (module $x (@interface func (export "f") (param $p (list u8)))) + (module $x + (@interface func (export "f") (param $p (list u8)))) ) (wasm (param i32 i32)) - (call_wasm get-arg0 list.pointer_length call.wasm return) - (call_interface get-arg0 get-arg1 list.from_pointer_length call.interface return) + (declared_import get-arg0 list.canon_lower arg0 call.wasm return) + (defined_import + get-arg0 + get-arg1 + list.canon_lift arg0 arg1 + call.interface + return) ) ;; variant parameter -- some not allowed at this time (assert_abi (witx - (typename $a (enum $b)) - (module $x (@interface func (export "f") (param $p $a))) + (module $x + (typename $a (enum $b)) + (@interface func (export "f") (param $p $a)) + ) ) (wasm (param i32)) - (call_wasm get-arg0 enum.lower call.wasm return) - (call_interface get-arg0 enum.lift call.interface return) + (declared_import + get-arg0 + block.push + i32.const 0 + block.finish + variant-lower arg0 + call.wasm + return) + (defined_import + get-arg0 + block.push + block.finish + variant-lift arg0 + call.interface + return) ) (assert_abi (witx - (typename $a (union f32)) - (module $x (@interface func (export "f") (param $p $a))) + (module $x + (typename $a (union f32)) + (@interface func (export "f") (param $p $a)) + ) ) (wasm (param i32)) - (call_wasm get-arg0 addr-of call.wasm return) - (call_interface get-arg0 load call.interface return) + (declared_import get-arg0 addr-of arg0 call.wasm return) + (defined_import + get-arg0 + i32.load8u offset=0 arg0 + block.push + f32.load offset=4 arg0 + if32.from_f32 + block.finish + variant-lift + call.interface + return) ) ;; scalar returns (assert_abi (witx (module $x (@interface func (export "f") (result $p u8)))) (wasm (result i32)) - (call_wasm call.wasm u8.from_i32 return) - (call_interface call.interface i32.from_u8 return) + (declared_import call.wasm u8.from_i32 ret0 return) + (defined_import call.interface i32.from_u8 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p s8)))) (wasm (result i32)) - (call_wasm call.wasm s8.from_i32 return) - (call_interface call.interface i32.from_s8 return) + (declared_import call.wasm s8.from_i32 ret0 return) + (defined_import call.interface i32.from_s8 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p u16)))) (wasm (result i32)) - (call_wasm call.wasm u16.from_i32 return) - (call_interface call.interface i32.from_u16 return) + (declared_import call.wasm u16.from_i32 ret0 return) + (defined_import call.interface i32.from_u16 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p s16)))) (wasm (result i32)) - (call_wasm call.wasm s16.from_i32 return) - (call_interface call.interface i32.from_s16 return) + (declared_import call.wasm s16.from_i32 ret0 return) + (defined_import call.interface i32.from_s16 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p u32)))) (wasm (result i32)) - (call_wasm call.wasm u32.from_i32 return) - (call_interface call.interface i32.from_u32 return) + (declared_import call.wasm u32.from_i32 ret0 return) + (defined_import call.interface i32.from_u32 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p s32)))) (wasm (result i32)) - (call_wasm call.wasm s32.from_i32 return) - (call_interface call.interface i32.from_s32 return) + (declared_import call.wasm s32.from_i32 ret0 return) + (defined_import call.interface i32.from_s32 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p u64)))) (wasm (result i64)) - (call_wasm call.wasm u64.from_i64 return) - (call_interface call.interface i64.from_u64 return) + (declared_import call.wasm u64.from_i64 ret0 return) + (defined_import call.interface i64.from_u64 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p s64)))) (wasm (result i64)) - (call_wasm call.wasm s64.from_i64 return) - (call_interface call.interface i64.from_s64 return) + (declared_import call.wasm s64.from_i64 ret0 return) + (defined_import call.interface i64.from_s64 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p f32)))) (wasm (result f32)) - (call_wasm call.wasm if32.from_f32 return) - (call_interface call.interface f32.from_if32 return) + (declared_import call.wasm if32.from_f32 ret0 return) + (defined_import call.interface f32.from_if32 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p f64)))) (wasm (result f64)) - (call_wasm call.wasm if64.from_f64 return) - (call_interface call.interface f64.from_if64 return) + (declared_import call.wasm if64.from_f64 ret0 return) + (defined_import call.interface f64.from_if64 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p (@witx usize))))) (wasm (result i32)) - (call_wasm call.wasm usize.from_i32 return) - (call_interface call.interface i32.from_usize return) + (declared_import call.wasm usize.from_i32 ret0 return) + (defined_import call.interface i32.from_usize ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p (@witx char8))))) (wasm (result i32)) - (call_wasm call.wasm char8.from_i32 return) - (call_interface call.interface i32.from_char8 return) + (declared_import call.wasm char8.from_i32 ret0 return) + (defined_import call.interface i32.from_char8 ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p char)))) (wasm (result i32)) - (call_wasm call.wasm char.from_i32 return) - (call_interface call.interface i32.from_char return) + (declared_import call.wasm char.from_i32 ret0 return) + (defined_import call.interface i32.from_char ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p (@witx pointer u8))))) (wasm (result i32)) - (call_wasm call.wasm pointer.from_i32 return) - (call_interface call.interface i32.from_pointer return) + (declared_import call.wasm pointer.from_i32 ret0 return) + (defined_import call.interface i32.from_pointer ret0 return) ) (assert_abi (witx (module $x (@interface func (export "f") (result $p (@witx const_pointer u8))))) (wasm (result i32)) - (call_wasm call.wasm const_pointer.from_i32 return) - (call_interface call.interface i32.from_const_pointer return) + (declared_import call.wasm const_pointer.from_i32 ret0 return) + (defined_import call.interface i32.from_const_pointer ret0 return) ) -;; flags return +;; flags ret0 return (assert_abi (witx - (typename $a (flags $x $y)) - (module $x (@interface func (export "f") (result $p $a))) + (module $x + (typename $a (flags $x $y)) + (@interface func (export "f") (result $p $a)) + ) ) (wasm (result i32)) - (call_wasm call.wasm bitflags.from_i32 return) - (call_interface call.interface i32.from_bitflags return) + (declared_import call.wasm bitflags.from_i32 ret0 return) + (defined_import call.interface i32.from_bitflags ret0 return) ) (assert_abi (witx - (typename $a (flags (@witx repr u64) $x $y)) - (module $x (@interface func (export "f") (result $p $a))) + (module $x + (typename $a (flags (@witx repr u64) $x $y)) + (@interface func (export "f") (result $p $a)) + ) ) (wasm (result i64)) - (call_wasm call.wasm bitflags.from_i64 return) - (call_interface call.interface i64.from_bitflags return) + (declared_import call.wasm bitflags.from_i64 ret0 return) + (defined_import call.interface i64.from_bitflags ret0 return) ) -;; handle return +;; handle ret0 return (assert_abi (witx - (resource $a) - (typename $a (handle $a)) - (module $x (@interface func (export "f") (result $p $a))) + (module $x + (resource $a) + (typename $a (handle $a)) + (@interface func (export "f") (result $p $a)) + ) ) (wasm (result i32)) - (call_wasm call.wasm handle.from_i32 return) - (call_interface call.interface i32.from_handle return) + (declared_import call.wasm handle.owned_from_i32 ret0 return) + (defined_import call.interface i32.from_owned_handle ret0 return) ) ;; struct return -- not supported (assert_invalid (witx - (typename $a (record (field $x u8))) - (module $x (@interface func (export "f") (result $p $a))) + (module $x + (typename $a (record (field $x u8))) + (@interface func (export "f") (result $p $a)) + ) ) "ABI error: invalid return type" ) @@ -314,30 +378,38 @@ ;; variant return -- only some allowed (assert_invalid (witx - (typename $a (enum $b)) - (module $x (@interface func (export "f") (result $p $a))) + (module $x + (typename $a (enum $b)) + (@interface func (export "f") (result $p $a)) + ) ) "ABI error: invalid return type" ) (assert_invalid (witx - (typename $a (union s32 f32)) - (module $x (@interface func (export "f") (result $p $a))) + (module $x + (typename $a (union s32 f32)) + (@interface func (export "f") (result $p $a)) + ) ) "ABI error: invalid return type" ) (assert_invalid (witx - (typename $a (expected (error f32))) - (module $x (@interface func (export "f") (result $p $a))) + (module $x + (typename $a (expected (error f32))) + (@interface func (export "f") (result $p $a)) + ) ) "ABI error: only named types are allowed in results" ) (assert_invalid (witx - (typename $errno (enum $success $bad)) - (typename $a (expected f32 (error $errno))) - (module $x (@interface func (export "f") (result $p $a))) + (module $x + (typename $errno (enum $success $bad)) + (typename $a (expected f32 (error $errno))) + (@interface func (export "f") (result $p $a)) + ) ) "ABI error: only named types are allowed in results" ) @@ -345,13 +417,15 @@ ;; Result<(), $errno> (assert_abi (witx - (typename $errno (enum $success $bad)) - (typename $a (expected (error $errno))) - (module $x (@interface func (export "f") (result $p $a))) + (module $x + (typename $errno (enum $success $bad)) + (typename $a (expected (error $errno))) + (@interface func (export "f") (result $p $a)) + ) ) (wasm (result i32)) - (call_wasm + (declared_import call.wasm ;; ok block, nothing happens block.push @@ -359,134 +433,167 @@ ;; err block, we lift the return value as the num block.push reuse_return - enum.lift + block.push + block.finish + block.push + block.finish + variant-lift block.finish ;; consumes 2 blocks and uses the return value of the call to discriminate - result.lift + variant-lift ret0 return) - (call_interface + (defined_import call.interface ;; ok block, nothing happens block.push + i32.const 0 block.finish ;; err block, lift the enum block.push variant-payload - enum.lower + block.push + i32.const 0 + block.finish + block.push + i32.const 1 + block.finish + variant-lower block.finish ;; consume the 2 blocks and lower based on the call - result.lower + variant-lower ret0 return) ) ;; Result<$ty, $errno> (assert_abi (witx - (typename $errno (enum $success $bad)) - (typename $size u32) - (typename $a (expected $size (error $errno))) - (module $x (@interface func (export "f") (result $p $a))) + (module $x + (typename $errno (enum $success $bad)) + (typename $size u32) + (typename $a (expected $size (error $errno))) + (@interface func (export "f") (result $p $a)) + ) ) (wasm (param i32) (result i32)) - (call_wasm - ;; make space for the return value and push its pointer - allocate-space - return_pointer.get0 - - call.wasm + (declared_import + call.wasm rp0 ;; ok block, load the return pointer and have it be the result for the `Ok` block.push - return_pointer.get0 - load + i32.load offset=0 rp0 + u32.from_i32 block.finish block.push reuse_return - enum.lift + block.push + block.finish + block.push + block.finish + variant-lift block.finish - result.lift + variant-lift ret0 return) - (call_interface + (defined_import call.interface ;; store the successful result at the first return pointer (the first param) block.push variant-payload get-arg0 - store + i32.from_u32 + i32.store offset=0 arg0 + i32.const 0 block.finish block.push variant-payload - enum.lower + block.push + i32.const 0 + block.finish + block.push + i32.const 1 + block.finish + variant-lower block.finish - result.lower + variant-lower ret0 return) ) ;; Result<($a, $b), $errno> (assert_abi (witx - (typename $errno (enum $success $bad)) - (typename $size u32) - (typename $other (record (field $a $size))) - (module $x (@interface func (export "f") - (result $p (expected (tuple $size $other) (error $errno))))) + (module $x + (typename $errno (enum $success $bad)) + (typename $size u32) + (typename $other (record (field $a $size))) + (@interface func (export "f") + (result $p (expected (tuple $size $other) (error $errno)))) + ) ) (wasm (param i32 i32) (result i32)) - (call_wasm - allocate-space - return_pointer.get0 - allocate-space - return_pointer.get1 - - call.wasm + (declared_import + call.wasm rp0 rp1 block.push - return_pointer.get0 - load - return_pointer.get1 - load - tuple.lift + i32.load offset=0 rp0 + u32.from_i32 + i32.load offset=0 rp1 + u32.from_i32 + record-lift + record-lift block.finish block.push reuse_return - enum.lift + block.push + block.finish + block.push + block.finish + variant-lift block.finish - result.lift + variant-lift ret0 return) - (call_interface + (defined_import call.interface ;; note the reverse order since we're consuming the results of lowering the ;; tuple block.push variant-payload - tuple.lower + record-lower get-arg1 - store + record-lower field.1 + i32.from_u32 field.a + i32.store offset=0 arg1 get-arg0 - store + i32.from_u32 field.0 + i32.store offset=0 arg0 + i32.const 0 block.finish block.push variant-payload - enum.lower + block.push + i32.const 0 + block.finish + block.push + i32.const 1 + block.finish + variant-lower block.finish - result.lower + variant-lower ret0 return) ) diff --git a/tools/witx/tests/witxt/anonymous.witxt b/tools/witx/tests/witxt/anonymous.witxt index 4ff47971..2130e9cb 100644 --- a/tools/witx/tests/witxt/anonymous.witxt +++ b/tools/witx/tests/witxt/anonymous.witxt @@ -3,55 +3,55 @@ (assert_invalid (witx - (typename $a (@witx pointer (record (field $b u8)))) + (module $x (typename $a (@witx pointer (record (field $b u8))))) ) "Anonymous structured types") (assert_invalid (witx - (typename $a (@witx pointer (union))) + (module $x (typename $a (@witx pointer (union)))) ) "Anonymous structured types") (assert_invalid (witx - (typename $a (@witx pointer (enum $b))) + (module $x (typename $a (@witx pointer (enum $b)))) ) "Anonymous structured types") (assert_invalid (witx - (typename $a (@witx pointer (flags $b))) + (module $x (typename $a (@witx pointer (flags $b)))) ) "Anonymous structured types") (assert_invalid - (witx + (witx (module $x (resource $x) (typename $a (@witx pointer (handle $x))) - ) + )) "Anonymous structured types") (assert_invalid (witx - (typename $a (record (field $b (record (field $c u8))))) + (module $x (typename $a (record (field $b (record (field $c u8)))))) ) "Anonymous structured types") (assert_invalid - (witx + (witx (module $x (typename $tag (enum $c)) (typename $a (record (field $b (union)))) - ) + )) "Anonymous structured types") ;; pointers don't count for anonymous indirections (witx - (typename $a (@witx pointer u8))) + (module $x (typename $a (@witx pointer u8)))) (witx - (typename $a (@witx pointer (@witx const_pointer u8)))) + (module $x (typename $a (@witx pointer (@witx const_pointer u8))))) (witx - (typename $a (record (field $b (@witx pointer u8))))) + (module $x (typename $a (record (field $b (@witx pointer u8)))))) diff --git a/tools/witx/tests/witxt/multimodule.witxt b/tools/witx/tests/witxt/multimodule.witxt index bffa1896..059a0ddb 100644 --- a/tools/witx/tests/witxt/multimodule.witxt +++ b/tools/witx/tests/witxt/multimodule.witxt @@ -1,20 +1,20 @@ ;; B uses A, and C uses A. -(witx $b (load "multimodule/type_b.witx")) -(witx $c (load "multimodule/type_c.witx")) +(witx (load "multimodule/type_b.witx")) +(witx (load "multimodule/type_c.witx")) -(witx $reference +(witx (module $reference (typename $a u32) (typename $b (record (field $member_a $a))) (typename $c (record (field $first_a $a) (field $second_a $a))) -) +)) -(assert_eq $reference "a" $b "a") -(assert_eq $reference "a" $c "a") -(assert_eq $reference "b" $b "b") -(assert_eq $reference "c" $c "c") +(assert_eq $reference "a" $type_b "a") +(assert_eq $reference "a" $type_c "a") +(assert_eq $reference "b" $type_b "b") +(assert_eq $reference "c" $type_c "c") (assert_invalid (witx (load "multimodule/redefine_a.witx")) "Redefinition of name `a`") -(witx $c (load "multimodule/use_of_structured.witx")) +(witx (load "multimodule/use_of_structured.witx")) diff --git a/tools/witx/tests/witxt/multimodule/redefine_a.witx b/tools/witx/tests/witxt/multimodule/redefine_a.witx index 76f04ffd..447a8440 100644 --- a/tools/witx/tests/witxt/multimodule/redefine_a.witx +++ b/tools/witx/tests/witxt/multimodule/redefine_a.witx @@ -1,2 +1,4 @@ -(use $a from $type_a) -(typename $a u8) +(module $redefine_a + (use $a from $type_a) + (typename $a u8) +) diff --git a/tools/witx/tests/witxt/multimodule/structured.witx b/tools/witx/tests/witxt/multimodule/structured.witx index 5b9cf5dc..e29d162f 100644 --- a/tools/witx/tests/witxt/multimodule/structured.witx +++ b/tools/witx/tests/witxt/multimodule/structured.witx @@ -1,2 +1,4 @@ -(typename $a u32) -(typename $foo (tuple $a)) +(module $structured + (typename $a u32) + (typename $foo (tuple $a)) +) diff --git a/tools/witx/tests/witxt/multimodule/type_a.witx b/tools/witx/tests/witxt/multimodule/type_a.witx index 5499ff3d..514b6cb8 100644 --- a/tools/witx/tests/witxt/multimodule/type_a.witx +++ b/tools/witx/tests/witxt/multimodule/type_a.witx @@ -1 +1 @@ -(typename $a u32) +(module $type_a (typename $a u32)) diff --git a/tools/witx/tests/witxt/multimodule/type_b.witx b/tools/witx/tests/witxt/multimodule/type_b.witx index ad695706..dc2b3a3c 100644 --- a/tools/witx/tests/witxt/multimodule/type_b.witx +++ b/tools/witx/tests/witxt/multimodule/type_b.witx @@ -1,2 +1,4 @@ -(use $a from $type_a) -(typename $b (record (field $member_a $a))) +(module $type_b + (use $a from $type_a) + (typename $b (record (field $member_a $a))) +) diff --git a/tools/witx/tests/witxt/multimodule/type_c.witx b/tools/witx/tests/witxt/multimodule/type_c.witx index 5b7af399..578524a9 100644 --- a/tools/witx/tests/witxt/multimodule/type_c.witx +++ b/tools/witx/tests/witxt/multimodule/type_c.witx @@ -1,2 +1,4 @@ -(use $a from $type_a) -(typename $c (record (field $first_a $a) (field $second_a $a))) +(module $type_c + (use $a from $type_a) + (typename $c (record (field $first_a $a) (field $second_a $a))) +) diff --git a/tools/witx/tests/witxt/multimodule/use_of_structured.witx b/tools/witx/tests/witxt/multimodule/use_of_structured.witx index 467e8ef3..2eaa90ed 100644 --- a/tools/witx/tests/witxt/multimodule/use_of_structured.witx +++ b/tools/witx/tests/witxt/multimodule/use_of_structured.witx @@ -1 +1,3 @@ -(use $foo from $structured) +(module $use_of_structured + (use $foo from $structured) +) diff --git a/tools/witx/tests/witxt/resources.witxt b/tools/witx/tests/witxt/resources.witxt index fb991903..ee1c3eb6 100644 --- a/tools/witx/tests/witxt/resources.witxt +++ b/tools/witx/tests/witxt/resources.witxt @@ -1,41 +1,41 @@ (assert_invalid - (witx (typename $x (handle $y))) + (witx (module $b (typename $x (handle $y)))) "Unknown name `y`") (assert_invalid - (witx + (witx (module $b (typename $y u32) - (typename $x (handle $y))) + (typename $x (handle $y)))) "Unknown name `y`") (assert_invalid - (witx + (witx (module $b (resource $x) - (resource $x)) + (resource $x))) "Redefinition of name `x`") -(witx +(witx (module $b (resource $x) - (typename $x (handle $x))) + (typename $x (handle $x)))) -(witx $a +(witx (module $a (resource $x) (typename $x (handle $x)) (typename $y (handle $x)) -) +)) (assert_eq $a "x" $a "y") -(witx $a +(witx (module $a (resource $x) (resource $y) (typename $x (handle $x)) (typename $y (handle $y)) -) +)) (assert_ne $a "x" $a "y") -(witx $a (load "resources/multi.witx")) +(witx (load "resources/multi.witx")) -(assert_eq $a "x1" $a "x2") -(assert_ne $a "y1" $a "y2") +(assert_eq $multi "x1" $multi "x2") +(assert_ne $multi "y1" $multi "y2") diff --git a/tools/witx/tests/witxt/resources/multi.witx b/tools/witx/tests/witxt/resources/multi.witx index 105fef36..ae851276 100644 --- a/tools/witx/tests/witxt/resources/multi.witx +++ b/tools/witx/tests/witxt/resources/multi.witx @@ -1,11 +1,13 @@ -(use ($x as $other_x) ($y as $other_y) from $other) +(module $multi + (use ($x as $other_x) ($y as $other_y) from $other) -;; this resource named `y` shouldn't be the same as another resource named `y` -;; defined elsewhere. -(resource $y) + ;; this resource named `y` shouldn't be the same as another resource named `y` + ;; defined elsewhere. + (resource $y) -(typename $x1 (handle $other_x)) -(typename $x2 (handle $other_x)) + (typename $x1 (handle $other_x)) + (typename $x2 (handle $other_x)) -(typename $y1 (handle $other_y)) -(typename $y2 (handle $y)) + (typename $y1 (handle $other_y)) + (typename $y2 (handle $y)) +) diff --git a/tools/witx/tests/witxt/resources/other.witx b/tools/witx/tests/witxt/resources/other.witx index 7de20bc3..2c723e09 100644 --- a/tools/witx/tests/witxt/resources/other.witx +++ b/tools/witx/tests/witxt/resources/other.witx @@ -1,2 +1,4 @@ -(resource $x) -(resource $y) +(module $other + (resource $x) + (resource $y) +) diff --git a/tools/witx/tests/witxt/shorthand.witxt b/tools/witx/tests/witxt/shorthand.witxt index 7379701b..2d6756ab 100644 --- a/tools/witx/tests/witxt/shorthand.witxt +++ b/tools/witx/tests/witxt/shorthand.witxt @@ -1,59 +1,65 @@ -(witx $a - (typename $a bool)) -(witx $b - (typename $a (variant (case $false) (case $true)))) +(witx + (module $a (typename $a bool))) +(witx + (module $b (typename $a (variant (case $false) (case $true))))) (assert_eq $a "a" $b "a") -(witx $a - (typename $a (expected (error)))) -(witx $b - (typename $a (variant (case $ok) (case $err)))) +(witx + (module $a (typename $a (expected (error))))) +(witx + (module $b (typename $a (variant (case $ok) (case $err))))) (assert_eq $a "a" $b "a") -(witx $a - (typename $a (expected (error u32)))) -(witx $b - (typename $a (variant (case $ok) (case $err u32)))) +(witx + (module $a (typename $a (expected (error u32))))) +(witx + (module $b (typename $a (variant (case $ok) (case $err u32))))) (assert_eq $a "a" $b "a") -(witx $a - (typename $a (expected u32 (error)))) -(witx $b - (typename $a (variant (case $ok u32) (case $err)))) +(witx + (module $a (typename $a (expected u32 (error))))) +(witx + (module $b (typename $a (variant (case $ok u32) (case $err))))) (assert_eq $a "a" $b "a") -(witx $a - (typename $a (expected u32 (error u64)))) -(witx $b - (typename $a (variant (case $ok u32) (case $err u64)))) +(witx + (module $a (typename $a (expected u32 (error u64))))) +(witx + (module $b (typename $a (variant (case $ok u32) (case $err u64))))) (assert_eq $a "a" $b "a") -(witx $a - (typename $a (flags $a $b))) -(witx $b - (typename $a (record (field $a bool) (field $b bool)))) +(witx + (module $a (typename $a (flags $a $b)))) +(witx + (module $b (typename $a (record (field $a bool) (field $b bool))))) (assert_eq $a "a" $b "a") -(witx $a - (typename $a (enum $a $b))) -(witx $b - (typename $a (variant (case $a) (case $b)))) +(witx + (module $a (typename $a (enum $a $b)))) +(witx + (module $b (typename $a (variant (case $a) (case $b))))) (assert_eq $a "a" $b "a") -(witx $a - (typename $a string)) -(witx $b - (typename $a (list char))) +(witx + (module $a (typename $a string))) +(witx + (module $b (typename $a (list char)))) (assert_eq $a "a" $b "a") -(witx $a - (typename $a (tuple u32 u64))) -(witx $b - (typename $a (record (field $0 u32) (field $1 u64)))) +(witx + (module $a (typename $a (tuple u32 u64)))) +(witx + (module $b (typename $a (record (field $0 u32) (field $1 u64))))) (assert_eq $a "a" $b "a") -(witx $a - (typename $a (union u32 u64))) -(witx $b - (typename $a (variant (case $0 u32) (case $1 u64)))) +(witx + (module $a (typename $a (union u32 u64)))) +(witx + (module $b (typename $a (variant (case $0 u32) (case $1 u64))))) +(assert_eq $a "a" $b "a") + +(witx + (module $a (typename $a (option u32)))) +(witx + (module $b (typename $a (variant (case $none) (case $some u32))))) (assert_eq $a "a" $b "a") diff --git a/tools/witx/tests/witxt/simple.witxt b/tools/witx/tests/witxt/simple.witxt index af240304..e3f9a915 100644 --- a/tools/witx/tests/witxt/simple.witxt +++ b/tools/witx/tests/witxt/simple.witxt @@ -1,12 +1,16 @@ -(witx) +(witx + (module $a) +) (witx - (typename $x u32) + (module $a (typename $x u32)) ) (assert_invalid (witx - (typename $x u32) - (typename $x u32) + (module $a + (typename $x u32) + (typename $x u32) + ) ) "Redefinition of name `x`") diff --git a/tools/witx/tests/witxt/snapshot_0.witx b/tools/witx/tests/witxt/snapshot_0.witx new file mode 100644 index 00000000..baca3db9 --- /dev/null +++ b/tools/witx/tests/witxt/snapshot_0.witx @@ -0,0 +1 @@ +(witx (load "../../../../phases/old/snapshot_0/witx/wasi_unstable.witx")) diff --git a/tools/witx/tests/witxt/snapshot_preview1.witx b/tools/witx/tests/witxt/snapshot_preview1.witx new file mode 100644 index 00000000..63c2575b --- /dev/null +++ b/tools/witx/tests/witxt/snapshot_preview1.witx @@ -0,0 +1 @@ +(witx (load "../../../../phases/snapshot/witx/wasi_snapshot_preview1.witx")) diff --git a/tools/witx/tests/witxt/union.witxt b/tools/witx/tests/witxt/union.witxt index 58d2ce9b..59b39ff4 100644 --- a/tools/witx/tests/witxt/union.witxt +++ b/tools/witx/tests/witxt/union.witxt @@ -1,87 +1,107 @@ (witx - (typename $u (union u8)) + (module $a (typename $u (union u8))) ) (witx - (typename $tag (enum (@witx tag u8) $c)) - (typename $u (union (@witx tag $tag) u8)) + (module $a + (typename $tag (enum (@witx tag u8) $c)) + (typename $u (union (@witx tag $tag) u8)) + ) ) (witx - (typename $tag (enum $a $b)) - (typename $u (variant (@witx tag $tag) (case $a) (case $b u16))) + (module $a + (typename $tag (enum $a $b)) + (typename $u (variant (@witx tag $tag) (case $a) (case $b u16))) + ) ) (witx - (typename $tag (enum $a $b)) - (typename $u (variant (@witx tag $tag) (case $a) (case $b))) + (module $a + (typename $tag (enum $a $b)) + (typename $u (variant (@witx tag $tag) (case $a) (case $b))) + ) ) (witx - (typename $u - (union - u8 - u16 - u32 - u64 - s8 - s16 - s32 - s64 - f32 - f64 - (@witx usize) - (@witx char8) + (module $a + (typename $u + (union + u8 + u16 + u32 + u64 + s8 + s16 + s32 + s64 + f32 + f64 + (@witx usize) + (@witx char8) + ) ) ) ) (assert_invalid - (witx (typename $u (union (@witx tag $tag) u8 u16))) + (witx (module $a (typename $u (union (@witx tag $tag) u8 u16)))) "Unknown name `tag`" ) (assert_invalid (witx - (typename $tag string) - (typename $u (union (@witx tag $tag) u8 u16)) + (module $a + (typename $tag string) + (typename $u (union (@witx tag $tag) u8 u16)) + ) ) "Wrong kind of name `tag`: expected enum or builtin, got list" ) (assert_invalid (witx - (typename $tag (enum $c)) - (typename $u (variant (@witx tag $tag) (case $b u8))) + (module $a + (typename $tag (enum $c)) + (typename $u (variant (@witx tag $tag) (case $b u8))) + ) ) "Invalid union field `b`: does not correspond to variant in tag `tag`" ) (assert_invalid (witx - (typename $tag (enum $c)) - (typename $u (union (@witx tag $tag) f32 u8)) + (module $a + (typename $tag (enum $c)) + (typename $u (union (@witx tag $tag) f32 u8)) + ) ) "Union expected 1 variants, found 2" ) (assert_invalid (witx - (typename $tag (enum $c $d)) - (typename $u (union (@witx tag $tag) f32)) + (module $a + (typename $tag (enum $c $d)) + (typename $u (union (@witx tag $tag) f32)) + ) ) "Union expected 2 variants, found 1" ) -(witx $d1 - (typename $tag (enum $a $b)) - (typename $u (union (@witx tag $tag) u8 u16)) +(witx + (module $d1 + (typename $tag (enum $a $b)) + (typename $u (union (@witx tag $tag) u8 u16)) + ) ) -(witx $d2 - (typename $tag (enum $a $b)) - (typename $u (variant (@witx tag $tag) (case $b u16) (case $a u8))) +(witx + (module $d2 + (typename $tag (enum $a $b)) + (typename $u (variant (@witx tag $tag) (case $b u16) (case $a u8))) + ) ) ;; These two unions should be represented the same: @@ -89,9 +109,11 @@ (assert_eq $d2 "u" $d1 "u") ;; Tag order doesnt matter for validation, but does for equality -(witx $d3 - (typename $tag (enum $b $a)) - (typename $u (union (@witx tag $tag) u16 u8)) +(witx + (module $d3 + (typename $tag (enum $b $a)) + (typename $u (union (@witx tag $tag) u16 u8)) + ) ) (assert_ne $d3 "u" $d1 "u") diff --git a/tools/witx/tests/witxt/wasi.witxt b/tools/witx/tests/witxt/wasi.witxt index b046a582..11b3a423 100644 --- a/tools/witx/tests/witxt/wasi.witxt +++ b/tools/witx/tests/witxt/wasi.witxt @@ -1,14 +1,10 @@ ;; Ensure that all current witx definitions in this repository load, parse, ;; roundtrip, and are documentable. -(witx (load "../../../../phases/old/snapshot_0/witx/wasi_unstable.witx")) -(witx (load "../../../../phases/snapshot/witx/wasi_snapshot_preview1.witx")) - (witx (load "../../../../phases/ephemeral/witx/wasi_ephemeral_args.witx")) (witx (load "../../../../phases/ephemeral/witx/wasi_ephemeral_clock.witx")) (witx (load "../../../../phases/ephemeral/witx/wasi_ephemeral_environ.witx")) (witx (load "../../../../phases/ephemeral/witx/wasi_ephemeral_fd.witx")) -(witx (load "../../../../phases/ephemeral/witx/wasi_ephemeral_fd.witx")) (witx (load "../../../../phases/ephemeral/witx/wasi_ephemeral_path.witx")) (witx (load "../../../../phases/ephemeral/witx/wasi_ephemeral_poll.witx")) (witx (load "../../../../phases/ephemeral/witx/wasi_ephemeral_proc.witx"))