Giter Site home page Giter Site logo

asmble's Introduction

Asmble

Asmble is a compiler that compiles WebAssembly code to JVM bytecode. It also contains an interpreter and utilities for working with WASM code from the command line and from JVM languages.

Features

  • WASM to JVM bytecode compiler (no runtime required)
  • WASM interpreter (instruction-at-a-time steppable)
  • Conversion utilities between WASM binary, WASM text, and WASM AST
  • Programmatic JVM library for all of the above (written in Kotlin)
  • Examples showing how to use other languages on the JVM via WASM (e.g. Rust)

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. Java 8 must be installed.

Download the latest TAR/ZIP from the releases area and extract it to asmble/.

WebAssembly code is either in a binary file (i.e. .wasm files) or a text file (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_i32" (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. See the examples directory for more examples.

CLI Usage

Assuming Java 8 is installed, download the latest release 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

Some of the commands are detailed below.

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 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 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.

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 compiler and annotations are deployed to Maven Central. The compiler is written in Kotlin and can be added as a Gradle dependency with:

compile 'com.github.cretz.asmble:asmble-compiler:0.3.0'

This is only needed to compile of course, the compiled code has no runtime requirement. The compiled code does include some annotations (but in Java its ok to have annotations that are not found). If you do want to reflect the annotations, the annotation library can be added as a Gradle dependency with:

compile 'com.github.cretz.asmble:asmble-annotations:0.3.0'

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. Unlike many Gradle projects, this project chooses not to embed the Gradle runtime library in the repository. To assemble the entire project with Gradle installed and on the PATH (tested with 4.6), run:

gradle :compiler:assembleDist

Library Notes

The API documentation is not yet available at this early stage. But as an overview, here are some useful 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 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.
  • asmble.run.jvm.interpret - The interpreter that can run WASM all at once or allow it to be stepped one instruction at a time.

Note, some code is not complete yet (e.g. a linker and javax.script support) but beginnings of the code still appear in the repository.

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.msplit (in Java, not Kotlin) - A rudimentary JVM method bytecode splitter for when method sizes exceed the limit allowed by the JVM (embedded from another project).
  • 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.
  • asmble.run.jvm.interpret.Interpreter - Full WASM interpreter in a few hundred lines of Kotlin.

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 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 ByteBuffers 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 that is final if not mutable. An import global is a MethodHandle to the getter and a MethodHandle to the setter if mutable. 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 (one for getter and another for setter if mutable), 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 MethodHandles 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.

As a special optimization, we put the memory instance as a local var if it is accessed a lot in a function. This is cheaper than constantly fetching the field.

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 use a single-byte-char string constant (i.e. ISO-8859 charset). This saves class file size, but this means we call String::getBytes on init to load bytes from the string constant. Due to the JVM using an unsigned 16-bit int as the string constant length, the maximum byte length is 65536. Since the string constants are stored as UTF-8 constants, they can be up to four bytes a character. Therefore, we populate memory in data chunks no larger than 16300 (nice round number to make sure that even in the worse case of 4 bytes per char in UTF-8 view, we're still under the max).
  • 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. ByteBuffers 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?

This is not an immediate goal of this project, at least not until the WASM GC proposal has been accepted. In the meantime, there is https://github.com/konsoletyper/teavm

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 some parts to JS and native with Kotlin
  • Add javax.script support (which can give things like a free repl w/ jrunscript)

asmble's People

Contributors

cretz avatar dcnick3 avatar hagbard avatar techcable avatar vlad20012 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

asmble's Issues

Code Documentation Improvements

Once #23 is done, we need to:

  • Update README to give more details to programmatic users
  • Add KDoc to more of the lib
  • Publish a dokka javadoc jar to maven central w/ next release
  • Update README to remove some of the less-called CLI stuff

Split really large methods

Yay, another fun algorithm. Currently on the JVM a method size cannot exceed 65535 bytes. Otherwise, with ASM at least, you get the dreaded Method code too large!. Some other JVM languages don't implement code splitting because it's too rare/complex, but we probably need to. At least one generic impl is here (see specifically this package and these tests) but I think I can do better.

I need to decide whether I want to split at the WASM level or the JVM level. Probably the former since we have an insn reworking step that counts stack depth anyways. Either way, I figure while the method is too large, we'll just walk the insns finding a large enough section to pull out. Then pull it out to a synthetic method w/ the params as the stack and the result set if needed. The rules for a section to find is probably where many stack values come down to one/zero, no locals are accessed, and all contained within a block.

While this might not be good enough for generic JVM uses, it should be good enough for me.

NPD with GOTO

I have stumbled with NPD during compilation of this example. It seems that this exception caused by generation of stack locals after GOTO operation: the field locals sets to null by asm after GOTO and then processing of iconst_1 operation tries to invoke get on null object here

localsRead.put(var, locals.get(var));

The stack trace is

visitVarInsn:334, Splitter$StackAndLocalTrackingAdapter (asmble.compile.jvm.msplit)
accept:74, VarInsnNode (org.objectweb.asm.tree)
splitPointFromInfo:267, Splitter$Iter (asmble.compile.jvm.msplit)
longestForCurrIndex:152, Splitter$Iter (asmble.compile.jvm.msplit)
nextOrNull:129, Splitter$Iter (asmble.compile.jvm.msplit)
hasNext:107, Splitter$Iter (asmble.compile.jvm.msplit)
split:52, SplitMethod (asmble.compile.jvm.msplit)
split:31, SplitMethod (asmble.compile.jvm.msplit)
fromClassNode:32, AsmToBinary (asmble.compile.jvm)
fromClassNode$default:16, AsmToBinary (asmble.compile.jvm)
run:72, Compile (asmble.cli)
run:10, Compile (asmble.cli)
runWithArgs:17, Command (asmble.cli)
main:35, MainKt (asmble.cli)

Also tested with the latest asm - the same result.

Cannot parse Wasm file generated from simple LLVM C file.

I was hoping to be able to compile some C code to WASM and run it on the JVM just for fun.

This is the blog post I was following to do this: https://dassur.ma/things/c-to-webassembly/

The C file is as basic as possible:

// Filename: add.c
int add(int a, int b) {
  return a*a + b;
}

Compile it with LLVM to WASM:

clang \
  --target=wasm32 \
  -nostdlib \ # Don’t try and link against a standard library
  -Wl,--no-entry \ # Flags passed to the linker
  -Wl,--export-all \
  -o add.wasm \
  add.c

Try to compile to JVM bytecode:

java -jar asmble.jar compile add.wasm -log info Add

Error:

Exception in thread "main" kotlin.reflect.jvm.internal.KotlinReflectionInternalError: Function 'limit' (JVM signature: limit(I)Ljava/nio/Buffer;) not resolved in class java.nio.ByteBuffer
	at kotlin.reflect.jvm.internal.KDeclarationContainerImpl.findFunctionDescriptor(KDeclarationContainerImpl.kt:153)
	at kotlin.reflect.jvm.internal.KFunctionImpl$descriptor$2.invoke(KFunctionImpl.kt:54)
	at kotlin.reflect.jvm.internal.KFunctionImpl$descriptor$2.invoke(KFunctionImpl.kt:34)
	at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:93)
	at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32)
	at kotlin.reflect.jvm.internal.KFunctionImpl.getDescriptor(KFunctionImpl.kt)
	at kotlin.reflect.jvm.internal.KFunctionImpl$caller$2.invoke(KFunctionImpl.kt:60)
	at kotlin.reflect.jvm.internal.KFunctionImpl$caller$2.invoke(KFunctionImpl.kt:34)
	at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:93)
	at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32)
	at kotlin.reflect.jvm.internal.KFunctionImpl.getCaller(KFunctionImpl.kt)
	at kotlin.reflect.jvm.ReflectJvmMapping.getJavaMethod(ReflectJvmMapping.kt:62)
	at asmble.compile.jvm.AsmExtKt.getDeclarer(AsmExt.kt:23)
	at asmble.compile.jvm.AsmExtKt.invokeVirtual(AsmExt.kt:29)
	at asmble.compile.jvm.ByteBufferMem.init(ByteBufferMem.kt:27)
	at asmble.compile.jvm.AstToAsm.addMemClassConstructor(AstToAsm.kt:146)
	at asmble.compile.jvm.AstToAsm.addConstructors(AstToAsm.kt:79)
	at asmble.compile.jvm.AstToAsm.fromModule(AstToAsm.kt:26)
	at asmble.cli.Compile.run(Compile.kt:71)
	at asmble.cli.Compile.run(Compile.kt:10)
	at asmble.cli.Command.runWithArgs(Command.kt:17)
	at asmble.cli.MainKt.main(Main.kt:31)

