From f8c36310638a04e2366909635e1f0093df0afed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 20 Apr 2024 13:07:30 +0200 Subject: [PATCH] Generate source maps. It turns out that source maps are supported for WebAssembly. Positions in the generated code are interpreted as * a single line, and * columns correspond to offsets in the `.wasm` file. We reuse the Scala.js facility to create source maps per se. We feed it the right data from the binary writer. To be consistent with how the Scala.js linker works when emitting JavaScript, we define a subclass of `WasmBinaryWriter` that handles source maps in addition of the regular output. In our model, we store positions as fake `WasmInstr` every time the position (potentially) changes. This is in contrast with the JavaScript backend, which stores a `pos` in every `js.Tree`. There are typically several Wasm instructions that encode any particular `ir.Trees.Tree` (and therefore correspond to one position), so this is probably a better trade-off. --- .../main/scala/WebAssemblyLinkerBackend.scala | 37 ++++- .../scala/converters/WasmBinaryWriter.scala | 92 ++++++++--- .../scala/converters/WasmTextWriter.scala | 38 +++-- .../main/scala/ir2wasm/HelperFunctions.scala | 3 +- wasm/src/main/scala/ir2wasm/WasmBuilder.scala | 10 ++ .../scala/ir2wasm/WasmExpressionBuilder.scala | 153 +++++++++++++++++- .../webassembly/SourceMapWriterAccess.scala | 22 +++ wasm/src/main/scala/wasm4s/Instructions.scala | 5 + wasm/src/main/scala/wasm4s/Wasm.scala | 5 +- wasm/src/main/scala/wasm4s/WasmContext.scala | 5 +- .../scala/wasm4s/WasmFunctionContext.scala | 29 ++-- 11 files changed, 338 insertions(+), 61 deletions(-) create mode 100644 wasm/src/main/scala/org/scalajs/linker/backend/webassembly/SourceMapWriterAccess.scala diff --git a/wasm/src/main/scala/WebAssemblyLinkerBackend.scala b/wasm/src/main/scala/WebAssemblyLinkerBackend.scala index 95955cca..bcdad67e 100644 --- a/wasm/src/main/scala/WebAssemblyLinkerBackend.scala +++ b/wasm/src/main/scala/WebAssemblyLinkerBackend.scala @@ -10,10 +10,13 @@ import org.scalajs.ir.Names._ import org.scalajs.logging.Logger import org.scalajs.linker._ +import org.scalajs.linker.backend.javascript.SourceMapWriter import org.scalajs.linker.interface._ import org.scalajs.linker.interface.unstable._ import org.scalajs.linker.standard._ +import org.scalajs.linker.backend.webassembly.SourceMapWriterAccess + import wasm.ir2wasm._ import wasm.ir2wasm.SpecialNames._ import wasm.wasm4s._ @@ -111,6 +114,7 @@ final class WebAssemblyLinkerBackend( val watFileName = s"$moduleID.wat" val wasmFileName = s"$moduleID.wasm" + val sourceMapFileName = s"$wasmFileName.map" val jsFileName = OutputPatternsImpl.jsFile(linkerConfig.outputPatterns, moduleID) val loaderJSFileName = OutputPatternsImpl.jsFile(linkerConfig.outputPatterns, "__loader") @@ -119,9 +123,12 @@ final class WebAssemblyLinkerBackend( loaderJSFileName, jsFileName ) - val filesToProduce = - if (linkerConfig.prettyPrint) filesToProduce0 + watFileName + val filesToProduce1 = + if (linkerConfig.sourceMap) filesToProduce0 + sourceMapFileName else filesToProduce0 + val filesToProduce = + if (linkerConfig.prettyPrint) filesToProduce1 + watFileName + else filesToProduce1 def maybeWriteWatFile(): Future[Unit] = { if (linkerConfig.prettyPrint) { @@ -135,8 +142,30 @@ final class WebAssemblyLinkerBackend( def writeWasmFile(): Future[Unit] = { val emitDebugInfo = !linkerConfig.minify - val binaryOutput = new converters.WasmBinaryWriter(wasmModule, emitDebugInfo).write() - outputImpl.writeFull(wasmFileName, ByteBuffer.wrap(binaryOutput)) + + if (linkerConfig.sourceMap) { + val sourceMapWriter = new SourceMapWriterAccess.ByteArrayWriterBox() + + val wasmFileURI = s"./$wasmFileName" + val sourceMapURI = s"./$sourceMapFileName" + + val smWriter = + sourceMapWriter.createSourceMapWriter(wasmFileURI, linkerConfig.relativizeSourceMapBase) + val binaryOutput = new converters.WasmBinaryWriter.WithSourceMap( + wasmModule, + emitDebugInfo, + smWriter, + sourceMapURI + ).write() + smWriter.complete() + + outputImpl.writeFull(wasmFileName, ByteBuffer.wrap(binaryOutput)).flatMap { _ => + outputImpl.writeFull(sourceMapFileName, sourceMapWriter.toByteBuffer()) + } + } else { + val binaryOutput = new converters.WasmBinaryWriter(wasmModule, emitDebugInfo).write() + outputImpl.writeFull(wasmFileName, ByteBuffer.wrap(binaryOutput)) + } } def writeLoaderFile(): Future[Unit] = diff --git a/wasm/src/main/scala/converters/WasmBinaryWriter.scala b/wasm/src/main/scala/converters/WasmBinaryWriter.scala index 60c0d576..687eefae 100644 --- a/wasm/src/main/scala/converters/WasmBinaryWriter.scala +++ b/wasm/src/main/scala/converters/WasmBinaryWriter.scala @@ -6,12 +6,15 @@ import scala.annotation.tailrec import java.io.OutputStream import java.io.ByteArrayOutputStream +import org.scalajs.ir.Position +import org.scalajs.linker.backend.javascript.SourceMapWriter + import wasm.wasm4s._ import wasm.wasm4s.Names._ import wasm.wasm4s.Types._ import wasm.wasm4s.WasmInstr.{BlockType, END} -final class WasmBinaryWriter(module: WasmModule, emitDebugInfo: Boolean) { +class WasmBinaryWriter(module: WasmModule, emitDebugInfo: Boolean) { import WasmBinaryWriter._ private val typeIdxValues: Map[WasmTypeName, Int] = @@ -56,6 +59,11 @@ final class WasmBinaryWriter(module: WasmModule, emitDebugInfo: Boolean) { finally localIdxValues = saved } + protected def emitStartFuncPosition(buf: Buffer, pos: Position): Unit = () + protected def emitPosition(buf: Buffer, pos: Position): Unit = () + protected def emitEndFuncPosition(buf: Buffer): Unit = () + protected def emitSourceMapSection(buf: Buffer): Unit = () + def write(): Array[Byte] = { val fullOutput = new Buffer() @@ -88,6 +96,8 @@ final class WasmBinaryWriter(module: WasmModule, emitDebugInfo: Boolean) { if (emitDebugInfo) writeCustomSection(fullOutput, "name")(writeNameCustomSection(_)) + emitSourceMapSection(fullOutput) + fullOutput.result() } @@ -96,7 +106,7 @@ final class WasmBinaryWriter(module: WasmModule, emitDebugInfo: Boolean) { fullOutput.byteLengthSubSection(f) } - private def writeCustomSection(fullOutput: Buffer, customSectionName: String)( + protected final def writeCustomSection(fullOutput: Buffer, customSectionName: String)( f: Buffer => Unit ): Unit = { writeSection(fullOutput, SectionCustom) { buf => @@ -254,6 +264,8 @@ final class WasmBinaryWriter(module: WasmModule, emitDebugInfo: Boolean) { } private def writeFunc(buf: Buffer, func: WasmFunction): Unit = { + emitStartFuncPosition(buf, func.pos) + buf.vec(func.locals.filter(!_.isParameter)) { local => buf.u32(1) writeType(buf, local.typ) @@ -262,6 +274,8 @@ final class WasmBinaryWriter(module: WasmModule, emitDebugInfo: Boolean) { withLocalIdxValues(func.locals.map(_.name).zipWithIndex.toMap) { writeExpr(buf, func.body) } + + emitEndFuncPosition(buf) } private def writeType(buf: Buffer, typ: WasmStorageType): Unit = { @@ -327,28 +341,34 @@ final class WasmBinaryWriter(module: WasmModule, emitDebugInfo: Boolean) { } private def writeInstr(buf: Buffer, instr: WasmInstr): Unit = { - val opcode = instr.opcode - if (opcode <= 0xFF) { - buf.byte(opcode.toByte) - } else { - assert( - opcode <= 0xFFFF, - s"cannot encode an opcode longer than 2 bytes yet: ${opcode.toHexString}" - ) - buf.byte((opcode >>> 8).toByte) - buf.byte(opcode.toByte) - } - - writeInstrImmediates(buf, instr) - instr match { - case instr: WasmInstr.StructuredLabeledInstr => - // We must register even the `None` labels, because they contribute to relative numbering - labelsInScope ::= instr.label - case END => - labelsInScope = labelsInScope.tail + case WasmInstr.PositionMark(pos) => + emitPosition(buf, pos) + case _ => - () + val opcode = instr.opcode + if (opcode <= 0xFF) { + buf.byte(opcode.toByte) + } else { + assert( + opcode <= 0xFFFF, + s"cannot encode an opcode longer than 2 bytes yet: ${opcode.toHexString}" + ) + buf.byte((opcode >>> 8).toByte) + buf.byte(opcode.toByte) + } + + writeInstrImmediates(buf, instr) + + instr match { + case instr: WasmInstr.StructuredLabeledInstr => + // We must register even the `None` labels, because they contribute to relative numbering + labelsInScope ::= instr.label + case END => + labelsInScope = labelsInScope.tail + case _ => + () + } } } @@ -429,6 +449,9 @@ final class WasmBinaryWriter(module: WasmModule, emitDebugInfo: Boolean) { writeBrOnCast(labelIdx, from, to) case BR_ON_CAST_FAIL(labelIdx, from, to) => writeBrOnCast(labelIdx, from, to) + + case PositionMark(pos) => + throw new AssertionError(s"Unexpected $instr") } } @@ -595,4 +618,29 @@ object WasmBinaryWriter { } } } + + final class WithSourceMap( + module: WasmModule, + emitDebugInfo: Boolean, + sourceMapWriter: SourceMapWriter, + sourceMapURI: String + ) extends WasmBinaryWriter(module, emitDebugInfo) { + + override protected def emitStartFuncPosition(buf: Buffer, pos: Position): Unit = + sourceMapWriter.startNode(buf.currentGlobalOffset, pos) + + override protected def emitPosition(buf: Buffer, pos: Position): Unit = { + sourceMapWriter.endNode(buf.currentGlobalOffset) + sourceMapWriter.startNode(buf.currentGlobalOffset, pos) + } + + override protected def emitEndFuncPosition(buf: Buffer): Unit = + sourceMapWriter.endNode(buf.currentGlobalOffset) + + override protected def emitSourceMapSection(buf: Buffer): Unit = { + writeCustomSection(buf, "sourceMappingURL") { buf => + buf.name(sourceMapURI) + } + } + } } diff --git a/wasm/src/main/scala/converters/WasmTextWriter.scala b/wasm/src/main/scala/converters/WasmTextWriter.scala index 658e9d67..d222569e 100644 --- a/wasm/src/main/scala/converters/WasmTextWriter.scala +++ b/wasm/src/main/scala/converters/WasmTextWriter.scala @@ -329,23 +329,30 @@ class WasmTextWriter { private def writeInstr(instr: WasmInstr)(implicit b: WatBuilder): Unit = { instr match { - case END | ELSE | _: CATCH | CATCH_ALL => b.deindent() - case _ => () - } - b.newLine() - b.appendElement(instr.mnemonic) - instr match { - case instr: StructuredLabeledInstr => - instr.label.foreach(writeLabelIdx(_)) - case _ => + case WasmInstr.PositionMark(_) => + // ignore () - } - writeInstrImmediates(instr) + case _ => + instr match { + case END | ELSE | _: CATCH | CATCH_ALL => b.deindent() + case _ => () + } + b.newLine() + b.appendElement(instr.mnemonic) + instr match { + case instr: StructuredLabeledInstr => + instr.label.foreach(writeLabelIdx(_)) + case _ => + () + } - instr match { - case _: StructuredLabeledInstr | ELSE | _: CATCH | CATCH_ALL => b.indent() - case _ => () + writeInstrImmediates(instr) + + instr match { + case _: StructuredLabeledInstr | ELSE | _: CATCH | CATCH_ALL => b.indent() + case _ => () + } } } @@ -419,6 +426,9 @@ class WasmTextWriter { writeLabelIdx(labelIdx) writeType(from) writeType(to) + + case PositionMark(pos) => + throw new AssertionError(s"Unexpected $instr") } } } diff --git a/wasm/src/main/scala/ir2wasm/HelperFunctions.scala b/wasm/src/main/scala/ir2wasm/HelperFunctions.scala index 263a8d0b..a29a3ccc 100644 --- a/wasm/src/main/scala/ir2wasm/HelperFunctions.scala +++ b/wasm/src/main/scala/ir2wasm/HelperFunctions.scala @@ -3,8 +3,8 @@ package wasm.ir2wasm import org.scalajs.ir.{Trees => IRTrees} import org.scalajs.ir.{Types => IRTypes} import org.scalajs.ir.{Names => IRNames} +import org.scalajs.ir.{ClassKind, Position} import org.scalajs.linker.standard.LinkedClass -import org.scalajs.ir.ClassKind import wasm.wasm4s._ import wasm.wasm4s.WasmContext._ @@ -17,6 +17,7 @@ import EmbeddedConstants._ import TypeTransformer._ object HelperFunctions { + private implicit val noPos: Position = Position.NoPosition def genGlobalHelpers()(implicit ctx: WasmContext): Unit = { genStringLiteral() diff --git a/wasm/src/main/scala/ir2wasm/WasmBuilder.scala b/wasm/src/main/scala/ir2wasm/WasmBuilder.scala index ba24cc66..78f0b36c 100644 --- a/wasm/src/main/scala/ir2wasm/WasmBuilder.scala +++ b/wasm/src/main/scala/ir2wasm/WasmBuilder.scala @@ -472,6 +472,8 @@ class WasmBuilder(coreSpec: CoreSpec) { } private def genLoadModuleFunc(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { + implicit val pos = clazz.pos + assert(clazz.kind == ClassKind.ModuleClass) val ctor = clazz.methods .find(_.methodName.isConstructor) @@ -934,6 +936,8 @@ class WasmBuilder(coreSpec: CoreSpec) { } private def genLoadJSClassFunction(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { + implicit val pos = clazz.pos + val cachedJSClassGlobal = WasmGlobal( WasmGlobalName.forJSClassValue(clazz.className), WasmRefType.anyref, @@ -964,6 +968,8 @@ class WasmBuilder(coreSpec: CoreSpec) { } private def genLoadJSModuleFunction(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = { + implicit val pos = clazz.pos + val className = clazz.className val cacheGlobalName = WasmGlobalName.forModuleInstance(className) @@ -1005,6 +1011,8 @@ class WasmBuilder(coreSpec: CoreSpec) { private def transformTopLevelMethodExportDef( exportDef: IRTrees.TopLevelMethodExportDef )(implicit ctx: WasmContext): Unit = { + implicit val pos = exportDef.pos + val method = exportDef.methodDef val exportedName = exportDef.topLevelExportName @@ -1070,6 +1078,8 @@ class WasmBuilder(coreSpec: CoreSpec) { clazz: LinkedClass, method: IRTrees.MethodDef )(implicit ctx: WasmContext): WasmFunction = { + implicit val pos = method.pos + val functionName = Names.WasmFunctionName( method.flags.namespace, clazz.name.name, diff --git a/wasm/src/main/scala/ir2wasm/WasmExpressionBuilder.scala b/wasm/src/main/scala/ir2wasm/WasmExpressionBuilder.scala index 4b12fe42..468e8984 100644 --- a/wasm/src/main/scala/ir2wasm/WasmExpressionBuilder.scala +++ b/wasm/src/main/scala/ir2wasm/WasmExpressionBuilder.scala @@ -797,6 +797,8 @@ private class WasmExpressionBuilder private ( */ expectedType } else { + fctx.markPosition(l) + l match { case IRTrees.BooleanLiteral(v) => instrs += WasmInstr.I32_CONST(if (v) 1 else 0) case IRTrees.ByteLiteral(v) => instrs += WasmInstr.I32_CONST(v) @@ -862,6 +864,8 @@ private class WasmExpressionBuilder private ( // For Select, the receiver can never be a hijacked class, so we can use genTreeAuto genTreeAuto(sel.qualifier) + fctx.markPosition(sel) + if (!classInfo.hasInstances) { /* The field may not exist in that case, and we cannot look it up. * However we necessarily have a `null` receiver if we reach this point, @@ -879,6 +883,7 @@ private class WasmExpressionBuilder private ( } private def genSelectStatic(tree: IRTrees.SelectStatic): IRTypes.Type = { + fctx.markPosition(tree) instrs += GLOBAL_GET(Names.WasmGlobalName.forStaticField(tree.field.name)) tree.tpe } @@ -887,7 +892,10 @@ private class WasmExpressionBuilder private ( val className = fctx.enclosingClassName.getOrElse { throw new AssertionError(s"Cannot emit $t at ${t.pos} without enclosing class name") } + genTreeAuto(IRTrees.This()(IRTypes.ClassType(className))(t.pos)) + + fctx.markPosition(t) instrs += GLOBAL_SET(WasmGlobalName.forModuleInstance(className)) IRTypes.NoType } @@ -897,6 +905,7 @@ private class WasmExpressionBuilder private ( * see: WasmBuilder.genLoadModuleFunc */ private def genLoadModule(t: IRTrees.LoadModule): IRTypes.Type = { + fctx.markPosition(t) instrs += CALL(Names.WasmFunctionName.loadModule(t.className)) t.tpe } @@ -906,6 +915,8 @@ private class WasmExpressionBuilder private ( genTreeAuto(unary.lhs) + fctx.markPosition(unary) + (unary.op: @switch) match { case Boolean_! => instrs += I32_CONST(1) @@ -960,6 +971,7 @@ private class WasmExpressionBuilder private ( def genLongShiftOp(shiftInstr: WasmInstr): IRTypes.Type = { genTree(binary.lhs, IRTypes.LongType) genTree(binary.rhs, IRTypes.IntType) + fctx.markPosition(binary) instrs += I64_EXTEND_I32_S instrs += shiftInstr IRTypes.LongType @@ -968,7 +980,7 @@ private class WasmExpressionBuilder private ( binary.op match { case BinaryOp.=== | BinaryOp.!== => genEq(binary) - case BinaryOp.String_+ => genStringConcat(binary.lhs, binary.rhs) + case BinaryOp.String_+ => genStringConcat(binary) case BinaryOp.Long_<< => genLongShiftOp(I64_SHL) case BinaryOp.Long_>>> => genLongShiftOp(I64_SHR_U) @@ -988,12 +1000,14 @@ private class WasmExpressionBuilder private ( instrs += F64_PROMOTE_F32 genTree(binary.rhs, IRTypes.FloatType) instrs += F64_PROMOTE_F32 + fctx.markPosition(binary) instrs += CALL(WasmFunctionName.fmod) instrs += F32_DEMOTE_F64 IRTypes.FloatType case BinaryOp.Double_% => genTree(binary.lhs, IRTypes.DoubleType) genTree(binary.rhs, IRTypes.DoubleType) + fctx.markPosition(binary) instrs += CALL(WasmFunctionName.fmod) IRTypes.DoubleType @@ -1001,6 +1015,7 @@ private class WasmExpressionBuilder private ( case BinaryOp.String_charAt => genTree(binary.lhs, IRTypes.StringType) // push the string genTree(binary.rhs, IRTypes.IntType) // push the index + fctx.markPosition(binary) instrs += CALL(WasmFunctionName.stringCharAt) IRTypes.CharType @@ -1026,6 +1041,8 @@ private class WasmExpressionBuilder private ( genTreeAuto(binary.rhs) instrs += LOCAL_SET(rhs) + fctx.markPosition(binary) + fctx.block(resType) { done => fctx.block() { default => fctx.block() { divisionByZero => @@ -1084,6 +1101,9 @@ private class WasmExpressionBuilder private ( // TODO Optimize this when the operands have a better type than `any` genTree(binary.lhs, IRTypes.AnyType) genTree(binary.rhs, IRTypes.AnyType) + + fctx.markPosition(binary) + instrs += CALL(WasmFunctionName.is) if (binary.op == IRTrees.BinaryOp.!==) { @@ -1096,8 +1116,12 @@ private class WasmExpressionBuilder private ( private def genElementaryBinaryOp(binary: IRTrees.BinaryOp): IRTypes.Type = { import IRTrees.BinaryOp + genTreeAuto(binary.lhs) genTreeAuto(binary.rhs) + + fctx.markPosition(binary) + val operation = binary.op match { case BinaryOp.Boolean_== => I32_EQ case BinaryOp.Boolean_!= => I32_NE @@ -1159,7 +1183,7 @@ private class WasmExpressionBuilder private ( binary.tpe } - private def genStringConcat(lhs: IRTrees.Tree, rhs: IRTrees.Tree): IRTypes.Type = { + private def genStringConcat(binary: IRTrees.BinaryOp): IRTypes.Type = { val wasmStringType = Types.WasmRefType.any def genToString(tree: IRTrees.Tree): Unit = { @@ -1197,6 +1221,7 @@ private class WasmExpressionBuilder private ( fctx.block(Types.WasmRefType.any) { labelDone => fctx.block() { labelIsNull => genTreeAuto(tree) + fctx.markPosition(binary) instrs += BR_ON_NULL(labelIsNull) instrs += LOCAL_TEE(receiverLocalForDispatch) genTableDispatch(objectClassInfo, toStringMethodName, receiverLocalForDispatch) @@ -1228,6 +1253,8 @@ private class WasmExpressionBuilder private ( // Load receiver genTreeAuto(tree) + fctx.markPosition(binary) + instrs += BR_ON_CAST_FAIL( labelNotOurObject, Types.WasmRefType.anyref, @@ -1248,6 +1275,9 @@ private class WasmExpressionBuilder private ( tree.tpe match { case primType: IRTypes.PrimType => genTreeAuto(tree) + + fctx.markPosition(binary) + primType match { case IRTypes.StringType => () // no-op @@ -1277,6 +1307,7 @@ private class WasmExpressionBuilder private ( case IRTypes.ClassType(IRNames.BoxedStringClass) => // Common case for which we want to avoid the hijacked class dispatch genTreeAuto(tree) + fctx.markPosition(binary) instrs += CALL(WasmFunctionName.jsValueToStringForConcat) // for `null` case IRTypes.ClassType(className) => @@ -1293,14 +1324,15 @@ private class WasmExpressionBuilder private ( } } - lhs match { + binary.lhs match { case IRTrees.StringLiteral("") => // Common case where we don't actually need a concatenation - genToString(rhs) + genToString(binary.rhs) case _ => - genToString(lhs) - genToString(rhs) + genToString(binary.lhs) + genToString(binary.rhs) + fctx.markPosition(binary) instrs += CALL(WasmFunctionName.stringConcat) } @@ -1310,6 +1342,8 @@ private class WasmExpressionBuilder private ( private def genIsInstanceOf(tree: IRTrees.IsInstanceOf): IRTypes.Type = { genTree(tree.expr, IRTypes.AnyType) + fctx.markPosition(tree) + def genIsPrimType(testType: IRTypes.PrimType): Unit = { testType match { case IRTypes.UndefType => @@ -1442,6 +1476,8 @@ private class WasmExpressionBuilder private ( } else { genTree(tree.expr, IRTypes.AnyType) + fctx.markPosition(tree) + def genAsPrimType(targetTpe: IRTypes.PrimType): Unit = { // TODO We could do something better for things like double.asInstanceOf[int] genUnbox(targetTpe)(tree.pos) @@ -1566,11 +1602,13 @@ private class WasmExpressionBuilder private ( val typeDataLocal = fctx.addSyntheticLocal(typeDataType) genTreeAuto(tree.expr) + fctx.markPosition(tree) instrs += STRUCT_GET(objectTypeIdx, WasmFieldIdx.vtable) // implicit trap on null instrs += LOCAL_SET(typeDataLocal) genClassOfFromTypeData(LOCAL_GET(typeDataLocal)) } else { genTree(tree.expr, IRTypes.AnyType) + fctx.markPosition(tree) instrs += REF_AS_NOT_NULL instrs += CALL(WasmFunctionName.anyGetClass) } @@ -1591,11 +1629,14 @@ private class WasmExpressionBuilder private ( } private def genVarRef(r: IRTrees.VarRef): IRTypes.Type = { + fctx.markPosition(r) genReadStorage(fctx.lookupLocal(r.ident.name)) r.tpe } private def genThis(t: IRTrees.This): IRTypes.Type = { + fctx.markPosition(t) + genReadStorage(fctx.receiverStorage) // Workaround wrong t.tpe for This nodes inside reflective proxies. @@ -1638,6 +1679,8 @@ private class WasmExpressionBuilder private ( val ty = TypeTransformer.transformResultType(expectedType)(ctx) genTree(t.cond, IRTypes.BooleanType) + fctx.markPosition(t) + t.elsep match { case IRTrees.Skip() => assert(expectedType == IRTypes.NoType) @@ -1667,8 +1710,10 @@ private class WasmExpressionBuilder private ( // br $label // end // unreachable + fctx.markPosition(t) fctx.loop() { label => genTree(t.body, IRTypes.NoType) + fctx.markPosition(t) instrs += BR(label) } instrs += UNREACHABLE @@ -1682,10 +1727,13 @@ private class WasmExpressionBuilder private ( // br $label // end // end + fctx.markPosition(t) fctx.loop() { label => genTree(t.cond, IRTypes.BooleanType) + fctx.markPosition(t) fctx.ifThen() { genTree(t.body, IRTypes.NoType) + fctx.markPosition(t) instrs += BR(label) } } @@ -1715,6 +1763,7 @@ private class WasmExpressionBuilder private ( if fVarRef.ident.name != t.keyVar.name && argIdent.name == t.keyVar.name => genTree(t.obj, IRTypes.AnyType) genTree(fVarRef, IRTypes.AnyType) + fctx.markPosition(t) instrs += CALL(WasmFunctionName.jsForInSimple) case _ => @@ -1728,8 +1777,10 @@ private class WasmExpressionBuilder private ( val resultType = TypeTransformer.transformResultType(expectedType)(ctx) if (UseLegacyExceptionsForTryCatch) { + fctx.markPosition(t) instrs += TRY(fctx.sigToBlockType(WasmFunctionSignature(Nil, resultType))) genTree(t.block, expectedType) + fctx.markPosition(t) instrs += CATCH(ctx.exceptionTagName) fctx.withNewLocal(t.errVar.name, Types.WasmRefType.anyref) { exceptionLocal => instrs += ANY_CONVERT_EXTERN @@ -1738,6 +1789,7 @@ private class WasmExpressionBuilder private ( } instrs += END } else { + fctx.markPosition(t) fctx.block(resultType) { doneLabel => fctx.block(Types.WasmRefType.externref) { catchLabel => /* We used to have `resultType` as result of the try_table, with the @@ -1750,6 +1802,7 @@ private class WasmExpressionBuilder private ( List(CatchClause.Catch(ctx.exceptionTagName, catchLabel)) ) { genTree(t.block, expectedType) + fctx.markPosition(t) instrs += BR(doneLabel) } } // end block $catch @@ -1769,6 +1822,7 @@ private class WasmExpressionBuilder private ( private def genThrow(tree: IRTrees.Throw): IRTypes.Type = { genTree(tree.expr, IRTypes.AnyType) + fctx.markPosition(tree) instrs += EXTERN_CONVERT_ANY instrs += THROW(ctx.exceptionTagName) @@ -1784,8 +1838,9 @@ private class WasmExpressionBuilder private ( final def genBlockStats[A](stats: List[IRTrees.Tree])(inner: => A): A = { stats match { - case IRTrees.VarDef(name, _, vtpe, _, rhs) :: rest => + case (stat @ IRTrees.VarDef(name, _, vtpe, _, rhs)) :: rest => genTree(rhs, vtpe) + fctx.markPosition(stat) fctx.withNewLocal(name.name, TypeTransformer.transformType(vtpe)(ctx)) { local => instrs += LOCAL_SET(local) genBlockStats(rest)(inner) @@ -1807,9 +1862,14 @@ private class WasmExpressionBuilder private ( Types.WasmRefType.nullable(WasmStructTypeName.forClass(n.className)) val localInstance = fctx.addSyntheticLocal(instanceTyp) + fctx.markPosition(n) instrs += CALL(WasmFunctionName.newDefault(n.className)) instrs += LOCAL_TEE(localInstance) + genArgs(n.args, n.ctor.name) + + fctx.markPosition(n) + instrs += CALL( WasmFunctionName( IRTrees.MemberNamespace.Constructor, @@ -1867,6 +1927,8 @@ private class WasmExpressionBuilder private ( private def genIdentityHashCode(tree: IRTrees.IdentityHashCode): IRTypes.Type = { // TODO Avoid dispatch when we know a more precise type than any genTree(tree.expr, IRTypes.AnyType) + + fctx.markPosition(tree) instrs += CALL(WasmFunctionName.identityHashCode) IRTypes.IntType @@ -1882,6 +1944,8 @@ private class WasmExpressionBuilder private ( fctx.block(throwableTyp) { doneLabel => genTree(tree.expr, IRTypes.AnyType) + fctx.markPosition(tree) + // if expr.isInstanceOf[Throwable], then br $done instrs += BR_ON_CAST( doneLabel, @@ -1915,6 +1979,8 @@ private class WasmExpressionBuilder private ( fctx.block(Types.WasmRefType.anyref) { doneLabel => genTree(tree.expr, IRTypes.ClassType(IRNames.ThrowableClass)) + fctx.markPosition(tree) + instrs += REF_AS_NOT_NULL // if !expr.isInstanceOf[js.JavaScriptException], then br $done @@ -1938,6 +2004,7 @@ private class WasmExpressionBuilder private ( private def genJSNew(tree: IRTrees.JSNew): IRTypes.Type = { genTree(tree.ctor, IRTypes.AnyType) genJSArgsArray(tree.args) + fctx.markPosition(tree) instrs += CALL(WasmFunctionName.jsNew) IRTypes.AnyType } @@ -1945,6 +2012,7 @@ private class WasmExpressionBuilder private ( private def genJSSelect(tree: IRTrees.JSSelect): IRTypes.Type = { genTree(tree.qualifier, IRTypes.AnyType) genTree(tree.item, IRTypes.AnyType) + fctx.markPosition(tree) instrs += CALL(WasmFunctionName.jsSelect) IRTypes.AnyType } @@ -1952,6 +2020,7 @@ private class WasmExpressionBuilder private ( private def genJSFunctionApply(tree: IRTrees.JSFunctionApply): IRTypes.Type = { genTree(tree.fun, IRTypes.AnyType) genJSArgsArray(tree.args) + fctx.markPosition(tree) instrs += CALL(WasmFunctionName.jsFunctionApply) IRTypes.AnyType } @@ -1960,22 +2029,27 @@ private class WasmExpressionBuilder private ( genTree(tree.receiver, IRTypes.AnyType) genTree(tree.method, IRTypes.AnyType) genJSArgsArray(tree.args) + fctx.markPosition(tree) instrs += CALL(WasmFunctionName.jsMethodApply) IRTypes.AnyType } private def genJSImportCall(tree: IRTrees.JSImportCall): IRTypes.Type = { genTree(tree.arg, IRTypes.AnyType) + fctx.markPosition(tree) instrs += CALL(WasmFunctionName.jsImportCall) IRTypes.AnyType } private def genJSImportMeta(tree: IRTrees.JSImportMeta): IRTypes.Type = { + fctx.markPosition(tree) instrs += CALL(WasmFunctionName.jsImportMeta) IRTypes.AnyType } private def genLoadJSConstructor(tree: IRTrees.LoadJSConstructor): IRTypes.Type = { + fctx.markPosition(tree) + val info = ctx.getClassInfo(tree.className) info.kind match { @@ -1997,6 +2071,8 @@ private class WasmExpressionBuilder private ( } private def genLoadJSModule(tree: IRTrees.LoadJSModule): IRTypes.Type = { + fctx.markPosition(tree) + val info = ctx.getClassInfo(tree.className) info.kind match { @@ -2030,12 +2106,14 @@ private class WasmExpressionBuilder private ( private def genJSDelete(tree: IRTrees.JSDelete): IRTypes.Type = { genTree(tree.qualifier, IRTypes.AnyType) genTree(tree.item, IRTypes.AnyType) + fctx.markPosition(tree) instrs += CALL(WasmFunctionName.jsDelete) IRTypes.NoType } private def genJSUnaryOp(tree: IRTrees.JSUnaryOp): IRTypes.Type = { genTree(tree.lhs, IRTypes.AnyType) + fctx.markPosition(tree) instrs += CALL(WasmFunctionName.jsUnaryOps(tree.op)) IRTypes.AnyType } @@ -2050,6 +2128,7 @@ private class WasmExpressionBuilder private ( */ val lhsLocal = fctx.addSyntheticLocal(Types.WasmRefType.anyref) genTree(tree.lhs, IRTypes.AnyType) + fctx.markPosition(tree) instrs += LOCAL_TEE(lhsLocal) instrs += CALL(WasmFunctionName.jsIsTruthy) instrs += IF(BlockType.ValueType(Types.WasmRefType.anyref)) @@ -2057,8 +2136,10 @@ private class WasmExpressionBuilder private ( instrs += LOCAL_GET(lhsLocal) instrs += ELSE genTree(tree.rhs, IRTypes.AnyType) + fctx.markPosition(tree) } else { genTree(tree.rhs, IRTypes.AnyType) + fctx.markPosition(tree) instrs += ELSE instrs += LOCAL_GET(lhsLocal) } @@ -2067,6 +2148,7 @@ private class WasmExpressionBuilder private ( case _ => genTree(tree.lhs, IRTypes.AnyType) genTree(tree.rhs, IRTypes.AnyType) + fctx.markPosition(tree) instrs += CALL(WasmFunctionName.jsBinaryOps(tree.op)) } @@ -2079,6 +2161,7 @@ private class WasmExpressionBuilder private ( } private def genJSObjectConstr(tree: IRTrees.JSObjectConstr): IRTypes.Type = { + fctx.markPosition(tree) instrs += CALL(WasmFunctionName.jsNewObject) for ((prop, value) <- tree.fields) { genTree(prop, IRTypes.AnyType) @@ -2089,12 +2172,14 @@ private class WasmExpressionBuilder private ( } private def genJSGlobalRef(tree: IRTrees.JSGlobalRef): IRTypes.Type = { + fctx.markPosition(tree) instrs ++= ctx.getConstantStringInstr(tree.name) instrs += CALL(WasmFunctionName.jsGlobalRefGet) IRTypes.AnyType } private def genJSTypeOfGlobalRef(tree: IRTrees.JSTypeOfGlobalRef): IRTypes.Type = { + fctx.markPosition(tree) instrs ++= ctx.getConstantStringInstr(tree.globalRef.name) instrs += CALL(WasmFunctionName.jsGlobalRefTypeof) IRTypes.AnyType @@ -2115,6 +2200,7 @@ private class WasmExpressionBuilder private ( } private def genJSLinkingInfo(tree: IRTrees.JSLinkingInfo): IRTypes.Type = { + fctx.markPosition(tree) instrs += CALL(WasmFunctionName.jsLinkingInfo) IRTypes.AnyType } @@ -2125,6 +2211,8 @@ private class WasmExpressionBuilder private ( private def genArrayLength(t: IRTrees.ArrayLength): IRTypes.Type = { genTreeAuto(t.array) + fctx.markPosition(t) + t.array.tpe match { case IRTypes.ArrayType(arrayTypeRef) => // Get the underlying array; implicit trap on null @@ -2157,11 +2245,15 @@ private class WasmExpressionBuilder private ( s"invalid lengths ${t.lengths} for array type ${arrayTypeRef.displayName}" ) + fctx.markPosition(t) + if (t.lengths.size == 1) { genLoadVTableAndITableForArray(arrayTypeRef) // Create the underlying array genTree(t.lengths.head, IRTypes.IntType) + fctx.markPosition(t) + val underlyingArrayType = WasmArrayTypeName.underlyingOf(arrayTypeRef) instrs += ARRAY_NEW_DEFAULT(underlyingArrayType) @@ -2180,6 +2272,7 @@ private class WasmExpressionBuilder private ( // Second arg: an array of the lengths for (length <- t.lengths) genTree(length, IRTypes.IntType) + fctx.markPosition(t) instrs += ARRAY_NEW_FIXED(WasmArrayTypeName.i32Array, t.lengths.size) // Third arg: constant 0 (start index inside the array of lengths) @@ -2206,6 +2299,8 @@ private class WasmExpressionBuilder private ( private def genArraySelect(t: IRTrees.ArraySelect): IRTypes.Type = { genTreeAuto(t.array) + fctx.markPosition(t) + t.array.tpe match { case IRTypes.ArrayType(arrayTypeRef) => // Get the underlying array; implicit trap on null @@ -2217,6 +2312,8 @@ private class WasmExpressionBuilder private ( // Load the index genTree(t.index, IRTypes.IntType) + fctx.markPosition(t) + // Use the appropriate variant of array.get for sign extension val typeIdx = WasmArrayTypeName.underlyingOf(arrayTypeRef) arrayTypeRef match { @@ -2265,6 +2362,8 @@ private class WasmExpressionBuilder private ( private def genArrayValue(t: IRTrees.ArrayValue): IRTypes.Type = { val arrayTypeRef = t.typeRef + fctx.markPosition(t) + genLoadVTableAndITableForArray(arrayTypeRef) val expectedElemType = arrayTypeRef match { @@ -2274,6 +2373,7 @@ private class WasmExpressionBuilder private ( // Create the underlying array t.elems.foreach(genTree(_, expectedElemType)) + fctx.markPosition(t) val underlyingArrayType = WasmArrayTypeName.underlyingOf(arrayTypeRef) instrs += ARRAY_NEW_FIXED(underlyingArrayType, t.elems.size) @@ -2284,6 +2384,7 @@ private class WasmExpressionBuilder private ( } private def genClosure(tree: IRTrees.Closure): IRTypes.Type = { + implicit val pos = tree.pos implicit val ctx = this.ctx val hasThis = !tree.arrow @@ -2314,12 +2415,15 @@ private class WasmExpressionBuilder private ( fctx.buildAndAddToContext() } + fctx.markPosition(tree) + // Put a reference to the function on the stack instrs += ctx.refFuncWithDeclaration(closureFuncName) // Evaluate the capture values and instantiate the capture data struct for ((param, value) <- tree.captureParams.zip(tree.captureValues)) genTree(value, param.ptpe) + fctx.markPosition(tree) instrs += STRUCT_NEW(dataStructTypeName) /* If there is a ...rest param, the helper requires as third argument the @@ -2342,7 +2446,11 @@ private class WasmExpressionBuilder private ( private def genClone(t: IRTrees.Clone): IRTypes.Type = { val expr = fctx.addSyntheticLocal(TypeTransformer.transformType(t.expr.tpe)(ctx)) + genTree(t.expr, IRTypes.ClassType(IRNames.CloneableClass)) + + fctx.markPosition(t) + instrs += REF_CAST(Types.WasmRefType(Types.WasmHeapType.ObjectType)) instrs += LOCAL_TEE(expr) instrs += REF_AS_NOT_NULL // cloneFunction argument is not nullable @@ -2375,7 +2483,11 @@ private class WasmExpressionBuilder private ( private def genMatch(tree: IRTrees.Match, expectedType: IRTypes.Type): IRTypes.Type = { val IRTrees.Match(selector, cases, defaultBody) = tree val selectorLocal = fctx.addSyntheticLocal(TypeTransformer.transformType(selector.tpe)(ctx)) + genTreeAuto(selector) + + fctx.markPosition(tree) + instrs += LOCAL_SET(selectorLocal) fctx.block(TypeTransformer.transformResultType(expectedType)(ctx)) { doneLabel => @@ -2388,6 +2500,7 @@ private class WasmExpressionBuilder private ( caseLabel <- caseLabels matchableLiteral <- caseLabel._1 } { + fctx.markPosition(matchableLiteral) val label = caseLabel._2 instrs += LOCAL_GET(selectorLocal) matchableLiteral match { @@ -2407,6 +2520,7 @@ private class WasmExpressionBuilder private ( instrs += BR(defaultLabel) for ((caseLabel, caze) <- caseLabels.zip(cases).reverse) { + fctx.markPosition(caze._2) instrs += END genTree(caze._2, expectedType) instrs += BR(doneLabel) @@ -2432,6 +2546,8 @@ private class WasmExpressionBuilder private ( for ((captureValue, captureParam) <- tree.captureValues.zip(jsClassCaptures)) genTree(captureValue, captureParam.ptpe) + fctx.markPosition(tree) + instrs += CALL(WasmFunctionName.createJSClassOf(tree.className)) IRTypes.AnyType @@ -2439,6 +2555,9 @@ private class WasmExpressionBuilder private ( private def genJSPrivateSelect(tree: IRTrees.JSPrivateSelect): IRTypes.Type = { genTree(tree.qualifier, IRTypes.AnyType) + + fctx.markPosition(tree) + instrs += GLOBAL_GET(WasmGlobalName.forJSPrivateField(tree.field.name)) instrs += CALL(WasmFunctionName.jsSelect) @@ -2449,6 +2568,9 @@ private class WasmExpressionBuilder private ( genTree(tree.superClass, IRTypes.AnyType) genTree(tree.receiver, IRTypes.AnyType) genTree(tree.item, IRTypes.AnyType) + + fctx.markPosition(tree) + instrs += CALL(WasmFunctionName.jsSuperGet) IRTypes.AnyType @@ -2459,12 +2581,17 @@ private class WasmExpressionBuilder private ( genTree(tree.receiver, IRTypes.AnyType) genTree(tree.method, IRTypes.AnyType) genJSArgsArray(tree.args) + + fctx.markPosition(tree) + instrs += CALL(WasmFunctionName.jsSuperCall) IRTypes.AnyType } private def genJSNewTarget(tree: IRTrees.JSNewTarget): IRTypes.Type = { + fctx.markPosition(tree) + genReadStorage(fctx.newTargetStorage) IRTypes.AnyType @@ -2761,6 +2888,8 @@ private class WasmExpressionBuilder private ( val ty = TypeTransformer.transformResultType(expectedType)(ctx) + fctx.markPosition(t) + // Manual BLOCK here because we have a specific `label` instrs += BLOCK( fctx.sigToBlockType(WasmFunctionSignature(Nil, ty)), @@ -2777,6 +2906,8 @@ private class WasmExpressionBuilder private ( genTree(t.body, expectedType) } + fctx.markPosition(t) + // Deal with crossing behavior if (entry.wasCrossUsed) { assert( @@ -2833,6 +2964,8 @@ private class WasmExpressionBuilder private ( val resultType = TypeTransformer.transformResultType(expectedType)(ctx) val resultLocals = resultType.map(fctx.addSyntheticLocal(_)) + fctx.markPosition(t) + fctx.block() { doneLabel => fctx.block(Types.WasmRefType.exnref) { catchLabel => /* Remember the position in the instruction stream, in case we need @@ -2846,6 +2979,8 @@ private class WasmExpressionBuilder private ( genTree(t.block, expectedType) } + fctx.markPosition(t) + // store the result in locals during the finally block for (resultLocal <- resultLocals.reverse) instrs += LOCAL_SET(resultLocal) @@ -2888,6 +3023,8 @@ private class WasmExpressionBuilder private ( // finally block (during which we leave the `(ref null exn)` on the stack) genTree(t.finalizer, IRTypes.NoType) + fctx.markPosition(t) + if (!entry.wasCrossed) { // If the `exnref` is non-null, rethrow it instrs += BR_ON_NULL(doneLabel) @@ -2979,6 +3116,8 @@ private class WasmExpressionBuilder private ( genTree(t.expr, targetEntry.expectedType) + fctx.markPosition(t) + if (targetEntry.expectedType != IRTypes.NothingType) { innermostTryFinally.filter(_.isInside(targetEntry)) match { case None => diff --git a/wasm/src/main/scala/org/scalajs/linker/backend/webassembly/SourceMapWriterAccess.scala b/wasm/src/main/scala/org/scalajs/linker/backend/webassembly/SourceMapWriterAccess.scala new file mode 100644 index 00000000..5734c86f --- /dev/null +++ b/wasm/src/main/scala/org/scalajs/linker/backend/webassembly/SourceMapWriterAccess.scala @@ -0,0 +1,22 @@ +package org.scalajs.linker.backend.webassembly + +import java.net.URI +import java.nio.ByteBuffer + +import org.scalajs.linker.backend.javascript.{ByteArrayWriter, SourceMapWriter} + +object SourceMapWriterAccess { + + /** A box to hold a `ByteArrayWriter`, which is unfortunately package-private in Scala.js but + * required to create a `SourceMapWriter`. + */ + final class ByteArrayWriterBox() { + private val underlying = new ByteArrayWriter() + + def createSourceMapWriter(jsFileName: String, relativizeBaseURI: Option[URI]): SourceMapWriter = + new SourceMapWriter(underlying, jsFileName, relativizeBaseURI) + + def toByteBuffer(): ByteBuffer = + underlying.toByteBuffer() + } +} diff --git a/wasm/src/main/scala/wasm4s/Instructions.scala b/wasm/src/main/scala/wasm4s/Instructions.scala index 316c0b31..a155ad6e 100644 --- a/wasm/src/main/scala/wasm4s/Instructions.scala +++ b/wasm/src/main/scala/wasm4s/Instructions.scala @@ -1,6 +1,8 @@ package wasm.wasm4s // https://webassembly.github.io/spec/core/syntax/instructions.html +import org.scalajs.ir.Position + import Types._ import Names._ import Names.WasmTypeName._ @@ -96,6 +98,9 @@ object WasmInstr { // The actual instruction list + // Fake instruction to mark position changes + final case class PositionMark(pos: Position) extends WasmInstr("pos", -1) + // Unary operations case object I32_EQZ extends WasmSimpleInstr("i32.eqz", 0x45) case object I64_EQZ extends WasmSimpleInstr("i64.eqz", 0x50) diff --git a/wasm/src/main/scala/wasm4s/Wasm.scala b/wasm/src/main/scala/wasm4s/Wasm.scala index 4542df01..eef9f5cf 100644 --- a/wasm/src/main/scala/wasm4s/Wasm.scala +++ b/wasm/src/main/scala/wasm4s/Wasm.scala @@ -2,6 +2,8 @@ package wasm.wasm4s import scala.collection.mutable +import org.scalajs.ir.Position + import Types._ import Names._ import Names.WasmTypeName._ @@ -36,7 +38,8 @@ case class WasmFunction( val typeName: WasmTypeName, val locals: List[WasmLocal], val results: List[WasmType], - val body: WasmExpr + val body: WasmExpr, + val pos: Position ) /** The index space for locals is only accessible inside a function and includes the parameters of diff --git a/wasm/src/main/scala/wasm4s/WasmContext.scala b/wasm/src/main/scala/wasm4s/WasmContext.scala index 0c6efb80..efaf2d8c 100644 --- a/wasm/src/main/scala/wasm4s/WasmContext.scala +++ b/wasm/src/main/scala/wasm4s/WasmContext.scala @@ -626,7 +626,9 @@ class WasmContext(val module: WasmModule) extends TypeDefinableWasmContext { import WasmInstr._ import WasmTypeName._ - val fctx = WasmFunctionContext(WasmFunctionName.start, Nil, Nil)(this) + implicit val pos = Position.NoPosition + + val fctx = WasmFunctionContext(WasmFunctionName.start, Nil, Nil)(this, pos) import fctx.instrs @@ -719,7 +721,6 @@ class WasmContext(val module: WasmModule) extends TypeDefinableWasmContext { WasmFunctionName(IRTrees.MemberNamespace.PublicStatic, className, methodName) instrs += WasmInstr.CALL(functionName) } - implicit val noPos: Position = Position.NoPosition val stringArrayTypeRef = IRTypes.ArrayTypeRef(IRTypes.ClassRef(IRNames.BoxedStringClass), 1) diff --git a/wasm/src/main/scala/wasm4s/WasmFunctionContext.scala b/wasm/src/main/scala/wasm4s/WasmFunctionContext.scala index cb3e67d3..c82b129f 100644 --- a/wasm/src/main/scala/wasm4s/WasmFunctionContext.scala +++ b/wasm/src/main/scala/wasm4s/WasmFunctionContext.scala @@ -5,6 +5,7 @@ import scala.collection.mutable import org.scalajs.ir.{Names => IRNames} import org.scalajs.ir.{Types => IRTypes} import org.scalajs.ir.{Trees => IRTrees} +import org.scalajs.ir.Position import wasm.wasm4s.Names._ import wasm.wasm4s.Types.WasmType @@ -20,7 +21,8 @@ class WasmFunctionContext private ( _newTargetStorage: Option[WasmFunctionContext.VarStorage.Local], _receiverStorage: Option[WasmFunctionContext.VarStorage.Local], _paramsEnv: WasmFunctionContext.Env, - _resultTypes: List[WasmType] + _resultTypes: List[WasmType], + functionPos: Position ) { import WasmFunctionContext._ @@ -101,6 +103,11 @@ class WasmFunctionContext private ( innerName } + // Position handling + + def markPosition(tree: IRTrees.Tree): Unit = + instrs += PositionMark(tree.pos) + // Helpers to build structured control flow def sigToBlockType(sig: WasmFunctionSignature): BlockType = sig match { @@ -342,7 +349,8 @@ class WasmFunctionContext private ( val dcedInstrs = localDeadCodeEliminationOfInstrs() val expr = WasmExpr(dcedInstrs) - val func = WasmFunction(functionName, functionTypeName, locals.toList, _resultTypes, expr) + val func = + WasmFunction(functionName, functionTypeName, locals.toList, _resultTypes, expr, functionPos) ctx.addFunction(func) func } @@ -428,7 +436,7 @@ object WasmFunctionContext { receiverTyp: Option[WasmType], paramDefs: List[IRTrees.ParamDef], resultTypes: List[WasmType] - )(implicit ctx: TypeDefinableWasmContext): WasmFunctionContext = { + )(implicit ctx: TypeDefinableWasmContext, pos: Position): WasmFunctionContext = { def makeCaptureLikeParamListAndEnv( captureParamName: String, captureLikes: Option[List[(IRNames.LocalName, IRTypes.Type)]] @@ -497,7 +505,8 @@ object WasmFunctionContext { newTargetStorage, receiverStorage, fullEnv, - resultTypes + resultTypes, + pos ) } @@ -508,7 +517,7 @@ object WasmFunctionContext { receiverTyp: Option[WasmType], paramDefs: List[IRTrees.ParamDef], resultTypes: List[WasmType] - )(implicit ctx: TypeDefinableWasmContext): WasmFunctionContext = { + )(implicit ctx: TypeDefinableWasmContext, pos: Position): WasmFunctionContext = { apply( enclosingClassName, name, @@ -527,7 +536,7 @@ object WasmFunctionContext { receiverTyp: Option[WasmType], paramDefs: List[IRTrees.ParamDef], resultTypes: List[WasmType] - )(implicit ctx: TypeDefinableWasmContext): WasmFunctionContext = { + )(implicit ctx: TypeDefinableWasmContext, pos: Position): WasmFunctionContext = { apply( enclosingClassName, name, @@ -544,7 +553,7 @@ object WasmFunctionContext { receiverTyp: Option[WasmType], paramDefs: List[IRTrees.ParamDef], resultType: IRTypes.Type - )(implicit ctx: TypeDefinableWasmContext): WasmFunctionContext = { + )(implicit ctx: TypeDefinableWasmContext, pos: Position): WasmFunctionContext = { apply( enclosingClassName, name, @@ -558,16 +567,16 @@ object WasmFunctionContext { name: WasmFunctionName, params: List[(String, WasmType)], resultTypes: List[WasmType] - )(implicit ctx: TypeDefinableWasmContext): WasmFunctionContext = { + )(implicit ctx: TypeDefinableWasmContext, pos: Position): WasmFunctionContext = { val paramLocals = params.map { param => WasmLocal(WasmLocalName.fromStr(param._1), param._2, isParameter = true) } - new WasmFunctionContext(ctx, None, name, paramLocals, None, None, Map.empty, resultTypes) + new WasmFunctionContext(ctx, None, name, paramLocals, None, None, Map.empty, resultTypes, pos) } def paramDefsToWasmParams( paramDefs: List[IRTrees.ParamDef] - )(implicit ctx: TypeDefinableWasmContext): List[WasmLocal] = { + )(implicit ctx: TypeDefinableWasmContext, pos: Position): List[WasmLocal] = { paramDefs.map { paramDef => WasmLocal( WasmLocalName.fromIR(paramDef.name.name),