wasm_instance (wasm v0.1.0)

View Source

Instantiation: turning a validated module into something executable.

You get here through wasm:instantiate/3. Read this when you want to know what instantiating costs, or what a large module defers.

It builds the two halves of an instance. The immutable half (#inst{}) holds everything derived from the module and is shared by every call. The mutable half (#mut{}) holds globals, tables, memories and dropped-segment flags.

The mutable half is threaded functionally through execution and written back once per call, rather than living in a shared table. A tuple update is a few nanoseconds; an ETS write is around forty, and global.get is on the hot path. When instances become supervised processes this record becomes the gen_server's state unchanged, which is why it is already separated.

Building the execution IR happens here too. Blocks carry resolved arities instead of type indices, so the interpreter never consults the type section, and the AST's nesting is kept rather than flattened; see wasm_exec for why continuation lists beat a program counter on this VM.

Above 256 functions a module is lowered progressively: a body is turned into IR the first time it is called, and cached per process, so a large module costs you what you use of it rather than what it contains.

Summary

Functions

Claim the right to ask for this instance's module to be compiled.

The execution IR of a function, building it the first time it is asked for.

The unfused IR of a function, for the compiler.

The passive element segments in a root_view/1.

The functions this process has actually called, by index.

The declared type of an exported function.

Auxiliary per-instance state for host interfaces.

The declared type of a global by index.

The instance's object store, or undefined if it can never allocate.

Which code slot this instance's compiled module lives in, or undefined.

The instance a funcref names, if this process can reach it.

The instance's current mutable state.

The mutable state behind a root_view/1, without rebuilding an instance.

Register something to run when this instance is destroyed.

Make a call's in-flight state visible to nested calls in this process.

Whatever is published for this instance in this process.

Release an instance's state table and this process's cache of it.

Give an ask back, for one that came to nothing.

Note this instance, so a funcref naming it can be resolved here later.

The four fields a garbage collection root scan reads from an instance.

Run the registered cleanups. Called by wasm:destroy/1, once.

Record the slot, once compilation has published into it.

A tag's identity, in the form another module can import it.

Undo publish/2, restoring whatever an enclosing call had published.

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

ask_compile/1

-spec ask_compile(#inst{id :: reference(),
                        ckpt :: reference(),
                        entry_key :: undefined | reference(),
                        types :: tuple(),
                        funcs :: tuple(),
                        exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                        elems :: tuple(),
                        datas :: tuple(),
                        globaltypes :: tuple(),
                        tags :: tuple(),
                        canon :: tuple(),
                        fields :: tuple(),
                        kinds :: tuple(),
                        supers :: tuple(),
                        heap :: undefined | wasm_heap:heap(),
                        identity :: undefined | {sha256, binary()} | reference(),
                        store :: term(),
                        version :: term(),
                        limits :: map(),
                        ctx :: term()}) ->
                     boolean().

Claim the right to ask for this instance's module to be compiled.

Answers true to one caller and then to nobody else for ?ASK_RETRY_SECONDS. Compiling a real module takes tens of seconds and happens off the calling process, so without this every hot call during those seconds would ask again, and each ask copies the instance to the process that will do the work.

It records a time rather than setting a flag, and that is the whole point. The process doing the work can be killed untrappably, which is what shutdown does to it, and no after covers that. A flag would stay set and this instance would never be compiled again for the life of the node, with nothing to see. A time expires, so every way of losing a compiler costs a minute rather than everything.

body_of/2

-spec body_of(#fn{nparams :: non_neg_integer(),
                  nresults :: non_neg_integer(),
                  defaults :: [term()],
                  body :: [tuple() | atom()] | {lazy, [term()]},
                  raw :: [term()],
                  idx :: non_neg_integer(),
                  type :: term(),
                  frame :: term()},
              #inst{id :: reference(),
                    ckpt :: reference(),
                    entry_key :: undefined | reference(),
                    types :: tuple(),
                    funcs :: tuple(),
                    exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                    elems :: tuple(),
                    datas :: tuple(),
                    globaltypes :: tuple(),
                    tags :: tuple(),
                    canon :: tuple(),
                    fields :: tuple(),
                    kinds :: tuple(),
                    supers :: tuple(),
                    heap :: undefined | wasm_heap:heap(),
                    identity :: undefined | {sha256, binary()} | reference(),
                    store :: term(),
                    version :: term(),
                    limits :: map(),
                    ctx :: term()}) ->
                 [term()].