Optimize memcpy and other standard memory manipulating functions

If my program uses memcpy, in java (byte)code I see this implementation:

    private int memcpy(int var1, int var2, int var3) {
        if (var3 != 0) {
            int var4 = var1;

            do {
                this.memory.put(var4, (byte)Byte.toUnsignedInt(this.memory.get(var2)));
                ++var2;
                ++var4;
            } while((var3 += -1) != 0);
        }

        return var1;
    }

But it can be implemented in much more efficient way by using ByteBuffer API:

    private int memcpy(int src, int dst, int len) {
        if (len != 0) {
            ByteBuffer srcBuf = memory.duplicate();
            srcBuf.position(src);
            srcBuf.limit(src + len);

            ByteBuffer dstBuf = memory.duplicate();
            dstBuf.position(dst);
            dstBuf.put(srcBuf);
        }

        return src;
    }

With this implementation the real native memcpy will be used (if memory is DirectByteBuffer).

We can detect functions like memcpy by their names, signatures and bodies, and then replace them with more efficient implementation.

Document interop between asmble output and Java code

This looks like an awesome project, thanks for working on this and publishing it as open source software! πŸ˜„ I'm really happy to see this because I'm interested in LLVM-to-JVM (Rust on the JVM in particular). Although you mention direct LLVM-to-JVM work in your README, I haven't been able to find a maintained project to get this working.

