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