Skip to content

Implement conversion of test scripts to JavaScript #337

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 41 additions & 12 deletions ml-proto/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ Either way, in order to run the test suite you'll need to have Python installed.

## Synopsis

#### Running Modules or Scripts

You can call the executable with

```
Expand All @@ -73,6 +75,8 @@ By default, the interpreter validates all modules.
The `-u` option selects "unchecked mode", which skips validation and runs code as is.
Runtime type errors will be captured and reported appropriately.

#### Converting Modules or Scripts

A file prefixed by `-o` is taken to be an output file. Depending on its extension, this will write out the preceding module definition in either S-expression or binary format. This option can be used to convert between the two in both directions, e.g.:

```
Expand All @@ -84,12 +88,28 @@ In the second case, the produced script contains exactly one module definition.
The `-d` option selects "dry mode" and ensures that the input module is not run, even if it has a start section.
In addition, the `-u` option for "unchecked mode" can be used to convert even modules that do not validate.

The interpreter can also convert entire test scripts:

```
wasm -d script.wast -o script.bin.wast
wasm -d script.wast -o script2.wast
wasm -d script.wast -o script.js
```

The first creates a new test scripts where all embedded modules are converted to binary, the second one where all are converted to textual.

The last invocation produces an equivalent, self-contained JavaScript test file.

#### Command Line Expressions

Finally, the option `-e` allows to provide arbitrary script commands directly on the command line. For example:

```
wasm module.wasm -e '(invoke "foo")'
```

#### Interactive Mode

If neither a file nor any of the previous options is given, you'll land in the REPL and can enter script commands interactively. You can also get into the REPL by explicitly passing `-` as a file name. You can do that in combination to giving a module file, so that you can then invoke its exports interactively, e.g.:

```
Expand Down Expand Up @@ -240,31 +260,40 @@ script: <cmd>*

cmd:
<module> ;; define, validate, and initialize module
<action> ;; perform action and print results
( register <string> <name>? ) ;; register module for imports
module with given failure string
<action> ;; perform action and print results
<assertion> ;; assert result of an action
<meta> ;; meta command

action:
( invoke <name>? <string> <expr>* ) ;; invoke function export
( get <name>? <string> ) ;; get global export

assertion:
( assert_return <action> <expr>* ) ;; assert action has expected results
( assert_return_nan <action> ) ;; assert action results in NaN
( assert_trap <action> <failure> ) ;; assert action traps with given failure string
( assert_malformed <module> <failure> ) ;; assert module cannot be decoded with given failure string
( assert_invalid <module> <failure> ) ;; assert module is invalid with given failure string
( assert_unlinkable <module> <failure> ) ;; assert module fails to link module with given failure string
( input <string> ) ;; read script or module from file
( output <name>? <string>? ) ;; output module to stout or file
( assert_unlinkable <module> <failure> ) ;; assert module fails to link

action:
( invoke <name>? <string> <expr>* ) ;; invoke function export
( get <name>? <string> ) ;; get global export
meta:
( script <name>? <script> ) ;; name a subscript
( input <name>? <string> ) ;; read script or module from file
( output <name>? <string>? ) ;; output module to stout or file
```

Commands are executed in sequence. Commands taking an optional module name refer to the most recently defined module if no name is given. They are only possible after a module has been defined.

After a module is _registered_ under a string name it is available for importing in other modules.

The input and output commands determine the requested file format from the file name extension. They can handle both `.wast` and `.wasm` files. In the case of input, a `.wast` script will be recursively executed.

Again, this is only a meta-level for testing, and not a part of the language proper.
There are also a number of meta commands.
The `script` command is a simple mechanism to name sub-scripts themselves. This is mainly useful for converting scripts with the `output` command. Commands inside a `script` will be executed normally, but nested meta are expanded in place (`input`, recursively) or elided (`output`) in the named script.

The interpreter also supports a "dry" mode (flag `-d`), in which modules are only validated. In this mode, `invoke` commands are ignored (and not needed).
The `input` and `output` meta commands determine the requested file format from the file name extension. They can handle both `.wast` and `.wasm` files. In the case of input, a `.wast` script will be recursively executed. Output additionally handles `.js` as a target, which will convert the referenced script to an equivalent, self-contained JavaScript runner. It also recognises `.bin.wast` specially, which creates a script where module definitions are in binary.

The interpreter supports a "dry" mode (flag `-d`), in which modules are only validated. In this mode, all actions and assertions are ignored.
It also supports an "unchecked" mode (flag `-u`), in which module definitions are not validated before use.

## Abstract Syntax

Expand Down
109 changes: 92 additions & 17 deletions ml-proto/host/arrange.ml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
open Source
open Ast
open Script
open Values
open Types
open Sexpr
Expand All @@ -11,21 +12,25 @@ let int = string_of_int
let int32 = Int32.to_string
let int64 = Int64.to_string

let string s =
let buf = Buffer.create (String.length s + 2) in
let add_hex_char buf c = Printf.bprintf buf "\\%02x" (Char.code c)
let add_char buf c =
if c < '\x20' || c >= '\x7f' then
add_hex_char buf c
else begin
if c = '\"' || c = '\\' then Buffer.add_char buf '\\';
Buffer.add_char buf c
end