Can you give some details in the README about what to expect on the interop side of things? Even if it's non-existent, that's good to know. I imagine it'll be difficult to have the asmble output talk with the Java world, in particular if you want to exchange data, given that pointers and such have to be modelled with byte buffers. But you must be able to do something given that you benchmarked the rust regex crate on the JVM.

Rust Examples

I want three examples:

  1. Simple add_one ala https://gist.github.com/steveklabnik/d86491646b9e3e420f8806a286ec8e92. This will force us to learn about how the wasm-unknown-unknown works.
  2. Something that prints "Rust: " + a provided string. This will force us to learn about how to manip the mem to get some bytes across.
  3. A benchmark comparing Rust regex and Java regex on the JVM. This will show the performance gap. Use JMH.

F64Sqrt is unary

In InsnReworker insnStackDiff() the Node Node.Instr.F64Sqrt is mapped to POP_PARAM + POP_PARAM + PUSH_RESULT. It should be only POP_PARAM + PUSH_RESULT.

Implement Threads

Well, now that V8 has it I might as well do it. I was hoping the spec would get standardized and the wait/wake stuff would get test cases and interpreter support in the repo. I put an overview of my expected impl approach on HN the other day. I have more details in personal notes, but should be doable without too much effort.

Error in command 'compile': Class too large: MyClass

Hello, I'm trying to compile wasm file which contains Swift app inside and getting Class too large error.

org.objectweb.asm.ClassTooLargeException: Class too large: MyClass
	at org.objectweb.asm.ClassWriter.toByteArray(ClassWriter.java:550)
	at asmble.compile.jvm.AsmToBinary.fromClassNode(AsmToBinary.kt:30)
	at asmble.compile.jvm.AsmToBinary.fromClassNode$default(AsmToBinary.kt:21)
	at asmble.cli.Compile.run(Compile.kt:72)
	at asmble.cli.Compile.run(Compile.kt:10)
	at asmble.cli.Command.runWithArgs(Command.kt:17)
	at asmble.cli.MainKt.main(Main.kt:31)

The wasm file is in attach.

Is there any chance to pass this limitation?
aaa.wasm.zip

asmble run wasm file failed

[ERROR] Error in command 'invoke': Failed loading .\basic.wasm - lateinit property logger has not been initialized
java.lang.Exception: Failed loading .\basic.wasm - lateinit property logger has not been initialized
at asmble.cli.ScriptCommand.prepareContext(ScriptCommand.kt:73)
at asmble.cli.Invoke.run(Invoke.kt:37)
at asmble.cli.Invoke.run(Invoke.kt:6)
at asmble.cli.Command.runWithArgs(Command.kt:17)
at asmble.cli.MainKt.main(Main.kt:31)
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property logger has not been initialized
at asmble.cli.Command.getLogger(Command.kt:9)
at asmble.cli.Translate.inToAst(Translate.kt:70)
at asmble.cli.ScriptCommand.prepareContext(ScriptCommand.kt:60)
... 4 more

Go Examples

Go 1.11 now has WebAssembly support and while the output is quite large, we can do some tests/examples which should be fun.

Text output fixes/improvements

Two major things:

  • Blocks such as block and if are not showing the end right and instead treat end as its own statement
  • Support a "sans-parens" form of text output

Also, maybe consider a "named" form which puts names for params and locals and globals and what not