The execution IR of a function, building it the first time it is asked for.

Cached in this process's dictionary, which is where it has to be: a dictionary read is 9 ns and copies nothing, where reading the IR out of a table would copy the whole function body on every call.

code_slot/1

-spec code_slot(#inst{id :: reference(),
                      ckpt :: reference(),
                      entry_key :: undefined | reference(),
                      types :: tuple(),
                      funcs :: tuple(),
                      exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                      elems :: tuple(),
                      datas :: tuple(),
                      globaltypes :: tuple(),
                      tags :: tuple(),
                      canon :: tuple(),
                      fields :: tuple(),
                      kinds :: tuple(),
                      supers :: tuple(),
                      heap :: undefined | wasm_heap:heap(),
                      identity :: undefined | {sha256, binary()} | reference(),
                      store :: term(),
                      version :: term(),
                      limits :: map(),
                      ctx :: term()}) ->
                   undefined | pos_integer().

compiler_ir/2

-spec compiler_ir(#fn{nparams :: non_neg_integer(),
                      nresults :: non_neg_integer(),
                      defaults :: [term()],
                      body :: [tuple() | atom()] | {lazy, [term()]},
                      raw :: [term()],
                      idx :: non_neg_integer(),
                      type :: term(),
                      frame :: term()},
                  #inst{id :: reference(),
                        ckpt :: reference(),
                        entry_key :: undefined | reference(),
                        types :: tuple(),
                        funcs :: tuple(),
                        exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                        elems :: tuple(),
                        datas :: tuple(),
                        globaltypes :: tuple(),
                        tags :: tuple(),
                        canon :: tuple(),
                        fields :: tuple(),
                        kinds :: tuple(),
                        supers :: tuple(),
                        heap :: undefined | wasm_heap:heap(),
                        identity :: undefined | {sha256, binary()} | reference(),
                        store :: term(),
                        version :: term(),
                        limits :: map(),
                        ctx :: term()}) ->
                     [term()].

The unfused IR of a function, for the compiler.

Fusion exists to save the interpreter a dispatch and there is no dispatch here to save, so a superinstruction would only mean a second spelling of a sequence the generator already handles. This ignores the instance's fuse option, which defaults to on, and reads #fn.raw rather than #fn.body so that a module small enough to have been lowered up front still has a source.

Deliberately not cached. It is an intermediate the generator walks once per function, and caching it would hold a module's worth of IR for nothing.

default_value/1

elems_of/1

-spec elems_of(term()) -> tuple().

The passive element segments in a root_view/1.

executed/1

-spec executed(#inst{id :: reference(),
                     ckpt :: reference(),
                     entry_key :: undefined | reference(),
                     types :: tuple(),
                     funcs :: tuple(),
                     exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                     elems :: tuple(),
                     datas :: tuple(),
                     globaltypes :: tuple(),
                     tags :: tuple(),
                     canon :: tuple(),
                     fields :: tuple(),
                     kinds :: tuple(),
                     supers :: tuple(),
                     heap :: undefined | wasm_heap:heap(),
                     identity :: undefined | {sha256, binary()} | reference(),
                     store :: term(),
                     version :: term(),
                     limits :: map(),
                     ctx :: term()}) ->
                  [non_neg_integer()].

The functions this process has actually called, by index.

A body is lowered on first call and the indices are recorded so that releasing the instance erases exactly those. That record is also the cheapest possible answer to "which functions does this workload run": it is kept for a different reason already, so asking it costs the call path nothing, where counting calls per function would cost it on every call.

