[Wasm] Add Wasm IR

Intended to be used by Kotlin/Wasm compiler.

Includes:
 * IR tree: declarations, instructions, types.
 * Convertors: ir2text, ir2bin, bin2ir
 * Spec tests, to test convertors against reference Wabt tool
This commit is contained in:
Svyatoslav Kuzmich
2020-10-16 05:06:03 +03:00
parent 65cf991e84
commit 3be38d1796
20 changed files with 2964 additions and 4 deletions
+76
View File
@@ -0,0 +1,76 @@
plugins {
kotlin("jvm")
id("jps-compatible")
kotlin("plugin.serialization") version "1.4.10"
}
dependencies {
implementation(kotlinStdlib())
testImplementation(commonDep("junit:junit"))
testCompileOnly(project(":kotlin-test:kotlin-test-jvm"))
testCompileOnly(project(":kotlin-test:kotlin-test-junit"))
testImplementation(projectTests(":compiler:tests-common"))
testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0")
}
val testSuiteRevision = "18f8340"
val testSuiteDir = File(buildDir, "testsuite")
val testSuiteZip = File(testSuiteDir, testSuiteRevision + ".zip")
val downloadTestSuite by task<de.undercouch.gradle.tasks.download.Download> {
src("https://github.com/WebAssembly/testsuite/zipball/$testSuiteRevision")
dest(testSuiteZip)
overwrite(false)
}
val unzipTestSuite by task<Copy> {
dependsOn(downloadTestSuite)
from(zipTree(downloadTestSuite.get().dest))
into(testSuiteDir)
}
val wabtDir = File(buildDir, "wabt")
val wabtVersion = "1.0.19"
val downloadWabt by task<de.undercouch.gradle.tasks.download.Download> {
val gradleOs = org.gradle.internal.os.OperatingSystem.current()
val os = when {
gradleOs.isMacOsX -> "macos"
gradleOs.isWindows -> "windows"
gradleOs.isLinux -> "ubuntu"
else -> error("Unsupported OS: $gradleOs")
}
val fileName = "wabt-$wabtVersion-$os.tar.gz"
src("https://github.com/WebAssembly/wabt/releases/download/$wabtVersion/$fileName")
dest(File(wabtDir, fileName))
overwrite(false)
}
val unzipWabt by task<Copy> {
dependsOn(downloadWabt)
from(tarTree(resources.gzip(downloadWabt.get().dest)))
into(wabtDir)
}
sourceSets {
"main" { projectDefault() }
"test" { projectDefault() }
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
kotlinOptions.freeCompilerArgs += listOf(
"-Xopt-in=kotlin.ExperimentalUnsignedTypes",
"-Xskip-prerelease-check"
)
}
projectTest("test", true) {
dependsOn(unzipWabt)
dependsOn(unzipTestSuite)
systemProperty("wabt.bin.path", "$wabtDir/wabt-$wabtVersion/bin")
systemProperty("wasm.testsuite.path", "$testSuiteDir/WebAssembly-testsuite-$testSuiteRevision")
workingDir = projectDir
}
testsJar()
@@ -0,0 +1,167 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.wasm.ir
class WasmModule(
val functionTypes: List<WasmFunctionType> = emptyList(),
val structs: List<WasmStructDeclaration> = emptyList(),
val importsInOrder: List<WasmNamedModuleField> = emptyList(),
val importedFunctions: List<WasmFunction.Imported> = emptyList(),
val importedMemories: List<WasmMemory> = emptyList(),
val importedTables: List<WasmTable> = emptyList(),
val importedGlobals: List<WasmGlobal> = emptyList(),
val definedFunctions: List<WasmFunction.Defined> = emptyList(),
val tables: List<WasmTable> = emptyList(),
val memories: List<WasmMemory> = emptyList(),
val globals: List<WasmGlobal> = emptyList(),
val exports: List<WasmExport<*>> = emptyList(),
val elements: List<WasmElement> = emptyList(),
val startFunction: WasmFunction? = null,
val data: List<WasmData> = emptyList(),
val dataCount: Boolean = false,
)
sealed class WasmNamedModuleField {
var id: Int? = null
open val name: String = ""
}
sealed class WasmFunction(
override val name: String,
val type: WasmFunctionType
) : WasmNamedModuleField() {
class Defined(
name: String,
type: WasmFunctionType,
val locals: MutableList<WasmLocal> = mutableListOf(),
val instructions: MutableList<WasmInstr> = mutableListOf()
) : WasmFunction(name, type)
class Imported(
name: String,
type: WasmFunctionType,
val importPair: WasmImportPair
) : WasmFunction(name, type)
}
class WasmMemory(
val limits: WasmLimits = WasmLimits(1u, null),
val importPair: WasmImportPair? = null,
) : WasmNamedModuleField()
sealed class WasmDataMode {
class Active(
val memoryIdx: Int,
val offset: MutableList<WasmInstr>
) : WasmDataMode() {
constructor(memoryIdx: Int, offset: Int) : this(memoryIdx, mutableListOf<WasmInstr>().also<MutableList<WasmInstr>> {
WasmIrExpressionBuilder(it).buildConstI32(offset)
})
}
object Passive : WasmDataMode()
}
class WasmData(
val mode: WasmDataMode,
val bytes: ByteArray,
) : WasmNamedModuleField()
class WasmTable(
val limits: WasmLimits = WasmLimits(1u, null),
val elementType: WasmType,
val importPair: WasmImportPair? = null
) : WasmNamedModuleField() {
sealed class Value {
class Function(val function: WasmSymbol<WasmFunction>) : Value() {
constructor(function: WasmFunction) : this(WasmSymbol(function))
}
class Expression(val expr: List<WasmInstr>) : Value()
}
}
class WasmElement(
val type: WasmType,
val values: List<WasmTable.Value>,
val mode: Mode,
) : WasmNamedModuleField() {
sealed class Mode {
object Passive : Mode()
class Active(val table: WasmTable, val offset: List<WasmInstr>) : Mode()
object Declarative : Mode()
}
}
class WasmLocal(
val id: Int,
val name: String,
val type: WasmType,
val isParameter: Boolean
)
class WasmGlobal(
override val name: String,
val type: WasmType,
val isMutable: Boolean,
val init: List<WasmInstr>,
val importPair: WasmImportPair? = null
) : WasmNamedModuleField()
sealed class WasmExport<T : WasmNamedModuleField>(
val name: String,
val field: T,
val kind: Byte,
val keyword: String
) {
class Function(name: String, field: WasmFunction) : WasmExport<WasmFunction>(name, field, 0x0, "func")
class Table(name: String, field: WasmTable) : WasmExport<WasmTable>(name, field, 0x1, "table")
class Memory(name: String, field: WasmMemory) : WasmExport<WasmMemory>(name, field, 0x2, "memory")
class Global(name: String, field: WasmGlobal) : WasmExport<WasmGlobal>(name, field, 0x3, "global")
}
sealed class WasmTypeDeclaration(
override val name: String
) : WasmNamedModuleField()
class WasmFunctionType(
name: String,
val parameterTypes: List<WasmType>,
val resultTypes: List<WasmType>
) : WasmTypeDeclaration(name)
class WasmStructDeclaration(
name: String,
val fields: List<WasmStructFieldDeclaration>
) : WasmTypeDeclaration(name)
class WasmStructFieldDeclaration(
val name: String,
val type: WasmType,
val isMutable: Boolean
)
class WasmInstr(
val operator: WasmOp,
val immediates: List<WasmImmediate> = emptyList()
)
data class WasmLimits(
val minSize: UInt,
val maxSize: UInt?
)
data class WasmImportPair(
val moduleName: String,
val declarationName: String
)
@@ -0,0 +1,339 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.wasm.ir
import org.jetbrains.kotlin.wasm.ir.WasmImmediateKind.*
enum class WasmImmediateKind {
CONST_I32,
CONST_I64,
CONST_F32,
CONST_F64,
MEM_ARG,
BLOCK_TYPE,
FUNC_IDX,
LOCAL_IDX,
GLOBAL_IDX,
TYPE_IDX,
VAL_TYPE_VECTOR,
MEMORY_IDX,
DATA_IDX,
TABLE_IDX,
LABEL_IDX,
LABEL_IDX_VECTOR,
ELEM_IDX,
STRUCT_TYPE_IDX,
STRUCT_FIELD_IDX,
TYPE_IMM,
HEAP_TYPE
}
sealed class WasmImmediate {
class ConstI32(val value: Int) : WasmImmediate()
class ConstI64(val value: Long) : WasmImmediate()
class ConstF32(val rawBits: UInt) : WasmImmediate()
class ConstF64(val rawBits: ULong) : WasmImmediate()
class SymbolI32(val value: WasmSymbol<Int>) : WasmImmediate()
class MemArg(val align: UInt, val offset: UInt) : WasmImmediate()
sealed class BlockType : WasmImmediate() {
class Function(val type: WasmFunctionType) : BlockType()
class Value(val type: WasmType?) : BlockType()
}
class FuncIdx(val value: WasmSymbol<WasmFunction>) : WasmImmediate() {
constructor(value: WasmFunction) : this(WasmSymbol(value))
}
class LocalIdx(val value: WasmSymbol<WasmLocal>) : WasmImmediate() {
constructor(value: WasmLocal) : this(WasmSymbol(value))
}
class GlobalIdx(val value: WasmSymbol<WasmGlobal>) : WasmImmediate() {
constructor(value: WasmGlobal) : this(WasmSymbol(value))
}
class TypeIdx(val value: WasmSymbol<WasmTypeDeclaration>) : WasmImmediate() {
constructor(value: WasmTypeDeclaration) : this(WasmSymbol(value))
}
class ValTypeVector(val value: List<WasmType>) : WasmImmediate()
class MemoryIdx(val value: WasmSymbol<WasmMemory>) : WasmImmediate() {
constructor(value: WasmMemory) : this(WasmSymbol(value))
}
class DataIdx(val value: Int) : WasmImmediate()
class TableIdx(val value: Int) : WasmImmediate()
class LabelIdx(val value: Int) : WasmImmediate()
class LabelIdxVector(val value: List<Int>) : WasmImmediate()
class ElemIdx(val value: WasmElement) : WasmImmediate()
class StructType(val value: WasmSymbol<WasmStructDeclaration>) : WasmImmediate() {
constructor(value: WasmStructDeclaration) : this(WasmSymbol(value))
}
class StructFieldIdx(val value: WasmSymbol<Int>) : WasmImmediate()
class HeapType(val value: WasmHeapType) : WasmImmediate() {
constructor(type: WasmType) : this(type.getHeapType())
}
}
enum class WasmOp(
val mnemonic: String,
val opcode: Int,
val immediates: List<WasmImmediateKind> = emptyList()
) {
// Unary
I32_EQZ("i32.eqz", 0x45),
I64_EQZ("i64.eqz", 0x50),
I32_CLZ("i32.clz", 0x67),
I32_CTZ("i32.ctz", 0x68),
I32_POPCNT("i32.popcnt", 0x69),
I64_CLZ("i64.clz", 0x79),
I64_CTZ("i64.ctz", 0x7A),
I64_POPCNT("i64.popcnt", 0x7B),
F32_ABS("f32.abs", 0x8B),
F32_NEG("f32.neg", 0x8C),
F32_CEIL("f32.ceil", 0x8D),
F32_FLOOR("f32.floor", 0x8E),
F32_TRUNC("f32.trunc", 0x8F),
F32_NEAREST("f32.nearest", 0x90),
F32_SQRT("f32.sqrt", 0x91),
F64_ABS("f64.abs", 0x99),
F64_NEG("f64.neg", 0x9A),
F64_CEIL("f64.ceil", 0x9B),
F64_FLOOR("f64.floor", 0x9C),
F64_TRUNC("f64.trunc", 0x9D),
F64_NEAREST("f64.nearest", 0x9E),
F64_SQRT("f64.sqrt", 0x9F),
I32_WRAP_I64("i32.wrap_i64", 0xA7),
I32_TRUNC_F32_S("i32.trunc_f32_s", 0xA8),
I32_TRUNC_F32_U("i32.trunc_f32_u", 0xA9),
I32_TRUNC_F64_S("i32.trunc_f64_s", 0xAA),
I32_TRUNC_F64_U("i32.trunc_f64_u", 0xAB),
I64_EXTEND_I32_S("i64.extend_i32_s", 0xAC),
I64_EXTEND_I32_U("i64.extend_i32_u", 0xAD),
I64_TRUNC_F32_S("i64.trunc_f32_s", 0xAE),
I64_TRUNC_F32_U("i64.trunc_f32_u", 0xAF),
I64_TRUNC_F64_S("i64.trunc_f64_s", 0xB0),
I64_TRUNC_F64_U("i64.trunc_f64_u", 0xB1),
F32_CONVERT_I32_S("f32.convert_i32_s", 0xB2),
F32_CONVERT_I32_U("f32.convert_i32_u", 0xB3),
F32_CONVERT_I64_S("f32.convert_i64_s", 0xB4),
F32_CONVERT_I64_U("f32.convert_i64_u", 0xB5),
F32_DEMOTE_F64("f32.demote_f64", 0xB6),
F64_CONVERT_I32_S("f64.convert_i32_s", 0xB7),
F64_CONVERT_I32_U("f64.convert_i32_u", 0xB8),
F64_CONVERT_I64_S("f64.convert_i64_s", 0xB9),
F64_CONVERT_I64_U("f64.convert_i64_u", 0xBA),
F64_PROMOTE_F32("f64.promote_f32", 0xBB),
I32_REINTERPRET_F32("i32.reinterpret_f32", 0xBC),
I64_REINTERPRET_F64("i64.reinterpret_f64", 0xBD),
F32_REINTERPRET_I32("f32.reinterpret_i32", 0xBE),
F64_REINTERPRET_I64("f64.reinterpret_i64", 0xBF),
I32_EXTEND8_S("i32.extend8_s", 0xC0),
I32_EXTEND16_S("i32.extend16_s", 0xC1),
I64_EXTEND8_S("i64.extend8_s", 0xC2),
I64_EXTEND16_S("i64.extend16_s", 0xC3),
I64_EXTEND32_S("i64.extend32_s", 0xC4),
// Non-trapping float to int
I32_TRUNC_SAT_F32_S("i32.trunc_sat_f32_s", 0xFC_00),
I32_TRUNC_SAT_F32_U("i32.trunc_sat_f32_u", 0xFC_01),
I32_TRUNC_SAT_F64_S("i32.trunc_sat_f64_s", 0xFC_02),
I32_TRUNC_SAT_F64_U("i32.trunc_sat_f64_u", 0xFC_03),
I64_TRUNC_SAT_F32_S("i64.trunc_sat_f32_s", 0xFC_04),
I64_TRUNC_SAT_F32_U("i64.trunc_sat_f32_u", 0xFC_05),
I64_TRUNC_SAT_F64_S("i64.trunc_sat_f64_s", 0xFC_06),
I64_TRUNC_SAT_F64_U("i64.trunc_sat_f64_u", 0xFC_07),
// Binary
I32_EQ("i32.eq", 0x46),
I32_NE("i32.ne", 0x47),
I32_LT_S("i32.lt_s", 0x48),
I32_LT_U("i32.lt_u", 0x49),
I32_GT_S("i32.gt_s", 0x4A),
I32_GT_U("i32.gt_u", 0x4B),
I32_LE_S("i32.le_s", 0x4C),
I32_LE_U("i32.le_u", 0x4D),
I32_GE_S("i32.ge_s", 0x4E),
I32_GE_U("i32.ge_u", 0x4F),
I64_EQ("i64.eq", 0x51),
I64_NE("i64.ne", 0x52),
I64_LT_S("i64.lt_s", 0x53),
I64_LT_U("i64.lt_u", 0x54),
I64_GT_S("i64.gt_s", 0x55),
I64_GT_U("i64.gt_u", 0x56),
I64_LE_S("i64.le_s", 0x57),
I64_LE_U("i64.le_u", 0x58),
I64_GE_S("i64.ge_s", 0x59),
I64_GE_U("i64.ge_u", 0x5A),
F32_EQ("f32.eq", 0x5B),
F32_NE("f32.ne", 0x5C),
F32_LT("f32.lt", 0x5D),
F32_GT("f32.gt", 0x5E),
F32_LE("f32.le", 0x5F),
F32_GE("f32.ge", 0x60),
F64_EQ("f64.eq", 0x61),
F64_NE("f64.ne", 0x62),
F64_LT("f64.lt", 0x63),
F64_GT("f64.gt", 0x64),
F64_LE("f64.le", 0x65),
F64_GE("f64.ge", 0x66),
I32_ADD("i32.add", 0x6A),
I32_SUB("i32.sub", 0x6B),
I32_MUL("i32.mul", 0x6C),
I32_DIV_S("i32.div_s", 0x6D),
I32_DIV_U("i32.div_u", 0x6E),
I32_REM_S("i32.rem_s", 0x6F),
I32_REM_U("i32.rem_u", 0x70),
I32_AND("i32.and", 0x71),
I32_OR("i32.or", 0x72),
I32_XOR("i32.xor", 0x73),
I32_SHL("i32.shl", 0x74),
I32_SHR_S("i32.shr_s", 0x75),
I32_SHR_U("i32.shr_u", 0x76),
I32_ROTL("i32.rotl", 0x77),
I32_ROTR("i32.rotr", 0x78),
I64_ADD("i64.add", 0x7C),
I64_SUB("i64.sub", 0x7D),
I64_MUL("i64.mul", 0x7E),
I64_DIV_S("i64.div_s", 0x7F),
I64_DIV_U("i64.div_u", 0x80),
I64_REM_S("i64.rem_s", 0x81),
I64_REM_U("i64.rem_u", 0x82),
I64_AND("i64.and", 0x83),
I64_OR("i64.or", 0x84),
I64_XOR("i64.xor", 0x85),
I64_SHL("i64.shl", 0x86),
I64_SHR_S("i64.shr_s", 0x87),
I64_SHR_U("i64.shr_u", 0x88),
I64_ROTL("i64.rotl", 0x89),
I64_ROTR("i64.rotr", 0x8A),
F32_ADD("f32.add", 0x92),
F32_SUB("f32.sub", 0x93),
F32_MUL("f32.mul", 0x94),
F32_DIV("f32.div", 0x95),
F32_MIN("f32.min", 0x96),
F32_MAX("f32.max", 0x97),
F32_COPYSIGN("f32.copysign", 0x98),
F64_ADD("f64.add", 0xA0),
F64_SUB("f64.sub", 0xA1),
F64_MUL("f64.mul", 0xA2),
F64_DIV("f64.div", 0xA3),
F64_MIN("f64.min", 0xA4),
F64_MAX("f64.max", 0xA5),
F64_COPYSIGN("f64.copysign", 0xA6),
// Constants
I32_CONST("i32.const", 0x41, CONST_I32),
I64_CONST("i64.const", 0x42, CONST_I64),
F32_CONST("f32.const", 0x43, CONST_F32),
F64_CONST("f64.const", 0x44, CONST_F64),
// Load
I32_LOAD("i32.load", 0x28, MEM_ARG),
I64_LOAD("i64.load", 0x29, MEM_ARG),
F32_LOAD("f32.load", 0x2A, MEM_ARG),
F64_LOAD("f64.load", 0x2B, MEM_ARG),
I32_LOAD8_S("i32.load8_s", 0x2C, MEM_ARG),
I32_LOAD8_U("i32.load8_u", 0x2D, MEM_ARG),
I32_LOAD16_S("i32.load16_s", 0x2E, MEM_ARG),
I32_LOAD16_U("i32.load16_u", 0x2F, MEM_ARG),
I64_LOAD8_S("i64.load8_s", 0x30, MEM_ARG),
I64_LOAD8_U("i64.load8_u", 0x31, MEM_ARG),
I64_LOAD16_S("i64.load16_s", 0x32, MEM_ARG),
I64_LOAD16_U("i64.load16_u", 0x33, MEM_ARG),
I64_LOAD32_S("i64.load32_s", 0x34, MEM_ARG),
I64_LOAD32_U("i64.load32_u", 0x35, MEM_ARG),
// Store
I32_STORE("i32.store", 0x36, MEM_ARG),
I64_STORE("i64.store", 0x37, MEM_ARG),
F32_STORE("f32.store", 0x38, MEM_ARG),
F64_STORE("f64.store", 0x39, MEM_ARG),
I32_STORE8("i32.store8", 0x3A, MEM_ARG),
I32_STORE16("i32.store16", 0x3B, MEM_ARG),
I64_STORE8("i64.store8", 0x3C, MEM_ARG),
I64_STORE16("i64.store16", 0x3D, MEM_ARG),
I64_STORE32("i64.store32", 0x3E, MEM_ARG),
// Memory
MEMORY_SIZE("memory.size", 0x3F, MEMORY_IDX),
MEMORY_GROW("memory.grow", 0x40, MEMORY_IDX),
MEMORY_INIT("memory.init", 0xFC_08, listOf(DATA_IDX, MEMORY_IDX)),
DATA_DROP("data.drop", 0xFC_09, DATA_IDX),
MEMORY_COPY("memory.copy", 0xFC_0A, listOf(MEMORY_IDX, MEMORY_IDX)),
MEMORY_FILL("memory.fill", 0xFC_0B, MEMORY_IDX),
// Table
TABLE_GET("table.get", 0x25, TABLE_IDX),
TABLE_SET("table.set", 0x26, TABLE_IDX),
TABLE_GROW("table.grow", 0xFC_0F, TABLE_IDX),
TABLE_SIZE("table.size", 0xFC_10, TABLE_IDX),
TABLE_FILL("table.fill", 0xFC_11, TABLE_IDX),
TABLE_INIT("table.init", 0xFC_0C, listOf(ELEM_IDX, TABLE_IDX)),
ELEM_DROP("elem.drop", 0xFC_0D, ELEM_IDX),
TABLE_COPY("table.copy", 0xFC_0E, listOf(TABLE_IDX, TABLE_IDX)),
// Control
UNREACHABLE("unreachable", 0x00),
NOP("nop", 0x01),
BLOCK("block", 0x02, BLOCK_TYPE),
LOOP("loop", 0x03, BLOCK_TYPE),
IF("if", 0x04, BLOCK_TYPE),
ELSE("else", 0x05),
END("end", 0x0B),
BR("br", 0x0C, LABEL_IDX),
BR_IF("br_if", 0x0D, LABEL_IDX),
BR_TABLE("br_table", 0x0E, listOf(LABEL_IDX_VECTOR, LABEL_IDX)),
RETURN("return", 0x0F),
CALL("call", 0x10, FUNC_IDX),
CALL_INDIRECT("call_indirect", 0x11, listOf(TYPE_IDX, TABLE_IDX)),
// Parametric
DROP("drop", 0x1A),
SELECT("select", 0x1B),
SELECT_TYPED("select", 0x1C, VAL_TYPE_VECTOR),
// Variable OP
LOCAL_GET("local.get", 0x20, LOCAL_IDX),
LOCAL_SET("local.set", 0x21, LOCAL_IDX),
LOCAL_TEE("local.tee", 0x22, LOCAL_IDX),
GLOBAL_GET("global.get", 0x23, GLOBAL_IDX),
GLOBAL_SET("global.set", 0x24, GLOBAL_IDX),
// Reference types
REF_NULL("ref.null", 0xD0, HEAP_TYPE),
REF_IS_NULL("ref.is_null", 0xD1),
REF_EQ("ref.eq", 0xD5),
REF_FUNC("ref.func", 0xD2, FUNC_IDX),
// GC
STRUCT_NEW_WITH_RTT("struct.new_with_rtt", 0xFB_01, STRUCT_TYPE_IDX),
STRUCT_GET("struct.get", 0xFB_03, listOf(STRUCT_TYPE_IDX, STRUCT_FIELD_IDX)),
STRUCT_SET("struct.set", 0xFB_06, listOf(STRUCT_TYPE_IDX, STRUCT_FIELD_IDX)),
REF_CAST("ref.cast", 0xFB_41, listOf(HEAP_TYPE, HEAP_TYPE)),
RTT_CANON("rtt.canon", 0xFB_30, HEAP_TYPE),
RTT_SUB("rtt.sub", 0xFB_31, HEAP_TYPE);
constructor(mnemonic: String, opcode: Int, vararg immediates: WasmImmediateKind) : this(mnemonic, opcode, immediates.toList())
}
val opcodesToOp: Map<Int, WasmOp> =
enumValues<WasmOp>().associateBy { it.opcode }
@@ -0,0 +1,35 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.wasm.ir
// Late binding box
interface WasmSymbolReadOnly<out T : Any> {
val owner: T
}
class WasmSymbol<out T : Any>(owner: T? = null) : WasmSymbolReadOnly<T> {
private var _owner: Any? = owner
@Suppress("UNCHECKED_CAST")
override val owner: T
get() = _owner as? T
?: error("Unbound wasm symbol $this")
@Suppress("UNCHECKED_CAST")
fun bind(value: Any) {
_owner = value as T
}
override fun equals(other: Any?): Boolean =
other is WasmSymbol<*> && _owner == other._owner
override fun hashCode(): Int =
_owner.hashCode()
override fun toString(): String =
_owner?.toString() ?: "UNBOUND-WASM-SYMBOL"
}
@@ -0,0 +1,74 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.wasm.ir
sealed class WasmType(
val name: String,
val code: Byte
) {
override fun toString(): String = name
}
object WasmUnreachableType : WasmType("unreachable", -0x40)
object WasmI32 : WasmType("i32", -0x1)
object WasmI1 : WasmType("i32", -0x1)
object WasmI64 : WasmType("i64", -0x2)
object WasmF32 : WasmType("f32", -0x3)
object WasmF64 : WasmType("f64", -0x4)
object WasmV128 : WasmType("v128", -0x5)
object WasmI8 : WasmType("i8", -0x6)
object WasmI16 : WasmType("i8", -0x7)
object WasmFuncRef : WasmType("funcref", -0x10)
object WasmExternRef : WasmType("externref", -0x11)
object WasmAnyRef : WasmType("anyref", -0x12)
object WasmEqRef : WasmType("eqref", -0x13)
class WasmRefNullType(val heapType: WasmHeapType) : WasmType("optref", -0x14)
class WasmRefType(val heapType: WasmHeapType) : WasmType("ref", -0x15)
@Suppress("unused")
object WasmI31Ref : WasmType("i31ref", -0x16)
class WasmRtt(val depth: Int, val heapType: WasmHeapType) : WasmType("rtt", -0x17)
@Suppress("unused")
object WasmExnRef : WasmType("exnref", -0x18)
sealed class WasmHeapType {
class Type(val type: WasmSymbolReadOnly<WasmTypeDeclaration>) : WasmHeapType() {
override fun toString(): String {
return "Type:$type"
}
}
sealed class Simple(val name: String, val code: Byte) : WasmHeapType() {
object Func : Simple("func", -0x10)
object Extern : Simple("extern", -0x11)
object Eq : Simple("eq", -0x13)
@Suppress("unused")
object ExnH : Simple("exn", -0x18)
override fun toString(): String {
return "Simple:$name(${code.toString(16)})"
}
}
}
sealed class WasmBlockType {
class Function(val type: WasmFunctionType) : WasmBlockType()
class Value(val type: WasmType) : WasmBlockType()
}
fun WasmType.getHeapType(): WasmHeapType =
when (this) {
is WasmRefType -> heapType
is WasmRefNullType -> heapType
is WasmEqRef -> WasmHeapType.Simple.Eq
is WasmExternRef -> WasmHeapType.Simple.Extern
is WasmFuncRef -> WasmHeapType.Simple.Func
else -> error("Unknown heap type for type $this")
}
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.wasm.ir
/**
* Calculate declaration IDs of linked wasm module
*/
fun WasmModule.calculateIds() {
fun List<WasmNamedModuleField>.calculateIds(startIndex: Int = 0) {
for ((index, field) in this.withIndex()) {
field.id = index + startIndex
}
}
functionTypes.calculateIds()
structs.calculateIds(startIndex = functionTypes.size)
importedFunctions.calculateIds()
importedMemories.calculateIds()
importedTables.calculateIds()
importedGlobals.calculateIds()
elements.calculateIds()
definedFunctions.calculateIds(startIndex = importedFunctions.size)
globals.calculateIds(startIndex = importedGlobals.size)
memories.calculateIds(startIndex = importedMemories.size)
tables.calculateIds(startIndex = importedTables.size)
}
@@ -0,0 +1,141 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.wasm.ir
interface WasmExpressionBuilder {
fun buildInstr(op: WasmOp, vararg immediates: WasmImmediate)
var numberOfNestedBlocks: Int
fun buildConstI32(value: Int) {
buildInstr(WasmOp.I32_CONST, WasmImmediate.ConstI32(value))
}
fun buildConstI64(value: Long) {
buildInstr(WasmOp.I64_CONST, WasmImmediate.ConstI64(value))
}
fun buildConstF32(value: Float) {
buildInstr(WasmOp.F32_CONST, WasmImmediate.ConstF32(value.toRawBits().toUInt()))
}
fun buildConstF64(value: Double) {
buildInstr(WasmOp.F64_CONST, WasmImmediate.ConstF64(value.toRawBits().toULong()))
}
fun buildConstI32Symbol(value: WasmSymbol<Int>) {
buildInstr(WasmOp.I32_CONST, WasmImmediate.SymbolI32(value))
}
fun buildUnreachable() {
buildInstr(WasmOp.UNREACHABLE)
}
fun buildBlock(label: String?, resultType: WasmType? = null) {
numberOfNestedBlocks++
buildInstr(WasmOp.BLOCK, WasmImmediate.BlockType.Value(resultType))
}
fun buildLoop(label: String?, resultType: WasmType? = null) {
numberOfNestedBlocks++
buildInstr(WasmOp.LOOP, WasmImmediate.BlockType.Value(resultType))
}
fun buildIf(label: String?, resultType: WasmType? = null) {
numberOfNestedBlocks++
buildInstr(WasmOp.IF, WasmImmediate.BlockType.Value(resultType))
}
fun buildElse() {
buildInstr(WasmOp.ELSE)
}
fun buildEnd() {
numberOfNestedBlocks--
buildInstr(WasmOp.END)
}
fun buildBr(absoluteBlockLevel: Int) {
val relativeLevel = numberOfNestedBlocks - absoluteBlockLevel
assert(relativeLevel >= 0) { "Negative relative block index" }
buildInstr(WasmOp.BR, WasmImmediate.LabelIdx(relativeLevel))
}
fun buildBrIf(absoluteBlockLevel: Int) {
val relativeLevel = numberOfNestedBlocks - absoluteBlockLevel
assert(relativeLevel >= 0) { "Negative relative block index" }
buildInstr(WasmOp.BR_IF, WasmImmediate.LabelIdx(relativeLevel))
}
fun buildCall(symbol: WasmSymbol<WasmFunction>) {
buildInstr(WasmOp.CALL, WasmImmediate.FuncIdx(symbol))
}
fun buildCallIndirect(symbol: WasmSymbol<WasmFunctionType>) {
buildInstr(
WasmOp.CALL_INDIRECT,
WasmImmediate.TypeIdx(symbol),
WasmImmediate.TableIdx(0)
)
}
fun buildGetLocal(local: WasmLocal) {
buildInstr(WasmOp.LOCAL_GET, WasmImmediate.LocalIdx(local))
}
fun buildSetLocal(local: WasmLocal) {
buildInstr(WasmOp.LOCAL_SET, WasmImmediate.LocalIdx(local))
}
fun buildGetGlobal(global: WasmSymbol<WasmGlobal>) {
buildInstr(WasmOp.GLOBAL_GET, WasmImmediate.GlobalIdx(global))
}
fun buildSetGlobal(global: WasmSymbol<WasmGlobal>) {
buildInstr(WasmOp.GLOBAL_SET, WasmImmediate.GlobalIdx(global))
}
fun buildStructGet(struct: WasmSymbol<WasmStructDeclaration>, fieldId: WasmSymbol<Int>) {
buildInstr(
WasmOp.STRUCT_GET,
WasmImmediate.StructType(struct),
WasmImmediate.StructFieldIdx(fieldId)
)
}
fun buildStructNew(struct: WasmSymbol<WasmStructDeclaration>) {
buildInstr(WasmOp.STRUCT_NEW_WITH_RTT, WasmImmediate.StructType(struct))
}
fun buildStructSet(struct: WasmSymbol<WasmStructDeclaration>, fieldId: WasmSymbol<Int>) {
buildInstr(
WasmOp.STRUCT_SET,
WasmImmediate.StructType(struct),
WasmImmediate.StructFieldIdx(fieldId)
)
}
fun buildStructNarrow(fromType: WasmType, toType: WasmType) {
buildInstr(
WasmOp.REF_CAST,
WasmImmediate.HeapType(fromType.getHeapType()),
WasmImmediate.HeapType(toType.getHeapType())
)
}
fun buildRefNull(type: WasmHeapType) {
buildInstr(WasmOp.REF_NULL, WasmImmediate.HeapType(WasmRefType(type)))
}
fun buildRttSub(heapType: WasmType) {
buildInstr(WasmOp.RTT_SUB, WasmImmediate.HeapType(heapType))
}
fun buildRttCanon(heapType: WasmType) {
buildInstr(WasmOp.RTT_CANON, WasmImmediate.HeapType(heapType))
}
}
@@ -0,0 +1,22 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.wasm.ir
class WasmIrExpressionBuilder(
val expression: MutableList<WasmInstr>
) : WasmExpressionBuilder {
override fun buildInstr(op: WasmOp, vararg immediates: WasmImmediate) {
expression.add(WasmInstr(op, immediates.toList()))
}
override var numberOfNestedBlocks: Int = 0
set(value) {
assert(value >= 0) { "end without matching block" }
field = value
}
}
@@ -0,0 +1,645 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("MemberVisibilityCanBePrivate", "MemberVisibilityCanBePrivate")
package org.jetbrains.kotlin.wasm.ir.convertors
import org.jetbrains.kotlin.wasm.ir.*
import java.nio.ByteBuffer
class WasmBinaryToIR(val b: MyByteReader) {
val validVersion = 1u
val functionTypes: MutableList<WasmFunctionType> = mutableListOf()
val structs: MutableList<WasmStructDeclaration> = mutableListOf()
val importsInOrder: MutableList<WasmNamedModuleField> = mutableListOf()
val importedFunctions: MutableList<WasmFunction.Imported> = mutableListOf()
val importedMemories: MutableList<WasmMemory> = mutableListOf()
val importedTables: MutableList<WasmTable> = mutableListOf()
val importedGlobals: MutableList<WasmGlobal> = mutableListOf()
val definedFunctions: MutableList<WasmFunction.Defined> = mutableListOf()
val table: MutableList<WasmTable> = mutableListOf()
val memory: MutableList<WasmMemory> = mutableListOf()
val globals: MutableList<WasmGlobal> = mutableListOf()
val exports: MutableList<WasmExport<*>> = mutableListOf()
var startFunction: WasmFunction? = null
val elements: MutableList<WasmElement> = mutableListOf()
val data: MutableList<WasmData> = mutableListOf()
var dataCount: Boolean = false
private fun <T> byIdx(l1: List<T>, l2: List<T>, index: Int): T {
if (index < l1.size)
return l1[index]
return l2[index - l1.size]
}
private fun funByIdx(index: Int) = byIdx(importedFunctions, definedFunctions, index)
private fun memoryByIdx(index: Int) = byIdx(importedMemories, memory, index)
private fun elemByIdx(index: Int) = elements[index]
private fun tableByIdx(index: Int) = byIdx(importedTables, table, index)
private fun globalByIdx(index: Int) = byIdx(importedGlobals, globals, index)
fun parseModule(): WasmModule {
if (b.readUInt32() != 0x6d736100u)
error("InvalidMagicNumber")
val version = b.readUInt32()
if (version != validVersion)
error("InvalidVersion(version.toLong(), listOf(validVersion.toLong()))")
var maxSectionId = 0
while (true) {
val sectionId = try {
b.readVarUInt7().toInt()
} catch (e: Throwable) { // Unexpected end
break
}
if (sectionId > 12) error("InvalidSectionId(sectionId)")
require(sectionId == 12 || maxSectionId == 12 || sectionId == 0 || sectionId > maxSectionId) {
"Section ID $sectionId came after $maxSectionId"
}
maxSectionId = maxOf(sectionId, maxSectionId)
val sectionLength = b.readVarUInt32AsInt()
b.limitSize(sectionLength, "Wasm section $sectionId of size $sectionLength") {
when (sectionId) {
// Skip custom section
0 -> b.readBytes(sectionLength)
// Type section
1 -> {
forEachVectorElement {
when (val type = readTypeDeclaration()) {
is WasmFunctionType ->
functionTypes += type
is WasmStructDeclaration ->
structs += type
}
}
}
// Import section
2 -> {
forEachVectorElement {
val importPair = WasmImportPair(readString(), readString())
when (val kind = b.readByte().toInt()) {
0 -> {
val type = functionTypes[b.readVarUInt32AsInt()]
importedFunctions += WasmFunction.Imported(
name = "",
type = type,
importPair = importPair,
).also { importsInOrder.add(it) }
}
// Table
1 -> {
val elementType = readRefType()
val limits = readLimits()
importedTables.add(WasmTable(limits, elementType, importPair).also { importsInOrder.add(it) })
}
2 -> {
val limits = readLimits()
importedMemories.add(WasmMemory(limits, importPair).also { importsInOrder.add(it) })
}
3 -> {
importedGlobals.add(
WasmGlobal(
name = "",
type = readValueType(),
isMutable = b.readVarUInt1(),
init = emptyList(),
importPair = importPair
).also { importsInOrder.add(it) }
)
}
else -> error(
"Unsupported import kind $kind"
)
}
}
}
// Function section
3 -> {
forEachVectorElement {
val functionType = functionTypes[b.readVarUInt32AsInt()]
definedFunctions.add(
WasmFunction.Defined(
"",
functionType,
locals = functionType.parameterTypes.mapIndexed { index, wasmType ->
WasmLocal(index, "", wasmType, true)
}.toMutableList()
)
)
}
}
// Table section
4 -> {
forEachVectorElement {
val elementType = readRefType()
val limits = readLimits()
table.add(
WasmTable(limits, elementType)
)
}
}
// Memory section
5 -> {
forEachVectorElement {
val limits = readLimits()
memory.add(WasmMemory(limits))
}
}
// Globals section
6 -> {
forEachVectorElement {
val expr = mutableListOf<WasmInstr>()
globals.add(
WasmGlobal(
name = "",
type = readValueType(),
isMutable = b.readVarUInt1(),
init = expr
)
)
readExpression(expr)
}
}
// Export section
7 -> {
forEachVectorElement {
val name = readString()
val kind = b.readByte().toInt()
val index = b.readVarUInt32AsInt()
exports.add(
when (kind) {
0 -> WasmExport.Function(name, funByIdx(index))
1 -> WasmExport.Table(name, tableByIdx(index))
2 -> WasmExport.Memory(name, memoryByIdx(index))
3 -> WasmExport.Global(name, globalByIdx(index))
else -> error("Invalid export kind $kind")
}
)
}
}
// Start section
8 -> {
require(startFunction == null) { "Start function is already defined" }
startFunction = funByIdx(b.readVarUInt32AsInt())
}
// Element section
9 -> {
forEachVectorElement {
val firstByte = b.readUByte().toInt()
val mode: WasmElement.Mode = when (firstByte) {
0, 4 -> {
val offset = readExpression()
WasmElement.Mode.Active(tableByIdx(0), offset)
}
1, 5 ->
WasmElement.Mode.Passive
2, 6 -> {
val tableIdx = b.readVarUInt32()
val offset = readExpression()
WasmElement.Mode.Active(tableByIdx(tableIdx.toInt()), offset)
}
3, 7 ->
WasmElement.Mode.Declarative
else ->
error("Invalid element first byte $firstByte")
}
val type = if (firstByte < 5) {
if (firstByte in 1..3) {
val elemKind = b.readByte()
require(elemKind == 0.toByte())
}
WasmFuncRef
} else {
readValueType()
}
val values: List<WasmTable.Value> = mapVector {
if (firstByte < 4) {
WasmTable.Value.Function(funByIdx(b.readVarUInt32AsInt()))
} else {
val exprBody = mutableListOf<WasmInstr>()
readExpression(exprBody)
WasmTable.Value.Expression(exprBody)
}
}
elements += WasmElement(
type,
values,
mode,
)
}
}
// Code section
10 -> {
forEachVectorElement { functionId ->
val function = definedFunctions[functionId.toInt()]
val size = b.readVarUInt32AsInt()
b.limitSize(size, "function body size") {
mapVector {
val count = b.readVarUInt32AsInt()
val valueType = readValueType()
val firstLocalId =
function.locals.lastOrNull()?.id?.plus(1) ?: 0
repeat(count) { thisIdx ->
function.locals.add(
WasmLocal(
firstLocalId + thisIdx,
"",
valueType,
false
)
)
}
}
readExpression(function.instructions, function.locals)
}
}
}
// Data section
11 -> {
forEachVectorElement {
val mode = when (val firstByte = b.readByte().toInt()) {
0 -> WasmDataMode.Active(0, readExpression())
1 -> WasmDataMode.Passive
2 -> WasmDataMode.Active(b.readVarUInt32AsInt(), readExpression())
else -> error("Unsupported data mode $firstByte")
}
val size = b.readVarUInt32AsInt()
val bytes = b.readBytes(size)
data += WasmData(mode, bytes)
}
}
// Data count section
12 -> {
b.readVarUInt32() // Data count
dataCount = true
}
}
}
}
return WasmModule(
functionTypes = functionTypes,
structs = structs,
importsInOrder = importsInOrder,
importedFunctions = importedFunctions,
importedMemories = importedMemories,
importedTables = importedTables,
importedGlobals = importedGlobals,
definedFunctions = definedFunctions,
tables = table,
memories = memory,
globals = globals,
exports = exports,
startFunction = startFunction,
elements = elements,
data = data,
dataCount = dataCount
)
}
private fun readLimits(): WasmLimits {
val hasMax = b.readVarUInt1()
return WasmLimits(
minSize = b.readVarUInt32(),
maxSize = if (hasMax) b.readVarUInt32() else null
)
}
private fun readExpression(): MutableList<WasmInstr> =
mutableListOf<WasmInstr>().also { readExpression(it) }
private fun readExpression(instructions: MutableList<WasmInstr>, locals: List<WasmLocal> = emptyList()) {
var blockCount = 0
while (true) {
require(blockCount >= 0)
val inst = readInstruction(locals)
when (inst.operator) {
WasmOp.END -> {
// Last instruction in expression is end.
if (blockCount == 0) {
return
}
blockCount--
}
WasmOp.BLOCK, WasmOp.LOOP, WasmOp.IF -> {
blockCount++
}
else -> {
}
}
instructions.add(inst)
}
}
private fun readInstruction(locals: List<WasmLocal>): WasmInstr {
val firstByte = b.readByte().toUByte().toInt()
val opcode = if (firstByte in twoByteOpcodes) {
val secondByte = b.readByte().toUByte().toInt()
(firstByte shl 8) + secondByte
} else {
firstByte
}
val op = opcodesToOp[opcode]
?: error("Wrong opcode 0x${opcode.toString(16)}")
val immediates = op.immediates.map {
when (it) {
WasmImmediateKind.CONST_I32 -> WasmImmediate.ConstI32(b.readVarInt32())
WasmImmediateKind.CONST_I64 -> WasmImmediate.ConstI64(b.readVarInt64())
WasmImmediateKind.CONST_F32 -> WasmImmediate.ConstF32(b.readUInt32())
WasmImmediateKind.CONST_F64 -> WasmImmediate.ConstF64(b.readUInt64())
WasmImmediateKind.MEM_ARG -> {
WasmImmediate.MemArg(
align = b.readVarUInt32(),
offset = b.readVarUInt32()
)
}
WasmImmediateKind.BLOCK_TYPE -> readBlockType()
WasmImmediateKind.FUNC_IDX -> WasmImmediate.FuncIdx(funByIdx(b.readVarUInt32AsInt()))
WasmImmediateKind.LOCAL_IDX -> WasmImmediate.LocalIdx(locals[b.readVarUInt32AsInt()])
WasmImmediateKind.GLOBAL_IDX -> WasmImmediate.GlobalIdx(globalByIdx(b.readVarUInt32AsInt()))
WasmImmediateKind.TYPE_IDX -> WasmImmediate.TypeIdx(functionTypes[b.readVarUInt32AsInt()])
WasmImmediateKind.MEMORY_IDX -> WasmImmediate.MemoryIdx(memoryByIdx(b.readVarUInt32AsInt()))
WasmImmediateKind.DATA_IDX -> WasmImmediate.DataIdx(b.readVarUInt32AsInt())
WasmImmediateKind.TABLE_IDX -> WasmImmediate.TableIdx(b.readVarUInt32AsInt())
WasmImmediateKind.LABEL_IDX -> WasmImmediate.LabelIdx(b.readVarUInt32AsInt())
WasmImmediateKind.LABEL_IDX_VECTOR -> WasmImmediate.LabelIdxVector(mapVector { b.readVarUInt32AsInt() })
WasmImmediateKind.ELEM_IDX -> WasmImmediate.ElemIdx(elemByIdx(b.readVarUInt32AsInt()))
WasmImmediateKind.VAL_TYPE_VECTOR -> WasmImmediate.ValTypeVector(mapVector { readValueType() })
WasmImmediateKind.STRUCT_TYPE_IDX -> TODO()
WasmImmediateKind.STRUCT_FIELD_IDX -> TODO()
WasmImmediateKind.TYPE_IMM -> TODO()
WasmImmediateKind.HEAP_TYPE -> WasmImmediate.HeapType(readRefType())
}
}
return WasmInstr(op, immediates)
}
private fun readTypeDeclaration(): WasmTypeDeclaration {
when (b.readVarInt7()) {
(-0x20).toByte() -> {
val types = mapVector { readValueType() }
val returnTypes = mapVector { readValueType() }
return WasmFunctionType("", types, returnTypes)
}
else -> TODO()
}
}
private val codeToSimpleValueType: Map<Byte, WasmType> = listOf(
WasmI32,
WasmI64,
WasmF32,
WasmF64,
WasmV128,
WasmI8,
WasmI16,
WasmFuncRef,
WasmExternRef,
WasmAnyRef,
WasmEqRef
).associateBy { it.code }
private fun readValueType(): WasmType {
val code = b.readVarInt7()
return readValueTypeImpl(code)
}
private fun readBlockType(): WasmImmediate.BlockType {
val code = b.readVarInt64()
return when {
code >= 0 -> WasmImmediate.BlockType.Function(functionTypes[code.toInt()])
code == -0x40L -> WasmImmediate.BlockType.Value(null)
else -> WasmImmediate.BlockType.Value(readValueTypeImpl(code.toByte()))
}
}
private fun readRefType(): WasmType {
val code = b.readByte()
return when (code.toInt()) {
0x70 -> WasmFuncRef
0x6F -> WasmExternRef
else -> error("Unsupported heap type ${code.toString(16)}")
}
}
private fun readValueTypeImpl(code: Byte): WasmType {
codeToSimpleValueType[code]?.let {
return it
}
error("InvalidType 0x${code.toString(16)}")
}
private inline fun forEachVectorElement(block: (index: UInt) -> Unit) {
val size = b.readVarUInt32()
for (index in 0u until size) {
block(index)
}
}
private inline fun <T> mapVector(block: (index: UInt) -> T): List<T> {
return (0u until b.readVarUInt32()).map { block(it) }
}
private fun MyByteReader.readVarUInt32AsInt() =
this.readVarUInt32().toInt()
fun readString() = b.readVarUInt32AsInt().let {
// We have to use the decoder directly to get malformed-input errors
Charsets.UTF_8.newDecoder().decode(ByteBuffer.wrap(b.readBytes(it))).toString()
}
}
class MyByteReader(val ins: java.io.InputStream) : ByteReader() {
var offset: Long = 0
class SizeLimit(val maxSize: Long, val reason: String)
var sizeLimits = mutableListOf(SizeLimit(Long.MAX_VALUE, "Root"))
var currentMaxSize: Long = Long.MAX_VALUE
override val isEof: Boolean
get() {
error("Not implemented")
}
override fun read(amount: Int): ByteReader {
error("Not implemented")
}
@OptIn(ExperimentalStdlibApi::class)
inline fun limitSize(size: Int, reason: String, block: () -> Unit) {
val maxSize = offset + size
sizeLimits.add(SizeLimit(maxSize, reason))
currentMaxSize = maxSize
block()
require(offset == currentMaxSize) {
"Ending size-limited block \"$reason\". We haven't read all $size bytes."
}
sizeLimits.removeLast()
currentMaxSize = sizeLimits.last().maxSize
}
override fun readByte(): Byte {
val b = ins.read()
if (b == -1)
error("UnexpectedEnd")
offset++
if (offset > currentMaxSize) {
error("Reading bytes past limit $currentMaxSize Reason: ${sizeLimits.last().reason}")
}
return b.toByte()
}
override fun readBytes(amount: Int?): ByteArray {
require(amount != null)
return ByteArray(amount) { readByte() }
}
}
// First byte of two byte opcodes
val twoByteOpcodes: Set<Int> =
opcodesToOp.keys.filter { it > 0xFF }.map { it ushr 8 }.toSet()
abstract class ByteReader {
abstract val isEof: Boolean
// Slices the next set off as its own and moves the position up that much
abstract fun read(amount: Int): ByteReader
abstract fun readByte(): Byte
abstract fun readBytes(amount: Int? = null): ByteArray
fun readUByte(): UByte =
readByte().toUByte()
fun readUInt32(): UInt =
readUByte().toUInt() or
(readUByte().toUInt() shl 8) or
(readUByte().toUInt() shl 16) or
(readUByte().toUInt() shl 24)
fun readUInt64(): ULong =
readUByte().toULong() or
(readUByte().toULong() shl 8) or
(readUByte().toULong() shl 16) or
(readUByte().toULong() shl 24) or
(readUByte().toULong() shl 32) or
(readUByte().toULong() shl 40) or
(readUByte().toULong() shl 48) or
(readUByte().toULong() shl 56)
fun readVarInt7() = readSignedLeb128().let {
if (it < Byte.MIN_VALUE.toLong() || it > Byte.MAX_VALUE.toLong()) error("InvalidLeb128Number")
it.toByte()
}
fun readVarInt32() = readSignedLeb128().let {
if (it < Int.MIN_VALUE.toLong() || it > Int.MAX_VALUE.toLong()) error("InvalidLeb128Number")
it.toInt()
}
fun readVarInt64() = readSignedLeb128(9)
fun readVarUInt1() = readUnsignedLeb128().let {
if (it != 1u && it != 0u) error("InvalidLeb128Number")
it == 1u
}
fun readVarUInt7() = readUnsignedLeb128().let {
if (it > 255u) error("InvalidLeb128Number")
it.toShort()
}
fun readVarUInt32() = readUnsignedLeb128()
protected fun readUnsignedLeb128(maxCount: Int = 4): UInt {
// Taken from Android source, Apache licensed
var result = 0u
var cur: UInt
var count = 0
do {
cur = readUByte().toUInt() and 0xffu
result = result or ((cur and 0x7fu) shl (count * 7))
count++
} while (cur and 0x80u == 0x80u && count <= maxCount)
if (cur and 0x80u == 0x80u) error("InvalidLeb128Number")
return result
}
private fun readSignedLeb128(maxCount: Int = 4): Long {
// Taken from Android source, Apache licensed
var result = 0L
var cur: Int
var count = 0
var signBits = -1L
do {
cur = readByte().toInt() and 0xff
result = result or ((cur and 0x7f).toLong() shl (count * 7))
signBits = signBits shl 7
count++
} while (cur and 0x80 == 0x80 && count <= maxCount)
if (cur and 0x80 == 0x80) error("InvalidLeb128Number")
// Check for 64 bit invalid, taken from Apache/MIT licensed:
// https://github.com/paritytech/parity-wasm/blob/2650fc14c458c6a252c9dc43dd8e0b14b6d264ff/src/elements/primitives.rs#L351
// TODO: probably need 32 bit checks too, but meh, not in the suite
if (count > maxCount && maxCount == 9) {
if (cur and 0b0100_0000 == 0b0100_0000) {
if ((cur or 0b1000_0000).toByte() != (-1).toByte()) error("InvalidLeb128Number")
} else if (cur != 0) {
error("InvalidLeb128Number")
}
}
if ((signBits shr 1) and result != 0L) result = result or signBits
return result
}
}
@@ -0,0 +1,511 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:OptIn(ExperimentalUnsignedTypes::class)
package org.jetbrains.kotlin.wasm.ir.convertors
import org.jetbrains.kotlin.wasm.ir.*
import java.io.ByteArrayOutputStream
import java.io.OutputStream
class WasmBinaryBuilder(outputStream: OutputStream, val module: WasmModule) {
var b: ByteWriter = ByteWriter.OutputStream(outputStream)
fun appendWasmModule() {
b.writeUInt32(0x6d736100u) // WebAssembly magic
b.writeUInt32(1u) // version
with(module) {
// type section
appendSection(1u) {
appendVectorSize(functionTypes.size + structs.size)
functionTypes.forEach { appendFunctionTypeDeclaration(it) }
structs.forEach { appendStructTypeDeclaration(it) }
}
// import section
appendSection(2u) {
appendVectorSize(importsInOrder.size)
importsInOrder.forEach {
when (it) {
is WasmFunction.Imported -> appendImportedFunction(it)
is WasmMemory -> appendMemory(it)
is WasmTable -> appendTable(it)
is WasmGlobal -> appendGlobal(it)
else -> error("Unknown import kind ${it::class}")
}
}
}
// function section
appendSection(3u) {
appendVectorSize(definedFunctions.size)
definedFunctions.forEach { appendDefinedFunction(it) }
}
// table section
appendSection(4u) {
appendVectorSize(tables.size)
tables.forEach { appendTable(it) }
}
// memory section
appendSection(5u) {
appendVectorSize(memories.size)
memories.forEach { appendMemory(it) }
}
// Good
appendSection(6u) {
appendVectorSize(globals.size)
globals.forEach { appendGlobal(it) }
}
appendSection(7u) {
appendVectorSize(exports.size)
exports.forEach { appendExport(it) }
}
if (startFunction != null) {
appendSection(8u) {
appendStartFunction(startFunction)
}
}
// element section
appendSection(9u) {
appendVectorSize(elements.size)
elements.forEach { appendElement(it) }
}
if (dataCount) {
appendSection(12u) {
b.writeVarUInt32(data.size.toUInt())
}
}
// code section
appendSection(10u) {
appendVectorSize(definedFunctions.size)
definedFunctions.forEach { appendCode(it) }
}
appendSection(11u) {
appendVectorSize(data.size)
data.forEach { appendData(it) }
}
}
}
private fun appendInstr(instr: WasmInstr) {
val opcode = instr.operator.opcode
if (opcode > 0xFF) {
b.writeByte((opcode ushr 8).toByte())
b.writeByte((opcode and 0xFF).toByte())
} else {
b.writeByte(opcode.toByte())
}
instr.immediates.forEach {
appendImmediate(it)
}
}
private fun appendImmediate(x: WasmImmediate) {
when (x) {
is WasmImmediate.ConstI32 -> b.writeVarInt32(x.value)
is WasmImmediate.ConstI64 -> b.writeVarInt64(x.value)
is WasmImmediate.ConstF32 -> b.writeUInt32(x.rawBits)
is WasmImmediate.ConstF64 -> b.writeUInt64(x.rawBits)
is WasmImmediate.SymbolI32 -> b.writeVarInt32(x.value.owner)
is WasmImmediate.MemArg -> {
b.writeVarUInt32(x.align)
b.writeVarUInt32(x.offset)
}
is WasmImmediate.BlockType -> appendBlockType(x)
is WasmImmediate.FuncIdx -> appendModuleFieldReference(x.value.owner)
is WasmImmediate.LocalIdx -> appendLocalReference(x.value.owner)
is WasmImmediate.GlobalIdx -> appendModuleFieldReference(x.value.owner)
is WasmImmediate.TypeIdx -> appendModuleFieldReference(x.value.owner)
is WasmImmediate.MemoryIdx -> appendModuleFieldReference(x.value.owner)
is WasmImmediate.DataIdx -> b.writeVarUInt32(x.value)
is WasmImmediate.TableIdx -> b.writeVarUInt32(x.value)
is WasmImmediate.LabelIdx -> b.writeVarUInt32(x.value)
is WasmImmediate.LabelIdxVector -> {
b.writeVarUInt32(x.value.size)
for (target in x.value) {
b.writeVarUInt32(target)
}
}
is WasmImmediate.ElemIdx -> appendModuleFieldReference(x.value)
is WasmImmediate.ValTypeVector -> {
b.writeVarUInt32(x.value.size)
for (type in x.value) {
appendType(type)
}
}
is WasmImmediate.StructType -> appendModuleFieldReference(x.value.owner)
is WasmImmediate.StructFieldIdx -> b.writeVarUInt32(x.value.owner)
is WasmImmediate.HeapType -> appendHeapType(x.value)
}
}
private fun appendSection(id: UShort, content: () -> Unit) {
b.writeVarUInt7(id)
withVarUInt32PayloadSizePrepended { content() }
}
@OptIn(ExperimentalStdlibApi::class)
fun withVarUInt32PayloadSizePrepended(fn: () -> Unit) {
val previousWriter = b
val newWriter = b.createTemp()
b = newWriter
fn()
b = previousWriter
b.writeVarUInt32(newWriter.written)
b.write(newWriter)
}
private fun appendVectorSize(size: Int) {
b.writeVarUInt32(size)
}
private fun appendFunctionTypeDeclaration(type: WasmFunctionType) {
b.writeVarInt7(-0x20)
b.writeVarUInt32(type.parameterTypes.size)
type.parameterTypes.forEach { appendType(it) }
b.writeVarUInt32(type.resultTypes.size)
type.resultTypes.forEach { appendType(it) }
}
private fun appendBlockType(type: WasmImmediate.BlockType) {
when (type) {
is WasmImmediate.BlockType.Function -> appendModuleFieldReference(type.type)
is WasmImmediate.BlockType.Value -> when (type.type) {
null -> b.writeVarInt7(-0x40)
else -> appendType(type.type)
}
}
}
private fun appendStructTypeDeclaration(type: WasmStructDeclaration) {
b.writeVarInt7(-0x21)
b.writeVarUInt32(type.fields.size)
type.fields.forEach {
appendType(it.type)
b.writeVarUInt1(it.isMutable)
}
}
val WasmFunctionType.index: Int
get() = module.functionTypes.indexOf(this)
private fun appendLimits(limits: WasmLimits) {
b.writeVarUInt1(limits.maxSize != null)
b.writeVarUInt32(limits.minSize)
if (limits.maxSize != null)
b.writeVarUInt32(limits.maxSize)
}
private fun appendImportedFunction(function: WasmFunction.Imported) {
b.writeString(function.importPair.moduleName)
b.writeString(function.importPair.declarationName)
b.writeByte(0) // Function external kind.
b.writeVarUInt32(function.type.index)
}
private fun appendDefinedFunction(function: WasmFunction.Defined) {
b.writeVarUInt32(function.type.index)
}
private fun appendTable(table: WasmTable) {
if (table.importPair != null) {
b.writeString(table.importPair.moduleName)
b.writeString(table.importPair.declarationName)
b.writeByte(1)
}
b.writeVarInt7(table.elementType.code)
appendLimits(table.limits)
}
private fun appendMemory(memory: WasmMemory) {
if (memory.importPair != null) {
b.writeString(memory.importPair.moduleName)
b.writeString(memory.importPair.declarationName)
b.writeByte(2)
}
appendLimits(memory.limits)
}
private fun appendGlobal(c: WasmGlobal) {
if (c.importPair != null) {
b.writeString(c.importPair.moduleName)
b.writeString(c.importPair.declarationName)
b.writeByte(3)
appendType(c.type)
b.writeVarUInt1(c.isMutable)
return
}
appendType(c.type)
b.writeVarUInt1(c.isMutable)
appendExpr(c.init)
}
private fun appendExpr(expr: Iterable<WasmInstr>) {
expr.forEach { appendInstr(it) }
appendInstr(WasmInstr(WasmOp.END))
}
private fun appendExport(export: WasmExport<*>) {
b.writeString(export.name)
b.writeByte(export.kind)
appendModuleFieldReference(export.field)
}
private fun appendStartFunction(startFunction: WasmFunction) {
appendModuleFieldReference(startFunction)
}
private fun appendElement(element: WasmElement) {
val isFuncIndices = element.values.all { it is WasmTable.Value.Function }
val funcIndices = if (isFuncIndices) {
element.values.map { (it as WasmTable.Value.Function).function.owner.id!! }
} else null
fun writeElements() {
appendVectorSize(element.values.size)
if (funcIndices != null) {
funcIndices.forEach { b.writeVarUInt32(it) }
} else {
element.values.forEach {
appendExpr((it as WasmTable.Value.Expression).expr)
}
}
}
fun writeTypeOrKind() {
if (isFuncIndices) {
b.writeByte(0x00)
} else {
appendType(element.type)
}
}
when (val mode = element.mode) {
WasmElement.Mode.Passive -> {
b.writeByte(if (isFuncIndices) 0x01 else 0x05)
writeTypeOrKind()
writeElements()
}
is WasmElement.Mode.Active -> {
val tableId = mode.table.id!!
when {
tableId == 0 && isFuncIndices -> {
b.writeByte(0x0)
appendExpr(mode.offset)
}
isFuncIndices -> {
b.writeByte(0x2)
appendModuleFieldReference(mode.table)
appendExpr(mode.offset)
writeTypeOrKind()
}
else -> {
b.writeByte(0x6)
appendModuleFieldReference(mode.table)
appendExpr(mode.offset)
writeTypeOrKind()
}
}
writeElements()
}
WasmElement.Mode.Declarative -> {
b.writeByte(if (isFuncIndices) 0x03 else 0x07)
writeTypeOrKind()
writeElements()
}
}
}
private fun appendCode(function: WasmFunction.Defined) {
withVarUInt32PayloadSizePrepended {
b.writeVarUInt32(function.locals.count { !it.isParameter })
function.locals.forEach { local ->
if (!local.isParameter) {
b.writeVarUInt32(1u)
appendType(local.type)
}
}
appendExpr(function.instructions)
}
}
private fun appendData(wasmData: WasmData) {
when (val mode = wasmData.mode) {
is WasmDataMode.Active -> {
if (mode.memoryIdx == 0) {
b.writeByte(0)
} else {
b.writeByte(2)
b.writeVarUInt32(mode.memoryIdx)
}
appendExpr(mode.offset)
}
WasmDataMode.Passive -> b.writeByte(1)
}
b.writeVarUInt32(wasmData.bytes.size)
b.writeBytes(wasmData.bytes)
}
fun appendHeapType(type: WasmHeapType) {
val code: Int = when (type) {
is WasmHeapType.Simple -> type.code.toInt()
is WasmHeapType.Type -> type.type.owner.id!!
}
b.writeVarInt32(code)
}
fun appendType(type: WasmType) {
b.writeVarInt7(type.code)
if (type is WasmRefType) {
appendHeapType(type.heapType)
}
if (type is WasmRefNullType) {
appendHeapType(type.heapType)
}
if (type is WasmRtt) {
b.writeVarUInt32(type.depth)
appendHeapType(type.heapType)
}
}
fun appendLocalReference(local: WasmLocal) {
b.writeVarUInt32(local.id)
}
fun appendModuleFieldReference(field: WasmNamedModuleField) {
val id = field.id ?: error("${field::class} ${field.name} ID is unlinked")
b.writeVarUInt32(id)
}
fun ByteWriter.writeVarUInt32(v: Int) {
this.writeVarUInt32(v.toUInt())
}
private fun ByteWriter.writeString(str: String) {
val bytes = str.toByteArray()
this.writeVarUInt32(bytes.size)
this.writeBytes(bytes)
}
}
abstract class ByteWriter {
abstract val written: Int
abstract fun write(v: ByteWriter)
abstract fun writeByte(v: Byte)
abstract fun writeBytes(v: ByteArray)
abstract fun createTemp(): ByteWriter
fun writeUInt32(v: UInt) {
writeByte(v.toByte())
writeByte((v shr 8).toByte())
writeByte((v shr 16).toByte())
writeByte((v shr 24).toByte())
}
fun writeUInt64(v: ULong) {
writeByte(v.toByte())
writeByte((v shr 8).toByte())
writeByte((v shr 16).toByte())
writeByte((v shr 24).toByte())
writeByte((v shr 32).toByte())
writeByte((v shr 40).toByte())
writeByte((v shr 48).toByte())
writeByte((v shr 56).toByte())
}
fun writeVarInt7(v: Byte) {
writeSignedLeb128(v.toLong())
}
fun writeVarInt32(v: Int) {
writeSignedLeb128(v.toLong())
}
fun writeVarInt64(v: Long) {
writeSignedLeb128(v)
}
fun writeVarUInt1(v: Boolean) {
writeUnsignedLeb128(if (v) 1u else 0u)
}
fun writeVarUInt7(v: UShort) {
writeUnsignedLeb128(v.toUInt())
}
fun writeVarUInt32(v: UInt) {
writeUnsignedLeb128(v)
}
private fun writeUnsignedLeb128(v: UInt) {
// Taken from Android source, Apache licensed
var v = v
var remaining = v shr 7
while (remaining != 0u) {
val byte = (v and 0x7fu) or 0x80u
writeByte(byte.toByte())
v = remaining
remaining = remaining shr 7
}
val byte = v and 0x7fu
writeByte(byte.toByte())
}
private fun writeSignedLeb128(v: Long) {
// Taken from Android source, Apache licensed
var v = v
var remaining = v shr 7
var hasMore = true
val end = if (v and Long.MIN_VALUE == 0L) 0L else -1L
while (hasMore) {
hasMore = remaining != end || remaining and 1 != (v shr 6) and 1
val byte = ((v and 0x7f) or if (hasMore) 0x80 else 0).toInt()
writeByte(byte.toByte())
v = remaining
remaining = remaining shr 7
}
}
class OutputStream(val os: java.io.OutputStream) : ByteWriter() {
override var written = 0; private set
override fun write(v: ByteWriter) {
if (v !is OutputStream || v.os !is ByteArrayOutputStream) error("Writer not created from createTemp")
v.os.writeTo(os)
written += v.os.size()
}
override fun writeByte(v: Byte) {
os.write(v.toInt())
written++
}
override fun writeBytes(v: ByteArray) {
os.write(v)
written += v.size
}
override fun createTemp() = OutputStream(ByteArrayOutputStream())
}
}
@@ -0,0 +1,496 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.wasm.ir.convertors
import org.jetbrains.kotlin.wasm.ir.*
open class SExpressionBuilder {
protected val stringBuilder = StringBuilder()
protected var indent = 0
protected inline fun indented(body: () -> Unit) {
indent++
body()
indent--
}
protected fun newLine() {
stringBuilder.appendLine()
repeat(indent) { stringBuilder.append(" ") }
}
protected inline fun newLineList(name: String, body: () -> Unit) {
newLine()
stringBuilder.append("($name")
indented { body() }
stringBuilder.append(")")
}
protected inline fun sameLineList(name: String, body: () -> Unit) {
stringBuilder.append(" ($name")
body()
stringBuilder.append(")")
}
protected fun appendElement(value: String) {
stringBuilder.append(" ")
stringBuilder.append(value)
}
override fun toString(): String =
stringBuilder.toString()
}
class WatBuilder : SExpressionBuilder() {
fun appendOffset(value: UInt) {
if (value != 0u)
appendElement("offset=$value")
}
fun appendAlign(value: UInt) {
var alignEffective: Long = 1
repeat(value.toInt()) { alignEffective *= 2 }
if (alignEffective != 0L)
appendElement("align=$alignEffective")
}
private fun appendInstr(wasmInstr: WasmInstr) {
newLine()
stringBuilder.append(wasmInstr.operator.mnemonic)
if (wasmInstr.operator in setOf(WasmOp.CALL_INDIRECT, WasmOp.TABLE_INIT)) {
wasmInstr.immediates.reversed().forEach {
appendImmediate(it)
}
return
}
wasmInstr.immediates.forEach {
appendImmediate(it)
}
}
private fun appendImmediate(x: WasmImmediate) {
when (x) {
is WasmImmediate.ConstI32 -> appendElement(x.value.toString().toLowerCase())
is WasmImmediate.ConstI64 -> appendElement(x.value.toString().toLowerCase())
is WasmImmediate.ConstF32 -> appendElement(f32Str(x).toLowerCase())
is WasmImmediate.ConstF64 -> appendElement(f64Str(x).toLowerCase())
is WasmImmediate.SymbolI32 -> appendElement(x.value.owner.toString())
is WasmImmediate.MemArg -> {
appendOffset(x.offset)
appendAlign(x.align)
}
is WasmImmediate.BlockType -> appendBlockType(x)
is WasmImmediate.FuncIdx -> appendModuleFieldReference(x.value.owner)
is WasmImmediate.LocalIdx -> appendLocalReference(x.value.owner)
is WasmImmediate.GlobalIdx -> appendModuleFieldReference(x.value.owner)
is WasmImmediate.TypeIdx -> sameLineList("type") { appendModuleFieldReference(x.value.owner) }
is WasmImmediate.MemoryIdx -> appendModuleFieldIdIfNotNull(x.value.owner)
is WasmImmediate.DataIdx -> appendElement(x.value.toString())
is WasmImmediate.TableIdx -> appendElement(x.value.toString())
is WasmImmediate.LabelIdx -> appendElement(x.value.toString())
is WasmImmediate.LabelIdxVector ->
x.value.forEach { appendElement(it.toString()) }
is WasmImmediate.ElemIdx -> appendElement(x.value.id!!.toString())
is WasmImmediate.ValTypeVector -> sameLineList("result") { x.value.forEach { appendType(it) } }
is WasmImmediate.StructType -> appendModuleFieldReference(x.value.owner)
is WasmImmediate.StructFieldIdx -> appendElement(x.value.owner.toString())
is WasmImmediate.HeapType -> {
appendHeapType(x.value)
}
}
}
private fun f32Str(x: WasmImmediate.ConstF32): String {
val bits = x.rawBits.toInt()
val v = Float.fromBits(bits)
return if (v.isNaN()) {
val sign = if ((bits and Int.MIN_VALUE) == 0) {
""
} else {
"-"
}
if (bits != Float.NaN.toRawBits()) {
val customPayload = bits and 0x7fffff
"${sign}nan:0x${customPayload.toString(16)}"
} else {
"${sign}nan"
}
} else {
when (v) {
Float.POSITIVE_INFINITY -> "inf"
Float.NEGATIVE_INFINITY -> "-inf"
else -> v.toString()
}
}
}
private fun f64Str(x: WasmImmediate.ConstF64): String {
val bits = x.rawBits.toLong()
val v = Double.fromBits(bits)
return if (v.isNaN()) {
val sign = if ((bits and Long.MIN_VALUE) == 0L) {
""
} else {
"-"
}
if (bits != Double.NaN.toRawBits()) {
val customPayload = bits and 0xfffffffffffff
"${sign}nan:0x${customPayload.toString(16)}"
} else {
"${sign}nan"
}
} else {
when (v) {
Double.POSITIVE_INFINITY -> "inf"
Double.NEGATIVE_INFINITY -> "-inf"
else -> v.toString()
}
}
}
fun appendBlockType(type: WasmImmediate.BlockType) {
when (type) {
is WasmImmediate.BlockType.Value -> {
if (type.type != null && type.type !is WasmUnreachableType) {
sameLineList("result") { appendType(type.type) }
}
}
is WasmImmediate.BlockType.Function -> {
val parameters = type.type.parameterTypes
val results = type.type.resultTypes
if (parameters.isNotEmpty()) {
sameLineList("param") { parameters.forEach { appendType(it) } }
}
if (results.isNotEmpty()) {
sameLineList("result") { results.forEach { appendType(it) } }
}
}
}
}
fun appendRefType(type: WasmRefType) {
when (type.heapType) {
is WasmHeapType.Simple -> appendElement(type.heapType.name + "ref")
is WasmHeapType.Type -> sameLineList("ref") { appendHeapType(type.heapType) }
}
}
fun appendWasmModule(module: WasmModule) {
with(module) {
newLineList("module") {
functionTypes.forEach { appendFunctionTypeDeclaration(it) }
structs.forEach { appendStructTypeDeclaration(it) }
importsInOrder.forEach {
when (it) {
is WasmFunction.Imported -> appendImportedFunction(it)
is WasmMemory -> appendMemory(it)
is WasmTable -> appendTable(it)
is WasmGlobal -> appendGlobal(it)
else -> error("Unknown import kind ${it::class}")
}
}
definedFunctions.forEach { appendDefinedFunction(it) }
tables.forEach { appendTable(it) }
memories.forEach { appendMemory(it) }
globals.forEach { appendGlobal(it) }
exports.forEach { appendExport(it) }
elements.forEach { appendWasmElement(it) }
startFunction?.let { appendStartFunction(it) }
data.forEach { appendData(it) }
}
}
}
private fun appendFunctionTypeDeclaration(type: WasmFunctionType) {
newLineList("type") {
appendModuleFieldReference(type)
sameLineList("func") {
sameLineList("param") {
type.parameterTypes.forEach { appendType(it) }
}
if (type.resultTypes.isNotEmpty()) {
sameLineList("result") {
type.resultTypes.forEach { appendType(it) }
}
}
}
}
}
private fun appendStructTypeDeclaration(type: WasmStructDeclaration) {
newLineList("type") {
appendModuleFieldReference(type)
sameLineList("struct") {
type.fields.forEach {
appendStructField(it)
}
}
}
}
private fun appendImportedFunction(function: WasmFunction.Imported) {
newLineList("func") {
appendModuleFieldReference(function)
function.importPair.appendImportPair()
sameLineList("type") { appendModuleFieldReference(function.type) }
}
}
private fun WasmImportPair.appendImportPair() {
sameLineList("import") {
toWatString(moduleName)
toWatString(declarationName)
}
}
private fun appendDefinedFunction(function: WasmFunction.Defined) {
newLineList("func") {
appendModuleFieldReference(function)
sameLineList("type") { appendModuleFieldReference(function.type) }
function.locals.forEach { if (it.isParameter) appendLocal(it) }
if (function.type.resultTypes.isNotEmpty()) {
sameLineList("result") {
function.type.resultTypes.forEach { appendType(it) }
}
}
function.locals.forEach { if (!it.isParameter) appendLocal(it) }
function.instructions.forEach { appendInstr(it) }
}
}
private fun appendTable(table: WasmTable) {
newLineList("table") {
appendModuleFieldReference(table)
table.importPair?.appendImportPair()
appendLimits(table.limits)
appendType(table.elementType)
}
}
private fun appendMemory(memory: WasmMemory) {
newLineList("memory") {
appendModuleFieldReference(memory)
memory.importPair?.appendImportPair()
appendLimits(memory.limits)
}
}
private fun appendLimits(limits: WasmLimits) {
appendElement(limits.minSize.toString())
limits.maxSize?.let { appendElement(limits.maxSize.toString()) }
}
private fun appendGlobal(global: WasmGlobal) {
newLineList("global") {
appendModuleFieldReference(global)
global.importPair?.appendImportPair()
if (global.isMutable)
sameLineList("mut") { appendType(global.type) }
else
appendType(global.type)
global.init.forEach { appendInstr(it) }
}
}
private fun appendExport(export: WasmExport<*>) {
newLineList("export") {
toWatString(export.name)
sameLineList(export.keyword) {
appendModuleFieldReference(export.field)
}
}
}
private fun appendWasmElement(element: WasmElement) {
newLineList("elem") {
when (val mode = element.mode) {
WasmElement.Mode.Passive -> {
}
is WasmElement.Mode.Active -> {
if (mode.table.id != 0) {
sameLineList("table") { appendModuleFieldReference(mode.table) }
}
sameLineList("") { appendInstr(mode.offset.single()) }
}
WasmElement.Mode.Declarative -> {
appendElement("declare")
}
}
val allFunctions = element.values.all { it is WasmTable.Value.Function }
if (allFunctions) {
appendElement("func")
for (value in element.values) {
require(value is WasmTable.Value.Function)
appendModuleFieldReference(value.function.owner)
}
} else {
appendType(element.type)
for (value in element.values) {
require(value is WasmTable.Value.Expression)
sameLineList("item") {
appendInstr(value.expr.single())
}
}
}
}
}
private fun appendStartFunction(startFunction: WasmFunction) {
newLineList("start") {
appendModuleFieldReference(startFunction)
}
}
private fun appendData(wasmData: WasmData) {
newLineList("data") {
when (val mode = wasmData.mode) {
is WasmDataMode.Active -> {
if (mode.memoryIdx != 0) {
sameLineList("memory") { appendElement(mode.memoryIdx.toString()) }
}
sameLineList("") {
appendInstr(mode.offset.single())
}
}
WasmDataMode.Passive -> {
}
}
appendElement(wasmData.bytes.toWatData())
}
}
private fun appendLocal(local: WasmLocal) {
newLineList(if (local.isParameter) "param" else "local") {
appendLocalReference(local)
appendType(local.type)
}
}
fun appendHeapType(type: WasmHeapType) {
when (type) {
is WasmHeapType.Simple ->
appendElement(type.name)
is WasmHeapType.Type -> {
// appendElement("opt")
appendModuleFieldReference(type.type.owner)
}
}
}
fun appendReferencedType(type: WasmType) {
when (type) {
is WasmFuncRef -> appendElement("func")
is WasmExternRef -> appendElement("extern")
else -> TODO()
}
}
fun appendType(type: WasmType) {
when (type) {
is WasmRefType ->
sameLineList("ref") {
appendHeapType(type.heapType)
}
is WasmRefNullType ->
sameLineList("ref null") {
appendHeapType(type.heapType)
}
is WasmRtt ->
sameLineList("rtt") {
appendElement(type.depth.toString())
appendHeapType(type.heapType)
}
WasmUnreachableType -> {
}
else ->
appendElement(type.name)
}
}
private fun appendStructField(field: WasmStructFieldDeclaration) {
sameLineList("field") {
if (field.isMutable) {
sameLineList("mut") { appendType(field.type) }
} else {
appendType(field.type)
}
}
}
fun appendLocalReference(local: WasmLocal) {
appendElement("$${local.id}_${sanitizeWatIdentifier(local.name)}")
}
fun appendModuleFieldIdIfNotNull(field: WasmNamedModuleField) {
val id = field.id
?: error("${field::class} ${field.name} ID is unlinked")
if (id != 0) appendElement(id.toString())
}
fun appendModuleFieldReference(field: WasmNamedModuleField) {
val id = field.id
?: error("${field::class} ${field.name} ID is unlinked")
val indexSpaceKind = when (field) {
is WasmData -> "data"
is WasmFunction -> "fun"
is WasmMemory -> "mem"
is WasmTable -> "table"
is WasmElement -> "elem"
is WasmGlobal -> "g"
is WasmTypeDeclaration -> "type"
}
appendElement("\$${sanitizeWatIdentifier(field.name)}___${indexSpaceKind}_$id")
}
private fun toWatString(s: String) {
if (s.all { isValidWatIdentifierChar(it) }) {
stringBuilder.append(" \"")
stringBuilder.append(s)
stringBuilder.append('"')
} else {
stringBuilder.append(s.toByteArray().toWatData())
}
}
}
fun Byte.toWatData() = "\\" + this.toUByte().toString(16).padStart(2, '0')
fun ByteArray.toWatData(): String = "\"" + joinToString("") { it.toWatData() } + "\""
fun sanitizeWatIdentifier(indent: String): String {
if (indent.isEmpty())
return "_"
if (indent.all(::isValidWatIdentifierChar))
return indent
return indent.map { if (isValidWatIdentifierChar(it)) it else "_" }.joinToString("")
}
// https://webassembly.github.io/spec/core/text/values.html#text-id
fun isValidWatIdentifierChar(c: Char): Boolean =
c in '0'..'9' || c in 'A'..'Z' || c in 'a'..'z'
// TODO: SpiderMonkey js shell can't parse some of the
// permitted identifiers: '?', '<'
|| c in "!#$%&*+-./:<=>?@\\^_`|~"
|| c in "$.@_"
@@ -0,0 +1,50 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.wasm.ir
import org.junit.Ignore
import org.junit.Test
class BinaryCodecTest {
@Test
fun core() {
runSpecTests("core", wasmTestSuitePath, emptyList())
}
@Test
@Ignore
fun `bulk-memory-operations`() =
testProposal("bulk-memory-operations")
@Test
@Ignore
fun `exception-handling`() =
testProposal("exception-handling")
@Test
@Ignore
fun `function-references`() =
testProposal("function-references")
@Test
fun `reference-types`() =
testProposal("reference-types", ignoreFiles = listOf("ref_func.wast"))
@Test
@Ignore
fun simd() =
testProposal("simd")
@Test
@Ignore
fun `tail-call`() =
testProposal("tail-call")
@Test
@Ignore
fun threads() =
testProposal("threads")
}
@@ -0,0 +1,262 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.wasm.ir
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import org.jetbrains.kotlin.test.KotlinTestUtils.assertEqualsToFile
import org.jetbrains.kotlin.utils.fileUtils.withReplacedExtensionOrNull
import org.jetbrains.kotlin.wasm.ir.convertors.MyByteReader
import org.jetbrains.kotlin.wasm.ir.convertors.WasmBinaryBuilder
import org.jetbrains.kotlin.wasm.ir.convertors.WasmBinaryToIR
import org.jetbrains.kotlin.wasm.ir.convertors.WatBuilder
import java.io.ByteArrayOutputStream
import java.io.File
@Suppress("unused")
@Serializable
data class SpecTest(
val source_filename: String,
val commands: List<Command>
) {
@Serializable
sealed class Command {
@SerialName("module")
@Serializable
data class Module(
val line: Int,
val filename: String,
val name: String? = null,
) : Command()
@SerialName("register")
@Serializable
data class Register(
val line: Int,
val name: String? = null,
val `as`: String? = null
) : Command()
@SerialName("assert_return")
@Serializable
data class AssertReturn(
val line: Int,
val action: Action,
val expected: List<Value>,
) : Command()
// TODO: Assert trap for modules?
@SerialName("assert_trap")
@Serializable
data class AssertTrap(
val line: Int,
val action: Action,
val text: String,
val expected: List<Value>,
) : Command()
@SerialName("assert_exhaustion")
@Serializable
data class AssertExhaustion(
val line: Int,
val action: Action,
val text: String,
val expected: List<Value>,
) : Command()
@SerialName("assert_malformed")
@Serializable
data class AssertMalformed(
val line: Int,
val filename: String,
val text: String,
val module_type: String,
) : Command()
@SerialName("assert_invalid")
@Serializable
data class AssertInvalid(
val line: Int,
val filename: String,
val text: String,
val module_type: String,
) : Command()
@SerialName("assert_unlinkable")
@Serializable
data class AssertUnlinkable(
val line: Int,
val filename: String,
val text: String,
val module_type: String,
) : Command()
@SerialName("assert_uninstantiable")
@Serializable
data class AssertUninstantiable(
val line: Int,
val filename: String,
val text: String,
val module_type: String,
) : Command()
@SerialName("action")
@Serializable
data class ActionCommand(
val line: Int,
val action: Action,
val expected: List<Value>,
) : Command()
}
@Serializable
data class Action(
val type: String,
val field: String,
val args: List<Value> = emptyList(),
val module: String? = null
)
@Serializable
data class Value(
val type: String,
val value: String? = null
)
}
private fun runSpecTest(specTest: SpecTest, testDir: File, wastFile: File, wabtOptions: List<String>) {
for (command in specTest.commands) {
when (command) {
is SpecTest.Command.Module -> {
val wasmFile = File(testDir, command.filename)
testWasmFile(wasmFile, testDir.name)
}
is SpecTest.Command.Register -> {
}
is SpecTest.Command.AssertReturn -> {
}
is SpecTest.Command.AssertTrap -> {
}
is SpecTest.Command.AssertExhaustion -> {
}
is SpecTest.Command.AssertMalformed -> {
}
is SpecTest.Command.AssertInvalid -> {
}
is SpecTest.Command.AssertUnlinkable -> {
}
is SpecTest.Command.AssertUninstantiable -> {
}
is SpecTest.Command.ActionCommand -> {
}
}
}
}
private fun runJsonTest(jsonFile: File, wastFile: File, wabtOptions: List<String>) {
require(jsonFile.isFile && jsonFile.exists())
val jsonText = jsonFile.readText()
val specTest = Json.decodeFromString(SpecTest.serializer(), jsonText)
val wasmDir = jsonFile.parentFile!!
println("Running json test ${jsonFile.path} ...")
runSpecTest(specTest, wasmDir, wastFile, wabtOptions)
}
val wasmTestSuitePath: String
get() = System.getProperty("wasm.testsuite.path")!!
fun testProposal(
name: String,
wabtOptions: List<String> = listOf("--enable-all"),
ignoreFiles: List<String> = emptyList()
) {
runSpecTests(name, "$wasmTestSuitePath/proposals/$name", wabtOptions, ignoreFiles)
}
fun runSpecTests(
name: String,
wastDirectoryPath: String,
wabtOptions: List<String>,
ignoreFiles: List<String> = emptyList()
) {
// Clean and prepare output dir for spec tests
val specTestsDir = File("build/spec-tests/$name")
if (specTestsDir.exists())
specTestsDir.deleteRecursively()
specTestsDir.mkdirs()
val testSuiteDir = File(wastDirectoryPath)
assert(testSuiteDir.isDirectory) { "${testSuiteDir.absolutePath} is not a directory" }
for (file in testSuiteDir.listFiles()!!) {
if (file.name in ignoreFiles) {
println("Ignoring file: ${file.absolutePath}")
continue
}
if (file.isFile && file.name.endsWith(".wast")) {
val jsonFileName = file.withReplacedExtensionOrNull(".wast", ".json")!!.name
val jsonFile = File(specTestsDir, jsonFileName)
println("Creating JSON for ${file.path}")
Wabt.wast2json(file, jsonFile, *wabtOptions.toTypedArray())
runJsonTest(jsonFile, file, wabtOptions)
}
}
}
fun testWasmFile(wasmFile: File, dirName: String) {
val testName = wasmFile.nameWithoutExtension
fun newFile(suffix: String): File =
File("build/spec-tests/tmp/$dirName/${testName}_$suffix")
.also {
it.parentFile.mkdirs()
it.createNewFile()
}
println("Testing wasm file : ${wasmFile.absolutePath} ... ")
val module = fileToWasmModule(wasmFile)
module.calculateIds()
val kotlinTextFormat = module.toTextFormat()
val kotlinBinaryFormat = module.toBinaryFormat()
val kotlinTextFile = newFile("kwt.wat")
kotlinTextFile.writeText(kotlinTextFormat)
val kotlinBinaryFile = newFile("kwt.wasm")
kotlinBinaryFile.writeBytes(kotlinBinaryFormat)
val kotlinTextToWasmTmpFile = newFile("kwt.tmp.wasm")
Wabt.wat2wasm(kotlinTextFile, kotlinTextToWasmTmpFile)
val kotlinTextCanonicalFile = newFile("kwt.canonical.wat")
Wabt.wasm2wat(kotlinTextToWasmTmpFile, kotlinTextCanonicalFile)
val wabtWatFile = newFile("wabt.wat")
Wabt.wasm2wat(wasmFile, wabtWatFile)
assertEqualsToFile("Kwt text format", wabtWatFile, kotlinTextCanonicalFile.readText())
val kotlinBinaryCanonicalFile = newFile("kwt.bin.canonical.wat")
Wabt.wasm2wat(kotlinBinaryFile, kotlinBinaryCanonicalFile)
assertEqualsToFile("Kwt binary format", wabtWatFile, kotlinBinaryCanonicalFile.readText())
}
fun WasmModule.toBinaryFormat(): ByteArray {
val os = ByteArrayOutputStream()
WasmBinaryBuilder(os, this).appendWasmModule()
return os.toByteArray()
}
fun WasmModule.toTextFormat(): String {
val builder = WatBuilder()
builder.appendWasmModule(this)
return builder.toString()
}
fun fileToWasmModule(file: File): WasmModule =
WasmBinaryToIR(MyByteReader(file.inputStream())).parseModule()
@@ -0,0 +1,54 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.wasm.ir
import java.io.BufferedReader
import java.io.File
import java.io.InputStream
import java.io.InputStreamReader
import kotlin.test.fail
open class ExternalTool(private val path: String) {
fun runAndPrint(vararg arguments: String) {
val command = arrayOf(path, *arguments)
val process = ProcessBuilder(*command)
.redirectErrorStream(true)
.start()
val commandString = command.joinToString(" ") { escapeShellArgument(it) }
println(commandString)
val inputStream: InputStream = process.inputStream
val input = BufferedReader(InputStreamReader(inputStream))
while (true) println(input.readLine() ?: break)
val exitValue = process.waitFor()
if (exitValue != 0) {
fail("Command \"$commandString\" terminated with exit code $exitValue")
}
}
}
object Wabt {
private val wabtBinPath = System.getProperty("wabt.bin.path")
private val wasm2watTool = ExternalTool("$wabtBinPath/wasm2wat")
private val wat2wasmTool = ExternalTool("$wabtBinPath/wat2wasm")
private val wast2jsonTool = ExternalTool("$wabtBinPath/wast2json")
fun wasm2wat(input: File, output: File) {
wasm2watTool.runAndPrint("--enable-all", input.absolutePath, "-o", output.absolutePath)
}
fun wat2wasm(input: File, output: File) {
wat2wasmTool.runAndPrint("--enable-all", input.absolutePath, "-o", output.absolutePath)
}
fun wast2json(input: File, output: File, vararg args: String) {
wast2jsonTool.runAndPrint(*args, input.absolutePath, "-o", output.absolutePath)
}
}
private fun escapeShellArgument(arg: String): String =
"'${arg.replace("'", "'\\''")}'"