Releases: CharlieTap/chasm
1.4.2
Whats Changed
Fixed a bug in recursive type rolling
I found a issue where unrolling each subtype in a recursive type group would cause the entire group to be traversed, this should speed up instantiation of modules with large recursive type groups
Faster import matching
The import matching code was previously using the same extensive defined type matching logic used during validation, this code rigorous but slow and non optimal for execution time. In this release I've began to remove defined types from the execution layer and now almost all checks are using canonical runtime types.
1.4.1
Whats changed
- +15% Execution Speed
I've been dialling in the interpreter loop as far as it can go in the instruction stack based interpreter, the bytecode produced is now what I could consider optimal. Theres another 20% that could be had if we moved to a instruction pointer based setup and I may well introduce this in the future.
- Faster instantiation for large modules
Previously chasm ran all expressions through its runtime expression interpreter, this meant having to compile constant expressions at instantiation time. I've now introduced a small specialised constant expression evaluator which is entirely decoupled from chasms runtime interpreter/compiler pipeline. Don't expect anything drastic here, the primary motivation for doing this was for speeding up the wasm testsuite execution as it contains 1000's of small constant expressions
1.4.0
What's Changed
2x faster execution
This release contains a large architectural rewrite of chasms runtime and compiler, it improves its execution speed roughly 2x on the industry standard coremark benchmark. I had long considered making chasm more performant a dead end, and that we are fundamentally limited by the type of interpretation which can be expressed in java bytecode. Last year I implemented a fusion algorithm that bumped the speed roughly 30%, but what I didn't realise is that this algorithm was suboptimal in a bunch of ways. Long story short we were fetching operands via a lambda to avoid the explosion of monomorphised instruction handlers, this was a decision I made at the time because it limited the scope and I generally assumed not a lot of performance had been left on the table.
In the last 6 months I have been working on a new runtime, written in a low level language that has some very impressive performance characteristics. This work is inspired by things I have learnt there and also from another open source runtime called stitch, check it out its very clever!
TLDR
We now create super instructions with the operands i and s, i meaning intermediate which are constants that are folded into the instruction, s being stack but notably stack with a slot index. Chasm has always stored locals on the stack and what this stack with index approach does is allow us to fuse both locals and normal stack operands using the same handler. Its a very clever trick which keeps code size/instruction cache under control. At compile time we calculate the the entire size needed for the call frame and the indicies for every instructions so there is no runtime overhead.
Further to this we also now lower wasms control flow instructions from a nested syntax representation to true jumps, this is something I started last year but it never showed the performance characteristics I needed. It transpires coupled with some other improvements it was meaningful. That being said theres still some performance on the table as we are maintaining an instruction stack for now, this can be removed in the future and changed to a real ip/pc and instruction pointer model.
Anyways, enjoy twice the performance. Shout out to GPT 5.4 which actually solved some very tricky compiler issues I ran intro, its remarkable what a good harness can do for these agents
1.3.1
Whats Changed
This release adds support for both AGP 8 and AGP 9 when using Chasm's Gradle plugin
1.3.0
What's changed in Chasm
- Chasm now supports decoding and validating binaries that contain SIMD and relaxed SIMD instructions, the intention here is to provide better error messaging when presented with binaries we are unable to execute. Historically Chasm would return an ambiguous "Unrecognised opcode", but now you will get a message describing what features are present in the binary but not supported by chasm.
- Minor performance improvements to the binary decoder, removing an O(n) search across instructions
Whats changed in the Gradle plugin
- producer mode is now deprecated, it will still function for now but will be removed in a future release. It's successor Glueball, allows you to do the same thing but also generate a native interface alongside your virtualised interface. Meaning you easily swap back and forth between native and virtual execution. Note that Glueball does not have the same level of type support as Producer does for now.
Full Changelog: 1.2.1...1.3.0
1.2.1
What's Changed
- Chasm is now able to both decode and validate binaries containing opcodes from the Threads proposal. The execution phase will come a little later, the proposal specification is still not rebased on top of Wasm 3.0 and I'm apprehensive to finish support whilst the spec is in flux.
- Optimisations to the binary validation algorithm, the original version heavily allocated during instruction simulation. The new version uses mutation where possible and as a result the validation is 5-10% faster.
Latest Version: 1.2.1
Latest Plugin Version: 2.0.1-1.2.1
Full Changelog: 1.2.0...1.2.1
1.2.0
What's Changed
JS Support for codegen produced by Chasms Gradle Plugin
Chasms Gradle Plugin historically produced codegen that was common amongst chasms kotlin multiplatform targets, but Chasm lacks support for the web because using an interpreter in an environment that supports JIT is nonsensical.
With this release the codegen is reworked to leverage a new generic wasm virtual machine interface. This interface uses Chasm under the hood on all native and JVM targets but uses the standard JS Wasm API for JS targets, bringing all that JIT perf.
For most consumers of the plugin no changes will likely be needed, it still generates an interface and impl in the same way as previously. That being said some typing has changed and if you're working with imports there will be breaking changes.
So with that in mind the plugin now has a new major version, alongside a sub version to help disambiguate what version of chasm it uses.
plugin-version-name = "2.0.0-1.2.0"As always please use the example project as a reference, I've updated it to use the latest plugin/codegen and added a web example now that we have JS support.
Wide Arithmetic Proposal
Chasm now supports another new proposal from the future Wasm 4.0 called wide arithmetic
1.1.0
Allocators and Strings
Chasms Gradle Plugin has had the ability for a while to generate Kotlin functions with String return types but has been lacking the ability to specify String parameters. This is because specifying a String as a parameter means allocating it in memory whereas returning a String simply means reading an existing allocation.
In order to safely allocate memory Chasm needs to coordinate via the programs allocator, certain frameworks like emscripten allow exporting the malloc and free when generating wasm binaries. Chasm can then use these functions to safely allocate regions of memory for strings.
This release includes the ability to define an allocator for a module, i.e. an alloc function and a dellocation/free function.
create("StringService") {
binary = layout.projectDirectory.file("src/commonMain/resources/truncate.wasm")
packageName = "com.test.chasm"
allocator = ExportedAllocator("malloc", "free")
}And finally stringParams are now possible in the function definition api, please note an error will be thrown
if a stringParam is defined but no allocator is present on the module definition.
create("StringService") {
binary = layout.projectDirectory.file("src/commonMain/resources/truncate.wasm")
packageName = "com.test.chasm"
allocator = ExportedAllocator("malloc", "free")
function("truncate") {
stringParam("parameterName")
stringReturnType()
}
}As always you can find an example of stringParams and allocators in the example project
Changes to Chasm
- a new generic allocator interface is available alongside a concrete Wasm32Allocator class, it's not expected that these types will be of much use outside of codegen and host function integration.
Changes to Chasm Gradle plugin
- a new optional property
ignoredExportsis now present when configuring interface gen from wasm modules, any export names present in this set will be excluded from the interface gen. - a new allocator property is now present on WasmModules which lets you specify an ExportedAllocator
- a new
stringParamfunction is available when specifying module function definitions - errors that occurs during codegen will now be correctly reported through stderr
1.0.0
Well... a year and a half later that "side project" is now ready. If you've been following Chasms progress you'll know it's been quite a journey! Thank you to all the people that used Chasm and reached out and gave feedback, if it wasn't for the help it wouldn't have been possible ❤️
Wasm 3.0
The Wasm 3.0 spec is on the cusp of being published and I've spent the last couple of weeks preparing for it. In short it takes a bunch of proposals (FuncRefs, TailCall, GC, Exceptions, Memory64 and more) and merges them into the latest iteration of stable Wasm. Chasm supports all of Wasm 3.0 with the exception of Memory64.
Reasons for not supporting Memory64
Memory64 requires larger pointers, memories and tables , which ultimately result in more overhead , more memory and slower runtime performance. Realistically workloads that require more than 4GB of memory are not well suited for Chasm in its current form and given we're only shipping one artefact its not worth slowing the 99% (32 bit workloads) for the 1%. I have plans for supporting this in the future but thats a long way down the road.
Upgrades to the Gradle plugin
Chasms Gradle Plugin now implicitly wires up the correct chasm runtime depending on the Kotlin plugin and targets configured. This was important as the Plugin depends on the tooling api which ships with the runtime and thus it was possible for a user to previously wire up a version of the runtime which is not compatible with the plugin.
As a result you can now configure whether the runtime is hooked up as an API or IMPLEMENTATION dependency using the Gradle plugin extension:
chasm {
// default is IMPLEMENTATION
runtimeDependencyConfiguration = RuntimeDependencyConfiguration.API
}Similarly chasm now supports configuration of type visibility for the Interface and Implementation its plugin generates:
chasm {
modules {
create("TestService") {
// default is PUBLIC
interfaceVisibility = TypeVisibility.PUBLIC
// default is IMPLEMENTATION
implementationVisibility = TypeVisibility.INTERNAL
}
}
}Unified Versioning
Chasms runtime and Gradle plugin now use the same version (1.0.0), given the plugin now implicitly wires up the runtime it made sense to align them so its easy to understand what version of the runtime is being used.
Stable ABI
For the past year the API for chasm has been in flux whilst I tried to figure out the best way to expose the virtual machine and the metadata associated with it. I'm now happy to say its stable and ready to be used, as a result we're now leveraging the experimental binary compatibility validator that shipped with KGP 2.2.0 to ensure we don't accidentally alter the ABI in unexpected ways between releases of the library.
Removal of the statically linked rust memory library
Last year I swapped out the linear memory implementation for native targets with a statically linked rust library. The intention was to enable efficient atomics for the upcoming threads proposal, unfortunately it doesn't look like the threads proposal is going to make it into the Wasm 3.0 release and it needs a little more time in the oven. Leveraging the native memory implementation has drawbacks in that it needs to be deallocated (technically unmapped), the cognitive overhead this adds to the api is not worth it given we don't support threads yet. For that reason its been removed... and yes we've lost our fancy SIMD string searcher for the time being
Better errors when encountering unsupported Wasm binaries
Previously when executing binaries that feature unsupported instructions the runtime would return a generic InstructionNotFound error. The past couple of weeks I've built support for decoding and validating newer (not yet supported) proposals so we can identify them and return better messaging. For example, given a Wasm binary that features Memory64 instructions, chasm will now return an error 'UnsupportedMemory64Proposal'
Full Changelog: 0.9.81...1.0.0
0.9.81
Chasm
Support for names decoding and the extended names proposal
Names sections are customs sections found in wasm binaries which contain names (Strings) used by debuggers and tooling. Previously all custom sections were left uninterpreted and none of their data was surfaced through the api.
This release adds support for optionally decoding the Names Section by providing the following flag in your module config
val moduleConfig = ModuleConfig(
decodeNameSection = true,
)When set the Name Section and all of its subsections will be decoded internally, for now we are only surfacing Function Names data through the api, this can be achieved by calling the moduleInfo api call.
val info = module(binary.get().asFile.readBytes(), moduleConfig)
.map { module ->
moduleInfo(module)
}Chasm Gradle Plugin 0.2.2
Simultaneously we're releasing 0.2.2 of Chasms gradle plugin, it has the following improvements
Improved default parameters in the Chasm Gradle plugin
Using the new module info api names data, the Gradle plugin is able to now give parameters their original names.
For example previously if you have a function like this:
(func $foo (param $a i32)(param $b i32))The gradle plugin would previously produce the following as the param names are not included in the primary module
data.
fun foo(p1: Int, p2: Int): Unit = UnitNow using the new names data the plugin will correctly generate:
fun foo(a: Int, b: Int): Unit = UnitFull Changelog: 0.9.80...0.9.81