Empty for a module small enough that lowering was not deferred, which records nothing because there was nothing to record. Read [] as "no information" rather than as "nothing ran".

export_kind/2

-spec export_kind(#inst{id :: reference(),
                        ckpt :: reference(),
                        entry_key :: undefined | reference(),
                        types :: tuple(),
                        funcs :: tuple(),
                        exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                        elems :: tuple(),
                        datas :: tuple(),
                        globaltypes :: tuple(),
                        tags :: tuple(),
                        canon :: tuple(),
                        fields :: tuple(),
                        kinds :: tuple(),
                        supers :: tuple(),
                        heap :: undefined | wasm_heap:heap(),
                        identity :: undefined | {sha256, binary()} | reference(),
                        store :: term(),
                        version :: term(),
                        limits :: map(),
                        ctx :: term()},
                  binary()) ->
                     {ok, term()} | error.

exports/1

-spec exports(#inst{id :: reference(),
                    ckpt :: reference(),
                    entry_key :: undefined | reference(),
                    types :: tuple(),
                    funcs :: tuple(),
                    exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                    elems :: tuple(),
                    datas :: tuple(),
                    globaltypes :: tuple(),
                    tags :: tuple(),
                    canon :: tuple(),
                    fields :: tuple(),
                    kinds :: tuple(),
                    supers :: tuple(),
                    heap :: undefined | wasm_heap:heap(),
                    identity :: undefined | {sha256, binary()} | reference(),
                    store :: term(),
                    version :: term(),
                    limits :: map(),
                    ctx :: term()}) ->
                 #{binary() => term()}.

func_type/2

-spec func_type(#inst{id :: reference(),
                      ckpt :: reference(),
                      entry_key :: undefined | reference(),
                      types :: tuple(),
                      funcs :: tuple(),
                      exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                      elems :: tuple(),
                      datas :: tuple(),
                      globaltypes :: tuple(),
                      tags :: tuple(),
                      canon :: tuple(),
                      fields :: tuple(),
                      kinds :: tuple(),
                      supers :: tuple(),
                      heap :: undefined | wasm_heap:heap(),
                      identity :: undefined | {sha256, binary()} | reference(),
                      store :: term(),
                      version :: term(),
                      limits :: map(),
                      ctx :: term()},
                binary()) ->
                   term() | undefined.

The declared type of an exported function.

get_extra/2

-spec get_extra(#inst{id :: reference(),
                      ckpt :: reference(),
                      entry_key :: undefined | reference(),
                      types :: tuple(),
                      funcs :: tuple(),
                      exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                      elems :: tuple(),
                      datas :: tuple(),
                      globaltypes :: tuple(),
                      tags :: tuple(),
                      canon :: tuple(),
                      fields :: tuple(),
                      kinds :: tuple(),
                      supers :: tuple(),
                      heap :: undefined | wasm_heap:heap(),
                      identity :: undefined | {sha256, binary()} | reference(),
                      store :: term(),
                      version :: term(),
                      limits :: map(),
                      ctx :: term()},
                atom()) ->
                   {ok, term()} | error.

Auxiliary per-instance state for host interfaces.

WASI needs somewhere to keep its file descriptor table that shares the instance's lifetime. Putting it here rather than in a table of its own means it is created and reclaimed with the instance, with no separate ownership to get wrong.

global_type/2

-spec global_type(#inst{id :: reference(),
                        ckpt :: reference(),
                        entry_key :: undefined | reference(),
                        types :: tuple(),
                        funcs :: tuple(),
                        exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                        elems :: tuple(),
                        datas :: tuple(),
                        globaltypes :: tuple(),
                        tags :: tuple(),
                        canon :: tuple(),
                        fields :: tuple(),
                        kinds :: tuple(),
                        supers :: tuple(),
                        heap :: undefined | wasm_heap:heap(),
                        identity :: undefined | {sha256, binary()} | reference(),
                        store :: term(),
                        version :: term(),
                        limits :: map(),
                        ctx :: term()},
                  non_neg_integer()) ->
                     #globaltype{valtype :: valtype(), mut :: mut()}.

