Repository navigation
2.0.0
Hey 馃憢馃徏
I've been working hard over the last few weeks on a set of changes that lay the foundation for the 2.0 series of chasm, and I'm proud to say we're ready for a release. Chasm 1.0'd back in August of last year and since then we've made huge strides in maturing and stabilising the runtime, in order to continue to grow and move forward I've made some breaking changes now with a view for what comes next. As always feedback is welcome and feel free to raise a discussion or an issue if bump into any problems
Breaking Changes
- A new Android target (min SDK 28)
- JVM target is now minimum JDK 17
- A redesigned Host Function API
- The deprecated
macosX64andiosX64targets have been removed - The Gradle plugin no longer supports AGP 8
- Changes to plugin generated implementations as they are now private by default and constructed through top level factory functions
- Previously deprecated APIs and configuration options have been removed, including producer mode and
RuntimeConfig.bytecodeFusion
New Targets and version requirements
Chasm historically had a JVM target only, Android used this same implementation. One of the things I have decided with chasm is that I want to do everything I can inside of the native constraints of each platform, that means no FFI or anything like that. In order to achieve this I need to have both an Android target and a JVM target so I can do what's best for both platforms. Therefore we now have an Android target (min SDK 28) and a JVM target which is min JDK 17
I've also removed the deprecated macosX64 and iosX64 Kotlin/Native targets.
Host functions
The host function API is probably the biggest change here. Previously host functions received a list of values and returned another list of values. This was straightforward to work with but meant we were creating objects and marshalling values every time we crossed the host boundary.
Host functions now read their parameters and write their results directly into Chasms stack. There are helpers for reading and writing the different Wasm types, so you don't need to manually interpret the underlying representation.
The new API also gives host functions access to memories, tables, globals and tags, including growing memories and tables and performing bulk operations. You can create and inspect extern references, allocate and modify Wasm GC structs and arrays, and raise Wasm exceptions that guest code can catch.
There's also explicit control over reference lifetimes. If your host code needs to hold onto a reference after the function returns you can retain it, then release it when you're done.
Gradle plugin and generated interfaces
If you're using the Gradle plugin you'll need to update how you construct generated services. For example, a service named FibonacciService now gets a fibonacciService(bytes) factory function which returns the interface.
The implementation is private by default, and the interface, factory and implementation each have their own visibility setting. If you're using chasm-coroutines the generated factory function will be suspending
Android integration now requires AGP 9, and the old producer mode and global suspending factory option have been removed. The deprecated RuntimeConfig.bytecodeFusion option has also gone; fusion was already always enabled, so this doesn't change how the runtime executes your code.
Performance
It wouldn't be a Chasm release without more performance, and nothing has changed here. I'll go into the details below but you should see roughly 20% faster execution and considerably less memory usage... testing Chasms corpus of peak memory was half what it was previously but mileage will vary case by case. Some platforms will see more of an uplift than others Android in particular is much faster now we use a ByteArray, it transpires ART has suboptimal performance when dealing with byte buffers.
The gains come from a couples of threads of work
Linear memory
The linear memory implementations have had a substantial rework across Native, JVM and Android.
Previously growing memory on Native meant allocating a larger array and copying the existing contents over. The JVM implementation reserved some spare capacity, but eventually had to do the same thing when that capacity was exhausted.
Native and JVM now use mapped memory, allowing us to reserve the address space upfront and grow the logical memory without copying its existing contents. If your workload regularly grows its memory this removes a considerable amount of unnecessary work.
There's also a new prefault option for these backends. When enabled it touches newly exposed memory during construction and growth, moving that work upfront instead of leaving it until the memory is first accessed. Probably don't use this unless your initialisation budget is large.
Android gets its own array backed implementation with spare capacity to accommodate growth and bulk operations built around array copies.
Interpreter
The interpreter has had another round of changes to instruction dispatch, function calls and stack frames. We've reduced the amount of state passed between instruction handlers, simplified the dispatch loop and moved direct call specialisation into linking.
Exception handling now uses compiled handler tables. Previously we maintained a stack of active handlers as execution entered and left protected regions. The compiler now records that information ahead of time, and the runtime consults it when an exception is thrown.
These changes continue the work from the 1.x releases: resolve more during compilation and leave less bookkeeping for the interpreter while your code is running.
The new host function API also removes the lists and boxed values from the low level calling convention, which matters for workloads that frequently call back into Kotlin.
Finally, generated global properties now resolve their export once during construction and reuse that handle for reads and writes. It's a small change, but there's no reason to repeat an export lookup every time you access a property.
What's next
- New proposals
Threads and Compact Import Descriptors are in the process of being standardised and whilst the ComponentModel is still a ways of being standardised Kotlin is moving ahead with supporting it which makes me feel I should have a stab at an early version of it.
- Other compiler tiers
Chasm is fast enough, it's the fastest Wasm interpreter on the JVM and it's not close. But there's obviously more we can do, I'm not committing to any particular approach at this point but I like the idea of exploring different compilation strategies and seeing how they can expand the scenarios where chasm is useful.