mirror of
https://github.com/fluencelabs/asmble
synced 2025-03-16 03:00:51 +00:00
Add license and readme
This commit is contained in:
parent
786c9205a5
commit
1955ddc69b
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 Chad Retz
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
432
README.md
Normal file
432
README.md
Normal file
@ -0,0 +1,432 @@
|
|||||||
|
# Asmble
|
||||||
|
|
||||||
|
Asmble is a compiler that compiles [WebAssembly](http://webassembly.org/) code to JVM bytecode. It also contains
|
||||||
|
utilities for working with WASM code from the command line and from JVM languages.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
WebAssembly by itself does not have routines for printing to stdout or any external platform features. For this
|
||||||
|
example we'll use the test harness used by the [spec](https://github.com/WebAssembly/spec/). Java 8 must be installed.
|
||||||
|
|
||||||
|
Download the latest TAR/ZIP from the [releases](https://github.com/cretz/asmble/releases) area and extract it to
|
||||||
|
`asmble/`.
|
||||||
|
|
||||||
|
WebAssembly code is either in a [binary file](http://webassembly.org/docs/binary-encoding/) (i.e. `.wasm` files) or a
|
||||||
|
[text file](http://webassembly.org/docs/text-format/) (i.e. `.wast` files). The following code imports the `print`
|
||||||
|
function from the test harness. Then it creates a function calling `print` for the integer 70 and sets it to be called
|
||||||
|
on module init:
|
||||||
|
|
||||||
|
```
|
||||||
|
(module
|
||||||
|
(import "spectest" "print" (func $print (param i32)))
|
||||||
|
(func $print70 (call $print (i32.const 70)))
|
||||||
|
(start $print70)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Save this as `print-70.wast`. Now to run this, execute:
|
||||||
|
|
||||||
|
./asmble/bin/asmble run -testharness print-70.wast
|
||||||
|
|
||||||
|
The result will be:
|
||||||
|
|
||||||
|
70 : i32
|
||||||
|
|
||||||
|
Which is how the test harness prints an integer.
|
||||||
|
|
||||||
|
## CLI Usage
|
||||||
|
|
||||||
|
Assuming Java 8 is installed, download the latest [release](https://github.com/cretz/asmble/releases) and extract it.
|
||||||
|
The `asmble` command is present in the `asmble/bin` folder. There are multiple commands in Asmble that can be seen by
|
||||||
|
executing `asmble` with no commands:
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
COMMAND options...
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
compile - Compile WebAssembly to class file
|
||||||
|
help - Show command help
|
||||||
|
invoke - Invoke WebAssembly function
|
||||||
|
run - Run WebAssembly script commands
|
||||||
|
translate - Translate WebAssembly from one form to another
|
||||||
|
|
||||||
|
For detailed command info, use:
|
||||||
|
help COMMAND
|
||||||
|
|
||||||
|
### Compiling
|
||||||
|
|
||||||
|
Running `asmble help compile`:
|
||||||
|
|
||||||
|
Command: compile
|
||||||
|
Description: Compile WebAssembly to class file
|
||||||
|
Usage:
|
||||||
|
compile <inFile> [-format <inFormat>] <outClass> [-out <outFile>]
|
||||||
|
|
||||||
|
Args:
|
||||||
|
<inFile> - The wast or wasm WebAssembly file name. Can be '--' to read from stdin. Required.
|
||||||
|
-format <inFormat> - Either 'wast' or 'wasm' to describe format. Optional, default: <use file extension>
|
||||||
|
-log <logLevel> - One of: trace, debug, info, warn, error, off. Optional, default: warn
|
||||||
|
<outClass> - The fully qualified class name. Required.
|
||||||
|
-out <outFile> - The file name to output to. Can be '--' to write to stdout. Optional, default: <outClass.class>
|
||||||
|
|
||||||
|
This is used to compile WebAssembly to a class file. See the [compilation details](#compilation-details) for details about how
|
||||||
|
WebAssembly translates to JVM bytecode. The result will be a `.class` file containing JVM bytecode.
|
||||||
|
|
||||||
|
NOTE: There is no runtime required with the class files. They are self-contained.
|
||||||
|
|
||||||
|
### Invoking
|
||||||
|
|
||||||
|
Running `asmble help invoke`:
|
||||||
|
|
||||||
|
Command: invoke
|
||||||
|
Description: Invoke WebAssembly function
|
||||||
|
Usage:
|
||||||
|
invoke [-in <inFile>]... [-reg <registration>]... [-mod <module>] [<export>] [<arg>]...
|
||||||
|
|
||||||
|
Args:
|
||||||
|
<arg> - Parameter for the export if export is present. Multiple allowed. Optional, default: <empty>
|
||||||
|
-defmaxmempages <defaultMaxMemPages> - The maximum number of memory pages when a module doesn't say. Optional, default: 5
|
||||||
|
<export> - The specific export function to invoke. Optional, default: <start-func>
|
||||||
|
-in <inFile> - Files to add to classpath. Can be wasm, wast, or class file. Named wasm/wast modules here are automatically registered unless -noreg is set. Multiple allowed. Optional, default: <empty>
|
||||||
|
-log <logLevel> - One of: trace, debug, info, warn, error, off. Optional, default: warn
|
||||||
|
-mod <module> - The module name to run. If it's a JVM class, it must have a no-arg constructor. Optional, default: <last-in-entry>
|
||||||
|
-noreg - If set, this will not auto-register modules with names. Optional.
|
||||||
|
-reg <registration> - Register class name to a module name. Format: modulename=classname. Multiple allowed. Optional, default: <empty>
|
||||||
|
-res - If there is a result, print it. Optional.
|
||||||
|
-testharness - If set, registers the spec test harness as 'spectest'. Optional.
|
||||||
|
|
||||||
|
This can run WebAssembly code including compiled `.class` files. For example, put the following WebAssembly at
|
||||||
|
`add-20.wast`:
|
||||||
|
|
||||||
|
```
|
||||||
|
(module
|
||||||
|
(func (export "doAdd") (param $i i32) (result i32)
|
||||||
|
(i32.add (get_local 0) (i32.const 20))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
This can be invoked via the following with the result shown:
|
||||||
|
|
||||||
|
asmble invoke -res -in add-20.wast doAdd 100
|
||||||
|
|
||||||
|
That will print `120`. However, it can be compiled first like so:
|
||||||
|
|
||||||
|
asmble compile add-20.wast MyClass
|
||||||
|
|
||||||
|
Now there is a file called `MyClass.class`. Since it has a no-arg constructor because it doesn't import anything (see
|
||||||
|
[compilation details](#compilation-details) below), it can be invoked as well:
|
||||||
|
|
||||||
|
asmble invoke -res -in MyClass.class -reg myMod=MyClass -mod myMod doAdd 100
|
||||||
|
|
||||||
|
Note, that any Java class can be registered for the most part. It just needs to have a no-arg consstructor and any
|
||||||
|
referenced functions need to be public, non-static, and with return/param types of only int, long, float, or double.
|
||||||
|
|
||||||
|
### Running Scripts
|
||||||
|
|
||||||
|
The WebAssembly spec has a concept of [scripts](https://github.com/WebAssembly/spec/tree/master/interpreter#scripts) for
|
||||||
|
testing purposes. Running `asmble help run`:
|
||||||
|
|
||||||
|
Command: run
|
||||||
|
Description: Run WebAssembly script commands
|
||||||
|
Usage:
|
||||||
|
run [-in <inFile>]... [-reg <registration>]... <scriptFile>
|
||||||
|
|
||||||
|
Args:
|
||||||
|
-defmaxmempages <defaultMaxMemPages> - The maximum number of memory pages when a module doesn't say. Optional, default: 5
|
||||||
|
-in <inFile> - Files to add to classpath. Can be wasm, wast, or class file. Named wasm/wast modules here are automatically registered unless -noreg is set. Multiple allowed. Optional, default: <empty>
|
||||||
|
-log <logLevel> - One of: trace, debug, info, warn, error, off. Optional, default: warn
|
||||||
|
-noreg - If set, this will not auto-register modules with names. Optional.
|
||||||
|
-reg <registration> - Register class name to a module name. Format: modulename=classname. Multiple allowed. Optional, default: <empty>
|
||||||
|
<scriptFile> - The script file to run all commands for. This can be '--' for stdin. Must be wast format. Required.
|
||||||
|
-testharness - If set, registers the spec test harness as 'spectest'. Optional.
|
||||||
|
|
||||||
|
So take something like the
|
||||||
|
[start.wast](https://github.com/WebAssembly/spec/blob/6a01dab6d29b7c2b5dfd3bb3879bbd6ab76fd5dc/test/core/start.wast)
|
||||||
|
test case from the spec and run it with the test harness:
|
||||||
|
|
||||||
|
asmble run -testharness start.wast
|
||||||
|
|
||||||
|
And confirm it returns the
|
||||||
|
[expected output](https://github.com/WebAssembly/spec/blob/6a01dab6d29b7c2b5dfd3bb3879bbd6ab76fd5dc/test/core/expected-output/start.wast.log).
|
||||||
|
|
||||||
|
The comments concerning importing Java classes for "invoke" apply here too.
|
||||||
|
|
||||||
|
### Translating
|
||||||
|
|
||||||
|
Running `asmble help translate`:
|
||||||
|
|
||||||
|
Command: translate
|
||||||
|
Description: Translate WebAssembly from one form to another
|
||||||
|
Usage:
|
||||||
|
translate <inFile> [-in <inFormat>] [<outFile>] [-out <outFormat>]
|
||||||
|
|
||||||
|
Args:
|
||||||
|
-compact - If set for wast out format, will be compacted. Optional.
|
||||||
|
<inFile> - The wast or wasm WebAssembly file name. Can be '--' to read from stdin. Required.
|
||||||
|
-in <inFormat> - Either 'wast' or 'wasm' to describe format. Optional, default: <use file extension>
|
||||||
|
-log <logLevel> - One of: trace, debug, info, warn, error, off. Optional, default: warn
|
||||||
|
<outFile> - The wast or wasm WebAssembly file name. Can be '--' to write to stdout. Optional, default: --
|
||||||
|
-out <outFormat> - Either 'wast' or 'wasm' to describe format. Optional, default: <use file extension or wast for stdout>
|
||||||
|
|
||||||
|
Asmble can translate `.wasm` files to `.wast` or vice versa. It can also translate `.wast` to `.wast` which has value
|
||||||
|
because it resolves all names and creates a more raw yet deterministic and sometimes more readable `.wast`. Technically,
|
||||||
|
it can translate `.wasm` to `.wasm` but there is no real benefit.
|
||||||
|
|
||||||
|
All Asmble is doing internally here is converting to a common AST regardless of input then writing it out in the desired
|
||||||
|
output.
|
||||||
|
|
||||||
|
## Programmatic Usage
|
||||||
|
|
||||||
|
Asmble is written in Kotlin but since Kotlin is a thin layer over traditional Java, it can be used quite easily in all
|
||||||
|
JVM languages.
|
||||||
|
|
||||||
|
### Getting
|
||||||
|
|
||||||
|
The latest tag can be added to your build script via [JitPack](https://jitpack.io). For example,
|
||||||
|
[here](https://jitpack.io/#cretz/asmble/master-SNAPSHOT) are instructions for using the latest master.
|
||||||
|
|
||||||
|
### Building and Testing
|
||||||
|
|
||||||
|
To manually build, clone the repository:
|
||||||
|
|
||||||
|
git clone --recursive https://github.com/cretz/asmble
|
||||||
|
|
||||||
|
The reason we use recursive is to clone the spec submodule we have embedded at `src/test/resources/spec`. Then, with
|
||||||
|
[gradle](https://gradle.org/) installed, navigate to the cloned repository and create the gradle wrapper via
|
||||||
|
`gradle wrapper`. Now the `gradlew` command is available.
|
||||||
|
|
||||||
|
To build, run `./gradlew build`. This will run all tests which includes the test suite from the WebAssembly spec.
|
||||||
|
|
||||||
|
### Library Notes
|
||||||
|
|
||||||
|
The API documentation is not yet available at this early stage. But as an overview, here are some interesting classes
|
||||||
|
and packages:
|
||||||
|
|
||||||
|
* `asmble.ast.Node` - All WebAssembly AST nodes as static inner classes.
|
||||||
|
* `asmble.cli` - All code for the CLI.
|
||||||
|
* `asmble.compile.jvm.AstToAsm` - Entry point to go from AST module to [ASM](http://asm.ow2.org/) ClassNode.
|
||||||
|
* `asmble.compile.jvm.Mem` - Interface that can be implemented to change how memory is handled. Right now
|
||||||
|
`ByteBufferMem` in the same package is the only implementation and it emits `ByteBuffer`.
|
||||||
|
* `FuncBuilder` - Where the bulk of the WASM-instruction-to-JVM-instruction translation happens.
|
||||||
|
* `asmble.io` - Classes for translating to/from ast nodes, bytes (i.e. wasm), sexprs (i.e. wast), and strings.
|
||||||
|
* `asmble.run.jvm` - Tools for running WASM code on the JVM. Specifically `ScriptContext` which helps with linking.
|
||||||
|
|
||||||
|
And for those reading code, here are some interesting algorithms:
|
||||||
|
|
||||||
|
* `asmble.compile.jvm.RuntimeHelpers#bootstrapIndirect` (in Java, not Kotlin) - Manipulating arguments to essentially
|
||||||
|
chain `MethodHandle` calls for an `invokedynamic` bootstrap. This is actually taken from the compiled Java class and
|
||||||
|
injected as a synthetic method of the module class if needed.
|
||||||
|
* `asmble.compile.jvm.InsnReworker#addEagerLocalInitializers` - Backwards navigation up the instruction list to make
|
||||||
|
sure that a local is set before it is get.
|
||||||
|
* `asmble.compile.jvm.InsnReworker#injectNeededStackVars` - Inject instructions at certain places to make sure we have
|
||||||
|
certain items on the stack when we need them.
|
||||||
|
* `asmble.io.ByteReader$InputStream` - A simple eof-peekable input stream reader.
|
||||||
|
|
||||||
|
## Compilation Details
|
||||||
|
|
||||||
|
Asmble does its best to compile WASM ops to JVM bytecodes with minimal overhead. Below are some details on how each part
|
||||||
|
is done. Every module is represented as a single class. This section assumes familiarity with WebAssembly concepts.
|
||||||
|
|
||||||
|
#### Constructors
|
||||||
|
|
||||||
|
Asmble creates different constructors based on the memory requirements. Each constructor created contains the imports as
|
||||||
|
parameters (see [imports](#imports) below)
|
||||||
|
|
||||||
|
If the module does not define memory, a single constructor is created that accepts all other imports. If the module does
|
||||||
|
define memory, two constructors are created: one accepting a memory instance, and an overload that instead accepts an
|
||||||
|
integer value for max memory that is used to create the memory instance before sending to the first one. If the maximum
|
||||||
|
memory is given for the module, a third constructor is created without any memory parameters and just calls the max
|
||||||
|
memory overload w/ the given max memory value. All three of course have other imports as the rest of the parameters.
|
||||||
|
|
||||||
|
After all other constructor duties (described in sections below), the module's start function is called if present.
|
||||||
|
|
||||||
|
#### Memory
|
||||||
|
|
||||||
|
Memory is built or accepted in the constructor and is stored in a field. The current implementation uses a `ByteBuffer`.
|
||||||
|
Since `ByteBuffer`s are not dynamically growable, the max memory is an absolute max even though there is a limit which
|
||||||
|
is adjusted on `grow_memory`. Any data for the memory is set in the constructor.
|
||||||
|
|
||||||
|
#### Table
|
||||||
|
|
||||||
|
In the WebAssembly MVP a table is just a set of function pointers. This is stored in a field as an array of
|
||||||
|
`MethodHandle` instances. Any elements for the table are set in the constructor.
|
||||||
|
|
||||||
|
#### Globals
|
||||||
|
|
||||||
|
Globals are stored as fields on the class. A non-import global is simply a field, but an import global is a
|
||||||
|
`MethodHandle` to the getter (and would be a `MethodHandle` to the setter if mutable globals were supported). Any values
|
||||||
|
for the globals are set in the constructor.
|
||||||
|
|
||||||
|
#### Imports
|
||||||
|
|
||||||
|
The constructor accepts all imports as params. Memory is imported via a `ByteBuffer` param, then function
|
||||||
|
imports as `MethodHandle` params, then global imports as `MethodHandle` params, then a `MethodHandle` array param for an
|
||||||
|
imported table. All of these values are set as fields in the constructor.
|
||||||
|
|
||||||
|
#### Exports
|
||||||
|
|
||||||
|
Exports are exported as public methods of the class. The export names are mangled to conform to Java identifier
|
||||||
|
requirements. Function exports are as is whereas memory, global, and table exports have the name capitalized and are
|
||||||
|
then prefixed with "get" to match Java getter conventions.
|
||||||
|
|
||||||
|
Exports are always separate methods instead of just changing the name of an existing method or field. This encapsulation
|
||||||
|
allows things like many exports for a single item.
|
||||||
|
|
||||||
|
#### Types
|
||||||
|
|
||||||
|
WebAssembly has 4 types: `i32`, `i64`, `f32`, and `f64`. These translate quite literally to `int`, `long`, `float`, and
|
||||||
|
`double` respectively.
|
||||||
|
|
||||||
|
#### Control Flow Operations
|
||||||
|
|
||||||
|
Operations such as `unreachable` (which throws) behave mostly as expected. Branching and looping are handled with jumps.
|
||||||
|
The problem that occurs with jumping is that WebAssembly does not require compiler writers to clean up their own stack.
|
||||||
|
Therefore, if the WASM ops have extra stack values, we pop it before jumping which has performance implications but not
|
||||||
|
big ones. For most sane compilers, the stack will be managed stringently and leftover stack items will not be present.
|
||||||
|
|
||||||
|
Luckily, `br_table` jumps translate literally to JVM table switches which makes them very fast. There is a special set
|
||||||
|
of code for handling really large tables (because of Java's method limit) but this is unlikely to affect most in
|
||||||
|
practice.
|
||||||
|
|
||||||
|
#### Call Operations
|
||||||
|
|
||||||
|
Normal `call` operations do different things depending upon whether it is an import or not. If it is an import, the
|
||||||
|
`MethodHandle` is retrieved from a field and called via `invokeExact`. Otherwise, a normal `invokevirtual` is done to
|
||||||
|
call the local method.
|
||||||
|
|
||||||
|
A `call_indirect` is done via `invokedynamic` on the JVM. Specifically, `invokedynamic` specifies a synthetic bootstrap
|
||||||
|
method that we create. It does a one-time call on that bootstrap method to get a `MethodHandle` that can be called in
|
||||||
|
the future. We wouldn't normally have to use `invokedynamic` because we could use the index to reference a
|
||||||
|
`MethodHandle` in the array field. However, in WebAssembly, that index is *after* the parameters of the call and the
|
||||||
|
stack manipulation we would have to do would be far too expensive.
|
||||||
|
|
||||||
|
So we need a MethodHandle that takes the params of the target method, and *then* the index, to make the call. But we
|
||||||
|
also need "this" because it is expected at some point in the future that the table field could be changed underneath and
|
||||||
|
we don't want that field reference to be cached via the one-time bootstrap call. We do this with a synthetic bootstrap
|
||||||
|
method which uses some `MethodHandle` trickery to manipulate it the way we want. This makes indirect calls very fast,
|
||||||
|
especially on successive invocations.
|
||||||
|
|
||||||
|
#### Parametric Operations
|
||||||
|
|
||||||
|
A `drop` translates literally to a `pop`. A select translates to a conditional swap, then a pop.
|
||||||
|
|
||||||
|
#### Variable Access
|
||||||
|
|
||||||
|
Local variable access translates fairly easily because WebAssembly and the JVM treat the concept of parameters as the
|
||||||
|
initial locals similarly. Granted the JVM form has "this" at slot 0. Also, WebAssembly doesn't treat 64-bit vars as 2
|
||||||
|
slots like the JVM, so some simple math is done like it is with the stack.
|
||||||
|
|
||||||
|
WebAssembly requires all locals the assume they are 0 whereas the JVM requires locals be set before use. An algorithm in
|
||||||
|
Asmble makes sure that locals are set to 0 before they are fetched in any situation where they weren't explicitly set
|
||||||
|
first.
|
||||||
|
|
||||||
|
Global variable access depends on whether it's an import or not. Imports call getter `MethodHandle`s whereas non-imports
|
||||||
|
simply do normal field access.
|
||||||
|
|
||||||
|
#### Memory Operations
|
||||||
|
|
||||||
|
Memory operations are done via `ByteBuffer` methods on a little-endian buffer. All operations including unsigned
|
||||||
|
operations are tailored to use specific existing Java stdlib functions.
|
||||||
|
|
||||||
|
#### Number Operations
|
||||||
|
|
||||||
|
Constants are simply `ldc` bytecode ops on the JVM. Comparisons are done via specific bytecodes sometimes combined with
|
||||||
|
JVM calls for things like unsigned comparison. Operators use idiomatic JVM approaches as well.
|
||||||
|
|
||||||
|
The WebAssembly spec requires a runtime check of overflow during `trunc` calls. This is enabled by default in Asmble. It
|
||||||
|
defers to an internal synthetic method that does the overflow check. This can be programmatically disabled for better
|
||||||
|
performance.
|
||||||
|
|
||||||
|
#### Stack
|
||||||
|
|
||||||
|
Asmble maintains knowledge of types on the stack during compilation and fails compilation for any invalid stack items.
|
||||||
|
This includes the somewhat complicated logic concerning unreachable code.
|
||||||
|
|
||||||
|
In several cases, Asmble needs something on the stack that WebAssembly doesn't, such as "this" before the value of a
|
||||||
|
`putfield` call when setting a non-import global. In order to facilitate this, Asmble does a preprocessing of the
|
||||||
|
instructions. It builds the stack diffs and injects the needed items (e.g. a reference to the memory class for a load)
|
||||||
|
at the right place in the instruction list to make sure they are present when needed.
|
||||||
|
|
||||||
|
As an unintended side effect of this kind of logic, it turns out that Asmble never needs local variables beyond what
|
||||||
|
WebAssembly specifies. No temp variables or anything. It could be argued however that the use of temp locals might make
|
||||||
|
some of the compilation logic less complicated and could even improve runtime performance in places where we overuse the
|
||||||
|
stack (e.g. some places where we do a swap).
|
||||||
|
|
||||||
|
## Caveats
|
||||||
|
|
||||||
|
Below are some performance and implementation quirks where there is a bit of an impedance mismatch between WebAssembly
|
||||||
|
and the JVM:
|
||||||
|
|
||||||
|
* WebAssembly has a nice data section for byte arrays whereas the JVM does not. Right now we build a byte array from
|
||||||
|
a bunch of consts at runtime which is multiple operations per byte. This can bloat the class file size, but is quite
|
||||||
|
fast compared to alternatives such as string constants.
|
||||||
|
* The JVM makes no guarantees about trailing bits being preserved on NaN floating point representations like WebAssembly
|
||||||
|
does. This causes some mismatch on WebAssembly tests depending on how the JVM "feels" (I haven't dug into why some
|
||||||
|
bit patterns stay and some don't when NaNs are passed through methods).
|
||||||
|
* The JVM requires strict stack management where the compiler writer is expected to pop off what he doesn't use before
|
||||||
|
performing unconditional jumps. WebAssembly requires the runtime to discard unused stack items before unconditional
|
||||||
|
jump so we have to handle this. This can cause performance issues because essentially we do a "pop-before-jump" which
|
||||||
|
pops all unneeded stack values before jumping. If the target of the jump expects a fresh item on the stack (i.e. a
|
||||||
|
typed block) then it gets worse because we have to pop what we don't need *except* for the last stack value which
|
||||||
|
leads to a swap-pop-and-swap. Hopefully in real world use, tools that compile to WebAssembly don't have a bunch of
|
||||||
|
these cases. If they do, we may need to look into spilling to temporary local vars.
|
||||||
|
* Both memory and tables have "max capacity" and "initial capacity". While memory uses a `ByteBuffer` which has these
|
||||||
|
concepts (i.e. "capacity" and "limit"), tables use an array which only has the "initial capacity". This means that
|
||||||
|
tests that check for max capacity on imports at link time do not fail because we don't store max capacity for a table.
|
||||||
|
This is not a real problem for the MVP since the table cannot be grown. But once it can, we may need to consider
|
||||||
|
bringing another int along with us for table max capacity (or at least make it an option).
|
||||||
|
* WebAssembly has a concept of "unset max capacity" which means there can theoretically be an infinite capacity memory
|
||||||
|
instance. `ByteBuffer`s do not support this, but care is taken to allow link time and runtime max memory setting to
|
||||||
|
give the caller freedom.
|
||||||
|
* WebAssembly requires some trunc calls to do overflow checks, whereas the JVM does not. So for example, WebAssembly
|
||||||
|
has `i32.trunc_s/f32` which would usually be a simple `f2i` JVM instruction, but we have to do an overflow check that
|
||||||
|
the JVM does not do. We do this via a private static synthetic method in the module. There is too much going on to
|
||||||
|
inline it in the method and if several functions need it, it can become hot and JIT'd. This may be an argument for a
|
||||||
|
more global set of runtime helpers, but we aim to be runtime free. Care was taken to allow the overflow checks to be
|
||||||
|
turned off programmatically.
|
||||||
|
* WebAssembly allows unsigned 32 bit int memory indices. `ByteBuffer` only has signed which means the value can
|
||||||
|
overflow. And in order to support even larger sets of memory, WebAssembly supports constant offsets which are added
|
||||||
|
to the runtime indices. Asmble will eagerly fail compilation if an offset is out of range. But at runtime we don't
|
||||||
|
check by default and the overflow can wrap around and access wrong memory. There is an option to do the overflow check
|
||||||
|
when added to the offset which is disabled by default. Other than this there is nothing we can do easily.
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
**Why?**
|
||||||
|
|
||||||
|
I like writing compilers and I needed a sufficiently large project to learn Kotlin really well to make a reasonable
|
||||||
|
judgement on it. I also wanted to become familiar w/ WebAssembly. I don't really have a business interest for this and
|
||||||
|
therefore I cannot promise it will forever be maintained.
|
||||||
|
|
||||||
|
**Will it work on Android?**
|
||||||
|
|
||||||
|
I have not investigated. But I do use `invokedynamic` and `MethodHandle` so it would need to be a modern version of
|
||||||
|
Android. I assume, then, that both runtime and compile-time code might run there. Experiment feedback welcome.
|
||||||
|
|
||||||
|
**What about JVM to WASM?**
|
||||||
|
|
||||||
|
I'll be watching the GC approach taken and then reevaluate options. Everyone is focused on targeting WASM with several
|
||||||
|
languages but is missing the big problem: lack of a standard library. There is not a lot of interoperability between
|
||||||
|
WASM compiled from Rust, C, Java, etc if e.g. they all have their own way of handling strings. Someone needs to build a
|
||||||
|
definition of an importable set of modules that does all of these things, even if it's in WebIDL. I dunno, maybe the
|
||||||
|
effort is already there, I haven't really looked.
|
||||||
|
|
||||||
|
**So I can compile something in C via Emscripten and have it run on the JVM with this?**
|
||||||
|
|
||||||
|
Yes, but work is required. WebAssembly is lacking any kind of standard library. So Emscripten will either embed it or
|
||||||
|
import it from the platform (not sure which/where, I haven't investigated). It might be a worthwhile project to build a
|
||||||
|
libc-of-sorts as Emscripten knows it for the JVM. Granted it is probably not the most logical approach to run C on the
|
||||||
|
JVM compared with direct LLVM-to-JVM work.
|
||||||
|
|
||||||
|
**Debugging?**
|
||||||
|
|
||||||
|
Not yet, once source maps get standardized I may revisit.
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
* Add "dump" that basically goes from WebAssembly to "javap" like output so details are clear
|
||||||
|
* Expose the advanced compilation options
|
||||||
|
* Add "link" command that will build an entire JAR out of several WebAssembly files and glue code between them
|
||||||
|
* Annotations to make it clear what imports are expected
|
||||||
|
* Compile to JS and native with Kotlin
|
@ -23,6 +23,9 @@ repositories {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
distTar.archiveName = 'asmble.tar'
|
||||||
|
distZip.archiveName = 'asmble.zip'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
|
@ -3,7 +3,7 @@ package asmble.cli
|
|||||||
import asmble.util.Logger
|
import asmble.util.Logger
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
val commands = listOf(Compile, Invoke, Help, Run, Translate)
|
val commands = listOf(Compile, Help, Invoke, Run, Translate)
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
if (args.isEmpty()) return println(
|
if (args.isEmpty()) return println(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user