C Examples

Or C++. Get the LLVM builds w/ WASM backend enabled (e.g. here are some nightlies). Following the lead from #9, create:

  • A simple hello world C example
  • A simple hello world C++ example
  • A regex comparison like we did in #9 ...use some of the native libs that were used in this blog post
    • Maybe even share some code

too many MemNeededOnStack

The following code fragment (from the larger attached output) causes too many MemNeededOnStack in the if block:

(if i32)
  (get_local 3)
  (i32.load align=2)
(else)
  (get_local 6)
  (i32.load align=2)
  (get_local 0)
  (i32.load align=2)
  (call 21)
(end)

The inject output looks correct:

[TRACE] Stack diff is 0 for If(type=asmble.ast.Node$Type$Value$I32@50378a4)
[TRACE] Stack diff is 1 for GetLocal(index=3)
[TRACE] Injecting asmble.compile.jvm.Insn$MemNeededOnStack@7eecb5b8 back 1 stack values
[TRACE] Stack diff is 0 for I32Load(align=2, offset=0)

[TRACE] Stack diff is 0 for asmble.ast.Node$Instr$Else@51891008
[TRACE] Stack diff is 1 for GetLocal(index=6)
[TRACE] Injecting asmble.compile.jvm.Insn$MemNeededOnStack@7eecb5b8 back 1 stack values
[TRACE] Stack diff is 0 for I32Load(align=2, offset=0)
[TRACE] Stack diff is 1 for GetLocal(index=0)
[TRACE] Injecting asmble.compile.jvm.Insn$MemNeededOnStack@7eecb5b8 back 1 stack values
[TRACE] Stack diff is 0 for I32Load(align=2, offset=0)
[TRACE] Injecting asmble.compile.jvm.Insn$ThisNeededOnStack@aba625 back 2 stack values
[TRACE] Stack diff is -1 for Call(index=21)
[TRACE] Stack diff is 0 for asmble.ast.Node$Instr$End@5db6b9cd

(One MemNeededOnStack in the If Block)

But when applying the nodes there are 2 MemNeededOnStack in that block which results in a different stack for the else block:

[DEBUG] Applying insn Node(insn=If(type=asmble.ast.Node$Type$Value$I32@50378a4))
[TRACE] Resulting stack: [TypeRef(asm=I)]
[DEBUG] Applying insn asmble.compile.jvm.Insn$MemNeededOnStack@7eecb5b8
[TRACE] Resulting stack: [TypeRef(asm=I), TypeRef(asm=Ljava/nio/ByteBuffer;)]
[DEBUG] Applying insn asmble.compile.jvm.Insn$MemNeededOnStack@7eecb5b8
[TRACE] Resulting stack: [TypeRef(asm=I), TypeRef(asm=Ljava/nio/ByteBuffer;), TypeRef(asm=Ljava/nio/ByteBuffer;)]
[DEBUG] Applying insn Node(insn=GetLocal(index=3))
[TRACE] Resulting stack: [TypeRef(asm=I), TypeRef(asm=Ljava/nio/ByteBuffer;), TypeRef(asm=Ljava/nio/ByteBuffer;), TypeRef(asm=I)]
[DEBUG] Applying insn Node(insn=I32Load(align=2, offset=0))
[TRACE] Resulting stack: [TypeRef(asm=I), TypeRef(asm=Ljava/nio/ByteBuffer;), TypeRef(asm=I)]


