[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) {
|
||||
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!!)
|
||||
}
|
||||
}
|
||||
|
||||
+18
-2
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user