mirror of
https://github.com/fluencelabs/asmble
synced 2025-07-04 17:01:34 +00:00
Beginning of emscripten emulation
This commit is contained in:
@ -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
|
||||||
|
111
src/main/kotlin/asmble/run/jvm/emscripten/Env.kt
Normal file
111
src/main/kotlin/asmble/run/jvm/emscripten/Env.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
3
src/main/kotlin/asmble/run/jvm/emscripten/Err.kt
Normal file
3
src/main/kotlin/asmble/run/jvm/emscripten/Err.kt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package asmble.run.jvm.emscripten
|
||||||
|
|
||||||
|
open class Err(message: String, cause: Throwable? = null) : Exception(message, cause)
|
142
src/main/kotlin/asmble/run/jvm/emscripten/Errno.kt
Normal file
142
src/main/kotlin/asmble/run/jvm/emscripten/Errno.kt
Normal 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)
|
||||||
|
}
|
15
src/main/kotlin/asmble/run/jvm/emscripten/Stream.kt
Normal file
15
src/main/kotlin/asmble/run/jvm/emscripten/Stream.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
src/main/kotlin/asmble/run/jvm/emscripten/Tty.kt
Normal file
7
src/main/kotlin/asmble/run/jvm/emscripten/Tty.kt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package asmble.run.jvm.emscripten
|
||||||
|
|
||||||
|
sealed class Tty {
|
||||||
|
class OutputStream(val os: java.io.OutputStream) : Tty() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
5
src/main/kotlin/asmble/util/ByteBufferExt.kt
Normal file
5
src/main/kotlin/asmble/util/ByteBufferExt.kt
Normal 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)
|
@ -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 {
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
Hello, world!
|
@ -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))
|
|
||||||
)
|
|
||||||
)
|
|
20755
src/test/resources/local-spec/hello.wast
Normal file
20755
src/test/resources/local-spec/hello.wast
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user