mirror of
https://github.com/fluencelabs/asmble
synced 2025-07-04 00:41:34 +00:00
Initial work to support emscripten runtime for issue #7
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@ -5,3 +5,5 @@
|
|||||||
/build
|
/build
|
||||||
/gradle
|
/gradle
|
||||||
/compiler/build
|
/compiler/build
|
||||||
|
/emscripten-runtime/build
|
||||||
|
/annotations/build
|
||||||
|
10
annotations/src/main/java/asmble/annotation/WasmName.java
Normal file
10
annotations/src/main/java/asmble/annotation/WasmName.java
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package asmble.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
public @interface WasmName {
|
||||||
|
String value();
|
||||||
|
}
|
@ -21,6 +21,12 @@ allprojects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
project(':emscripten-runtime') {
|
||||||
|
dependencies {
|
||||||
|
compile project(':annotations')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
project(':compiler') {
|
project(':compiler') {
|
||||||
apply plugin: 'kotlin'
|
apply plugin: 'kotlin'
|
||||||
apply plugin: 'application'
|
apply plugin: 'application'
|
||||||
@ -32,6 +38,8 @@ project(':compiler') {
|
|||||||
distZip.archiveName = 'asmble.zip'
|
distZip.archiveName = 'asmble.zip'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
compile project(':emscripten-runtime')
|
||||||
|
|
||||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
compile "org.ow2.asm:asm-tree:$asm_version"
|
compile "org.ow2.asm:asm-tree:$asm_version"
|
||||||
|
21
compiler/src/main/kotlin/asmble/io/Emscripten.kt
Normal file
21
compiler/src/main/kotlin/asmble/io/Emscripten.kt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package asmble.io
|
||||||
|
|
||||||
|
open class Emscripten {
|
||||||
|
|
||||||
|
fun metadataFromWast(wast: String) =
|
||||||
|
wast.lastIndexOf(";; METADATA:").takeIf { it != -1 }?.let { metaIndex ->
|
||||||
|
wast.indexOfAny(listOf("\n", "\"staticBump\": "), metaIndex).
|
||||||
|
takeIf { it != -1 && wast[it] != '\n' }?.
|
||||||
|
let { bumpIndex ->
|
||||||
|
wast.indexOfAny(charArrayOf('\n', ','), bumpIndex).takeIf { it != -1 }?.let { commaIndex ->
|
||||||
|
wast.substring(bumpIndex + 14, commaIndex).trim().toIntOrNull()?.let { staticBump ->
|
||||||
|
Metadata(staticBump)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Metadata(val staticBump: Int)
|
||||||
|
|
||||||
|
companion object : Emscripten()
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
package asmble.run.jvm
|
package asmble.run.jvm
|
||||||
|
|
||||||
|
import asmble.annotation.WasmName
|
||||||
import asmble.ast.Node
|
import asmble.ast.Node
|
||||||
import asmble.compile.jvm.Mem
|
import asmble.compile.jvm.Mem
|
||||||
import asmble.compile.jvm.ref
|
import asmble.compile.jvm.ref
|
||||||
import asmble.run.jvm.annotation.WasmName
|
|
||||||
import java.lang.invoke.MethodHandle
|
import java.lang.invoke.MethodHandle
|
||||||
import java.lang.invoke.MethodHandles
|
import java.lang.invoke.MethodHandles
|
||||||
import java.lang.invoke.MethodType
|
import java.lang.invoke.MethodType
|
||||||
|
@ -4,6 +4,7 @@ import asmble.ast.Node
|
|||||||
import asmble.ast.Script
|
import asmble.ast.Script
|
||||||
import asmble.compile.jvm.*
|
import asmble.compile.jvm.*
|
||||||
import asmble.io.AstToSExpr
|
import asmble.io.AstToSExpr
|
||||||
|
import asmble.io.Emscripten
|
||||||
import asmble.io.SExprToStr
|
import asmble.io.SExprToStr
|
||||||
import asmble.util.Logger
|
import asmble.util.Logger
|
||||||
import asmble.util.toRawIntBits
|
import asmble.util.toRawIntBits
|
||||||
@ -11,6 +12,8 @@ import asmble.util.toRawLongBits
|
|||||||
import org.objectweb.asm.ClassReader
|
import org.objectweb.asm.ClassReader
|
||||||
import org.objectweb.asm.ClassVisitor
|
import org.objectweb.asm.ClassVisitor
|
||||||
import org.objectweb.asm.Opcodes
|
import org.objectweb.asm.Opcodes
|
||||||
|
import run.jvm.emscripten.Env
|
||||||
|
import java.io.OutputStream
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.lang.invoke.MethodHandle
|
import java.lang.invoke.MethodHandle
|
||||||
import java.lang.invoke.MethodHandles
|
import java.lang.invoke.MethodHandles
|
||||||
@ -32,6 +35,14 @@ data class ScriptContext(
|
|||||||
fun withHarnessRegistered(out: PrintWriter = PrintWriter(System.out, true)) =
|
fun withHarnessRegistered(out: PrintWriter = PrintWriter(System.out, true)) =
|
||||||
withModuleRegistered("spectest", Module.Native(TestHarness(out)))
|
withModuleRegistered("spectest", Module.Native(TestHarness(out)))
|
||||||
|
|
||||||
|
fun withEmscriptenRegistered(metadata: Emscripten.Metadata, out: OutputStream) =
|
||||||
|
Env(metadata.staticBump, out).let { env ->
|
||||||
|
val mods = Env.subModules.fold(listOf(Module.Native(env))) { mods, subMod ->
|
||||||
|
mods + Module.Native(subMod.apply(env))
|
||||||
|
}
|
||||||
|
withModuleRegistered("env", Module.Composite(mods))
|
||||||
|
}
|
||||||
|
|
||||||
fun withModuleRegistered(name: String, mod: Module) = copy(registrations = registrations + (name to mod))
|
fun withModuleRegistered(name: String, mod: Module) = copy(registrations = registrations + (name to mod))
|
||||||
|
|
||||||
fun runCommand(cmd: Script.Cmd) = when (cmd) {
|
fun runCommand(cmd: Script.Cmd) = when (cmd) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package asmble.run.jvm
|
package asmble.run.jvm
|
||||||
|
|
||||||
|
import asmble.annotation.WasmName
|
||||||
import asmble.compile.jvm.Mem
|
import asmble.compile.jvm.Mem
|
||||||
import asmble.run.jvm.annotation.WasmName
|
|
||||||
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
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
package asmble.run.jvm.annotation
|
|
||||||
|
|
||||||
@Target(AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.FUNCTION)
|
|
||||||
annotation class WasmName(val value: String)
|
|
@ -1,67 +0,0 @@
|
|||||||
package asmble.run.jvm.emscripten
|
|
||||||
|
|
||||||
import asmble.compile.jvm.Mem
|
|
||||||
import asmble.run.jvm.Module
|
|
||||||
import asmble.run.jvm.annotation.WasmName
|
|
||||||
import asmble.util.Logger
|
|
||||||
import java.io.ByteArrayOutputStream
|
|
||||||
import java.io.OutputStream
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.ByteOrder
|
|
||||||
|
|
||||||
class Env(
|
|
||||||
val logger: Logger,
|
|
||||||
val staticBump: Int,
|
|
||||||
val out: OutputStream
|
|
||||||
) : Logger by logger {
|
|
||||||
fun alignTo16(num: Int) = Math.ceil(num / 16.0).toInt() * 16
|
|
||||||
|
|
||||||
val memory = ByteBuffer.allocateDirect(256 * Mem.PAGE_SIZE).order(ByteOrder.LITTLE_ENDIAN)
|
|
||||||
|
|
||||||
init {
|
|
||||||
// Emscripten sets where "stack top" can start in mem at position 1024.
|
|
||||||
// See https://github.com/WebAssembly/binaryen/issues/979
|
|
||||||
val stackBase = alignTo16(staticBump + 1024 + 16)
|
|
||||||
val stackTop = stackBase + TOTAL_STACK
|
|
||||||
memory.putInt(1024, stackTop)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun readCStringBytes(ptr: Int) = ByteArrayOutputStream().let { bos ->
|
|
||||||
var ptr = ptr
|
|
||||||
while (true) {
|
|
||||||
val byte = memory.get(ptr++)
|
|
||||||
if (byte == 0.toByte()) break
|
|
||||||
bos.write(byte.toInt())
|
|
||||||
}
|
|
||||||
bos.toByteArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun readCString(ptr: Int) = readCStringBytes(ptr).toString(Charsets.ISO_8859_1)
|
|
||||||
|
|
||||||
fun abort() { TODO() }
|
|
||||||
|
|
||||||
@WasmName("__lock")
|
|
||||||
fun lock(arg: Int) { TODO() }
|
|
||||||
|
|
||||||
fun sbrk(increment: Int): Int { TODO() }
|
|
||||||
|
|
||||||
@WasmName("__unlock")
|
|
||||||
fun unlock(arg: Int) { TODO() }
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TOTAL_STACK = 5242880
|
|
||||||
const val TOTAL_MEMORY = 16777216
|
|
||||||
|
|
||||||
val subModules = listOf(::Stdio, ::Syscall)
|
|
||||||
|
|
||||||
fun module(
|
|
||||||
logger: Logger,
|
|
||||||
staticBump: Int,
|
|
||||||
out: OutputStream
|
|
||||||
): Module = Env(logger, staticBump, out).let { env ->
|
|
||||||
Module.Composite(subModules.fold(listOf(Module.Native(env))) { list, subMod ->
|
|
||||||
list + Module.Native(subMod(env))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
package asmble.run.jvm.emscripten
|
|
||||||
|
|
||||||
open class Err(message: String, cause: Throwable? = null) : Exception(message, cause)
|
|
@ -1,28 +0,0 @@
|
|||||||
package asmble.run.jvm.emscripten
|
|
||||||
|
|
||||||
class Stdio(val env: Env) {
|
|
||||||
|
|
||||||
fun printf(format: Int, argStart: Int) = format(format, argStart).let { formatted ->
|
|
||||||
env.out.write(formatted.toByteArray(Charsets.ISO_8859_1))
|
|
||||||
formatted.length
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun format(format: Int, argStart: Int): String {
|
|
||||||
// TODO: the rest of this. We should actually take musl, compile it to the JVM,
|
|
||||||
// and then go from there. Not musl.wast which has some imports of its own.
|
|
||||||
val str = env.readCString(format)
|
|
||||||
// Only support %s for now...
|
|
||||||
val strReplacementIndices = str.foldIndexed(emptyList<Int>()) { index, indices, char ->
|
|
||||||
if (char != '%') indices
|
|
||||||
else if (str.getOrNull(index + 1) != 's') error("Only '%s' supported for now")
|
|
||||||
else indices + index
|
|
||||||
}
|
|
||||||
val strs = strReplacementIndices.indices.map { index ->
|
|
||||||
env.readCString(env.memory.getInt(argStart + (index * 4)))
|
|
||||||
}
|
|
||||||
// Replace reversed
|
|
||||||
return strReplacementIndices.zip(strs).asReversed().fold(str) { str, (index, toPlace) ->
|
|
||||||
str.substring(0, index) + toPlace + str.substring(index + 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package asmble.run.jvm.emscripten
|
|
||||||
|
|
||||||
sealed class Stream {
|
|
||||||
open val tty: Tty? = null
|
|
||||||
|
|
||||||
abstract fun write(bytes: ByteArray)
|
|
||||||
|
|
||||||
class OutputStream(val os: java.io.OutputStream) : Stream() {
|
|
||||||
override val tty by lazy { Tty.OutputStream(os) }
|
|
||||||
|
|
||||||
override fun write(bytes: ByteArray) {
|
|
||||||
os.write(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
package asmble.run.jvm.emscripten
|
|
||||||
|
|
||||||
import asmble.run.jvm.annotation.WasmName
|
|
||||||
import asmble.util.get
|
|
||||||
|
|
||||||
class Syscall(val env: Env) {
|
|
||||||
var fds: Map<Int, Stream> = mapOf(
|
|
||||||
1 to Stream.OutputStream(env.out)
|
|
||||||
)
|
|
||||||
|
|
||||||
@WasmName("__syscall6")
|
|
||||||
fun close(arg0: Int, arg1: Int): Int { TODO() }
|
|
||||||
|
|
||||||
@WasmName("__syscall54")
|
|
||||||
fun ioctl(which: Int, varargs: Int): Int {
|
|
||||||
val fd = fd(env.memory.getInt(varargs))
|
|
||||||
val op = env.memory.getInt(varargs + 4)
|
|
||||||
return when (IoctlOp[op]) {
|
|
||||||
IoctlOp.TCGETS, IoctlOp.TCSETS, IoctlOp.TIOCGWINSZ ->
|
|
||||||
if (fd.tty == null) -Errno.ENOTTY.number else 0
|
|
||||||
IoctlOp.TIOCGPGRP ->
|
|
||||||
if (fd.tty == null) -Errno.ENOTTY.number else {
|
|
||||||
env.memory.putInt(env.memory.getInt(varargs + 8), 0)
|
|
||||||
0
|
|
||||||
}
|
|
||||||
IoctlOp.TIOCSPGRP ->
|
|
||||||
if (fd.tty == null) -Errno.ENOTTY.number else -Errno.EINVAL.number
|
|
||||||
IoctlOp.FIONREAD ->
|
|
||||||
if (fd.tty == null) -Errno.ENOTTY.number else TODO("ioctl FIONREAD")
|
|
||||||
null ->
|
|
||||||
error("Unrecognized op: $op")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@WasmName("__syscall140")
|
|
||||||
fun llseek(arg0: Int, arg1: Int): Int { TODO() }
|
|
||||||
|
|
||||||
@WasmName("__syscall146")
|
|
||||||
fun writev(which: Int, varargs: Int): Int {
|
|
||||||
val fd = fd(env.memory.getInt(varargs))
|
|
||||||
val iov = env.memory.getInt(varargs + 4)
|
|
||||||
val iovcnt = env.memory.getInt(varargs + 8)
|
|
||||||
return (0 until iovcnt).fold(0) { total, i ->
|
|
||||||
val ptr = env.memory.getInt(iov + (i * 8))
|
|
||||||
val len = env.memory.getInt(iov + (i * 8) + 4)
|
|
||||||
if (len > 0) {
|
|
||||||
fd.write(try {
|
|
||||||
ByteArray(len).also { env.memory.get(ptr, it) }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// TODO: set errno?
|
|
||||||
return -1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
total + len
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fd(v: Int) = fds[v] ?: Errno.EBADF.raise()
|
|
||||||
|
|
||||||
enum class IoctlOp(val number: Int) {
|
|
||||||
TCGETS(0x5401),
|
|
||||||
TCSETS(0x5402),
|
|
||||||
TIOCGPGRP(0x540F),
|
|
||||||
TIOCSPGRP(0x5410),
|
|
||||||
FIONREAD(0x541B),
|
|
||||||
TIOCGWINSZ(0x5413);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val byNumber = IoctlOp.values().associateBy { it.number }
|
|
||||||
operator fun get(number: Int) = byNumber[number]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package asmble.run.jvm.emscripten
|
|
||||||
|
|
||||||
sealed class Tty {
|
|
||||||
class OutputStream(val os: java.io.OutputStream) : Tty() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
28
compiler/src/test/kotlin/asmble/BaseTestUnit.kt
Normal file
28
compiler/src/test/kotlin/asmble/BaseTestUnit.kt
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package asmble
|
||||||
|
|
||||||
|
import asmble.ast.SExpr
|
||||||
|
import asmble.ast.Script
|
||||||
|
import asmble.io.Emscripten
|
||||||
|
import asmble.io.SExprToAst
|
||||||
|
import asmble.io.StrToSExpr
|
||||||
|
|
||||||
|
open class BaseTestUnit(val name: String, val wast: String, val expectedOutput: String?) {
|
||||||
|
override fun toString() = "Test unit: $name"
|
||||||
|
|
||||||
|
open val packageName = "asmble.temp." + name.replace('/', '.')
|
||||||
|
open val shouldFail get() = false
|
||||||
|
open val skipRunReason: String? get() = null
|
||||||
|
open val defaultMaxMemPages get() = 1
|
||||||
|
open val emscriptenMetadata by lazy { Emscripten.metadataFromWast(wast) }
|
||||||
|
open val parseResult: StrToSExpr.ParseResult.Success by lazy {
|
||||||
|
StrToSExpr.parse(wast).let {
|
||||||
|
when (it) {
|
||||||
|
is StrToSExpr.ParseResult.Error -> throw Exception("$name[${it.pos}] Parse fail: ${it.msg}")
|
||||||
|
is StrToSExpr.ParseResult.Success -> it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open val ast: List<SExpr> get() = parseResult.vals
|
||||||
|
open val script: Script by lazy { SExprToAst.toScript(SExpr.Multi(ast)) }
|
||||||
|
open fun warningInsteadOfErrReason(t: Throwable): String? = null
|
||||||
|
}
|
@ -1,45 +1,25 @@
|
|||||||
package asmble
|
package asmble
|
||||||
|
|
||||||
import asmble.ast.Node
|
import asmble.ast.Node
|
||||||
import asmble.ast.SExpr
|
|
||||||
import asmble.ast.Script
|
import asmble.ast.Script
|
||||||
import asmble.io.SExprToAst
|
|
||||||
import asmble.io.StrToSExpr
|
|
||||||
import asmble.run.jvm.ScriptAssertionError
|
import asmble.run.jvm.ScriptAssertionError
|
||||||
import java.nio.file.FileSystems
|
import java.nio.file.FileSystems
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.util.stream.Collectors
|
import java.util.stream.Collectors
|
||||||
|
|
||||||
class SpecTestUnit(val name: String, val wast: String, val expectedOutput: String?) {
|
class SpecTestUnit(name: String, wast: String, expectedOutput: String?) : BaseTestUnit(name, wast, expectedOutput) {
|
||||||
|
|
||||||
override fun toString() = "Spec unit: $name"
|
override val shouldFail get() = name.endsWith(".fail")
|
||||||
|
|
||||||
val shouldFail get() = name.endsWith(".fail")
|
override val defaultMaxMemPages get() = when (name) {
|
||||||
|
|
||||||
val skipRunReason: String? get() = null
|
|
||||||
|
|
||||||
val defaultMaxMemPages get() = when (name) {
|
|
||||||
"nop"-> 20
|
"nop"-> 20
|
||||||
"resizing" -> 830
|
"resizing" -> 830
|
||||||
"imports" -> 5
|
"imports" -> 5
|
||||||
else -> 1
|
else -> 1
|
||||||
}
|
}
|
||||||
|
|
||||||
val emscriptenStaticBump by lazy {
|
override fun warningInsteadOfErrReason(t: Throwable) = when (name) {
|
||||||
// I am not about to pull in a JSON parser just for this
|
|
||||||
wast.lastIndexOf(";; METADATA:").takeIf { it != -1 }?.let { metaIndex ->
|
|
||||||
wast.indexOfAny(listOf("\n", "\"staticBump\": "), metaIndex).
|
|
||||||
takeIf { it != -1 && wast[it] != '\n' }?.
|
|
||||||
let { bumpIndex ->
|
|
||||||
wast.indexOfAny(charArrayOf('\n', ','), bumpIndex).takeIf { it != -1 }?.let { commaIndex ->
|
|
||||||
wast.substring(bumpIndex + 14, commaIndex).trim().toIntOrNull()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun warningInsteadOfErrReason(t: Throwable) = when (name) {
|
|
||||||
// NaN bit patterns can be off
|
// NaN bit patterns can be off
|
||||||
"float_literals", "float_exprs" ->
|
"float_literals", "float_exprs" ->
|
||||||
if (isNanMismatch(t)) "NaN JVM bit patterns can be off" else null
|
if (isNanMismatch(t)) "NaN JVM bit patterns can be off" else null
|
||||||
@ -74,19 +54,6 @@ class SpecTestUnit(val name: String, val wast: String, val expectedOutput: Strin
|
|||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
val parseResult: StrToSExpr.ParseResult.Success by lazy {
|
|
||||||
StrToSExpr.parse(wast).let {
|
|
||||||
when (it) {
|
|
||||||
is StrToSExpr.ParseResult.Error -> throw Exception("$name[${it.pos}] Parse fail: ${it.msg}")
|
|
||||||
is StrToSExpr.ParseResult.Success -> it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val ast: List<SExpr> get() = parseResult.vals
|
|
||||||
|
|
||||||
val script: Script by lazy { SExprToAst.toScript(SExpr.Multi(ast)) }
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val unitsPath = "/spec/test/core"
|
val unitsPath = "/spec/test/core"
|
||||||
|
|
||||||
|
9
compiler/src/test/kotlin/asmble/TestBase.kt
Normal file
9
compiler/src/test/kotlin/asmble/TestBase.kt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package asmble
|
||||||
|
|
||||||
|
import asmble.util.Logger
|
||||||
|
|
||||||
|
abstract class TestBase : Logger by TestBase.logger {
|
||||||
|
companion object {
|
||||||
|
val logger = Logger.Print(Logger.Level.INFO)
|
||||||
|
}
|
||||||
|
}
|
@ -1,76 +1,11 @@
|
|||||||
package asmble.run.jvm
|
package asmble.run.jvm
|
||||||
|
|
||||||
import asmble.SpecTestUnit
|
import asmble.SpecTestUnit
|
||||||
import asmble.io.AstToSExpr
|
|
||||||
import asmble.io.SExprToStr
|
|
||||||
import asmble.run.jvm.emscripten.Env
|
|
||||||
import asmble.util.Logger
|
|
||||||
import org.junit.Assume
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.Parameterized
|
import org.junit.runners.Parameterized
|
||||||
import java.io.ByteArrayOutputStream
|
|
||||||
import java.io.OutputStreamWriter
|
|
||||||
import java.io.PrintWriter
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertNotNull
|
|
||||||
|
|
||||||
@RunWith(Parameterized::class)
|
@RunWith(Parameterized::class)
|
||||||
class RunTest(val unit: SpecTestUnit) : Logger by Logger.Print(Logger.Level.INFO) {
|
class RunTest(unit: SpecTestUnit) : TestRunner<SpecTestUnit>(unit) {
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testRun() {
|
|
||||||
unit.skipRunReason?.let { Assume.assumeTrue("Skipping ${unit.name}, reason: $it", false) }
|
|
||||||
|
|
||||||
val ex = try { run(); null } catch (e: Throwable) { e }
|
|
||||||
if (unit.shouldFail) {
|
|
||||||
assertNotNull(ex, "Expected failure, but succeeded")
|
|
||||||
debug { "Got expected failure: $ex" }
|
|
||||||
} else if (ex != null) throw ex
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun run() {
|
|
||||||
debug { "AST SExpr: " + unit.ast }
|
|
||||||
debug { "AST Str: " + SExprToStr.fromSExpr(*unit.ast.toTypedArray()) }
|
|
||||||
debug { "AST: " + unit.script }
|
|
||||||
debug { "AST Str: " + SExprToStr.fromSExpr(*AstToSExpr.fromScript(unit.script).toTypedArray()) }
|
|
||||||
|
|
||||||
val out = ByteArrayOutputStream()
|
|
||||||
var scriptContext = ScriptContext(
|
|
||||||
packageName = "asmble.temp.${unit.name}",
|
|
||||||
logger = this,
|
|
||||||
adjustContext = { it.copy(eagerFailLargeMemOffset = false) },
|
|
||||||
defaultMaxMemPages = unit.defaultMaxMemPages
|
|
||||||
).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
|
|
||||||
unit.emscriptenStaticBump?.also { staticBump ->
|
|
||||||
scriptContext = scriptContext.withModuleRegistered("env", Env.module(this, staticBump, out))
|
|
||||||
}
|
|
||||||
|
|
||||||
// This will fail assertions as necessary
|
|
||||||
scriptContext = unit.script.commands.fold(scriptContext) { scriptContext, cmd ->
|
|
||||||
try {
|
|
||||||
scriptContext.runCommand(cmd)
|
|
||||||
} catch (t: Throwable) {
|
|
||||||
val warningReason = unit.warningInsteadOfErrReason(t) ?: throw t
|
|
||||||
warn { "Unexpected error on ${unit.name}, but is a warning. Reason: $warningReason. Orig err: $t" }
|
|
||||||
scriptContext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is a main, we run it w/ no args because emscripten doesn't set it as the start func
|
|
||||||
scriptContext.modules.lastOrNull()?.also { lastMod ->
|
|
||||||
lastMod.cls.methods.find {
|
|
||||||
it.name == "main" &&
|
|
||||||
it.returnType == Int::class.java &&
|
|
||||||
it.parameterTypes.asList() == listOf(Int::class.java, Int::class.java)
|
|
||||||
}?.invoke(lastMod.instance(scriptContext), 0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
unit.expectedOutput?.let { assertEquals(it, out.toByteArray().toString(Charsets.UTF_8)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic @Parameterized.Parameters(name = "{0}")
|
@JvmStatic @Parameterized.Parameters(name = "{0}")
|
||||||
fun data() = SpecTestUnit.allUnits
|
fun data() = SpecTestUnit.allUnits
|
||||||
|
72
compiler/src/test/kotlin/asmble/run/jvm/TestRunner.kt
Normal file
72
compiler/src/test/kotlin/asmble/run/jvm/TestRunner.kt
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package asmble.run.jvm
|
||||||
|
|
||||||
|
import asmble.BaseTestUnit
|
||||||
|
import asmble.TestBase
|
||||||
|
import asmble.io.AstToSExpr
|
||||||
|
import asmble.io.SExprToStr
|
||||||
|
import org.junit.Assume
|
||||||
|
import org.junit.Test
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.OutputStreamWriter
|
||||||
|
import java.io.PrintWriter
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
|
||||||
|
abstract class TestRunner<out T : BaseTestUnit>(val unit: T) : TestBase() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test() {
|
||||||
|
unit.skipRunReason?.let { Assume.assumeTrue("Skipping ${unit.name}, reason: $it", false) }
|
||||||
|
val ex = try { run(); null } catch (e: Throwable) { e }
|
||||||
|
if (unit.shouldFail) {
|
||||||
|
assertNotNull(ex, "Expected failure, but succeeded")
|
||||||
|
debug { "Got expected failure: $ex" }
|
||||||
|
} else if (ex != null) throw ex
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun run() {
|
||||||
|
debug { "AST SExpr: " + unit.ast }
|
||||||
|
debug { "AST Str: " + SExprToStr.fromSExpr(*unit.ast.toTypedArray()) }
|
||||||
|
debug { "AST: " + unit.script }
|
||||||
|
debug { "AST Str: " + SExprToStr.fromSExpr(*AstToSExpr.fromScript(unit.script).toTypedArray()) }
|
||||||
|
|
||||||
|
val out = ByteArrayOutputStream()
|
||||||
|
var scriptContext = ScriptContext(
|
||||||
|
packageName = unit.packageName,
|
||||||
|
logger = this,
|
||||||
|
adjustContext = { it.copy(eagerFailLargeMemOffset = false) },
|
||||||
|
defaultMaxMemPages = unit.defaultMaxMemPages
|
||||||
|
).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
|
||||||
|
unit.emscriptenMetadata?.also { scriptContext = scriptContext.withEmscriptenRegistered(it, out) }
|
||||||
|
|
||||||
|
// This will fail assertions as necessary
|
||||||
|
scriptContext = unit.script.commands.fold(scriptContext) { scriptContext, cmd ->
|
||||||
|
try {
|
||||||
|
scriptContext.runCommand(cmd)
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
val warningReason = unit.warningInsteadOfErrReason(t) ?: throw t
|
||||||
|
warn { "Unexpected error on ${unit.name}, but is a warning. Reason: $warningReason. Orig err: $t" }
|
||||||
|
scriptContext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is a main, we run it w/ no args because emscripten doesn't set it as the start func
|
||||||
|
scriptContext.modules.lastOrNull()?.also { lastMod ->
|
||||||
|
lastMod.cls.methods.find { it.name == "main" && it.returnType == Int::class.java }?.let { mainMethod ->
|
||||||
|
if (mainMethod.parameterTypes.isEmpty())
|
||||||
|
mainMethod.invoke(lastMod.instance(scriptContext))
|
||||||
|
else if (mainMethod.parameterTypes.asList() == listOf(Int::class.java, Int::class.java))
|
||||||
|
mainMethod.invoke(lastMod.instance(scriptContext), 0, 0)
|
||||||
|
else
|
||||||
|
error("Unrecognized main method params for $mainMethod")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unit.expectedOutput?.let {
|
||||||
|
// Sadly, sometimes the expected output is trimmed in Emscripten tests
|
||||||
|
assertEquals(it.trimEnd(), out.toByteArray().toString(Charsets.UTF_8).trimEnd())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package asmble.run.jvm.emscripten
|
||||||
|
|
||||||
|
import asmble.run.jvm.TestRunner
|
||||||
|
import org.junit.BeforeClass
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.Parameterized
|
||||||
|
|
||||||
|
@RunWith(Parameterized::class)
|
||||||
|
class EmscriptenTest(unit: EmscriptenTestUnit) : TestRunner<EmscriptenTestUnit>(unit) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
var failureReason: Throwable? = null
|
||||||
|
|
||||||
|
@JvmStatic @BeforeClass
|
||||||
|
fun setup() { failureReason?.also { throw it } }
|
||||||
|
|
||||||
|
@JvmStatic @Parameterized.Parameters(name = "{0}")
|
||||||
|
fun data() = try { EmscriptenTestUnit.allUnits } catch (t: Throwable) { failureReason = t; listOf(null) }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
package asmble.run.jvm.emscripten
|
||||||
|
|
||||||
|
import asmble.BaseTestUnit
|
||||||
|
import asmble.TestBase
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.AssumptionViolatedException
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import java.util.stream.Collectors
|
||||||
|
|
||||||
|
class EmscriptenTestUnit(
|
||||||
|
name: String,
|
||||||
|
wast: String,
|
||||||
|
expectedOutput: String
|
||||||
|
) : BaseTestUnit(name, wast, expectedOutput) {
|
||||||
|
companion object {
|
||||||
|
val knownGoodNames = listOf(
|
||||||
|
"core/test_addr_of_stacked"
|
||||||
|
)
|
||||||
|
|
||||||
|
val allUnits by lazy { loadUnits() }
|
||||||
|
|
||||||
|
fun loadUnits(): List<EmscriptenTestUnit> {
|
||||||
|
val wasmInstall = Paths.get(
|
||||||
|
System.getenv("WASM_INSTALL") ?: throw AssumptionViolatedException("WASM_INSTALL not set")
|
||||||
|
)
|
||||||
|
val testBase = wasmInstall.resolve("emscripten/tests")
|
||||||
|
fun Path.unitNameFromCFile() =
|
||||||
|
testBase.relativize(this).toString().substringBeforeLast(".c").replace('\\', '/')
|
||||||
|
// Obtain C files we know work
|
||||||
|
val goodCFiles = Files.walk(testBase).
|
||||||
|
filter { knownGoodNames.contains(it.unitNameFromCFile()) }.
|
||||||
|
collect(Collectors.toList())
|
||||||
|
// Go over each one and create a test unit
|
||||||
|
val tempDir = createTempDir("emscriptenout")
|
||||||
|
val isWindows = System.getProperty("os.name").contains("windows", true)
|
||||||
|
val emccCommand =
|
||||||
|
if (isWindows) arrayOf("cmd", "/c", wasmInstall.resolve("emscripten/emcc.bat").toString())
|
||||||
|
else arrayOf(wasmInstall.resolve("emscripten/emcc").toString())
|
||||||
|
try {
|
||||||
|
return goodCFiles.map { cFile ->
|
||||||
|
try {
|
||||||
|
// Run emcc on the cFile
|
||||||
|
val nameSansExt = cFile.fileName.toString().substringBeforeLast(".c")
|
||||||
|
val cmdArgs = emccCommand + cFile.toString() + "-s" + "WASM=1" + "-o" + "$nameSansExt.html"
|
||||||
|
TestBase.logger.debug { "Running ${cmdArgs.joinToString(" ")}" }
|
||||||
|
val proc = ProcessBuilder(*cmdArgs).
|
||||||
|
directory(tempDir).
|
||||||
|
redirectErrorStream(true).
|
||||||
|
also { it.environment() += "BINARYEN" to wasmInstall.toString() }.
|
||||||
|
start()
|
||||||
|
proc.inputStream.bufferedReader().forEachLine { TestBase.logger.debug { "[OUT] $it" } }
|
||||||
|
Assert.assertTrue("Timeout", proc.waitFor(10, TimeUnit.SECONDS))
|
||||||
|
Assert.assertEquals(0, proc.exitValue())
|
||||||
|
EmscriptenTestUnit(
|
||||||
|
name = cFile.unitNameFromCFile(),
|
||||||
|
wast = File(tempDir, "$nameSansExt.wast").readText(),
|
||||||
|
expectedOutput = cFile.resolveSibling("$nameSansExt.out").toFile().readText()
|
||||||
|
)
|
||||||
|
} catch (e: Exception) { throw Exception("Unable to compile $cFile", e) }
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
try { tempDir.deleteRecursively() }
|
||||||
|
catch (e: Exception) { TestBase.logger.warn { "Unable to delete temp dir: $e" } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1 +0,0 @@
|
|||||||
Hello, world!
|
|
@ -1,4 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
int main(int argc, char ** argv) {
|
|
||||||
printf("%s, %s!\n", "Hello", "world");
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
(module
|
|
||||||
(type $FUNCSIG$ii (func (param i32) (result i32)))
|
|
||||||
(type $FUNCSIG$iii (func (param i32 i32) (result i32)))
|
|
||||||
(import "env" "printf" (func $printf (param i32 i32) (result i32)))
|
|
||||||
(import "env" "memory" (memory $0 256))
|
|
||||||
(table 0 anyfunc)
|
|
||||||
(data (i32.const 1040) "%s, %s!\n\00")
|
|
||||||
(data (i32.const 1056) "Hello\00")
|
|
||||||
(data (i32.const 1072) "world\00")
|
|
||||||
(export "main" (func $main))
|
|
||||||
(func $main (param $0 i32) (param $1 i32) (result i32)
|
|
||||||
(local $2 i32)
|
|
||||||
(i32.store offset=1024
|
|
||||||
(i32.const 0)
|
|
||||||
(tee_local $2
|
|
||||||
(i32.sub
|
|
||||||
(i32.load offset=1024
|
|
||||||
(i32.const 0)
|
|
||||||
)
|
|
||||||
(i32.const 16)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(i32.store offset=12
|
|
||||||
(get_local $2)
|
|
||||||
(get_local $0)
|
|
||||||
)
|
|
||||||
(i32.store offset=8
|
|
||||||
(get_local $2)
|
|
||||||
(get_local $1)
|
|
||||||
)
|
|
||||||
(i32.store offset=4
|
|
||||||
(get_local $2)
|
|
||||||
(i32.const 1072)
|
|
||||||
)
|
|
||||||
(i32.store
|
|
||||||
(get_local $2)
|
|
||||||
(i32.const 1056)
|
|
||||||
)
|
|
||||||
(drop
|
|
||||||
(call $printf
|
|
||||||
(i32.const 1040)
|
|
||||||
(get_local $2)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(i32.store offset=1024
|
|
||||||
(i32.const 0)
|
|
||||||
(i32.add
|
|
||||||
(get_local $2)
|
|
||||||
(i32.const 16)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(i32.const 0)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
;; METADATA: { "asmConsts": {},"staticBump": 54, "initializers": [] }
|
|
@ -0,0 +1,19 @@
|
|||||||
|
package run.jvm.emscripten;
|
||||||
|
|
||||||
|
public class EmscriptenException extends RuntimeException {
|
||||||
|
|
||||||
|
public EmscriptenException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmscriptenException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmscriptenException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmscriptenException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
72
emscripten-runtime/src/main/java/run/jvm/emscripten/Env.java
Normal file
72
emscripten-runtime/src/main/java/run/jvm/emscripten/Env.java
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package run.jvm.emscripten;
|
||||||
|
|
||||||
|
import asmble.annotation.WasmName;
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class Env {
|
||||||
|
public static final int TOTAL_STACK = 5242880;
|
||||||
|
public static final int TOTAL_MEMORY = 16777216;
|
||||||
|
|
||||||
|
public static final List<Function<Env, Object>> subModules = Arrays.asList(
|
||||||
|
Syscall::new
|
||||||
|
);
|
||||||
|
|
||||||
|
private static int alignTo16(int num) {
|
||||||
|
return ((int) Math.ceil(num / 16.0)) * 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ByteBuffer memory;
|
||||||
|
private final int staticBump;
|
||||||
|
final OutputStream out;
|
||||||
|
|
||||||
|
public Env(int staticBump, OutputStream out) {
|
||||||
|
this(ByteBuffer.allocateDirect(TOTAL_MEMORY), staticBump, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Env(ByteBuffer memory, int staticBump, OutputStream out) {
|
||||||
|
this.memory = memory.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
this.staticBump = staticBump;
|
||||||
|
this.out = out;
|
||||||
|
// Emscripten sets where "stack top" can start in mem at position 1024.
|
||||||
|
// See https://github.com/WebAssembly/binaryen/issues/979
|
||||||
|
int stackBase = alignTo16(staticBump + 1024 + 16);
|
||||||
|
int stackTop = stackBase + TOTAL_STACK;
|
||||||
|
memory.putInt(1024, stackTop);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteBuffer getMemory() {
|
||||||
|
return memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getMemoryBulk(int index, int len) {
|
||||||
|
byte[] ret = new byte[len];
|
||||||
|
ByteBuffer dup = memory.duplicate();
|
||||||
|
dup.position(index);
|
||||||
|
dup.get(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void abort() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@WasmName("__lock")
|
||||||
|
public void lock(int arg) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int sbrk(int increment) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@WasmName("__unlock")
|
||||||
|
public void unlock(int arg) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package asmble.run.jvm.emscripten
|
package run.jvm.emscripten;
|
||||||
|
|
||||||
enum class Errno(val number: Int) {
|
public enum Errno {
|
||||||
EPERM(1),
|
EPERM(1),
|
||||||
ENOENT(2),
|
ENOENT(2),
|
||||||
ESRCH(3),
|
ESRCH(3),
|
||||||
@ -136,7 +136,30 @@ enum class Errno(val number: Int) {
|
|||||||
ERFKILL(132),
|
ERFKILL(132),
|
||||||
EHWPOISON(133);
|
EHWPOISON(133);
|
||||||
|
|
||||||
fun raise(cause: Throwable? = null): Nothing = throw Err(this, cause)
|
final int number;
|
||||||
|
|
||||||
class Err(errno: Errno, cause: Throwable? = null) : asmble.run.jvm.emscripten.Err("Errno: $errno", cause)
|
Errno(int number) {
|
||||||
|
this.number = number;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void raise() {
|
||||||
|
raise(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void raise(Throwable cause) {
|
||||||
|
throw new ErrnoException(this, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ErrnoException extends EmscriptenException {
|
||||||
|
public final Errno errno;
|
||||||
|
|
||||||
|
public ErrnoException(Errno errno) {
|
||||||
|
this(errno, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ErrnoException(Errno errno, Throwable cause) {
|
||||||
|
super("Errno: " + errno, cause);
|
||||||
|
this.errno = errno;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package run.jvm.emscripten;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public abstract class FStream {
|
||||||
|
public abstract Tty getTty();
|
||||||
|
|
||||||
|
public abstract void write(byte[] bytes);
|
||||||
|
|
||||||
|
public static class OutputStream extends FStream {
|
||||||
|
private final Tty.OutputStream tty;
|
||||||
|
|
||||||
|
public OutputStream(java.io.OutputStream out) {
|
||||||
|
this.tty = new Tty.OutputStream(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Tty.OutputStream getTty() {
|
||||||
|
return tty;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] bytes) {
|
||||||
|
try {
|
||||||
|
tty.out.write(bytes);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new EmscriptenException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
101
emscripten-runtime/src/main/java/run/jvm/emscripten/Syscall.java
Normal file
101
emscripten-runtime/src/main/java/run/jvm/emscripten/Syscall.java
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package run.jvm.emscripten;
|
||||||
|
|
||||||
|
import asmble.annotation.WasmName;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class Syscall {
|
||||||
|
|
||||||
|
private final Map<Integer, FStream> fds;
|
||||||
|
private final Env env;
|
||||||
|
|
||||||
|
public Syscall(Env env) {
|
||||||
|
this.fds = new HashMap<>();
|
||||||
|
fds.put(1, new FStream.OutputStream(env.out));
|
||||||
|
this.env = env;
|
||||||
|
}
|
||||||
|
|
||||||
|
@WasmName("__syscall6")
|
||||||
|
public int close(int arg0, int arg1) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@WasmName("__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));
|
||||||
|
Objects.requireNonNull(op);
|
||||||
|
switch (op) {
|
||||||
|
case TCGETS:
|
||||||
|
case TCSETS:
|
||||||
|
case TIOCGWINSZ:
|
||||||
|
return fd.getTty() == null ? -Errno.ENOTTY.number : 0;
|
||||||
|
case TIOCGPGRP:
|
||||||
|
if (fd.getTty() == null) return -Errno.ENOTTY.number;
|
||||||
|
env.getMemory().putInt(env.getMemory().getInt(varargs + 8), 0);
|
||||||
|
return 0;
|
||||||
|
case TIOCSPGRP:
|
||||||
|
return fd.getTty() == null ? -Errno.ENOTTY.number : -Errno.EINVAL.number;
|
||||||
|
case FIONREAD:
|
||||||
|
if (fd.getTty() == null) return -Errno.ENOTTY.number;
|
||||||
|
throw new UnsupportedOperationException("TODO");
|
||||||
|
default:
|
||||||
|
throw new EmscriptenException("Unrecognized op: " + op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@WasmName("__syscall140")
|
||||||
|
public int llseek(int arg0, int arg1) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
//
|
||||||
|
@WasmName("__syscall146")
|
||||||
|
public int writev(int which, int varargs) {
|
||||||
|
FStream fd = fd(env.getMemory().getInt(varargs));
|
||||||
|
int iov = env.getMemory().getInt(varargs + 4);
|
||||||
|
int iovcnt = env.getMemory().getInt(varargs + 8);
|
||||||
|
return IntStream.range(0, iovcnt).reduce(0, (total, i) -> {
|
||||||
|
int ptr = env.getMemory().getInt(iov + (i * 8));
|
||||||
|
int len = env.getMemory().getInt(iov + (i * 8) + 4);
|
||||||
|
if (len > 0) fd.write(env.getMemoryBulk(ptr, len));
|
||||||
|
return total + len;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private FStream fd(int v) {
|
||||||
|
FStream ret = fds.get(v);
|
||||||
|
if (ret == null) Errno.EBADF.raise();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static enum IoctlOp {
|
||||||
|
TCGETS(0x5401),
|
||||||
|
TCSETS(0x5402),
|
||||||
|
TIOCGPGRP(0x540F),
|
||||||
|
TIOCSPGRP(0x5410),
|
||||||
|
FIONREAD(0x541B),
|
||||||
|
TIOCGWINSZ(0x5413);
|
||||||
|
|
||||||
|
static final Map<Integer, IoctlOp> byNumber;
|
||||||
|
|
||||||
|
static {
|
||||||
|
byNumber = Stream.of(values()).collect(Collectors.toMap(IoctlOp::getNumber, Function.identity()));
|
||||||
|
}
|
||||||
|
|
||||||
|
final int number;
|
||||||
|
|
||||||
|
IoctlOp(int number) {
|
||||||
|
this.number = number;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNumber() {
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
emscripten-runtime/src/main/java/run/jvm/emscripten/Tty.java
Normal file
12
emscripten-runtime/src/main/java/run/jvm/emscripten/Tty.java
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package run.jvm.emscripten;
|
||||||
|
|
||||||
|
public abstract class Tty {
|
||||||
|
|
||||||
|
public static class OutputStream extends Tty {
|
||||||
|
final java.io.OutputStream out;
|
||||||
|
|
||||||
|
public OutputStream(java.io.OutputStream out) {
|
||||||
|
this.out = out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,2 +1,2 @@
|
|||||||
rootProject.name = 'asmble'
|
rootProject.name = 'asmble'
|
||||||
include 'compiler'
|
include 'annotations', 'compiler', 'emscripten-runtime'
|
Reference in New Issue
Block a user