Compare commits

..

7 Commits

Author SHA1 Message Date
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
Dima
1d6002624f
Fix memory builder (#10)
* fix memory instance creation

* change version
2019-06-03 14:02:15 +03:00
Dima
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
16 changed files with 336 additions and 77 deletions

View File

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

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

@ -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
) )
} }

View File

@ -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'")
} }
} }

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

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

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

@ -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()

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

@ -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)) =

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

@ -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()) {

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