31 Commits

Author SHA1 Message Date
vms
23665fa16b initial commit 2019-09-05 17:45:42 +03:00
vms
4c65740d03 initial commit (#13) 2019-08-10 14:52:25 +03:00
vms
728b78d713 add EIC metering (#12) 2019-08-06 17:25:48 +03:00
vms
cb907ae2da Env module for gas metering (#11) 2019-08-06 10:45:16 +03:00
1d6002624f Fix memory builder (#10)
* fix memory instance creation

* change version
2019-06-03 14:02:15 +03:00
ad2b7c071f Bytebuffer abstraction (#9) 2019-05-13 16:40:07 +03:00
vms
119ce58c9e add using optional module name to registred names (#8) 2019-03-29 19:01:49 +03:00
vms
1323e02c95 fix bintray user and key properties absence (#4) 2018-11-19 15:25:31 +03:00
9172fba948 Add posibillity to add a Logger Wasm module (#3)
* Add logger module

* Add LoggerModuleTest
2018-11-15 12:38:50 +04:00
b9b45cf997 Merge pull request #2 from fluencelabs/logger
Fix logger and return C example
2018-11-09 13:47:41 +04:00
2bfa39a3c6 Tweaking after merge 2018-11-09 10:30:38 +04:00
317b608048 Merge fix for late init for logger 2018-11-09 10:28:47 +04:00
21b023f1c6 Return C example and skip it by default 2018-11-09 10:18:49 +04:00
765d8b4dba Possibility to skip examples 2018-11-09 10:03:44 +04:00
58cf836b76 Disable Go and Rust examples and up project version 2018-10-01 13:40:38 +04:00
56c2c8d672 Fix lateinit error with logger for wasm files 2018-10-01 13:39:08 +04:00
fb0be9d31a Merge remote-tracking branch 'upstream/master'
# Conflicts:
#	compiler/src/test/resources/spec
2018-09-18 15:56:16 +04:00
da70c9fca4 Fix for "lateinit property logger has not been initialized" 2018-08-28 16:50:37 +04:00
a1a5563367 Publishing the fork to binTray 2018-08-14 17:32:10 +04:00
e489e7c889 Publishing the fork to binTray 2018-08-14 17:22:30 +04:00
c1391b2701 Expected but isn't working version 2018-08-14 12:11:48 +04:00
6b28c5a93b merge original-master to master 2018-08-10 13:10:28 +04:00
1990f46743 merge asmble-master to master 2018-07-26 10:49:28 +04:00
559df45f09 Merge pull request #1 from cretz/master
Fetch master changes
2018-07-26 10:34:42 +04:00
80a8a1fbb9 Temporary disable rust-regex example 2018-07-25 10:35:37 +04:00
dd72c7124c Fix Rust Simple and Rust String examples 2018-07-25 10:21:38 +04:00
c04a3c4a9b Add some java docs 2018-07-24 11:00:41 +04:00
51520ac07d Add some java docs 2018-07-23 12:52:30 +04:00
97660de6ba Add some java docs 2018-07-20 12:52:21 +04:00
cee7a86773 Fix rust-regex example 2018-07-19 17:02:25 +04:00
cfa4a35af1 remove C example 2018-07-19 09:14:01 +04:00
39 changed files with 939 additions and 274 deletions

View File

@ -14,13 +14,18 @@ buildscript {
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'me.champeau.gradle:jmh-gradle-plugin:0.4.5' classpath 'me.champeau.gradle:jmh-gradle-plugin:0.4.5'
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4'
} }
} }
allprojects { allprojects {
apply plugin: 'java' apply plugin: 'java'
group 'com.github.cretz.asmble' group 'com.github.cretz.asmble'
version '0.4.0-SNAPSHOT' version '0.4.11-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()
@ -34,7 +39,7 @@ project(':annotations') {
options.addStringOption 'Xdoclint:all', '-Xdoclint:-missing' options.addStringOption 'Xdoclint:all', '-Xdoclint:-missing'
} }
publishSettings(project, 'asmble-annotations', 'Asmble WASM Annotations', true) publishSettings(project, 'asmble-annotations', 'Asmble WASM Annotations')
} }
project(':compiler') { project(':compiler') {
@ -58,7 +63,7 @@ project(':compiler') {
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
} }
publishSettings(project, 'asmble-compiler', 'Asmble WASM Compiler', false) publishSettings(project, 'asmble-compiler', 'Asmble WASM Compiler')
} }
project(':examples') { project(':examples') {
@ -66,7 +71,7 @@ project(':examples') {
dependencies { dependencies {
compileOnly project(':compiler') compileOnly project(':compiler')
} }
// C/C++ example helpers // C/C++ example helpers
task cToWasm { task cToWasm {
@ -76,7 +81,7 @@ project(':examples') {
def cFileName = fileTree(dir: 'src', includes: ['*.c']).files.iterator().next() def cFileName = fileTree(dir: 'src', includes: ['*.c']).files.iterator().next()
commandLine 'clang', '--target=wasm32-unknown-unknown-wasm', '-O3', cFileName, '-c', '-o', 'build/lib.wasm' commandLine 'clang', '--target=wasm32-unknown-unknown-wasm', '-O3', cFileName, '-c', '-o', 'build/lib.wasm'
} }
} }
} }
task showCWast(type: JavaExec) { task showCWast(type: JavaExec) {
@ -174,11 +179,19 @@ project(':examples') {
} }
project(':examples:c-simple') { 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
}
apply plugin: 'application' apply plugin: 'application'
ext.wasmCompiledClassName = 'asmble.generated.CSimple' ext.wasmCompiledClassName = 'asmble.generated.CSimple'
dependencies { dependencies {
compile files('build/wasm-classes') compile files('build/wasm-classes')
} }
compileJava { compileJava {
dependsOn compileCWasm dependsOn compileCWasm
} }
@ -186,6 +199,12 @@ project(':examples:c-simple') {
} }
project(':examples:go-simple') { project(':examples:go-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.GoSimple' ext.wasmCompiledClassName = 'asmble.generated.GoSimple'
dependencies { dependencies {
@ -197,30 +216,44 @@ project(':examples:go-simple') {
mainClassName = 'asmble.examples.gosimple.Main' mainClassName = 'asmble.examples.gosimple.Main'
} }
project(':examples:rust-regex') { project(':examples:rust-regex') {
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') compileTestJava.onlyIf { false } // explicit skipping compile
testCompile 'junit:junit:4.12' return
} }
compileJava { apply plugin: 'application'
dependsOn compileRustWasm apply plugin: 'me.champeau.gradle.jmh'
} ext.wasmCompiledClassName = 'asmble.generated.RustRegex'
mainClassName = 'asmble.examples.rustregex.Main' dependencies {
test { compile files('build/wasm-classes')
testLogging.showStandardStreams = true testCompile 'junit:junit:4.12'
testLogging.events 'PASSED', 'SKIPPED' }
} compileJava {
jmh { dependsOn compileRustWasm
iterations = 5 }
warmupIterations = 5 mainClassName = 'asmble.examples.rustregex.Main'
fork = 3
} 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 {
@ -233,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 {
@ -244,75 +283,59 @@ project(':examples:rust-string') {
mainClassName = 'asmble.examples.ruststring.Main' mainClassName = 'asmble.examples.ruststring.Main'
} }
def publishSettings(project, projectName, projectDescription, includeJavadoc) { def publishSettings(project, projectName, projectDescription) {
project.with { project.with {
if (!project.hasProperty('ossrhUsername')) return apply plugin: 'com.jfrog.bintray'
apply plugin: 'maven-publish'
apply plugin: 'maven' apply plugin: 'maven'
apply plugin: 'signing'
archivesBaseName = projectName
task packageSources(type: Jar) { task sourcesJar(type: Jar) {
from sourceSets.main.allJava
classifier = 'sources' classifier = 'sources'
from sourceSets.main.allSource
} }
if (includeJavadoc) { publishing {
task packageJavadoc(type: Jar, dependsOn: 'javadoc') { publications {
from javadoc.destinationDir MyPublication(MavenPublication) {
classifier = 'javadoc' from components.java
} groupId group
} else { artifactId projectName
task packageJavadoc(type: Jar) { artifact sourcesJar
// Empty to satisfy Sonatype's javadoc.jar requirement version version
classifier 'javadoc'
}
}
artifacts {
archives packageSources, packageJavadoc
}
signing {
sign configurations.archives
}
uploadArchives {
repositories {
mavenDeployer {
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
repository(url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/') {
authentication(userName: ossrhUsername, password: ossrhPassword)
}
snapshotRepository(url: 'https://oss.sonatype.org/content/repositories/snapshots/') {
authentication(userName: ossrhUsername, password: ossrhPassword)
}
pom.project {
name projectName
packaging 'jar'
description projectDescription
url 'https://github.com/cretz/asmble'
scm {
connection 'scm:git:git@github.com:cretz/asmble.git'
developerConnection 'scm:git:git@github.com:cretz/asmble.git'
url 'git@github.com:cretz/asmble.git'
}
licenses {
license {
name 'MIT License'
url 'https://opensource.org/licenses/MIT'
}
}
developers {
developer {
id 'cretz'
name 'Chad Retz'
url 'https://github.com/cretz'
}
}
}
} }
} }
} }
bintray {
if(!hasProperty("bintrayUser") || !hasProperty("bintrayKey")) {
return
}
user = bintrayUser
key = bintrayKey
publications = ['MyPublication']
//[Default: false] Whether to override version artifacts already published
override = false
//[Default: false] Whether version should be auto published after an upload
publish = true
pkg {
repo = 'releases'
name = projectName
userOrg = 'fluencelabs'
licenses = ['MIT']
vcsUrl = 'https://github.com/fluencelabs/asmble'
version {
name = project.version
desc = projectDescription
released = new Date()
vcsTag = project.version
}
}
}
} }
}
}

View File

@ -0,0 +1,40 @@
package asmble.compile.jvm;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* The abstraction that describes work with the memory of the virtual machine.
*/
public abstract class MemoryBuffer {
/**
* The default implementation of MemoryBuffer that based on java.nio.DirectByteBuffer
*/
public static MemoryBuffer init(int capacity) {
return new MemoryByteBuffer(ByteBuffer.allocateDirect(capacity));
}
public abstract int capacity();
public abstract int limit();
public abstract MemoryBuffer clear();
public abstract MemoryBuffer limit(int newLimit);
public abstract MemoryBuffer position(int newPosition);
public abstract MemoryBuffer order(ByteOrder order);
public abstract MemoryBuffer duplicate();
public abstract MemoryBuffer put(byte[] arr, int offset, int length);
public abstract MemoryBuffer put(byte[] arr);
public abstract MemoryBuffer put(int index, byte b);
public abstract MemoryBuffer putInt(int index, int n);
public abstract MemoryBuffer putLong(int index, long n);
public abstract MemoryBuffer putDouble(int index, double n);
public abstract MemoryBuffer putShort(int index, short n);
public abstract MemoryBuffer putFloat(int index, float n);
public abstract byte get(int index);
public abstract int getInt(int index);
public abstract long getLong(int index);
public abstract short getShort(int index);
public abstract float getFloat(int index);
public abstract double getDouble(int index);
public abstract MemoryBuffer get(byte[] arr);
}

View File

@ -0,0 +1,8 @@
package asmble.compile.jvm;
/**
* Interface to initialize MemoryBuffer
*/
public interface MemoryBufferBuilder {
MemoryBuffer build(int capacity);
}

View File

@ -81,9 +81,10 @@ public class SplitMethod {
localsMap.put(index, args.size() - 1); localsMap.put(index, args.size() - 1);
}); });
// Create the new method // Create the new method
String name = orig.name.replace("<", "__").replace(">", "__") + "$split";
MethodNode newMethod = new MethodNode(api, MethodNode newMethod = new MethodNode(api,
Opcodes.ACC_STATIC + Opcodes.ACC_PRIVATE + Opcodes.ACC_SYNTHETIC, orig.name + "$split", Opcodes.ACC_STATIC + Opcodes.ACC_PRIVATE + Opcodes.ACC_SYNTHETIC, name,
Type.getMethodDescriptor(Type.getType(Object[].class), args.toArray(new Type[0])), null, null); 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 // Add the written locals to the map that are not already there
int newLocalIndex = args.size(); int newLocalIndex = args.size();
for (Integer key : splitPoint.localsWritten.keySet()) { for (Integer key : splitPoint.localsWritten.keySet()) {
@ -102,9 +103,13 @@ public class SplitMethod {
for (int i = 0; i < splitPoint.length; i++) { for (int i = 0; i < splitPoint.length; i++) {
AbstractInsnNode insn = orig.instructions.get(i + splitPoint.start); AbstractInsnNode insn = orig.instructions.get(i + splitPoint.start);
// Skip frames // Skip frames
if (insn instanceof FrameNode) continue; if (insn instanceof FrameNode) {
insn.accept(newMethod);
continue;
}
// Store the label // Store the label
if (insn instanceof LabelNode) seenLabels.add(((LabelNode) insn).getLabel()); if (insn instanceof LabelNode)
seenLabels.add(((LabelNode) insn).getLabel());
// Change the local if needed // Change the local if needed
if (insn instanceof VarInsnNode) { if (insn instanceof VarInsnNode) {
insn = insn.clone(Collections.emptyMap()); insn = insn.clone(Collections.emptyMap());
@ -168,13 +173,13 @@ public class SplitMethod {
} }
protected MethodNode createTrimmedMethod(String owner, MethodNode orig, protected MethodNode createTrimmedMethod(String owner, MethodNode orig,
MethodNode splitOff, Splitter.SplitPoint splitPoint) { 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 // 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 // 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. // replace the stack and written locals.
// Effectively clone the orig // Effectively clone the orig
MethodNode newMethod = new MethodNode(api, orig.access, orig.name, orig.desc, MethodNode newMethod = new MethodNode(api, orig.access, orig.name, orig.desc,
orig.signature, orig.exceptions.toArray(new String[0])); orig.signature, orig.exceptions.toArray(new String[0]));
orig.accept(newMethod); orig.accept(newMethod);
// Remove all insns, we'll re-add the ones outside the split range // Remove all insns, we'll re-add the ones outside the split range
newMethod.instructions.clear(); newMethod.instructions.clear();
@ -183,11 +188,18 @@ public class SplitMethod {
Set<Label> seenLabels = new HashSet<>(); Set<Label> seenLabels = new HashSet<>();
// Also keep track of the locals that have been stored, need to know // Also keep track of the locals that have been stored, need to know
Set<Integer> seenStoredLocals = new HashSet<>(); Set<Integer> seenStoredLocals = new HashSet<>();
int paramOffset = 0;
// If this is an instance method, we consider "0" (i.e. "this") as seen // If this is an instance method, we consider "0" (i.e. "this") as seen
if ((orig.access & Opcodes.ACC_STATIC) == 0) seenStoredLocals.add(0); if ((orig.access & Opcodes.ACC_STATIC) == 0) {
seenStoredLocals.add(0);
paramOffset = 1;
}
// We also consider parameters as seen
int paramCount = Type.getArgumentTypes(orig.desc).length;
for (int i = 0; i < paramCount; i++) seenStoredLocals.add(i + paramOffset);
// Add the insns before split // Add the insns before split
for (int i = 0; i < splitPoint.start; i++) { for (int i = 0; i < splitPoint.start; i++) {
AbstractInsnNode insn = orig.instructions.get(i + splitPoint.start); AbstractInsnNode insn = orig.instructions.get(i);
// Skip frames // Skip frames
if (insn instanceof FrameNode) continue; if (insn instanceof FrameNode) continue;
// Record label // Record label
@ -255,7 +267,7 @@ public class SplitMethod {
} }
// Now we have restored all locals and all stack...add the rest of the insns after the split // 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++) { for (int i = splitPoint.start + splitPoint.length; i < orig.instructions.size(); i++) {
AbstractInsnNode insn = orig.instructions.get(i + splitPoint.start); AbstractInsnNode insn = orig.instructions.get(i);
// Skip frames // Skip frames
if (insn instanceof FrameNode) continue; if (insn instanceof FrameNode) continue;
// Record label // Record label

View File

@ -67,7 +67,7 @@ public class Splitter implements Iterable<Splitter.SplitPoint> {
public final int length; public final int length;
public SplitPoint(SortedMap<Integer, Type> localsRead, SortedMap<Integer, Type>localsWritten, public SplitPoint(SortedMap<Integer, Type> localsRead, SortedMap<Integer, Type>localsWritten,
List<Type> neededFromStackAtStart, List<Type> putOnStackAtEnd, int start, int length) { List<Type> neededFromStackAtStart, List<Type> putOnStackAtEnd, int start, int length) {
this.localsRead = localsRead; this.localsRead = localsRead;
this.localsWritten = localsWritten; this.localsWritten = localsWritten;
this.neededFromStackAtStart = neededFromStackAtStart; this.neededFromStackAtStart = neededFromStackAtStart;
@ -123,11 +123,22 @@ public class Splitter implements Iterable<Splitter.SplitPoint> {
return ret; return ret;
} }
private int shiftIndexValue = 1;
protected SplitPoint nextOrNull() { protected SplitPoint nextOrNull() {
// Try for each index // Try for each index
while (++currIndex + minSize <= insns.length) { while (++currIndex + minSize <= insns.length) {
if(shiftIndexValue > 0) {
currIndex += shiftIndexValue;
} else {
++currIndex;
}
SplitPoint longest = longestForCurrIndex(); SplitPoint longest = longestForCurrIndex();
if (longest != null) return longest; if (longest != null) {
shiftIndexValue += longest.length;
return longest;
}
} }
return null; return null;
} }
@ -139,6 +150,8 @@ public class Splitter implements Iterable<Splitter.SplitPoint> {
InsnTraverseInfo info = new InsnTraverseInfo(); InsnTraverseInfo info = new InsnTraverseInfo();
info.startIndex = currIndex; info.startIndex = currIndex;
info.endIndex = Math.min(currIndex + maxSize - 1, insns.length - 1); info.endIndex = Math.min(currIndex + maxSize - 1, insns.length - 1);
// Reduce the end by special calls
constrainEndByInvokeSpecial(info);
// Reduce the end based on try/catch blocks the start is in or that jump to // Reduce the end based on try/catch blocks the start is in or that jump to
constrainEndByTryCatchBlocks(info); constrainEndByTryCatchBlocks(info);
// Reduce the end based on any jumps within // Reduce the end based on any jumps within
@ -146,12 +159,23 @@ public class Splitter implements Iterable<Splitter.SplitPoint> {
// Reduce the end based on any jumps into // Reduce the end based on any jumps into
constrainEndByExternalJumps(info); constrainEndByExternalJumps(info);
// Make sure we didn't reduce the end too far // Make sure we didn't reduce the end too far
if (info.getSize() < minSize) return null; //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 // 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 // For the stack, we are going to use the
return splitPointFromInfo(info); return splitPointFromInfo(info);
} }
protected void constrainEndByInvokeSpecial(InsnTraverseInfo info) {
// Can't have an invoke special of <init>
for (int i = info.startIndex; i <= info.endIndex; i++) {
AbstractInsnNode node = insns[i];
if (node.getOpcode() == Opcodes.INVOKESPECIAL && ((MethodInsnNode) node).name.equals("<init>")) {
info.endIndex = Math.max(info.startIndex, i - 1);
return;
}
}
}
protected void constrainEndByTryCatchBlocks(InsnTraverseInfo info) { protected void constrainEndByTryCatchBlocks(InsnTraverseInfo info) {
// Go over all the try/catch blocks, sorted by earliest // Go over all the try/catch blocks, sorted by earliest
for (TryCatchBlockNode block : tryCatchBlocks) { for (TryCatchBlockNode block : tryCatchBlocks) {
@ -251,12 +275,53 @@ public class Splitter implements Iterable<Splitter.SplitPoint> {
} }
} }
// visits instructions with given adaptor
private void visitInstructions(int begin, int end, AnalyzerAdapter adapter) {
if(end - begin <= 0) {
return;
}
List<Object> stack = new ArrayList<>();
List<Object> locals = new ArrayList<>();
boolean gotoSeen = false;
for (int i = begin; i < end; ++i) {
if(insns[i].getOpcode() == Opcodes.GOTO || insns[i].getOpcode() == Opcodes.ATHROW ||
(insns[i].getOpcode() >= Opcodes.TABLESWITCH && insns[i].getOpcode() <= Opcodes.RETURN) ) {
stack = adapter.stack;
locals = adapter.locals;
gotoSeen = true;
} else if(gotoSeen) {
gotoSeen = false;
if(insns[i].getOpcode() >= Opcodes.IRETURN && insns[i].getOpcode() <= Opcodes.RETURN) {
stack.clear();
locals.clear();
}
adapter.visitFrame(Opcodes.F_NEW, locals.size(), locals.toArray(), stack.size(), stack.toArray());
}
insns[i].accept(adapter);
}
AbstractInsnNode lastInsn = insns[end - 1];
if(lastInsn.getOpcode() == Opcodes.GOTO || lastInsn.getOpcode() == Opcodes.ATHROW ||
(lastInsn.getOpcode() >= Opcodes.TABLESWITCH && lastInsn.getOpcode() <= Opcodes.RETURN) ) {
if(lastInsn.getOpcode() >= Opcodes.IRETURN && lastInsn.getOpcode() <= Opcodes.RETURN) {
stack.clear();
locals.clear();
}
adapter.visitFrame(Opcodes.F_NEW, locals.size(), locals.toArray(), stack.size(), stack.toArray());
}
}
protected SplitPoint splitPointFromInfo(InsnTraverseInfo info) { 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 // 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); StackAndLocalTrackingAdapter adapter = new StackAndLocalTrackingAdapter(Splitter.this);
// Visit all of the insns up our start. // 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 // 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); visitInstructions(0, info.startIndex, adapter);
// Take the stack at the start and copy it off // Take the stack at the start and copy it off
List<Object> stackAtStart = new ArrayList<>(adapter.stack); List<Object> stackAtStart = new ArrayList<>(adapter.stack);
// Reset some adapter state // Reset some adapter state
@ -264,27 +329,34 @@ public class Splitter implements Iterable<Splitter.SplitPoint> {
adapter.localsRead.clear(); adapter.localsRead.clear();
adapter.localsWritten.clear(); adapter.localsWritten.clear();
// Now go over the remaining range // Now go over the remaining range
for (int i = info.startIndex; i <= info.endIndex; i++) insns[i].accept(adapter); visitInstructions(info.startIndex, info.endIndex + 1, adapter);
// Build the split point // Build the split point
return new SplitPoint( return new SplitPoint(
localMapFromAdapterLocalMap(adapter.localsRead, adapter.uninitializedTypes), localMapFromAdapterLocalMap(adapter.localsRead, adapter.uninitializedTypes),
localMapFromAdapterLocalMap(adapter.localsWritten, adapter.uninitializedTypes), localMapFromAdapterLocalMap(adapter.localsWritten, adapter.uninitializedTypes),
typesFromAdapterStackRange(stackAtStart, adapter.lowestStackSize, adapter.uninitializedTypes), typesFromAdapterStackRange(stackAtStart, adapter.lowestStackSize, adapter.uninitializedTypes),
typesFromAdapterStackRange(adapter.stack, adapter.lowestStackSize, adapter.uninitializedTypes), typesFromAdapterStackRange(adapter.stack, adapter.lowestStackSize, adapter.uninitializedTypes),
info.startIndex, info.startIndex,
info.getSize() info.getSize()
); );
} }
protected SortedMap<Integer, Type> localMapFromAdapterLocalMap( protected SortedMap<Integer, Type> localMapFromAdapterLocalMap(
SortedMap<Integer, Object> map, Map<Object, Object> uninitializedTypes) { SortedMap<Integer, Object> map, Map<Object, Object> uninitializedTypes) {
SortedMap<Integer, Type> ret = new TreeMap<>(); SortedMap<Integer, Type> ret = new TreeMap<>();
map.forEach((k, v) -> ret.put(k, typeFromAdapterStackItem(v, uninitializedTypes))); map.forEach((k, v) ->
{
if(v != Opcodes.TOP ) {
ret.put(k, typeFromAdapterStackItem(v, uninitializedTypes));
}
}
);
return ret; return ret;
} }
protected List<Type> typesFromAdapterStackRange( protected List<Type> typesFromAdapterStackRange(
List<Object> stack, int start, Map<Object, Object> uninitializedTypes) { List<Object> stack, int start, Map<Object, Object> uninitializedTypes) {
List<Type> ret = new ArrayList<>(); List<Type> ret = new ArrayList<>();
for (int i = start; i < stack.size(); i++) { for (int i = start; i < stack.size(); i++) {
Object item = stack.get(i); Object item = stack.get(i);

View File

@ -19,7 +19,7 @@ class Util {
static boolean isStoreOp(int opcode) { static boolean isStoreOp(int opcode) {
return opcode == Opcodes.ISTORE || opcode == Opcodes.LSTORE || opcode == Opcodes.FSTORE || return opcode == Opcodes.ISTORE || opcode == Opcodes.LSTORE || opcode == Opcodes.FSTORE ||
opcode == Opcodes.DSTORE || opcode == Opcodes.ASTORE; opcode == Opcodes.DSTORE || opcode == Opcodes.ASTORE;
} }
static int storeOpFromType(Type type) { static int storeOpFromType(Type type) {
@ -55,13 +55,13 @@ class Util {
static void unboxStackIfNecessary(Type type, MethodNode method) { static void unboxStackIfNecessary(Type type, MethodNode method) {
if (type == Type.INT_TYPE) method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, if (type == Type.INT_TYPE) method.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/lang/Integer", "intValue", Type.getMethodDescriptor(Type.INT_TYPE), false); "java/lang/Integer", "intValue", Type.getMethodDescriptor(Type.INT_TYPE), false);
else if (type == Type.FLOAT_TYPE) method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, else if (type == Type.FLOAT_TYPE) method.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/lang/Float", "floatValue", Type.getMethodDescriptor(Type.FLOAT_TYPE), false); "java/lang/Float", "floatValue", Type.getMethodDescriptor(Type.FLOAT_TYPE), false);
else if (type == Type.LONG_TYPE) method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, else if (type == Type.LONG_TYPE) method.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/lang/Long", "longValue", Type.getMethodDescriptor(Type.LONG_TYPE), false); "java/lang/Long", "longValue", Type.getMethodDescriptor(Type.LONG_TYPE), false);
else if (type == Type.DOUBLE_TYPE) method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, else if (type == Type.DOUBLE_TYPE) method.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/lang/Double", "doubleValue", Type.getMethodDescriptor(Type.DOUBLE_TYPE), false); "java/lang/Double", "doubleValue", Type.getMethodDescriptor(Type.DOUBLE_TYPE), false);
} }
static AbstractInsnNode intConst(int v) { static AbstractInsnNode intConst(int v) {
@ -79,6 +79,6 @@ class Util {
static MethodInsnNode boxCall(Class<?> boxType, Type primType) { static MethodInsnNode boxCall(Class<?> boxType, Type primType) {
return new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(boxType), return new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(boxType),
"valueOf", Type.getMethodDescriptor(Type.getType(boxType), primType), false); "valueOf", Type.getMethodDescriptor(Type.getType(boxType), primType), false);
} }
} }

View File

@ -3,7 +3,19 @@ package asmble.ast
import java.util.* import java.util.*
import kotlin.reflect.KClass import kotlin.reflect.KClass
/**
* All WebAssembly AST nodes as static inner classes.
*/
sealed class Node { sealed class Node {
/**
* Wasm module definition.
*
* The unit of WebAssembly code is the module. A module collects definitions
* for types, functions, tables, memories, and globals. In addition, it can
* declare imports and exports and provide initialization logic in the form
* of data and element segments or a start function.
*/
data class Module( data class Module(
val types: List<Type.Func> = emptyList(), val types: List<Type.Func> = emptyList(),
val imports: List<Import> = emptyList(), val imports: List<Import> = emptyList(),
@ -172,12 +184,15 @@ sealed class Node {
interface Const<out T : Number> : Args { val value: T } interface Const<out T : Number> : Args { val value: T }
} }
// Control flow // Control instructions [https://www.w3.org/TR/2018/WD-wasm-core-1-20180215/#control-instructions]
object Unreachable : Instr(), Args.None object Unreachable : Instr(), Args.None
object Nop : Instr(), Args.None object Nop : Instr(), Args.None
data class Block(override val type: Type.Value?) : Instr(), Args.Type data class Block(override val type: Type.Value?) : Instr(), Args.Type
data class Loop(override val type: Type.Value?) : Instr(), Args.Type data class Loop(override val type: Type.Value?) : Instr(), Args.Type
data class If(override val type: Type.Value?) : Instr(), Args.Type data class If(override val type: Type.Value?) : Instr(), Args.Type
object Else : Instr(), Args.None object Else : Instr(), Args.None
object End : Instr(), Args.None object End : Instr(), Args.None
data class Br(override val relativeDepth: Int) : Instr(), Args.RelativeDepth data class Br(override val relativeDepth: Int) : Instr(), Args.RelativeDepth
@ -188,25 +203,27 @@ sealed class Node {
) : Instr(), Args.Table ) : Instr(), Args.Table
object Return : Instr() object Return : Instr()
// Call operators
data class Call(override val index: Int) : Instr(), Args.Index data class Call(override val index: Int) : Instr(), Args.Index
data class CallIndirect( data class CallIndirect(
override val index: Int, override val index: Int,
override val reserved: Boolean override val reserved: Boolean
) : Instr(), Args.ReservedIndex ) : Instr(), Args.ReservedIndex
// Parametric operators // Parametric instructions [https://www.w3.org/TR/2018/WD-wasm-core-1-20180215/#parametric-instructions]
object Drop : Instr(), Args.None object Drop : Instr(), Args.None
object Select : Instr(), Args.None object Select : Instr(), Args.None
// Variable access // Variable instructions [https://www.w3.org/TR/2018/WD-wasm-core-1-20180215/#variable-instructions]
data class GetLocal(override val index: Int) : Instr(), Args.Index data class GetLocal(override val index: Int) : Instr(), Args.Index
data class SetLocal(override val index: Int) : Instr(), Args.Index data class SetLocal(override val index: Int) : Instr(), Args.Index
data class TeeLocal(override val index: Int) : Instr(), Args.Index data class TeeLocal(override val index: Int) : Instr(), Args.Index
data class GetGlobal(override val index: Int) : Instr(), Args.Index data class GetGlobal(override val index: Int) : Instr(), Args.Index
data class SetGlobal(override val index: Int) : Instr(), Args.Index data class SetGlobal(override val index: Int) : Instr(), Args.Index
// Memory operators // Memory instructions [https://www.w3.org/TR/2018/WD-wasm-core-1-20180215/#memory-instructions]
data class I32Load(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset data class I32Load(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class I64Load(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset data class I64Load(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
data class F32Load(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset data class F32Load(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
@ -233,7 +250,9 @@ sealed class Node {
data class MemorySize(override val reserved: Boolean) : Instr(), Args.Reserved data class MemorySize(override val reserved: Boolean) : Instr(), Args.Reserved
data class MemoryGrow(override val reserved: Boolean) : Instr(), Args.Reserved data class MemoryGrow(override val reserved: Boolean) : Instr(), Args.Reserved
// Constants // Numeric instructions [https://www.w3.org/TR/2018/WD-wasm-core-1-20180215/#numeric-instructions]
// Constants operators
data class I32Const(override val value: Int) : Instr(), Args.Const<Int> data class I32Const(override val value: Int) : Instr(), Args.Const<Int>
data class I64Const(override val value: Long) : Instr(), Args.Const<Long> data class I64Const(override val value: Long) : Instr(), Args.Const<Long>
data class F32Const(override val value: Float) : Instr(), Args.Const<Float> data class F32Const(override val value: Float) : Instr(), Args.Const<Float>

View File

@ -2,10 +2,16 @@ package asmble.ast
import asmble.io.SExprToStr import asmble.io.SExprToStr
/**
* Ast representation of wasm S-expressions (wast format).
* see [[https://webassembly.github.io/spec/core/text/index.html]]
*/
sealed class SExpr { sealed class SExpr {
data class Multi(val vals: List<SExpr> = emptyList()) : SExpr() { data class Multi(val vals: List<SExpr> = emptyList()) : SExpr() {
override fun toString() = SExprToStr.Compact.fromSExpr(this) override fun toString() = SExprToStr.Compact.fromSExpr(this)
} }
data class Symbol( data class Symbol(
val contents: String = "", val contents: String = "",
val quoted: Boolean = false, val quoted: Boolean = false,
@ -15,4 +21,5 @@ sealed class SExpr {
// This is basically the same as the deprecated java.lang.String#getBytes // This is basically the same as the deprecated java.lang.String#getBytes
fun rawContentCharsToBytes() = contents.toCharArray().map(Char::toByte) fun rawContentCharsToBytes() = contents.toCharArray().map(Char::toByte)
} }
} }

View File

@ -1,6 +1,10 @@
package asmble.ast package asmble.ast
/**
* Ast representation of wasm script.
*/
data class Script(val commands: List<Cmd>) { data class Script(val commands: List<Cmd>) {
sealed class Cmd { sealed class Cmd {
data class Module(val module: Node.Module, val name: String?): Cmd() data class Module(val module: Node.Module, val name: String?): Cmd()
data class Register(val string: String, val name: String?): Cmd() data class Register(val string: String, val name: String?): Cmd()

View File

@ -3,6 +3,9 @@ package asmble.cli
import asmble.compile.jvm.javaIdent import asmble.compile.jvm.javaIdent
import asmble.run.jvm.Module import asmble.run.jvm.Module
/**
* This class provide ''invoke'' WASM code functionality.
*/
open class Invoke : ScriptCommand<Invoke.Args>() { open class Invoke : ScriptCommand<Invoke.Args>() {
override val name = "invoke" override val name = "invoke"
@ -34,6 +37,7 @@ open class Invoke : ScriptCommand<Invoke.Args>() {
).also { bld.done() } ).also { bld.done() }
override fun run(args: Args) { override fun run(args: Args) {
// Compiles wasm to bytecode, do registrations and so on.
val ctx = prepareContext(args.scriptArgs) val ctx = prepareContext(args.scriptArgs)
// Instantiate the module // Instantiate the module
val module = val module =
@ -41,11 +45,11 @@ open class Invoke : ScriptCommand<Invoke.Args>() {
else ctx.registrations[args.module] as? Module.Instance ?: else ctx.registrations[args.module] as? Module.Instance ?:
error("Unable to find module registered as ${args.module}") error("Unable to find module registered as ${args.module}")
// Just make sure the module is instantiated here... // Just make sure the module is instantiated here...
module.instance(ctx) val instance = module.instance(ctx)
// If an export is provided, call it // If an export is provided, call it
if (args.export != "<start-func>") args.export.javaIdent.let { javaName -> if (args.export != "<start-func>") args.export.javaIdent.let { javaName ->
val method = module.cls.declaredMethods.find { it.name == javaName } ?: // Finds java method(wasm fn) in class(wasm module) by name(declared in <start-func>)
error("Unable to find export '${args.export}'") val method = module.cls.declaredMethods.find { it.name == javaName } ?: error("Unable to find export '${args.export}'")
// Map args to params // Map args to params
require(method.parameterTypes.size == args.args.size) { require(method.parameterTypes.size == args.args.size) {
"Given arg count of ${args.args.size} is invalid for $method" "Given arg count of ${args.args.size} is invalid for $method"
@ -59,11 +63,20 @@ open class Invoke : ScriptCommand<Invoke.Args>() {
else -> error("Unrecognized type for param ${index + 1}: $paramType") else -> error("Unrecognized type for param ${index + 1}: $paramType")
} }
} }
val result = method.invoke(module.instance(ctx), *params.toTypedArray()) val result = method.invoke(instance, *params.toTypedArray())
if (args.resultToStdout && method.returnType != Void.TYPE) println(result) if (args.resultToStdout && method.returnType != Void.TYPE) println(result)
} }
} }
/**
* Arguments for 'invoke' command.
*
* @param scriptArgs Common arguments for 'invoke' and 'run' ScriptCommands.
* @param module The module name to run. If it's a JVM class, it must have a no-arg constructor
* @param export The specific export function to invoke
* @param args Parameter for the export if export is present
* @param resultToStdout If true result will print to stout
*/
data class Args( data class Args(
val scriptArgs: ScriptCommand.ScriptArgs, val scriptArgs: ScriptCommand.ScriptArgs,
val module: String, val module: String,

View File

@ -5,6 +5,9 @@ import kotlin.system.exitProcess
val commands = listOf(Compile, Help, Invoke, Link, Run, Translate) val commands = listOf(Compile, Help, Invoke, Link, Run, Translate)
/**
* Entry point of command line interface.
*/
fun main(args: Array<String>) { fun main(args: Array<String>) {
if (args.isEmpty()) return println( if (args.isEmpty()) return println(
""" """
@ -28,6 +31,7 @@ fun main(args: Array<String>) {
val globals = Main.globalArgs(argBuild) val globals = Main.globalArgs(argBuild)
logger = Logger.Print(globals.logLevel) logger = Logger.Print(globals.logLevel)
command.logger = logger command.logger = logger
logger.info { "Running the command=${command.name} with args=${argBuild.args}" }
command.runWithArgs(argBuild) command.runWithArgs(argBuild)
} catch (e: Exception) { } catch (e: Exception) {
logger.error { "Error ${command?.let { "in command '${it.name}'" } ?: ""}: ${e.message}" } logger.error { "Error ${command?.let { "in command '${it.name}'" } ?: ""}: ${e.message}" }

View File

@ -1,10 +1,10 @@
package asmble.cli package asmble.cli
import asmble.ast.Script import asmble.ast.Script
import asmble.compile.jvm.javaIdent import asmble.compile.jvm.*
import asmble.run.jvm.Module import asmble.run.jvm.*
import asmble.run.jvm.ScriptContext
import java.io.File import java.io.File
import java.io.PrintWriter
import java.util.* import java.util.*
abstract class ScriptCommand<T> : Command<T>() { abstract class ScriptCommand<T> : Command<T>() {
@ -41,48 +41,97 @@ abstract class ScriptCommand<T> : Command<T>() {
desc = "The maximum number of memory pages when a module doesn't say.", desc = "The maximum number of memory pages when a module doesn't say.",
default = "5", default = "5",
lowPriority = true lowPriority = true
).toInt() ).toInt(),
enableLogger = bld.arg(
name = "enableLogger",
opt = "enableLogger",
desc = "Enables the special module the could be used for logging",
default = "false",
lowPriority = true
).toBoolean()
) )
fun prepareContext(args: ScriptArgs): ScriptContext { fun prepareContext(args: ScriptArgs): ScriptContext {
var ctx = ScriptContext( var context = ScriptContext(
packageName = "asmble.temp" + UUID.randomUUID().toString().replace("-", ""), packageName = "asmble.temp" + UUID.randomUUID().toString().replace("-", ""),
defaultMaxMemPages = args.defaultMaxMemPages defaultMaxMemPages = args.defaultMaxMemPages,
memoryBuilder = args.memoryBuilder
) )
// Compile everything // Compile everything
ctx = args.inFiles.foldIndexed(ctx) { index, ctx, inFile -> context = args.inFiles.foldIndexed(context) { index, ctx, inFile ->
try { try {
when (inFile.substringAfterLast('.')) { when (inFile.substringAfterLast('.')) {
// if input file is class file
"class" -> ctx.classLoader.addClass(File(inFile).readBytes()).let { ctx } "class" -> ctx.classLoader.addClass(File(inFile).readBytes()).let { ctx }
else -> Translate.inToAst(inFile, inFile.substringAfterLast('.')).let { inAst -> // if input file is wasm file
val (mod, name) = (inAst.commands.singleOrNull() as? Script.Cmd.Module) ?: else -> {
val translateCmd = Translate
translateCmd.logger = this.logger
translateCmd.inToAst(inFile, inFile.substringAfterLast('.')).let { inAst ->
val (mod, name) = (inAst.commands.singleOrNull() as? Script.Cmd.Module) ?:
error("Input file must only contain a single module") error("Input file must only contain a single module")
val className = name?.javaIdent?.capitalize() ?: val className = name?.javaIdent?.capitalize() ?:
"Temp" + UUID.randomUUID().toString().replace("-", "") "Temp" + UUID.randomUUID().toString().replace("-", "")
ctx.withCompiledModule(mod, className, name).let { ctx -> ctx.withCompiledModule(mod, className, name).let { ctx ->
if (name == null && index != args.inFiles.size - 1) if (name == null && index != args.inFiles.size - 1)
logger.warn { "File '$inFile' not last and has no name so will be unused" } logger.warn { "File '$inFile' not last and has no name so will be unused" }
if (name == null || args.disableAutoRegister) ctx if (name == null || args.disableAutoRegister) ctx
else ctx.runCommand(Script.Cmd.Register(name, null)) else ctx.runCommand(Script.Cmd.Register(name, null))
}
} }
} }
} }
} catch (e: Exception) { throw Exception("Failed loading $inFile - ${e.message}", e) } } catch (e: Exception) {
throw Exception("Failed loading $inFile - ${e.message}", e)
}
} }
// Do registrations // Do registrations
ctx = args.registrations.fold(ctx) { ctx, (moduleName, className) -> context = args.registrations.fold(context) { ctx, (moduleName, className) ->
ctx.withModuleRegistered(moduleName, ctx.withModuleRegistered(moduleName,
Module.Native(Class.forName(className, true, ctx.classLoader).newInstance())) Module.Native(Class.forName(className, true, ctx.classLoader).newInstance()))
} }
if (args.specTestRegister) ctx = ctx.withHarnessRegistered()
return ctx if (args.specTestRegister) context = context.withHarnessRegistered()
if (args.enableLogger) {
// add logger Wasm module for logging
context =
context.withModuleRegistered(
"logger",
Module.Native(LoggerModule(PrintWriter(System.out)))
)
}
// add env Wasm module for gas metering
context =
context.withModuleRegistered(
"env",
// TODO: currently we are using almost infinite gas limit
Module.Native(EnvModule(Long.MAX_VALUE))
)
return context
} }
/**
* Common arguments for 'invoke' and 'run' ScriptCommands.
*
* @param inFiles Files to add to classpath. Can be wasm, wast, or class file
* @param registrations Register class name to a module name
* @param disableAutoRegister If set, this will not auto-register modules with names
* @param specTestRegister If true, registers the spec test harness as 'spectest'
* @param defaultMaxMemPages The maximum number of memory pages when a module doesn't say
* @param enableLogger If set, the special logger module will be registred.
* @param memoryBuilder The builder to initialize new memory class.
*/
data class ScriptArgs( data class ScriptArgs(
val inFiles: List<String>, val inFiles: List<String>,
val registrations: List<Pair<String, String>>, val registrations: List<Pair<String, String>>,
val disableAutoRegister: Boolean, val disableAutoRegister: Boolean,
val specTestRegister: Boolean, val specTestRegister: Boolean,
val defaultMaxMemPages: Int val defaultMaxMemPages: Int,
val enableLogger: Boolean,
val memoryBuilder: MemoryBufferBuilder? = null
) )
} }

View File

@ -58,7 +58,10 @@ open class Translate : Command<Translate.Args>() {
fun inToAst(inFile: String, inFormat: String): Script { fun inToAst(inFile: String, inFormat: String): Script {
val inBytes = val inBytes =
if (inFile == "--") System.`in`.use { it.readBytes() } if (inFile == "--") System.`in`.use { it.readBytes() }
else File(inFile).let { f -> FileInputStream(f).use { it.readBytes(f.length().toIntExact()) } } else File(inFile).let { f ->
// Input file might not fit into the memory
FileInputStream(f).use { it.readBytes(f.length().toIntExact()) }
}
return when (inFormat) { return when (inFormat) {
"wast" -> StrToSExpr.parse(inBytes.toString(Charsets.UTF_8)).let { res -> "wast" -> StrToSExpr.parse(inBytes.toString(Charsets.UTF_8)).let { res ->
when (res) { when (res) {
@ -67,8 +70,10 @@ open class Translate : Command<Translate.Args>() {
} }
} }
"wasm" -> "wasm" ->
Script(listOf(Script.Cmd.Module(BinaryToAst(logger = logger).toModule( BinaryToAst(logger = this.logger).toModule(
ByteReader.InputStream(inBytes.inputStream())), null))) ByteReader.InputStream(inBytes.inputStream())).let { module ->
Script(listOf(Script.Cmd.Module(module, module.names?.moduleName)))
}
else -> error("Unknown in format '$inFormat'") else -> error("Unknown in format '$inFormat'")
} }
} }

View File

@ -13,7 +13,7 @@ import org.objectweb.asm.tree.ClassNode
open class AsmToBinary(val splitMethod: SplitMethod? = SplitMethod(Opcodes.ASM6)) { open class AsmToBinary(val splitMethod: SplitMethod? = SplitMethod(Opcodes.ASM6)) {
fun fromClassNode( fun fromClassNode(
cn: ClassNode, cn: ClassNode,
newClassWriter: () -> ClassWriter = { ClassWriter(ClassWriter.COMPUTE_FRAMES + ClassWriter.COMPUTE_MAXS) } newClassWriter: () -> ClassWriter = { ClassWriter(ClassWriter.COMPUTE_FRAMES) }
): ByteArray { ): ByteArray {
while (true) { while (true) {
val cw = newClassWriter() val cw = newClassWriter()
@ -29,6 +29,7 @@ open class AsmToBinary(val splitMethod: SplitMethod? = SplitMethod(Opcodes.ASM6)
require(cn.name == e.className) require(cn.name == e.className)
val tooLargeIndex = cn.methods.indexOfFirst { it.name == e.methodName && it.desc == e.descriptor } val tooLargeIndex = cn.methods.indexOfFirst { it.name == e.methodName && it.desc == e.descriptor }
require(tooLargeIndex >= 0) require(tooLargeIndex >= 0)
val tt = cn.methods[tooLargeIndex]
val split = splitMethod.split(cn.name, cn.methods[tooLargeIndex]) val split = splitMethod.split(cn.name, cn.methods[tooLargeIndex])
split ?: throw IllegalStateException("Failed to split", e) split ?: throw IllegalStateException("Failed to split", e)
// Change the split off method's name if there's already one // Change the split off method's name if there's already one

View File

@ -30,10 +30,11 @@ open class AstToAsm {
} }
fun addFields(ctx: ClsContext) { fun addFields(ctx: ClsContext) {
// Mem field if present // Mem field if present, adds `private final field memory` to
if (ctx.hasMemory) if (ctx.hasMemory)
ctx.cls.fields.add(FieldNode(Opcodes.ACC_PRIVATE + Opcodes.ACC_FINAL, "memory", ctx.cls.fields.add(
ctx.mem.memType.asmDesc, null, null)) FieldNode((Opcodes.ACC_PRIVATE + Opcodes.ACC_FINAL), "memory", ctx.mem.memType.asmDesc, null, null)
)
// Table field if present... // Table field if present...
// Private final for now, but likely won't be final in future versions supporting // Private final for now, but likely won't be final in future versions supporting
// mutable tables, may be not even a table but a list (and final) // mutable tables, may be not even a table but a list (and final)

View File

@ -4,32 +4,30 @@ import asmble.ast.Node
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.*
import java.nio.Buffer
import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.KFunction import kotlin.reflect.KFunction
open class ByteBufferMem(val direct: Boolean = true) : Mem { open class ByteBufferMem : Mem {
override val memType = ByteBuffer::class.ref override val memType: TypeRef = MemoryBuffer::class.ref
override fun limitAndCapacity(instance: Any) = override fun limitAndCapacity(instance: Any): Pair<Int, Int> =
if (instance !is ByteBuffer) error("Unrecognized memory instance: $instance") if (instance !is MemoryBuffer) error("Unrecognized memory instance: $instance")
else instance.limit() to instance.capacity() else instance.limit() to instance.capacity()
override fun create(func: Func) = func.popExpecting(Int::class.ref).addInsns( override fun create(func: Func) = func.popExpecting(Int::class.ref).addInsns(
(if (direct) ByteBuffer::allocateDirect else ByteBuffer::allocate).invokeStatic() (MemoryBuffer::init).invokeStatic()
).push(memType) ).push(memType)
override fun init(func: Func, initial: Int) = func.popExpecting(memType).addInsns( override fun init(func: Func, initial: Int) = func.popExpecting(memType).addInsns(
// Set the limit to initial // Set the limit to initial
(initial * Mem.PAGE_SIZE).const, (initial * Mem.PAGE_SIZE).const,
forceFnType<ByteBuffer.(Int) -> Buffer>(ByteBuffer::limit).invokeVirtual(), forceFnType<MemoryBuffer.(Int) -> MemoryBuffer>(MemoryBuffer::limit).invokeVirtual(),
TypeInsnNode(Opcodes.CHECKCAST, ByteBuffer::class.ref.asmName), TypeInsnNode(Opcodes.CHECKCAST, memType.asmName),
// Set it to use little endian // Set it to use little endian
ByteOrder::LITTLE_ENDIAN.getStatic(), ByteOrder::LITTLE_ENDIAN.getStatic(),
forceFnType<ByteBuffer.(ByteOrder) -> ByteBuffer>(ByteBuffer::order).invokeVirtual() forceFnType<MemoryBuffer.(ByteOrder) -> MemoryBuffer>(MemoryBuffer::order).invokeVirtual()
).push(ByteBuffer::class.ref) ).push(memType)
override fun data(func: Func, bytes: ByteArray, buildOffset: (Func) -> Func) = override fun data(func: Func, bytes: ByteArray, buildOffset: (Func) -> Func) =
// Sadly there is no absolute bulk put, so we need to fake one. Ref: // Sadly there is no absolute bulk put, so we need to fake one. Ref:
@ -42,10 +40,10 @@ open class ByteBufferMem(val direct: Boolean = true) : Mem {
// where we could call put directly, but it too is negligible for now. // where we could call put directly, but it too is negligible for now.
// Note, with this approach, the mem not be left on the stack for future data() calls which is fine. // Note, with this approach, the mem not be left on the stack for future data() calls which is fine.
func.popExpecting(memType). func.popExpecting(memType).
addInsns(ByteBuffer::duplicate.invokeVirtual()). addInsns(MemoryBuffer::duplicate.invokeVirtual()).
let(buildOffset).popExpecting(Int::class.ref). let(buildOffset).popExpecting(Int::class.ref).
addInsns( addInsns(
forceFnType<ByteBuffer.(Int) -> Buffer>(ByteBuffer::position).invokeVirtual(), forceFnType<MemoryBuffer.(Int) -> MemoryBuffer>(MemoryBuffer::position).invokeVirtual(),
TypeInsnNode(Opcodes.CHECKCAST, memType.asmName) TypeInsnNode(Opcodes.CHECKCAST, memType.asmName)
).addInsns( ).addInsns(
// We're going to do this as an LDC string in ISO-8859 and read it back at runtime. However, // We're going to do this as an LDC string in ISO-8859 and read it back at runtime. However,
@ -61,7 +59,7 @@ open class ByteBufferMem(val direct: Boolean = true) : Mem {
"getBytes", "(Ljava/lang/String;)[B", false), "getBytes", "(Ljava/lang/String;)[B", false),
0.const, 0.const,
bytes.size.const, bytes.size.const,
forceFnType<ByteBuffer.(ByteArray, Int, Int) -> ByteBuffer>(ByteBuffer::put).invokeVirtual() forceFnType<MemoryBuffer.(ByteArray, Int, Int) -> MemoryBuffer>(MemoryBuffer::put).invokeVirtual()
) )
}.toList() }.toList()
).addInsns( ).addInsns(
@ -69,7 +67,7 @@ open class ByteBufferMem(val direct: Boolean = true) : Mem {
) )
override fun currentMemory(ctx: FuncContext, func: Func) = func.popExpecting(memType).addInsns( override fun currentMemory(ctx: FuncContext, func: Func) = func.popExpecting(memType).addInsns(
forceFnType<ByteBuffer.() -> Int>(ByteBuffer::limit).invokeVirtual(), forceFnType<MemoryBuffer.() -> Int>(MemoryBuffer::limit).invokeVirtual(),
Mem.PAGE_SIZE.const, Mem.PAGE_SIZE.const,
InsnNode(Opcodes.IDIV) InsnNode(Opcodes.IDIV)
).push(Int::class.ref) ).push(Int::class.ref)
@ -86,10 +84,10 @@ open class ByteBufferMem(val direct: Boolean = true) : Mem {
val okLim = LabelNode() val okLim = LabelNode()
val node = MethodNode( val node = MethodNode(
Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC, Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC,
"\$\$growMemory", "(Ljava/nio/ByteBuffer;I)I", null, null "\$\$growMemory", "(Lasmble/compile/jvm/MemoryBuffer;I)I", null, null
).addInsns( ).addInsns(
VarInsnNode(Opcodes.ALOAD, 0), // [mem] VarInsnNode(Opcodes.ALOAD, 0), // [mem]
forceFnType<ByteBuffer.() -> Int>(ByteBuffer::limit).invokeVirtual(), // [lim] forceFnType<MemoryBuffer.() -> Int>(MemoryBuffer::limit).invokeVirtual(), // [lim]
InsnNode(Opcodes.DUP), // [lim, lim] InsnNode(Opcodes.DUP), // [lim, lim]
VarInsnNode(Opcodes.ALOAD, 0), // [lim, lim, mem] VarInsnNode(Opcodes.ALOAD, 0), // [lim, lim, mem]
InsnNode(Opcodes.SWAP), // [lim, mem, lim] InsnNode(Opcodes.SWAP), // [lim, mem, lim]
@ -102,7 +100,7 @@ open class ByteBufferMem(val direct: Boolean = true) : Mem {
InsnNode(Opcodes.LADD), // [lim, mem, newlimL] InsnNode(Opcodes.LADD), // [lim, mem, newlimL]
InsnNode(Opcodes.DUP2), // [lim, mem, newlimL, newlimL] InsnNode(Opcodes.DUP2), // [lim, mem, newlimL, newlimL]
VarInsnNode(Opcodes.ALOAD, 0), // [lim, mem, newlimL, newlimL, mem] VarInsnNode(Opcodes.ALOAD, 0), // [lim, mem, newlimL, newlimL, mem]
ByteBuffer::capacity.invokeVirtual(), // [lim, mem, newlimL, newlimL, cap] MemoryBuffer::capacity.invokeVirtual(), // [lim, mem, newlimL, newlimL, cap]
InsnNode(Opcodes.I2L), // [lim, mem, newlimL, newlimL, capL] InsnNode(Opcodes.I2L), // [lim, mem, newlimL, newlimL, capL]
InsnNode(Opcodes.LCMP), // [lim, mem, newlimL, cmpres] InsnNode(Opcodes.LCMP), // [lim, mem, newlimL, cmpres]
JumpInsnNode(Opcodes.IFLE, okLim), // [lim, mem, newlimL] JumpInsnNode(Opcodes.IFLE, okLim), // [lim, mem, newlimL]
@ -111,7 +109,7 @@ open class ByteBufferMem(val direct: Boolean = true) : Mem {
InsnNode(Opcodes.IRETURN), InsnNode(Opcodes.IRETURN),
okLim, // [lim, mem, newlimL] okLim, // [lim, mem, newlimL]
InsnNode(Opcodes.L2I), // [lim, mem, newlim] InsnNode(Opcodes.L2I), // [lim, mem, newlim]
forceFnType<ByteBuffer.(Int) -> Buffer>(ByteBuffer::limit).invokeVirtual(), // [lim, mem] forceFnType<MemoryBuffer.(Int) -> MemoryBuffer>(MemoryBuffer::limit).invokeVirtual(), // [lim, mem]
InsnNode(Opcodes.POP), // [lim] InsnNode(Opcodes.POP), // [lim]
Mem.PAGE_SIZE.const, // [lim, pagesize] Mem.PAGE_SIZE.const, // [lim, pagesize]
InsnNode(Opcodes.IDIV), // [limpages] InsnNode(Opcodes.IDIV), // [limpages]
@ -125,7 +123,7 @@ open class ByteBufferMem(val direct: Boolean = true) : Mem {
// Ug, some tests expect this to be a runtime failure so we feature flagged it // Ug, some tests expect this to be a runtime failure so we feature flagged it
if (ctx.cls.eagerFailLargeMemOffset) if (ctx.cls.eagerFailLargeMemOffset)
require(insn.offset <= Int.MAX_VALUE, { "Offsets > ${Int.MAX_VALUE} unsupported" }).let { this } require(insn.offset <= Int.MAX_VALUE, { "Offsets > ${Int.MAX_VALUE} unsupported" }).let { this }
fun Func.load(fn: ByteBuffer.(Int) -> Any, retClass: KClass<*>) = fun Func.load(fn: MemoryBuffer.(Int) -> Any, retClass: KClass<*>) =
this.popExpecting(Int::class.ref).let { func -> this.popExpecting(Int::class.ref).let { func ->
// No offset means we'll access it directly // No offset means we'll access it directly
(if (insn.offset == 0L) func else { (if (insn.offset == 0L) func else {
@ -141,9 +139,9 @@ open class ByteBufferMem(val direct: Boolean = true) : Mem {
} }
}).popExpecting(memType).addInsns((fn as KFunction<*>).invokeVirtual()) }).popExpecting(memType).addInsns((fn as KFunction<*>).invokeVirtual())
}.push(retClass.ref) }.push(retClass.ref)
fun Func.loadI32(fn: ByteBuffer.(Int) -> Any) = fun Func.loadI32(fn: MemoryBuffer.(Int) -> Any) =
this.load(fn, Int::class) this.load(fn, Int::class)
fun Func.loadI64(fn: ByteBuffer.(Int) -> Any) = fun Func.loadI64(fn: MemoryBuffer.(Int) -> Any) =
this.load(fn, Long::class) this.load(fn, Long::class)
/* Ug: https://youtrack.jetbrains.com/issue/KT-17064 /* Ug: https://youtrack.jetbrains.com/issue/KT-17064
fun Func.toUnsigned(fn: KFunction<*>) = fun Func.toUnsigned(fn: KFunction<*>) =
@ -163,33 +161,33 @@ open class ByteBufferMem(val direct: Boolean = true) : Mem {
// Had to move this in here instead of as first expr because of https://youtrack.jetbrains.com/issue/KT-8689 // Had to move this in here instead of as first expr because of https://youtrack.jetbrains.com/issue/KT-8689
return when (insn) { return when (insn) {
is Node.Instr.I32Load -> is Node.Instr.I32Load ->
func.loadI32(ByteBuffer::getInt) func.loadI32(MemoryBuffer::getInt)
is Node.Instr.I64Load -> is Node.Instr.I64Load ->
func.loadI64(ByteBuffer::getLong) func.loadI64(MemoryBuffer::getLong)
is Node.Instr.F32Load -> is Node.Instr.F32Load ->
func.load(ByteBuffer::getFloat, Float::class) func.load(MemoryBuffer::getFloat, Float::class)
is Node.Instr.F64Load -> is Node.Instr.F64Load ->
func.load(ByteBuffer::getDouble, Double::class) func.load(MemoryBuffer::getDouble, Double::class)
is Node.Instr.I32Load8S -> is Node.Instr.I32Load8S ->
func.loadI32(ByteBuffer::get) func.loadI32(MemoryBuffer::get)
is Node.Instr.I32Load8U -> is Node.Instr.I32Load8U ->
func.loadI32(ByteBuffer::get).toUnsigned32(java.lang.Byte::class, "toUnsignedInt", Byte::class) func.loadI32(MemoryBuffer::get).toUnsigned32(java.lang.Byte::class, "toUnsignedInt", Byte::class)
is Node.Instr.I32Load16S -> is Node.Instr.I32Load16S ->
func.loadI32(ByteBuffer::getShort) func.loadI32(MemoryBuffer::getShort)
is Node.Instr.I32Load16U -> is Node.Instr.I32Load16U ->
func.loadI32(ByteBuffer::getShort).toUnsigned32(java.lang.Short::class, "toUnsignedInt", Short::class) func.loadI32(MemoryBuffer::getShort).toUnsigned32(java.lang.Short::class, "toUnsignedInt", Short::class)
is Node.Instr.I64Load8S -> is Node.Instr.I64Load8S ->
func.loadI32(ByteBuffer::get).i32ToI64() func.loadI32(MemoryBuffer::get).i32ToI64()
is Node.Instr.I64Load8U -> is Node.Instr.I64Load8U ->
func.loadI32(ByteBuffer::get).toUnsigned64(java.lang.Byte::class, "toUnsignedLong", Byte::class) func.loadI32(MemoryBuffer::get).toUnsigned64(java.lang.Byte::class, "toUnsignedLong", Byte::class)
is Node.Instr.I64Load16S -> is Node.Instr.I64Load16S ->
func.loadI32(ByteBuffer::getShort).i32ToI64() func.loadI32(MemoryBuffer::getShort).i32ToI64()
is Node.Instr.I64Load16U -> is Node.Instr.I64Load16U ->
func.loadI32(ByteBuffer::getShort).toUnsigned64(java.lang.Short::class, "toUnsignedLong", Short::class) func.loadI32(MemoryBuffer::getShort).toUnsigned64(java.lang.Short::class, "toUnsignedLong", Short::class)
is Node.Instr.I64Load32S -> is Node.Instr.I64Load32S ->
func.loadI32(ByteBuffer::getInt).i32ToI64() func.loadI32(MemoryBuffer::getInt).i32ToI64()
is Node.Instr.I64Load32U -> is Node.Instr.I64Load32U ->
func.loadI32(ByteBuffer::getInt).toUnsigned64(java.lang.Integer::class, "toUnsignedLong", Int::class) func.loadI32(MemoryBuffer::getInt).toUnsigned64(java.lang.Integer::class, "toUnsignedLong", Int::class)
else -> throw IllegalArgumentException("Unknown load op $insn") else -> throw IllegalArgumentException("Unknown load op $insn")
} }
} }
@ -224,12 +222,12 @@ open class ByteBufferMem(val direct: Boolean = true) : Mem {
popExpecting(Int::class.ref). popExpecting(Int::class.ref).
popExpecting(memType). popExpecting(memType).
addInsns(fn). addInsns(fn).
push(ByteBuffer::class.ref) push(memType)
} }
// Ug, I hate these as strings but can't introspect Kotlin overloads // Ug, I hate these as strings but can't introspect Kotlin overloads
fun bufStoreFunc(name: String, valType: KClass<*>) = fun bufStoreFunc(name: String, valType: KClass<*>) =
MethodInsnNode(Opcodes.INVOKEVIRTUAL, ByteBuffer::class.ref.asmName, name, MethodInsnNode(Opcodes.INVOKEVIRTUAL, memType.asmName, name,
ByteBuffer::class.ref.asMethodRetDesc(Int::class.ref, valType.ref), false) memType.asMethodRetDesc(Int::class.ref, valType.ref), false)
fun Func.changeI64ToI32() = fun Func.changeI64ToI32() =
this.popExpecting(Long::class.ref).push(Int::class.ref) this.popExpecting(Long::class.ref).push(Int::class.ref)
when (insn) { when (insn) {

View File

@ -4,6 +4,24 @@ import asmble.ast.Node
import org.objectweb.asm.Opcodes import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.* import org.objectweb.asm.tree.*
/**
* Jvm representation of a function.
*
* @param name Name of the fn.
* @param params List of parameters of the fn.
* @param ret Type of the fn returner value.
* @param access The value of the access_flags item is a mask of flags used to
* denote access permissions to and properties of this class or
* interface [https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1-200-E.1].
* @param insns List of nodes that represents a bytecode instruction.
* @param stack A stack of operand types. Mirror of the operand stack(jvm stack)
* where types of operands instead operands.
* @param blockStack List of blocks of code
* @param ifStack Contains index of [org.objectweb.asm.tree.JumpInsnNode] that
* has a null label initially
* @param lastStackIsMemLeftover If there is the memory on the stack and we need it
* in the future, we mark it as leftover and reuse
*/
data class Func( data class Func(
val name: String, val name: String,
val params: List<TypeRef> = emptyList(), val params: List<TypeRef> = emptyList(),
@ -12,7 +30,6 @@ data class Func(
val insns: List<AbstractInsnNode> = emptyList(), val insns: List<AbstractInsnNode> = emptyList(),
val stack: List<TypeRef> = emptyList(), val stack: List<TypeRef> = emptyList(),
val blockStack: List<Block> = emptyList(), val blockStack: List<Block> = emptyList(),
// Contains index of JumpInsnNode that has a null label initially
val ifStack: List<Int> = emptyList(), val ifStack: List<Int> = emptyList(),
val lastStackIsMemLeftover: Boolean = false val lastStackIsMemLeftover: Boolean = false
) { ) {
@ -110,10 +127,11 @@ data class Func(
} }
} }
fun pushBlock(insn: Node.Instr, labelType: Node.Type.Value?, endType: Node.Type.Value?) = /** Creates new block with specified instruction and pushes it into the blockStack.*/
fun pushBlock(insn: Node.Instr, labelType: Node.Type.Value?, endType: Node.Type.Value?): Func =
pushBlock(insn, listOfNotNull(labelType?.typeRef), listOfNotNull(endType?.typeRef)) pushBlock(insn, listOfNotNull(labelType?.typeRef), listOfNotNull(endType?.typeRef))
fun pushBlock(insn: Node.Instr, labelTypes: List<TypeRef>, endTypes: List<TypeRef>) = fun pushBlock(insn: Node.Instr, labelTypes: List<TypeRef>, endTypes: List<TypeRef>): Func =
copy(blockStack = blockStack + Block(insn, insns.size, stack, labelTypes, endTypes)) copy(blockStack = blockStack + Block(insn, insns.size, stack, labelTypes, endTypes))
fun popBlock() = copy(blockStack = blockStack.dropLast(1)) to blockStack.last() fun popBlock() = copy(blockStack = blockStack.dropLast(1)) to blockStack.last()
@ -127,6 +145,22 @@ data class Func(
fun popIf() = copy(ifStack = ifStack.dropLast(1)) to peekIf() fun popIf() = copy(ifStack = ifStack.dropLast(1)) to peekIf()
/**
* Representation of code block.
*
* Blocks are composed of matched pairs of `block ... end` instructions, loops
* with matched pairs of `loop ... end` instructions, and ifs with either
* `if ... end` or if ... else ... end sequences. For each of these constructs
* the instructions in the ellipsis are said to be enclosed in the construct.
*
* @param isns Start instruction of this block, might be a 'Block', 'Loop'
* or 'If'
* @param startIndex Index of start instruction of this block in list of all
* instructions
* @param origStack Current block stack of operand types.
* @param labelTypes A type of label for this block
* @param endTypes A type of block return value
*/
class Block( class Block(
val insn: Node.Instr, val insn: Node.Instr,
val startIndex: Int, val startIndex: Int,

View File

@ -14,23 +14,31 @@ import java.lang.invoke.MethodHandle
// TODO: modularize // TODO: modularize
open class FuncBuilder { open class FuncBuilder {
fun fromFunc(ctx: ClsContext, f: Node.Func, index: Int): Func {
/**
* Converts wasm AST [asmble.ast.Node.Func] to Jvm bytecode representation [asmble.compile.jvm.Func].
*
* @param ctx A Global context for converting.
* @param fn AST of wasm fn.
* @param index Fn index, used for generating fn name
*/
fun fromFunc(ctx: ClsContext, fn: Node.Func, index: Int): Func {
ctx.debug { "Building function ${ctx.funcName(index)}" } ctx.debug { "Building function ${ctx.funcName(index)}" }
ctx.trace { "Function ast:\n${SExprToStr.fromSExpr(AstToSExpr.fromFunc(f))}" } ctx.trace { "Function ast:\n${SExprToStr.fromSExpr(AstToSExpr.fromFunc(fn))}" }
var func = Func( var func = Func(
access = Opcodes.ACC_PRIVATE, access = Opcodes.ACC_PRIVATE,
name = ctx.funcName(index), name = ctx.funcName(index),
params = f.type.params.map(Node.Type.Value::typeRef), params = fn.type.params.map(Node.Type.Value::typeRef),
ret = f.type.ret?.let(Node.Type.Value::typeRef) ?: Void::class.ref ret = fn.type.ret?.let(Node.Type.Value::typeRef) ?: Void::class.ref
) )
// Rework the instructions // Rework the instructions
val reworkedInsns = ctx.reworker.rework(ctx, f) val reworkedInsns = ctx.reworker.rework(ctx, fn)
// Start the implicit block // Start the implicit block
func = func.pushBlock(Node.Instr.Block(f.type.ret), f.type.ret, f.type.ret) func = func.pushBlock(Node.Instr.Block(fn.type.ret), fn.type.ret, fn.type.ret)
// Create the context // Create the context
val funcCtx = FuncContext( val funcCtx = FuncContext(
cls = ctx, cls = ctx,
node = f, node = fn,
insns = reworkedInsns, insns = reworkedInsns,
memIsLocalVar = memIsLocalVar =
ctx.reworker.nonAdjacentMemAccesses(reworkedInsns) >= ctx.nonAdjacentMemAccessesRequiringLocalVar ctx.reworker.nonAdjacentMemAccesses(reworkedInsns) >= ctx.nonAdjacentMemAccessesRequiringLocalVar
@ -46,9 +54,9 @@ open class FuncBuilder {
// Add all instructions // Add all instructions
ctx.debug { "Applying insns for function ${ctx.funcName(index)}" } ctx.debug { "Applying insns for function ${ctx.funcName(index)}" }
// All functions have an implicit block // All functions have an implicit block
func = funcCtx.insns.foldIndexed(func) { index, func, insn -> func = funcCtx.insns.foldIndexed(func) { idx, f, insn ->
ctx.debug { "Applying insn $insn" } ctx.debug { "Applying insn $insn" }
val ret = applyInsn(funcCtx, func, insn, index) val ret = applyInsn(funcCtx, f, insn, idx)
ctx.trace { "Resulting stack: ${ret.stack}"} ctx.trace { "Resulting stack: ${ret.stack}"}
ret ret
} }
@ -56,11 +64,11 @@ open class FuncBuilder {
// End the implicit block // End the implicit block
val implicitBlock = func.currentBlock val implicitBlock = func.currentBlock
func = applyEnd(funcCtx, func) func = applyEnd(funcCtx, func)
f.type.ret?.typeRef?.also { func = func.popExpecting(it, implicitBlock) } fn.type.ret?.typeRef?.also { func = func.popExpecting(it, implicitBlock) }
// If the last instruction does not terminate, add the expected return // If the last instruction does not terminate, add the expected return
if (func.insns.isEmpty() || !func.insns.last().isTerminating) { if (func.insns.isEmpty() || !func.insns.last().isTerminating) {
func = func.addInsns(InsnNode(when (f.type.ret) { func = func.addInsns(InsnNode(when (fn.type.ret) {
null -> Opcodes.RETURN null -> Opcodes.RETURN
Node.Type.Value.I32 -> Opcodes.IRETURN Node.Type.Value.I32 -> Opcodes.IRETURN
Node.Type.Value.I64 -> Opcodes.LRETURN Node.Type.Value.I64 -> Opcodes.LRETURN
@ -72,8 +80,10 @@ open class FuncBuilder {
} }
fun applyInsn(ctx: FuncContext, fn: Func, i: Insn, index: Int) = when (i) { fun applyInsn(ctx: FuncContext, fn: Func, i: Insn, index: Int) = when (i) {
is Insn.Node -> is Insn.Node ->
applyNodeInsn(ctx, fn, i.insn, index) applyNodeInsn(ctx, fn, i.insn, index)
is Insn.ImportFuncRefNeededOnStack -> is Insn.ImportFuncRefNeededOnStack ->
// Func refs are method handle fields // Func refs are method handle fields
fn.addInsns( fn.addInsns(
@ -81,6 +91,7 @@ open class FuncBuilder {
FieldInsnNode(Opcodes.GETFIELD, ctx.cls.thisRef.asmName, FieldInsnNode(Opcodes.GETFIELD, ctx.cls.thisRef.asmName,
ctx.cls.funcName(i.index), MethodHandle::class.ref.asmDesc) ctx.cls.funcName(i.index), MethodHandle::class.ref.asmDesc)
).push(MethodHandle::class.ref) ).push(MethodHandle::class.ref)
is Insn.ImportGlobalSetRefNeededOnStack -> is Insn.ImportGlobalSetRefNeededOnStack ->
// Import setters are method handle fields // Import setters are method handle fields
fn.addInsns( fn.addInsns(
@ -88,13 +99,17 @@ open class FuncBuilder {
FieldInsnNode(Opcodes.GETFIELD, ctx.cls.thisRef.asmName, FieldInsnNode(Opcodes.GETFIELD, ctx.cls.thisRef.asmName,
ctx.cls.importGlobalSetterFieldName(i.index), MethodHandle::class.ref.asmDesc) ctx.cls.importGlobalSetterFieldName(i.index), MethodHandle::class.ref.asmDesc)
).push(MethodHandle::class.ref) ).push(MethodHandle::class.ref)
is Insn.ThisNeededOnStack -> is Insn.ThisNeededOnStack ->
// load a reference onto the stack from a local variable
fn.addInsns(VarInsnNode(Opcodes.ALOAD, 0)).push(ctx.cls.thisRef) fn.addInsns(VarInsnNode(Opcodes.ALOAD, 0)).push(ctx.cls.thisRef)
is Insn.MemNeededOnStack -> is Insn.MemNeededOnStack ->
putMemoryOnStack(ctx, fn) putMemoryOnStack(ctx, fn)
} }
fun applyNodeInsn(ctx: FuncContext, fn: Func, i: Node.Instr, index: Int) = when (i) { fun applyNodeInsn(ctx: FuncContext, fn: Func, i: Node.Instr, index: Int) = when (i) {
is Node.Instr.Unreachable -> is Node.Instr.Unreachable ->
fn.addInsns(UnsupportedOperationException::class.athrow("Unreachable")).markUnreachable() fn.addInsns(UnsupportedOperationException::class.athrow("Unreachable")).markUnreachable()
is Node.Instr.Nop -> is Node.Instr.Nop ->
@ -127,18 +142,16 @@ open class FuncBuilder {
fn.pop().let { (fn, popped) -> fn.pop().let { (fn, popped) ->
fn.addInsns(InsnNode(if (popped.stackSize == 2) Opcodes.POP2 else Opcodes.POP)) fn.addInsns(InsnNode(if (popped.stackSize == 2) Opcodes.POP2 else Opcodes.POP))
} }
is Node.Instr.Select -> is Node.Instr.Select -> applySelectInsn(ctx, fn)
applySelectInsn(ctx, fn)
is Node.Instr.GetLocal -> // Variable access
applyGetLocal(ctx, fn, i.index) is Node.Instr.GetLocal -> applyGetLocal(ctx, fn, i.index)
is Node.Instr.SetLocal -> is Node.Instr.SetLocal -> applySetLocal(ctx, fn, i.index)
applySetLocal(ctx, fn, i.index) is Node.Instr.TeeLocal -> applyTeeLocal(ctx, fn, i.index)
is Node.Instr.TeeLocal -> is Node.Instr.GetGlobal -> applyGetGlobal(ctx, fn, i.index)
applyTeeLocal(ctx, fn, i.index) is Node.Instr.SetGlobal -> applySetGlobal(ctx, fn, i.index)
is Node.Instr.GetGlobal ->
applyGetGlobal(ctx, fn, i.index) // Memory operators
is Node.Instr.SetGlobal ->
applySetGlobal(ctx, fn, i.index)
is Node.Instr.I32Load, is Node.Instr.I64Load, is Node.Instr.F32Load, is Node.Instr.F64Load, is Node.Instr.I32Load, is Node.Instr.I64Load, is Node.Instr.F32Load, is Node.Instr.F64Load,
is Node.Instr.I32Load8S, is Node.Instr.I32Load8U, is Node.Instr.I32Load16U, is Node.Instr.I32Load16S, is Node.Instr.I32Load8S, is Node.Instr.I32Load8U, is Node.Instr.I32Load16U, is Node.Instr.I32Load16S,
is Node.Instr.I64Load8S, is Node.Instr.I64Load8U, is Node.Instr.I64Load16U, is Node.Instr.I64Load16S, is Node.Instr.I64Load8S, is Node.Instr.I64Load8U, is Node.Instr.I64Load16U, is Node.Instr.I64Load16S,
@ -461,18 +474,18 @@ open class FuncBuilder {
fun popUntilStackSize( fun popUntilStackSize(
ctx: FuncContext, ctx: FuncContext,
fn: Func, func: Func,
block: Func.Block, block: Func.Block,
untilStackSize: Int, untilStackSize: Int,
keepLast: Boolean keepLast: Boolean
): Func { ): Func {
ctx.debug { "For block ${block.insn}, popping until stack size $untilStackSize, keeping last? $keepLast" } ctx.debug { "For block ${block.insn}, popping until stack size $untilStackSize, keeping last? $keepLast" }
// Just get the latest, don't actually pop... // Just get the latest, don't actually pop...
val type = if (keepLast) fn.pop().second else null val type = if (keepLast) func.pop().second else null
return (0 until Math.max(0, fn.stack.size - untilStackSize)).fold(fn) { fn, _ -> return (0 until Math.max(0, func.stack.size - untilStackSize)).fold(func) { fn, _ ->
// Essentially swap and pop if they want to keep the latest // Essentially swap and pop if they want to keep the latest
(if (type != null && fn.stack.size > 1) fn.stackSwap(block) else fn).let { fn -> (if (type != null && fn.stack.size > 1) fn.stackSwap(block) else fn).let { f ->
fn.pop(block).let { (fn, poppedType) -> f.pop(block).let { (fn, poppedType) ->
fn.addInsns(InsnNode(if (poppedType.stackSize == 2) Opcodes.POP2 else Opcodes.POP)) fn.addInsns(InsnNode(if (poppedType.stackSize == 2) Opcodes.POP2 else Opcodes.POP))
} }
} }
@ -1076,10 +1089,12 @@ open class FuncBuilder {
putMemoryOnStack(ctx, fn).let { fn -> ctx.cls.mem.currentMemory(ctx, fn) } putMemoryOnStack(ctx, fn).let { fn -> ctx.cls.mem.currentMemory(ctx, fn) }
} }
/**
* Store is a special case where the memory ref is already pre-injected on
* the stack before this call. But it can have a memory leftover on the stack
* so we pop it if we need to
*/
fun applyStoreOp(ctx: FuncContext, fn: Func, insn: Node.Instr.Args.AlignOffset, insnIndex: Int) = fun applyStoreOp(ctx: FuncContext, fn: Func, insn: Node.Instr.Args.AlignOffset, insnIndex: Int) =
// Store is a special case where the memory ref is already pre-injected on
// the stack before this call. But it can have a memory leftover on the stack
// so we pop it if we need to
ctx.cls.assertHasMemory().let { ctx.cls.assertHasMemory().let {
ctx.cls.mem.storeOp(ctx, fn, insn).let { fn -> ctx.cls.mem.storeOp(ctx, fn, insn).let { fn ->
// As a special case, if this leaves the mem on the stack // As a special case, if this leaves the mem on the stack
@ -1268,11 +1283,11 @@ open class FuncBuilder {
} }
} }
fun applyReturnInsn(ctx: FuncContext, fn: Func): Func { fun applyReturnInsn(ctx: FuncContext, func: Func): Func {
// If the current stakc is unreachable, we consider that our block since it // If the current stack is unreachable, we consider that our block since it
// will pop properly. // will pop properly.
val block = if (fn.currentBlock.unreachable) fn.currentBlock else fn.blockStack.first() val block = if (func.currentBlock.unreachable) func.currentBlock else func.blockStack.first()
popForBlockEscape(ctx, fn, block).let { fn -> popForBlockEscape(ctx, func, block).let { fn ->
return when (ctx.node.type.ret) { return when (ctx.node.type.ret) {
null -> null ->
fn.addInsns(InsnNode(Opcodes.RETURN)) fn.addInsns(InsnNode(Opcodes.RETURN))
@ -1284,9 +1299,9 @@ open class FuncBuilder {
fn.popExpecting(Float::class.ref, block).addInsns(InsnNode(Opcodes.FRETURN)) fn.popExpecting(Float::class.ref, block).addInsns(InsnNode(Opcodes.FRETURN))
Node.Type.Value.F64 -> Node.Type.Value.F64 ->
fn.popExpecting(Double::class.ref, block).addInsns(InsnNode(Opcodes.DRETURN)) fn.popExpecting(Double::class.ref, block).addInsns(InsnNode(Opcodes.DRETURN))
}.let { fn -> }.let { it ->
if (fn.stack.isNotEmpty()) throw CompileErr.UnusedStackOnReturn(fn.stack) if (it.stack.isNotEmpty()) throw CompileErr.UnusedStackOnReturn(it.stack)
fn.markUnreachable() it.markUnreachable()
} }
} }
} }

View File

@ -3,6 +3,15 @@ package asmble.compile.jvm
import asmble.ast.Node import asmble.ast.Node
import asmble.util.Logger import asmble.util.Logger
/**
* Jvm context of execution a function.
*
* @param cls Class execution context
* @param node Ast of this function
* @param insns A list of instructions
* @param memIsLocalVar If true then function use only local variables and don't load
* and store from memory.
*/
data class FuncContext( data class FuncContext(
val cls: ClsContext, val cls: ClsContext,
val node: Node.Func, val node: Node.Func,

View File

@ -2,6 +2,9 @@ package asmble.compile.jvm
import asmble.ast.Node import asmble.ast.Node
/**
* Does some special manipulations with instruction.
*/
open class InsnReworker { open class InsnReworker {
fun rework(ctx: ClsContext, func: Node.Func): List<Insn> { fun rework(ctx: ClsContext, func: Node.Func): List<Insn> {
@ -29,6 +32,7 @@ open class InsnReworker {
// Note, while walking backwards up the insns to find set/tee, we do skip entire // Note, while walking backwards up the insns to find set/tee, we do skip entire
// blocks/loops/if+else combined with "end" // blocks/loops/if+else combined with "end"
var neededEagerLocalIndices = emptySet<Int>() var neededEagerLocalIndices = emptySet<Int>()
fun addEagerSetIfNeeded(getInsnIndex: Int, localIndex: Int) { fun addEagerSetIfNeeded(getInsnIndex: Int, localIndex: Int) {
// Within the param range? nothing needed // Within the param range? nothing needed
if (localIndex < func.type.params.size) return if (localIndex < func.type.params.size) return
@ -71,6 +75,7 @@ open class InsnReworker {
(insn is Insn.Node && insn.insn !is Node.Instr.SetLocal && insn.insn !is Node.Instr.TeeLocal) (insn is Insn.Node && insn.insn !is Node.Instr.SetLocal && insn.insn !is Node.Instr.TeeLocal)
if (needsEagerInit) neededEagerLocalIndices += localIndex if (needsEagerInit) neededEagerLocalIndices += localIndex
} }
insns.forEachIndexed { index, insn -> insns.forEachIndexed { index, insn ->
if (insn is Insn.Node && insn.insn is Node.Instr.GetLocal) addEagerSetIfNeeded(index, insn.insn.index) if (insn is Insn.Node && insn.insn is Node.Instr.GetLocal) addEagerSetIfNeeded(index, insn.insn.index)
} }
@ -86,11 +91,18 @@ open class InsnReworker {
} + insns } + insns
} }
/**
* Puts into instruction list needed instructions for pushing local variables
* into the stack and returns list of resulting instructions.
*
* @param ctx The Execution context
* @param insns The original instructions
*/
fun injectNeededStackVars(ctx: ClsContext, insns: List<Node.Instr>): List<Insn> { fun injectNeededStackVars(ctx: ClsContext, insns: List<Node.Instr>): List<Insn> {
ctx.trace { "Calculating places to inject needed stack variables" } ctx.trace { "Calculating places to inject needed stack variables" }
// How we do this: // How we do this:
// We run over each insn, and keep a running list of stack // We run over each insn, and keep a running list of stack
// manips. If there is an insn that needs something so far back, // manips. If there is an ins'n that needs something so far back,
// we calc where it needs to be added and keep a running list of // we calc where it needs to be added and keep a running list of
// insn inserts. Then at the end we settle up. // insn inserts. Then at the end we settle up.
// //
@ -109,6 +121,14 @@ open class InsnReworker {
// guarantee the value will be in the right order if there are // guarantee the value will be in the right order if there are
// multiple for the same index // multiple for the same index
var insnsToInject = emptyMap<Int, List<Insn>>() var insnsToInject = emptyMap<Int, List<Insn>>()
/**
* This function inject current instruction in stack.
*
* @param insn The instruction to inject
* @param count Number of step back on the stack that should we do for
* finding injection index.
*/
fun injectBeforeLastStackCount(insn: Insn, count: Int) { fun injectBeforeLastStackCount(insn: Insn, count: Int) {
ctx.trace { "Injecting $insn back $count stack values" } ctx.trace { "Injecting $insn back $count stack values" }
fun inject(index: Int) { fun inject(index: Int) {
@ -140,9 +160,11 @@ open class InsnReworker {
} }
countSoFar += amountChanged countSoFar += amountChanged
if (!foundUnconditionalJump) foundUnconditionalJump = insns[insnIndex].let { insn -> if (!foundUnconditionalJump) {
insn is Node.Instr.Br || insn is Node.Instr.BrTable || foundUnconditionalJump = insns[insnIndex].let { insn ->
insn is Node.Instr.Unreachable || insn is Node.Instr.Return insn is Node.Instr.Br || insn is Node.Instr.BrTable ||
insn is Node.Instr.Unreachable || insn is Node.Instr.Return
}
} }
if (countSoFar == count) { if (countSoFar == count) {
ctx.trace { "Found injection point as before insn #$insnIndex" } ctx.trace { "Found injection point as before insn #$insnIndex" }
@ -205,13 +227,19 @@ open class InsnReworker {
} }
// Build resulting list // Build resulting list
return insns.foldIndexed(emptyList<Insn>()) { index, ret, insn -> return insns.foldIndexed(emptyList()) { index, ret, insn ->
val injections = insnsToInject[index] ?: emptyList() val injections = insnsToInject[index] ?: emptyList()
ret + injections + Insn.Node(insn) ret + injections + Insn.Node(insn)
} }
} }
fun insnStackDiff(ctx: ClsContext, insn: Node.Instr) = when (insn) { /**
* Calculate stack difference after calling instruction current instruction.
* Returns the difference from stack cursor position before instruction and after.
* `N = PUSH_OPS - POP_OPS.` '-n' mean that POP operation will be more than PUSH.
* If '0' then stack won't changed.
*/
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.Else, is Node.Instr.End, is Node.Instr.Br, is Node.Instr.Loop, is Node.Instr.Else, is Node.Instr.End, is Node.Instr.Br,
is Node.Instr.Return -> NOP is Node.Instr.Return -> NOP
@ -283,7 +311,8 @@ open class InsnReworker {
is Node.Instr.F64ReinterpretI64 -> POP_PARAM + PUSH_RESULT is Node.Instr.F64ReinterpretI64 -> POP_PARAM + PUSH_RESULT
} }
fun nonAdjacentMemAccesses(insns: List<Insn>) = insns.fold(0 to false) { (count, lastCouldHaveMem), insn -> /** Returns number of memory accesses. */
fun nonAdjacentMemAccesses(insns: List<Insn>): Int = insns.fold(0 to false) { (count, lastCouldHaveMem), insn ->
val inc = val inc =
if (lastCouldHaveMem) 0 if (lastCouldHaveMem) 0
else if (insn == Insn.MemNeededOnStack) 1 else if (insn == Insn.MemNeededOnStack) 1

View File

@ -0,0 +1,122 @@
package asmble.compile.jvm
import java.nio.ByteBuffer
import java.nio.ByteOrder
/**
* The default implementation of MemoryBuffer that based on java.nio.ByteBuffer
*/
open class MemoryByteBuffer(val bb: ByteBuffer) : MemoryBuffer() {
override fun put(arr: ByteArray): MemoryBuffer {
bb.put(arr)
return this
}
override fun clear(): MemoryBuffer {
bb.clear()
return this
}
override fun get(arr: ByteArray): MemoryBuffer {
bb.get(arr)
return this
}
override fun putLong(index: Int, n: Long): MemoryBuffer {
bb.putLong(index, n)
return this
}
override fun putDouble(index: Int, n: Double): MemoryBuffer {
bb.putDouble(index, n)
return this
}
override fun putShort(index: Int, n: Short): MemoryBuffer {
bb.putShort(index, n)
return this
}
override fun putFloat(index: Int, n: Float): MemoryBuffer {
bb.putFloat(index, n)
return this
}
override fun put(index: Int, b: Byte): MemoryBuffer {
bb.put(index, b)
return this
}
override fun putInt(index: Int, n: Int): MemoryBuffer {
bb.putInt(index, n)
return this
}
override fun capacity(): Int {
return bb.capacity()
}
override fun limit(): Int {
return bb.limit()
}
override fun limit(newLimit: Int): MemoryBuffer {
bb.limit(newLimit)
return this
}
override fun position(newPosition: Int): MemoryBuffer {
bb.position(newPosition)
return this
}
override fun order(order: ByteOrder): MemoryBuffer {
bb.order(order)
return this
}
override fun duplicate(): MemoryBuffer {
return MemoryByteBuffer(bb.duplicate())
}
override fun put(arr: ByteArray, offset: Int, length: Int): MemoryBuffer {
bb.put(arr, offset, length)
return this
}
override fun getInt(index: Int): Int {
return bb.getInt(index)
}
override fun get(index: Int): Byte {
return bb.get(index)
}
override fun getLong(index: Int): Long {
return bb.getLong(index)
}
override fun getShort(index: Int): Short {
return bb.getShort(index)
}
override fun getFloat(index: Int): Float {
return bb.getFloat(index)
}
override fun getDouble(index: Int): Double {
return bb.getDouble(index)
}
override fun equals(other: Any?): Boolean {
if (this === other)
return true
if (other !is MemoryByteBuffer)
return false
return bb == other.bb
}
override fun hashCode(): Int {
return bb.hashCode()
}
}

View File

@ -2,14 +2,25 @@ package asmble.compile.jvm
import org.objectweb.asm.Type import org.objectweb.asm.Type
/**
* A Java field or method type. This class can be used to make it easier to
* manipulate type and method descriptors.
*
* @param asm Wrapped [org.objectweb.asm.Type] from asm library
*/
data class TypeRef(val asm: Type) { data class TypeRef(val asm: Type) {
/** The internal name of the class corresponding to this object or array type. */
val asmName: String get() = asm.internalName val asmName: String get() = asm.internalName
/** The descriptor corresponding to this Java type. */
val asmDesc: String get() = asm.descriptor val asmDesc: String get() = asm.descriptor
fun asMethodRetDesc(vararg args: TypeRef) = Type.getMethodDescriptor(asm, *args.map { it.asm }.toTypedArray()) /** Size of this type in stack, either 1 or 2 only allowed, where 1 = 2^32` bits */
val stackSize: Int get() = if (asm == Type.DOUBLE_TYPE || asm == Type.LONG_TYPE) 2 else 1 val stackSize: Int get() = if (asm == Type.DOUBLE_TYPE || asm == Type.LONG_TYPE) 2 else 1
fun asMethodRetDesc(vararg args: TypeRef) = Type.getMethodDescriptor(asm, *args.map { it.asm }.toTypedArray())
fun equivalentTo(other: TypeRef) = this == other || this == Unknown || other == Unknown fun equivalentTo(other: TypeRef) = this == other || this == Unknown || other == Unknown
object UnknownType object UnknownType

View File

@ -2,12 +2,11 @@ package asmble.io
import asmble.ast.Node import asmble.ast.Node
import asmble.util.* import asmble.util.*
import java.io.ByteArrayInputStream
import java.nio.ByteBuffer import java.nio.ByteBuffer
open class BinaryToAst( open class BinaryToAst(
val version: Long = 1L, val version: Long = 1L,
val logger: Logger = Logger.Print(Logger.Level.OFF), val logger: Logger = Logger.Print(Logger.Level.WARN),
val includeNameSection: Boolean = true val includeNameSection: Boolean = true
) : Logger by logger { ) : Logger by logger {
@ -186,12 +185,14 @@ open class BinaryToAst(
require(sectionId > maxSectionId) { "Section ID $sectionId came after $maxSectionId" }. require(sectionId > maxSectionId) { "Section ID $sectionId came after $maxSectionId" }.
also { maxSectionId = sectionId } also { maxSectionId = sectionId }
val sectionLen = b.readVarUInt32AsInt() val sectionLen = b.readVarUInt32AsInt()
// each 'read' invocation creates new InputStream and don't closes it
sections += sectionId to b.read(sectionLen) sections += sectionId to b.read(sectionLen)
} }
// Now build the module // Now build the module
fun <T> readSectionList(sectionId: Int, fn: (ByteReader) -> T) = fun <T> readSectionList(sectionId: Int, fn: (ByteReader) -> T) =
sections.find { it.first == sectionId }?.second?.readList(fn) ?: emptyList() sections.find { it.first == sectionId }?.second?.readList(fn) ?: emptyList()
val types = readSectionList(1, this::toFuncType) val types = readSectionList(1, this::toFuncType)
val funcIndices = readSectionList(3) { it.readVarUInt32AsInt() } val funcIndices = readSectionList(3) { it.readVarUInt32AsInt() }
var nameSection: Node.NameSection? = null var nameSection: Node.NameSection? = null

View File

@ -100,6 +100,7 @@ abstract class ByteReader {
return result return result
} }
// todo looks like this InputStream isn't ever closed
class InputStream(val ins: java.io.InputStream) : ByteReader() { class InputStream(val ins: java.io.InputStream) : ByteReader() {
private var nextByte: Byte? = null private var nextByte: Byte? = null
private var sawEof = false private var sawEof = false

View File

@ -0,0 +1,56 @@
package asmble.run.jvm
/**
* Used to tack the state of the environment module.
*/
data class EnvState(
var spentGas: Long = 0,
// executed instruction counter
var EIC: Long = 0
)
/**
* Module used for gas and EIC metering.
*/
open class EnvModule(private val gasLimit: Long) {
private var state = EnvState();
/**
* [Wasm function]
* Adds spent gas to overall spent gas and checks limit exceeding.
*/
fun gas(spentGas: Int) {
if(state.spentGas + spentGas > gasLimit) {
// TODO : check for overflow, throw an exception
}
state.spentGas += spentGas;
}
/**
* [Wasm function]
* Adds EIC to overall executed instruction counter.
*/
fun eic(EIC: Int) {
state.EIC += EIC;
}
/**
* Sets spent gas and EIC value to 0. Used from WasmVm to clear gas value before metering.
* It should be impossible to call this function from a Wasm module.
*/
fun clearState() {
state.spentGas = 0;
state.EIC = 0;
}
/**
* Returns environment module state.
* Used from WasmVm to determine spent gas and executed instruction counter after each invocation.
*/
fun getState(): EnvState {
return state;
}
}

View File

@ -0,0 +1,45 @@
package asmble.run.jvm
import asmble.compile.jvm.Mem
import java.io.PrintWriter
import java.nio.ByteBuffer
/**
* Module used for logging UTF-8 strings from a Wasm module to a given writer.
*/
open class LoggerModule(val writer: PrintWriter) {
// one memory page is quite enough for save temporary buffer
private val memoryPages = 1
private val memory =
ByteBuffer.allocate(memoryPages * Mem.PAGE_SIZE) as ByteBuffer
/**
* [Wasm function]
* Writes one byte to the logger memory buffer. If there is no place flushes
* all data from the buffer to [PrintWriter] and try to put the byte again.
*/
fun write(byte: Int) {
val isFull = memory.position() >= memory.limit()
if (isFull) {
flush()
}
memory.put(byte.toByte())
}
/**
* [Wasm function]
* Reads all bytes from the logger memory buffer, convert its to UTF-8
* string and writes to stdout.
* Cleans the logger memory buffer.
*/
fun flush() {
val message = String(memory.array(), 0, memory.position())
writer.print(message)
writer.flush()
memory.clear()
}
}

View File

@ -76,6 +76,8 @@ interface Module {
// If there is a memory import, we have to get the one with the mem class as the first // If there is a memory import, we have to get the one with the mem class as the first
val memImport = mod.imports.find { it.kind is Node.Import.Kind.Memory } val memImport = mod.imports.find { it.kind is Node.Import.Kind.Memory }
val builder = ctx.memoryBuilder
val memLimit = if (memImport != null) { val memLimit = if (memImport != null) {
constructor = cls.declaredConstructors.find { it.parameterTypes.firstOrNull()?.ref == mem.memType } constructor = cls.declaredConstructors.find { it.parameterTypes.firstOrNull()?.ref == mem.memType }
val memImportKind = memImport.kind as Node.Import.Kind.Memory val memImportKind = memImport.kind as Node.Import.Kind.Memory
@ -89,6 +91,13 @@ interface Module {
throw RunErr.ImportMemoryCapacityTooLarge(it * Mem.PAGE_SIZE, memCap) throw RunErr.ImportMemoryCapacityTooLarge(it * Mem.PAGE_SIZE, memCap)
} }
memLimit memLimit
} else if (builder != null) {
constructor = cls.declaredConstructors.find { it.parameterTypes.firstOrNull()?.ref == mem.memType }
val memLimit = ctx.defaultMaxMemPages * Mem.PAGE_SIZE
val memInst = builder.build(memLimit)
constructorParams += memInst
memLimit
} else { } else {
// Find the constructor with no max mem amount (i.e. not int and not memory) // Find the constructor with no max mem amount (i.e. not int and not memory)
constructor = cls.declaredConstructors.find { constructor = cls.declaredConstructors.find {

View File

@ -19,6 +19,20 @@ import java.lang.invoke.MethodType
import java.lang.reflect.InvocationTargetException import java.lang.reflect.InvocationTargetException
import java.util.* import java.util.*
/**
* Script context. Contains all the information needed to execute this script.
*
* @param packageName Package name for this script
* @param modules List of all modules to be load for this script
* @param registrations Registered modules, key - module name, value - module instance
* @param logger Logger for this script
* @param adjustContext Fn for tuning context (looks redundant)
* @param classLoader ClassLoader for loading all classes for this script
* @param exceptionTranslator Converts exceptions to error messages
* @param defaultMaxMemPages The maximum number of memory pages when a module doesn't say
* @param includeBinaryInCompiledClass Store binary wasm code to compiled class
* file as annotation [asmble.annotation.WasmModule]
*/
data class ScriptContext( data class ScriptContext(
val packageName: String, val packageName: String,
val modules: List<Module.Compiled> = emptyList(), val modules: List<Module.Compiled> = emptyList(),
@ -29,8 +43,10 @@ data class ScriptContext(
ScriptContext.SimpleClassLoader(ScriptContext::class.java.classLoader, logger), ScriptContext.SimpleClassLoader(ScriptContext::class.java.classLoader, logger),
val exceptionTranslator: ExceptionTranslator = ExceptionTranslator, val exceptionTranslator: ExceptionTranslator = ExceptionTranslator,
val defaultMaxMemPages: Int = 1, val defaultMaxMemPages: Int = 1,
val includeBinaryInCompiledClass: Boolean = false val includeBinaryInCompiledClass: Boolean = false,
val memoryBuilder: MemoryBufferBuilder? = null
) : Logger by logger { ) : Logger by logger {
fun withHarnessRegistered(out: PrintWriter = PrintWriter(System.out, true)) = fun withHarnessRegistered(out: PrintWriter = PrintWriter(System.out, true)) =
withModuleRegistered("spectest", Module.Native(TestHarness(out))) withModuleRegistered("spectest", Module.Native(TestHarness(out)))
@ -330,4 +346,4 @@ data class ScriptContext(
defineClass(className, bytes, 0, bytes.size) defineClass(className, bytes, 0, bytes.size)
} }
} }
} }

View File

@ -3,6 +3,8 @@ package asmble.run.jvm
import asmble.annotation.WasmExport import asmble.annotation.WasmExport
import asmble.annotation.WasmExternalKind import asmble.annotation.WasmExternalKind
import asmble.compile.jvm.Mem import asmble.compile.jvm.Mem
import asmble.compile.jvm.MemoryBuffer
import asmble.compile.jvm.MemoryByteBuffer
import java.io.PrintWriter import java.io.PrintWriter
import java.lang.invoke.MethodHandle import java.lang.invoke.MethodHandle
import java.nio.ByteBuffer import java.nio.ByteBuffer
@ -17,10 +19,10 @@ open class TestHarness(val out: PrintWriter) {
val global_f32 = 666.6f val global_f32 = 666.6f
val global_f64 = 666.6 val global_f64 = 666.6
val table = arrayOfNulls<MethodHandle>(10) val table = arrayOfNulls<MethodHandle>(10)
val memory = ByteBuffer. val memory = MemoryByteBuffer(ByteBuffer.
allocateDirect(2 * Mem.PAGE_SIZE). allocateDirect(2 * Mem.PAGE_SIZE).
order(ByteOrder.LITTLE_ENDIAN). order(ByteOrder.LITTLE_ENDIAN).
limit(Mem.PAGE_SIZE) as ByteBuffer limit(Mem.PAGE_SIZE) as ByteBuffer) as MemoryBuffer
// Note, we have all of these overloads because my import method // Note, we have all of these overloads because my import method
// resolver is simple right now and only finds exact methods via // resolver is simple right now and only finds exact methods via

View File

@ -5,7 +5,6 @@ import asmble.ast.Node
import asmble.run.jvm.ScriptContext import asmble.run.jvm.ScriptContext
import asmble.util.get import asmble.util.get
import org.junit.Test import org.junit.Test
import java.nio.ByteBuffer
import java.util.* import java.util.*
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -35,9 +34,9 @@ class LargeDataTest : TestBase() {
val cls = ScriptContext.SimpleClassLoader(javaClass.classLoader, logger).fromBuiltContext(ctx) val cls = ScriptContext.SimpleClassLoader(javaClass.classLoader, logger).fromBuiltContext(ctx)
// Instantiate it, get the memory out, and check it // Instantiate it, get the memory out, and check it
val field = cls.getDeclaredField("memory").apply { isAccessible = true } val field = cls.getDeclaredField("memory").apply { isAccessible = true }
val buf = field[cls.newInstance()] as ByteBuffer val buf = field[cls.newInstance()] as MemoryByteBuffer
// Grab all + 1 and check values // Grab all + 1 and check values
val bytesActual = ByteArray(70001).also { buf.get(0, it) } val bytesActual = ByteArray(70001).also { buf.bb.get(0, it) }
bytesActual.forEachIndexed { index, byte -> bytesActual.forEachIndexed { index, byte ->
assertEquals(if (index == 70000) 0.toByte() else bytesExpected[index], byte) assertEquals(if (index == 70000) 0.toByte() else bytesExpected[index], byte)
} }

View File

@ -4,10 +4,10 @@ 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.compile.jvm.MemoryByteBuffer
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test
import org.objectweb.asm.MethodTooLargeException import org.objectweb.asm.MethodTooLargeException
import java.nio.ByteBuffer
import java.util.* import java.util.*
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -62,7 +62,7 @@ class LargeFuncTest : TestBase() {
// 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 MemoryByteBuffer
// Read out the mem values // Read out the mem values
(0 until numInsnChunks).forEach { assertEquals(it * (it - 1), mem.getInt(it * 4)) } (0 until numInsnChunks).forEach { assertEquals(it * (it - 1), mem.getInt(it * 4)) }
} }

View File

@ -0,0 +1,51 @@
package asmble.run.jvm
import asmble.TestBase
import org.junit.Test
import java.io.PrintWriter
import java.io.StringWriter
import kotlin.test.assertEquals
class LoggerModuleTest : TestBase() {
@Test
fun writeAndFlushTest() {
val stream = StringWriter()
val logger = LoggerModule(PrintWriter(stream))
logger.flush() // checks that no raise error
val testString = "test String for log to stdout"
for (byte: Byte in testString.toByteArray()) {
logger.write(byte.toInt())
}
logger.flush()
val loggedString = stream.toString()
assertEquals(testString, loggedString)
}
@Test
fun writeAndFlushMoreThanLoggerBufferTest() {
val stream = StringWriter()
// logger buffer has 65Kb size
val logger = LoggerModule(PrintWriter(stream))
val testString = longString(65_000 * 2) // twice as much as logger buffer
for (byte: Byte in testString.toByteArray()) {
logger.write(byte.toInt())
}
logger.flush()
val loggedString = stream.toString()
assertEquals(testString, loggedString)
}
private fun longString(size: Int): String {
val stringBuffer = StringBuffer()
for (idx: Int in (1 until size)) {
stringBuffer.append((idx % Byte.MAX_VALUE).toChar())
}
return stringBuffer.toString()
}
}

View File

@ -3,6 +3,8 @@ package asmble.run.jvm
import asmble.BaseTestUnit import asmble.BaseTestUnit
import asmble.TestBase import asmble.TestBase
import asmble.annotation.WasmModule import asmble.annotation.WasmModule
import asmble.compile.jvm.MemoryBufferBuilder
import asmble.compile.jvm.MemoryByteBuffer
import asmble.io.AstToBinary import asmble.io.AstToBinary
import asmble.io.AstToSExpr import asmble.io.AstToSExpr
import asmble.io.ByteWriter import asmble.io.ByteWriter
@ -12,6 +14,7 @@ import org.junit.Test
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.OutputStreamWriter import java.io.OutputStreamWriter
import java.io.PrintWriter import java.io.PrintWriter
import java.nio.ByteBuffer
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
@ -40,7 +43,10 @@ abstract class TestRunner<out T : BaseTestUnit>(val unit: T) : TestBase() {
adjustContext = { it.copy(eagerFailLargeMemOffset = false) }, adjustContext = { it.copy(eagerFailLargeMemOffset = false) },
defaultMaxMemPages = unit.defaultMaxMemPages, defaultMaxMemPages = unit.defaultMaxMemPages,
// Include the binary data so we can check it later // Include the binary data so we can check it later
includeBinaryInCompiledClass = true includeBinaryInCompiledClass = true,
memoryBuilder = MemoryBufferBuilder { it ->
MemoryByteBuffer(ByteBuffer.allocateDirect(it))
}
).withHarnessRegistered(PrintWriter(OutputStreamWriter(out, Charsets.UTF_8), true)) ).withHarnessRegistered(PrintWriter(OutputStreamWriter(out, Charsets.UTF_8), true))
// This will fail assertions as necessary // This will fail assertions as necessary

View File

@ -9,9 +9,3 @@ Compile Rust to WASM and then to the JVM. In order of complexity:
* [rust-simple](rust-simple) * [rust-simple](rust-simple)
* [rust-string](rust-string) * [rust-string](rust-string)
* [rust-regex](rust-regex) * [rust-regex](rust-regex)
### C/C++
Compile C to WASM and then to the JVM. In order of complexity:
* [c-simple](c-simple)

View File

@ -2,8 +2,9 @@
extern crate regex; extern crate regex;
use std::ptr::NonNull;
use regex::Regex; use regex::Regex;
use std::heap::{Alloc, Heap, Layout}; use std::alloc::{Alloc, Global, Layout};
use std::mem; use std::mem;
use std::str; use std::str;
@ -37,17 +38,17 @@ pub extern "C" fn match_count(r: *mut Regex, str_ptr: *mut u8, len: usize) -> us
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn alloc(size: usize) -> *mut u8 { pub extern "C" fn alloc(size: usize) -> NonNull<u8> {
unsafe { unsafe {
let layout = Layout::from_size_align(size, mem::align_of::<u8>()).unwrap(); let layout = Layout::from_size_align(size, mem::align_of::<u8>()).unwrap();
Heap.alloc(layout).unwrap() Global.alloc(layout).unwrap()
} }
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn dealloc(ptr: *mut u8, size: usize) { pub extern "C" fn dealloc(ptr: NonNull<u8>, size: usize) {
unsafe { unsafe {
let layout = Layout::from_size_align(size, mem::align_of::<u8>()).unwrap(); let layout = Layout::from_size_align(size, mem::align_of::<u8>()).unwrap();
Heap.dealloc(ptr, layout); Global.dealloc(ptr, layout);
} }
} }

View File

@ -3,6 +3,9 @@ package asmble.examples.rustregex;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/**
* Implementation of {@link RegexLib} based on `java.util.regex`.
*/
public class JavaLib implements RegexLib<String> { public class JavaLib implements RegexLib<String> {
@Override @Override
public JavaPattern compile(String str) { public JavaPattern compile(String str) {

View File

@ -5,6 +5,10 @@ import asmble.generated.RustRegex;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
/**
* Implementation of {@link RegexLib} based on `asmble.generated.RustRegex` that
* was composed from Rust code (see lib.rs).
*/
public class RustLib implements RegexLib<RustLib.Ptr> { public class RustLib implements RegexLib<RustLib.Ptr> {
// 600 pages is enough for our use // 600 pages is enough for our use

View File

@ -1,6 +1,7 @@
#![feature(allocator_api)] #![feature(allocator_api)]
use std::heap::{Alloc, Heap, Layout}; use std::ptr::NonNull;
use std::alloc::{Alloc, Global, Layout};
use std::ffi::{CString}; use std::ffi::{CString};
use std::mem; use std::mem;
use std::os::raw::c_char; use std::os::raw::c_char;
@ -30,17 +31,17 @@ pub extern "C" fn prepend_from_rust(ptr: *mut u8, len: usize) -> *const c_char {
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn alloc(size: usize) -> *mut u8 { pub extern "C" fn alloc(size: usize) -> NonNull<u8> {
unsafe { unsafe {
let layout = Layout::from_size_align(size, mem::align_of::<u8>()).unwrap(); let layout = Layout::from_size_align(size, mem::align_of::<u8>()).unwrap();
Heap.alloc(layout).unwrap() Global.alloc(layout).unwrap()
} }
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn dealloc(ptr: *mut u8, size: usize) { pub extern "C" fn dealloc(ptr: NonNull<u8>, size: usize) {
unsafe { unsafe {
let layout = Layout::from_size_align(size, mem::align_of::<u8>()).unwrap(); let layout = Layout::from_size_align(size, mem::align_of::<u8>()).unwrap();
Heap.dealloc(ptr, layout); Global.dealloc(ptr, layout);
} }
} }