The declared type of a global by index.

heap/1

-spec heap(#inst{id :: reference(),
                 ckpt :: reference(),
                 entry_key :: undefined | reference(),
                 types :: tuple(),
                 funcs :: tuple(),
                 exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                 elems :: tuple(),
                 datas :: tuple(),
                 globaltypes :: tuple(),
                 tags :: tuple(),
                 canon :: tuple(),
                 fields :: tuple(),
                 kinds :: tuple(),
                 supers :: tuple(),
                 heap :: undefined | wasm_heap:heap(),
                 identity :: undefined | {sha256, binary()} | reference(),
                 store :: term(),
                 version :: term(),
                 limits :: map(),
                 ctx :: term()}) ->
              undefined | wasm_heap:heap().

The instance's object store, or undefined if it can never allocate.

identity/1

-spec identity(#inst{id :: reference(),
                     ckpt :: reference(),
                     entry_key :: undefined | reference(),
                     types :: tuple(),
                     funcs :: tuple(),
                     exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                     elems :: tuple(),
                     datas :: tuple(),
                     globaltypes :: tuple(),
                     tags :: tuple(),
                     canon :: tuple(),
                     fields :: tuple(),
                     kinds :: tuple(),
                     supers :: tuple(),
                     heap :: undefined | wasm_heap:heap(),
                     identity :: undefined | {sha256, binary()} | reference(),
                     store :: term(),
                     version :: term(),
                     limits :: map(),
                     ctx :: term()}) ->
                  undefined | {sha256, binary()} | reference().

Which code slot this instance's compiled module lives in, or undefined.

Read on the outermost call, so it is an atomics:get/2 and not a lookup. Zero means the module has not been compiled, which is every instance until a hot call compiles one and every instance of a module the compiler refused.

lookup(Id)

-spec lookup(term()) ->
                {ok,
                 #inst{id :: reference(),
                       ckpt :: reference(),
                       entry_key :: undefined | reference(),
                       types :: tuple(),
                       funcs :: tuple(),
                       exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                       elems :: tuple(),
                       datas :: tuple(),
                       globaltypes :: tuple(),
                       tags :: tuple(),
                       canon :: tuple(),
                       fields :: tuple(),
                       kinds :: tuple(),
                       supers :: tuple(),
                       heap :: undefined | wasm_heap:heap(),
                       identity :: undefined | {sha256, binary()} | reference(),
                       store :: term(),
                       version :: term(),
                       limits :: map(),
                       ctx :: term()}} |
                error.

The instance a funcref names, if this process can reach it.

error means a reference arrived from an instance this process has never seen or has destroyed. That is the same situation foreign_reference already describes for objects, and the caller reports it the same way rather than guessing at an instance.

memory(Inst, Idx)

-spec memory(#inst{id :: reference(),
                   ckpt :: reference(),
                   entry_key :: undefined | reference(),
                   types :: tuple(),
                   funcs :: tuple(),
                   exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                   elems :: tuple(),
                   datas :: tuple(),
                   globaltypes :: tuple(),
                   tags :: tuple(),
                   canon :: tuple(),
                   fields :: tuple(),
                   kinds :: tuple(),
                   supers :: tuple(),
                   heap :: undefined | wasm_heap:heap(),
                   identity :: undefined | {sha256, binary()} | reference(),
                   store :: term(),
                   version :: term(),
                   limits :: map(),
                   ctx :: term()},
             non_neg_integer()) ->
                wasm_memory:mem().

mut/1

