wasm_wat (wasm v0.1.0)

View Source

The 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

Functions

Parse a text module.

Parse a module that has already been read.

Types

annotated()

-type annotated() ::
          {Height :: non_neg_integer(), instr()} |
          {Height :: non_neg_integer(), Base :: non_neg_integer(), instr()}.

externtype()

-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()}}.

funcidx()

-type funcidx() :: non_neg_integer().

heaptype()

-type heaptype() ::
          func | extern | exn | any | eq | i31 | struct | array | nofunc | noextern | noexn | none |
          {type, typeidx()}.

instr()

-type instr() :: atom() | tuple().

memidx()

-type memidx() :: non_neg_integer().

mut()

-type mut() :: const | var.

numtype()

-type numtype() :: i32 | i64 | f32 | f64.

reftype()

-type reftype() :: {ref, null | nonull, heaptype()}.

tableidx()

-type tableidx() :: non_neg_integer().

typeidx()

-type typeidx() :: non_neg_integer().

valtype()

-type valtype() :: numtype() | vectype() | reftype().

vectype()

-type vectype() :: v128.

Functions

module(Source)

-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.

module_form(Form)

-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.