Beta Release 0.1.3

This release of Onyx brings a large change to the compile-time code blocks feature, runtime stack traces, as well as a massive speedup to Onyx's ovmwasm runtime. There are also numerous additions to the standard library.

Code blocks with captures

Code blocks have always been a very powerful feature of Onyx, especially when combined with macros. Most of the core.slice library is builtin around using code blocks. However, they were always a little weird, as you had to use the name of the variable as it was prescribed by the procedure you were calling. Look at core.slice.fold as an example.

// This is the old way of doing this.
arr := i32.[2, 3, 5, 7, 11];

total := core.slice.fold(arr, 0, #(it + acc));

println(total);

When using core.slice.fold, you just had to know that it gave you two values: it for the current value, and acc for the accumulator.

Now, with the new, required syntax for code-blocks you can specify the names of these variables.

// This is the new way of doing this.
arr := i32.[2, 3, 5, 7, 11];

total := core.slice.fold(arr, 0, [element, partial_total](element + partial_total));

println(total);

This does require a syntax change, and hence is a breaking change. However, this feature has not been used much in actual code yet, so there shouldn't be much to change. Also, there is no semantic difference between the previous way this worked and the current way. The current way only allows for explicit bindings; this would also work in the new version.

// This also works
arr := i32.[2, 3, 5, 7, 11];

total := core.slice.fold(arr, 0, [](it + acc));

println(total);

Notice that no captures are given, and we can still access it and acc directly like before. This may change in the future.

In order to capture values in this way, the corresponding #unquote must provide values in the following way.

// How slice.fold is implemented
slice.fold :: macro (arr: [] $T, initial: $R, condition: Code) -> R {
    acc := initial;
    for arr {
        acc = #unquote condition(it, acc);
    }
    return acc;
}

This is intended to look a lot like a function call, but the #unquote is still required to aid readability that this is a code-block being expanded.

Runtime stack traces

You can now ask for a stack trace at any point in your program, so long as you compile it with the --stack-trace option. Simply call runtime.info.get_stack_trace. The stack trace is allocated into the temporary allocator of the current context, so make sure you copy it if you want to store it for later.


print_stack_trace :: () {
    use runtime.info;

    trace := info.get_stack_trace();
    for trace {
        // `it.info` points to static data about the trace node, which contains:
        //     func_name - the function name
        //     file      - the file name
        //     line      - the line the function was defined on
        //     func_type - the type of the function
        //
        // `it.current_line` is the line the function is executing.
        //
        printf("{} ({}:{})", it.info.func_name, it.info.file, it.current_line);
    }
}

main :: () {
    print_stack_trace();
}

This feature is already integrated into the standard library to print stack traces in useful places, such as in the logging allocator and on failed assertions. There are plans to make a remote memory debugger, so trace every allocation made in your program, which will use the stack trace to give you a better idea where the memory is getting allocated.

Note, at the time of writing, there is no way to enable the --stack-trace compiler flag on the Onyx Playground. This will be addressed soon.

OVM-Wasm compiler optimizations

OVM-Wasm, the WebAssembly runtime written for and used by Onyx was originally written as quickly as possible to get something to work. Then, it wasn't touched for about a year. It is understandably going to be slower than any native runtime, as it is an interpreter for a bytecode, not a native compiler. However, it still had plenty of room to speedup, as there were absolutely zero optimizations done to the generated bytecode. That changed in this update.

Thanks to performing copy propagation, all unnecessary register copies were removed (there were a lot!), and the result was a 1.4 times speedup, or 30 percent time reduction for most programs. I would like to say this was some heroic effort that took a lot of work, but it was one evening of thinking about the problem a little harder and realising that everything needed to perform copy propagation was already there. The diff was around 50 lines of code to implement the optimization. Either way, this was a huge performance win for OVM-Wasm.

Full Changelog

Additions:
- New syntax for declaring quoted code blocks.
    - `[captures] { body }` for blocks.
    - `[captures] ( expr )` for expressions.
- User-level stack trace.
    - Enable with `--stack-trace`
    - Use `runtime.info.get_stack_trace()` to get the current stack trace.
    - Used in assertions and heap allocator for better error reporting
- `Optional.with` for running a block of code with the value in an Optional, if one is present.
- `-Dvariable=value` command line option to add symbols to the `runtime.vars` package.
- `--no-type-info` command line option to omit type information from the binary.
- `Allocator.move`. Moves a value into an allocator, returning a pointer to it.
    - This is a copy operation (and might be renamed later)
- `core.encoding.hex` package
    - Quickly convert a byte array to and from its hex equivalent.
- `os.path_clean`
- `os.path_directory`
- `os.path_extension`
- `os.path_split`
- `slice.equal`
- `iter.find`
- `iter.flatten`

Removals:
- Remove old syntax for quoted blocks, `#quote` and `#()`.
    - Switch to `[] {}` and `[] ()` respectively.
- Old WASI specific modules for time and environment variables.
- `Result.return_err` and `Result.return_ok`.
    - Unnecessary with new union features.

Changes:
- Much faster backend (approximate 1.3-1.4x speedup for most programs)
- Added support for optionals in `json.encode`, `json.from_any`, and `json.as_any`.
- Added support for optionals in `conv.parse_any`.
- `Set` implementation no longer contains a "default value". Instead, an optional is returned from `get`.
    - A similar thing may happen to `Map` soon, but that is a significant breaking change.
- Indexing for union tag types starts at 0 now, instead of 1.
    - There were many annoyances where Zero-Is-Initialization (ZII) was not followed and
      that was causing too many bugs to be worth it.

Bugfixes:
- Numerous bugs related to polymorphic procedures
© 2020-2024 Brendan Hansen