Building wasm2brs
All the dependencies for wasm2brs are installed within a Docker image that can be run with ./run.sh.
To build our repo:
git submodule update --init --recursive ./run.sh make
To run make without the Docker image (not recommended) be sure to install the same dependencies as listed in the Dockerfile.
Running the samples
This will place all the output files in project/. To run, either side load the project manually or use vscode with the BrightScript Language extension. When opening our repository in vscode, running the debugger will run project/.
The samples we have are:
cmakedoomfilesmandelbrotjavascriptrust
Starting your own project
The easiest way to get started is to look in our samples directory. Specifically the cmake sample is setup to be used as a template.
If you wish to use libc/libc++, then you should use install wasienv, which comes with wasi-libc as well as helpful scripts that run clang/make/cmake/etc with the correct compiler flags and directories.
The mandelbrot sample shows how to use clang directly without wasienv and no standard libraries. Note that wasm-ld is required.
In general the process looks like:
- Run your build tool of choice to output a
.wasmfile, typicaly in Release mode with-Oz - Run Binaryen's
wasm-optto perform wasm specific optimizations that reduce goto/labels and stack variables. This is located inbuild/wasm2brs/third_party/binaryen/bin/wasm-opt. The recommended optimization level is-O4 - Run
wasm2brsto convert into a.brsfile. This is located inbuild/wasm2brs/wasm2brs
Rust projects
Rust is considerably easier to setup and involves changing the target of the project to wasm32-wasi and compiling with optimization level z:
rustup target add wasm32-wasi cargo build --target wasm32-wasi
Or
rustc -C opt-level=z --target wasm32-wasi yourfile.rs
As mentioned above, you'll want to run wasm-opt on the output wasm file and finally wasm2brs to convert into a .brs file.
Note: Some Rust libraries depend upon crates that do not have a target built for wasm32-wasi, such as the unix crate. The easiest path is to fork those libraries and remove their dependence upon those crates.
WASM / BrightScript limitations
- Maximum number of arguments to a function is 32 due to BrightScript
- Stack depth is dependent upon BrightScript's limitations and may be less than WASM standards
- Floating point math is approximate (where possible we use the correct algorithm, but it may not perfectly match processors)
- NaN value bit patterns are not represented
- Loading and storing (or reinterpreting) Float/Double (also called f32/f64) to i32/i64 and back may lose precision / bits
- Any Float/Double with an exponent of 0 (denormalized) is treated as 0 when loaded
- Long jumps and exceptions are not yet supported (header
setjmp.hdoes not exist, but we provide a stub that aborts) - BrightScript files cannot exceed 2MB and must be broken up
- Results in
Error loading file. (compile error &hb9) in pkg:/source/test.brs(NaN) - Files are broken up automatically by wasm2brs via adding a number to the end (e.g. main.brs, main1.brs, main2.brs...)
- Results in
- BrightScript debugger will wrap line numbers beyond 65536 (overflow)
- Files are broken up automatically by wasm2brs so they don't exceed 65536, in the same way as the 2MB limit
- For the following BrightScript errors, optimizing (O4 or Oz) helps alleviate the issues:
- BrightScript has an internal limit on the number of
If/Else Ifblocks in a function- Results in
Internal limit size exceeded. (compile error &hae) in pkg:/source/test.brs(...) - By observation, allowed to have maximum 279 blocks for the first group, and then maximum 25 blocks for subsequent groups
- The last
Elseclause does not contribute to this limit - No limit on how many groups
- Results in
- BrightScript has an internal limit of 253 variables in a function including function parameters
- Results in
Variable table size exceeded. (compile error &hb0)
- Results in
- BrightScript has an internal limit of 256 goto labels in a function
- Results in
Label/Line Not Found. (compile error &h0e) in pkg:/source/test.brs(NaN)'label256' - A function can actually have more than 256 labels, but any attempts to goto labels beyond 256 will fail with the above error
- BrightScript compilation becomes exponentially slower with the number of labels in a function (beyond 10000 will hard lock the device)
- Results in
- BrightScript has an internal limit on the number of
WASI limitations
- Environment variables, command line arguments, and stdout/stderr/stdin strings are always UTF8 encoding
API
Function external_append_stdin(bytesOrString as Dynamic) as Void
- Append an
roByteArrayorStringto stdin
Hooks
m.external_print_line = custom_print_line:
- Signature:
Function custom_print_line(fd as Integer, str as String) as Void - Will be called every time a line is parsed by stdout or stderr.
- Will NOT be called if the user provides their own
m.external_output, however the helper functionPrintAndConsumeLinescan emulate the same behavior.
m.external_output = custom_output:
- Signature:
Function custom_output(fd as Integer, bytes as Object) as Void - Parameter bytes is an
roByteArray - Will be called when raw bytes are written to stdout (fd = 1) or stderr (fd = 2).
- Useful if the output of a program is binary data instead of text, or if special parsing is needed.
- Overriding this function will prevent
m.external_print_linefrom being called, however the helper functionPrintAndConsumeLinescan emulate the same behavior.
m.external_wait_for_stdin = custom_wait_for_stdin:
- Signature:
Function custom_wait_for_stdin() as Void - Called when an attempt was made to read from stdin, but there was no bytes available.
- During this callback, you should invoke
external_append_stdin. - When the callback completes, the program will continue its attempt to read from stdin.
Run tests
Run all the tests, this will auto discover your device with a default password of rokudev.
To run a specific test you can specify the .wast file as an absolute path, otherwise it assumes it's in the third_part/testsuite/ directory, for example i32.wast:
./run.sh make run_test ARGS="wast i32.wast"To use a non-default password:
./run.sh make run_test ARGS="password ..."To deploy to a specific device (e.g. 1.2.3.4):
./run.sh make run_test ARGS="deploy 1.2.3.4"To provide multiple arguments:
./run.sh make run_test ARGS="password ... deploy 1.2.3.4 wast i32.wast"