-spec mut(#inst{id :: reference(),
                ckpt :: reference(),
                entry_key :: undefined | reference(),
                types :: tuple(),
                funcs :: tuple(),
                exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                elems :: tuple(),
                datas :: tuple(),
                globaltypes :: tuple(),
                tags :: tuple(),
                canon :: tuple(),
                fields :: tuple(),
                kinds :: tuple(),
                supers :: tuple(),
                heap :: undefined | wasm_heap:heap(),
                identity :: undefined | {sha256, binary()} | reference(),
                store :: term(),
                version :: term(),
                limits :: map(),
                ctx :: term()}) ->
             #mut{globals :: tuple(),
                  tables :: tuple(),
                  mems :: tuple(),
                  dropped_elems :: map(),
                  dropped_datas :: map()}.

The instance's current mutable state.

Reading it out of ETS copies the whole #mut{} term, which measured 105 ns against 30 ns for the same lookup returning an atom: the cost is the copy, not the table. On a short call that was more than half the total.

So each process caches the last state it saw, alongside the version counter it was current at. A cache hit is an atomics:get plus a process dictionary lookup, neither of which copies anything. The counter is what keeps this honest across processes: a write by anyone bumps it, and every other process's cache is invalidated the next time it looks.

mut_of/1

-spec mut_of(term()) ->
                #mut{globals :: tuple(),
                     tables :: tuple(),
                     mems :: tuple(),
                     dropped_elems :: map(),
                     dropped_datas :: map()}.

The mutable state behind a root_view/1, without rebuilding an instance.

new(M, Imports)

-spec new(#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()}]},
          map()) ->
             {ok,
              #inst{id :: reference(),
                    ckpt :: reference(),
                    entry_key :: undefined | reference(),
                    types :: tuple(),
                    funcs :: tuple(),
                    exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                    elems :: tuple(),
                    datas :: tuple(),
                    globaltypes :: tuple(),
                    tags :: tuple(),
                    canon :: tuple(),
                    fields :: tuple(),
                    kinds :: tuple(),
                    supers :: tuple(),
                    heap :: undefined | wasm_heap:heap(),
                    identity :: undefined | {sha256, binary()} | reference(),
                    store :: term(),
                    version :: term(),
                    limits :: map(),
                    ctx :: term()}} |
             {error, wasm_error:error()}.

new(M, Imports, Opts)

-spec new(#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()}]},
          map(),
          map()) ->
             {ok,
              #inst{id :: reference(),
                    ckpt :: reference(),
                    entry_key :: undefined | reference(),
                    types :: tuple(),
                    funcs :: tuple(),
                    exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                    elems :: tuple(),
                    datas :: tuple(),
                    globaltypes :: tuple(),
                    tags :: tuple(),
                    canon :: tuple(),
                    fields :: tuple(),
                    kinds :: tuple(),
                    supers :: tuple(),
                    heap :: undefined | wasm_heap:heap(),
                    identity :: undefined | {sha256, binary()} | reference(),
                    store :: term(),
                    version :: term(),
                    limits :: map(),
                    ctx :: term()}} |
             {error, wasm_error:error()}.

on_destroy(Inst, Fun)

-spec on_destroy(#inst{id :: reference(),
                       ckpt :: reference(),
                       entry_key :: undefined | reference(),
                       types :: tuple(),
                       funcs :: tuple(),
                       exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                       elems :: tuple(),
                       datas :: tuple(),
                       globaltypes :: tuple(),
                       tags :: tuple(),
                       canon :: tuple(),
                       fields :: tuple(),
                       kinds :: tuple(),
                       supers :: tuple(),
                       heap :: undefined | wasm_heap:heap(),
                       identity :: undefined | {sha256, binary()} | reference(),
                       store :: term(),
                       version :: term(),
                       limits :: map(),
                       ctx :: term()},
                 fun((#inst{id :: reference(),
                            ckpt :: reference(),
                            entry_key :: undefined | reference(),
                            types :: tuple(),
                            funcs :: tuple(),
                            exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                            elems :: tuple(),
                            datas :: tuple(),
                            globaltypes :: tuple(),
                            tags :: tuple(),
                            canon :: tuple(),
                            fields :: tuple(),
                            kinds :: tuple(),
                            supers :: tuple(),
                            heap :: undefined | wasm_heap:heap(),
                            identity :: undefined | {sha256, binary()} | reference(),
                            store :: term(),
                            version :: term(),
                            limits :: map(),
                            ctx :: term()}) ->
                         any())) ->
                    ok.

