[Wasm] Add a basic end-to-end sourcemap generation in wasm backend
More constructions/instructions will be supported separately.
This commit is contained in:
@@ -367,18 +367,23 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
|
|||||||
if (arguments.irDce) {
|
if (arguments.irDce) {
|
||||||
eliminateDeadDeclarations(allModules, backendContext)
|
eliminateDeadDeclarations(allModules, backendContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val sourceMapFileName = if (configuration.getBoolean(JSConfigurationKeys.SOURCE_MAP)) "$outputName.map" else null
|
||||||
|
|
||||||
val res = compileWasm(
|
val res = compileWasm(
|
||||||
allModules = allModules,
|
allModules = allModules,
|
||||||
backendContext = backendContext,
|
backendContext = backendContext,
|
||||||
emitNameSection = arguments.wasmDebug,
|
emitNameSection = arguments.wasmDebug,
|
||||||
allowIncompleteImplementations = arguments.irDce,
|
allowIncompleteImplementations = arguments.irDce,
|
||||||
generateWat = true,
|
generateWat = true,
|
||||||
|
sourceMapFileName = sourceMapFileName
|
||||||
)
|
)
|
||||||
|
|
||||||
writeCompilationResult(
|
writeCompilationResult(
|
||||||
result = res,
|
result = res,
|
||||||
dir = outputDir,
|
dir = outputDir,
|
||||||
fileNameBase = outputName
|
fileNameBase = outputName,
|
||||||
|
sourceMapFileName = sourceMapFileName
|
||||||
)
|
)
|
||||||
|
|
||||||
return OK
|
return OK
|
||||||
|
|||||||
@@ -11,20 +11,31 @@ import org.jetbrains.kotlin.backend.common.serialization.linkerissues.checkNoUnb
|
|||||||
import org.jetbrains.kotlin.backend.wasm.ir2wasm.WasmCompiledModuleFragment
|
import org.jetbrains.kotlin.backend.wasm.ir2wasm.WasmCompiledModuleFragment
|
||||||
import org.jetbrains.kotlin.backend.wasm.ir2wasm.WasmModuleFragmentGenerator
|
import org.jetbrains.kotlin.backend.wasm.ir2wasm.WasmModuleFragmentGenerator
|
||||||
import org.jetbrains.kotlin.backend.wasm.lower.markExportedDeclarations
|
import org.jetbrains.kotlin.backend.wasm.lower.markExportedDeclarations
|
||||||
|
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||||
import org.jetbrains.kotlin.ir.backend.js.MainModule
|
import org.jetbrains.kotlin.ir.backend.js.MainModule
|
||||||
import org.jetbrains.kotlin.ir.backend.js.ModulesStructure
|
import org.jetbrains.kotlin.ir.backend.js.ModulesStructure
|
||||||
|
import org.jetbrains.kotlin.ir.backend.js.SourceMapsInfo
|
||||||
import org.jetbrains.kotlin.ir.backend.js.loadIr
|
import org.jetbrains.kotlin.ir.backend.js.loadIr
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrFactory
|
import org.jetbrains.kotlin.ir.declarations.IrFactory
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||||
import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator
|
import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator
|
||||||
import org.jetbrains.kotlin.ir.util.patchDeclarationParents
|
import org.jetbrains.kotlin.ir.util.patchDeclarationParents
|
||||||
|
import org.jetbrains.kotlin.js.sourceMap.SourceFilePathResolver
|
||||||
|
import org.jetbrains.kotlin.js.sourceMap.SourceMap3Builder
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.wasm.ir.source.location.SourceLocation
|
||||||
import org.jetbrains.kotlin.wasm.ir.convertors.WasmIrToBinary
|
import org.jetbrains.kotlin.wasm.ir.convertors.WasmIrToBinary
|
||||||
import org.jetbrains.kotlin.wasm.ir.convertors.WasmIrToText
|
import org.jetbrains.kotlin.wasm.ir.convertors.WasmIrToText
|
||||||
|
import org.jetbrains.kotlin.wasm.ir.source.location.SourceLocationMapping
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class WasmCompilerResult(val wat: String?, val js: String, val wasm: ByteArray)
|
class WasmCompilerResult(
|
||||||
|
val wat: String?,
|
||||||
|
val js: String,
|
||||||
|
val wasm: ByteArray,
|
||||||
|
val sourceMap: String?
|
||||||
|
)
|
||||||
|
|
||||||
fun compileToLoweredIr(
|
fun compileToLoweredIr(
|
||||||
depsDescriptors: ModulesStructure,
|
depsDescriptors: ModulesStructure,
|
||||||
@@ -77,6 +88,7 @@ fun compileWasm(
|
|||||||
emitNameSection: Boolean = false,
|
emitNameSection: Boolean = false,
|
||||||
allowIncompleteImplementations: Boolean = false,
|
allowIncompleteImplementations: Boolean = false,
|
||||||
generateWat: Boolean = false,
|
generateWat: Boolean = false,
|
||||||
|
sourceMapFileName: String? = null,
|
||||||
): WasmCompilerResult {
|
): WasmCompilerResult {
|
||||||
val compiledWasmModule = WasmCompiledModuleFragment(backendContext.irBuiltIns)
|
val compiledWasmModule = WasmCompiledModuleFragment(backendContext.irBuiltIns)
|
||||||
val codeGenerator = WasmModuleFragmentGenerator(backendContext, compiledWasmModule, allowIncompleteImplementations = allowIncompleteImplementations)
|
val codeGenerator = WasmModuleFragmentGenerator(backendContext, compiledWasmModule, allowIncompleteImplementations = allowIncompleteImplementations)
|
||||||
@@ -95,16 +107,65 @@ fun compileWasm(
|
|||||||
val js = compiledWasmModule.generateJs()
|
val js = compiledWasmModule.generateJs()
|
||||||
|
|
||||||
val os = ByteArrayOutputStream()
|
val os = ByteArrayOutputStream()
|
||||||
WasmIrToBinary(os, linkedModule, allModules.last().descriptor.name.asString(), emitNameSection).appendWasmModule()
|
|
||||||
|
val sourceLocationMappings =
|
||||||
|
if (sourceMapFileName != null) mutableListOf<SourceLocationMapping>() else null
|
||||||
|
|
||||||
|
val wasmIrToBinary =
|
||||||
|
WasmIrToBinary(
|
||||||
|
os,
|
||||||
|
linkedModule,
|
||||||
|
allModules.last().descriptor.name.asString(),
|
||||||
|
emitNameSection,
|
||||||
|
sourceMapFileName,
|
||||||
|
sourceLocationMappings
|
||||||
|
)
|
||||||
|
|
||||||
|
wasmIrToBinary.appendWasmModule()
|
||||||
|
|
||||||
val byteArray = os.toByteArray()
|
val byteArray = os.toByteArray()
|
||||||
|
|
||||||
return WasmCompilerResult(
|
return WasmCompilerResult(
|
||||||
wat = wat,
|
wat = wat,
|
||||||
js = js,
|
js = js,
|
||||||
wasm = byteArray
|
wasm = byteArray,
|
||||||
|
sourceMap = generateSourceMap(backendContext.configuration, sourceLocationMappings)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun generateSourceMap(
|
||||||
|
configuration: CompilerConfiguration,
|
||||||
|
sourceLocationMappings: MutableList<SourceLocationMapping>?
|
||||||
|
): String? {
|
||||||
|
if (sourceLocationMappings == null) return null
|
||||||
|
|
||||||
|
val sourceMapsInfo = SourceMapsInfo.from(configuration) ?: return null
|
||||||
|
|
||||||
|
val sourceMapBuilder =
|
||||||
|
SourceMap3Builder(null, { error("This should not be called for Kotlin/Wasm") }, sourceMapsInfo.sourceMapPrefix)
|
||||||
|
|
||||||
|
val pathResolver =
|
||||||
|
SourceFilePathResolver.create(sourceMapsInfo.sourceRoots, sourceMapsInfo.sourceMapPrefix, sourceMapsInfo.outputDir)
|
||||||
|
|
||||||
|
var prev: SourceLocation? = null
|
||||||
|
|
||||||
|
for (mapping in sourceLocationMappings) {
|
||||||
|
val location = mapping.sourceLocation as? SourceLocation.Location ?: continue
|
||||||
|
|
||||||
|
if (location == prev) continue
|
||||||
|
|
||||||
|
prev = location
|
||||||
|
|
||||||
|
location.apply {
|
||||||
|
// TODO resulting path goes too deep since temporary directory we compiled first is deeper than final destination.
|
||||||
|
val relativePath = pathResolver.getPathRelativeToSourceRoots(File(file)).substring(3)
|
||||||
|
sourceMapBuilder.addMapping(relativePath, null, { null }, line, column, null, mapping.offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sourceMapBuilder.build()
|
||||||
|
}
|
||||||
|
|
||||||
fun WasmCompiledModuleFragment.generateJs(): String {
|
fun WasmCompiledModuleFragment.generateJs(): String {
|
||||||
//language=js
|
//language=js
|
||||||
val runtime = """
|
val runtime = """
|
||||||
@@ -170,7 +231,8 @@ fun generateJsWasmLoader(wasmFilePath: String, externalJs: String): String =
|
|||||||
fun writeCompilationResult(
|
fun writeCompilationResult(
|
||||||
result: WasmCompilerResult,
|
result: WasmCompilerResult,
|
||||||
dir: File,
|
dir: File,
|
||||||
fileNameBase: String = "index",
|
fileNameBase: String,
|
||||||
|
sourceMapFileName: String?
|
||||||
) {
|
) {
|
||||||
dir.mkdirs()
|
dir.mkdirs()
|
||||||
if (result.wat != null) {
|
if (result.wat != null) {
|
||||||
@@ -180,4 +242,8 @@ fun writeCompilationResult(
|
|||||||
|
|
||||||
val jsWithLoader = generateJsWasmLoader("./$fileNameBase.wasm", result.js)
|
val jsWithLoader = generateJsWasmLoader("./$fileNameBase.wasm", result.js)
|
||||||
File(dir, "$fileNameBase.mjs").writeText(jsWithLoader)
|
File(dir, "$fileNameBase.mjs").writeText(jsWithLoader)
|
||||||
|
|
||||||
|
if (sourceMapFileName != null) {
|
||||||
|
File(dir, sourceMapFileName).writeText(result.sourceMap!!)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-2
@@ -27,6 +27,8 @@ import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
|||||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||||
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
|
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
|
||||||
import org.jetbrains.kotlin.wasm.ir.*
|
import org.jetbrains.kotlin.wasm.ir.*
|
||||||
|
import org.jetbrains.kotlin.wasm.ir.source.location.SourceLocation
|
||||||
|
import org.jetbrains.kotlin.wasm.ir.source.location.withLocation
|
||||||
|
|
||||||
class BodyGenerator(
|
class BodyGenerator(
|
||||||
val context: WasmFunctionCodegenContext,
|
val context: WasmFunctionCodegenContext,
|
||||||
@@ -420,7 +422,9 @@ class BodyGenerator(
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Static function call
|
// Static function call
|
||||||
body.buildCall(context.referenceFunction(function.symbol))
|
withLocation(call.getSourceLocation()) {
|
||||||
|
body.buildCall(context.referenceFunction(function.symbol), location)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unit types don't cross function boundaries
|
// Unit types don't cross function boundaries
|
||||||
@@ -646,7 +650,7 @@ class BodyGenerator(
|
|||||||
body.buildGetLocal(context.referenceLocal(0))
|
body.buildGetLocal(context.referenceLocal(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
body.buildInstr(WasmOp.RETURN)
|
body.buildInstr(WasmOp.RETURN, expression.getSourceLocation())
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun generateWithExpectedType(expression: IrExpression, expectedType: IrType) {
|
internal fun generateWithExpectedType(expression: IrExpression, expectedType: IrType) {
|
||||||
@@ -883,4 +887,16 @@ class BodyGenerator(
|
|||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun IrExpression.getSourceLocation(): SourceLocation {
|
||||||
|
val fileEntry = context.irFunction.fileEntry
|
||||||
|
val path = fileEntry.name
|
||||||
|
val startLine = fileEntry.getLineNumber(startOffset)
|
||||||
|
val startColumn = fileEntry.getColumnNumber(startOffset)
|
||||||
|
|
||||||
|
if (startLine < 0 || startColumn < 0) return SourceLocation.NoLocation
|
||||||
|
|
||||||
|
return SourceLocation.Location(path, startLine, startColumn)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ abstract class BasicWasmBoxTest(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
writeCompilationResult(res, dir)
|
writeCompilationResult(res, dir, "index", sourceMapFileName = null)
|
||||||
File(dir, "test.mjs").writeText(testJs)
|
File(dir, "test.mjs").writeText(testJs)
|
||||||
ExternalTool(System.getProperty("javascript.engine.path.V8"))
|
ExternalTool(System.getProperty("javascript.engine.path.V8"))
|
||||||
.run(
|
.run(
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ plugins {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(kotlinStdlib())
|
implementation(kotlinStdlib())
|
||||||
|
implementation(kotlinxCollectionsImmutable())
|
||||||
testImplementation(commonDependency("junit:junit"))
|
testImplementation(commonDependency("junit:junit"))
|
||||||
testCompileOnly(project(":kotlin-test:kotlin-test-jvm"))
|
testCompileOnly(project(":kotlin-test:kotlin-test-jvm"))
|
||||||
testCompileOnly(project(":kotlin-test:kotlin-test-junit"))
|
testCompileOnly(project(":kotlin-test:kotlin-test-junit"))
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.wasm.ir
|
package org.jetbrains.kotlin.wasm.ir
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.wasm.ir.source.location.SourceLocation
|
||||||
|
|
||||||
|
|
||||||
class WasmModule(
|
class WasmModule(
|
||||||
val functionTypes: List<WasmFunctionType> = emptyList(),
|
val functionTypes: List<WasmFunctionType> = emptyList(),
|
||||||
@@ -169,10 +171,30 @@ class WasmStructFieldDeclaration(
|
|||||||
val isMutable: Boolean
|
val isMutable: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
class WasmInstr(
|
sealed class WasmInstr(
|
||||||
val operator: WasmOp,
|
val operator: WasmOp,
|
||||||
val immediates: List<WasmImmediate> = emptyList()
|
val immediates: List<WasmImmediate> = emptyList()
|
||||||
)
|
) {
|
||||||
|
abstract val location: SourceLocation?
|
||||||
|
}
|
||||||
|
|
||||||
|
class WasmInstrWithLocation(
|
||||||
|
operator: WasmOp,
|
||||||
|
immediates: List<WasmImmediate>,
|
||||||
|
override val location: SourceLocation
|
||||||
|
) : WasmInstr(operator, immediates) {
|
||||||
|
constructor(
|
||||||
|
operator: WasmOp,
|
||||||
|
location: SourceLocation
|
||||||
|
) : this(operator, emptyList(), location)
|
||||||
|
}
|
||||||
|
|
||||||
|
class WasmInstrWithoutLocation(
|
||||||
|
operator: WasmOp,
|
||||||
|
immediates: List<WasmImmediate> = emptyList(),
|
||||||
|
) : WasmInstr(operator, immediates) {
|
||||||
|
override val location: SourceLocation? get() = null
|
||||||
|
}
|
||||||
|
|
||||||
data class WasmLimits(
|
data class WasmLimits(
|
||||||
val minSize: UInt,
|
val minSize: UInt,
|
||||||
@@ -182,4 +204,4 @@ data class WasmLimits(
|
|||||||
data class WasmImportPair(
|
data class WasmImportPair(
|
||||||
val moduleName: String,
|
val moduleName: String,
|
||||||
val declarationName: String
|
val declarationName: String
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,12 +5,19 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.wasm.ir
|
package org.jetbrains.kotlin.wasm.ir
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.wasm.ir.source.location.SourceLocation
|
||||||
|
|
||||||
abstract class WasmExpressionBuilder {
|
abstract class WasmExpressionBuilder {
|
||||||
abstract fun buildInstr(op: WasmOp, vararg immediates: WasmImmediate)
|
abstract fun buildInstr(op: WasmOp, location: SourceLocation, vararg immediates: WasmImmediate)
|
||||||
|
|
||||||
|
fun buildInstr(op: WasmOp, vararg immediates: WasmImmediate) {
|
||||||
|
buildInstr(op, SourceLocation.TBDLocation, *immediates)
|
||||||
|
}
|
||||||
|
|
||||||
abstract var numberOfNestedBlocks: Int
|
abstract var numberOfNestedBlocks: Int
|
||||||
|
|
||||||
fun buildConstI32(value: Int) {
|
fun buildConstI32(value: Int, location: SourceLocation = SourceLocation.TBDLocation) {
|
||||||
buildInstr(WasmOp.I32_CONST, WasmImmediate.ConstI32(value))
|
buildInstr(WasmOp.I32_CONST, location, WasmImmediate.ConstI32(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildConstI64(value: Long) {
|
fun buildConstI64(value: Long) {
|
||||||
@@ -105,8 +112,8 @@ abstract class WasmExpressionBuilder {
|
|||||||
buildBrInstr(WasmOp.BR_IF, absoluteBlockLevel)
|
buildBrInstr(WasmOp.BR_IF, absoluteBlockLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildCall(symbol: WasmSymbol<WasmFunction>) {
|
fun buildCall(symbol: WasmSymbol<WasmFunction>, location: SourceLocation = SourceLocation.TBDLocation) {
|
||||||
buildInstr(WasmOp.CALL, WasmImmediate.FuncIdx(symbol))
|
buildInstr(WasmOp.CALL, location, WasmImmediate.FuncIdx(symbol))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildCallIndirect(
|
fun buildCallIndirect(
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.wasm.ir
|
package org.jetbrains.kotlin.wasm.ir
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.wasm.ir.source.location.SourceLocation
|
||||||
|
|
||||||
private fun WasmOp.isOutCfgNode() = when (this) {
|
private fun WasmOp.isOutCfgNode() = when (this) {
|
||||||
WasmOp.UNREACHABLE, WasmOp.RETURN, WasmOp.THROW, WasmOp.RETHROW, WasmOp.BR, WasmOp.BR_TABLE -> true
|
WasmOp.UNREACHABLE, WasmOp.RETURN, WasmOp.THROW, WasmOp.RETHROW, WasmOp.BR, WasmOp.BR_TABLE -> true
|
||||||
else -> false
|
else -> false
|
||||||
@@ -28,8 +30,8 @@ class WasmIrExpressionBuilder(
|
|||||||
get() = expression.lastOrNull()
|
get() = expression.lastOrNull()
|
||||||
private var eatEverythingUntilLevel: Int? = null
|
private var eatEverythingUntilLevel: Int? = null
|
||||||
|
|
||||||
private fun addInstruction(op: WasmOp, immediates: Array<out WasmImmediate>) {
|
private fun addInstruction(op: WasmOp, location: SourceLocation, immediates: Array<out WasmImmediate>) {
|
||||||
val newInstruction = WasmInstr(op, immediates.toList())
|
val newInstruction = WasmInstrWithLocation(op, immediates.toList(), location)
|
||||||
expression.add(newInstruction)
|
expression.add(newInstruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,21 +48,21 @@ class WasmIrExpressionBuilder(
|
|||||||
return eatLevel
|
return eatLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun buildInstr(op: WasmOp, vararg immediates: WasmImmediate) {
|
override fun buildInstr(op: WasmOp, location: SourceLocation, vararg immediates: WasmImmediate) {
|
||||||
val currentEatUntil = getCurrentEatLevel(op)
|
val currentEatUntil = getCurrentEatLevel(op)
|
||||||
if (currentEatUntil != null) {
|
if (currentEatUntil != null) {
|
||||||
if (currentEatUntil <= numberOfNestedBlocks) return
|
if (currentEatUntil <= numberOfNestedBlocks) return
|
||||||
} else {
|
} else {
|
||||||
if (op.isOutCfgNode()) {
|
if (op.isOutCfgNode()) {
|
||||||
eatEverythingUntilLevel = numberOfNestedBlocks
|
eatEverythingUntilLevel = numberOfNestedBlocks
|
||||||
addInstruction(op, immediates)
|
addInstruction(op, location, immediates)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val lastInstruction = lastInstr
|
val lastInstruction = lastInstr
|
||||||
if (lastInstruction == null) {
|
if (lastInstruction == null) {
|
||||||
addInstruction(op, immediates)
|
addInstruction(op, location, immediates)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val lastOperator = lastInstruction.operator
|
val lastOperator = lastInstruction.operator
|
||||||
@@ -78,13 +80,13 @@ class WasmIrExpressionBuilder(
|
|||||||
val localGetNumber = (immediates.firstOrNull() as? WasmImmediate.LocalIdx)?.value
|
val localGetNumber = (immediates.firstOrNull() as? WasmImmediate.LocalIdx)?.value
|
||||||
if (localGetNumber == localSetNumber) {
|
if (localGetNumber == localSetNumber) {
|
||||||
expression.removeLast()
|
expression.removeLast()
|
||||||
addInstruction(WasmOp.LOCAL_TEE, immediates)
|
addInstruction(WasmOp.LOCAL_TEE, location, immediates)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addInstruction(op, immediates)
|
addInstruction(op, location, immediates)
|
||||||
}
|
}
|
||||||
|
|
||||||
override var numberOfNestedBlocks: Int = 0
|
override var numberOfNestedBlocks: Int = 0
|
||||||
@@ -98,4 +100,4 @@ inline fun buildWasmExpression(body: WasmExpressionBuilder.() -> Unit): MutableL
|
|||||||
val res = mutableListOf<WasmInstr>()
|
val res = mutableListOf<WasmInstr>()
|
||||||
WasmIrExpressionBuilder(res).body()
|
WasmIrExpressionBuilder(res).body()
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -442,8 +442,8 @@ class WasmBinaryToIR(val b: MyByteReader) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We don't need location in Binary -> WasmIR, yet.
|
||||||
return WasmInstr(op, immediates)
|
return WasmInstrWithoutLocation(op, immediates)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readTypeDeclaration(): WasmTypeDeclaration {
|
private fun readTypeDeclaration(): WasmTypeDeclaration {
|
||||||
|
|||||||
@@ -3,16 +3,30 @@
|
|||||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
* 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
|
package org.jetbrains.kotlin.wasm.ir.convertors
|
||||||
|
|
||||||
import org.jetbrains.kotlin.wasm.ir.*
|
import org.jetbrains.kotlin.wasm.ir.*
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
import kotlinx.collections.immutable.*
|
||||||
|
import org.jetbrains.kotlin.wasm.ir.source.location.Box
|
||||||
|
import org.jetbrains.kotlin.wasm.ir.source.location.SourceLocation
|
||||||
|
import org.jetbrains.kotlin.wasm.ir.source.location.SourceLocationMapping
|
||||||
|
|
||||||
class WasmIrToBinary(outputStream: OutputStream, val module: WasmModule, val moduleName: String, val emitNameSection: Boolean) {
|
class WasmIrToBinary(
|
||||||
var b: ByteWriter = ByteWriter.OutputStream(outputStream)
|
outputStream: OutputStream,
|
||||||
|
val module: WasmModule,
|
||||||
|
val moduleName: String,
|
||||||
|
val emitNameSection: Boolean,
|
||||||
|
private val sourceMapFileName: String? = null,
|
||||||
|
private val sourceLocationMappings: MutableList<SourceLocationMapping>? = null
|
||||||
|
) {
|
||||||
|
private var b: ByteWriter = ByteWriter.OutputStream(outputStream)
|
||||||
|
|
||||||
|
// "Stack" of offsets waiting initialization.
|
||||||
|
// Since blocks has as a prefix variable length number encoding its size we can't calculate absolute offsets inside those blocks
|
||||||
|
// until we generate whole block and generate size. So, we put them into "stack" and initialize as soo as we have all required data.
|
||||||
|
private var offsets = persistentListOf<Box>()
|
||||||
|
|
||||||
fun appendWasmModule() {
|
fun appendWasmModule() {
|
||||||
b.writeUInt32(0x6d736100u) // WebAssembly magic
|
b.writeUInt32(0x6d736100u) // WebAssembly magic
|
||||||
@@ -114,10 +128,18 @@ class WasmIrToBinary(outputStream: OutputStream, val module: WasmModule, val mod
|
|||||||
data.forEach { appendData(it) }
|
data.forEach { appendData(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
//text section (should be placed after data)
|
// text section (should be placed after data)
|
||||||
if (emitNameSection) {
|
if (emitNameSection) {
|
||||||
appendTextSection(definedFunctions)
|
appendTextSection(definedFunctions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sourceMapFileName != null) {
|
||||||
|
// Custom section with URL to sourcemap
|
||||||
|
appendSection(0u) {
|
||||||
|
b.writeString("sourceMappingURL")
|
||||||
|
b.writeString(sourceMapFileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,6 +205,10 @@ class WasmIrToBinary(outputStream: OutputStream, val module: WasmModule, val mod
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun appendInstr(instr: WasmInstr) {
|
private fun appendInstr(instr: WasmInstr) {
|
||||||
|
instr.location?.let {
|
||||||
|
sourceLocationMappings?.add(SourceLocationMapping(offsets + Box(b.written), it))
|
||||||
|
}
|
||||||
|
|
||||||
val opcode = instr.operator.opcode
|
val opcode = instr.operator.opcode
|
||||||
if (opcode > 0xFF) {
|
if (opcode > 0xFF) {
|
||||||
b.writeByte((opcode ushr 8).toByte())
|
b.writeByte((opcode ushr 8).toByte())
|
||||||
@@ -241,14 +267,21 @@ class WasmIrToBinary(outputStream: OutputStream, val module: WasmModule, val mod
|
|||||||
withVarUInt32PayloadSizePrepended { content() }
|
withVarUInt32PayloadSizePrepended { content() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
private fun withVarUInt32PayloadSizePrepended(fn: () -> Unit) {
|
||||||
fun withVarUInt32PayloadSizePrepended(fn: () -> Unit) {
|
val box = Box(-1)
|
||||||
|
val previousOffsets = offsets
|
||||||
|
offsets += box
|
||||||
|
|
||||||
val previousWriter = b
|
val previousWriter = b
|
||||||
val newWriter = b.createTemp()
|
val newWriter = b.createTemp()
|
||||||
b = newWriter
|
b = newWriter
|
||||||
fn()
|
fn()
|
||||||
b = previousWriter
|
b = previousWriter
|
||||||
b.writeVarUInt32(newWriter.written)
|
b.writeVarUInt32(newWriter.written)
|
||||||
|
|
||||||
|
box.value = b.written
|
||||||
|
offsets = previousOffsets
|
||||||
|
|
||||||
b.write(newWriter)
|
b.write(newWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,7 +383,7 @@ class WasmIrToBinary(outputStream: OutputStream, val module: WasmModule, val mod
|
|||||||
}
|
}
|
||||||
appendType(c.type)
|
appendType(c.type)
|
||||||
b.writeVarUInt1(c.isMutable)
|
b.writeVarUInt1(c.isMutable)
|
||||||
appendExpr(c.init)
|
appendExpr(c.init, SourceLocation.TBDLocation)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun appendTag(t: WasmTag) {
|
private fun appendTag(t: WasmTag) {
|
||||||
@@ -365,9 +398,9 @@ class WasmIrToBinary(outputStream: OutputStream, val module: WasmModule, val mod
|
|||||||
b.writeVarUInt32(t.type.id!!)
|
b.writeVarUInt32(t.type.id!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun appendExpr(expr: Iterable<WasmInstr>) {
|
private fun appendExpr(expr: Iterable<WasmInstr>, location: SourceLocation) {
|
||||||
expr.forEach { appendInstr(it) }
|
expr.forEach { appendInstr(it) }
|
||||||
appendInstr(WasmInstr(WasmOp.END))
|
appendInstr(WasmInstrWithLocation(WasmOp.END, location))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun appendExport(export: WasmExport<*>) {
|
private fun appendExport(export: WasmExport<*>) {
|
||||||
@@ -393,7 +426,7 @@ class WasmIrToBinary(outputStream: OutputStream, val module: WasmModule, val mod
|
|||||||
funcIndices.forEach { b.writeVarUInt32(it) }
|
funcIndices.forEach { b.writeVarUInt32(it) }
|
||||||
} else {
|
} else {
|
||||||
element.values.forEach {
|
element.values.forEach {
|
||||||
appendExpr((it as WasmTable.Value.Expression).expr)
|
appendExpr((it as WasmTable.Value.Expression).expr, SourceLocation.TBDLocation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -417,18 +450,18 @@ class WasmIrToBinary(outputStream: OutputStream, val module: WasmModule, val mod
|
|||||||
when {
|
when {
|
||||||
tableId == 0 && isFuncIndices -> {
|
tableId == 0 && isFuncIndices -> {
|
||||||
b.writeByte(0x0)
|
b.writeByte(0x0)
|
||||||
appendExpr(mode.offset)
|
appendExpr(mode.offset, SourceLocation.TBDLocation)
|
||||||
}
|
}
|
||||||
isFuncIndices -> {
|
isFuncIndices -> {
|
||||||
b.writeByte(0x2)
|
b.writeByte(0x2)
|
||||||
appendModuleFieldReference(mode.table)
|
appendModuleFieldReference(mode.table)
|
||||||
appendExpr(mode.offset)
|
appendExpr(mode.offset, SourceLocation.TBDLocation)
|
||||||
writeTypeOrKind()
|
writeTypeOrKind()
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
b.writeByte(0x6)
|
b.writeByte(0x6)
|
||||||
appendModuleFieldReference(mode.table)
|
appendModuleFieldReference(mode.table)
|
||||||
appendExpr(mode.offset)
|
appendExpr(mode.offset, SourceLocation.TBDLocation)
|
||||||
writeTypeOrKind()
|
writeTypeOrKind()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -452,7 +485,7 @@ class WasmIrToBinary(outputStream: OutputStream, val module: WasmModule, val mod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
appendExpr(function.instructions)
|
appendExpr(function.instructions, SourceLocation.TBDLocation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,7 +498,7 @@ class WasmIrToBinary(outputStream: OutputStream, val module: WasmModule, val mod
|
|||||||
b.writeByte(2)
|
b.writeByte(2)
|
||||||
b.writeVarUInt32(mode.memoryIdx)
|
b.writeVarUInt32(mode.memoryIdx)
|
||||||
}
|
}
|
||||||
appendExpr(mode.offset)
|
appendExpr(mode.offset, SourceLocation.TBDLocation)
|
||||||
}
|
}
|
||||||
WasmDataMode.Passive -> b.writeByte(1)
|
WasmDataMode.Passive -> b.writeByte(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2022 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.source.location
|
||||||
|
|
||||||
|
class Box(var value: Int)
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2022 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.source.location
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
value class LocationHolder(val location: SourceLocation)
|
||||||
|
|
||||||
|
inline fun <R> withLocation(location: SourceLocation, body: LocationHolder.() -> R): R = LocationHolder(location).body()
|
||||||
|
|
||||||
|
inline fun <R> withNoLocation(body: LocationHolder.() -> R): R = withLocation(SourceLocation.NoLocation, body)
|
||||||
|
|
||||||
|
inline fun <R> withTBDLocation(body: LocationHolder.() -> R): R = withLocation<R>(SourceLocation.TBDLocation, body)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2022 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.source.location
|
||||||
|
|
||||||
|
sealed class SourceLocation {
|
||||||
|
object NoLocation: SourceLocation()
|
||||||
|
object TBDLocation: SourceLocation()
|
||||||
|
|
||||||
|
data class Location(val file: String, val line: Int, val column: Int) : SourceLocation()
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2022 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.source.location
|
||||||
|
|
||||||
|
class SourceLocationMapping(
|
||||||
|
// Offsets in generating binary, initialized lazily. Since blocks has as a prefix variable length number encoding its size
|
||||||
|
// we can't calculate absolute offsets inside those blocks until we generate whole block and generate size.
|
||||||
|
private val offsets: List<Box>,
|
||||||
|
val sourceLocation: SourceLocation
|
||||||
|
) {
|
||||||
|
val offset by lazy {
|
||||||
|
offsets.sumOf {
|
||||||
|
assert(it.value >= 0) { "Offset must be >=0 but ${it.value}" }
|
||||||
|
it.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user