Beginning of emscripten emulation

This commit is contained in:
Chad Retz
2017-04-20 23:35:02 -05:00
parent da1d94dc9e
commit 5890a1cd7c
12 changed files with 21061 additions and 14 deletions

View File

@ -6,12 +6,14 @@ import asmble.compile.jvm.*
import asmble.io.AstToSExpr import asmble.io.AstToSExpr
import asmble.io.SExprToStr import asmble.io.SExprToStr
import asmble.run.jvm.annotation.WasmName import asmble.run.jvm.annotation.WasmName
import asmble.run.jvm.emscripten.Env
import asmble.util.Logger import asmble.util.Logger
import asmble.util.toRawIntBits import asmble.util.toRawIntBits
import asmble.util.toRawLongBits 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 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
@ -36,6 +38,9 @@ data class ScriptContext(
"spectest" to NativeModule(TestHarness::class.java, TestHarness(out)) "spectest" to NativeModule(TestHarness::class.java, TestHarness(out))
)) ))
fun withEmscriptenEnvRegistered(out: OutputStream = System.out) =
copy(registrations = registrations + ("env" to NativeModule(Env::class.java, Env(logger, out))))
fun runCommand(cmd: Script.Cmd) = when (cmd) { fun runCommand(cmd: Script.Cmd) = when (cmd) {
is Script.Cmd.Module -> is Script.Cmd.Module ->
// We ask for the module instance because some things are built on <init> expectation // We ask for the module instance because some things are built on <init> expectation

View File

@ -0,0 +1,111 @@
package asmble.run.jvm.emscripten
import asmble.compile.jvm.Mem
import asmble.run.jvm.annotation.WasmName
import asmble.util.Logger
import asmble.util.get
import java.io.OutputStream
import java.nio.ByteBuffer
import java.nio.ByteOrder
class Env(
val logger: Logger,
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)
var fds: Map<Int, Stream> = mapOf(
1 to Stream.OutputStream(out)
)
init {
// Emscripten sets where "stack top" can start in mem at position 1024.
// TODO: Waiting for https://github.com/WebAssembly/binaryen/issues/979
val staticBump = 4044
val stackBase = alignTo16(staticBump + 1024 + 16)
val stackTop = stackBase + TOTAL_STACK
// We have to set some values like Emscripten
memory.putInt(1024, stackTop)
}
fun abort() { TODO() }
@WasmName("__syscall6")
fun close(arg0: Int, arg1: Int): Int { TODO() }
@WasmName("__syscall54")
fun ioctl(which: Int, varargs: Int): Int {
val fd = fd(memory.getInt(varargs))
val op = 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 {
memory.putInt(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("__lock")
fun lock(arg: Int) { TODO() }
fun sbrk(increment: Int): Int { TODO() }
@WasmName("__unlock")
fun unlock(arg: Int) { TODO() }
@WasmName("__syscall146")
fun writev(which: Int, varargs: Int): Int {
val fd = fd(memory.getInt(varargs))
val iov = memory.getInt(varargs + 4)
val iovcnt = memory.getInt(varargs + 8)
return (0 until iovcnt).fold(0) { total, i ->
val ptr = memory.getInt(iov + (i * 8))
val len = memory.getInt(iov + (i * 8) + 4)
if (len > 0) {
fd.write(try {
ByteArray(len).also { 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]
}
}
companion object {
const val TOTAL_STACK = 5242880
const val TOTAL_MEMORY = 16777216
const val GLOBAL_BASE = 1024
}
}

View File

@ -0,0 +1,3 @@
package asmble.run.jvm.emscripten
open class Err(message: String, cause: Throwable? = null) : Exception(message, cause)

View File

@ -0,0 +1,142 @@
package asmble.run.jvm.emscripten
enum class Errno(val number: Int) {
EPERM(1),
ENOENT(2),
ESRCH(3),
EINTR(4),
EIO(5),
ENXIO(6),
E2BIG(7),
ENOEXEC(8),
EBADF(9),
ECHILD(10),
EAGAIN(11),
ENOMEM(12),
EACCES(13),
EFAULT(14),
ENOTBLK(15),
EBUSY(16),
EEXIST(17),
EXDEV(18),
ENODEV(19),
ENOTDIR(20),
EISDIR(21),
EINVAL(22),
ENFILE(23),
EMFILE(24),
ENOTTY(25),
ETXTBSY(26),
EFBIG(27),
ENOSPC(28),
ESPIPE(29),
EROFS(30),
EMLINK(31),
EPIPE(32),
EDOM(33),
ERANGE(34),
EDEADLK(35),
ENAMETOOLONG(36),
ENOLCK(37),
ENOSYS(38),
ENOTEMPTY(39),
ELOOP(40),
EWOULDBLOCK(EAGAIN.number),
ENOMSG(42),
EIDRM(43),
ECHRNG(44),
EL2NSYNC(45),
EL3HLT(46),
EL3RST(47),
ELNRNG(48),
EUNATCH(49),
ENOCSI(50),
EL2HLT(51),
EBADE(52),
EBADR(53),
EXFULL(54),
ENOANO(55),
EBADRQC(56),
EBADSLT(57),
EDEADLOCK(EDEADLK.number),
EBFONT(59),
ENOSTR(60),
ENODATA(61),
ETIME(62),
ENOSR(63),
ENONET(64),
ENOPKG(65),
EREMOTE(66),
ENOLINK(67),
EADV(68),
ESRMNT(69),
ECOMM(70),
EPROTO(71),
EMULTIHOP(72),
EDOTDOT(73),
EBADMSG(74),
EOVERFLOW(75),
ENOTUNIQ(76),
EBADFD(77),
EREMCHG(78),
ELIBACC(79),
ELIBBAD(80),
ELIBSCN(81),
ELIBMAX(82),
ELIBEXEC(83),
EILSEQ(84),
ERESTART(85),
ESTRPIPE(86),
EUSERS(87),
ENOTSOCK(88),
EDESTADDRREQ(89),
EMSGSIZE(90),
EPROTOTYPE(91),
ENOPROTOOPT(92),
EPROTONOSUPPORT(93),
ESOCKTNOSUPPORT(94),
EOPNOTSUPP(95),
ENOTSUP(EOPNOTSUPP.number),
EPFNOSUPPORT(96),
EAFNOSUPPORT(97),
EADDRINUSE(98),
EADDRNOTAVAIL(99),
ENETDOWN(100),
ENETUNREACH(101),
ENETRESET(102),
ECONNABORTED(103),
ECONNRESET(104),
ENOBUFS(105),
EISCONN(106),
ENOTCONN(107),
ESHUTDOWN(108),
ETOOMANYREFS(109),
ETIMEDOUT(110),
ECONNREFUSED(111),
EHOSTDOWN(112),
EHOSTUNREACH(113),
EALREADY(114),
EINPROGRESS(115),
ESTALE(116),
EUCLEAN(117),
ENOTNAM(118),
ENAVAIL(119),
EISNAM(120),
EREMOTEIO(121),
EDQUOT(122),
ENOMEDIUM(123),
EMEDIUMTYPE(124),
ECANCELED(125),
ENOKEY(126),
EKEYEXPIRED(127),
EKEYREVOKED(128),
EKEYREJECTED(129),
EOWNERDEAD(130),
ENOTRECOVERABLE(131),
ERFKILL(132),
EHWPOISON(133);
fun raise(cause: Throwable? = null): Nothing = throw Err(this, cause)
class Err(errno: Errno, cause: Throwable? = null) : asmble.run.jvm.emscripten.Err("Errno: $errno", cause)
}

View File

@ -0,0 +1,15 @@
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)
}
}
}

View File

@ -0,0 +1,7 @@
package asmble.run.jvm.emscripten
sealed class Tty {
class OutputStream(val os: java.io.OutputStream) : Tty() {
}
}

View File

@ -0,0 +1,5 @@
package asmble.util
import java.nio.ByteBuffer
fun ByteBuffer.get(index: Int, bytes: ByteArray) = this.duplicate().also { it.position(index) }.get(bytes)

View File

@ -8,8 +8,9 @@ import org.junit.Assume
import org.junit.Test 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 java.io.PrintWriter
import java.io.StringWriter
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
@ -33,16 +34,17 @@ class RunTest(val unit: SpecTestUnit) : Logger by Logger.Print(Logger.Level.INFO
debug { "AST: " + unit.script } debug { "AST: " + unit.script }
debug { "AST Str: " + SExprToStr.fromSExpr(*AstToSExpr.fromScript(unit.script).toTypedArray()) } debug { "AST Str: " + SExprToStr.fromSExpr(*AstToSExpr.fromScript(unit.script).toTypedArray()) }
val out = StringWriter() val out = ByteArrayOutputStream()
val scriptContext = ScriptContext( var scriptContext = ScriptContext(
packageName = "asmble.temp.${unit.name}", packageName = "asmble.temp.${unit.name}",
logger = this, logger = this,
adjustContext = { it.copy(eagerFailLargeMemOffset = false) }, adjustContext = { it.copy(eagerFailLargeMemOffset = false) },
defaultMaxMemPages = unit.defaultMaxMemPages defaultMaxMemPages = unit.defaultMaxMemPages
).withHarnessRegistered(PrintWriter(out)) ).withHarnessRegistered(PrintWriter(OutputStreamWriter(out, Charsets.UTF_8), true)).
withEmscriptenEnvRegistered(out)
// This will fail assertions as necessary // This will fail assertions as necessary
unit.script.commands.fold(scriptContext) { scriptContext, cmd -> scriptContext = unit.script.commands.fold(scriptContext) { scriptContext, cmd ->
try { try {
scriptContext.runCommand(cmd) scriptContext.runCommand(cmd)
} catch (t: Throwable) { } catch (t: Throwable) {
@ -52,7 +54,16 @@ class RunTest(val unit: SpecTestUnit) : Logger by Logger.Print(Logger.Level.INFO
} }
} }
unit.expectedOutput?.let { assertEquals(it, out.toString()) } // 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 {

View File

@ -0,0 +1 @@
Hello, world!

View File

@ -1,8 +0,0 @@
;; TODO: test w/ emscripten compiled code
(module
(import "spectest" "print" (func $print (param i32)))
(func (export "printNum") (param $i i32)
(call $print (get_local $i))
)
)

File diff suppressed because it is too large Load Diff