Skip to content

CLI reference: burroughs

Work in progress

This page is a draft. It describes Burroughs as of its latest release and may change. Where it disagrees with the engine's own documentation or CHANGELOG, those win.

Written against v0.12.0 from cmd/burroughs/main.go and run.go. Every transcript is generated by this site's CI: it builds the CLI at the latest release tag (go build github.com/scttfrdmn/burroughs/cmd/burroughs), runs the sessions in examples/cli/, and fails if any output differs from what is shown here. [exit N] is the exit status when it is not 0.

examples/cli/transcripts/cli-usage.txt
$ burroughs
usage: burroughs <command> [arguments]
  run [--strict] file.wasm [func [value...]]   instantiate and call an export
  inspect file.wasm                            section-level module dump
  version
[exit 2]

The top-level usage line above lists only --strict. run takes more flags than that. burroughs run with no file prints the full list.

The run subcommand goes through the public Go API and nothing else, so anything it does, an embedder can do too (decision 0029). inspect is the exception: its section dump uses the engine's internal decoder, which has no public equivalent.

burroughs version

Prints a fixed banner and exits 0:

examples/cli/transcripts/gs-version.txt
$ burroughs version
burroughs v0.0.1 — the machine Burroughs never built

Known issue: the banner says v0.0.1 (#1130)

The banner is a constant. It does not report the module version, and says v0.0.1 even in v0.12.0.

burroughs inspect file.wasm

Decodes a core module and lists its sections and their sizes. The second command shows it refusing a component:

examples/cli/transcripts/cli-inspect.txt
$ burroughs inspect embed-core/calc.wasm
embed-core/calc.wasm: wasm v1, 6 section(s)
  [0] type           29 bytes
  [1] function        7 bytes
  [2] global          6 bytes
  [3] export         47 bytes
  [4] code           63 bytes
  [5] custom         24 bytes
$ burroughs inspect testdata/p3hello.wasm
burroughs: malformed module: unknown binary version: 65549
[exit 3]
  • It decodes and does not validate. A module that fails typing still exits 0, because the section dump is complete.
  • A file it cannot decode exits 3 (malformed). A construct from a gated-off proposal exits 6.
  • It does not understand components. A component is refused as malformed (above).
  • It never exits 4 or 5, because nothing it does can trap or reach an unimplemented instruction.

burroughs run

burroughs run [--strict] [--dir HOST[:/GUEST]]... [--scratch HOST[:/GUEST]]... [--env NAME[=VALUE]]...
              [--features NAME[,NAME]]... <file.wasm> [-- <arg>...] | [<func> [<value>...]]

(That is the first line of burroughs run's usage text, wrapped; the transcript of the two-colon refusal below shows it as printed.)

How the file is routed

The bytes decide the path, in this order, whatever flags are given. There is no flag to choose it.

  1. Component (the preamble's layer field says component): its wasi:cli/run export is run through ComponentConfig.Run, with the process's stdin, stdout and stderr, and the arguments after --. (Since v0.12.0, #1128: before it, a component given flags was misread as a malformed core module.)
  2. WASI preview 1 command: a module that imports wasi_snapshot_preview1 and exports _start. _start is run with argv[0] set to the file's base name and the arguments after -- following it. Stdin, stdout and stderr are the process's own. The guest's exit code becomes the CLI's exit code. If the module imports "burroughs" "spawn" and exports mstart, the import is served, and the guest can start its own threads.
  3. Core module: instantiated with Config{Strict: --strict}. With no function named, its exported functions are listed one per line. With a function named, that function is called with the typed values that follow, and the results are printed on one line separated by spaces. A function with no results prints nothing.

Flags

Flag Applies to Meaning
--strict core modules Refuse a module the validator could not fully check (exit 3) instead of running it. See declined.
--dir HOST[:/GUEST] wasip1 Grant a directory read-only. Repeatable. With no :/GUEST, the directory is mapped under its own absolute host path. Refused on a component (exit 2).
--scratch HOST[:/GUEST] wasip1 Grant a writable directory. Writes are confined beneath it. Repeatable. Refused on a component (exit 2).
--env NAME=VALUE / --env NAME wasip1 Put a variable in the guest's environment. A bare NAME copies the host's value and is refused if it is unset. Repeatable. Nothing is passed unless named. Refused on a component (exit 2).
--features NAME[,NAME] wasip1, components Proposal capabilities the guest needs. The only recognized name is threads. An unrecognized name is refused first (exit 5). Refused on a core module (exit 2), because Config has no feature set.
-- arg... wasip1, components The guest's arguments.

How --dir and --scratch are parsed:

  • The separator is one colon: --dir ./data:/data. wasmtime's two-colon form is refused by name. The first two lines of the transcript; the full usage text and [exit 2] follow:

    examples/cli/transcripts/cli-dir-twocolon.txt
    $ burroughs run --dir ./data::/data guest.wasm
    invalid value "./data::/data" for flag -dir: --dir "./data::/data": the guest path ":/data" starts with a colon, so nothing can open through it. Burroughs' separator is ONE colon (--dir HOST:/guest/path); two is wasmtime's grammar
    
    - The guest path must be absolute. --dir ./data:data is refused. A relative guest path would be a grant that nothing can open through. - A refused flag prints the full usage text after the message and exits 2.

What a guest gets by default:

  • The guest's environment is empty unless you pass --env. One thing to know: a Go wasip1 guest takes its working directory from PWD, so --env PWD=/x changes where its relative paths resolve.
  • --dir is read-only. A write through it fails in the guest with Capabilities insufficient (ENOTCAPABLE).
  • --scratch does not filter what is inside the directory you give it. Bind mounts, /proc and device files inside it are reachable. You choose the directory, and confinement only keeps the guest from leaving it (ADR 0091).

Value syntax (core module arguments and results)

Spelling Value
i32:42, i32:-1, i32:4294967295, i32:0x2a i32. The signed and unsigned spellings of one bit pattern are both accepted.
i64:-1 i64
f32:1.5, f32:nan, f32:nan:0x200000, f32:inf f32. NaN payloads are preserved.
f64:inf, f64:nan f64
v128:0x1:0x2 v128, as low and high 64-bit halves. Printed zero-padded: v128:0x0000000000000001:0x0000000000000002.
extern:3 non-null externref with host identity 3
null:func, null:extern null reference of that type

A result may print as ref:<payload>:<type>, for example a GC struct or a funcref. Except for a host reference (ref:host:N:<type>, which reads back), those are results only: there is no argument spelling for them, because a host cannot construct one.

Transcripts

Core module:

examples/cli/transcripts/cli-core.txt
$ burroughs run embed-core/calc.wasm add i32:2 i32:40
i32:42
$ burroughs run embed-core/calc.wasm divmod i32:17 i32:5
i32:3 i32:2
$ burroughs run embed-core/calc.wasm divmod i32:1 i32:0
burroughs: trap: integer divide by zero
[exit 4]
$ burroughs run embed-core/calc.wasm add i32:1
burroughs: interp: "add" takes 2 arguments, got 1
[exit 1]
$ burroughs run embed-core/calc.wasm nope
burroughs: interp: no exported function "nope"
[exit 1]

WASI preview 1 command (the Go guest from tutorial 2, run in examples/wasip1/):

examples/cli/transcripts/t2-run.txt
$ mkdir -p out
$ burroughs run --dir ./data:/data --scratch ./out:/out --env GREETING=hi guest.wasm -- first second < in.txt
args: [guest.wasm first second]
GREETING env: hi
env entries: 1
/data/greeting.txt: "hello from the host"
write under /data: open /data/new.txt: Capabilities insufficient
wrote /out/result.txt
stdin lines: 3
$ cat out/result.txt
written by the guest
examples/cli/transcripts/t2-fail.txt
$ burroughs run --dir ./data:/data guest.wasm -- fail < /dev/null
args: [guest.wasm fail]
GREETING env: 
env entries: 0
/data/greeting.txt: "hello from the host"
write under /data: open /data/new.txt: Capabilities insufficient
write under /out: open /out/result.txt: Bad file number
stdin lines: 0
[exit 7]
examples/cli/transcripts/cli-wasip1-start.txt
$ burroughs run guest.wasm _start
burroughs: a wasip1 command takes its arguments after --, e.g. run guest.wasm -- ARG
[exit 2]

Component (the fixtures are the engine repository's internal/component/testdata/, fetched at the tag into testdata/):

examples/cli/transcripts/cli-component.txt
$ burroughs run testdata/p3hello.wasm
Hello, world!
$ burroughs run testdata/cli-run-trap.wasm
burroughs: trap: unreachable
[exit 4]
$ burroughs run testdata/record-synth.wasm addrec
burroughs: a component runs its wasi:cli/run world; it takes no function name (its arguments go after --)
[exit 2]

Components through the CLI

Since v0.12.0 (#1128, fixed), a component is routed as a component whatever flags are given, and a flag that cannot apply is refused by name rather than misread or ignored:

examples/cli/transcripts/cli-component-misrouted.txt
$ echo "echo me" | burroughs run testdata/p3echo.wasm -- a b
args=["p3echo.wasm", "a", "b"]
stdin[8]="echo me\n"
$ burroughs run --dir ./wasip1/data:/data testdata/p3hello.wasm
burroughs: a component takes no --dir or --scratch: this build's component host grants no directories
[exit 2]
$ burroughs run --features=threads testdata/p3hello.wasm
Hello, world!
$ burroughs run --env GREETING=hi testdata/p3hello.wasm
burroughs: a component takes no --env: this build's component host serves an empty environment
[exit 2]
$ burroughs run --env GREETING=hi --scratch ./wasip1/out:/out testdata/p3hello.wasm
burroughs: a component takes no --dir or --scratch: this build's component host grants no directories
[exit 2]
$ burroughs run --features=gc embed-core/calc.wasm add i32:1 i32:2
burroughs: feature not implemented in this phase: unknown feature "gc"; this build recognizes "threads"
[exit 5]
  • A component takes its arguments after -- (the first command), as a wasip1 command does. A Go embedder can also pass them through ComponentConfig.Args.
  • --dir, --scratch and --env are refused on a component (exit 2), each naming why: this build's component host grants no directories and serves an empty environment. --features=threads is accepted.
  • An unrecognized --features name is refused first (exit 5, the last command), before the file's kind is considered.
  • --features on a core module is refused (exit 2): a core module takes the default proposal set (see tutorial 4).
  • A component's typed exports cannot be called from the CLI. A function name after a component is refused by name (exit 2). Use Component.Call from Go.
  • --strict has no effect on wasip1 commands or components. It only affects the core-module path.

Exit codes

There is one table for the whole CLI, so inspect and run mean the same thing by each code.

Code Meaning From
0 The module ran.
1 This invocation's own failure: an unreadable file, a value with no type prefix, no such export, wrong arity or argument type. anything unclassified
2 Wrong arguments to the CLI itself. usage errors, refused flags
3 The module was refused: malformed, invalid, declined under --strict, or unlinkable (an import nothing supplies). ErrMalformed, ErrInvalid, ErrDeclined, ErrUnlinkable
4 The module executed correctly and the program went wrong: a trap in a core module or a component, or a component call refused because an earlier trap locked the instance. *Trap, ErrInstanceLocked
5 The engine reached something it does not implement in this phase. An unrecognized --features name also lands here. ErrUnsupported
6 The module is fine, but this build has that proposal's gate off. ErrGated
7 A component call the caller cancelled. ErrCancelled
8 A call on a component that has been closed. ErrComponentClosed
9 Close reached its bound with a guest still cancelling, and tore down anyway. ErrCloseIncomplete
guest's A wasip1 command or component that exits non-zero passes its own code through. proc_exit / wasi:cli/exit

Codes 7, 8 and 9 cannot happen through this CLI: run never cancels or closes a component. They exist because every public sentinel has a code, which the repo checks with TestExitCodesCoverEveryPublicSentinel.

Two things to know about the table:

  • A guest's own exit code can collide with these. A wasip1 guest that exits 3 is indistinguishable by code alone from a refused module. The message on stderr tells them apart: a guest exit prints no burroughs: line.
  • The repository README's version of this table leaves out unlinkable modules. The code (exitCode in cmd/burroughs/run.go) maps ErrUnlinkable to 3, and that is what you see (the imports.wasm line in tutorial 4). The README's omission is filed as #1130.

Environment switches

These environment variables roll back default-on gates. Each is read when it is used, and the only value with any effect is 0:

Variable Effect of =0
BURROUGHS_THREADS Shared memories and atomics are refused as gated (exit 6). --features=threads admits them again for a wasip1 command. Nothing re-admits them for a core module, and --features on one is refused (exit 2).
BURROUGHS_EXTENDED_CONST Extended constant expressions are refused as gated (exit 6). Since v0.12.0 (ADR 0107).
BURROUGHS_TAIL_CALL return_call and return_call_indirect are refused as gated (exit 6). Since v0.12.0 (ADR 0108).
BURROUGHS_MULTI_MEMORY A second memory, or a nonzero memory index, is refused as gated (exit 6). Since v0.12.0 (ADR 0109).
BURROUGHS_MEMORY64 An i64 index type on a memory or a table is refused as gated (exit 6). Since v0.12.0 (ADR 0110).
BURROUGHS_COMPONENTS Every component is refused as gated (exit 6).
BURROUGHS_ASYNC Components using the async ABI are refused as gated (exit 6).
BURROUGHS_CM_THREADS Components using component-model threads are refused as gated.

BURROUGHS_NO_SKIP also exists, but it belongs to the repository's own test suite and has no effect on the CLI. Tutorial 4 has transcripts.