[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

View File

@@ -0,0 +1,27 @@
<component name="ProjectDictionaryState">
<dictionary name="svyatoslav.kuzmich">
<words>
<w>anyfunc</w>
<w>copysign</w>
<w>eqref</w>
<w>exnref</w>
<w>externref</w>
<w>funcref</w>
<w>jetbrains</w>
<w>kotlinx</w>
<w>ktor</w>
<w>optref</w>
<w>popcnt</w>
<w>rotl</w>
<w>rotr</w>
<w>simd</w>
<w>sqrt</w>
<w>testsuite</w>
<w>uninstantiable</w>
<w>unlinkable</w>
<w>vtable</w>
<w>wabt</w>
<w>xopt</w>
</words>
</dictionary>
</component>

View File

@@ -292,7 +292,8 @@ extra["compilerModules"] = arrayOf(
":compiler:fir:jvm",
":compiler:fir:checkers",
":compiler:fir:entrypoint",
":compiler:fir:analysis-tests"
":compiler:fir:analysis-tests",
":wasm:wasm.ir"
)
extra["compilerModulesForJps"] = listOf(
@@ -650,8 +651,8 @@ tasks {
}
register("wasmCompilerTest") {
// TODO: fix once
// dependsOn(":js:js.tests:wasmTest")
dependsOn(":js:js.tests:wasmTest")
dependsOn(":wasm:wasm.ir:test")
}
register("nativeCompilerTest") {

View File

@@ -15,6 +15,7 @@ dependencies {
compile(project(":js:js.ast"))
compile(project(":js:js.frontend"))
compile(project(":compiler:backend.js"))
compile(project(":wasm:wasm.ir"))
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
}

View File

@@ -92,6 +92,11 @@ the Kotlin IntelliJ IDEA plugin:
- Path: idea/idea-gradle-tooling-api/src/org/gradle/tooling/model/kotlin/dsl
- License: Apache 2 ([license/third_party/gradle_license.txt][gradle])
- Origin: Gradle, Copyright 2002-2017 Gradle, Inc.
- Path: wasm/ir/src/org/jetbrains/kotlin/wasm/ir/convertors
- License: MIT ([license/third_party/asmble_license.txt][asmble])
- Origin: Copyright (C) 2018 Chad Retz
## Kotlin Test Data
@@ -220,6 +225,7 @@ any distributions of the compiler, libraries or plugin:
[aosp]: third_party/aosp_license.txt
[asm]: third_party/asm_license.txt
[asmble]: third_party/asmble_license.txt
[boost]: third_party/boost_LICENSE.txt
[closure-compiler]: third_party/closure-compiler_LICENSE.txt
[dagger]: third_party/testdata/dagger_license.txt

21
license/third_party/asmble_license.txt vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Chad Retz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -302,7 +302,9 @@ include ":benchmarks",
":kotlinx-serialization-compiler-plugin",
":kotlinx-serialization-ide-plugin",
":kotlin-serialization",
":kotlin-serialization-unshaded"
":kotlin-serialization-unshaded",
":wasm:wasm.ir"
include ":compiler:fir:cones",
":compiler:fir:tree",

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 "$.@_"

View File

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

View File

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

View File

@@ -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("'", "'\\''")}'"