Register something to run when this instance is destroyed.

For resources the runtime hands out but does not own: a WASI file handle or socket is closed by whoever opened it, and dropping the reference is not the same as closing it. Without this, an instance destroyed while the guest still held a socket left that socket open until the owning process exited, which in a worker resetting per request is a descriptor leaked per request.

Nothing here knows what WASI is; wasi_preview1 registers its own.

publish/2

-spec publish(#inst{id :: reference(),
                    ckpt :: reference(),
                    entry_key :: undefined | reference(),
                    types :: tuple(),
                    funcs :: tuple(),
                    exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                    elems :: tuple(),
                    datas :: tuple(),
                    globaltypes :: tuple(),
                    tags :: tuple(),
                    canon :: tuple(),
                    fields :: tuple(),
                    kinds :: tuple(),
                    supers :: tuple(),
                    heap :: undefined | wasm_heap:heap(),
                    identity :: undefined | {sha256, binary()} | reference(),
                    store :: term(),
                    version :: term(),
                    limits :: map(),
                    ctx :: term()},
              #mut{globals :: tuple(),
                   tables :: tuple(),
                   mems :: tuple(),
                   dropped_elems :: map(),
                   dropped_datas :: map()}) ->
                 term().

Make a call's in-flight state visible to nested calls in this process.

A call reads the state once, threads it through execution and writes it back at the end, which is what keeps a tuple update at a few nanoseconds where an ETS write is forty. The cost is that a nested call could not see it: it read the last committed copy, did its work, committed, and then the outer call's own write-back discarded everything it did. Measured: a host import calling back into its own instance allocated four objects and the outer call's return dropped the store from four to one.

Publishing is process-local and costs nothing to a call that never re-enters: one process-dictionary write before a host call and one after.

published/1

-spec published(#inst{id :: reference(),
                      ckpt :: reference(),
                      entry_key :: undefined | reference(),
                      types :: tuple(),
                      funcs :: tuple(),
                      exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                      elems :: tuple(),
                      datas :: tuple(),
                      globaltypes :: tuple(),
                      tags :: tuple(),
                      canon :: tuple(),
                      fields :: tuple(),
                      kinds :: tuple(),
                      supers :: tuple(),
                      heap :: undefined | wasm_heap:heap(),
                      identity :: undefined | {sha256, binary()} | reference(),
                      store :: term(),
                      version :: term(),
                      limits :: map(),
                      ctx :: term()}) ->
                   #mut{globals :: tuple(),
                        tables :: tuple(),
                        mems :: tuple(),
                        dropped_elems :: map(),
                        dropped_datas :: map()}.

Whatever is published for this instance in this process.

The same term that was published if nothing nested wrote, and a newer one if something did, because set_mut/2 refreshes it. Comparing the answer with what was published is therefore a pointer comparison that says whether a nested call happened, which is cheaper than reading the version counter twice.

release/1

-spec release(#inst{id :: reference(),
                    ckpt :: reference(),
                    entry_key :: undefined | reference(),
                    types :: tuple(),
                    funcs :: tuple(),
                    exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                    elems :: tuple(),
                    datas :: tuple(),
                    globaltypes :: tuple(),
                    tags :: tuple(),
                    canon :: tuple(),
                    fields :: tuple(),
                    kinds :: tuple(),
                    supers :: tuple(),
                    heap :: undefined | wasm_heap:heap(),
                    identity :: undefined | {sha256, binary()} | reference(),
                    store :: term(),
                    version :: term(),
                    limits :: map(),
                    ctx :: term()}) ->
                 ok.

Release an instance's state table and this process's cache of it.

