diff --git a/.idea/dictionaries/svyatoslav_kuzmich.xml b/.idea/dictionaries/svyatoslav_kuzmich.xml
new file mode 100644
index 00000000000..c5c5f0f09d1
--- /dev/null
+++ b/.idea/dictionaries/svyatoslav_kuzmich.xml
@@ -0,0 +1,27 @@
+
+
+
+ anyfunc
+ copysign
+ eqref
+ exnref
+ externref
+ funcref
+ jetbrains
+ kotlinx
+ ktor
+ optref
+ popcnt
+ rotl
+ rotr
+ simd
+ sqrt
+ testsuite
+ uninstantiable
+ unlinkable
+ vtable
+ wabt
+ xopt
+
+
+
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index a589ec76fa4..f962b3ce496 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -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") {
diff --git a/compiler/ir/backend.wasm/build.gradle.kts b/compiler/ir/backend.wasm/build.gradle.kts
index cc9386c3abc..64bed5c9355 100644
--- a/compiler/ir/backend.wasm/build.gradle.kts
+++ b/compiler/ir/backend.wasm/build.gradle.kts
@@ -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") }
}
diff --git a/license/README.md b/license/README.md
index 9ed84957d45..4823b912053 100644
--- a/license/README.md
+++ b/license/README.md
@@ -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
diff --git a/license/third_party/asmble_license.txt b/license/third_party/asmble_license.txt
new file mode 100644
index 00000000000..6a6444d0eca
--- /dev/null
+++ b/license/third_party/asmble_license.txt
@@ -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.
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 5d949923667..cb7d390dc18 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -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",
diff --git a/wasm/wasm.ir/build.gradle.kts b/wasm/wasm.ir/build.gradle.kts
new file mode 100644
index 00000000000..0161aeb5dbc
--- /dev/null
+++ b/wasm/wasm.ir/build.gradle.kts
@@ -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 {
+ src("https://github.com/WebAssembly/testsuite/zipball/$testSuiteRevision")
+ dest(testSuiteZip)
+ overwrite(false)
+}
+
+val unzipTestSuite by task {
+ dependsOn(downloadTestSuite)
+ from(zipTree(downloadTestSuite.get().dest))
+ into(testSuiteDir)
+}
+
+val wabtDir = File(buildDir, "wabt")
+val wabtVersion = "1.0.19"
+
+val downloadWabt by task {
+ 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 {
+ dependsOn(downloadWabt)
+ from(tarTree(resources.gzip(downloadWabt.get().dest)))
+ into(wabtDir)
+}
+
+sourceSets {
+ "main" { projectDefault() }
+ "test" { projectDefault() }
+}
+
+tasks.withType().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()
\ No newline at end of file
diff --git a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Declarations.kt b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Declarations.kt
new file mode 100644
index 00000000000..3010da3cef3
--- /dev/null
+++ b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Declarations.kt
@@ -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 = emptyList(),
+ val structs: List = emptyList(),
+
+ val importsInOrder: List = emptyList(),
+ val importedFunctions: List = emptyList(),
+ val importedMemories: List = emptyList(),
+ val importedTables: List = emptyList(),
+ val importedGlobals: List = emptyList(),
+
+ val definedFunctions: List = emptyList(),
+ val tables: List = emptyList(),
+ val memories: List = emptyList(),
+ val globals: List = emptyList(),
+ val exports: List> = emptyList(),
+ val elements: List = emptyList(),
+
+ val startFunction: WasmFunction? = null,
+
+ val data: List = 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 = mutableListOf(),
+ val instructions: MutableList = 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
+ ) : WasmDataMode() {
+ constructor(memoryIdx: Int, offset: Int) : this(memoryIdx, mutableListOf().also> {
+ 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) : Value() {
+ constructor(function: WasmFunction) : this(WasmSymbol(function))
+ }
+
+ class Expression(val expr: List) : Value()
+ }
+
+}
+
+class WasmElement(
+ val type: WasmType,
+ val values: List,
+ val mode: Mode,
+) : WasmNamedModuleField() {
+ sealed class Mode {
+ object Passive : Mode()
+ class Active(val table: WasmTable, val offset: List) : 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,
+ val importPair: WasmImportPair? = null
+) : WasmNamedModuleField()
+
+sealed class WasmExport(
+ val name: String,
+ val field: T,
+ val kind: Byte,
+ val keyword: String
+) {
+ class Function(name: String, field: WasmFunction) : WasmExport(name, field, 0x0, "func")
+ class Table(name: String, field: WasmTable) : WasmExport(name, field, 0x1, "table")
+ class Memory(name: String, field: WasmMemory) : WasmExport(name, field, 0x2, "memory")
+ class Global(name: String, field: WasmGlobal) : WasmExport(name, field, 0x3, "global")
+}
+
+sealed class WasmTypeDeclaration(
+ override val name: String
+) : WasmNamedModuleField()
+
+class WasmFunctionType(
+ name: String,
+ val parameterTypes: List,
+ val resultTypes: List
+) : WasmTypeDeclaration(name)
+
+class WasmStructDeclaration(
+ name: String,
+ val fields: List
+) : WasmTypeDeclaration(name)
+
+class WasmStructFieldDeclaration(
+ val name: String,
+ val type: WasmType,
+ val isMutable: Boolean
+)
+
+class WasmInstr(
+ val operator: WasmOp,
+ val immediates: List = emptyList()
+)
+
+data class WasmLimits(
+ val minSize: UInt,
+ val maxSize: UInt?
+)
+
+data class WasmImportPair(
+ val moduleName: String,
+ val declarationName: String
+)
\ No newline at end of file
diff --git a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Operators.kt b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Operators.kt
new file mode 100644
index 00000000000..7b5f7cff947
--- /dev/null
+++ b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Operators.kt
@@ -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) : 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) : WasmImmediate() {
+ constructor(value: WasmFunction) : this(WasmSymbol(value))
+ }
+
+ class LocalIdx(val value: WasmSymbol) : WasmImmediate() {
+ constructor(value: WasmLocal) : this(WasmSymbol(value))
+ }
+
+ class GlobalIdx(val value: WasmSymbol) : WasmImmediate() {
+ constructor(value: WasmGlobal) : this(WasmSymbol(value))
+ }
+
+ class TypeIdx(val value: WasmSymbol) : WasmImmediate() {
+ constructor(value: WasmTypeDeclaration) : this(WasmSymbol(value))
+ }
+
+ class ValTypeVector(val value: List) : WasmImmediate()
+
+ class MemoryIdx(val value: WasmSymbol) : 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) : WasmImmediate()
+ class ElemIdx(val value: WasmElement) : WasmImmediate()
+
+ class StructType(val value: WasmSymbol) : WasmImmediate() {
+ constructor(value: WasmStructDeclaration) : this(WasmSymbol(value))
+ }
+
+ class StructFieldIdx(val value: WasmSymbol) : 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 = 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 =
+ enumValues().associateBy { it.opcode }
\ No newline at end of file
diff --git a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Symbol.kt b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Symbol.kt
new file mode 100644
index 00000000000..e5278554b8f
--- /dev/null
+++ b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Symbol.kt
@@ -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 {
+ val owner: T
+}
+
+class WasmSymbol(owner: T? = null) : WasmSymbolReadOnly {
+ 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"
+}
diff --git a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Types.kt b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Types.kt
new file mode 100644
index 00000000000..77f3637d5ba
--- /dev/null
+++ b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Types.kt
@@ -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) : 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")
+ }
diff --git a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Utils.kt b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Utils.kt
new file mode 100644
index 00000000000..b4656e9c967
--- /dev/null
+++ b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Utils.kt
@@ -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.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)
+}
diff --git a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/WasmExpressionBuilder.kt b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/WasmExpressionBuilder.kt
new file mode 100644
index 00000000000..0db3c67eb6a
--- /dev/null
+++ b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/WasmExpressionBuilder.kt
@@ -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) {
+ 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) {
+ buildInstr(WasmOp.CALL, WasmImmediate.FuncIdx(symbol))
+ }
+
+ fun buildCallIndirect(symbol: WasmSymbol) {
+ 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) {
+ buildInstr(WasmOp.GLOBAL_GET, WasmImmediate.GlobalIdx(global))
+ }
+
+ fun buildSetGlobal(global: WasmSymbol) {
+ buildInstr(WasmOp.GLOBAL_SET, WasmImmediate.GlobalIdx(global))
+ }
+
+ fun buildStructGet(struct: WasmSymbol, fieldId: WasmSymbol) {
+ buildInstr(
+ WasmOp.STRUCT_GET,
+ WasmImmediate.StructType(struct),
+ WasmImmediate.StructFieldIdx(fieldId)
+ )
+ }
+
+ fun buildStructNew(struct: WasmSymbol) {
+ buildInstr(WasmOp.STRUCT_NEW_WITH_RTT, WasmImmediate.StructType(struct))
+ }
+
+ fun buildStructSet(struct: WasmSymbol, fieldId: WasmSymbol) {
+ 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))
+ }
+}
+
diff --git a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/WasmIrExpressionBuilder.kt b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/WasmIrExpressionBuilder.kt
new file mode 100644
index 00000000000..122af82aaff
--- /dev/null
+++ b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/WasmIrExpressionBuilder.kt
@@ -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
+) : 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
+ }
+}
diff --git a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/convertors/BinaryToAst.kt b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/convertors/BinaryToAst.kt
new file mode 100644
index 00000000000..23208979c6b
--- /dev/null
+++ b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/convertors/BinaryToAst.kt
@@ -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 = mutableListOf()
+ val structs: MutableList = mutableListOf()
+
+ val importsInOrder: MutableList = mutableListOf()
+ val importedFunctions: MutableList = mutableListOf()
+ val importedMemories: MutableList = mutableListOf()
+ val importedTables: MutableList = mutableListOf()
+ val importedGlobals: MutableList = mutableListOf()
+
+ val definedFunctions: MutableList = mutableListOf()
+ val table: MutableList = mutableListOf()
+ val memory: MutableList = mutableListOf()
+ val globals: MutableList = mutableListOf()
+ val exports: MutableList> = mutableListOf()
+ var startFunction: WasmFunction? = null
+ val elements: MutableList = mutableListOf()
+ val data: MutableList = mutableListOf()
+ var dataCount: Boolean = false
+
+
+ private fun byIdx(l1: List, l2: List, 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()
+ 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 = mapVector {
+ if (firstByte < 4) {
+ WasmTable.Value.Function(funByIdx(b.readVarUInt32AsInt()))
+ } else {
+ val exprBody = mutableListOf()
+ 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 =
+ mutableListOf().also { readExpression(it) }
+
+ private fun readExpression(instructions: MutableList, locals: List = 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): 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 = 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 mapVector(block: (index: UInt) -> T): List {
+ 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 =
+ 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
+ }
+}
diff --git a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/convertors/WasmAstToBinary.kt b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/convertors/WasmAstToBinary.kt
new file mode 100644
index 00000000000..d41142a68b4
--- /dev/null
+++ b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/convertors/WasmAstToBinary.kt
@@ -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) {
+ 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())
+ }
+}
\ No newline at end of file
diff --git a/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/convertors/WasmAstToWat.kt b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/convertors/WasmAstToWat.kt
new file mode 100644
index 00000000000..6b501da6fef
--- /dev/null
+++ b/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/convertors/WasmAstToWat.kt
@@ -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 "$.@_"
diff --git a/wasm/wasm.ir/test/org/jetbrains/kotlin/wasm/ir/BinaryCodecTest.kt b/wasm/wasm.ir/test/org/jetbrains/kotlin/wasm/ir/BinaryCodecTest.kt
new file mode 100644
index 00000000000..7b52b9b8c85
--- /dev/null
+++ b/wasm/wasm.ir/test/org/jetbrains/kotlin/wasm/ir/BinaryCodecTest.kt
@@ -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")
+}
diff --git a/wasm/wasm.ir/test/org/jetbrains/kotlin/wasm/ir/SpecTestRunner.kt b/wasm/wasm.ir/test/org/jetbrains/kotlin/wasm/ir/SpecTestRunner.kt
new file mode 100644
index 00000000000..e062e83ccc4
--- /dev/null
+++ b/wasm/wasm.ir/test/org/jetbrains/kotlin/wasm/ir/SpecTestRunner.kt
@@ -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
+) {
+ @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,
+ ) : 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,
+ ) : Command()
+
+ @SerialName("assert_exhaustion")
+ @Serializable
+ data class AssertExhaustion(
+ val line: Int,
+ val action: Action,
+ val text: String,
+ val expected: List,
+ ) : 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,
+ ) : Command()
+ }
+
+ @Serializable
+ data class Action(
+ val type: String,
+ val field: String,
+ val args: List = 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) {
+ 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) {
+ 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 = listOf("--enable-all"),
+ ignoreFiles: List = emptyList()
+) {
+
+ runSpecTests(name, "$wasmTestSuitePath/proposals/$name", wabtOptions, ignoreFiles)
+}
+
+
+fun runSpecTests(
+ name: String,
+ wastDirectoryPath: String,
+ wabtOptions: List,
+ ignoreFiles: List = 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()
diff --git a/wasm/wasm.ir/test/org/jetbrains/kotlin/wasm/ir/Wabt.kt b/wasm/wasm.ir/test/org/jetbrains/kotlin/wasm/ir/Wabt.kt
new file mode 100644
index 00000000000..1d562f0588a
--- /dev/null
+++ b/wasm/wasm.ir/test/org/jetbrains/kotlin/wasm/ir/Wabt.kt
@@ -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("'", "'\\''")}'"