mirror of
https://github.com/fluencelabs/asmble
synced 2025-07-05 09:21:36 +00:00
Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
23665fa16b | |||
4c65740d03 | |||
728b78d713 | |||
cb907ae2da | |||
1d6002624f | |||
ad2b7c071f | |||
119ce58c9e | |||
1323e02c95 |
@ -21,7 +21,7 @@ buildscript {
|
|||||||
allprojects {
|
allprojects {
|
||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
group 'com.github.cretz.asmble'
|
group 'com.github.cretz.asmble'
|
||||||
version '0.4.2-fl'
|
version '0.4.11-fl'
|
||||||
|
|
||||||
// skips building and running for the specified examples
|
// skips building and running for the specified examples
|
||||||
ext.skipExamples = ['c-simple', 'go-simple', 'rust-regex']
|
ext.skipExamples = ['c-simple', 'go-simple', 'rust-regex']
|
||||||
@ -309,6 +309,9 @@ def publishSettings(project, projectName, projectDescription) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bintray {
|
bintray {
|
||||||
|
if(!hasProperty("bintrayUser") || !hasProperty("bintrayKey")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
user = bintrayUser
|
user = bintrayUser
|
||||||
key = bintrayKey
|
key = bintrayKey
|
||||||
|
40
compiler/src/main/java/asmble/compile/jvm/MemoryBuffer.java
Normal file
40
compiler/src/main/java/asmble/compile/jvm/MemoryBuffer.java
Normal 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);
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package asmble.compile.jvm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to initialize MemoryBuffer
|
||||||
|
*/
|
||||||
|
public interface MemoryBufferBuilder {
|
||||||
|
MemoryBuffer build(int capacity);
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
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.LoggerModule
|
import asmble.run.jvm.*
|
||||||
import asmble.run.jvm.Module
|
|
||||||
import asmble.run.jvm.ScriptContext
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -44,19 +42,20 @@ abstract class ScriptCommand<T> : Command<T>() {
|
|||||||
default = "5",
|
default = "5",
|
||||||
lowPriority = true
|
lowPriority = true
|
||||||
).toInt(),
|
).toInt(),
|
||||||
loggerMemPages = bld.arg(
|
enableLogger = bld.arg(
|
||||||
name = "loggerMemPages",
|
name = "enableLogger",
|
||||||
opt = "loggermempages",
|
opt = "enableLogger",
|
||||||
desc = "The maximum number of memory pages of the logger module.",
|
desc = "Enables the special module the could be used for logging",
|
||||||
default = "0",
|
default = "false",
|
||||||
lowPriority = true
|
lowPriority = true
|
||||||
).toInt()
|
).toBoolean()
|
||||||
)
|
)
|
||||||
|
|
||||||
fun prepareContext(args: ScriptArgs): ScriptContext {
|
fun prepareContext(args: ScriptArgs): ScriptContext {
|
||||||
var context = 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
|
||||||
context = args.inFiles.foldIndexed(context) { index, ctx, inFile ->
|
context = args.inFiles.foldIndexed(context) { index, ctx, inFile ->
|
||||||
@ -86,21 +85,32 @@ abstract class ScriptCommand<T> : Command<T>() {
|
|||||||
throw Exception("Failed loading $inFile - ${e.message}", e)
|
throw Exception("Failed loading $inFile - ${e.message}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do registrations
|
// Do registrations
|
||||||
context = args.registrations.fold(context) { 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) context = context.withHarnessRegistered()
|
if (args.specTestRegister) context = context.withHarnessRegistered()
|
||||||
|
|
||||||
if (args.loggerMemPages > 0) {
|
if (args.enableLogger) {
|
||||||
// creates additional Wasm module with logger functionality
|
// add logger Wasm module for logging
|
||||||
context =
|
context =
|
||||||
context.withModuleRegistered(
|
context.withModuleRegistered(
|
||||||
"logger",
|
"logger",
|
||||||
Module.Native(LoggerModule(args.loggerMemPages, PrintWriter(System.out)))
|
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
|
return context
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +122,8 @@ abstract class ScriptCommand<T> : Command<T>() {
|
|||||||
* @param disableAutoRegister If set, this will not auto-register modules with names
|
* @param disableAutoRegister If set, this will not auto-register modules with names
|
||||||
* @param specTestRegister If true, registers the spec test harness as 'spectest'
|
* @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 defaultMaxMemPages The maximum number of memory pages when a module doesn't say
|
||||||
* @param loggerMemPages The maximum number of memory pages of the logger module.
|
* @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>,
|
||||||
@ -120,6 +131,7 @@ abstract class ScriptCommand<T> : Command<T>() {
|
|||||||
val disableAutoRegister: Boolean,
|
val disableAutoRegister: Boolean,
|
||||||
val specTestRegister: Boolean,
|
val specTestRegister: Boolean,
|
||||||
val defaultMaxMemPages: Int,
|
val defaultMaxMemPages: Int,
|
||||||
val loggerMemPages: Int
|
val enableLogger: Boolean,
|
||||||
|
val memoryBuilder: MemoryBufferBuilder? = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -70,8 +70,10 @@ open class Translate : Command<Translate.Args>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"wasm" ->
|
"wasm" ->
|
||||||
Script(listOf(Script.Cmd.Module(BinaryToAst(logger = this.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'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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) {
|
||||||
|
122
compiler/src/main/kotlin/asmble/compile/jvm/MemoryByteBuffer.kt
Normal file
122
compiler/src/main/kotlin/asmble/compile/jvm/MemoryByteBuffer.kt
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
56
compiler/src/main/kotlin/asmble/run/jvm/EnvModule.kt
Normal file
56
compiler/src/main/kotlin/asmble/run/jvm/EnvModule.kt
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,19 +5,20 @@ import java.io.PrintWriter
|
|||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module with possibility to write bytes to any 'writer'. This module actually
|
* Module used for logging UTF-8 strings from a Wasm module to a given writer.
|
||||||
* used for logging from the Wasm code outside to 'embedder' (host environment).
|
|
||||||
*/
|
*/
|
||||||
open class LoggerModule(pagesOfMemory: Int, val writer: PrintWriter) {
|
open class LoggerModule(val writer: PrintWriter) {
|
||||||
|
|
||||||
|
// one memory page is quite enough for save temporary buffer
|
||||||
|
private val memoryPages = 1
|
||||||
|
|
||||||
private val memory =
|
private val memory =
|
||||||
ByteBuffer.allocate(pagesOfMemory * Mem.PAGE_SIZE) as ByteBuffer
|
ByteBuffer.allocate(memoryPages * Mem.PAGE_SIZE) as ByteBuffer
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Wasm function]
|
* [Wasm function]
|
||||||
* Writes one byte to the logger memory buffer. If there is no place to write
|
* Writes one byte to the logger memory buffer. If there is no place flushes
|
||||||
* one byte into the buffer then flush all data from the buffer to [PrintWriter]
|
* all data from the buffer to [PrintWriter] and try to put the byte again.
|
||||||
* and after that try to put the byte again.
|
|
||||||
*/
|
*/
|
||||||
fun write(byte: Int) {
|
fun write(byte: Int) {
|
||||||
val isFull = memory.position() >= memory.limit()
|
val isFull = memory.position() >= memory.limit()
|
||||||
|
@ -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 {
|
||||||
|
@ -43,7 +43,8 @@ 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)) =
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)) }
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ class LoggerModuleTest : TestBase() {
|
|||||||
@Test
|
@Test
|
||||||
fun writeAndFlushTest() {
|
fun writeAndFlushTest() {
|
||||||
val stream = StringWriter()
|
val stream = StringWriter()
|
||||||
val logger = LoggerModule(1, PrintWriter(stream))
|
val logger = LoggerModule(PrintWriter(stream))
|
||||||
|
|
||||||
logger.flush() // checks that no raise error
|
logger.flush() // checks that no raise error
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ class LoggerModuleTest : TestBase() {
|
|||||||
fun writeAndFlushMoreThanLoggerBufferTest() {
|
fun writeAndFlushMoreThanLoggerBufferTest() {
|
||||||
val stream = StringWriter()
|
val stream = StringWriter()
|
||||||
// logger buffer has 65Kb size
|
// logger buffer has 65Kb size
|
||||||
val logger = LoggerModule(1, PrintWriter(stream))
|
val logger = LoggerModule(PrintWriter(stream))
|
||||||
|
|
||||||
val testString = longString(65_000 * 2) // twice as much as logger buffer
|
val testString = longString(65_000 * 2) // twice as much as logger buffer
|
||||||
for (byte: Byte in testString.toByteArray()) {
|
for (byte: Byte in testString.toByteArray()) {
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user