Initial work to support emscripten runtime for issue #7

This commit is contained in:
Chad Retz
2017-04-26 15:35:34 -05:00
parent d94b5ce898
commit b4140c8189
31 changed files with 523 additions and 369 deletions

View 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()
}

View File

@ -1,9 +1,9 @@
package asmble.run.jvm
import asmble.annotation.WasmName
import asmble.ast.Node
import asmble.compile.jvm.Mem
import asmble.compile.jvm.ref
import asmble.run.jvm.annotation.WasmName
import java.lang.invoke.MethodHandle
import java.lang.invoke.MethodHandles
import java.lang.invoke.MethodType

View File

@ -4,6 +4,7 @@ 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
@ -11,6 +12,8 @@ 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
@ -32,6 +35,14 @@ data class ScriptContext(
fun withHarnessRegistered(out: PrintWriter = PrintWriter(System.out, true)) =
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 runCommand(cmd: Script.Cmd) = when (cmd) {

View File

@ -1,7 +1,7 @@
package asmble.run.jvm
import asmble.annotation.WasmName
import asmble.compile.jvm.Mem
import asmble.run.jvm.annotation.WasmName
import java.io.PrintWriter
import java.lang.invoke.MethodHandle
import java.nio.ByteBuffer

View File

@ -1,4 +0,0 @@
package asmble.run.jvm.annotation
@Target(AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.FUNCTION)
annotation class WasmName(val value: String)

View File

@ -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))
})
}
}
}

View File

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

View File

@ -1,142 +0,0 @@
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

@ -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)
}
}
}

View File

@ -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)
}
}
}

View File

@ -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]
}
}
}

View File

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

View 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
}

View File

@ -1,45 +1,25 @@
package asmble
import asmble.ast.Node
import asmble.ast.SExpr
import asmble.ast.Script
import asmble.io.SExprToAst
import asmble.io.StrToSExpr
import asmble.run.jvm.ScriptAssertionError
import java.nio.file.FileSystems
import java.nio.file.Files
import java.nio.file.Paths
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")
val skipRunReason: String? get() = null
val defaultMaxMemPages get() = when (name) {
override val defaultMaxMemPages get() = when (name) {
"nop"-> 20
"resizing" -> 830
"imports" -> 5
else -> 1
}
val emscriptenStaticBump by lazy {
// 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) {
override fun warningInsteadOfErrReason(t: Throwable) = when (name) {
// NaN bit patterns can be off
"float_literals", "float_exprs" ->
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
}
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 {
val unitsPath = "/spec/test/core"

View 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)
}
}

View File

@ -1,76 +1,11 @@
package asmble.run.jvm
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.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)
class RunTest(val unit: SpecTestUnit) : Logger by Logger.Print(Logger.Level.INFO) {
@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)) }
}
class RunTest(unit: SpecTestUnit) : TestRunner<SpecTestUnit>(unit) {
companion object {
@JvmStatic @Parameterized.Parameters(name = "{0}")
fun data() = SpecTestUnit.allUnits

View 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())
}
}
}

View File

@ -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) }
}
}

View File

@ -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" } }
}
}
}
}

View File

@ -1,4 +0,0 @@
#include <stdio.h>
int main(int argc, char ** argv) {
printf("%s, %s!\n", "Hello", "world");
}

View File

@ -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": [] }