mirror of
https://github.com/fluencelabs/asmble
synced 2025-04-25 06:42:22 +00:00
Update WASM spec tests and change implementation to conform
This commit is contained in:
parent
cb8470f54f
commit
0c4fb45d79
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,5 +5,8 @@
|
|||||||
/build
|
/build
|
||||||
/gradle
|
/gradle
|
||||||
/compiler/build
|
/compiler/build
|
||||||
|
/compiler/out
|
||||||
/emscripten-runtime/build
|
/emscripten-runtime/build
|
||||||
|
/emscripten-runtime/out
|
||||||
/annotations/build
|
/annotations/build
|
||||||
|
/annotations/out
|
||||||
|
@ -10,6 +10,7 @@ sealed class CompileErr(message: String, cause: Throwable? = null) : RuntimeExce
|
|||||||
val actual: TypeRef?
|
val actual: TypeRef?
|
||||||
) : CompileErr("Expected any type of ${Arrays.toString(expected)}, got $actual") {
|
) : CompileErr("Expected any type of ${Arrays.toString(expected)}, got $actual") {
|
||||||
override val asmErrString get() = "type mismatch"
|
override val asmErrString get() = "type mismatch"
|
||||||
|
override val asmErrStrings get() = listOf(asmErrString, "mismatching label")
|
||||||
}
|
}
|
||||||
|
|
||||||
class StackInjectionMismatch(
|
class StackInjectionMismatch(
|
||||||
@ -88,6 +89,7 @@ sealed class CompileErr(message: String, cause: Throwable? = null) : RuntimeExce
|
|||||||
|
|
||||||
class UnknownTable(val index: Int) : CompileErr("No table present at index $index") {
|
class UnknownTable(val index: Int) : CompileErr("No table present at index $index") {
|
||||||
override val asmErrString get() = "unknown table"
|
override val asmErrString get() = "unknown table"
|
||||||
|
override val asmErrStrings get() = listOf(asmErrString, "unknown table $index")
|
||||||
}
|
}
|
||||||
|
|
||||||
class UnknownType(val index: Int) : CompileErr("No type present for index $index") {
|
class UnknownType(val index: Int) : CompileErr("No type present for index $index") {
|
||||||
|
@ -494,8 +494,12 @@ open class FuncBuilder {
|
|||||||
// Must at least have the item on the stack that the block expects if it expects something
|
// Must at least have the item on the stack that the block expects if it expects something
|
||||||
val needsPopBeforeJump = needsToPopBeforeJumping(ctx, fn, block)
|
val needsPopBeforeJump = needsToPopBeforeJumping(ctx, fn, block)
|
||||||
val toLabel = if (needsPopBeforeJump) LabelNode() else block.requiredLabel
|
val toLabel = if (needsPopBeforeJump) LabelNode() else block.requiredLabel
|
||||||
fn.addInsns(JumpInsnNode(Opcodes.IFNE, toLabel)).let { fn ->
|
fn.addInsns(JumpInsnNode(Opcodes.IFNE, toLabel)).let { origFn ->
|
||||||
block.endTypes.firstOrNull()?.let { fn.peekExpecting(it) }
|
val fn = block.endTypes.firstOrNull()?.let { endType ->
|
||||||
|
// We have to pop the stack and re-push to get the right type after unreachable here...
|
||||||
|
// Ref: https://github.com/WebAssembly/spec/pull/537
|
||||||
|
origFn.popExpecting(endType).push(endType)
|
||||||
|
} ?: origFn
|
||||||
if (needsPopBeforeJump) buildPopBeforeJump(ctx, fn, block, toLabel)
|
if (needsPopBeforeJump) buildPopBeforeJump(ctx, fn, block, toLabel)
|
||||||
else fn
|
else fn
|
||||||
}
|
}
|
||||||
|
@ -101,9 +101,10 @@ open class AstToSExpr(val parensInstrs: Boolean = true) {
|
|||||||
fun fromImportMemory(v: Node.Import.Kind.Memory, name: String? = null) =
|
fun fromImportMemory(v: Node.Import.Kind.Memory, name: String? = null) =
|
||||||
newMulti("memory", name) + fromMemorySig(v.type)
|
newMulti("memory", name) + fromMemorySig(v.type)
|
||||||
|
|
||||||
fun fromImportOrExport(v: ImportOrExport) =
|
fun fromImportOrExport(v: ImportOrExport) = when (v) {
|
||||||
if (v.importModule == null) newMulti("export") + v.field
|
is ImportOrExport.Import -> listOf((newMulti("import") + v.module) + v.name)
|
||||||
else (newMulti("import") + v.importModule) + v.field
|
is ImportOrExport.Export -> v.fields.map { newMulti("export") + it }
|
||||||
|
}
|
||||||
|
|
||||||
fun fromImportTable(v: Node.Import.Kind.Table, name: String? = null) =
|
fun fromImportTable(v: Node.Import.Kind.Table, name: String? = null) =
|
||||||
newMulti("table", name) + fromTableSig(v.type)
|
newMulti("table", name) + fromTableSig(v.type)
|
||||||
@ -232,8 +233,8 @@ open class AstToSExpr(val parensInstrs: Boolean = true) {
|
|||||||
if (exp == null) this else this.copy(vals = this.vals + fromString(exp))
|
if (exp == null) this else this.copy(vals = this.vals + fromString(exp))
|
||||||
private operator fun SExpr.Multi.plus(exp: SExpr?) =
|
private operator fun SExpr.Multi.plus(exp: SExpr?) =
|
||||||
if (exp == null) this else this.copy(vals = this.vals + exp)
|
if (exp == null) this else this.copy(vals = this.vals + exp)
|
||||||
private operator fun SExpr.Multi.plus(exps: List<SExpr>) =
|
private operator fun SExpr.Multi.plus(exps: List<SExpr>?) =
|
||||||
if (exps.isEmpty()) this else this.copy(vals = this.vals + exps)
|
if (exps == null || exps.isEmpty()) this else this.copy(vals = this.vals + exps)
|
||||||
private fun newMulti(initSymb: String? = null, initName: String? = null): SExpr.Multi {
|
private fun newMulti(initSymb: String? = null, initName: String? = null): SExpr.Multi {
|
||||||
initName?.also { require(it.startsWith("$")) }
|
initName?.also { require(it.startsWith("$")) }
|
||||||
return SExpr.Multi() + initSymb + initName
|
return SExpr.Multi() + initSymb + initName
|
||||||
|
@ -1,3 +1,18 @@
|
|||||||
package asmble.io
|
package asmble.io
|
||||||
|
|
||||||
data class ImportOrExport(val field: String, val importModule: String?)
|
/*
|
||||||
|
data class ImportOrExport(val field: String, val importModule: String?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
sealed class ImportOrExport {
|
||||||
|
abstract val itemCount: Int
|
||||||
|
|
||||||
|
data class Import(val name: String, val module: String, val exportFields: List<String>) : ImportOrExport() {
|
||||||
|
override val itemCount get() = 1 + exportFields.size
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Export(val fields: List<String>) : ImportOrExport() {
|
||||||
|
override val itemCount get() = fields.size
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package asmble.io
|
package asmble.io
|
||||||
|
|
||||||
import asmble.AsmErr
|
import asmble.AsmErr
|
||||||
|
import java.math.BigInteger
|
||||||
|
|
||||||
sealed class IoErr(message: String, cause: Throwable? = null) : RuntimeException(message, cause), AsmErr {
|
sealed class IoErr(message: String, cause: Throwable? = null) : RuntimeException(message, cause), AsmErr {
|
||||||
class UnexpectedEnd : IoErr("Unexpected EOF") {
|
class UnexpectedEnd : IoErr("Unexpected EOF") {
|
||||||
@ -44,7 +45,11 @@ sealed class IoErr(message: String, cause: Throwable? = null) : RuntimeException
|
|||||||
override val asmErrString get() = "memory size must be at most 65536 pages (4GiB)"
|
override val asmErrString get() = "memory size must be at most 65536 pages (4GiB)"
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvalidAlign(val align: Int, val allowed: Int) : IoErr("Alignment $align larger than $allowed") {
|
class InvalidAlignPower(val align: Int) : IoErr("Alignment expected to be positive power of 2, but got $align") {
|
||||||
|
override val asmErrString get() = "alignment must be positive power of 2"
|
||||||
|
}
|
||||||
|
|
||||||
|
class InvalidAlignTooLarge(val align: Int, val allowed: Int) : IoErr("Alignment $align larger than $allowed") {
|
||||||
override val asmErrString get() = "alignment must not be larger than natural"
|
override val asmErrString get() = "alignment must not be larger than natural"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,4 +60,48 @@ sealed class IoErr(message: String, cause: Throwable? = null) : RuntimeException
|
|||||||
class UnknownType(val index: Int) : IoErr("No type present for index $index") {
|
class UnknownType(val index: Int) : IoErr("No type present for index $index") {
|
||||||
override val asmErrString get() = "unknown type"
|
override val asmErrString get() = "unknown type"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class InvalidType(val str: String) : IoErr("Invalid type: $str") {
|
||||||
|
override val asmErrString get() = "unexpected token"
|
||||||
|
}
|
||||||
|
|
||||||
|
class MismatchLabelEnd(val expected: String?, val actual: String) :
|
||||||
|
IoErr("Expected end for $expected, got $actual") {
|
||||||
|
override val asmErrString get() = "mismatching label"
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConstantOutOfRange(val actual: Number) : IoErr("Constant out of range: $actual") {
|
||||||
|
override val asmErrString get() = "constant out of range"
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConstantUnknownOperator(val str: String) : IoErr("Unknown constant operator for: $str") {
|
||||||
|
override val asmErrString get() = "unknown operator"
|
||||||
|
}
|
||||||
|
|
||||||
|
class FuncTypeRefMismatch : IoErr("Func type for type ref doesn't match explicit params/returns") {
|
||||||
|
override val asmErrString get() = "inline function type"
|
||||||
|
override val asmErrStrings get() = listOf(asmErrString, "unexpected token")
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnrecognizedInstruction(val instr: String) : IoErr("Unrecognized instruction: $instr") {
|
||||||
|
override val asmErrString get() = "unexpected token"
|
||||||
|
override val asmErrStrings get() = listOf(asmErrString, "unknown operator")
|
||||||
|
}
|
||||||
|
|
||||||
|
class ImportAfterNonImport(val nonImportType: String) : IoErr("Import happened after $nonImportType") {
|
||||||
|
override val asmErrString get() = "import after $nonImportType"
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnknownOperator : IoErr("Unknown operator") {
|
||||||
|
override val asmErrString get() = "unknown operator"
|
||||||
|
}
|
||||||
|
|
||||||
|
class InvalidVar(val found: String) : IoErr("Var ref expected, found: $found") {
|
||||||
|
override val asmErrString get() = "unknown operator"
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResultBeforeParameter : IoErr("Function result before parameter") {
|
||||||
|
override val asmErrString get() = "result before parameter"
|
||||||
|
override val asmErrStrings get() = listOf(asmErrString, "unexpected token")
|
||||||
|
}
|
||||||
}
|
}
|
@ -66,13 +66,15 @@ open class SExprToAst {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun toBlockSigMaybe(exp: SExpr.Multi, offset: Int): List<Node.Type.Value> {
|
fun toBlockSigMaybe(exp: SExpr.Multi, offset: Int): List<Node.Type.Value> {
|
||||||
val types = exp.vals.drop(offset).takeUntilNullLazy { if (it is SExpr.Symbol) toTypeMaybe(it) else null }
|
val multi = exp.vals.getOrNull(offset) as? SExpr.Multi
|
||||||
|
if (multi == null || multi.vals.firstOrNull()?.symbolStr() != "result") return emptyList()
|
||||||
|
val types = multi.vals.drop(1).map { it.symbol()?.let { toTypeMaybe(it) } ?: error("Unknown type on $it") }
|
||||||
// We can only handle one type for now
|
// We can only handle one type for now
|
||||||
require(types.size <= 1)
|
require(types.size <= 1)
|
||||||
return types
|
return types
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toCmd(exp: SExpr.Multi): Script.Cmd {
|
fun toCmdMaybe(exp: SExpr.Multi): Script.Cmd? {
|
||||||
val expName = exp.vals.first().symbolStr()
|
val expName = exp.vals.first().symbolStr()
|
||||||
return when(expName) {
|
return when(expName) {
|
||||||
"module" ->
|
"module" ->
|
||||||
@ -87,7 +89,7 @@ open class SExprToAst {
|
|||||||
"script", "input", "output" ->
|
"script", "input", "output" ->
|
||||||
toMeta(exp)
|
toMeta(exp)
|
||||||
else ->
|
else ->
|
||||||
error("Unrecognized cmd expr '$expName'")
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,7 +154,7 @@ open class SExprToAst {
|
|||||||
if (maybeOpAndOffset != null) {
|
if (maybeOpAndOffset != null) {
|
||||||
// Everything left in the multi should be a a multi expression
|
// Everything left in the multi should be a a multi expression
|
||||||
return exp.vals.drop(maybeOpAndOffset.second).flatMap {
|
return exp.vals.drop(maybeOpAndOffset.second).flatMap {
|
||||||
toExprMaybe(it as SExpr.Multi, ctx)
|
toExprMaybe(it as SExpr.Multi, ctx).also { if (it.isEmpty()) throw IoErr.UnknownOperator() }
|
||||||
} + maybeOpAndOffset.first
|
} + maybeOpAndOffset.first
|
||||||
}
|
}
|
||||||
// Other blocks take up the rest (ignore names)
|
// Other blocks take up the rest (ignore names)
|
||||||
@ -214,7 +216,7 @@ open class SExprToAst {
|
|||||||
val name = exp.maybeName(currentIndex)
|
val name = exp.maybeName(currentIndex)
|
||||||
if (name != null) currentIndex++
|
if (name != null) currentIndex++
|
||||||
val maybeImpExp = toImportOrExportMaybe(exp, currentIndex)
|
val maybeImpExp = toImportOrExportMaybe(exp, currentIndex)
|
||||||
if (maybeImpExp != null) currentIndex++
|
maybeImpExp?.also { currentIndex += it.itemCount }
|
||||||
var (nameMap, exprsUsed, sig) = toFuncSig(exp, currentIndex, origNameMap, types)
|
var (nameMap, exprsUsed, sig) = toFuncSig(exp, currentIndex, origNameMap, types)
|
||||||
currentIndex += exprsUsed
|
currentIndex += exprsUsed
|
||||||
val locals = exp.repeated("local", currentIndex, { toLocals(it) }).mapIndexed { index, (nameMaybe, vals) ->
|
val locals = exp.repeated("local", currentIndex, { toLocals(it) }).mapIndexed { index, (nameMaybe, vals) ->
|
||||||
@ -224,7 +226,7 @@ open class SExprToAst {
|
|||||||
currentIndex += locals.size
|
currentIndex += locals.size
|
||||||
val (instrs, _) = toInstrs(exp, currentIndex, ExprContext(nameMap))
|
val (instrs, _) = toInstrs(exp, currentIndex, ExprContext(nameMap))
|
||||||
// Imports can't have locals or instructions
|
// Imports can't have locals or instructions
|
||||||
if (maybeImpExp?.importModule != null) require(locals.isEmpty() && instrs.isEmpty())
|
if (maybeImpExp is ImportOrExport.Import) require(locals.isEmpty() && instrs.isEmpty())
|
||||||
return Triple(name, Node.Func(sig, locals.flatten(), instrs), maybeImpExp)
|
return Triple(name, Node.Func(sig, locals.flatten(), instrs), maybeImpExp)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,16 +247,22 @@ open class SExprToAst {
|
|||||||
nameMaybe?.also { require(vals.size == 1); nameMap += "local:$it" to index }
|
nameMaybe?.also { require(vals.size == 1); nameMap += "local:$it" to index }
|
||||||
vals
|
vals
|
||||||
}
|
}
|
||||||
val results = exp.repeated("result", offset + params.size, this::toResult)
|
val resultExps = exp.repeated("result", offset + params.size, this::toResult)
|
||||||
|
val results = resultExps.flatten()
|
||||||
if (results.size > 1) throw IoErr.InvalidResultArity()
|
if (results.size > 1) throw IoErr.InvalidResultArity()
|
||||||
val usedExps = params.size + results.size + if (typeRef == null) 0 else 1
|
val usedExps = params.size + resultExps.size + if (typeRef == null) 0 else 1
|
||||||
|
// Make sure there aren't parameters following the result
|
||||||
|
if (resultExps.isNotEmpty() && (exp.vals.getOrNull(offset + params.size + resultExps.size) as? SExpr.Multi)?.
|
||||||
|
vals?.firstOrNull()?.symbolStr() == "param") {
|
||||||
|
throw IoErr.ResultBeforeParameter()
|
||||||
|
}
|
||||||
// Check against type ref
|
// Check against type ref
|
||||||
if (typeRef != null) {
|
if (typeRef != null) {
|
||||||
// No params or results means just use it
|
// No params or results means just use it
|
||||||
if (params.isEmpty() && results.isEmpty()) return Triple(nameMap, usedExps, typeRef)
|
if (params.isEmpty() && results.isEmpty()) return Triple(nameMap, usedExps, typeRef)
|
||||||
// Otherwise, just make sure it matches
|
// Otherwise, just make sure it matches
|
||||||
require(typeRef.params == params.flatten() && typeRef.ret == results.firstOrNull()) {
|
if (typeRef.params != params.flatten() || typeRef.ret != results.firstOrNull()) {
|
||||||
"Params for type ref do not match explicit ones"
|
throw IoErr.FuncTypeRefMismatch()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Triple(nameMap, usedExps, Node.Type.Func(params.flatten(), results.firstOrNull()))
|
return Triple(nameMap, usedExps, Node.Type.Func(params.flatten(), results.firstOrNull()))
|
||||||
@ -266,12 +274,12 @@ open class SExprToAst {
|
|||||||
val name = exp.maybeName(currIndex)
|
val name = exp.maybeName(currIndex)
|
||||||
if (name != null) currIndex++
|
if (name != null) currIndex++
|
||||||
val maybeImpExp = toImportOrExportMaybe(exp, currIndex)
|
val maybeImpExp = toImportOrExportMaybe(exp, currIndex)
|
||||||
if (maybeImpExp != null) currIndex++
|
maybeImpExp?.also { currIndex += it.itemCount }
|
||||||
val sig = toGlobalSig(exp.vals[currIndex])
|
val sig = toGlobalSig(exp.vals[currIndex])
|
||||||
currIndex++
|
currIndex++
|
||||||
val (instrs, _) = toInstrs(exp, currIndex, ExprContext(nameMap))
|
val (instrs, _) = toInstrs(exp, currIndex, ExprContext(nameMap))
|
||||||
// Imports can't have instructions
|
// Imports can't have instructions
|
||||||
if (maybeImpExp?.importModule != null) require(instrs.isEmpty())
|
if (maybeImpExp is ImportOrExport.Import) require(instrs.isEmpty())
|
||||||
return Triple(name, Node.Global(sig, instrs), maybeImpExp)
|
return Triple(name, Node.Global(sig, instrs), maybeImpExp)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,7 +291,11 @@ open class SExprToAst {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toImport(exp: SExpr.Multi): Triple<String, String, Node.Type> {
|
fun toImport(
|
||||||
|
exp: SExpr.Multi,
|
||||||
|
origNameMap: NameMap,
|
||||||
|
types: List<Node.Type.Func>
|
||||||
|
): Triple<String, String, Node.Type> {
|
||||||
exp.requireFirstSymbol("import")
|
exp.requireFirstSymbol("import")
|
||||||
val module = exp.vals[1].symbolStr()!!
|
val module = exp.vals[1].symbolStr()!!
|
||||||
val field = exp.vals[2].symbolStr()!!
|
val field = exp.vals[2].symbolStr()!!
|
||||||
@ -291,7 +303,7 @@ open class SExprToAst {
|
|||||||
val kindName = kind.vals.firstOrNull()?.symbolStr()
|
val kindName = kind.vals.firstOrNull()?.symbolStr()
|
||||||
val kindSubOffset = if (kind.maybeName(1) == null) 1 else 2
|
val kindSubOffset = if (kind.maybeName(1) == null) 1 else 2
|
||||||
return Triple(module, field, when(kindName) {
|
return Triple(module, field, when(kindName) {
|
||||||
"func" -> toFuncSig(kind, kindSubOffset, emptyMap(), emptyList()).third
|
"func" -> toFuncSig(kind, kindSubOffset, origNameMap, types).third
|
||||||
"global" -> toGlobalSig(kind.vals[kindSubOffset])
|
"global" -> toGlobalSig(kind.vals[kindSubOffset])
|
||||||
"table" -> toTableSig(kind, kindSubOffset)
|
"table" -> toTableSig(kind, kindSubOffset)
|
||||||
"memory" -> toMemorySig(kind, kindSubOffset)
|
"memory" -> toMemorySig(kind, kindSubOffset)
|
||||||
@ -301,12 +313,23 @@ open class SExprToAst {
|
|||||||
|
|
||||||
fun toImportOrExportMaybe(exp: SExpr.Multi, offset: Int): ImportOrExport? {
|
fun toImportOrExportMaybe(exp: SExpr.Multi, offset: Int): ImportOrExport? {
|
||||||
if (offset >= exp.vals.size) return null
|
if (offset >= exp.vals.size) return null
|
||||||
val multi = exp.vals[offset] as? SExpr.Multi ?: return null
|
var currOffset = offset
|
||||||
val multiHead = multi.vals[0] as? SExpr.Symbol ?: return null
|
// Get all export fields first
|
||||||
return when (multiHead.contents) {
|
var exportFields = emptyList<String>()
|
||||||
"export" -> ImportOrExport(multi.vals[1].symbolStr()!!, null)
|
while (true) {
|
||||||
"import" -> ImportOrExport(multi.vals[2].symbolStr()!!, multi.vals[1].symbolStr()!!)
|
val multi = exp.vals.getOrNull(currOffset) as? SExpr.Multi
|
||||||
else -> null
|
when (multi?.vals?.firstOrNull()?.symbolStr()) {
|
||||||
|
"import" -> return ImportOrExport.Import(
|
||||||
|
name = multi.vals.getOrNull(2)?.symbolStr() ?: error("No import name"),
|
||||||
|
module = multi.vals.getOrNull(1)?.symbolStr() ?: error("No import module"),
|
||||||
|
exportFields = exportFields
|
||||||
|
)
|
||||||
|
"export" -> multi.vals.getOrNull(1)?.symbolStr().also {
|
||||||
|
exportFields += it ?: error("No export field")
|
||||||
|
}
|
||||||
|
else -> return if (exportFields.isEmpty()) null else ImportOrExport.Export(exportFields)
|
||||||
|
}
|
||||||
|
currOffset++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,8 +347,8 @@ open class SExprToAst {
|
|||||||
ret += maybeInstrAndOffset.first
|
ret += maybeInstrAndOffset.first
|
||||||
runningOffset += maybeInstrAndOffset.second
|
runningOffset += maybeInstrAndOffset.second
|
||||||
}
|
}
|
||||||
if (mustCompleteExp) require(offset + runningOffset == exp.vals.size) {
|
if (mustCompleteExp && offset + runningOffset != exp.vals.size) {
|
||||||
"Unrecognized instruction: ${exp.vals[offset + runningOffset]}"
|
throw IoErr.UnrecognizedInstruction(exp.vals[offset + runningOffset].toString())
|
||||||
}
|
}
|
||||||
return Pair(ret, runningOffset)
|
return Pair(ret, runningOffset)
|
||||||
}
|
}
|
||||||
@ -397,7 +420,7 @@ open class SExprToAst {
|
|||||||
opOffset++
|
opOffset++
|
||||||
exp.maybeName(offset + opOffset)?.also {
|
exp.maybeName(offset + opOffset)?.also {
|
||||||
opOffset++
|
opOffset++
|
||||||
require(it == maybeName, { "Expected end for $maybeName, got $it" })
|
if (it != maybeName) throw IoErr.MismatchLabelEnd(maybeName, it)
|
||||||
}
|
}
|
||||||
return Pair(ret, opOffset)
|
return Pair(ret, opOffset)
|
||||||
}
|
}
|
||||||
@ -415,7 +438,7 @@ open class SExprToAst {
|
|||||||
val name = exp.maybeName(currIndex)
|
val name = exp.maybeName(currIndex)
|
||||||
if (name != null) currIndex++
|
if (name != null) currIndex++
|
||||||
val maybeImpExp = toImportOrExportMaybe(exp, currIndex)
|
val maybeImpExp = toImportOrExportMaybe(exp, currIndex)
|
||||||
if (maybeImpExp != null) currIndex++
|
maybeImpExp?.also { currIndex += it.itemCount }
|
||||||
// If it's a multi we assume "data", otherwise assume sig
|
// If it's a multi we assume "data", otherwise assume sig
|
||||||
val memOrData = exp.vals[currIndex].let {
|
val memOrData = exp.vals[currIndex].let {
|
||||||
when (it) {
|
when (it) {
|
||||||
@ -449,23 +472,34 @@ open class SExprToAst {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun toModule(exp: SExpr.Multi): Pair<String?, Node.Module> {
|
fun toModule(exp: SExpr.Multi): Pair<String?, Node.Module> {
|
||||||
|
// As a special case, if this isn't a "module", wrap it and try again
|
||||||
|
if (exp.vals.firstOrNull()?.symbolStr() != "module") {
|
||||||
|
return toModule(SExpr.Multi(listOf(SExpr.Symbol("module")) + exp.vals))
|
||||||
|
}
|
||||||
exp.requireFirstSymbol("module")
|
exp.requireFirstSymbol("module")
|
||||||
val name = exp.maybeName(1)
|
val name = exp.maybeName(1)
|
||||||
|
|
||||||
// If all of the other symbols after the name are quoted strings,
|
// Special cases for "quote" and "binary" modules.
|
||||||
// this needs to be parsed as a binary
|
val quoteOrBinary = exp.vals.elementAtOrNull(if (name == null) 1 else 2)?.
|
||||||
exp.vals.drop(if (name == null) 1 else 2).also { otherVals ->
|
symbolStr()?.takeIf { it == "quote" || it == "binary" }
|
||||||
if (otherVals.isNotEmpty() && otherVals.find { it !is SExpr.Symbol || !it.quoted } == null)
|
if (quoteOrBinary != null) {
|
||||||
return name to toModuleFromBytes(otherVals.fold(byteArrayOf()) { bytes, strVal ->
|
val bytes = exp.vals.drop(if (name == null) 2 else 3).fold(byteArrayOf()) { bytes, expr ->
|
||||||
bytes + (strVal as SExpr.Symbol).rawContentCharsToBytes()
|
bytes + (
|
||||||
})
|
expr.symbol()?.takeIf { it.quoted }?.rawContentCharsToBytes() ?: error("Expected quoted string")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// For binary, just load from bytes
|
||||||
|
if (quoteOrBinary == "binary") return name to toModuleFromBytes(bytes)
|
||||||
|
// Otherwise, take the quoted strings and parse em
|
||||||
|
return toModuleFromQuotedString(bytes.toString(Charsets.US_ASCII))
|
||||||
}
|
}
|
||||||
|
|
||||||
var mod = Node.Module()
|
var mod = Node.Module()
|
||||||
val exps = exp.vals.mapNotNull { it as? SExpr.Multi }
|
val exps = exp.vals.mapNotNull { it as? SExpr.Multi }
|
||||||
|
|
||||||
// Eagerly build the names (for forward decls)
|
// Eagerly build the names (for forward decls)
|
||||||
var nameMap = toModuleForwardNameMap(exps)
|
val (nameMap, eagerTypes) = toModuleForwardNameMapAndTypes(exps)
|
||||||
|
mod = mod.copy(types = eagerTypes)
|
||||||
|
|
||||||
fun Node.Module.addTypeIfNotPresent(type: Node.Type.Func): Pair<Node.Module, Int> {
|
fun Node.Module.addTypeIfNotPresent(type: Node.Type.Func): Pair<Node.Module, Int> {
|
||||||
val index = this.types.indexOf(type)
|
val index = this.types.indexOf(type)
|
||||||
@ -478,64 +512,74 @@ open class SExprToAst {
|
|||||||
var globalCount = 0
|
var globalCount = 0
|
||||||
var tableCount = 0
|
var tableCount = 0
|
||||||
var memoryCount = 0
|
var memoryCount = 0
|
||||||
fun handleImport(module: String, field: String, kind: Node.Type) {
|
fun handleImport(module: String, field: String, kind: Node.Type, exportFields: List<String>) {
|
||||||
// We make sure that an import doesn't happen after a non-import
|
// We make sure that an import doesn't happen after a non-import
|
||||||
require(mod.funcs.isEmpty() && mod.globals.isEmpty() &&
|
if (mod.funcs.isNotEmpty()) throw IoErr.ImportAfterNonImport("function")
|
||||||
mod.tables.isEmpty() && mod.memories.isEmpty()) { "Import happened after non-import" }
|
if (mod.globals.isNotEmpty()) throw IoErr.ImportAfterNonImport("global")
|
||||||
val importKind = when(kind) {
|
if (mod.tables.isNotEmpty()) throw IoErr.ImportAfterNonImport("table")
|
||||||
|
if (mod.memories.isNotEmpty()) throw IoErr.ImportAfterNonImport("memory")
|
||||||
|
val (importKind, indexAndExtKind) = when(kind) {
|
||||||
is Node.Type.Func -> mod.addTypeIfNotPresent(kind).let { (m, idx) ->
|
is Node.Type.Func -> mod.addTypeIfNotPresent(kind).let { (m, idx) ->
|
||||||
funcCount++
|
|
||||||
mod = m
|
mod = m
|
||||||
Node.Import.Kind.Func(idx)
|
Node.Import.Kind.Func(idx) to (funcCount++ to Node.ExternalKind.FUNCTION)
|
||||||
}
|
}
|
||||||
is Node.Type.Global -> { globalCount++; Node.Import.Kind.Global(kind) }
|
is Node.Type.Global ->
|
||||||
is Node.Type.Table -> { tableCount++; Node.Import.Kind.Table(kind) }
|
Node.Import.Kind.Global(kind) to (globalCount++ to Node.ExternalKind.GLOBAL)
|
||||||
is Node.Type.Memory -> { memoryCount++; Node.Import.Kind.Memory(kind) }
|
is Node.Type.Table ->
|
||||||
|
Node.Import.Kind.Table(kind) to (tableCount++ to Node.ExternalKind.TABLE)
|
||||||
|
is Node.Type.Memory ->
|
||||||
|
Node.Import.Kind.Memory(kind) to (memoryCount++ to Node.ExternalKind.MEMORY)
|
||||||
else -> throw Exception("Unrecognized import kind: $kind")
|
else -> throw Exception("Unrecognized import kind: $kind")
|
||||||
}
|
}
|
||||||
mod = mod.copy(imports = mod.imports + Node.Import(module, field, importKind))
|
mod = mod.copy(
|
||||||
|
imports = mod.imports + Node.Import(module, field, importKind),
|
||||||
|
exports = mod.exports + exportFields.map {
|
||||||
|
Node.Export(it, indexAndExtKind.second, indexAndExtKind.first)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addMaybeExport(impExp: ImportOrExport?, extKind: Node.ExternalKind, index: Int) {
|
fun addExport(exp: ImportOrExport.Export, extKind: Node.ExternalKind, index: Int) {
|
||||||
impExp?.also { mod = mod.copy(exports = mod.exports + Node.Export(it.field, extKind, index)) }
|
mod = mod.copy(exports = mod.exports + exp.fields.map { Node.Export(it, extKind, index) })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now just handle all expressions in order
|
// Now just handle all expressions in order
|
||||||
exps.forEach { exp ->
|
exps.forEach { exp ->
|
||||||
when(exp.vals.firstOrNull()?.symbolStr()) {
|
when(exp.vals.firstOrNull()?.symbolStr()) {
|
||||||
"import" -> toImport(exp).let { (module, field, type) -> handleImport(module, field, type) }
|
"import" -> toImport(exp, nameMap, mod.types).let { (module, field, type) ->
|
||||||
"type" -> toTypeDef(exp, nameMap).let { (name, type) ->
|
handleImport(module, field, type, emptyList())
|
||||||
// We always add the type, even if it's a duplicate.
|
|
||||||
// Ref: https://github.com/WebAssembly/design/issues/1041
|
|
||||||
if (name != null) nameMap += "type:$name" to mod.types.size
|
|
||||||
mod = mod.copy(types = mod.types + type)
|
|
||||||
}
|
}
|
||||||
|
// We do not handle types here anymore. They are handled eagerly as part of the forward pass.
|
||||||
|
"type" -> { }
|
||||||
"export" -> mod = mod.copy(exports = mod.exports + toExport(exp, nameMap))
|
"export" -> mod = mod.copy(exports = mod.exports + toExport(exp, nameMap))
|
||||||
"elem" -> mod = mod.copy(elems = mod.elems + toElem(exp, nameMap))
|
"elem" -> mod = mod.copy(elems = mod.elems + toElem(exp, nameMap))
|
||||||
"data" -> mod = mod.copy(data = mod.data + toData(exp, nameMap))
|
"data" -> mod = mod.copy(data = mod.data + toData(exp, nameMap))
|
||||||
"start" -> mod = mod.copy(startFuncIndex = toStart(exp, nameMap))
|
"start" -> mod = mod.copy(startFuncIndex = toStart(exp, nameMap))
|
||||||
"func" -> toFunc(exp, nameMap, mod.types).also { (_, fn, impExp) ->
|
"func" -> toFunc(exp, nameMap, mod.types).also { (_, fn, impExp) ->
|
||||||
if (impExp != null && impExp.importModule != null) {
|
if (impExp is ImportOrExport.Import) {
|
||||||
handleImport(impExp.importModule, impExp.field, fn.type)
|
handleImport(impExp.module, impExp.name, fn.type, impExp.exportFields)
|
||||||
} else {
|
} else {
|
||||||
addMaybeExport(impExp, Node.ExternalKind.FUNCTION, funcCount++)
|
if (impExp is ImportOrExport.Export) addExport(impExp, Node.ExternalKind.FUNCTION, funcCount)
|
||||||
|
funcCount++
|
||||||
mod = mod.copy(funcs = mod.funcs + fn).addTypeIfNotPresent(fn.type).first
|
mod = mod.copy(funcs = mod.funcs + fn).addTypeIfNotPresent(fn.type).first
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"global" -> toGlobal(exp, nameMap).let { (_, glb, impExp) ->
|
"global" -> toGlobal(exp, nameMap).let { (_, glb, impExp) ->
|
||||||
if (impExp != null && impExp.importModule != null) {
|
if (impExp is ImportOrExport.Import) {
|
||||||
handleImport(impExp.importModule, impExp.field, glb.type)
|
handleImport(impExp.module, impExp.name, glb.type, impExp.exportFields)
|
||||||
} else {
|
} else {
|
||||||
addMaybeExport(impExp, Node.ExternalKind.GLOBAL, globalCount++)
|
if (impExp is ImportOrExport.Export) addExport(impExp, Node.ExternalKind.GLOBAL, globalCount)
|
||||||
|
globalCount++
|
||||||
mod = mod.copy(globals = mod.globals + glb)
|
mod = mod.copy(globals = mod.globals + glb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"table" -> toTable(exp, nameMap).let { (_, tbl, impExp) ->
|
"table" -> toTable(exp, nameMap).let { (_, tbl, impExp) ->
|
||||||
if (impExp != null && impExp.importModule != null) {
|
if (impExp is ImportOrExport.Import) {
|
||||||
if (tbl !is Either.Left) error("Elem segment on import table")
|
if (tbl !is Either.Left) error("Elem segment on import table")
|
||||||
handleImport(impExp.importModule, impExp.field, tbl.v)
|
handleImport(impExp.module, impExp.name, tbl.v, impExp.exportFields)
|
||||||
} else {
|
} else {
|
||||||
addMaybeExport(impExp, Node.ExternalKind.TABLE, tableCount++)
|
if (impExp is ImportOrExport.Export) addExport(impExp, Node.ExternalKind.TABLE, tableCount)
|
||||||
|
tableCount++
|
||||||
when (tbl) {
|
when (tbl) {
|
||||||
is Either.Left -> mod = mod.copy(tables = mod.tables + tbl.v)
|
is Either.Left -> mod = mod.copy(tables = mod.tables + tbl.v)
|
||||||
is Either.Right -> mod = mod.copy(
|
is Either.Right -> mod = mod.copy(
|
||||||
@ -549,11 +593,12 @@ open class SExprToAst {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"memory" -> toMemory(exp).let { (_, mem, impExp) ->
|
"memory" -> toMemory(exp).let { (_, mem, impExp) ->
|
||||||
if (impExp != null && impExp.importModule != null) {
|
if (impExp is ImportOrExport.Import) {
|
||||||
if (mem !is Either.Left) error("Data segment on import mem")
|
if (mem !is Either.Left) error("Data segment on import mem")
|
||||||
handleImport(impExp.importModule, impExp.field, mem.v)
|
handleImport(impExp.module, impExp.name, mem.v, impExp.exportFields)
|
||||||
} else {
|
} else {
|
||||||
addMaybeExport(impExp, Node.ExternalKind.MEMORY, memoryCount++)
|
if (impExp is ImportOrExport.Export) addExport(impExp, Node.ExternalKind.MEMORY, memoryCount)
|
||||||
|
memoryCount++
|
||||||
when (mem) {
|
when (mem) {
|
||||||
is Either.Left -> mod = mod.copy(memories = mod.memories + mem.v)
|
is Either.Left -> mod = mod.copy(memories = mod.memories + mem.v)
|
||||||
is Either.Right -> mod = mod.copy(
|
is Either.Right -> mod = mod.copy(
|
||||||
@ -579,7 +624,20 @@ open class SExprToAst {
|
|||||||
|
|
||||||
fun toModuleFromBytes(bytes: ByteArray) = BinaryToAst.toModule(ByteReader.InputStream(ByteArrayInputStream(bytes)))
|
fun toModuleFromBytes(bytes: ByteArray) = BinaryToAst.toModule(ByteReader.InputStream(ByteArrayInputStream(bytes)))
|
||||||
|
|
||||||
fun toModuleForwardNameMap(exps: List<SExpr.Multi>): NameMap {
|
fun toModuleFromQuotedString(str: String) = StrToSExpr.parse(str).let {
|
||||||
|
when (it) {
|
||||||
|
is StrToSExpr.ParseResult.Error -> error("Failed parsing quoted module: ${it.msg}")
|
||||||
|
is StrToSExpr.ParseResult.Success -> {
|
||||||
|
// If the result is not a single module sexpr, wrap it in one
|
||||||
|
val sexpr = it.vals.singleOrNull()?.let { it as? SExpr.Multi }?.takeIf {
|
||||||
|
it.vals.firstOrNull()?.symbolStr() == "module"
|
||||||
|
} ?: SExpr.Multi(listOf(SExpr.Symbol("module")) + it.vals)
|
||||||
|
toModule(sexpr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toModuleForwardNameMapAndTypes(exps: List<SExpr.Multi>): Pair<NameMap, List<Node.Type.Func>> {
|
||||||
// We break into import and non-import because the index
|
// We break into import and non-import because the index
|
||||||
// tables do imports first
|
// tables do imports first
|
||||||
val (importExps, nonImportExps) = exps.partition {
|
val (importExps, nonImportExps) = exps.partition {
|
||||||
@ -598,6 +656,7 @@ open class SExprToAst {
|
|||||||
var tableCount = 0
|
var tableCount = 0
|
||||||
var memoryCount = 0
|
var memoryCount = 0
|
||||||
var namesToIndices = emptyMap<String, Int>()
|
var namesToIndices = emptyMap<String, Int>()
|
||||||
|
var types = emptyList<Node.Type.Func>()
|
||||||
fun maybeAddName(name: String?, index: Int, type: String) {
|
fun maybeAddName(name: String?, index: Int, type: String) {
|
||||||
name?.let { namesToIndices += "$type:$it" to index }
|
name?.let { namesToIndices += "$type:$it" to index }
|
||||||
}
|
}
|
||||||
@ -625,10 +684,14 @@ open class SExprToAst {
|
|||||||
"global" -> maybeAddName(kindName, globalCount++, "global")
|
"global" -> maybeAddName(kindName, globalCount++, "global")
|
||||||
"table" -> maybeAddName(kindName, tableCount++, "table")
|
"table" -> maybeAddName(kindName, tableCount++, "table")
|
||||||
"memory" -> maybeAddName(kindName, memoryCount++, "memory")
|
"memory" -> maybeAddName(kindName, memoryCount++, "memory")
|
||||||
|
// We go ahead and do the full type def build here eagerly
|
||||||
|
"type" -> maybeAddName(kindName, types.size, "type").also { _ ->
|
||||||
|
toTypeDef(it, namesToIndices).also { (_, type) -> types += type }
|
||||||
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return namesToIndices
|
return namesToIndices to types
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toOpMaybe(exp: SExpr.Multi, offset: Int, ctx: ExprContext): Pair<Node.Instr, Int>? {
|
fun toOpMaybe(exp: SExpr.Multi, offset: Int, ctx: ExprContext): Pair<Node.Instr, Int>? {
|
||||||
@ -678,10 +741,10 @@ open class SExprToAst {
|
|||||||
if (exp.vals.size > offset + count) exp.vals[offset + count].symbolStr().also {
|
if (exp.vals.size > offset + count) exp.vals[offset + count].symbolStr().also {
|
||||||
if (it != null && it.startsWith("align=")) {
|
if (it != null && it.startsWith("align=")) {
|
||||||
instrAlign = it.substring(6).toInt()
|
instrAlign = it.substring(6).toInt()
|
||||||
require(instrAlign > 0 && instrAlign and (instrAlign - 1) == 0) {
|
if (instrAlign <= 0 || instrAlign and (instrAlign - 1) != 0) {
|
||||||
"Alignment expected to be positive power of 2, but got $instrAlign"
|
throw IoErr.InvalidAlignPower(instrAlign)
|
||||||
}
|
}
|
||||||
if (instrAlign > op.argBits / 8) throw IoErr.InvalidAlign(instrAlign, op.argBits)
|
if (instrAlign > op.argBits / 8) throw IoErr.InvalidAlignTooLarge(instrAlign, op.argBits)
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -723,14 +786,18 @@ open class SExprToAst {
|
|||||||
return Node.ResizableLimits(init.toInt(), max?.toInt())
|
return Node.ResizableLimits(init.toInt(), max?.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toResult(exp: SExpr.Multi): Node.Type.Value {
|
fun toResult(exp: SExpr.Multi): List<Node.Type.Value> {
|
||||||
exp.requireFirstSymbol("result")
|
exp.requireFirstSymbol("result")
|
||||||
if (exp.vals.size > 2) throw IoErr.InvalidResultArity()
|
return exp.vals.drop(1).map { toType(it.symbol() ?: error("Invalid result type")) }
|
||||||
return toType(exp.vals[1].symbol()!!)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toScript(exp: SExpr.Multi): Script {
|
fun toScript(exp: SExpr.Multi): Script {
|
||||||
return Script(exp.vals.map { toCmd(it as SExpr.Multi) })
|
val cmds = exp.vals.map { toCmdMaybe(it as SExpr.Multi) }
|
||||||
|
// If the commands are non-empty but they are all null, it's an inline module
|
||||||
|
if (cmds.isNotEmpty() && cmds.all { it == null }) {
|
||||||
|
return toModule(exp).let { Script(listOf(Script.Cmd.Module(it.second, it.first))) }
|
||||||
|
}
|
||||||
|
return Script(cmds.filterNotNull())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toStart(exp: SExpr.Multi, nameMap: NameMap): Int {
|
fun toStart(exp: SExpr.Multi, nameMap: NameMap): Int {
|
||||||
@ -747,12 +814,12 @@ open class SExprToAst {
|
|||||||
val name = exp.maybeName(currIndex)
|
val name = exp.maybeName(currIndex)
|
||||||
if (name != null) currIndex++
|
if (name != null) currIndex++
|
||||||
val maybeImpExp = toImportOrExportMaybe(exp, currIndex)
|
val maybeImpExp = toImportOrExportMaybe(exp, currIndex)
|
||||||
if (maybeImpExp != null) currIndex++
|
maybeImpExp?.also { currIndex += it.itemCount }
|
||||||
// If elem type is there, we load the elems instead
|
// If elem type is there, we load the elems instead
|
||||||
val elemType = toElemTypeMaybe(exp, currIndex)
|
val elemType = toElemTypeMaybe(exp, currIndex)
|
||||||
val tableOrElems =
|
val tableOrElems =
|
||||||
if (elemType != null) {
|
if (elemType != null) {
|
||||||
require(maybeImpExp?.importModule == null)
|
require(maybeImpExp !is ImportOrExport.Import)
|
||||||
val elem = exp.vals[currIndex + 1] as SExpr.Multi
|
val elem = exp.vals[currIndex + 1] as SExpr.Multi
|
||||||
elem.requireFirstSymbol("elem")
|
elem.requireFirstSymbol("elem")
|
||||||
Either.Right(Node.Elem(
|
Either.Right(Node.Elem(
|
||||||
@ -770,7 +837,7 @@ open class SExprToAst {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun toType(exp: SExpr.Symbol): Node.Type.Value {
|
fun toType(exp: SExpr.Symbol): Node.Type.Value {
|
||||||
return toTypeMaybe(exp) ?: throw Exception("Unknown value type: ${exp.contents}")
|
return toTypeMaybe(exp) ?: throw IoErr.InvalidType(exp.contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toTypeMaybe(exp: SExpr.Symbol): Node.Type.Value? = when(exp.contents) {
|
fun toTypeMaybe(exp: SExpr.Symbol): Node.Type.Value? = when(exp.contents) {
|
||||||
@ -792,7 +859,7 @@ open class SExprToAst {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun toVar(exp: SExpr.Symbol, nameMap: NameMap, nameType: String): Int {
|
fun toVar(exp: SExpr.Symbol, nameMap: NameMap, nameType: String): Int {
|
||||||
return toVarMaybe(exp, nameMap, nameType) ?: throw Exception("No var on exp $exp")
|
return toVarMaybe(exp, nameMap, nameType) ?: throw IoErr.InvalidVar(exp.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toVarMaybe(exp: SExpr, nameMap: NameMap, nameType: String): Int? {
|
fun toVarMaybe(exp: SExpr, nameMap: NameMap, nameType: String): Int? {
|
||||||
@ -805,37 +872,74 @@ open class SExprToAst {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun String.sansUnderscores(): String {
|
||||||
|
// The underscores can only be between digits (which can be hex)
|
||||||
|
fun isDigit(c: Char) = c.isDigit() || (startsWith("0x", true) && (c in 'a'..'f' || c in 'A'..'F'))
|
||||||
|
var ret = this
|
||||||
|
var underscoreIndex = 0
|
||||||
|
while (true){
|
||||||
|
underscoreIndex = ret.indexOf('_', underscoreIndex)
|
||||||
|
if (underscoreIndex == -1) return ret
|
||||||
|
// Can't be at beginning or end
|
||||||
|
if (underscoreIndex == 0 || underscoreIndex == ret.length - 1 ||
|
||||||
|
!isDigit(ret[underscoreIndex - 1]) || !isDigit(ret[underscoreIndex + 1])) {
|
||||||
|
throw IoErr.ConstantUnknownOperator(this)
|
||||||
|
}
|
||||||
|
ret = ret.removeRange(underscoreIndex, underscoreIndex + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
private fun String.toBigIntegerConst() =
|
private fun String.toBigIntegerConst() =
|
||||||
if (this.contains("0x")) BigInteger(this.replace("0x", ""), 16)
|
if (contains("0x")) BigInteger(replace("0x", ""), 16)
|
||||||
else BigInteger(this)
|
else BigInteger(this)
|
||||||
private fun String.toIntConst() = toBigIntegerConst().toInt()
|
private fun String.toIntConst() = sansUnderscores().run {
|
||||||
private fun String.toLongConst() = toBigIntegerConst().toLong()
|
toBigIntegerConst().
|
||||||
private fun String.toUnsignedIntConst() =
|
also { if (it > MAX_UINT32) throw IoErr.ConstantOutOfRange(it) }.
|
||||||
(if (this.contains("0x")) Long.valueOf(this.replace("0x", ""), 16)
|
also { if (it < MIN_INT32) throw IoErr.ConstantOutOfRange(it) }.
|
||||||
|
toInt()
|
||||||
|
}
|
||||||
|
private fun String.toLongConst() = sansUnderscores().run {
|
||||||
|
toBigIntegerConst().
|
||||||
|
also { if (it > MAX_UINT64) throw IoErr.ConstantOutOfRange(it) }.
|
||||||
|
also { if (it < MIN_INT64) throw IoErr.ConstantOutOfRange(it) }.
|
||||||
|
toLong()
|
||||||
|
}
|
||||||
|
private fun String.toUnsignedIntConst() = sansUnderscores().run {
|
||||||
|
(if (contains("0x")) Long.valueOf(replace("0x", ""), 16)
|
||||||
else Long.valueOf(this)).unsignedToSignedInt().toUnsignedLong()
|
else Long.valueOf(this)).unsignedToSignedInt().toUnsignedLong()
|
||||||
|
}
|
||||||
|
|
||||||
private fun String.toFloatConst() =
|
private fun String.toFloatConst() = sansUnderscores().run {
|
||||||
if (this == "infinity" || this == "+infinity") Float.POSITIVE_INFINITY
|
if (this == "infinity" || this == "+infinity" || this == "inf" || this == "+inf") Float.POSITIVE_INFINITY
|
||||||
else if (this == "-infinity") Float.NEGATIVE_INFINITY
|
else if (this == "-infinity" || this == "-inf") Float.NEGATIVE_INFINITY
|
||||||
else if (this == "nan" || this == "+nan") Float.fromIntBits(0x7fc00000)
|
else if (this == "nan" || this == "+nan") Float.fromIntBits(0x7fc00000)
|
||||||
else if (this == "-nan") Float.fromIntBits(0xffc00000.toInt())
|
else if (this == "-nan") Float.fromIntBits(0xffc00000.toInt())
|
||||||
else if (this.startsWith("nan:") || this.startsWith("+nan:")) Float.fromIntBits(
|
else if (this.startsWith("nan:") || this.startsWith("+nan:")) Float.fromIntBits(
|
||||||
0x7f800000 + this.substring(this.indexOf(':') + 1).toIntConst()
|
0x7f800000 + this.substring(this.indexOf(':') + 1).toIntConst()
|
||||||
) else if (this.startsWith("-nan:")) Float.fromIntBits(
|
) else if (this.startsWith("-nan:")) Float.fromIntBits(
|
||||||
0xff800000.toInt() + this.substring(this.indexOf(':') + 1).toIntConst()
|
0xff800000.toInt() + this.substring(this.indexOf(':') + 1).toIntConst()
|
||||||
) else if (this.startsWith("0x") && !this.contains('P', true)) this.toLongConst().toFloat()
|
) else {
|
||||||
else this.toFloat()
|
// If there is no "p" on a hex, we have to add it
|
||||||
private fun String.toDoubleConst() =
|
var str = this
|
||||||
if (this == "infinity" || this == "+infinity") Double.POSITIVE_INFINITY
|
if (str.startsWith("0x", true) && !str.contains('P', true)) str += "p0"
|
||||||
else if (this == "-infinity") Double.NEGATIVE_INFINITY
|
str.toFloat().also { if (it.isInfinite()) throw IoErr.ConstantOutOfRange(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private fun String.toDoubleConst() = sansUnderscores().run {
|
||||||
|
if (this == "infinity" || this == "+infinity" || this == "inf" || this == "+inf") Double.POSITIVE_INFINITY
|
||||||
|
else if (this == "-infinity" || this == "-inf") Double.NEGATIVE_INFINITY
|
||||||
else if (this == "nan" || this == "+nan") Double.fromLongBits(0x7ff8000000000000)
|
else if (this == "nan" || this == "+nan") Double.fromLongBits(0x7ff8000000000000)
|
||||||
else if (this == "-nan") Double.fromLongBits(-2251799813685248) // i.e. 0xfff8000000000000
|
else if (this == "-nan") Double.fromLongBits(-2251799813685248) // i.e. 0xfff8000000000000
|
||||||
else if (this.startsWith("nan:") || this.startsWith("+nan:")) Double.fromLongBits(
|
else if (this.startsWith("nan:") || this.startsWith("+nan:")) Double.fromLongBits(
|
||||||
0x7ff0000000000000 + this.substring(this.indexOf(':') + 1).toLongConst()
|
0x7ff0000000000000 + this.substring(this.indexOf(':') + 1).toLongConst()
|
||||||
) else if (this.startsWith("-nan:")) Double.fromLongBits(
|
) else if (this.startsWith("-nan:")) Double.fromLongBits(
|
||||||
-4503599627370496 + this.substring(this.indexOf(':') + 1).toLongConst() // i.e. 0xfff0000000000000
|
-4503599627370496 + this.substring(this.indexOf(':') + 1).toLongConst() // i.e. 0xfff0000000000000
|
||||||
) else if (this.startsWith("0x") && !this.contains('P', true)) this.toLongConst().toDouble()
|
) else {
|
||||||
else this.toDouble()
|
// If there is no "p" on a hex, we have to add it
|
||||||
|
var str = this
|
||||||
|
if (str.startsWith("0x", true) && !str.contains('P', true)) str += "p0"
|
||||||
|
str.toDouble().also { if (it.isInfinite()) throw IoErr.ConstantOutOfRange(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun SExpr.requireSymbol(contents: String, quotedCheck: Boolean? = null) {
|
private fun SExpr.requireSymbol(contents: String, quotedCheck: Boolean? = null) {
|
||||||
if (this is SExpr.Symbol && this.contents == contents &&
|
if (this is SExpr.Symbol && this.contents == contents &&
|
||||||
|
@ -15,11 +15,11 @@ open class ExceptionTranslator {
|
|||||||
is AsmErr -> ex.asmErrStrings
|
is AsmErr -> ex.asmErrStrings
|
||||||
is IndexOutOfBoundsException -> listOf("out of bounds memory access")
|
is IndexOutOfBoundsException -> listOf("out of bounds memory access")
|
||||||
is MalformedInputException -> listOf("invalid UTF-8 encoding")
|
is MalformedInputException -> listOf("invalid UTF-8 encoding")
|
||||||
is NoSuchMethodException -> listOf("unknown import", "type mismatch")
|
|
||||||
is NullPointerException -> listOf("undefined element", "uninitialized element")
|
is NullPointerException -> listOf("undefined element", "uninitialized element")
|
||||||
is StackOverflowError -> listOf("call stack exhausted")
|
is StackOverflowError -> listOf("call stack exhausted")
|
||||||
is UnsupportedOperationException -> listOf("unreachable executed")
|
is UnsupportedOperationException -> listOf("unreachable executed")
|
||||||
is WrongMethodTypeException -> listOf("indirect call signature mismatch")
|
is WrongMethodTypeException -> listOf("indirect call signature mismatch")
|
||||||
|
is NumberFormatException -> listOf("i32 constant")
|
||||||
else -> emptyList()
|
else -> emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,14 +8,14 @@ sealed class RunErr(message: String, cause: Throwable? = null) : RuntimeExceptio
|
|||||||
val expected: Int,
|
val expected: Int,
|
||||||
val actual: Int
|
val actual: Int
|
||||||
) : RunErr("Import memory limit $actual but expecting at least $expected") {
|
) : RunErr("Import memory limit $actual but expecting at least $expected") {
|
||||||
override val asmErrString get() = "actual size smaller than declared"
|
override val asmErrString get() = "incompatible import type"
|
||||||
}
|
}
|
||||||
|
|
||||||
class ImportMemoryCapacityTooLarge(
|
class ImportMemoryCapacityTooLarge(
|
||||||
val expected: Int,
|
val expected: Int,
|
||||||
val actual: Int
|
val actual: Int
|
||||||
) : RunErr("Import table capacity $actual but expecting no more than $expected") {
|
) : RunErr("Import table capacity $actual but expecting no more than $expected") {
|
||||||
override val asmErrString get() = "maximum size larger than declared"
|
override val asmErrString get() = "incompatible import type"
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvalidDataIndex(
|
class InvalidDataIndex(
|
||||||
@ -30,14 +30,14 @@ sealed class RunErr(message: String, cause: Throwable? = null) : RuntimeExceptio
|
|||||||
val expected: Int,
|
val expected: Int,
|
||||||
val actual: Int
|
val actual: Int
|
||||||
) : RunErr("Import table sized $actual but expecting at least $expected") {
|
) : RunErr("Import table sized $actual but expecting at least $expected") {
|
||||||
override val asmErrString get() = "actual size smaller than declared"
|
override val asmErrString get() = "incompatible import type"
|
||||||
}
|
}
|
||||||
|
|
||||||
class ImportTableTooLarge(
|
class ImportTableTooLarge(
|
||||||
val expected: Int,
|
val expected: Int,
|
||||||
val actual: Int
|
val actual: Int
|
||||||
) : RunErr("Import table sized $actual but expecting no more than $expected") {
|
) : RunErr("Import table sized $actual but expecting no more than $expected") {
|
||||||
override val asmErrString get() = "maximum size larger than declared"
|
override val asmErrString get() = "incompatible import type"
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvalidElemIndex(
|
class InvalidElemIndex(
|
||||||
@ -46,4 +46,12 @@ sealed class RunErr(message: String, cause: Throwable? = null) : RuntimeExceptio
|
|||||||
) : RunErr("Trying to set elem at index $index but table size is only $tableSize") {
|
) : RunErr("Trying to set elem at index $index but table size is only $tableSize") {
|
||||||
override val asmErrString get() = "elements segment does not fit"
|
override val asmErrString get() = "elements segment does not fit"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ImportNotFound(
|
||||||
|
val module: String,
|
||||||
|
val field: String
|
||||||
|
) : RunErr("Cannot find compatible import for $module::$field") {
|
||||||
|
override val asmErrString get() = "unknown import"
|
||||||
|
override val asmErrStrings get() = listOf(asmErrString, "incompatible import type")
|
||||||
|
}
|
||||||
}
|
}
|
@ -176,7 +176,12 @@ data class ScriptContext(
|
|||||||
val msgs = exceptionTranslator.translate(innerEx)
|
val msgs = exceptionTranslator.translate(innerEx)
|
||||||
if (msgs.isEmpty())
|
if (msgs.isEmpty())
|
||||||
throw ScriptAssertionError(a, "Expected failure '$expectedString' but got unknown err", cause = innerEx)
|
throw ScriptAssertionError(a, "Expected failure '$expectedString' but got unknown err", cause = innerEx)
|
||||||
if (!msgs.any { it.contains(expectedString) })
|
var msgToFind = expectedString
|
||||||
|
// Special case for "uninitialized element" error match. This is because the error is expected to
|
||||||
|
// be "uninitialized number #" where # is the indirect call number. But it is at runtime where this fails
|
||||||
|
// so it is not worth it for us to store the index of failure. So we generalize it.
|
||||||
|
if (msgToFind.startsWith("uninitialized element")) msgToFind = "uninitialized element"
|
||||||
|
if (!msgs.any { it.contains(msgToFind) })
|
||||||
throw ScriptAssertionError(a, "Expected failure '$expectedString' in $msgs", cause = innerEx)
|
throw ScriptAssertionError(a, "Expected failure '$expectedString' in $msgs", cause = innerEx)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,7 +274,7 @@ data class ScriptContext(
|
|||||||
is Node.Import.Kind.Global -> WasmExternalKind.GLOBAL
|
is Node.Import.Kind.Global -> WasmExternalKind.GLOBAL
|
||||||
}
|
}
|
||||||
return module.bindMethod(this, import.field, kind, javaName, methodType) ?:
|
return module.bindMethod(this, import.field, kind, javaName, methodType) ?:
|
||||||
throw NoSuchMethodException("Cannot find import for ${import.module}::${import.field}")
|
throw RunErr.ImportNotFound(import.module, import.field)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resolveImportFunc(import: Node.Import, funcType: Node.Type.Func) =
|
fun resolveImportFunc(import: Node.Import, funcType: Node.Type.Func) =
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
package asmble.util
|
package asmble.util
|
||||||
|
|
||||||
|
import java.math.BigDecimal
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
|
|
||||||
internal const val INT_MASK = 0xffffffffL
|
internal const val INT_MASK = 0xffffffffL
|
||||||
|
internal val MAX_UINT32 = BigInteger("ffffffff", 16)
|
||||||
|
internal val MIN_INT32 = BigInteger.valueOf(Int.MIN_VALUE.toLong())
|
||||||
|
internal val MAX_UINT64 = BigInteger("ffffffffffffffff", 16)
|
||||||
|
internal val MIN_INT64 = BigInteger.valueOf(Long.MIN_VALUE)
|
||||||
|
|
||||||
fun Byte.toUnsignedShort() = (this.toInt() and 0xff).toShort()
|
fun Byte.toUnsignedShort() = (this.toInt() and 0xff).toShort()
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ class SpecTestUnit(name: String, wast: String, expectedOutput: String?) : BaseTe
|
|||||||
// capacity since you lose speed.
|
// capacity since you lose speed.
|
||||||
"imports" -> {
|
"imports" -> {
|
||||||
val isTableMaxErr = t is ScriptAssertionError && (t.assertion as? Script.Cmd.Assertion.Unlinkable).let {
|
val isTableMaxErr = t is ScriptAssertionError && (t.assertion as? Script.Cmd.Assertion.Unlinkable).let {
|
||||||
it != null && it.failure == "maximum size larger than declared" &&
|
it != null && it.failure == "incompatible import type" &&
|
||||||
it.module.imports.singleOrNull()?.kind is Node.Import.Kind.Table
|
it.module.imports.singleOrNull()?.kind is Node.Import.Kind.Table
|
||||||
}
|
}
|
||||||
if (isTableMaxErr) "Table max capacities are not validated" else null
|
if (isTableMaxErr) "Table max capacities are not validated" else null
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
;; Conditionals w/ different load counts had bad stack diff
|
;; Conditionals w/ different load counts had bad stack diff
|
||||||
(func (export "testConditional") (param $p i32) (result i32)
|
(func (export "testConditional") (param $p i32) (result i32)
|
||||||
(get_local $p)
|
(get_local $p)
|
||||||
(if i32 (get_local $p)
|
(if (result i32) (get_local $p)
|
||||||
(then (i32.load (get_local $p)))
|
(then (i32.load (get_local $p)))
|
||||||
(else
|
(else
|
||||||
(i32.add
|
(i32.add
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 913fbf477ecf113f254ab27918a883257fff8bcb
|
Subproject commit 89573ee3eabc690637deeb1b8dadec13a963ec30
|
Loading…
x
Reference in New Issue
Block a user