let string_with add_char s =
let buf = Buffer.create (3 * String.length s + 2) in
Buffer.add_char buf '\"';
for i = 0 to String.length s - 1 do
let c = s.[i] in
if c = '\"' then
Buffer.add_string buf "\\\""
else if '\x20' <= c && c < '\x7f' then
Buffer.add_char buf c
else
Buffer.add_string buf (Printf.sprintf "\\%02x" (Char.code c));
done;
String.iter (add_char buf) s;
Buffer.add_char buf '\"';
Buffer.contents buf

let bytes = string_with add_hex_char
let string = string_with add_char

let list_of_opt = function None -> [] | Some x -> [x]

let list f xs = List.map f xs
Expand All @@ -35,9 +40,9 @@ let opt f xo = list f (list_of_opt xo)
let tab head f xs = if xs = [] then [] else [Node (head, list f xs)]
let atom f x = Atom (f x)

let break_string s =
let ss = Lib.String.breakup s (!Flags.width / 2) in
list (atom string) ss
let break_bytes s =
let ss = Lib.String.breakup s (!Flags.width / 3 - 4) in
list (atom bytes) ss


(* Types *)
Expand Down Expand Up @@ -277,7 +282,7 @@ let elems seg =
segment "elem" (list (atom var)) seg

let data seg =
segment "data" break_string seg
segment "data" break_bytes seg


(* Modules *)
Expand Down Expand Up @@ -319,6 +324,10 @@ let global off i g =

(* Modules *)

let var_opt = function
| None -> ""
| Some x -> " " ^ x.it

let is_func_import im =
match im.it.ikind.it with FuncImport _ -> true | _ -> false
let is_table_import im =
Expand All @@ -328,12 +337,12 @@ let is_memory_import im =
let is_global_import im =
match im.it.ikind.it with GlobalImport _ -> true | _ -> false

let module_ m =
let module_with_var_opt x_opt m =
let func_imports = List.filter is_func_import m.it.imports in
let table_imports = List.filter is_table_import m.it.imports in
let memory_imports = List.filter is_memory_import m.it.imports in
let global_imports = List.filter is_global_import m.it.imports in
Node ("module",
Node ("module" ^ var_opt x_opt,
listi typedef m.it.types @
listi import table_imports @
listi import memory_imports @
Expand All @@ -349,3 +358,69 @@ let module_ m =
list data m.it.data
)

let binary_module_with_var_opt x_opt bs =
Node ("module" ^ var_opt x_opt, break_bytes bs)

let module_ = module_with_var_opt None
let binary_module = binary_module_with_var_opt None


(* Scripts *)

let literal lit =
match lit.it with
| Values.I32 i -> Node ("i32.const " ^ I32.to_string i, [])
| Values.I64 i -> Node ("i64.const " ^ I64.to_string i, [])
| Values.F32 z -> Node ("f32.const " ^ F32.to_string z, [])
| Values.F64 z -> Node ("f64.const " ^ F64.to_string z, [])

let definition mode x_opt def =
match mode, def.it with
| `Textual, _ | `Original, Textual _ ->
let m =
match def.it with
| Textual m -> m
| Binary (_, bs) -> Decode.decode "" bs
in module_with_var_opt x_opt m
| `Binary, _ | `Original, Binary _ ->
let bs =
match def.it with
| Textual m -> Encode.encode m
| Binary (_, bs) -> bs
in binary_module_with_var_opt x_opt bs

let access x_opt name =
String.concat " " [var_opt x_opt; string name]

let action act =
match act.it with
| Invoke (x_opt, name, lits) ->
Node ("invoke" ^ access x_opt name, List.map literal lits)
| Get (x_opt, name) ->
Node ("get" ^ access x_opt name, [])

let assertion mode ass =
match ass.it with
| AssertMalformed (def, re) ->
Node ("assert_malformed", [definition `Original None def; Atom (string re)])
| AssertInvalid (def, re) ->
Node ("assert_invalid", [definition mode None def; Atom (string re)])
| AssertUnlinkable (def, re) ->
Node ("assert_unlinkable", [definition mode None def; Atom (string re)])
| AssertReturn (act, lits) ->
Node ("assert_return", action act :: List.map literal lits)
| AssertReturnNaN act ->
Node ("assert_return_nan", [action act])
| AssertTrap (act, re) ->
Node ("assert_trap", [action act; Atom (string re)])

let command mode cmd =
match cmd.it with
| Module (x_opt, def) -> definition mode x_opt def
| Register (name, x_opt) ->
Node ("register " ^ string name ^ var_opt x_opt, [])
| Action act -> action act
| Assertion ass -> assertion mode ass
| Meta _ -> assert false

let script mode scr = List.map (command mode) scr
2 changes: 1 addition & 1 deletion ml-proto/host/arrange.mli
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ val func_type : Types.func_type -> sexpr

val instr : Ast.instr -> sexpr
val module_ : Ast.module_ -> sexpr

val script : [`Textual | `Binary] -> Script.script -> sexpr list
3 changes: 0 additions & 3 deletions ml-proto/host/flags.ml
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
let name = "wasm"
let version = "0.2"

let interactive = ref false
let trace = ref false
let unchecked = ref false
Expand Down
Loading