wasm_wat (wasm v0.1.0)
View SourceThe text format, parsed into the same #module{} the binary decoder produces.
Hand a .wat file to wasm:load/1 and you land here. Everything downstream is
unchanged: a text module is validated, instantiated and
run by exactly the code a binary one is. The text format is a front end, not a
second runtime, and the way to keep it honest is to make it produce the same
value rather than a parallel one.
Three passes
Index spaces are collected first and bodies parsed last, because the format allows forward references: a function may call one declared later in the file. A single pass would have to patch indices afterwards, which is the same passes with the bookkeeping hidden.
Between them sits a pass for implicit types. A function written
(func (param i32)) names no type, and the specification says it means the
lowest-numbered existing type that matches, or a new one appended to the module
if none does. So the type section is not known until every type use in the file
has been read, including the ones inside function bodies at call_indirect and
at any block that takes parameters. That pass walks for type uses and nothing
else, and the pass that follows only ever finds a type: appending one there
would be an index nobody else agrees with, so it is an error instead.
Abbreviations
Most of the work is not instructions, it is the shorthand. (func $f (export "f") (param i32)) is three declarations wearing one set of parentheses: a type if none matches, a function, and an export. The specification calls these abbreviations and they are not optional; real files are written almost entirely in them.
Summary
Types
-type annotated() :: {Height :: non_neg_integer(), instr()} | {Height :: non_neg_integer(), Base :: non_neg_integer(), instr()}.
-type externtype() :: {func, typeidx()} | {table, #tabletype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}, elemtype :: reftype(), init :: undefined | [instr()]}} | {mem, #memtype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}}} | {global, #globaltype{valtype :: valtype(), mut :: mut()}} | {tag, #tagtype{type :: typeidx()}}.
-type funcidx() :: non_neg_integer().
-type heaptype() :: func | extern | exn | any | eq | i31 | struct | array | nofunc | noextern | noexn | none | {type, typeidx()}.
-type memidx() :: non_neg_integer().
-type mut() :: const | var.
-type numtype() :: i32 | i64 | f32 | f64.
-type reftype() :: {ref, null | nonull, heaptype()}.
-type tableidx() :: non_neg_integer().
-type typeidx() :: non_neg_integer().
-type vectype() :: v128.
Functions
-spec module(binary()) -> {ok, #module{identity :: undefined | {sha256, binary()} | reference(), types :: [#subtype{final :: boolean(), supers :: [typeidx()], body :: #functype{params :: [valtype()], results :: [valtype()]} | #structtype{fields :: [#fieldtype{type :: valtype() | i8 | i16, mut :: mut()}]} | #arraytype{field :: #fieldtype{type :: valtype() | i8 | i16, mut :: mut()}}}], rec_groups :: [{non_neg_integer(), non_neg_integer()}], imports :: [#import{module :: binary(), name :: binary(), desc :: externtype()}], funcs :: [#func{type :: typeidx(), locals :: [valtype()], body :: [instr()] | {validated, [annotated()]}}], tables :: [#tabletype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}, elemtype :: reftype(), init :: undefined | [instr()]}], mems :: [#memtype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}}], tags :: [#tagtype{type :: typeidx()}], globals :: [#global{type :: #globaltype{valtype :: valtype(), mut :: mut()}, init :: [instr()]}], exports :: [#export{name :: binary(), desc :: {func | table | mem | global | tag, non_neg_integer()}}], start :: undefined | funcidx(), elems :: [#elem{type :: reftype(), init :: [[instr()]], mode :: passive | declarative | {active, tableidx(), [instr()]}}], datas :: [#data{init :: binary(), mode :: passive | {active, memidx(), [instr()]}}], data_count :: undefined | non_neg_integer(), customs :: [{binary(), binary()}]}} | {error, wasm_error:error()}.
Parse a text module.
-spec module_form(wasm_wat_sexp:sexp()) -> {ok, #module{identity :: undefined | {sha256, binary()} | reference(), types :: [#subtype{final :: boolean(), supers :: [typeidx()], body :: #functype{params :: [valtype()], results :: [valtype()]} | #structtype{fields :: [#fieldtype{type :: valtype() | i8 | i16, mut :: mut()}]} | #arraytype{field :: #fieldtype{type :: valtype() | i8 | i16, mut :: mut()}}}], rec_groups :: [{non_neg_integer(), non_neg_integer()}], imports :: [#import{module :: binary(), name :: binary(), desc :: externtype()}], funcs :: [#func{type :: typeidx(), locals :: [valtype()], body :: [instr()] | {validated, [annotated()]}}], tables :: [#tabletype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}, elemtype :: reftype(), init :: undefined | [instr()]}], mems :: [#memtype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}}], tags :: [#tagtype{type :: typeidx()}], globals :: [#global{type :: #globaltype{valtype :: valtype(), mut :: mut()}, init :: [instr()]}], exports :: [#export{name :: binary(), desc :: {func | table | mem | global | tag, non_neg_integer()}}], start :: undefined | funcidx(), elems :: [#elem{type :: reftype(), init :: [[instr()]], mode :: passive | declarative | {active, tableidx(), [instr()]}}], datas :: [#data{init :: binary(), mode :: passive | {active, memidx(), [instr()]}}], data_count :: undefined | non_neg_integer(), customs :: [{binary(), binary()}]}} | {error, wasm_error:error()}.
Parse a module that has already been read.
For .wast scripts, whose commands are read once as a whole and whose modules
are parsed one at a time afterwards. Reading the file twice would mean a script
with one malformed module in an assert_malformed could not be read at all.