[DEBUG] Applying insn Node(insn=asmble.ast.Node$Instr$Else@51891008)
[DEBUG] Else block for If(type=asmble.ast.Node$Type$Value$I32@50378a4), orig stack [TypeRef(asm=I)]
[TRACE] Resulting stack: [TypeRef(asm=I)]
[DEBUG] Applying insn asmble.compile.jvm.Insn$ThisNeededOnStack@aba625
[TRACE] Resulting stack: [TypeRef(asm=I), TypeRef(asm=Ltest/class;)]
[DEBUG] Applying insn asmble.compile.jvm.Insn$MemNeededOnStack@7eecb5b8
[TRACE] Resulting stack: [TypeRef(asm=I), TypeRef(asm=Ltest/class;), TypeRef(asm=Ljava/nio/ByteBuffer;)]
[DEBUG] Applying insn Node(insn=GetLocal(index=6))
[TRACE] Resulting stack: [TypeRef(asm=I), TypeRef(asm=Ltest/class;), TypeRef(asm=Ljava/nio/ByteBuffer;), TypeRef(asm=I)]
[DEBUG] Applying insn Node(insn=I32Load(align=2, offset=0))
[TRACE] Resulting stack: [TypeRef(asm=I), TypeRef(asm=Ltest/class;), TypeRef(asm=I)]
[DEBUG] Applying insn asmble.compile.jvm.Insn$MemNeededOnStack@7eecb5b8
[TRACE] Resulting stack: [TypeRef(asm=I), TypeRef(asm=Ltest/class;), TypeRef(asm=I), TypeRef(asm=Ljava/nio/ByteBuffer;)]
[DEBUG] Applying insn Node(insn=GetLocal(index=0))
[TRACE] Resulting stack: [TypeRef(asm=I), TypeRef(asm=Ltest/class;), TypeRef(asm=I), TypeRef(asm=Ljava/nio/ByteBuffer;), TypeRef(asm=I)]
[DEBUG] Applying insn Node(insn=I32Load(align=2, offset=0))
[TRACE] Resulting stack: [TypeRef(asm=I), TypeRef(asm=Ltest/class;), TypeRef(asm=I), TypeRef(asm=I)]
[DEBUG] Applying insn Node(insn=Call(index=21))
[DEBUG] Applying call to $func21 of type Func(params=[asmble.ast.Node$Type$Value$I32@50378a4, asmble.ast.Node$Type$Value$I32@50378a4], ret=asmble.ast.Node$Type$Value$I32@50378a4) with stack [TypeRef(asm=I), TypeRef(asm=Ltest/class;), TypeRef(asm=I), TypeRef(asm=I)]
[TRACE] Resulting stack: [TypeRef(asm=I), TypeRef(asm=I)]
[DEBUG] Applying insn Node(insn=asmble.ast.Node$Instr$End@5db6b9cd)
[DEBUG] End of block If(type=asmble.ast.Node$Type$Value$I32@50378a4), orig stack [TypeRef(asm=I)], unreachable? false
[ERROR] Error in command 'compile': At block end, expected stack [TypeRef(asm=I), TypeRef(asm=Ljava/nio/ByteBuffer;), TypeRef(asm=I)], got [TypeRef(asm=I), TypeRef(asm=I)]

log.txt

Sign extension

The sign extension instructions (i32.extend8_s, i32.extend16_s, i64.extend8_s, i64.extend16_s, and i64.extend32_s) seem like they would map quite nicely onto efficient JVM code.

Failure to use WASM compiled class from a JAR

Hello,

I have a JavaFX app that uses a Rust WASM compiled in a Class file using asmble. When I run the project from IntelliJ, the app is working fine. When I create an artifact (fat JAR) however, I am getting this error every time the interface is calling a WASM function:

Caused by: java.lang.NoSuchMethodError: java.nio.ByteBuffer.position(I)Ljava/nio/ByteBuffer;
        at wasm.WasmLinker$Ptr.put(WasmLinker.kt:44)
        at wasm.WasmLinker.ptrFromString(WasmLinker.kt:33)
        at wasm.WasmLinker.price(WasmLinker.kt:12)
        at ui.JavaFXTut.calculate(JavaFXTut.kt:43)

Any idea, what the problem could be?

Failed to compile asm policy.rego/policy.wasm

I get the following error compiling a file:

[ERROR] Error in command 'compile': Unrecognized instruction: global.get
asmble.io.IoErr$UnrecognizedInstruction: Unrecognized instruction: global.get
	at asmble.io.SExprToAst.toInstrs(SExprToAst.kt:381)
	at asmble.io.SExprToAst.toInstrs$default(SExprToAst.kt:370)
	at asmble.io.SExprToAst.toFunc(SExprToAst.kt:251)
	at asmble.io.SExprToAst.toModule(SExprToAst.kt:589)
	at asmble.io.SExprToAst.toCmdMaybe(SExprToAst.kt:99)
	at asmble.io.SExprToAst.toScript(SExprToAst.kt:864)
	at asmble.cli.Translate.inToAst(Translate.kt:66)
	at asmble.cli.Compile.run(Compile.kt:54)
	at asmble.cli.Compile.run(Compile.kt:10)
	at asmble.cli.Command.runWithArgs(Command.kt:17)
	at asmble.cli.MainKt.main(Main.kt:31)

policy.wast.txt

mv policy.wast.txt policy.wast
asmble compile -log info policy.wast MyClass

The wast file is https://github.com/open-policy-agent/opa-docker-authz/blob/master/example.rego and compiled to wast

