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