mirror of
https://github.com/fluencelabs/asmble
synced 2025-04-25 06:42:22 +00:00
Begin linker dev for issue #8
This commit is contained in:
parent
43333edfd0
commit
3e912b2b15
11
annotations/src/main/java/asmble/annotation/WasmExport.java
Normal file
11
annotations/src/main/java/asmble/annotation/WasmExport.java
Normal file
@ -0,0 +1,11 @@
|
||||
package asmble.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
public @interface WasmExport {
|
||||
String value();
|
||||
WasmExternalKind kind() default WasmExternalKind.FUNCTION;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package asmble.annotation;
|
||||
|
||||
public enum WasmExternalKind {
|
||||
MEMORY, GLOBAL, FUNCTION, TABLE
|
||||
}
|
16
annotations/src/main/java/asmble/annotation/WasmImport.java
Normal file
16
annotations/src/main/java/asmble/annotation/WasmImport.java
Normal file
@ -0,0 +1,16 @@
|
||||
package asmble.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.PARAMETER)
|
||||
public @interface WasmImport {
|
||||
String module();
|
||||
String field();
|
||||
// The JVM method descriptor of an export that will match this
|
||||
String desc();
|
||||
WasmExternalKind kind();
|
||||
int resizableLimitInitial() default -1;
|
||||
int resizableLimitMaximum() default -1;
|
||||
}
|
11
annotations/src/main/java/asmble/annotation/WasmModule.java
Normal file
11
annotations/src/main/java/asmble/annotation/WasmModule.java
Normal file
@ -0,0 +1,11 @@
|
||||
package asmble.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface WasmModule {
|
||||
String name() default "";
|
||||
String binary() default "";
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package asmble.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface WasmName {
|
||||
String value();
|
||||
}
|
@ -2,7 +2,7 @@ group 'asmble'
|
||||
version '0.1.0'
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.1.1'
|
||||
ext.kotlin_version = '1.1.2'
|
||||
ext.asm_version = '5.2'
|
||||
|
||||
repositories {
|
||||
|
@ -31,6 +31,17 @@ open class Compile : Command<Compile.Args>() {
|
||||
opt = "out",
|
||||
desc = "The file name to output to. Can be '--' to write to stdout.",
|
||||
default = "<outClass.class>"
|
||||
),
|
||||
name = bld.arg(
|
||||
name = "name",
|
||||
opt = "name",
|
||||
desc = "The name to use for this module. Will override the name on the module if present.",
|
||||
default = "<name on module or none>"
|
||||
).takeIf { it != "<name on module or none>" },
|
||||
includeBinary = bld.flag(
|
||||
opt = "bindata",
|
||||
desc = "Embed the WASM binary as an annotation on the class.",
|
||||
lowPriority = true
|
||||
)
|
||||
).also { bld.done() }
|
||||
|
||||
@ -40,7 +51,7 @@ open class Compile : Command<Compile.Args>() {
|
||||
if (args.inFormat != "<use file extension>") args.inFormat
|
||||
else args.inFile.substringAfterLast('.', "<unknown>")
|
||||
val script = Translate.inToAst(args.inFile, inFormat)
|
||||
val mod = (script.commands.firstOrNull() as? Script.Cmd.Module)?.module ?:
|
||||
val mod = (script.commands.firstOrNull() as? Script.Cmd.Module) ?:
|
||||
error("Only a single sexpr for (module) allowed")
|
||||
val outStream = when (args.outFile) {
|
||||
"<outClass.class>" -> FileOutputStream(args.outClass.substringAfterLast('.') + ".class")
|
||||
@ -51,8 +62,10 @@ open class Compile : Command<Compile.Args>() {
|
||||
val ctx = ClsContext(
|
||||
packageName = if (!args.outClass.contains('.')) "" else args.outClass.substringBeforeLast('.'),
|
||||
className = args.outClass.substringAfterLast('.'),
|
||||
mod = mod,
|
||||
logger = logger
|
||||
mod = mod.module,
|
||||
modName = args.name ?: mod.name,
|
||||
logger = logger,
|
||||
includeBinary = args.includeBinary
|
||||
)
|
||||
AstToAsm.fromModule(ctx)
|
||||
outStream.write(ctx.cls.withComputedFramesAndMaxs())
|
||||
@ -63,7 +76,9 @@ open class Compile : Command<Compile.Args>() {
|
||||
val inFile: String,
|
||||
val inFormat: String,
|
||||
val outClass: String,
|
||||
val outFile: String
|
||||
val outFile: String,
|
||||
val name: String?,
|
||||
val includeBinary: Boolean
|
||||
)
|
||||
|
||||
companion object : Compile()
|
||||
|
67
compiler/src/main/kotlin/asmble/cli/Link.kt
Normal file
67
compiler/src/main/kotlin/asmble/cli/Link.kt
Normal file
@ -0,0 +1,67 @@
|
||||
package asmble.cli
|
||||
|
||||
import asmble.compile.jvm.Linker
|
||||
import asmble.compile.jvm.withComputedFramesAndMaxs
|
||||
import java.io.FileOutputStream
|
||||
|
||||
open class Link : Command<Link.Args>() {
|
||||
|
||||
override val name = "link"
|
||||
override val desc = "Link WebAssembly modules in a single class file. TODO: not done"
|
||||
|
||||
override fun args(bld: Command.ArgsBuilder) = Args(
|
||||
outFile = bld.arg(
|
||||
name = "outFile",
|
||||
opt = "out",
|
||||
desc = "The file name to output to. Can be '--' to write to stdout.",
|
||||
default = "<outClass.class>"
|
||||
),
|
||||
modules = bld.args(
|
||||
name = "modules",
|
||||
desc = "The fully qualified class name of the modules on the classpath to link. A module name can be" +
|
||||
" added after an equals sign to set/override the existing module name."
|
||||
),
|
||||
outClass = bld.arg(
|
||||
name = "outClass",
|
||||
desc = "The fully qualified class name."
|
||||
),
|
||||
defaultMaxMem = bld.arg(
|
||||
name = "defaultMaxMem",
|
||||
opt = "maxmem",
|
||||
desc = "The max number of pages to build memory with when not specified by the module/import.",
|
||||
default = "10"
|
||||
).toInt()
|
||||
).also { bld.done() }
|
||||
|
||||
override fun run(args: Args) {
|
||||
val outStream = when (args.outFile) {
|
||||
"<outClass.class>" -> FileOutputStream(args.outClass.substringAfterLast('.') + ".class")
|
||||
"--" -> System.out
|
||||
else -> FileOutputStream(args.outFile)
|
||||
}
|
||||
outStream.use { outStream ->
|
||||
val ctx = Linker.Context(
|
||||
classes = args.modules.map { module ->
|
||||
val pieces = module.split('=', limit = 2)
|
||||
Linker.ModuleClass(
|
||||
cls = Class.forName(pieces.first()),
|
||||
overrideName = pieces.getOrNull(1)
|
||||
)
|
||||
},
|
||||
className = args.outClass,
|
||||
defaultMaxMemPages = args.defaultMaxMem
|
||||
)
|
||||
Linker.link(ctx)
|
||||
outStream.write(ctx.cls.withComputedFramesAndMaxs())
|
||||
}
|
||||
}
|
||||
|
||||
data class Args(
|
||||
val modules: List<String>,
|
||||
val outClass: String,
|
||||
val outFile: String,
|
||||
val defaultMaxMem: Int
|
||||
)
|
||||
|
||||
companion object : Link()
|
||||
}
|
@ -3,7 +3,7 @@ package asmble.cli
|
||||
import asmble.util.Logger
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
val commands = listOf(Compile, Help, Invoke, Run, Translate)
|
||||
val commands = listOf(Compile, Help, Invoke, Link, Run, Translate)
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
if (args.isEmpty()) return println(
|
||||
|
@ -9,6 +9,9 @@ import org.objectweb.asm.tree.*
|
||||
import org.objectweb.asm.util.TraceClassVisitor
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import java.lang.reflect.Constructor
|
||||
import java.lang.reflect.Executable
|
||||
import java.lang.reflect.Method
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KProperty
|
||||
@ -60,6 +63,13 @@ val Class<*>.valueType: Node.Type.Value? get() = when (this) {
|
||||
else -> error("Unrecognized value type class: $this")
|
||||
}
|
||||
|
||||
val Executable.ref: TypeRef get() = when (this) {
|
||||
is Method -> TypeRef(Type.getType(this))
|
||||
is Constructor<*> -> TypeRef(Type.getType(this))
|
||||
else -> error("Unknown executable $this")
|
||||
}
|
||||
|
||||
|
||||
val KProperty<*>.declarer: Class<*> get() = this.javaField!!.declaringClass
|
||||
val KProperty<*>.asmDesc: String get() = Type.getDescriptor(this.javaField!!.type)
|
||||
|
||||
@ -178,11 +188,12 @@ fun MethodNode.toAsmString(): String {
|
||||
val Node.Type.Func.asmDesc: String get() =
|
||||
(this.ret?.typeRef ?: Void::class.ref).asMethodRetDesc(*this.params.map { it.typeRef }.toTypedArray())
|
||||
|
||||
fun ClassNode.withComputedFramesAndMaxs(): ByteArray {
|
||||
fun ClassNode.withComputedFramesAndMaxs(
|
||||
cw: ClassWriter = ClassWriter(ClassWriter.COMPUTE_FRAMES + ClassWriter.COMPUTE_MAXS)
|
||||
): ByteArray {
|
||||
// Note, compute maxs adds a bunch of NOPs for unreachable code.
|
||||
// See $func12 of block.wast. I don't believe the extra time over the
|
||||
// instructions to remove the NOPs is worth it.
|
||||
val cw = ClassWriter(ClassWriter.COMPUTE_FRAMES + ClassWriter.COMPUTE_MAXS)
|
||||
this.accept(cw)
|
||||
return cw.toByteArray()
|
||||
}
|
||||
|
@ -1,10 +1,17 @@
|
||||
package asmble.compile.jvm
|
||||
|
||||
import asmble.annotation.WasmExport
|
||||
import asmble.annotation.WasmExternalKind
|
||||
import asmble.annotation.WasmImport
|
||||
import asmble.annotation.WasmModule
|
||||
import asmble.ast.Node
|
||||
import asmble.io.AstToBinary
|
||||
import asmble.io.ByteWriter
|
||||
import asmble.util.Either
|
||||
import org.objectweb.asm.Opcodes
|
||||
import org.objectweb.asm.Type
|
||||
import org.objectweb.asm.tree.*
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.lang.invoke.MethodHandle
|
||||
import java.lang.invoke.MethodHandles
|
||||
|
||||
@ -13,12 +20,13 @@ open class AstToAsm {
|
||||
fun fromModule(ctx: ClsContext) {
|
||||
// Invoke dynamic among other things
|
||||
ctx.cls.superName = Object::class.ref.asmName
|
||||
ctx.cls.version = Opcodes.V1_7
|
||||
ctx.cls.version = Opcodes.V1_8
|
||||
ctx.cls.access += Opcodes.ACC_PUBLIC
|
||||
addFields(ctx)
|
||||
addConstructors(ctx)
|
||||
addFuncs(ctx)
|
||||
addExports(ctx)
|
||||
addAnnotations(ctx)
|
||||
}
|
||||
|
||||
fun addFields(ctx: ClsContext) {
|
||||
@ -85,7 +93,7 @@ open class AstToAsm {
|
||||
func = initializeConstructorTables(ctx, func, 0)
|
||||
func = executeConstructorStartFunction(ctx, func, 0)
|
||||
func = func.addInsns(InsnNode(Opcodes.RETURN))
|
||||
ctx.cls.methods.add(func.toMethodNode())
|
||||
ctx.cls.methods.add(toConstructorNode(ctx, func))
|
||||
}
|
||||
|
||||
fun addMaxMemConstructor(ctx: ClsContext) {
|
||||
@ -107,7 +115,7 @@ open class AstToAsm {
|
||||
MethodInsnNode(Opcodes.INVOKESPECIAL, ctx.thisRef.asmName, "<init>", desc, false),
|
||||
InsnNode(Opcodes.RETURN)
|
||||
)
|
||||
ctx.cls.methods.add(func.toMethodNode())
|
||||
ctx.cls.methods.add(toConstructorNode(ctx, func))
|
||||
}
|
||||
|
||||
fun addMemClassConstructor(ctx: ClsContext) {
|
||||
@ -148,7 +156,7 @@ open class AstToAsm {
|
||||
|
||||
func = executeConstructorStartFunction(ctx, func, 1)
|
||||
func = func.addInsns(InsnNode(Opcodes.RETURN))
|
||||
ctx.cls.methods.add(func.toMethodNode())
|
||||
ctx.cls.methods.add(toConstructorNode(ctx, func))
|
||||
}
|
||||
|
||||
fun addMemDefaultConstructor(ctx: ClsContext) {
|
||||
@ -167,7 +175,7 @@ open class AstToAsm {
|
||||
MethodInsnNode(Opcodes.INVOKESPECIAL, ctx.thisRef.asmName, "<init>", desc, false),
|
||||
InsnNode(Opcodes.RETURN)
|
||||
)
|
||||
ctx.cls.methods.add(func.toMethodNode())
|
||||
ctx.cls.methods.add(toConstructorNode(ctx, func))
|
||||
}
|
||||
|
||||
fun constructorImportTypes(ctx: ClsContext) =
|
||||
@ -176,6 +184,61 @@ open class AstToAsm {
|
||||
ctx.importGlobals.map { MethodHandle::class.ref } +
|
||||
ctx.mod.imports.filter { it.kind is Node.Import.Kind.Table }.map { Array<MethodHandle>::class.ref }
|
||||
|
||||
fun toConstructorNode(ctx: ClsContext, func: Func) = mutableListOf<List<AnnotationNode>>().let { paramAnns ->
|
||||
// If the first param is a mem class and imported, add annotation
|
||||
// Otherwise if it is a mem class and not-imported or an int, no annotations
|
||||
// Otherwise do nothing because the rest of the params are imports
|
||||
func.params.firstOrNull()?.also { firstParam ->
|
||||
if (firstParam == Int::class.ref) {
|
||||
paramAnns.add(emptyList())
|
||||
} else if (firstParam == ctx.mem.memType) {
|
||||
val importMem = ctx.mod.imports.find { it.kind is Node.Import.Kind.Memory }
|
||||
if (importMem == null) paramAnns.add(emptyList())
|
||||
else paramAnns.add(listOf(importAnnotation(ctx, importMem)))
|
||||
}
|
||||
}
|
||||
// All non-mem imports one after another
|
||||
ctx.importFuncs.forEach { paramAnns.add(listOf(importAnnotation(ctx, it))) }
|
||||
ctx.importGlobals.forEach { paramAnns.add(listOf(importAnnotation(ctx, it))) }
|
||||
ctx.mod.imports.forEach {
|
||||
if (it.kind is Node.Import.Kind.Table) paramAnns.add(listOf(importAnnotation(ctx, it)))
|
||||
}
|
||||
func.toMethodNode().also { it.visibleParameterAnnotations = paramAnns.toTypedArray() }
|
||||
}
|
||||
|
||||
fun importAnnotation(ctx: ClsContext, import: Node.Import) = AnnotationNode(WasmImport::class.ref.asmDesc).also {
|
||||
it.values = mutableListOf<Any>("module", import.module, "field", import.field)
|
||||
fun addValues(desc: String, limits: Node.ResizableLimits? = null) {
|
||||
it.values.add("desc")
|
||||
it.values.add(desc)
|
||||
if (limits != null) {
|
||||
it.values.add("resizableLimitInitial")
|
||||
it.values.add(limits.initial)
|
||||
if (limits.maximum != null) {
|
||||
it.values.add("resizableLimitMaximum")
|
||||
it.values.add(limits.maximum)
|
||||
}
|
||||
}
|
||||
it.values.add("kind")
|
||||
it.values.add(arrayOf(WasmExternalKind::class.ref.asmDesc, when (import.kind) {
|
||||
is Node.Import.Kind.Func -> WasmExternalKind.FUNCTION.name
|
||||
is Node.Import.Kind.Table -> WasmExternalKind.TABLE.name
|
||||
is Node.Import.Kind.Memory -> WasmExternalKind.MEMORY.name
|
||||
is Node.Import.Kind.Global -> WasmExternalKind.GLOBAL.name
|
||||
}))
|
||||
}
|
||||
when (import.kind) {
|
||||
is Node.Import.Kind.Func ->
|
||||
ctx.typeAtIndex(import.kind.typeIndex).let { addValues(it.asmDesc) }
|
||||
is Node.Import.Kind.Table ->
|
||||
addValues(Array<MethodHandle>::class.ref.asMethodRetDesc(), import.kind.type.limits)
|
||||
is Node.Import.Kind.Memory ->
|
||||
addValues(ctx.mem.memType.asMethodRetDesc(), import.kind.type.limits)
|
||||
is Node.Import.Kind.Global ->
|
||||
addValues(import.kind.type.contentType.typeRef.asMethodRetDesc())
|
||||
}
|
||||
}
|
||||
|
||||
fun setConstructorGlobalImports(ctx: ClsContext, func: Func, paramsBeforeImports: Int) =
|
||||
ctx.importGlobals.indices.fold(func) { func, importIndex ->
|
||||
func.addInsns(
|
||||
@ -411,6 +474,13 @@ open class AstToAsm {
|
||||
}
|
||||
}
|
||||
|
||||
fun exportAnnotation(export: Node.Export) = AnnotationNode(WasmExport::class.ref.asmDesc).also {
|
||||
it.values = listOf(
|
||||
"value", export.field,
|
||||
"kind", arrayOf(WasmExternalKind::class.ref.asmDesc, export.kind.name)
|
||||
)
|
||||
}
|
||||
|
||||
fun addExportFunc(ctx: ClsContext, export: Node.Export) {
|
||||
val funcType = ctx.funcTypeAtIndex(export.index)
|
||||
val method = MethodNode(Opcodes.ACC_PUBLIC, export.field.javaIdent, funcType.asmDesc, null, null)
|
||||
@ -452,6 +522,7 @@ open class AstToAsm {
|
||||
Node.Type.Value.F32 -> Opcodes.FRETURN
|
||||
Node.Type.Value.F64 -> Opcodes.DRETURN
|
||||
}))
|
||||
method.visibleAnnotations = listOf(exportAnnotation(export))
|
||||
ctx.cls.methods.plusAssign(method)
|
||||
}
|
||||
|
||||
@ -481,6 +552,7 @@ open class AstToAsm {
|
||||
Node.Type.Value.F32 -> Opcodes.FRETURN
|
||||
Node.Type.Value.F64 -> Opcodes.DRETURN
|
||||
}))
|
||||
method.visibleAnnotations = listOf(exportAnnotation(export))
|
||||
ctx.cls.methods.plusAssign(method)
|
||||
}
|
||||
|
||||
@ -494,6 +566,7 @@ open class AstToAsm {
|
||||
FieldInsnNode(Opcodes.GETFIELD, ctx.thisRef.asmName, "memory", ctx.mem.memType.asmDesc),
|
||||
InsnNode(Opcodes.ARETURN)
|
||||
)
|
||||
method.visibleAnnotations = listOf(exportAnnotation(export))
|
||||
ctx.cls.methods.plusAssign(method)
|
||||
}
|
||||
|
||||
@ -507,6 +580,7 @@ open class AstToAsm {
|
||||
FieldInsnNode(Opcodes.GETFIELD, ctx.thisRef.asmName, "table", Array<MethodHandle>::class.ref.asmDesc),
|
||||
InsnNode(Opcodes.ARETURN)
|
||||
)
|
||||
method.visibleAnnotations = listOf(exportAnnotation(export))
|
||||
ctx.cls.methods.plusAssign(method)
|
||||
}
|
||||
|
||||
@ -516,5 +590,22 @@ open class AstToAsm {
|
||||
})
|
||||
}
|
||||
|
||||
fun addAnnotations(ctx: ClsContext) {
|
||||
val annotationVals = mutableListOf<Any>()
|
||||
ctx.modName?.let { annotationVals.addAll(listOf("name", it)) }
|
||||
if (ctx.includeBinary) {
|
||||
// We are going to store this as a string of bytes in an annotation on the class. The linker
|
||||
// used to use this, but no longer does so it is opt-in for others to use. We choose to use an
|
||||
// annotation instead of an attribute for the same reasons Scala chose to make the switch in
|
||||
// 2.8+: Easier runtime reflection despite some size cost.
|
||||
annotationVals.addAll(listOf("binary", ByteArrayOutputStream().also {
|
||||
ByteWriter.OutputStream(it).also { AstToBinary.fromModule(it, ctx.mod) }
|
||||
}.toByteArray().toString(Charsets.ISO_8859_1)))
|
||||
}
|
||||
ctx.cls.visibleAnnotations = listOf(
|
||||
AnnotationNode(WasmModule::class.ref.asmDesc).also { it.values = annotationVals }
|
||||
)
|
||||
}
|
||||
|
||||
companion object : AstToAsm()
|
||||
}
|
@ -16,6 +16,7 @@ data class ClsContext(
|
||||
val mod: Node.Module,
|
||||
val cls: ClassNode = ClassNode().also { it.name = (packageName.replace('.', '/') + "/$className").trimStart('/') },
|
||||
val mem: Mem = ByteBufferMem,
|
||||
val modName: String? = null,
|
||||
val reworker: InsnReworker = InsnReworker,
|
||||
val logger: Logger = Logger.Print(Logger.Level.OFF),
|
||||
val funcBuilder: FuncBuilder = FuncBuilder,
|
||||
@ -26,7 +27,8 @@ data class ClsContext(
|
||||
val preventMemIndexOverflow: Boolean = false,
|
||||
val accurateNanBits: Boolean = true,
|
||||
val checkSignedDivIntegerOverflow: Boolean = true,
|
||||
val jumpTableChunkSize: Int = 5000
|
||||
val jumpTableChunkSize: Int = 5000,
|
||||
val includeBinary: Boolean = false
|
||||
) : Logger by logger {
|
||||
val importFuncs: List<Node.Import> by lazy { mod.imports.filter { it.kind is Node.Import.Kind.Func } }
|
||||
val importGlobals: List<Node.Import> by lazy { mod.imports.filter { it.kind is Node.Import.Kind.Global } }
|
||||
|
244
compiler/src/main/kotlin/asmble/compile/jvm/Linker.kt
Normal file
244
compiler/src/main/kotlin/asmble/compile/jvm/Linker.kt
Normal file
@ -0,0 +1,244 @@
|
||||
package asmble.compile.jvm
|
||||
|
||||
import asmble.annotation.WasmExport
|
||||
import asmble.annotation.WasmExternalKind
|
||||
import asmble.annotation.WasmImport
|
||||
import asmble.annotation.WasmModule
|
||||
import org.objectweb.asm.Handle
|
||||
import org.objectweb.asm.Opcodes
|
||||
import org.objectweb.asm.Type
|
||||
import org.objectweb.asm.tree.*
|
||||
import java.lang.invoke.MethodHandle
|
||||
|
||||
open class Linker {
|
||||
|
||||
fun link(ctx: Context) {
|
||||
// Quick check to prevent duplicate names
|
||||
ctx.classes.groupBy { it.name }.values.forEach {
|
||||
require(it.size == 1) { "Duplicate module name: ${it.first().name}"}
|
||||
}
|
||||
|
||||
// Common items
|
||||
ctx.cls.superName = Object::class.ref.asmName
|
||||
ctx.cls.version = Opcodes.V1_8
|
||||
ctx.cls.access += Opcodes.ACC_PUBLIC
|
||||
addConstructor(ctx)
|
||||
addDefaultMaxMemField(ctx)
|
||||
|
||||
// Go over each module and add its creation and instance methods
|
||||
ctx.classes.forEach {
|
||||
addCreationMethod(ctx, it)
|
||||
addInstanceField(ctx, it)
|
||||
addInstanceMethod(ctx, it)
|
||||
}
|
||||
|
||||
TODO()
|
||||
}
|
||||
|
||||
fun addConstructor(ctx: Context) {
|
||||
// Just the default empty constructor
|
||||
ctx.cls.methods.plusAssign(
|
||||
Func(
|
||||
access = Opcodes.ACC_PUBLIC,
|
||||
name = "<init>",
|
||||
params = emptyList(),
|
||||
ret = Void::class.ref,
|
||||
insns = listOf(
|
||||
VarInsnNode(Opcodes.ALOAD, 0),
|
||||
MethodInsnNode(Opcodes.INVOKESPECIAL, Object::class.ref.asmName, "<init>", "()V", false),
|
||||
InsnNode(Opcodes.RETURN)
|
||||
)
|
||||
).toMethodNode()
|
||||
)
|
||||
}
|
||||
|
||||
fun addDefaultMaxMemField(ctx: Context) {
|
||||
(Int.MAX_VALUE / Mem.PAGE_SIZE).let { maxAllowed ->
|
||||
require(ctx.defaultMaxMemPages <= maxAllowed) {
|
||||
"Page size ${ctx.defaultMaxMemPages} over max allowed $maxAllowed"
|
||||
}
|
||||
}
|
||||
ctx.cls.fields.plusAssign(FieldNode(
|
||||
// Make it volatile since it will be publicly mutable
|
||||
Opcodes.ACC_PUBLIC + Opcodes.ACC_VOLATILE,
|
||||
"defaultMaxMem",
|
||||
"I",
|
||||
null,
|
||||
ctx.defaultMaxMemPages * Mem.PAGE_SIZE
|
||||
))
|
||||
}
|
||||
|
||||
fun addCreationMethod(ctx: Context, mod: ModuleClass) {
|
||||
// The creation method accepts everything needed for import in order of
|
||||
// imports. For creating a mod w/ self-built memory, we use a default max
|
||||
// mem field on the linkage class if there isn't a default already.
|
||||
val params = mod.importClasses(ctx)
|
||||
var func = Func(
|
||||
access = Opcodes.ACC_PROTECTED,
|
||||
name = "create" + mod.name.javaIdent.capitalize(),
|
||||
params = params.map(ModuleClass::ref),
|
||||
ret = mod.ref
|
||||
)
|
||||
// The stack here on our is for building params to constructor...
|
||||
|
||||
// The constructor we'll use is:
|
||||
// * Mem-class based constructor if it's an import
|
||||
// * Max-mem int based constructor if mem is self-built and doesn't have a no-mem-no-max ctr
|
||||
// * Should be only single constructor with imports when there's no mem
|
||||
val memClassCtr = mod.cls.constructors.find { it.parameters.firstOrNull()?.type?.ref == ctx.mem.memType }
|
||||
val constructor = if (memClassCtr == null) mod.cls.constructors.singleOrNull() else {
|
||||
// Use the import annotated one if there
|
||||
if (memClassCtr.parameters.first().isAnnotationPresent(WasmImport::class.java)) memClassCtr else {
|
||||
// If there is a non-int-starting constructor, we want to use that
|
||||
val nonMaxMemCtr = mod.cls.constructors.find {
|
||||
it != memClassCtr && it.parameters.firstOrNull()?.type != Integer.TYPE
|
||||
}
|
||||
if (nonMaxMemCtr != null) nonMaxMemCtr else {
|
||||
// Use the max-mem constructor and put the int on the stack
|
||||
func = func.addInsns(
|
||||
VarInsnNode(Opcodes.ALOAD, 0),
|
||||
FieldInsnNode(Opcodes.GETFIELD, ctx.cls.name, "defaultMaxMem", "I")
|
||||
)
|
||||
mod.cls.constructors.find { it.parameters.firstOrNull()?.type != Integer.TYPE }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (constructor == null) error("Unable to find suitable constructor for ${mod.cls}")
|
||||
// Now just go over the imports and put them on the stack
|
||||
func = constructor.parameters.fold(func) { func, param ->
|
||||
param.getAnnotation(WasmImport::class.java).let { import ->
|
||||
when (import.kind) {
|
||||
// Invoke the mem handle to get the mem
|
||||
// TODO: for imported memory, fail if import.limit < limits.init * page size at runtime
|
||||
// TODO: for imported memory, fail if import.cap > limits.max * page size at runtime
|
||||
WasmExternalKind.MEMORY -> func.addInsns(
|
||||
VarInsnNode(Opcodes.ALOAD, 1 + params.indexOfFirst { it.name == import.module }),
|
||||
ctx.resolveImportHandle(import).let { memGet ->
|
||||
MethodInsnNode(Opcodes.INVOKEVIRTUAL, memGet.owner, memGet.name, memGet.desc, false)
|
||||
}
|
||||
)
|
||||
// Bind the method
|
||||
WasmExternalKind.FUNCTION -> func.addInsns(
|
||||
LdcInsnNode(ctx.resolveImportHandle(import)),
|
||||
VarInsnNode(Opcodes.ALOAD, 1 + params.indexOfFirst { it.name == import.module }),
|
||||
MethodHandle::bindTo.invokeVirtual()
|
||||
)
|
||||
// Bind the getter
|
||||
WasmExternalKind.GLOBAL -> func.addInsns(
|
||||
LdcInsnNode(ctx.resolveImportHandle(import)),
|
||||
VarInsnNode(Opcodes.ALOAD, 1 + params.indexOfFirst { it.name == import.module }),
|
||||
MethodHandle::bindTo.invokeVirtual()
|
||||
)
|
||||
// Invoke to get handle array
|
||||
// TODO: for imported table, fail if import.size < limits.init * page size at runtime
|
||||
// TODO: for imported table, fail if import.size > limits.max * page size at runtime
|
||||
WasmExternalKind.TABLE -> func.addInsns(
|
||||
VarInsnNode(Opcodes.ALOAD, 1 + params.indexOfFirst { it.name == import.module }),
|
||||
ctx.resolveImportHandle(import).let { tblGet ->
|
||||
MethodInsnNode(Opcodes.INVOKEVIRTUAL, tblGet.owner, tblGet.name, tblGet.desc, false)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now with all items on the stack we can instantiate and return
|
||||
func = func.addInsns(
|
||||
TypeInsnNode(Opcodes.NEW, mod.ref.asmName),
|
||||
InsnNode(Opcodes.DUP),
|
||||
MethodInsnNode(
|
||||
Opcodes.INVOKESPECIAL,
|
||||
mod.ref.asmName,
|
||||
"<init>",
|
||||
constructor.ref.asmDesc,
|
||||
false
|
||||
),
|
||||
InsnNode(Opcodes.ARETURN)
|
||||
)
|
||||
ctx.cls.methods.plusAssign(func.toMethodNode())
|
||||
}
|
||||
|
||||
fun addInstanceField(ctx: Context, mod: ModuleClass) {
|
||||
// Simple protected field that is lazily populated (but doesn't need to be volatile)
|
||||
ctx.cls.fields.plusAssign(
|
||||
FieldNode(Opcodes.ACC_PROTECTED, "instance" + mod.name.javaIdent.capitalize(),
|
||||
mod.ref.asmDesc, null, null)
|
||||
)
|
||||
}
|
||||
|
||||
fun addInstanceMethod(ctx: Context, mod: ModuleClass) {
|
||||
// The instance method accepts no parameters. It lazily populates a field by calling the
|
||||
// creation method. The parameters for the creation method are the imports that are
|
||||
// accessed via their instance methods. The entire method is synchronized as that is the
|
||||
// most straightforward way to thread-safely lock the lazy population for now.
|
||||
val params = mod.importClasses(ctx)
|
||||
var func = Func(
|
||||
access = Opcodes.ACC_PUBLIC + Opcodes.ACC_SYNCHRONIZED,
|
||||
name = mod.name.javaIdent,
|
||||
ret = mod.ref
|
||||
)
|
||||
val alreadyThereLabel = LabelNode()
|
||||
func = func.addInsns(
|
||||
VarInsnNode(Opcodes.ALOAD, 0),
|
||||
FieldInsnNode(Opcodes.GETFIELD, ctx.cls.name,
|
||||
"instance" + mod.name.javaIdent.capitalize(), mod.ref.asmDesc),
|
||||
JumpInsnNode(Opcodes.IFNONNULL, alreadyThereLabel),
|
||||
VarInsnNode(Opcodes.ALOAD, 0)
|
||||
)
|
||||
func = params.fold(func) { func, importMod ->
|
||||
func.addInsns(
|
||||
VarInsnNode(Opcodes.ALOAD, 0),
|
||||
MethodInsnNode(Opcodes.INVOKEVIRTUAL, importMod.ref.asmName,
|
||||
importMod.name.javaIdent, importMod.ref.asMethodRetDesc(), false)
|
||||
)
|
||||
}
|
||||
func = func.addInsns(
|
||||
FieldInsnNode(Opcodes.PUTFIELD, ctx.cls.name,
|
||||
"instance" + mod.name.javaIdent.capitalize(), mod.ref.asmDesc),
|
||||
alreadyThereLabel,
|
||||
VarInsnNode(Opcodes.ALOAD, 0),
|
||||
FieldInsnNode(Opcodes.GETFIELD, ctx.cls.name,
|
||||
"instance" + mod.name.javaIdent.capitalize(), mod.ref.asmDesc),
|
||||
InsnNode(Opcodes.ARETURN)
|
||||
)
|
||||
ctx.cls.methods.plusAssign(func)
|
||||
}
|
||||
|
||||
class ModuleClass(val cls: Class<*>, overrideName: String? = null) {
|
||||
val name = overrideName ?:
|
||||
cls.getDeclaredAnnotation(WasmModule::class.java)?.name ?: error("No module name available for class $cls")
|
||||
val ref = TypeRef(Type.getType(cls))
|
||||
|
||||
fun importClasses(ctx: Context): List<ModuleClass> {
|
||||
// Try to find constructor with mem class first, otherwise there should be only one
|
||||
val constructorWithImports = cls.constructors.find {
|
||||
it.parameters.firstOrNull()?.type?.ref == ctx.mem.memType
|
||||
} ?: cls.constructors.singleOrNull() ?: error("Unable to find suitable constructor for $cls")
|
||||
return constructorWithImports.parameters.toList().mapNotNull {
|
||||
it.getAnnotation(WasmImport::class.java)?.module
|
||||
}.distinct().map(ctx::namedModuleClass)
|
||||
}
|
||||
}
|
||||
|
||||
data class Context(
|
||||
val classes: List<ModuleClass>,
|
||||
val className: String,
|
||||
val cls: ClassNode = ClassNode().also { it.name = className.replace('.', '/') },
|
||||
val mem: Mem = ByteBufferMem,
|
||||
val defaultMaxMemPages: Int = 10
|
||||
) {
|
||||
fun namedModuleClass(name: String) = classes.find { it.name == name } ?: error("No module named '$name'")
|
||||
|
||||
fun resolveImportMethod(import: WasmImport) =
|
||||
namedModuleClass(import.module).cls.methods.find { method ->
|
||||
method.getAnnotation(WasmExport::class.java)?.value == import.field &&
|
||||
method.ref.asmDesc == import.desc
|
||||
} ?: error("Unable to find export named '${import.field}' in module '${import.module}'")
|
||||
|
||||
fun resolveImportHandle(import: WasmImport) = resolveImportMethod(import).let { method ->
|
||||
Handle(Opcodes.INVOKEVIRTUAL, method.declaringClass.ref.asmName, method.name, method.ref.asmDesc, false)
|
||||
}
|
||||
}
|
||||
|
||||
companion object : Linker()
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package asmble.run.jvm
|
||||
|
||||
import asmble.annotation.WasmName
|
||||
import asmble.annotation.WasmExport
|
||||
import asmble.annotation.WasmExternalKind
|
||||
import asmble.ast.Node
|
||||
import asmble.compile.jvm.Mem
|
||||
import asmble.compile.jvm.ref
|
||||
@ -8,13 +9,25 @@ import java.lang.invoke.MethodHandle
|
||||
import java.lang.invoke.MethodHandles
|
||||
import java.lang.invoke.MethodType
|
||||
import java.lang.reflect.Constructor
|
||||
import java.lang.reflect.Modifier
|
||||
|
||||
interface Module {
|
||||
fun bindMethod(ctx: ScriptContext, wasmName: String, javaName: String, type: MethodType): MethodHandle?
|
||||
fun bindMethod(
|
||||
ctx: ScriptContext,
|
||||
wasmName: String,
|
||||
wasmKind: WasmExternalKind,
|
||||
javaName: String,
|
||||
type: MethodType
|
||||
): MethodHandle?
|
||||
|
||||
data class Composite(val modules: List<Module>) : Module {
|
||||
override fun bindMethod(ctx: ScriptContext, wasmName: String, javaName: String, type: MethodType) =
|
||||
modules.asSequence().mapNotNull { it.bindMethod(ctx, wasmName, javaName, type) }.singleOrNull()
|
||||
override fun bindMethod(
|
||||
ctx: ScriptContext,
|
||||
wasmName: String,
|
||||
wasmKind: WasmExternalKind,
|
||||
javaName: String,
|
||||
type: MethodType
|
||||
) = modules.asSequence().mapNotNull { it.bindMethod(ctx, wasmName, wasmKind, javaName, type) }.singleOrNull()
|
||||
}
|
||||
|
||||
interface Instance : Module {
|
||||
@ -22,16 +35,22 @@ interface Module {
|
||||
// Guaranteed to be the same instance when there is no error
|
||||
fun instance(ctx: ScriptContext): Any
|
||||
|
||||
override fun bindMethod(ctx: ScriptContext, wasmName: String, javaName: String, type: MethodType) =
|
||||
try {
|
||||
MethodHandles.lookup().bind(instance(ctx), javaName, type)
|
||||
} catch (_: NoSuchMethodException) {
|
||||
// Try any method w/ the proper annotation
|
||||
cls.methods.mapNotNull { method ->
|
||||
if (method.getAnnotation(WasmName::class.java)?.value != wasmName) null
|
||||
else MethodHandles.lookup().unreflect(method).bindTo(instance(ctx)).takeIf { it.type() == type }
|
||||
}.singleOrNull()
|
||||
override fun bindMethod(
|
||||
ctx: ScriptContext,
|
||||
wasmName: String,
|
||||
wasmKind: WasmExternalKind,
|
||||
javaName: String,
|
||||
type: MethodType
|
||||
) = cls.methods.filter {
|
||||
// @WasmExport match or just javaName match
|
||||
Modifier.isPublic(it.modifiers) &&
|
||||
!Modifier.isStatic(it.modifiers) &&
|
||||
it.getDeclaredAnnotation(WasmExport::class.java).let { ann ->
|
||||
if (ann == null) it.name == javaName else ann.value == wasmName && ann.kind == wasmKind
|
||||
}
|
||||
}.mapNotNull {
|
||||
MethodHandles.lookup().unreflect(it).bindTo(instance(ctx)).takeIf { it.type() == type }
|
||||
}.singleOrNull()
|
||||
}
|
||||
|
||||
data class Native(override val cls: Class<*>, val inst: Any) : Instance {
|
||||
|
@ -1,10 +1,10 @@
|
||||
package asmble.run.jvm
|
||||
|
||||
import asmble.annotation.WasmExternalKind
|
||||
import asmble.ast.Node
|
||||
import asmble.ast.Script
|
||||
import asmble.compile.jvm.*
|
||||
import asmble.io.AstToSExpr
|
||||
import asmble.io.Emscripten
|
||||
import asmble.io.SExprToStr
|
||||
import asmble.util.Logger
|
||||
import asmble.util.toRawIntBits
|
||||
@ -12,8 +12,6 @@ import asmble.util.toRawLongBits
|
||||
import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
import org.objectweb.asm.Opcodes
|
||||
import run.jvm.emscripten.Env
|
||||
import java.io.OutputStream
|
||||
import java.io.PrintWriter
|
||||
import java.lang.invoke.MethodHandle
|
||||
import java.lang.invoke.MethodHandles
|
||||
@ -30,7 +28,8 @@ data class ScriptContext(
|
||||
val classLoader: SimpleClassLoader =
|
||||
ScriptContext.SimpleClassLoader(ScriptContext::class.java.classLoader, logger),
|
||||
val exceptionTranslator: ExceptionTranslator = ExceptionTranslator,
|
||||
val defaultMaxMemPages: Int = 1
|
||||
val defaultMaxMemPages: Int = 1,
|
||||
val includeBinaryInCompiledClass: Boolean = false
|
||||
) : Logger by logger {
|
||||
fun withHarnessRegistered(out: PrintWriter = PrintWriter(System.out, true)) =
|
||||
withModuleRegistered("spectest", Module.Native(TestHarness(out)))
|
||||
@ -252,7 +251,8 @@ data class ScriptContext(
|
||||
packageName = packageName,
|
||||
className = className,
|
||||
mod = mod,
|
||||
logger = logger
|
||||
logger = logger,
|
||||
includeBinary = includeBinaryInCompiledClass
|
||||
).let(adjustContext)
|
||||
AstToAsm.fromModule(ctx)
|
||||
return Module.Compiled(mod, classLoader.fromBuiltContext(ctx), name, ctx.mem)
|
||||
@ -262,7 +262,13 @@ data class ScriptContext(
|
||||
// Find a method that matches our expectations
|
||||
val module = registrations[import.module] ?: error("Unable to find module ${import.module}")
|
||||
val javaName = if (getter) "get" + import.field.javaIdent.capitalize() else import.field.javaIdent
|
||||
return module.bindMethod(this, import.field, javaName, methodType) ?:
|
||||
val kind = when (import.kind) {
|
||||
is Node.Import.Kind.Func -> WasmExternalKind.FUNCTION
|
||||
is Node.Import.Kind.Table -> WasmExternalKind.TABLE
|
||||
is Node.Import.Kind.Memory -> WasmExternalKind.MEMORY
|
||||
is Node.Import.Kind.Global -> WasmExternalKind.GLOBAL
|
||||
}
|
||||
return module.bindMethod(this, import.field, kind, javaName, methodType) ?:
|
||||
throw NoSuchMethodException("Cannot find import for ${import.module}::${import.field}")
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package asmble.run.jvm
|
||||
|
||||
import asmble.annotation.WasmName
|
||||
import asmble.annotation.WasmExport
|
||||
import asmble.annotation.WasmExternalKind
|
||||
import asmble.compile.jvm.Mem
|
||||
import java.io.PrintWriter
|
||||
import java.lang.invoke.MethodHandle
|
||||
@ -11,10 +12,10 @@ open class TestHarness(val out: PrintWriter) {
|
||||
|
||||
// WASM is evil, not me:
|
||||
// https://github.com/WebAssembly/spec/blob/6a01dab6d29b7c2b5dfd3bb3879bbd6ab76fd5dc/interpreter/host/import/spectest.ml#L12
|
||||
@get:WasmName("global") val globalInt = 666
|
||||
@get:WasmName("global") val globalLong = 666L
|
||||
@get:WasmName("global") val globalFloat = 666.6f
|
||||
@get:WasmName("global") val globalDouble = 666.6
|
||||
@get:WasmExport("global", kind = WasmExternalKind.GLOBAL) val globalInt = 666
|
||||
@get:WasmExport("global", kind = WasmExternalKind.GLOBAL) val globalLong = 666L
|
||||
@get:WasmExport("global", kind = WasmExternalKind.GLOBAL) val globalFloat = 666.6f
|
||||
@get:WasmExport("global", kind = WasmExternalKind.GLOBAL) val globalDouble = 666.6
|
||||
val table = arrayOfNulls<MethodHandle>(10)
|
||||
val memory = ByteBuffer.
|
||||
allocateDirect(2 * Mem.PAGE_SIZE).
|
||||
|
@ -21,7 +21,7 @@ class SpecTestUnit(name: String, wast: String, expectedOutput: String?) : BaseTe
|
||||
|
||||
override fun warningInsteadOfErrReason(t: Throwable) = when (name) {
|
||||
// NaN bit patterns can be off
|
||||
"float_literals", "float_exprs" ->
|
||||
"float_literals", "float_exprs", "float_misc" ->
|
||||
if (isNanMismatch(t)) "NaN JVM bit patterns can be off" else null
|
||||
// We don't hold table capacity right now
|
||||
// TODO: Figure out how we want to store/retrieve table capacity. Right now
|
||||
|
@ -2,7 +2,10 @@ package asmble.run.jvm
|
||||
|
||||
import asmble.BaseTestUnit
|
||||
import asmble.TestBase
|
||||
import asmble.annotation.WasmModule
|
||||
import asmble.io.AstToBinary
|
||||
import asmble.io.AstToSExpr
|
||||
import asmble.io.ByteWriter
|
||||
import asmble.io.SExprToStr
|
||||
import org.junit.Assume
|
||||
import org.junit.Test
|
||||
@ -36,7 +39,9 @@ abstract class TestRunner<out T : BaseTestUnit>(val unit: T) : TestBase() {
|
||||
packageName = unit.packageName,
|
||||
logger = this,
|
||||
adjustContext = { it.copy(eagerFailLargeMemOffset = false) },
|
||||
defaultMaxMemPages = unit.defaultMaxMemPages
|
||||
defaultMaxMemPages = unit.defaultMaxMemPages,
|
||||
// Include the binary data so we can check it later
|
||||
includeBinaryInCompiledClass = true
|
||||
).withHarnessRegistered(PrintWriter(OutputStreamWriter(out, Charsets.UTF_8), true))
|
||||
|
||||
// If there's a staticBump, we are an emscripten mod and we need to include the env
|
||||
@ -80,5 +85,15 @@ abstract class TestRunner<out T : BaseTestUnit>(val unit: T) : TestBase() {
|
||||
// Sadly, sometimes the expected output is trimmed in Emscripten tests
|
||||
assertEquals(it.trimEnd(), out.toByteArray().toString(Charsets.UTF_8).trimEnd())
|
||||
}
|
||||
|
||||
// Also check the annotations
|
||||
scriptContext.modules.forEach { mod ->
|
||||
val expectedBinaryString = ByteArrayOutputStream().also {
|
||||
ByteWriter.OutputStream(it).also { AstToBinary.fromModule(it, mod.mod) }
|
||||
}.toByteArray().toString(Charsets.ISO_8859_1)
|
||||
val actualBinaryString =
|
||||
mod.cls.getDeclaredAnnotation(WasmModule::class.java)?.binary ?: error("No annotation")
|
||||
assertEquals(expectedBinaryString, actualBinaryString)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package run.jvm.emscripten;
|
||||
|
||||
import asmble.annotation.WasmName;
|
||||
import asmble.annotation.WasmExport;
|
||||
|
||||
public class Common {
|
||||
private final Env env;
|
||||
@ -13,7 +13,7 @@ public class Common {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@WasmName("__assert_fail")
|
||||
@WasmExport("__assert_fail")
|
||||
public void assertFail(int conditionPtr, int filenamePtr, int line, int funcPtr) {
|
||||
throw new AssertionError("Assertion failed: " + env.mem.getCString(conditionPtr) + ", at " +
|
||||
env.mem.getCString(filenamePtr) + ":" + line + ", func " + env.mem.getCString(funcPtr));
|
||||
@ -29,12 +29,12 @@ public class Common {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@WasmName("__cxa_call_unexpected")
|
||||
@WasmExport("__cxa_call_unexpected")
|
||||
public void callUnexpected(int ex) {
|
||||
throw new EmscriptenException("Unexpected: " + ex);
|
||||
}
|
||||
|
||||
@WasmName("__lock")
|
||||
@WasmExport("__lock")
|
||||
public void lock(int arg) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
@ -43,7 +43,7 @@ public class Common {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@WasmName("__unlock")
|
||||
@WasmExport("__unlock")
|
||||
public void unlock(int arg) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
package run.jvm.emscripten;
|
||||
|
||||
import asmble.annotation.WasmName;
|
||||
import asmble.annotation.WasmExport;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@ -23,12 +21,12 @@ public class Syscall {
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
@WasmName("__syscall6")
|
||||
@WasmExport("__syscall6")
|
||||
public int close(int arg0, int arg1) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@WasmName("__syscall54")
|
||||
@WasmExport("__syscall54")
|
||||
public int ioctl(int which, int varargs) {
|
||||
FStream fd = fd(env.getMemory().getInt(varargs));
|
||||
IoctlOp op = IoctlOp.byNumber.get(env.getMemory().getInt(varargs + 4));
|
||||
@ -52,12 +50,12 @@ public class Syscall {
|
||||
}
|
||||
}
|
||||
|
||||
@WasmName("__syscall140")
|
||||
@WasmExport("__syscall140")
|
||||
public int llseek(int arg0, int arg1) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@WasmName("__syscall146")
|
||||
@WasmExport("__syscall146")
|
||||
public int writev(int which, int varargs) {
|
||||
FStream fd = fd(env.getMemory().getInt(varargs));
|
||||
int iov = env.getMemory().getInt(varargs + 4);
|
||||
|
Loading…
x
Reference in New Issue
Block a user