opa build -t wasm -e example/allow example.rego
tar xvf bundle.tar.gz
wasm2wat policy.wasm
asmble compile -log info policy.wast MyClass

Fails to compile simple clang-generated WASM file due to `list not empty`

I've got clang to compile this to WASM:

// Filename: add.c
int add(int a, int b) {
  return a + b;
}

When trying to compile to JVM with asmble, it fails:

Caused by: java.util.NoSuchElementException: List is empty.
        at kotlin.collections.CollectionsKt___CollectionsKt.last(_Collections.kt:360)
        at asmble.io.BinaryToAst.toModule(BinaryToAst.kt:216)
        at asmble.cli.Translate.inToAst(Translate.kt:70)
        at asmble.cli.Compile.run(Compile.kt:54)

This was working with version 0.3.0 but broke in 0.4.0.

This is the code that fails, where you call customSections.last():

if (sectionId != 0) customSections else {
    // If the last section was custom, use the last custom section's after-ID,
    // otherwise just use the last section ID
    val afterSectionId = if (index == 0) 0 else sections[index - 1].let { (prevSectionId, _) ->
    if (prevSectionId == 0) customSections.last().afterSectionId else prevSectionId
}

This code is hard to follow, but looks like customSection starts off empty, so it's likely the last() call is going to be called on the empty list, as is happening to me. Not sure what afterSectionId should be assigned to in that case though, hope you can help find a solution.

Support mutable globals

  • Update README
  • For imports, decide whether we want to use VarHandle (sadly requires Java 9+) or a getter/setter MethodHandle (can make that already-large constructor more confusing)
    • Can't support both with option in config very easily because it affects cross-module compat (could technically have annotations determine the difference, but meh)
    • I think the getter/setter MethodHandle is ideal right now

Failure to find RuntimeHelpers when Asmble is loaded by a non-system class loader

asmble.compile.jvm.SyntheticFuncBuilder.buildIndirectBootstrap calls ClassReader(RuntimeHelpers::class.java.name). Per the documentation:

The ClassFile structure is retrieved with the current class loader's ClassLoader.getSystemResourceAsStream(java.lang.String)

According to the documentation of that method in turn:

This method locates the resource through the system class loader (see getSystemClassLoader()).

That means that, if Asmble itself is loaded by something other than the system class loader, this call will fail. IMO it would be better to replace the ClassReader(RuntimeHelpers::class.java.name) call with something along the lines of ClassReader(RuntimeHelpers::class.java.getClassLoader().getResourceAsStream(RuntimeHelpers::class.java.name.replace('.', '/') + ".class")) (forgive me if I messed up the syntax; I don’t know Kotlin).

Consider compiling Components & WASI to bytecode

WASI is the WebAssembly System Interface and it's a collection of individual interfaces for things like (clocks, random, file-systems, sockets, etc.) which are expressed in an interface definition language called WIT and are imported and exported by Wasm Components.

WASI is rapidly approaching "preview 2" (est. Jan 11th) which is a major milestone with a stable version of the Component-Model and a set of WASI interfaces. Now would be a great time for asmble's developers to start investigating running components.

If you have any questions about WASI or the Component-Model, I'd be happy to answer what I can, point you to resources, and connect you with people and groups working on them.

Source Map Support

Support source maps. The problem is the JVM only supports a single SourceFile attribute. So I'm thinking maybe use SourceDebugAttribute to store filename-to-method map, then use "" as the SourceFile then maybe provide some runtime thing to translate stack traces. Or maybe, add an attribute on top of each method saying what source file it comes from (but has to be runtime visible). Or maybe, add a static method on the module that will translate method + line number to source file + line number. Actually, that would just translate method name to source file name, because line number would already be accurate.

No opcode found: 252

When trying to compile pdfcpu from webassembly to Java, I get the following error (I tried the release for Version 0.8.0):

asmble compile ./pdfcpu.wasm -format wasm PdfCpu -log info
[ERROR] Error in command 'compile': No opcode found: 252
java.lang.IllegalStateException: No opcode found: 252
        at asmble.ast.Node$InstrOp$Companion.op(Node.kt:456)
        at asmble.io.BinaryToAst.toInstr(BinaryToAst.kt:119)
        at asmble.io.BinaryToAst.toInstrs(BinaryToAst.kt:117)
        at asmble.io.BinaryToAst.toFunc(BinaryToAst.kt:78)
        at asmble.io.BinaryToAst.toModule(BinaryToAst.kt:209)
        at asmble.cli.Translate.inToAst(Translate.kt:70)
        at asmble.cli.Compile.run(Compile.kt:54)
        at asmble.cli.Compile.run(Compile.kt:10)
        at asmble.cli.Command.runWithArgs(Command.kt:17)
        at asmble.cli.MainKt.main(Main.kt:31)

According to https://pengowray.github.io/wasm-ops/ this means that FC extensions (Multibyte instructions beginning with 0xFC.) are used.

Is it feasible to add support for this in asmble?

Rust Examples Test

gradle :examples:rust-simple:run
succeed, while
gradle run --args="compile ./examples/rust-simple/target/wasm32-unknown-unknown/release/rust_simple.wasm RustSimple"
failed, why?
[ERROR] Error in command 'compile': Invalid section ID of 19
asmble.io.IoErr$InvalidSectionId: Invalid section ID of 19
at asmble.io.BinaryToAst.toModule(BinaryToAst.kt:184)
at asmble.cli.Translate.inToAst(Translate.kt:70)
at asmble.cli.Compile.run(Compile.kt:54)
at asmble.cli.Compile.run(Compile.kt:10)
at asmble.cli.Command.runWithArgs(Command.kt:17)
at asmble.cli.MainKt.main(Main.kt:31)

asmble compile error

I download 0.4.0 release ,and extract to my computer .
when I run : MacBook-Pro:asmble wudream$ ./asmble/bin/asmble compile main.wasm hello
Exception in thread "main" kotlin.reflect.jvm.internal.KotlinReflectionInternalError: Function 'limit' (JVM signature: limit(I)Ljava/nio/Buffer;) not resolved in class java.nio.ByteBuffer:
public open fun limit(arg0: kotlin.Int): (java.nio.ByteBuffer..java.nio.ByteBuffer?) defined in java.nio.ByteBuffer | kotlin.reflect.jvm.internal.JvmFunctionSignature$JavaMethod@f19c9d2
public final fun limit(): kotlin.Int defined in java.nio.ByteBuffer | kotlin.reflect.jvm.internal.JvmFunctionSignature$JavaMethod@7807ac2c
at kotlin.reflect.jvm.internal.KDeclarationContainerImpl.findFunctionDescriptor(KDeclarationContainerImpl.kt:159)
at kotlin.reflect.jvm.internal.KFunctionImpl$descriptor$2.invoke(KFunctionImpl.kt:54)
at kotlin.reflect.jvm.internal.KFunctionImpl$descriptor$2.invoke(KFunctionImpl.kt:34)
at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:92)
at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:31)
at kotlin.reflect.jvm.internal.KFunctionImpl.getDescriptor(KFunctionImpl.kt)
at kotlin.reflect.jvm.internal.KFunctionImpl$caller$2.invoke(KFunctionImpl.kt:60)
at kotlin.reflect.jvm.internal.KFunctionImpl$caller$2.invoke(KFunctionImpl.kt:34)
at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:92)
at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:31)
at kotlin.reflect.jvm.internal.KFunctionImpl.getCaller(KFunctionImpl.kt)
at kotlin.reflect.jvm.ReflectJvmMapping.getJavaMethod(ReflectJvmMapping.kt:62)
at asmble.compile.jvm.AsmExtKt.getDeclarer(AsmExt.kt:22)
at asmble.compile.jvm.AsmExtKt.invokeVirtual(AsmExt.kt:28)
at asmble.compile.jvm.ByteBufferMem.init(ByteBufferMem.kt:27)
at asmble.compile.jvm.AstToAsm.addMemClassConstructor(AstToAsm.kt:146)
at asmble.compile.jvm.AstToAsm.addConstructors(AstToAsm.kt:79)
at asmble.compile.jvm.AstToAsm.fromModule(AstToAsm.kt:26)
at asmble.cli.Compile.run(Compile.kt:71)
at asmble.cli.Compile.run(Compile.kt:10)
at asmble.cli.Command.runWithArgs(Command.kt:17)
at asmble.cli.MainKt.main(Main.kt:31)

