mirror of
https://github.com/fluencelabs/asmble
synced 2025-07-04 08:51:35 +00:00
Compare commits
10 Commits
lateinit_f
...
0.4.1-fl
Author | SHA1 | Date | |
---|---|---|---|
b9b45cf997 | |||
2bfa39a3c6 | |||
317b608048 | |||
21b023f1c6 | |||
765d8b4dba | |||
fb0be9d31a | |||
d1f48aaaa0 | |||
46a8ce3f52 | |||
6352efaa96 | |||
326a0cdaba |
155
build.gradle
155
build.gradle
@ -2,8 +2,8 @@ group 'asmble'
|
|||||||
version '0.2.0'
|
version '0.2.0'
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.2.51'
|
ext.kotlin_version = '1.2.61'
|
||||||
ext.asm_version = '5.2'
|
ext.asm_version = '6.2.1'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
@ -21,7 +21,11 @@ buildscript {
|
|||||||
allprojects {
|
allprojects {
|
||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
group 'com.github.cretz.asmble'
|
group 'com.github.cretz.asmble'
|
||||||
version '0.4.0-fl-fix'
|
version '0.4.1-fl'
|
||||||
|
|
||||||
|
// skips building and running for the specified examples
|
||||||
|
ext.skipExamples = ['c-simple', 'go-simple', 'rust-regex']
|
||||||
|
// todo disabling Rust regex is temporary because some strings in wasm code exceed the size in 65353 bytes.
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
@ -54,9 +58,9 @@ project(':compiler') {
|
|||||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
compile "org.ow2.asm:asm-tree:$asm_version"
|
compile "org.ow2.asm:asm-tree:$asm_version"
|
||||||
compile "org.ow2.asm:asm-util:$asm_version"
|
compile "org.ow2.asm:asm-util:$asm_version"
|
||||||
|
compile "org.ow2.asm:asm-commons:$asm_version"
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
|
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
|
||||||
testCompile "org.ow2.asm:asm-debug-all:$asm_version"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
publishSettings(project, 'asmble-compiler', 'Asmble WASM Compiler')
|
publishSettings(project, 'asmble-compiler', 'Asmble WASM Compiler')
|
||||||
@ -67,6 +71,38 @@ project(':examples') {
|
|||||||
dependencies {
|
dependencies {
|
||||||
compileOnly project(':compiler')
|
compileOnly project(':compiler')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// C/C++ example helpers
|
||||||
|
|
||||||
|
task cToWasm {
|
||||||
|
doFirst {
|
||||||
|
mkdir 'build'
|
||||||
|
exec {
|
||||||
|
def cFileName = fileTree(dir: 'src', includes: ['*.c']).files.iterator().next()
|
||||||
|
commandLine 'clang', '--target=wasm32-unknown-unknown-wasm', '-O3', cFileName, '-c', '-o', 'build/lib.wasm'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task showCWast(type: JavaExec) {
|
||||||
|
dependsOn cToWasm
|
||||||
|
classpath configurations.compileClasspath
|
||||||
|
main = 'asmble.cli.MainKt'
|
||||||
|
doFirst {
|
||||||
|
args 'translate', 'build/lib.wasm'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task compileCWasm(type: JavaExec) {
|
||||||
|
dependsOn cToWasm
|
||||||
|
classpath configurations.compileClasspath
|
||||||
|
main = 'asmble.cli.MainKt'
|
||||||
|
doFirst {
|
||||||
|
def outFile = 'build/wasm-classes/' + wasmCompiledClassName.replace('.', '/') + '.class'
|
||||||
|
file(outFile).parentFile.mkdirs()
|
||||||
|
args 'compile', 'build/lib.wasm', wasmCompiledClassName, '-out', outFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Go example helpers
|
// Go example helpers
|
||||||
|
|
||||||
@ -142,45 +178,82 @@ project(':examples') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
project(':examples:c-simple') {
|
||||||
|
if (project.name in skipExamples) {
|
||||||
|
println("[Note!] Building and runnig for ${project.name} was skipped")
|
||||||
|
test.onlyIf { false } // explicit skipping tests
|
||||||
|
compileJava.onlyIf { false } // explicit skipping compile
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
//project(':examples:go-simple') {
|
apply plugin: 'application'
|
||||||
// apply plugin: 'application'
|
ext.wasmCompiledClassName = 'asmble.generated.CSimple'
|
||||||
// ext.wasmCompiledClassName = 'asmble.generated.GoSimple'
|
dependencies {
|
||||||
// dependencies {
|
compile files('build/wasm-classes')
|
||||||
// compile files('build/wasm-classes')
|
}
|
||||||
// }
|
|
||||||
// compileJava {
|
|
||||||
// dependsOn compileGoWasm
|
|
||||||
// }
|
|
||||||
// mainClassName = 'asmble.examples.gosimple.Main'
|
|
||||||
//}
|
|
||||||
|
|
||||||
// todo temporary disable Rust regex, because some strings in wasm code exceed the size in 65353 bytes.
|
compileJava {
|
||||||
|
dependsOn compileCWasm
|
||||||
|
}
|
||||||
|
mainClassName = 'asmble.examples.csimple.Main'
|
||||||
|
}
|
||||||
|
|
||||||
// project(':examples:rust-regex') {
|
project(':examples:go-simple') {
|
||||||
// apply plugin: 'application'
|
if (project.name in skipExamples) {
|
||||||
// apply plugin: 'me.champeau.gradle.jmh'
|
println("[Note!] Building and runnig for ${project.name} was skipped")
|
||||||
// ext.wasmCompiledClassName = 'asmble.generated.RustRegex'
|
test.onlyIf { false } // explicit skipping tests
|
||||||
// dependencies {
|
compileJava.onlyIf { false } // explicit skipping compile
|
||||||
// compile files('build/wasm-classes')
|
return
|
||||||
// testCompile 'junit:junit:4.12'
|
}
|
||||||
// }
|
apply plugin: 'application'
|
||||||
// compileJava {
|
ext.wasmCompiledClassName = 'asmble.generated.GoSimple'
|
||||||
// dependsOn compileRustWasm
|
dependencies {
|
||||||
// }
|
compile files('build/wasm-classes')
|
||||||
// mainClassName = 'asmble.examples.rustregex.Main'
|
}
|
||||||
// test {
|
compileJava {
|
||||||
// testLogging.showStandardStreams = true
|
dependsOn compileGoWasm
|
||||||
// testLogging.events 'PASSED', 'SKIPPED'
|
}
|
||||||
// }
|
mainClassName = 'asmble.examples.gosimple.Main'
|
||||||
// jmh {
|
}
|
||||||
// iterations = 5
|
|
||||||
// warmupIterations = 5
|
project(':examples:rust-regex') {
|
||||||
// fork = 3
|
if (project.name in skipExamples) {
|
||||||
// }
|
println("[Note!] Building and runnig for ${project.name} was skipped")
|
||||||
// }
|
test.onlyIf { false } // explicit skipping tests
|
||||||
|
compileJava.onlyIf { false } // explicit skipping compile
|
||||||
|
compileTestJava.onlyIf { false } // explicit skipping compile
|
||||||
|
return
|
||||||
|
}
|
||||||
|
apply plugin: 'application'
|
||||||
|
apply plugin: 'me.champeau.gradle.jmh'
|
||||||
|
ext.wasmCompiledClassName = 'asmble.generated.RustRegex'
|
||||||
|
dependencies {
|
||||||
|
compile files('build/wasm-classes')
|
||||||
|
testCompile 'junit:junit:4.12'
|
||||||
|
}
|
||||||
|
compileJava {
|
||||||
|
dependsOn compileRustWasm
|
||||||
|
}
|
||||||
|
mainClassName = 'asmble.examples.rustregex.Main'
|
||||||
|
|
||||||
|
test {
|
||||||
|
testLogging.showStandardStreams = true
|
||||||
|
testLogging.events 'PASSED', 'SKIPPED'
|
||||||
|
}
|
||||||
|
jmh {
|
||||||
|
iterations = 5
|
||||||
|
warmupIterations = 5
|
||||||
|
fork = 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
project(':examples:rust-simple') {
|
project(':examples:rust-simple') {
|
||||||
|
if (project.name in skipExamples) {
|
||||||
|
println("[Note!] Building and runnig for ${project.name} was skipped")
|
||||||
|
test.onlyIf { false } // explicit skipping tests
|
||||||
|
compileJava.onlyIf { false } // explicit skipping compile
|
||||||
|
return
|
||||||
|
}
|
||||||
apply plugin: 'application'
|
apply plugin: 'application'
|
||||||
ext.wasmCompiledClassName = 'asmble.generated.RustSimple'
|
ext.wasmCompiledClassName = 'asmble.generated.RustSimple'
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -193,6 +266,12 @@ project(':examples:rust-simple') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
project(':examples:rust-string') {
|
project(':examples:rust-string') {
|
||||||
|
if (project.name in skipExamples) {
|
||||||
|
println("[Note!] Building and runnig for ${project.name} was skipped")
|
||||||
|
test.onlyIf { false } // explicit skipping tests
|
||||||
|
compileJava.onlyIf { false } // explicit skipping compile
|
||||||
|
return
|
||||||
|
}
|
||||||
apply plugin: 'application'
|
apply plugin: 'application'
|
||||||
ext.wasmCompiledClassName = 'asmble.generated.RustString'
|
ext.wasmCompiledClassName = 'asmble.generated.RustString'
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
Taken from https://github.com/cretz/msplit
|
@ -0,0 +1,286 @@
|
|||||||
|
package asmble.compile.jvm.msplit;
|
||||||
|
|
||||||
|
|
||||||
|
import org.objectweb.asm.Label;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
import org.objectweb.asm.tree.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static asmble.compile.jvm.msplit.Util.*;
|
||||||
|
|
||||||
|
/** Splits a method into two */
|
||||||
|
public class SplitMethod {
|
||||||
|
|
||||||
|
protected final int api;
|
||||||
|
|
||||||
|
/** @param api Same as for {@link org.objectweb.asm.MethodVisitor#MethodVisitor(int)} or any other ASM class */
|
||||||
|
public SplitMethod(int api) { this.api = api; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls {@link #split(String, MethodNode, int, int, int)} with minSize as 20% + 1 of the original, maxSize as
|
||||||
|
* 70% + 1 of the original, and firstAtLeast as maxSize. The original method is never modified and the result can
|
||||||
|
* be null if no split points are found.
|
||||||
|
*/
|
||||||
|
public Result split(String owner, MethodNode method) {
|
||||||
|
// Between 20% + 1 and 70% + 1 of size
|
||||||
|
int insnCount = method.instructions.size();
|
||||||
|
int minSize = (int) (insnCount * 0.2) + 1;
|
||||||
|
int maxSize = (int) (insnCount * 0.7) + 1;
|
||||||
|
return split(owner, method, minSize, maxSize, maxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits the given method into two. This uses a {@link Splitter} to consistently create
|
||||||
|
* {@link asmble.compile.jvm.msplit.Splitter.SplitPoint}s until one reaches firstAtLeast or the largest otherwise, and then calls
|
||||||
|
* {@link #fromSplitPoint(String, MethodNode, Splitter.SplitPoint)}.
|
||||||
|
*
|
||||||
|
* @param owner The internal name of the owning class. Needed when splitting to call the split off method.
|
||||||
|
* @param method The method to split, never modified
|
||||||
|
* @param minSize The minimum number of instructions the split off method must have
|
||||||
|
* @param maxSize The maximum number of instructions the split off method can have
|
||||||
|
* @param firstAtLeast The number of instructions that, when first reached, will immediately be used without
|
||||||
|
* continuing. Since split points are streamed, this allows splitting without waiting to
|
||||||
|
* find the largest overall. If this is <= 0, it will not apply and all split points will be
|
||||||
|
* checked to find the largest before doing the split.
|
||||||
|
* @return The resulting split method or null if there were no split points found
|
||||||
|
*/
|
||||||
|
public Result split(String owner, MethodNode method, int minSize, int maxSize, int firstAtLeast) {
|
||||||
|
// Get the largest split point
|
||||||
|
Splitter.SplitPoint largest = null;
|
||||||
|
for (Splitter.SplitPoint point : new Splitter(api, owner, method, minSize, maxSize)) {
|
||||||
|
if (largest == null || point.length > largest.length) {
|
||||||
|
largest = point;
|
||||||
|
// Early exit?
|
||||||
|
if (firstAtLeast > 0 && largest.length >= firstAtLeast) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (largest == null) return null;
|
||||||
|
return fromSplitPoint(owner, method, largest);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split the given method at the given split point. Called by {@link #split(String, MethodNode, int, int, int)}. The
|
||||||
|
* original method is never modified.
|
||||||
|
*/
|
||||||
|
public Result fromSplitPoint(String owner, MethodNode orig, Splitter.SplitPoint splitPoint) {
|
||||||
|
MethodNode splitOff = createSplitOffMethod(orig, splitPoint);
|
||||||
|
MethodNode trimmed = createTrimmedMethod(owner, orig, splitOff, splitPoint);
|
||||||
|
return new Result(trimmed, splitOff);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MethodNode createSplitOffMethod(MethodNode orig, Splitter.SplitPoint splitPoint) {
|
||||||
|
// The new method is a static synthetic method named method.name + "$split" that returns an object array
|
||||||
|
// Key is previous local index, value is new local index
|
||||||
|
Map<Integer, Integer> localsMap = new HashMap<>();
|
||||||
|
// The new method's parameters are all stack items + all read locals
|
||||||
|
List<Type> args = new ArrayList<>(splitPoint.neededFromStackAtStart);
|
||||||
|
splitPoint.localsRead.forEach((index, type) -> {
|
||||||
|
args.add(type);
|
||||||
|
localsMap.put(index, args.size() - 1);
|
||||||
|
});
|
||||||
|
// Create the new method
|
||||||
|
MethodNode newMethod = new MethodNode(api,
|
||||||
|
Opcodes.ACC_STATIC + Opcodes.ACC_PRIVATE + Opcodes.ACC_SYNTHETIC, orig.name + "$split",
|
||||||
|
Type.getMethodDescriptor(Type.getType(Object[].class), args.toArray(new Type[0])), null, null);
|
||||||
|
// Add the written locals to the map that are not already there
|
||||||
|
int newLocalIndex = args.size();
|
||||||
|
for (Integer key : splitPoint.localsWritten.keySet()) {
|
||||||
|
if (!localsMap.containsKey(key)) {
|
||||||
|
localsMap.put(key, newLocalIndex);
|
||||||
|
newLocalIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// First set of instructions is pushing the new stack from the params
|
||||||
|
for (int i = 0; i < splitPoint.neededFromStackAtStart.size(); i++) {
|
||||||
|
Type item = splitPoint.neededFromStackAtStart.get(i);
|
||||||
|
newMethod.visitVarInsn(loadOpFromType(item), i);
|
||||||
|
}
|
||||||
|
// Next set of instructions comes verbatim from the original, but we have to change the local indexes
|
||||||
|
Set<Label> seenLabels = new HashSet<>();
|
||||||
|
for (int i = 0; i < splitPoint.length; i++) {
|
||||||
|
AbstractInsnNode insn = orig.instructions.get(i + splitPoint.start);
|
||||||
|
// Skip frames
|
||||||
|
if (insn instanceof FrameNode) continue;
|
||||||
|
// Store the label
|
||||||
|
if (insn instanceof LabelNode) seenLabels.add(((LabelNode) insn).getLabel());
|
||||||
|
// Change the local if needed
|
||||||
|
if (insn instanceof VarInsnNode) {
|
||||||
|
insn = insn.clone(Collections.emptyMap());
|
||||||
|
((VarInsnNode) insn).var = localsMap.get(((VarInsnNode) insn).var);
|
||||||
|
} else if (insn instanceof IincInsnNode) {
|
||||||
|
insn = insn.clone(Collections.emptyMap());
|
||||||
|
((VarInsnNode) insn).var = localsMap.get(((VarInsnNode) insn).var);
|
||||||
|
}
|
||||||
|
insn.accept(newMethod);
|
||||||
|
}
|
||||||
|
// Final set of instructions is an object array of stack to set and then locals written
|
||||||
|
// Create the object array
|
||||||
|
int retArrSize = splitPoint.putOnStackAtEnd.size() + splitPoint.localsWritten.size();
|
||||||
|
intConst(retArrSize).accept(newMethod);
|
||||||
|
newMethod.visitTypeInsn(Opcodes.ANEWARRAY, OBJECT_TYPE.getInternalName());
|
||||||
|
// So, we're going to store the arr in the next avail local
|
||||||
|
int retArrLocalIndex = newLocalIndex;
|
||||||
|
newMethod.visitVarInsn(Opcodes.ASTORE, retArrLocalIndex);
|
||||||
|
// Now go over each stack item and load the arr, swap w/ the stack, add the index, swap with the stack, and store
|
||||||
|
for (int i = splitPoint.putOnStackAtEnd.size() - 1; i >= 0; i--) {
|
||||||
|
Type item = splitPoint.putOnStackAtEnd.get(i);
|
||||||
|
// Box the item on the stack if necessary
|
||||||
|
boxStackIfNecessary(item, newMethod);
|
||||||
|
// Load the array
|
||||||
|
newMethod.visitVarInsn(Opcodes.ALOAD, retArrLocalIndex);
|
||||||
|
// Swap to put stack back on top
|
||||||
|
newMethod.visitInsn(Opcodes.SWAP);
|
||||||
|
// Add the index
|
||||||
|
intConst(i).accept(newMethod);
|
||||||
|
// Swap to put the stack value back on top
|
||||||
|
newMethod.visitInsn(Opcodes.SWAP);
|
||||||
|
// Now that we have arr, index, value, we can store in the array
|
||||||
|
newMethod.visitInsn(Opcodes.AASTORE);
|
||||||
|
}
|
||||||
|
// Do the same with written locals
|
||||||
|
int currIndex = splitPoint.putOnStackAtEnd.size();
|
||||||
|
for (Integer index : splitPoint.localsWritten.keySet()) {
|
||||||
|
Type item = splitPoint.localsWritten.get(index);
|
||||||
|
// Load the array
|
||||||
|
newMethod.visitVarInsn(Opcodes.ALOAD, retArrLocalIndex);
|
||||||
|
// Add the arr index
|
||||||
|
intConst(currIndex).accept(newMethod);
|
||||||
|
currIndex++;
|
||||||
|
// Load the var
|
||||||
|
newMethod.visitVarInsn(loadOpFromType(item), localsMap.get(index));
|
||||||
|
// Box it if necessary
|
||||||
|
boxStackIfNecessary(item, newMethod);
|
||||||
|
// Store in array
|
||||||
|
newMethod.visitInsn(Opcodes.AASTORE);
|
||||||
|
}
|
||||||
|
// Load the array out and return it
|
||||||
|
newMethod.visitVarInsn(Opcodes.ALOAD, retArrLocalIndex);
|
||||||
|
newMethod.visitInsn(Opcodes.ARETURN);
|
||||||
|
// Any try catch blocks that start in here
|
||||||
|
for (TryCatchBlockNode tryCatch : orig.tryCatchBlocks) {
|
||||||
|
if (seenLabels.contains(tryCatch.start.getLabel())) tryCatch.accept(newMethod);
|
||||||
|
}
|
||||||
|
// Reset the labels
|
||||||
|
newMethod.instructions.resetLabels();
|
||||||
|
return newMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MethodNode createTrimmedMethod(String owner, MethodNode orig,
|
||||||
|
MethodNode splitOff, Splitter.SplitPoint splitPoint) {
|
||||||
|
// The trimmed method is the same as the original, yet the split area is replaced with a call to the split off
|
||||||
|
// portion. Before calling the split-off, we have to add locals to the stack part. Then afterwards, we have to
|
||||||
|
// replace the stack and written locals.
|
||||||
|
// Effectively clone the orig
|
||||||
|
MethodNode newMethod = new MethodNode(api, orig.access, orig.name, orig.desc,
|
||||||
|
orig.signature, orig.exceptions.toArray(new String[0]));
|
||||||
|
orig.accept(newMethod);
|
||||||
|
// Remove all insns, we'll re-add the ones outside the split range
|
||||||
|
newMethod.instructions.clear();
|
||||||
|
// Remove all try catch blocks and keep track of seen labels, we'll re-add them at the end
|
||||||
|
newMethod.tryCatchBlocks.clear();
|
||||||
|
Set<Label> seenLabels = new HashSet<>();
|
||||||
|
// Also keep track of the locals that have been stored, need to know
|
||||||
|
Set<Integer> seenStoredLocals = new HashSet<>();
|
||||||
|
// If this is an instance method, we consider "0" (i.e. "this") as seen
|
||||||
|
if ((orig.access & Opcodes.ACC_STATIC) == 0) seenStoredLocals.add(0);
|
||||||
|
// Add the insns before split
|
||||||
|
for (int i = 0; i < splitPoint.start; i++) {
|
||||||
|
AbstractInsnNode insn = orig.instructions.get(i + splitPoint.start);
|
||||||
|
// Skip frames
|
||||||
|
if (insn instanceof FrameNode) continue;
|
||||||
|
// Record label
|
||||||
|
if (insn instanceof LabelNode) seenLabels.add(((LabelNode) insn).getLabel());
|
||||||
|
// Check a local store has happened
|
||||||
|
if (insn instanceof VarInsnNode && isStoreOp(insn.getOpcode())) seenStoredLocals.add(((VarInsnNode) insn).var);
|
||||||
|
insn.accept(newMethod);
|
||||||
|
}
|
||||||
|
// Push all the read locals on the stack
|
||||||
|
splitPoint.localsRead.forEach((index, type) -> {
|
||||||
|
// We've seen a store for this, so just load it, otherwise use a zero val
|
||||||
|
// TODO: safe? if not, maybe just put at the top of the method a bunch of defaulted locals?
|
||||||
|
if (seenStoredLocals.contains(index)) newMethod.visitVarInsn(loadOpFromType(type), index);
|
||||||
|
else zeroVal(type).accept(newMethod);
|
||||||
|
});
|
||||||
|
// Invoke the split off method
|
||||||
|
newMethod.visitMethodInsn(Opcodes.INVOKESTATIC, owner, splitOff.name, splitOff.desc, false);
|
||||||
|
// Now the object array is on the stack which contains stack pieces + written locals
|
||||||
|
// Take off the locals
|
||||||
|
int localArrIndex = splitPoint.putOnStackAtEnd.size();
|
||||||
|
for (Integer index : splitPoint.localsWritten.keySet()) {
|
||||||
|
// Dupe the array
|
||||||
|
newMethod.visitInsn(Opcodes.DUP);
|
||||||
|
// Put the index on the stack
|
||||||
|
intConst(localArrIndex).accept(newMethod);
|
||||||
|
localArrIndex++;
|
||||||
|
// Load the written local
|
||||||
|
Type item = splitPoint.localsWritten.get(index);
|
||||||
|
newMethod.visitInsn(Opcodes.AALOAD);
|
||||||
|
// Cast to local type
|
||||||
|
if (!item.equals(OBJECT_TYPE)) {
|
||||||
|
newMethod.visitTypeInsn(Opcodes.CHECKCAST, boxedTypeIfNecessary(item).getInternalName());
|
||||||
|
}
|
||||||
|
// Unbox if necessary
|
||||||
|
unboxStackIfNecessary(item, newMethod);
|
||||||
|
// Store in the local
|
||||||
|
newMethod.visitVarInsn(storeOpFromType(item), index);
|
||||||
|
}
|
||||||
|
// Now just load up the stack
|
||||||
|
for (int i = 0; i < splitPoint.putOnStackAtEnd.size(); i++) {
|
||||||
|
boolean last = i == splitPoint.putOnStackAtEnd.size() - 1;
|
||||||
|
// Since the loop started with the array, we only dupe the array every time but the last
|
||||||
|
if (!last) newMethod.visitInsn(Opcodes.DUP);
|
||||||
|
// Put the index on the stack
|
||||||
|
intConst(i).accept(newMethod);
|
||||||
|
// Load the stack item
|
||||||
|
Type item = splitPoint.putOnStackAtEnd.get(i);
|
||||||
|
newMethod.visitInsn(Opcodes.AALOAD);
|
||||||
|
// Cast to local type
|
||||||
|
if (!item.equals(OBJECT_TYPE)) {
|
||||||
|
newMethod.visitTypeInsn(Opcodes.CHECKCAST, boxedTypeIfNecessary(item).getInternalName());
|
||||||
|
}
|
||||||
|
// Unbox if necessary
|
||||||
|
unboxStackIfNecessary(item, newMethod);
|
||||||
|
// For all but the last stack item, we need to swap with the arr ref above.
|
||||||
|
if (!last) {
|
||||||
|
// Note if the stack item takes two slots, we do a form of dup then pop since there's no swap1x2
|
||||||
|
if (item == Type.LONG_TYPE || item == Type.DOUBLE_TYPE) {
|
||||||
|
newMethod.visitInsn(Opcodes.DUP_X2);
|
||||||
|
newMethod.visitInsn(Opcodes.POP);
|
||||||
|
} else {
|
||||||
|
newMethod.visitInsn(Opcodes.SWAP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Now we have restored all locals and all stack...add the rest of the insns after the split
|
||||||
|
for (int i = splitPoint.start + splitPoint.length; i < orig.instructions.size(); i++) {
|
||||||
|
AbstractInsnNode insn = orig.instructions.get(i + splitPoint.start);
|
||||||
|
// Skip frames
|
||||||
|
if (insn instanceof FrameNode) continue;
|
||||||
|
// Record label
|
||||||
|
if (insn instanceof LabelNode) seenLabels.add(((LabelNode) insn).getLabel());
|
||||||
|
insn.accept(newMethod);
|
||||||
|
}
|
||||||
|
// Add any try catch blocks that started in here
|
||||||
|
for (TryCatchBlockNode tryCatch : orig.tryCatchBlocks) {
|
||||||
|
if (seenLabels.contains(tryCatch.start.getLabel())) tryCatch.accept(newMethod);
|
||||||
|
}
|
||||||
|
// Reset the labels
|
||||||
|
newMethod.instructions.resetLabels();
|
||||||
|
return newMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Result of a split method */
|
||||||
|
public static class Result {
|
||||||
|
/** A copy of the original method, but changed to invoke {@link #splitOffMethod} */
|
||||||
|
public final MethodNode trimmedMethod;
|
||||||
|
/** The new method that was split off the original and is called by {@link #splitOffMethod} */
|
||||||
|
public final MethodNode splitOffMethod;
|
||||||
|
|
||||||
|
public Result(MethodNode trimmedMethod, MethodNode splitOffMethod) {
|
||||||
|
this.trimmedMethod = trimmedMethod;
|
||||||
|
this.splitOffMethod = splitOffMethod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
392
compiler/src/main/java/asmble/compile/jvm/msplit/Splitter.java
Normal file
392
compiler/src/main/java/asmble/compile/jvm/msplit/Splitter.java
Normal file
@ -0,0 +1,392 @@
|
|||||||
|
package asmble.compile.jvm.msplit;
|
||||||
|
|
||||||
|
import org.objectweb.asm.Label;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
import org.objectweb.asm.commons.AnalyzerAdapter;
|
||||||
|
import org.objectweb.asm.tree.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static asmble.compile.jvm.msplit.Util.*;
|
||||||
|
|
||||||
|
/** For a given method, iterate over possible split points */
|
||||||
|
public class Splitter implements Iterable<Splitter.SplitPoint> {
|
||||||
|
protected final int api;
|
||||||
|
protected final String owner;
|
||||||
|
protected final MethodNode method;
|
||||||
|
protected final int minSize;
|
||||||
|
protected final int maxSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param api Same as for {@link org.objectweb.asm.MethodVisitor#MethodVisitor(int)} or any other ASM class
|
||||||
|
* @param owner Internal name of the method's owner
|
||||||
|
* @param method The method to find split points for
|
||||||
|
* @param minSize The minimum number of instructions required for the split point to be valid
|
||||||
|
* @param maxSize The maximum number of instructions that split points cannot exceeed
|
||||||
|
*/
|
||||||
|
public Splitter(int api, String owner, MethodNode method, int minSize, int maxSize) {
|
||||||
|
this.api = api;
|
||||||
|
this.owner = owner;
|
||||||
|
this.method = method;
|
||||||
|
this.minSize = minSize;
|
||||||
|
this.maxSize = maxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<SplitPoint> iterator() { return new Iter(); }
|
||||||
|
|
||||||
|
// Types are always int, float, long, double, or ref (no other primitives)
|
||||||
|
/** A split point in a method that can be split off into another method */
|
||||||
|
public static class SplitPoint {
|
||||||
|
/**
|
||||||
|
* The locals read in this split area, keyed by index. Value type is always int, float, long, double, or object.
|
||||||
|
*/
|
||||||
|
public final SortedMap<Integer, Type> localsRead;
|
||||||
|
/**
|
||||||
|
* The locals written in this split area, keyed by index. Value type is always int, float, long, double, or object.
|
||||||
|
*/
|
||||||
|
public final SortedMap<Integer, Type> localsWritten;
|
||||||
|
/**
|
||||||
|
* The values of the stack needed at the start of this split area. Type is always int, float, long, double, or
|
||||||
|
* object.
|
||||||
|
*/
|
||||||
|
public final List<Type> neededFromStackAtStart;
|
||||||
|
/**
|
||||||
|
* The values of the stack at the end of this split area that are needed to put back on the original. Type is always
|
||||||
|
* int, float, long, double, or object.
|
||||||
|
*/
|
||||||
|
public final List<Type> putOnStackAtEnd;
|
||||||
|
/**
|
||||||
|
* The instruction index this split area begins at.
|
||||||
|
*/
|
||||||
|
public final int start;
|
||||||
|
/**
|
||||||
|
* The number of instructions this split area has.
|
||||||
|
*/
|
||||||
|
public final int length;
|
||||||
|
|
||||||
|
public SplitPoint(SortedMap<Integer, Type> localsRead, SortedMap<Integer, Type>localsWritten,
|
||||||
|
List<Type> neededFromStackAtStart, List<Type> putOnStackAtEnd, int start, int length) {
|
||||||
|
this.localsRead = localsRead;
|
||||||
|
this.localsWritten = localsWritten;
|
||||||
|
this.neededFromStackAtStart = neededFromStackAtStart;
|
||||||
|
this.putOnStackAtEnd = putOnStackAtEnd;
|
||||||
|
this.start = start;
|
||||||
|
this.length = length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int compareInsnIndexes(AbstractInsnNode o1, AbstractInsnNode o2) {
|
||||||
|
return Integer.compare(method.instructions.indexOf(o1), method.instructions.indexOf(o2));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class Iter implements Iterator<SplitPoint> {
|
||||||
|
protected final AbstractInsnNode[] insns;
|
||||||
|
protected final List<TryCatchBlockNode> tryCatchBlocks;
|
||||||
|
protected int currIndex = -1;
|
||||||
|
protected boolean peeked;
|
||||||
|
protected SplitPoint peekedValue;
|
||||||
|
|
||||||
|
protected Iter() {
|
||||||
|
insns = method.instructions.toArray();
|
||||||
|
tryCatchBlocks = new ArrayList<>(method.tryCatchBlocks);
|
||||||
|
// Must be sorted by earliest starting index then earliest end index then earliest handler
|
||||||
|
tryCatchBlocks.sort((o1, o2) -> {
|
||||||
|
int cmp = compareInsnIndexes(o1.start, o2.start);
|
||||||
|
if (cmp == 0) compareInsnIndexes(o1.end, o2.end);
|
||||||
|
if (cmp == 0) compareInsnIndexes(o1.handler, o2.handler);
|
||||||
|
return cmp;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
if (!peeked) {
|
||||||
|
peeked = true;
|
||||||
|
peekedValue = nextOrNull();
|
||||||
|
}
|
||||||
|
return peekedValue != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SplitPoint next() {
|
||||||
|
// If we've peeked in hasNext, use that
|
||||||
|
SplitPoint ret;
|
||||||
|
if (peeked) {
|
||||||
|
peeked = false;
|
||||||
|
ret = peekedValue;
|
||||||
|
} else {
|
||||||
|
ret = nextOrNull();
|
||||||
|
}
|
||||||
|
if (ret == null) throw new NoSuchElementException();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SplitPoint nextOrNull() {
|
||||||
|
// Try for each index
|
||||||
|
while (++currIndex + minSize <= insns.length) {
|
||||||
|
SplitPoint longest = longestForCurrIndex();
|
||||||
|
if (longest != null) return longest;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SplitPoint longestForCurrIndex() {
|
||||||
|
// As a special case, if the previous insn was a line number, that was good enough
|
||||||
|
if (currIndex - 1 >- 0 && insns[currIndex - 1] instanceof LineNumberNode) return null;
|
||||||
|
// Build the info object
|
||||||
|
InsnTraverseInfo info = new InsnTraverseInfo();
|
||||||
|
info.startIndex = currIndex;
|
||||||
|
info.endIndex = Math.min(currIndex + maxSize - 1, insns.length - 1);
|
||||||
|
// Reduce the end based on try/catch blocks the start is in or that jump to
|
||||||
|
constrainEndByTryCatchBlocks(info);
|
||||||
|
// Reduce the end based on any jumps within
|
||||||
|
constrainEndByInternalJumps(info);
|
||||||
|
// Reduce the end based on any jumps into
|
||||||
|
constrainEndByExternalJumps(info);
|
||||||
|
// Make sure we didn't reduce the end too far
|
||||||
|
if (info.getSize() < minSize) return null;
|
||||||
|
// Now that we have our largest range from the start index, we can go over each updating the local refs and stack
|
||||||
|
// For the stack, we are going to use the
|
||||||
|
return splitPointFromInfo(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void constrainEndByTryCatchBlocks(InsnTraverseInfo info) {
|
||||||
|
// Go over all the try/catch blocks, sorted by earliest
|
||||||
|
for (TryCatchBlockNode block : tryCatchBlocks) {
|
||||||
|
int handleIndex = method.instructions.indexOf(block.handler);
|
||||||
|
int startIndex = method.instructions.indexOf(block.start);
|
||||||
|
int endIndex = method.instructions.indexOf(block.end) - 1;
|
||||||
|
boolean catchWithinDisallowed;
|
||||||
|
|
||||||
|
if (info.startIndex <= startIndex && info.endIndex >= endIndex) {
|
||||||
|
// The try block is entirely inside the range...
|
||||||
|
catchWithinDisallowed = false;
|
||||||
|
// Since it's entirely within, we need the catch handler within too
|
||||||
|
if (handleIndex < info.startIndex || handleIndex > info.endIndex) {
|
||||||
|
// Well, it's not within, so that means we can't include this try block at all
|
||||||
|
info.endIndex = Math.min(info.endIndex, startIndex - 1);
|
||||||
|
}
|
||||||
|
} else if (info.startIndex > startIndex && info.endIndex > endIndex) {
|
||||||
|
// The try block started before this range, but ends inside of it...
|
||||||
|
// The end has to be changed to the block's end so it doesn't go over the boundary
|
||||||
|
info.endIndex = Math.min(info.endIndex, endIndex);
|
||||||
|
// The catch can't jump in here
|
||||||
|
catchWithinDisallowed = true;
|
||||||
|
} else if (info.startIndex <= startIndex && info.endIndex < endIndex) {
|
||||||
|
// The try block started in this range, but ends outside of it...
|
||||||
|
// Can't have the block then, reduce it to before the start
|
||||||
|
info.endIndex = Math.min(info.endIndex, startIndex - 1);
|
||||||
|
// Since we don't have the block, we can't jump in here either
|
||||||
|
catchWithinDisallowed = true;
|
||||||
|
} else {
|
||||||
|
// The try block is completely outside, just restrict the catch from jumping in
|
||||||
|
catchWithinDisallowed = true;
|
||||||
|
}
|
||||||
|
// If the catch is within and not allowed to be, we have to change the end to before it
|
||||||
|
if (catchWithinDisallowed && info.startIndex <= handleIndex && info.endIndex >= handleIndex) {
|
||||||
|
info.endIndex = Math.min(info.endIndex, handleIndex - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void constrainEndByInternalJumps(InsnTraverseInfo info) {
|
||||||
|
for (int i = info.startIndex; i <= info.endIndex; i++) {
|
||||||
|
AbstractInsnNode node = insns[i];
|
||||||
|
int earliestIndex;
|
||||||
|
int furthestIndex;
|
||||||
|
if (node instanceof JumpInsnNode) {
|
||||||
|
earliestIndex = method.instructions.indexOf(((JumpInsnNode) node).label);
|
||||||
|
furthestIndex = earliestIndex;
|
||||||
|
} else if (node instanceof TableSwitchInsnNode) {
|
||||||
|
earliestIndex = method.instructions.indexOf(((TableSwitchInsnNode) node).dflt);
|
||||||
|
furthestIndex = earliestIndex;
|
||||||
|
for (LabelNode label : ((TableSwitchInsnNode) node).labels) {
|
||||||
|
int index = method.instructions.indexOf(label);
|
||||||
|
earliestIndex = Math.min(earliestIndex, index);
|
||||||
|
furthestIndex = Math.max(furthestIndex, index);
|
||||||
|
}
|
||||||
|
} else if (node instanceof LookupSwitchInsnNode) {
|
||||||
|
earliestIndex = method.instructions.indexOf(((LookupSwitchInsnNode) node).dflt);
|
||||||
|
furthestIndex = earliestIndex;
|
||||||
|
for (LabelNode label : ((LookupSwitchInsnNode) node).labels) {
|
||||||
|
int index = method.instructions.indexOf(label);
|
||||||
|
earliestIndex = Math.min(earliestIndex, index);
|
||||||
|
furthestIndex = Math.max(furthestIndex, index);
|
||||||
|
}
|
||||||
|
} else continue;
|
||||||
|
// Stop here if any indexes are out of range, otherwise, change end
|
||||||
|
if (earliestIndex < info.startIndex || furthestIndex > info.endIndex) {
|
||||||
|
info.endIndex = i - 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
info.endIndex = Math.max(info.endIndex, furthestIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void constrainEndByExternalJumps(InsnTraverseInfo info) {
|
||||||
|
// Basically, if any external jumps jump into our range, that can't be included in the range
|
||||||
|
for (int i = 0; i < insns.length; i++) {
|
||||||
|
if (i >= info.startIndex && i <= info.endIndex) continue;
|
||||||
|
AbstractInsnNode node = insns[i];
|
||||||
|
if (node instanceof JumpInsnNode) {
|
||||||
|
int index = method.instructions.indexOf(((JumpInsnNode) node).label);
|
||||||
|
if (index >= info.startIndex) info.endIndex = Math.min(info.endIndex, index - 1);
|
||||||
|
} else if (node instanceof TableSwitchInsnNode) {
|
||||||
|
int index = method.instructions.indexOf(((TableSwitchInsnNode) node).dflt);
|
||||||
|
if (index >= info.startIndex) info.endIndex = Math.min(info.endIndex, index - 1);
|
||||||
|
for (LabelNode label : ((TableSwitchInsnNode) node).labels) {
|
||||||
|
index = method.instructions.indexOf(label);
|
||||||
|
if (index >= info.startIndex) info.endIndex = Math.min(info.endIndex, index - 1);
|
||||||
|
}
|
||||||
|
} else if (node instanceof LookupSwitchInsnNode) {
|
||||||
|
int index = method.instructions.indexOf(((LookupSwitchInsnNode) node).dflt);
|
||||||
|
if (index >= info.startIndex) info.endIndex = Math.min(info.endIndex, index - 1);
|
||||||
|
for (LabelNode label : ((LookupSwitchInsnNode) node).labels) {
|
||||||
|
index = method.instructions.indexOf(label);
|
||||||
|
if (index >= info.startIndex) info.endIndex = Math.min(info.endIndex, index - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SplitPoint splitPointFromInfo(InsnTraverseInfo info) {
|
||||||
|
// We're going to use the analyzer adapter and run it for the up until the end, a step at a time
|
||||||
|
StackAndLocalTrackingAdapter adapter = new StackAndLocalTrackingAdapter(Splitter.this);
|
||||||
|
// Visit all of the insns up our start.
|
||||||
|
// XXX: I checked the source of AnalyzerAdapter to confirm I don't need any of the surrounding stuff
|
||||||
|
for (int i = 0; i < info.startIndex; i++) insns[i].accept(adapter);
|
||||||
|
// Take the stack at the start and copy it off
|
||||||
|
List<Object> stackAtStart = new ArrayList<>(adapter.stack);
|
||||||
|
// Reset some adapter state
|
||||||
|
adapter.lowestStackSize = stackAtStart.size();
|
||||||
|
adapter.localsRead.clear();
|
||||||
|
adapter.localsWritten.clear();
|
||||||
|
// Now go over the remaining range
|
||||||
|
for (int i = info.startIndex; i <= info.endIndex; i++) insns[i].accept(adapter);
|
||||||
|
// Build the split point
|
||||||
|
return new SplitPoint(
|
||||||
|
localMapFromAdapterLocalMap(adapter.localsRead, adapter.uninitializedTypes),
|
||||||
|
localMapFromAdapterLocalMap(adapter.localsWritten, adapter.uninitializedTypes),
|
||||||
|
typesFromAdapterStackRange(stackAtStart, adapter.lowestStackSize, adapter.uninitializedTypes),
|
||||||
|
typesFromAdapterStackRange(adapter.stack, adapter.lowestStackSize, adapter.uninitializedTypes),
|
||||||
|
info.startIndex,
|
||||||
|
info.getSize()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SortedMap<Integer, Type> localMapFromAdapterLocalMap(
|
||||||
|
SortedMap<Integer, Object> map, Map<Object, Object> uninitializedTypes) {
|
||||||
|
SortedMap<Integer, Type> ret = new TreeMap<>();
|
||||||
|
map.forEach((k, v) -> ret.put(k, typeFromAdapterStackItem(v, uninitializedTypes)));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<Type> typesFromAdapterStackRange(
|
||||||
|
List<Object> stack, int start, Map<Object, Object> uninitializedTypes) {
|
||||||
|
List<Type> ret = new ArrayList<>();
|
||||||
|
for (int i = start; i < stack.size(); i++) {
|
||||||
|
Object item = stack.get(i);
|
||||||
|
ret.add(typeFromAdapterStackItem(item, uninitializedTypes));
|
||||||
|
// Jump an extra spot for longs and doubles
|
||||||
|
if (item == Opcodes.LONG || item == Opcodes.DOUBLE) {
|
||||||
|
if (stack.get(++i) != Opcodes.TOP) throw new IllegalStateException("Expected top after long/double");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Type typeFromAdapterStackItem(Object item, Map<Object, Object> uninitializedTypes) {
|
||||||
|
if (item == Opcodes.INTEGER) return Type.INT_TYPE;
|
||||||
|
else if (item == Opcodes.FLOAT) return Type.FLOAT_TYPE;
|
||||||
|
else if (item == Opcodes.LONG) return Type.LONG_TYPE;
|
||||||
|
else if (item == Opcodes.DOUBLE) return Type.DOUBLE_TYPE;
|
||||||
|
else if (item == Opcodes.NULL) return OBJECT_TYPE;
|
||||||
|
else if (item == Opcodes.UNINITIALIZED_THIS) return Type.getObjectType(owner);
|
||||||
|
else if (item instanceof Label) return Type.getObjectType((String) uninitializedTypes.get(item));
|
||||||
|
else if (item instanceof String) return Type.getObjectType((String) item);
|
||||||
|
else throw new IllegalStateException("Unrecognized stack item: " + item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class StackAndLocalTrackingAdapter extends AnalyzerAdapter {
|
||||||
|
public int lowestStackSize;
|
||||||
|
public final SortedMap<Integer, Object> localsRead = new TreeMap<>();
|
||||||
|
public final SortedMap<Integer, Object> localsWritten = new TreeMap<>();
|
||||||
|
|
||||||
|
protected StackAndLocalTrackingAdapter(Splitter splitter) {
|
||||||
|
super(splitter.api, splitter.owner, splitter.method.access, splitter.method.name, splitter.method.desc, null);
|
||||||
|
stack = new SizeChangeNotifyList<Object>() {
|
||||||
|
@Override
|
||||||
|
protected void onSizeChanged() { lowestStackSize = Math.min(lowestStackSize, size()); }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitVarInsn(int opcode, int var) {
|
||||||
|
switch (opcode) {
|
||||||
|
case Opcodes.ILOAD:
|
||||||
|
case Opcodes.LLOAD:
|
||||||
|
case Opcodes.FLOAD:
|
||||||
|
case Opcodes.DLOAD:
|
||||||
|
case Opcodes.ALOAD:
|
||||||
|
localsRead.put(var, locals.get(var));
|
||||||
|
break;
|
||||||
|
case Opcodes.ISTORE:
|
||||||
|
case Opcodes.FSTORE:
|
||||||
|
case Opcodes.ASTORE:
|
||||||
|
localsWritten.put(var, stack.get(stack.size() - 1));
|
||||||
|
break;
|
||||||
|
case Opcodes.LSTORE:
|
||||||
|
case Opcodes.DSTORE:
|
||||||
|
localsWritten.put(var, stack.get(stack.size() - 2));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
super.visitVarInsn(opcode, var);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitIincInsn(int var, int increment) {
|
||||||
|
localsRead.put(var, Type.INT_TYPE);
|
||||||
|
localsWritten.put(var, Type.INT_TYPE);
|
||||||
|
super.visitIincInsn(var, increment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class SizeChangeNotifyList<T> extends AbstractList<T> {
|
||||||
|
protected final ArrayList<T> list = new ArrayList<>();
|
||||||
|
|
||||||
|
protected void onSizeChanged() { }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T get(int index) { return list.get(index); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() { return list.size(); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T set(int index, T element) { return list.set(index, element); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(int index, T element) {
|
||||||
|
list.add(index, element);
|
||||||
|
onSizeChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T remove(int index) {
|
||||||
|
T ret = list.remove(index);
|
||||||
|
onSizeChanged();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class InsnTraverseInfo {
|
||||||
|
public int startIndex;
|
||||||
|
// Can only shrink, never increase in size
|
||||||
|
public int endIndex;
|
||||||
|
|
||||||
|
public int getSize() { return endIndex - startIndex + 1; }
|
||||||
|
}
|
||||||
|
}
|
84
compiler/src/main/java/asmble/compile/jvm/msplit/Util.java
Normal file
84
compiler/src/main/java/asmble/compile/jvm/msplit/Util.java
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package asmble.compile.jvm.msplit;
|
||||||
|
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
import org.objectweb.asm.Type;
|
||||||
|
import org.objectweb.asm.tree.*;
|
||||||
|
|
||||||
|
class Util {
|
||||||
|
private Util() { }
|
||||||
|
|
||||||
|
static final Type OBJECT_TYPE = Type.getType(Object.class);
|
||||||
|
|
||||||
|
static AbstractInsnNode zeroVal(Type type) {
|
||||||
|
if (type == Type.INT_TYPE) return new InsnNode(Opcodes.ICONST_0);
|
||||||
|
else if (type == Type.LONG_TYPE) return new InsnNode(Opcodes.LCONST_0);
|
||||||
|
else if (type == Type.FLOAT_TYPE) return new InsnNode(Opcodes.FCONST_0);
|
||||||
|
else if (type == Type.DOUBLE_TYPE) return new InsnNode(Opcodes.DCONST_0);
|
||||||
|
else return new InsnNode(Opcodes.ACONST_NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isStoreOp(int opcode) {
|
||||||
|
return opcode == Opcodes.ISTORE || opcode == Opcodes.LSTORE || opcode == Opcodes.FSTORE ||
|
||||||
|
opcode == Opcodes.DSTORE || opcode == Opcodes.ASTORE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int storeOpFromType(Type type) {
|
||||||
|
if (type == Type.INT_TYPE) return Opcodes.ISTORE;
|
||||||
|
else if (type == Type.LONG_TYPE) return Opcodes.LSTORE;
|
||||||
|
else if (type == Type.FLOAT_TYPE) return Opcodes.FSTORE;
|
||||||
|
else if (type == Type.DOUBLE_TYPE) return Opcodes.DSTORE;
|
||||||
|
else return Opcodes.ASTORE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int loadOpFromType(Type type) {
|
||||||
|
if (type == Type.INT_TYPE) return Opcodes.ILOAD;
|
||||||
|
else if (type == Type.LONG_TYPE) return Opcodes.LLOAD;
|
||||||
|
else if (type == Type.FLOAT_TYPE) return Opcodes.FLOAD;
|
||||||
|
else if (type == Type.DOUBLE_TYPE) return Opcodes.DLOAD;
|
||||||
|
else return Opcodes.ALOAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Type boxedTypeIfNecessary(Type type) {
|
||||||
|
if (type == Type.INT_TYPE) return Type.getType(Integer.class);
|
||||||
|
else if (type == Type.LONG_TYPE) return Type.getType(Long.class);
|
||||||
|
else if (type == Type.FLOAT_TYPE) return Type.getType(Float.class);
|
||||||
|
else if (type == Type.DOUBLE_TYPE) return Type.getType(Double.class);
|
||||||
|
else return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void boxStackIfNecessary(Type type, MethodNode method) {
|
||||||
|
if (type == Type.INT_TYPE) boxCall(Integer.class, type).accept(method);
|
||||||
|
else if (type == Type.FLOAT_TYPE) boxCall(Float.class, type).accept(method);
|
||||||
|
else if (type == Type.LONG_TYPE) boxCall(Long.class, type).accept(method);
|
||||||
|
else if (type == Type.DOUBLE_TYPE) boxCall(Double.class, type).accept(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void unboxStackIfNecessary(Type type, MethodNode method) {
|
||||||
|
if (type == Type.INT_TYPE) method.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
|
||||||
|
"java/lang/Integer", "intValue", Type.getMethodDescriptor(Type.INT_TYPE), false);
|
||||||
|
else if (type == Type.FLOAT_TYPE) method.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
|
||||||
|
"java/lang/Float", "floatValue", Type.getMethodDescriptor(Type.FLOAT_TYPE), false);
|
||||||
|
else if (type == Type.LONG_TYPE) method.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
|
||||||
|
"java/lang/Long", "longValue", Type.getMethodDescriptor(Type.LONG_TYPE), false);
|
||||||
|
else if (type == Type.DOUBLE_TYPE) method.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
|
||||||
|
"java/lang/Double", "doubleValue", Type.getMethodDescriptor(Type.DOUBLE_TYPE), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static AbstractInsnNode intConst(int v) {
|
||||||
|
switch (v) {
|
||||||
|
case -1: return new InsnNode(Opcodes.ICONST_M1);
|
||||||
|
case 0: return new InsnNode(Opcodes.ICONST_0);
|
||||||
|
case 1: return new InsnNode(Opcodes.ICONST_1);
|
||||||
|
case 2: return new InsnNode(Opcodes.ICONST_2);
|
||||||
|
case 3: return new InsnNode(Opcodes.ICONST_3);
|
||||||
|
case 4: return new InsnNode(Opcodes.ICONST_4);
|
||||||
|
case 5: return new InsnNode(Opcodes.ICONST_5);
|
||||||
|
default: return new LdcInsnNode(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static MethodInsnNode boxCall(Class<?> boxType, Type primType) {
|
||||||
|
return new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(boxType),
|
||||||
|
"valueOf", Type.getMethodDescriptor(Type.getType(boxType), primType), false);
|
||||||
|
}
|
||||||
|
}
|
@ -1,183 +0,0 @@
|
|||||||
package asmble.ast.opt
|
|
||||||
|
|
||||||
import asmble.ast.Node
|
|
||||||
import asmble.ast.Stack
|
|
||||||
|
|
||||||
// This is a naive implementation that just grabs adjacent sets of restricted insns and breaks the one that will save
|
|
||||||
// the most instructions off into its own function.
|
|
||||||
open class SplitLargeFunc(
|
|
||||||
val minSetLength: Int = 5,
|
|
||||||
val maxSetLength: Int = 40,
|
|
||||||
val maxParamCount: Int = 30
|
|
||||||
) {
|
|
||||||
|
|
||||||
// Null if no replacement. Second value is number of instructions saved. fnIndex must map to actual func,
|
|
||||||
// not imported one.
|
|
||||||
fun apply(mod: Node.Module, fnIndex: Int): Pair<Node.Module, Int>? {
|
|
||||||
// Get the func
|
|
||||||
val importFuncCount = mod.imports.count { it.kind is Node.Import.Kind.Func }
|
|
||||||
val actualFnIndex = fnIndex - importFuncCount
|
|
||||||
val func = mod.funcs.getOrElse(actualFnIndex) {
|
|
||||||
error("Unable to find non-import func at $fnIndex (actual $actualFnIndex)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Just take the best pattern and apply it
|
|
||||||
val newFuncIndex = importFuncCount + mod.funcs.size
|
|
||||||
return commonPatterns(mod, func).firstOrNull()?.let { pattern ->
|
|
||||||
// Name it as <funcname>$splitN (n is num just to disambiguate) if names are part of the mod
|
|
||||||
val newName = mod.names?.funcNames?.get(fnIndex)?.let {
|
|
||||||
"$it\$split".let { it + mod.names.funcNames.count { (_, v) -> v.startsWith(it) } }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go over every replacement in reverse, changing the instructions to our new set
|
|
||||||
val newInsns = pattern.replacements.foldRight(func.instructions) { repl, insns ->
|
|
||||||
insns.take(repl.range.start) +
|
|
||||||
repl.preCallConsts +
|
|
||||||
Node.Instr.Call(newFuncIndex) +
|
|
||||||
insns.drop(repl.range.endInclusive + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the module w/ the new function, it's new name, and the insns saved
|
|
||||||
mod.copy(
|
|
||||||
funcs = mod.funcs.toMutableList().also {
|
|
||||||
it[actualFnIndex] = func.copy(instructions = newInsns)
|
|
||||||
} + pattern.newFunc,
|
|
||||||
names = mod.names?.copy(funcNames = mod.names.funcNames.toMutableMap().also {
|
|
||||||
it[newFuncIndex] = newName!!
|
|
||||||
})
|
|
||||||
) to pattern.insnsSaved
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Results are by most insns saved. There can be overlap across patterns but never within a single pattern.
|
|
||||||
fun commonPatterns(mod: Node.Module, fn: Node.Func): List<CommonPattern> {
|
|
||||||
// Walk the stack for validation needs
|
|
||||||
val stack = Stack.walkStrict(mod, fn)
|
|
||||||
|
|
||||||
// Let's grab sets of insns that qualify. In this naive impl, in order to qualify the insn set needs to
|
|
||||||
// only have a certain set of insns that can be broken off. It can also only change the stack by 0 or 1
|
|
||||||
// value while never dipping below the starting stack. We also store the index they started at.
|
|
||||||
var insnSets = emptyList<InsnSet>()
|
|
||||||
// Pair in fold keyed by insn index
|
|
||||||
fn.instructions.foldIndexed(null as List<Pair<Int, Node.Instr>>?) { index, lastInsns, insn ->
|
|
||||||
if (!insn.canBeMoved) null else (lastInsns ?: emptyList()).plus(index to insn).also { fullNewInsnSet ->
|
|
||||||
// Get all final instructions between min and max size and with allowed param count (i.e. const count)
|
|
||||||
val trailingInsnSet = fullNewInsnSet.takeLast(maxSetLength)
|
|
||||||
|
|
||||||
// Get all instructions between the min and max
|
|
||||||
insnSets += (minSetLength..maxSetLength).
|
|
||||||
asSequence().
|
|
||||||
flatMap { trailingInsnSet.asSequence().windowed(it) }.
|
|
||||||
filter { it.count { it.second is Node.Instr.Args.Const<*> } <= maxParamCount }.
|
|
||||||
mapNotNull { newIndexedInsnSet ->
|
|
||||||
// Before adding, make sure it qualifies with the stack
|
|
||||||
InsnSet(
|
|
||||||
startIndex = newIndexedInsnSet.first().first,
|
|
||||||
insns = newIndexedInsnSet.map { it.second },
|
|
||||||
valueAddedToStack = null
|
|
||||||
).withStackValueIfValid(stack)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Sort the insn sets by the ones with the most insns
|
|
||||||
insnSets = insnSets.sortedByDescending { it.insns.size }
|
|
||||||
|
|
||||||
// Now let's create replacements for each, keyed by the extracted func
|
|
||||||
val patterns = insnSets.fold(emptyMap<Node.Func, List<Replacement>>()) { map, insnSet ->
|
|
||||||
insnSet.extractCommonFunc().let { (func, replacement) ->
|
|
||||||
val existingReplacements = map.getOrDefault(func, emptyList())
|
|
||||||
// Ignore if there is any overlap
|
|
||||||
if (existingReplacements.any(replacement::overlaps)) map
|
|
||||||
else map + (func to existingReplacements.plus(replacement))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now sort the patterns by most insns saved and return
|
|
||||||
return patterns.map { (k, v) ->
|
|
||||||
CommonPattern(k, v.sortedBy { it.range.first })
|
|
||||||
}.sortedByDescending { it.insnsSaved }
|
|
||||||
}
|
|
||||||
|
|
||||||
val Node.Instr.canBeMoved get() =
|
|
||||||
// No blocks
|
|
||||||
this !is Node.Instr.Block && this !is Node.Instr.Loop && this !is Node.Instr.If &&
|
|
||||||
this !is Node.Instr.Else && this !is Node.Instr.End &&
|
|
||||||
// No breaks
|
|
||||||
this !is Node.Instr.Br && this !is Node.Instr.BrIf && this !is Node.Instr.BrTable &&
|
|
||||||
// No return
|
|
||||||
this !is Node.Instr.Return &&
|
|
||||||
// No local access
|
|
||||||
this !is Node.Instr.GetLocal && this !is Node.Instr.SetLocal && this !is Node.Instr.TeeLocal
|
|
||||||
|
|
||||||
fun InsnSet.withStackValueIfValid(stack: Stack): InsnSet? {
|
|
||||||
// This makes sure that the stack only changes by at most one item and never dips below its starting val.
|
|
||||||
// If it is invalid, null is returned. If it qualifies and does change 1 value, it is set.
|
|
||||||
|
|
||||||
// First, make sure the stack after the last insn is the same as the first or the same + 1 val
|
|
||||||
val startingStack = stack.insnApplies[startIndex].stackAtBeginning!!
|
|
||||||
val endingStack = stack.insnApplies.getOrNull(startIndex + insns.size)?.stackAtBeginning ?: stack.current!!
|
|
||||||
if (endingStack.size != startingStack.size && endingStack.size != startingStack.size + 1) return null
|
|
||||||
if (endingStack.take(startingStack.size) != startingStack) return null
|
|
||||||
|
|
||||||
// Now, walk the insns and make sure they never pop below the start
|
|
||||||
var stackCounter = 0
|
|
||||||
stack.insnApplies.subList(startIndex, startIndex + insns.size).forEach {
|
|
||||||
it.stackChanges.forEach {
|
|
||||||
stackCounter += if (it.pop) -1 else 1
|
|
||||||
if (stackCounter < 0) return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// We're good, now only if the ending stack is one over the start do we have a ret val
|
|
||||||
return copy(
|
|
||||||
valueAddedToStack = endingStack.lastOrNull()?.takeIf { endingStack.size == startingStack.size + 1 }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun InsnSet.extractCommonFunc() =
|
|
||||||
// This extracts a function with constants changed to parameters
|
|
||||||
insns.fold(Pair(
|
|
||||||
Node.Func(Node.Type.Func(params = emptyList(), ret = valueAddedToStack), emptyList(), emptyList()),
|
|
||||||
Replacement(range = startIndex until startIndex + insns.size, preCallConsts = emptyList()))
|
|
||||||
) { (func, repl), insn ->
|
|
||||||
if (insn !is Node.Instr.Args.Const<*>) func.copy(instructions = func.instructions + insn) to repl
|
|
||||||
else func.copy(
|
|
||||||
type = func.type.copy(params = func.type.params + insn.constType),
|
|
||||||
instructions = func.instructions + Node.Instr.GetLocal(func.type.params.size)
|
|
||||||
) to repl.copy(preCallConsts = repl.preCallConsts + insn)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected val Node.Instr.Args.Const<*>.constType get() = when (this) {
|
|
||||||
is Node.Instr.I32Const -> Node.Type.Value.I32
|
|
||||||
is Node.Instr.I64Const -> Node.Type.Value.I64
|
|
||||||
is Node.Instr.F32Const -> Node.Type.Value.F32
|
|
||||||
is Node.Instr.F64Const -> Node.Type.Value.F64
|
|
||||||
else -> error("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
data class InsnSet(
|
|
||||||
val startIndex: Int,
|
|
||||||
val insns: List<Node.Instr>,
|
|
||||||
val valueAddedToStack: Node.Type.Value?
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Replacement(
|
|
||||||
val range: IntRange,
|
|
||||||
val preCallConsts: List<Node.Instr>
|
|
||||||
) {
|
|
||||||
// Subtract one because there is a call after this
|
|
||||||
val insnsSaved get() = (range.last + 1) - range.first - 1 - preCallConsts.size
|
|
||||||
fun overlaps(o: Replacement) = range.contains(o.range.first) || range.contains(o.range.last) ||
|
|
||||||
o.range.contains(range.first) || o.range.contains(range.last)
|
|
||||||
}
|
|
||||||
|
|
||||||
data class CommonPattern(
|
|
||||||
val newFunc: Node.Func,
|
|
||||||
// In order by earliest replacement first
|
|
||||||
val replacements: List<Replacement>
|
|
||||||
) {
|
|
||||||
// Replacement pieces saved (with one added for the invocation) less new func instructions
|
|
||||||
val insnsSaved get() = replacements.sumBy { it.insnsSaved } - newFunc.instructions.size
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object : SplitLargeFunc()
|
|
||||||
}
|
|
@ -1,9 +1,9 @@
|
|||||||
package asmble.cli
|
package asmble.cli
|
||||||
|
|
||||||
import asmble.ast.Script
|
import asmble.ast.Script
|
||||||
|
import asmble.compile.jvm.AsmToBinary
|
||||||
import asmble.compile.jvm.AstToAsm
|
import asmble.compile.jvm.AstToAsm
|
||||||
import asmble.compile.jvm.ClsContext
|
import asmble.compile.jvm.ClsContext
|
||||||
import asmble.compile.jvm.withComputedFramesAndMaxs
|
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
|
|
||||||
@Suppress("NAME_SHADOWING")
|
@Suppress("NAME_SHADOWING")
|
||||||
@ -69,7 +69,7 @@ open class Compile : Command<Compile.Args>() {
|
|||||||
includeBinary = args.includeBinary
|
includeBinary = args.includeBinary
|
||||||
)
|
)
|
||||||
AstToAsm.fromModule(ctx)
|
AstToAsm.fromModule(ctx)
|
||||||
outStream.write(ctx.cls.withComputedFramesAndMaxs())
|
outStream.write(AsmToBinary.fromClassNode(ctx.cls))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package asmble.cli
|
package asmble.cli
|
||||||
|
|
||||||
|
import asmble.compile.jvm.AsmToBinary
|
||||||
import asmble.compile.jvm.Linker
|
import asmble.compile.jvm.Linker
|
||||||
import asmble.compile.jvm.withComputedFramesAndMaxs
|
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
|
|
||||||
open class Link : Command<Link.Args>() {
|
open class Link : Command<Link.Args>() {
|
||||||
@ -52,7 +52,7 @@ open class Link : Command<Link.Args>() {
|
|||||||
defaultMaxMemPages = args.defaultMaxMem
|
defaultMaxMemPages = args.defaultMaxMem
|
||||||
)
|
)
|
||||||
Linker.link(ctx)
|
Linker.link(ctx)
|
||||||
outStream.write(ctx.cls.withComputedFramesAndMaxs())
|
outStream.write(AsmToBinary.fromClassNode(ctx.cls))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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, Help, Invoke, Link, Run, SplitFunc, Translate)
|
val commands = listOf(Compile, Help, Invoke, Link, Run, Translate)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entry point of command line interface.
|
* Entry point of command line interface.
|
||||||
|
@ -1,146 +0,0 @@
|
|||||||
package asmble.cli
|
|
||||||
|
|
||||||
import asmble.ast.Node
|
|
||||||
import asmble.ast.Script
|
|
||||||
import asmble.ast.opt.SplitLargeFunc
|
|
||||||
|
|
||||||
open class SplitFunc : Command<SplitFunc.Args>() {
|
|
||||||
override val name = "split-func"
|
|
||||||
override val desc = "Split a WebAssembly function into two"
|
|
||||||
|
|
||||||
override fun args(bld: Command.ArgsBuilder) = Args(
|
|
||||||
inFile = bld.arg(
|
|
||||||
name = "inFile",
|
|
||||||
desc = "The wast or wasm WebAssembly file name. Can be '--' to read from stdin."
|
|
||||||
),
|
|
||||||
funcName = bld.arg(
|
|
||||||
name = "funcName",
|
|
||||||
desc = "The name (or '#' + function space index) of the function to split"
|
|
||||||
),
|
|
||||||
inFormat = bld.arg(
|
|
||||||
name = "inFormat",
|
|
||||||
opt = "in",
|
|
||||||
desc = "Either 'wast' or 'wasm' to describe format.",
|
|
||||||
default = "<use file extension>",
|
|
||||||
lowPriority = true
|
|
||||||
),
|
|
||||||
outFile = bld.arg(
|
|
||||||
name = "outFile",
|
|
||||||
opt = "outFile",
|
|
||||||
desc = "The wast or wasm WebAssembly file name. Can be '--' to write to stdout.",
|
|
||||||
default = "<inFileSansExt.split.wasm or stdout>",
|
|
||||||
lowPriority = true
|
|
||||||
),
|
|
||||||
outFormat = bld.arg(
|
|
||||||
name = "outFormat",
|
|
||||||
opt = "out",
|
|
||||||
desc = "Either 'wast' or 'wasm' to describe format.",
|
|
||||||
default = "<use file extension or wast for stdout>",
|
|
||||||
lowPriority = true
|
|
||||||
),
|
|
||||||
compact = bld.flag(
|
|
||||||
opt = "compact",
|
|
||||||
desc = "If set for wast out format, will be compacted.",
|
|
||||||
lowPriority = true
|
|
||||||
),
|
|
||||||
minInsnSetLength = bld.arg(
|
|
||||||
name = "minInsnSetLength",
|
|
||||||
opt = "minLen",
|
|
||||||
desc = "The minimum number of instructions allowed for the split off function.",
|
|
||||||
default = "5",
|
|
||||||
lowPriority = true
|
|
||||||
).toInt(),
|
|
||||||
maxInsnSetLength = bld.arg(
|
|
||||||
name = "maxInsnSetLength",
|
|
||||||
opt = "maxLen",
|
|
||||||
desc = "The maximum number of instructions allowed for the split off function.",
|
|
||||||
default = "40",
|
|
||||||
lowPriority = true
|
|
||||||
).toInt(),
|
|
||||||
maxNewFuncParamCount = bld.arg(
|
|
||||||
name = "maxNewFuncParamCount",
|
|
||||||
opt = "maxParams",
|
|
||||||
desc = "The maximum number of params allowed for the split off function.",
|
|
||||||
default = "30",
|
|
||||||
lowPriority = true
|
|
||||||
).toInt(),
|
|
||||||
attempts = bld.arg(
|
|
||||||
name = "attempts",
|
|
||||||
opt = "attempts",
|
|
||||||
desc = "The number of attempts to perform.",
|
|
||||||
default = "1",
|
|
||||||
lowPriority = true
|
|
||||||
).toInt()
|
|
||||||
).also { bld.done() }
|
|
||||||
|
|
||||||
override fun run(args: Args) {
|
|
||||||
// Load the mod
|
|
||||||
val translate = Translate().also { it.logger = logger }
|
|
||||||
val inFormat =
|
|
||||||
if (args.inFormat != "<use file extension>") args.inFormat
|
|
||||||
else args.inFile.substringAfterLast('.', "<unknown>")
|
|
||||||
val script = translate.inToAst(args.inFile, inFormat)
|
|
||||||
var mod = (script.commands.firstOrNull() as? Script.Cmd.Module)?.module ?: error("Only a single module allowed")
|
|
||||||
|
|
||||||
// Do attempts
|
|
||||||
val splitter = SplitLargeFunc(
|
|
||||||
minSetLength = args.minInsnSetLength,
|
|
||||||
maxSetLength = args.maxInsnSetLength,
|
|
||||||
maxParamCount = args.maxNewFuncParamCount
|
|
||||||
)
|
|
||||||
for (attempt in 0 until args.attempts) {
|
|
||||||
// Find the function
|
|
||||||
var index = mod.names?.funcNames?.toList()?.find { it.second == args.funcName }?.first
|
|
||||||
if (index == null && args.funcName.startsWith('#')) index = args.funcName.drop(1).toInt()
|
|
||||||
val origFunc = index?.let {
|
|
||||||
mod.funcs.getOrNull(it - mod.imports.count { it.kind is Node.Import.Kind.Func })
|
|
||||||
} ?: error("Unable to find func")
|
|
||||||
|
|
||||||
// Split it
|
|
||||||
val results = splitter.apply(mod, index)
|
|
||||||
if (results == null) {
|
|
||||||
logger.warn { "No instructions after attempt $attempt" }
|
|
||||||
break
|
|
||||||
}
|
|
||||||
val (splitMod, insnsSaved) = results
|
|
||||||
val newFunc = splitMod.funcs[index - mod.imports.count { it.kind is Node.Import.Kind.Func }]
|
|
||||||
val splitFunc = splitMod.funcs.last()
|
|
||||||
logger.warn {
|
|
||||||
"Split complete, from func with ${origFunc.instructions.size} insns to a func " +
|
|
||||||
"with ${newFunc.instructions.size} insns + delegated func " +
|
|
||||||
"with ${splitFunc.instructions.size} insns and ${splitFunc.type.params.size} params, " +
|
|
||||||
"saved $insnsSaved insns"
|
|
||||||
}
|
|
||||||
mod = splitMod
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write it
|
|
||||||
val outFile = when {
|
|
||||||
args.outFile != "<inFileSansExt.split.wasm or stdout>" -> args.outFile
|
|
||||||
args.inFile == "--" -> "--"
|
|
||||||
else -> args.inFile.replaceAfterLast('.', "split." + args.inFile.substringAfterLast('.'))
|
|
||||||
}
|
|
||||||
val outFormat = when {
|
|
||||||
args.outFormat != "<use file extension or wast for stdout>" -> args.outFormat
|
|
||||||
outFile == "--" -> "wast"
|
|
||||||
else -> outFile.substringAfterLast('.', "<unknown>")
|
|
||||||
}
|
|
||||||
translate.astToOut(outFile, outFormat, args.compact,
|
|
||||||
Script(listOf(Script.Cmd.Module(mod, mod.names?.moduleName))))
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Args(
|
|
||||||
val inFile: String,
|
|
||||||
val inFormat: String,
|
|
||||||
val funcName: String,
|
|
||||||
val outFile: String,
|
|
||||||
val outFormat: String,
|
|
||||||
val compact: Boolean,
|
|
||||||
val minInsnSetLength: Int,
|
|
||||||
val maxInsnSetLength: Int,
|
|
||||||
val maxNewFuncParamCount: Int,
|
|
||||||
val attempts: Int
|
|
||||||
)
|
|
||||||
|
|
||||||
companion object : SplitFunc()
|
|
||||||
}
|
|
@ -2,7 +2,6 @@ package asmble.compile.jvm
|
|||||||
|
|
||||||
import asmble.ast.Node
|
import asmble.ast.Node
|
||||||
import org.objectweb.asm.ClassReader
|
import org.objectweb.asm.ClassReader
|
||||||
import org.objectweb.asm.ClassWriter
|
|
||||||
import org.objectweb.asm.Opcodes
|
import org.objectweb.asm.Opcodes
|
||||||
import org.objectweb.asm.Type
|
import org.objectweb.asm.Type
|
||||||
import org.objectweb.asm.tree.*
|
import org.objectweb.asm.tree.*
|
||||||
@ -189,16 +188,6 @@ fun MethodNode.toAsmString(): String {
|
|||||||
val Node.Type.Func.asmDesc: String get() =
|
val Node.Type.Func.asmDesc: String get() =
|
||||||
(this.ret?.typeRef ?: Void::class.ref).asMethodRetDesc(*this.params.map { it.typeRef }.toTypedArray())
|
(this.ret?.typeRef ?: Void::class.ref).asMethodRetDesc(*this.params.map { it.typeRef }.toTypedArray())
|
||||||
|
|
||||||
fun ClassNode.withComputedFramesAndMaxs(
|
|
||||||
cw: ClassWriter = ClassWriter(ClassWriter.COMPUTE_FRAMES + ClassWriter.COMPUTE_MAXS)
|
|
||||||
): ByteArray {
|
|
||||||
// Note, compute maxs adds a bunch of NOPs for unreachable code.
|
|
||||||
// See $func12 of block.wast. I don't believe the extra time over the
|
|
||||||
// instructions to remove the NOPs is worth it.
|
|
||||||
this.accept(cw)
|
|
||||||
return cw.toByteArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ClassNode.toAsmString(): String {
|
fun ClassNode.toAsmString(): String {
|
||||||
val stringWriter = StringWriter()
|
val stringWriter = StringWriter()
|
||||||
this.accept(TraceClassVisitor(PrintWriter(stringWriter)))
|
this.accept(TraceClassVisitor(PrintWriter(stringWriter)))
|
||||||
|
51
compiler/src/main/kotlin/asmble/compile/jvm/AsmToBinary.kt
Normal file
51
compiler/src/main/kotlin/asmble/compile/jvm/AsmToBinary.kt
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package asmble.compile.jvm
|
||||||
|
|
||||||
|
import asmble.compile.jvm.msplit.SplitMethod
|
||||||
|
import org.objectweb.asm.ClassWriter
|
||||||
|
import org.objectweb.asm.MethodTooLargeException
|
||||||
|
import org.objectweb.asm.Opcodes
|
||||||
|
import org.objectweb.asm.tree.ClassNode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* May mutate given class nodes on [fromClassNode] if [splitMethod] is present (the default). Uses the two-param
|
||||||
|
* [SplitMethod.split] call to try and split overly large methods.
|
||||||
|
*/
|
||||||
|
open class AsmToBinary(val splitMethod: SplitMethod? = SplitMethod(Opcodes.ASM6)) {
|
||||||
|
fun fromClassNode(
|
||||||
|
cn: ClassNode,
|
||||||
|
newClassWriter: () -> ClassWriter = { ClassWriter(ClassWriter.COMPUTE_FRAMES + ClassWriter.COMPUTE_MAXS) }
|
||||||
|
): ByteArray {
|
||||||
|
while (true) {
|
||||||
|
val cw = newClassWriter()
|
||||||
|
// Note, compute maxs adds a bunch of NOPs for unreachable code.
|
||||||
|
// See $func12 of block.wast. I don't believe the extra time over the
|
||||||
|
// instructions to remove the NOPs is worth it.
|
||||||
|
cn.accept(cw)
|
||||||
|
try {
|
||||||
|
return cw.toByteArray()
|
||||||
|
} catch (e: MethodTooLargeException) {
|
||||||
|
if (splitMethod == null) throw e
|
||||||
|
// Split the offending method by removing it and replacing it with the split ones
|
||||||
|
require(cn.name == e.className)
|
||||||
|
val tooLargeIndex = cn.methods.indexOfFirst { it.name == e.methodName && it.desc == e.descriptor }
|
||||||
|
require(tooLargeIndex >= 0)
|
||||||
|
val split = splitMethod.split(cn.name, cn.methods[tooLargeIndex])
|
||||||
|
split ?: throw IllegalStateException("Failed to split", e)
|
||||||
|
// Change the split off method's name if there's already one
|
||||||
|
val origName = split.splitOffMethod.name
|
||||||
|
var foundCount = 0
|
||||||
|
while (cn.methods.any { it.name == split.splitOffMethod.name }) {
|
||||||
|
split.splitOffMethod.name = origName + (++foundCount)
|
||||||
|
}
|
||||||
|
// Replace at the index
|
||||||
|
cn.methods.removeAt(tooLargeIndex)
|
||||||
|
cn.methods.add(tooLargeIndex, split.splitOffMethod)
|
||||||
|
cn.methods.add(tooLargeIndex, split.trimmedMethod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : AsmToBinary() {
|
||||||
|
val noSplit = AsmToBinary(null)
|
||||||
|
}
|
||||||
|
}
|
@ -150,19 +150,11 @@ open class InsnReworker {
|
|||||||
// if we are at 0, add the result of said block if necessary to the count.
|
// if we are at 0, add the result of said block if necessary to the count.
|
||||||
if (insideOfBlocks > 0) {
|
if (insideOfBlocks > 0) {
|
||||||
// If it's not a block, just ignore it
|
// If it's not a block, just ignore it
|
||||||
val blockStackDiff = insns[insnIndex].let {
|
(insns[insnIndex] as? Node.Instr.Args.Type)?.let {
|
||||||
when (it) {
|
|
||||||
is Node.Instr.Block -> if (it.type == null) 0 else 1
|
|
||||||
is Node.Instr.Loop -> 0
|
|
||||||
is Node.Instr.If -> if (it.type == null) -1 else 0
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (blockStackDiff != null) {
|
|
||||||
insideOfBlocks--
|
insideOfBlocks--
|
||||||
ctx.trace { "Found block begin, number of blocks we're still inside: $insideOfBlocks" }
|
ctx.trace { "Found block begin, number of blocks we're still inside: $insideOfBlocks" }
|
||||||
// We're back on our block, change the count
|
// We're back on our block, change the count if it had a result
|
||||||
if (insideOfBlocks == 0) countSoFar += blockStackDiff
|
if (insideOfBlocks == 0 && it.type != null) countSoFar++
|
||||||
}
|
}
|
||||||
if (insideOfBlocks > 0) continue
|
if (insideOfBlocks > 0) continue
|
||||||
}
|
}
|
||||||
@ -249,10 +241,9 @@ open class InsnReworker {
|
|||||||
*/
|
*/
|
||||||
fun insnStackDiff(ctx: ClsContext, insn: Node.Instr): Int = when (insn) {
|
fun insnStackDiff(ctx: ClsContext, insn: Node.Instr): Int = when (insn) {
|
||||||
is Node.Instr.Unreachable, is Node.Instr.Nop, is Node.Instr.Block,
|
is Node.Instr.Unreachable, is Node.Instr.Nop, is Node.Instr.Block,
|
||||||
is Node.Instr.Loop, is Node.Instr.If, is Node.Instr.Else,
|
is Node.Instr.Loop, is Node.Instr.Else, is Node.Instr.End, is Node.Instr.Br,
|
||||||
is Node.Instr.End, is Node.Instr.Br, is Node.Instr.BrIf,
|
|
||||||
is Node.Instr.Return -> NOP
|
is Node.Instr.Return -> NOP
|
||||||
is Node.Instr.BrTable -> POP_PARAM
|
is Node.Instr.If, is Node.Instr.BrIf, is Node.Instr.BrTable -> POP_PARAM
|
||||||
is Node.Instr.Call -> ctx.funcTypeAtIndex(insn.index).let {
|
is Node.Instr.Call -> ctx.funcTypeAtIndex(insn.index).let {
|
||||||
// All calls pop params and any return is a push
|
// All calls pop params and any return is a push
|
||||||
(POP_PARAM * it.params.size) + (if (it.ret == null) NOP else PUSH_RESULT)
|
(POP_PARAM * it.params.size) + (if (it.ret == null) NOP else PUSH_RESULT)
|
||||||
|
@ -201,7 +201,7 @@ open class Linker {
|
|||||||
"instance" + mod.name.javaIdent.capitalize(), mod.ref.asmDesc),
|
"instance" + mod.name.javaIdent.capitalize(), mod.ref.asmDesc),
|
||||||
InsnNode(Opcodes.ARETURN)
|
InsnNode(Opcodes.ARETURN)
|
||||||
)
|
)
|
||||||
ctx.cls.methods.plusAssign(func)
|
ctx.cls.methods.plusAssign(func.toMethodNode())
|
||||||
}
|
}
|
||||||
|
|
||||||
class ModuleClass(val cls: Class<*>, overrideName: String? = null) {
|
class ModuleClass(val cls: Class<*>, overrideName: String? = null) {
|
||||||
|
@ -319,10 +319,15 @@ data class ScriptContext(
|
|||||||
bindImport(import, true, MethodType.methodType(Array<MethodHandle>::class.java)).
|
bindImport(import, true, MethodType.methodType(Array<MethodHandle>::class.java)).
|
||||||
invokeWithArguments()!! as Array<MethodHandle>
|
invokeWithArguments()!! as Array<MethodHandle>
|
||||||
|
|
||||||
open class SimpleClassLoader(parent: ClassLoader, logger: Logger) : ClassLoader(parent), Logger by logger {
|
open class SimpleClassLoader(
|
||||||
|
parent: ClassLoader,
|
||||||
|
logger: Logger,
|
||||||
|
val splitWhenTooLarge: Boolean = true
|
||||||
|
) : ClassLoader(parent), Logger by logger {
|
||||||
fun fromBuiltContext(ctx: ClsContext): Class<*> {
|
fun fromBuiltContext(ctx: ClsContext): Class<*> {
|
||||||
trace { "Computing frames for ASM class:\n" + ctx.cls.toAsmString() }
|
trace { "Computing frames for ASM class:\n" + ctx.cls.toAsmString() }
|
||||||
return ctx.cls.withComputedFramesAndMaxs().let { bytes ->
|
val writer = if (splitWhenTooLarge) AsmToBinary else AsmToBinary.noSplit
|
||||||
|
return writer.fromClassNode(ctx.cls).let { bytes ->
|
||||||
debug { "ASM class:\n" + bytes.asClassNode().toAsmString() }
|
debug { "ASM class:\n" + bytes.asClassNode().toAsmString() }
|
||||||
defineClass("${ctx.packageName}.${ctx.className}", bytes, 0, bytes.size)
|
defineClass("${ctx.packageName}.${ctx.className}", bytes, 0, bytes.size)
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ class SpecTestUnit(name: String, wast: String, expectedOutput: String?) : BaseTe
|
|||||||
"nop" -> 20
|
"nop" -> 20
|
||||||
"memory_grow" -> 830
|
"memory_grow" -> 830
|
||||||
"imports" -> 5
|
"imports" -> 5
|
||||||
else -> 1
|
else -> 2
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun warningInsteadOfErrReason(t: Throwable) = when (name) {
|
override fun warningInsteadOfErrReason(t: Throwable) = when (name) {
|
||||||
|
@ -1,29 +1,31 @@
|
|||||||
package asmble.ast.opt
|
package asmble.run.jvm
|
||||||
|
|
||||||
import asmble.TestBase
|
import asmble.TestBase
|
||||||
import asmble.ast.Node
|
import asmble.ast.Node
|
||||||
import asmble.compile.jvm.AstToAsm
|
import asmble.compile.jvm.AstToAsm
|
||||||
import asmble.compile.jvm.ClsContext
|
import asmble.compile.jvm.ClsContext
|
||||||
import asmble.run.jvm.ScriptContext
|
import org.junit.Assert
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.objectweb.asm.MethodTooLargeException
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class SplitLargeFuncTest : TestBase() {
|
class LargeFuncTest : TestBase() {
|
||||||
@Test
|
@Test
|
||||||
fun testSplitLargeFunc() {
|
fun testLargeFunc() {
|
||||||
// We're going to make a large function that does some addition and then stores in mem
|
val numInsnChunks = 6001
|
||||||
|
// Make large func that does some math
|
||||||
val ctx = ClsContext(
|
val ctx = ClsContext(
|
||||||
packageName = "test",
|
packageName = "test",
|
||||||
className = "Temp" + UUID.randomUUID().toString().replace("-", ""),
|
className = "Temp" + UUID.randomUUID().toString().replace("-", ""),
|
||||||
logger = logger,
|
logger = logger,
|
||||||
mod = Node.Module(
|
mod = Node.Module(
|
||||||
memories = listOf(Node.Type.Memory(Node.ResizableLimits(initial = 2, maximum = 2))),
|
memories = listOf(Node.Type.Memory(Node.ResizableLimits(initial = 4, maximum = 4))),
|
||||||
funcs = listOf(Node.Func(
|
funcs = listOf(Node.Func(
|
||||||
type = Node.Type.Func(params = emptyList(), ret = null),
|
type = Node.Type.Func(params = emptyList(), ret = null),
|
||||||
locals = emptyList(),
|
locals = emptyList(),
|
||||||
instructions = (0 until 501).flatMap {
|
instructions = (0 until numInsnChunks).flatMap {
|
||||||
listOf<Node.Instr>(
|
listOf<Node.Instr>(
|
||||||
Node.Instr.I32Const(it * 4),
|
Node.Instr.I32Const(it * 4),
|
||||||
// Let's to i * (i = 1)
|
// Let's to i * (i = 1)
|
||||||
@ -47,43 +49,21 @@ class SplitLargeFuncTest : TestBase() {
|
|||||||
)
|
)
|
||||||
// Compile it
|
// Compile it
|
||||||
AstToAsm.fromModule(ctx)
|
AstToAsm.fromModule(ctx)
|
||||||
|
// Confirm the method size is too large
|
||||||
|
try {
|
||||||
|
ScriptContext.SimpleClassLoader(javaClass.classLoader, logger, splitWhenTooLarge = false).
|
||||||
|
fromBuiltContext(ctx)
|
||||||
|
Assert.fail()
|
||||||
|
} catch (e: MethodTooLargeException) { }
|
||||||
|
// Try again with split
|
||||||
val cls = ScriptContext.SimpleClassLoader(javaClass.classLoader, logger).fromBuiltContext(ctx)
|
val cls = ScriptContext.SimpleClassLoader(javaClass.classLoader, logger).fromBuiltContext(ctx)
|
||||||
|
// Create it and check that it still does what we expect
|
||||||
val inst = cls.newInstance()
|
val inst = cls.newInstance()
|
||||||
// Run someFunc
|
// Run someFunc
|
||||||
cls.getMethod("someFunc").invoke(inst)
|
cls.getMethod("someFunc").invoke(inst)
|
||||||
// Get the memory out
|
// Get the memory out
|
||||||
val mem = cls.getMethod("getMemory").invoke(inst) as ByteBuffer
|
val mem = cls.getMethod("getMemory").invoke(inst) as ByteBuffer
|
||||||
// Read out the mem values
|
// Read out the mem values
|
||||||
(0 until 501).forEach { assertEquals(it * (it - 1), mem.getInt(it * 4)) }
|
(0 until numInsnChunks).forEach { assertEquals(it * (it - 1), mem.getInt(it * 4)) }
|
||||||
|
|
||||||
// Now split it
|
|
||||||
val (splitMod, insnsSaved) = SplitLargeFunc.apply(ctx.mod, 0) ?: error("Nothing could be split")
|
|
||||||
// Count insns and confirm it is as expected
|
|
||||||
val origInsnCount = ctx.mod.funcs.sumBy { it.instructions.size }
|
|
||||||
val newInsnCount = splitMod.funcs.sumBy { it.instructions.size }
|
|
||||||
assertEquals(origInsnCount - newInsnCount, insnsSaved)
|
|
||||||
// Compile it
|
|
||||||
val splitCtx = ClsContext(
|
|
||||||
packageName = "test",
|
|
||||||
className = "Temp" + UUID.randomUUID().toString().replace("-", ""),
|
|
||||||
logger = logger,
|
|
||||||
mod = splitMod
|
|
||||||
)
|
|
||||||
AstToAsm.fromModule(splitCtx)
|
|
||||||
val splitCls = ScriptContext.SimpleClassLoader(javaClass.classLoader, logger).fromBuiltContext(splitCtx)
|
|
||||||
val splitInst = splitCls.newInstance()
|
|
||||||
// Run someFunc
|
|
||||||
splitCls.getMethod("someFunc").invoke(splitInst)
|
|
||||||
// Get the memory out and compare it
|
|
||||||
val splitMem = splitCls.getMethod("getMemory").invoke(splitInst) as ByteBuffer
|
|
||||||
assertEquals(mem, splitMem)
|
|
||||||
// Dump some info
|
|
||||||
logger.debug {
|
|
||||||
val orig = ctx.mod.funcs.first()
|
|
||||||
val (new, split) = splitMod.funcs.let { it.first() to it.last() }
|
|
||||||
"Split complete, from single func with ${orig.instructions.size} insns to func " +
|
|
||||||
"with ${new.instructions.size} insns + delegated func " +
|
|
||||||
"with ${split.instructions.size} insns and ${split.type.params.size} params"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
14
examples/c-simple/README.md
Normal file
14
examples/c-simple/README.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
### Example: C Simple
|
||||||
|
|
||||||
|
This shows a simple example of compiling C to WASM and then to the JVM. This is the C version of
|
||||||
|
[rust-simple](../rust-simple).
|
||||||
|
|
||||||
|
In order to run the C or C++ examples, the latest LLVM binaries must be on the `PATH`, built with the experimental
|
||||||
|
WebAssembly target. This can be built by passing `-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly` to `cmake` when
|
||||||
|
building WebAssembly. Or it can be downloaded from a nightly build site
|
||||||
|
([this one](http://gsdview.appspot.com/wasm-llvm/builds/) was used for these examples at the time of writing).
|
||||||
|
|
||||||
|
Everything else is basically the same as [rust-simple](../rust-simple) except with C code and using `clang`. To run
|
||||||
|
execute the following from the root `asmble` dir:
|
||||||
|
|
||||||
|
gradlew --no-daemon :examples:c-simple:run
|
3
examples/c-simple/src/lib.c
Normal file
3
examples/c-simple/src/lib.c
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
int addOne(int x) {
|
||||||
|
return x + 1;
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package asmble.examples.csimple;
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
|
||||||
|
import asmble.generated.CSimple;
|
||||||
|
|
||||||
|
class Main {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// Doesn't need memory or method table
|
||||||
|
CSimple simple = new CSimple(0, new MethodHandle[0]);
|
||||||
|
System.out.println("25 + 1 = " + simple.addOne(25));
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,8 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Printf("Args: %v", os.Args)
|
fmt.Println("Hello, World!")
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ rootProject.name = 'asmble'
|
|||||||
include 'annotations',
|
include 'annotations',
|
||||||
'compiler',
|
'compiler',
|
||||||
'examples:c-simple',
|
'examples:c-simple',
|
||||||
// 'examples:go-simple',
|
'examples:go-simple',
|
||||||
// 'examples:rust-regex', // todo will be enabled when the problem with string max size will be solved
|
'examples:rust-regex',
|
||||||
'examples:rust-simple',
|
'examples:rust-simple',
|
||||||
'examples:rust-string'
|
'examples:rust-string'
|
Reference in New Issue
Block a user