Two things kept a destroyed instance's state alive. The table holding it is owned by the creating process and was reclaimed only when that process exited, so a process creating and discarding many instances accumulated one table each. And mut/1 caches the whole #mut{} in the process dictionary of every process that has ever called the instance, keyed by instance id, which was never erased.

Only the calling process's cache can be erased from here. Another process that called this instance keeps its copy until it next looks, which is the same bounded staleness the version counter already handles.

release_ask/1

-spec release_ask(#inst{id :: reference(),
                        ckpt :: reference(),
                        entry_key :: undefined | reference(),
                        types :: tuple(),
                        funcs :: tuple(),
                        exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                        elems :: tuple(),
                        datas :: tuple(),
                        globaltypes :: tuple(),
                        tags :: tuple(),
                        canon :: tuple(),
                        fields :: tuple(),
                        kinds :: tuple(),
                        supers :: tuple(),
                        heap :: undefined | wasm_heap:heap(),
                        identity :: undefined | {sha256, binary()} | reference(),
                        store :: term(),
                        version :: term(),
                        limits :: map(),
                        ctx :: term()}) ->
                     ok.

Give an ask back, for one that came to nothing.

No slot free, or another process got there first. Those should be retried at the next hot call rather than after the retry interval, which is the only reason this exists: the interval alone would already be correct.

remember/1

-spec remember(#inst{id :: reference(),
                     ckpt :: reference(),
                     entry_key :: undefined | reference(),
                     types :: tuple(),
                     funcs :: tuple(),
                     exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                     elems :: tuple(),
                     datas :: tuple(),
                     globaltypes :: tuple(),
                     tags :: tuple(),
                     canon :: tuple(),
                     fields :: tuple(),
                     kinds :: tuple(),
                     supers :: tuple(),
                     heap :: undefined | wasm_heap:heap(),
                     identity :: undefined | {sha256, binary()} | reference(),
                     store :: term(),
                     version :: term(),
                     limits :: map(),
                     ctx :: term()}) ->
                  ok.

Note this instance, so a funcref naming it can be resolved here later.

Idempotent and cheap. Called wherever an instance record is in hand and a reference to it might escape.

root_view/1

-spec root_view(#inst{id :: reference(),
                      ckpt :: reference(),
                      entry_key :: undefined | reference(),
                      types :: tuple(),
                      funcs :: tuple(),
                      exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                      elems :: tuple(),
                      datas :: tuple(),
                      globaltypes :: tuple(),
                      tags :: tuple(),
                      canon :: tuple(),
                      fields :: tuple(),
                      kinds :: tuple(),
                      supers :: tuple(),
                      heap :: undefined | wasm_heap:heap(),
                      identity :: undefined | {sha256, binary()} | reference(),
                      store :: term(),
                      version :: term(),
                      limits :: map(),
                      ctx :: term()}) ->
                   term().

The four fields a garbage collection root scan reads from an instance.

Registered with the heap so that a collection triggered by one instance can trace every instance sharing the store. Not the whole #inst{}: ETS copies on insert, and an instance carries its type table, its compiled functions and its exports, which measured 1.9 us of a 5.7 us instantiation.

run_cleanups(Inst)

-spec run_cleanups(#inst{id :: reference(),
                         ckpt :: reference(),
                         entry_key :: undefined | reference(),
                         types :: tuple(),
                         funcs :: tuple(),
                         exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                         elems :: tuple(),
                         datas :: tuple(),
                         globaltypes :: tuple(),
                         tags :: tuple(),
                         canon :: tuple(),
                         fields :: tuple(),
                         kinds :: tuple(),
                         supers :: tuple(),
                         heap :: undefined | wasm_heap:heap(),
                         identity :: undefined | {sha256, binary()} | reference(),
                         store :: term(),
                         version :: term(),
                         limits :: map(),
                         ctx :: term()}) ->
                      ok.

Run the registered cleanups. Called by wasm:destroy/1, once.

set_code_slot/2