I do quick glance at google search , it seems to be java.nio.ByteBuffer incompatibility with JDK9+ . jetty/jetty.project#3244
pls. have a look !

Unable to inject asmble.compile.jvm.Insn$ThisNeededOnStack

It think the stack diff for GetGlobal should be 1 since "this" is inserted here

VarInsnNode(Opcodes.ALOAD, 0),
(but I am not sure if I understood the algorithm correctly). It seems to work when i remove the POP_THIS in insnStackDiff().

    is Node.Instr.GetGlobal -> POP_THIS + PUSH_RESULT

changed to
is Node.Instr.GetGlobal -> PUSH_RESULT

Here is the function I tried to compile with the trace (happens with any wasm file from ogv.js):

[TRACE] Stack diff is 0 for GetGlobal(index=4)
[TRACE] Stack diff is 0 for GetGlobal(index=4)
[TRACE] Stack diff is 1 for I32Const(value=1027)
[TRACE] Stack diff is 1 for I32Const(value=2272)
[TRACE] Stack diff is 1 for I32Const(value=7356)
[TRACE] Stack diff is 1 for I32Const(value=7408)
[TRACE] Stack diff is 1 for I32Const(value=7460)
[TRACE] Stack diff is 1 for I32Const(value=7512)
[TRACE] Stack diff is 1 for I32Const(value=18792)
[TRACE] Stack diff is 1 for I32Const(value=18848)
[TRACE] Stack diff is 1 for I32Const(value=19906)
[TRACE] Stack diff is 1 for I32Const(value=19956)
[TRACE] Stack diff is 1 for I32Const(value=19990)
[TRACE] Stack diff is 1 for I32Const(value=25441)
[TRACE] Stack diff is 1 for I32Const(value=25462)
[DEBUG] Building function $func13
[TRACE] Function ast:
(func
  (param i32)
  (result i32)
  (local i32)
  (block i32)
  (get_global 6)
  (set_local 1)
  (get_global 6)
  (get_local 0)
  (i32.add)
  (set_global 6)
  (get_global 6)
  (i32.const 15)
  (i32.add)
  (i32.const -16)
  (i32.and)
  (set_global 6)
  (get_local 1)
  (end)
)
[TRACE] Stack diff is 0 for Block(type=asmble.ast.Node$Type$Value$I32@5a5a729f)
[TRACE] Stack diff is 0 for GetGlobal(index=6)
[TRACE] Stack diff is -1 for SetLocal(index=1)
[TRACE] Stack diff is 0 for GetGlobal(index=6)
[TRACE] Stack diff is 1 for GetLocal(index=0)
[TRACE] Stack diff is -1 for asmble.ast.Node$Instr$I32Add@4b520ea8
[TRACE] Injecting asmble.compile.jvm.Insn$ThisNeededOnStack@782859e back 1 stack values
[ERROR] Error in command 'compile': Unable to inject asmble.compile.jvm.Insn$ThisNeededOnStack@782859e back 1 stack values
asmble.compile.jvm.CompileErr$StackInjectionMismatch: Unable to inject asmble.compile.jvm.Insn$ThisNeededOnStack@782859e back 1 stack values
	at asmble.compile.jvm.InsnReworker$injectNeededStackVars$1.invoke(InsnReworker.kt:127)
	at asmble.compile.jvm.InsnReworker.injectNeededStackVars(InsnReworker.kt:149)
	at asmble.compile.jvm.InsnReworker.rework(InsnReworker.kt:8)
	at asmble.compile.jvm.FuncBuilder.fromFunc(FuncBuilder.kt:29)
	at asmble.compile.jvm.AstToAsm.addFuncs(AstToAsm.kt:516)
	at asmble.compile.jvm.AstToAsm.fromModule(AstToAsm.kt:20)
	at asmble.cli.Compile.run(Compile.kt:58)
	at asmble.cli.Compile.run(Compile.kt:10)
	at asmble.cli.Command.runWithArgs(Command.kt:17)
	at asmble.cli.MainKt.main(Main.kt:31)

.Net Examples

Now that Mono has WASM support, let's write some C# or F# and get that going on the JVM! Like issues #11 and #9, create three examples:

  • A simple hello world in C#
  • A simple hello world in F#
  • A regex comparison like in the other issues

Maybe toss in some F# w/ type providers for fun...awesome.

Build Linker

Linker:

  • Command is "link"
  • Output is a class w/ named fields representing each module. Setting these fields would be a way to cross-link maybe?
  • Has parameterless methods for get-or-create-style lazy access to the module (not sure if it needs to be synchronized)
  • The creating of the other modules calls the parameterless methods of its dependencies
  • Maybe with-module-dep-as-params version of each method and that's what does the actual create
  • Classes accepts args array which does NOT include the prog name and is
  • Options
    • Method to call on main
    • Emscripten-support (includes adding it to module list AND calling "main" or erroring if we find multiple "mains")
    • Embed emscripten - basically shade the emscripten env classes

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.