mirror of
https://github.com/fluencelabs/asmble
synced 2025-07-03 00:11:36 +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.SExprToStr
|
||||
import asmble.run.jvm.annotation.WasmName
|
||||
import asmble.run.jvm.emscripten.Env
|
||||
import asmble.util.Logger
|
||||
import asmble.util.toRawIntBits
|
||||
import asmble.util.toRawLongBits
|
||||
import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
import org.objectweb.asm.Opcodes
|
||||
import java.io.OutputStream
|
||||
import java.io.PrintWriter
|
||||
import java.lang.invoke.MethodHandle
|
||||
import java.lang.invoke.MethodHandles
|
||||
@ -36,6 +38,9 @@ data class ScriptContext(
|
||||
"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) {
|
||||
is Script.Cmd.Module ->
|
||||
// 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.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.OutputStreamWriter
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import kotlin.test.assertEquals
|
||||
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 Str: " + SExprToStr.fromSExpr(*AstToSExpr.fromScript(unit.script).toTypedArray()) }
|
||||
|
||||
val out = StringWriter()
|
||||
val scriptContext = ScriptContext(
|
||||
val out = ByteArrayOutputStream()
|
||||
var scriptContext = ScriptContext(
|
||||
packageName = "asmble.temp.${unit.name}",
|
||||
logger = this,
|
||||
adjustContext = { it.copy(eagerFailLargeMemOffset = false) },
|
||||
defaultMaxMemPages = unit.defaultMaxMemPages
|
||||
).withHarnessRegistered(PrintWriter(out))
|
||||
).withHarnessRegistered(PrintWriter(OutputStreamWriter(out, Charsets.UTF_8), true)).
|
||||
withEmscriptenEnvRegistered(out)
|
||||
|
||||
// This will fail assertions as necessary
|
||||
unit.script.commands.fold(scriptContext) { scriptContext, cmd ->
|
||||
scriptContext = unit.script.commands.fold(scriptContext) { scriptContext, cmd ->
|
||||
try {
|
||||
scriptContext.runCommand(cmd)
|
||||
} 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 {
|
||||
|
@ -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