-spec set_code_slot(#inst{id :: reference(),
                          ckpt :: reference(),
                          entry_key :: undefined | reference(),
                          types :: tuple(),
                          funcs :: tuple(),
                          exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                          elems :: tuple(),
                          datas :: tuple(),
                          globaltypes :: tuple(),
                          tags :: tuple(),
                          canon :: tuple(),
                          fields :: tuple(),
                          kinds :: tuple(),
                          supers :: tuple(),
                          heap :: undefined | wasm_heap:heap(),
                          identity :: undefined | {sha256, binary()} | reference(),
                          store :: term(),
                          version :: term(),
                          limits :: map(),
                          ctx :: term()},
                    pos_integer()) ->
                       boolean().

Record the slot, once compilation has published into it.

Answers whether this call was the one that set it. Two processes may reach a hot call at the same moment and both be told the module is resident, and only one of them should take the instance lease that keeps the slot alive.

set_extra/3

-spec set_extra(#inst{id :: reference(),
                      ckpt :: reference(),
                      entry_key :: undefined | reference(),
                      types :: tuple(),
                      funcs :: tuple(),
                      exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                      elems :: tuple(),
                      datas :: tuple(),
                      globaltypes :: tuple(),
                      tags :: tuple(),
                      canon :: tuple(),
                      fields :: tuple(),
                      kinds :: tuple(),
                      supers :: tuple(),
                      heap :: undefined | wasm_heap:heap(),
                      identity :: undefined | {sha256, binary()} | reference(),
                      store :: term(),
                      version :: term(),
                      limits :: map(),
                      ctx :: term()},
                atom(),
                term()) ->
                   ok.

set_mut/2

-spec set_mut(#inst{id :: reference(),
                    ckpt :: reference(),
                    entry_key :: undefined | reference(),
                    types :: tuple(),
                    funcs :: tuple(),
                    exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                    elems :: tuple(),
                    datas :: tuple(),
                    globaltypes :: tuple(),
                    tags :: tuple(),
                    canon :: tuple(),
                    fields :: tuple(),
                    kinds :: tuple(),
                    supers :: tuple(),
                    heap :: undefined | wasm_heap:heap(),
                    identity :: undefined | {sha256, binary()} | reference(),
                    store :: term(),
                    version :: term(),
                    limits :: map(),
                    ctx :: term()},
              #mut{globals :: tuple(),
                   tables :: tuple(),
                   mems :: tuple(),
                   dropped_elems :: map(),
                   dropped_datas :: map()}) ->
                 ok.

tag/2

-spec tag(#inst{id :: reference(),
                ckpt :: reference(),
                entry_key :: undefined | reference(),
                types :: tuple(),
                funcs :: tuple(),
                exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                elems :: tuple(),
                datas :: tuple(),
                globaltypes :: tuple(),
                tags :: tuple(),
                canon :: tuple(),
                fields :: tuple(),
                kinds :: tuple(),
                supers :: tuple(),
                heap :: undefined | wasm_heap:heap(),
                identity :: undefined | {sha256, binary()} | reference(),
                store :: term(),
                version :: term(),
                limits :: map(),
                ctx :: term()},
          non_neg_integer()) ->
             term().

A tag's identity, in the form another module can import it.

unpublish/2

-spec unpublish(#inst{id :: reference(),
                      ckpt :: reference(),
                      entry_key :: undefined | reference(),
                      types :: tuple(),
                      funcs :: tuple(),
                      exports :: #{binary() => {func | table | mem | global, non_neg_integer()}},
                      elems :: tuple(),
                      datas :: tuple(),
                      globaltypes :: tuple(),
                      tags :: tuple(),
                      canon :: tuple(),
                      fields :: tuple(),
                      kinds :: tuple(),
                      supers :: tuple(),
                      heap :: undefined | wasm_heap:heap(),
                      identity :: undefined | {sha256, binary()} | reference(),
                      store :: term(),
                      version :: term(),
                      limits :: map(),
                      ctx :: term()},
                term()) ->
                   ok.

Undo publish/2, restoring whatever an enclosing call had published.