[Wasm] Add a basic end-to-end sourcemap generation in wasm backend

More constructions/instructions will be supported separately.
This commit is contained in:
Zalim Bashorov
2022-12-06 16:19:59 +01:00
committed by teamcity
parent 1c4614e93b
commit 49c3ba33f1
14 changed files with 250 additions and 42 deletions
@@ -367,18 +367,23 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
if (arguments.irDce) {
eliminateDeadDeclarations(allModules, backendContext)
}
val sourceMapFileName = if (configuration.getBoolean(JSConfigurationKeys.SOURCE_MAP)) "$outputName.map" else null
val res = compileWasm(
allModules = allModules,
backendContext = backendContext,
emitNameSection = arguments.wasmDebug,
allowIncompleteImplementations = arguments.irDce,
generateWat = true,
sourceMapFileName = sourceMapFileName
)
writeCompilationResult(
result = res,
dir = outputDir,
fileNameBase = outputName
fileNameBase = outputName,
sourceMapFileName = sourceMapFileName
)
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.WasmModuleFragmentGenerator
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.ModulesStructure
import org.jetbrains.kotlin.ir.backend.js.SourceMapsInfo
import org.jetbrains.kotlin.ir.backend.js.loadIr
import org.jetbrains.kotlin.ir.declarations.IrFactory
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator
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.wasm.ir.source.location.SourceLocation
import org.jetbrains.kotlin.wasm.ir.convertors.WasmIrToBinary
import org.jetbrains.kotlin.wasm.ir.convertors.WasmIrToText
import org.jetbrains.kotlin.wasm.ir.source.location.SourceLocationMapping
import java.io.ByteArrayOutputStream
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(
depsDescriptors: ModulesStructure,
@@ -77,6 +88,7 @@ fun compileWasm(
emitNameSection: Boolean = false,
allowIncompleteImplementations: Boolean = false,
generateWat: Boolean = false,
sourceMapFileName: String? = null,
): WasmCompilerResult {
val compiledWasmModule = WasmCompiledModuleFragment(backendContext.irBuiltIns)
val codeGenerator = WasmModuleFragmentGenerator(backendContext, compiledWasmModule, allowIncompleteImplementations = allowIncompleteImplementations)
@@ -95,16 +107,65 @@ fun compileWasm(
val js = compiledWasmModule.generateJs()
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()
return WasmCompilerResult(
wat = wat,
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 {
//language=js
val runtime = """
@@ -170,7 +231,8 @@ fun generateJsWasmLoader(wasmFilePath: String, externalJs: String): String =
fun writeCompilationResult(
result: WasmCompilerResult,
dir: File,
fileNameBase: String = "index",
fileNameBase: String,
sourceMapFileName: String?
) {
dir.mkdirs()
if (result.wat != null) {
@@ -180,4 +242,8 @@ fun writeCompilationResult(
val jsWithLoader = generateJsWasmLoader("./$fileNameBase.wasm", result.js)
File(dir, "$fileNameBase.mjs").writeText(jsWithLoader)
if (sourceMapFileName != null) {
File(dir, sourceMapFileName).writeText(result.sourceMap!!)
}
}
@@ -27,6 +27,8 @@ import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
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(
val context: WasmFunctionCodegenContext,
@@ -420,7 +422,9 @@ class BodyGenerator(
} else {
// 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
@@ -646,7 +650,7 @@ class BodyGenerator(
body.buildGetLocal(context.referenceLocal(0))
}
body.buildInstr(WasmOp.RETURN)
body.buildInstr(WasmOp.RETURN, expression.getSourceLocation())
}
internal fun generateWithExpectedType(expression: IrExpression, expectedType: IrType) {
@@ -883,4 +887,16 @@ class BodyGenerator(
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)
}
}