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.
$ 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:
$ 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:
$ 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.
- Component (the preamble's layer field says component): its
wasi:cli/runexport is run throughComponentConfig.Run, with the process's stdin, stdout and stderr, and the arguments after--. (Sincev0.12.0, #1128: before it, a component given flags was misread as a malformed core module.) - WASI preview 1 command: a module that imports
wasi_snapshot_preview1and exports_start._startis run withargv[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 exportsmstart, the import is served, and the guest can start its own threads. - 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- The guest path must be absolute.$ 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--dir ./data:datais 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 Gowasip1guest takes its working directory fromPWD, so--env PWD=/xchanges where its relative paths resolve. --diris read-only. A write through it fails in the guest withCapabilities insufficient(ENOTCAPABLE).--scratchdoes not filter what is inside the directory you give it. Bind mounts,/procand 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:
$ 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/):
$ 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
$ 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]
$ 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/):
$ 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:
$ 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 awasip1command does. A Go embedder can also pass them throughComponentConfig.Args. --dir,--scratchand--envare refused on a component (exit 2), each naming why: this build's component host grants no directories and serves an empty environment.--features=threadsis accepted.- An unrecognized
--featuresname is refused first (exit 5, the last command), before the file's kind is considered. --featureson 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.Callfrom Go. --stricthas 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
wasip1guest that exits 3 is indistinguishable by code alone from a refused module. The message on stderr tells them apart: a guest exit prints noburroughs:line. - The repository README's version of this table leaves out unlinkable modules. The code (
exitCodeincmd/burroughs/run.go) mapsErrUnlinkableto 3, and that is what you see (theimports.wasmline 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.