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),