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