From d68018d2d31e13e50f0e99364b5550d38986502b Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Mon, 14 Oct 2024 20:24:34 +0200 Subject: [PATCH] wasm gc: support source maps --- .../org/teavm/backend/wasm/WasmGCTarget.java | 33 ++- .../wasm/debug/CompositeDebugLines.java | 59 +++++ .../debug/sourcemap/SourceMapBuilder.java | 205 ++++++++++++++++++ .../java/org/teavm/tooling/TeaVMTool.java | 64 +++--- .../sources/DefaultSourceFileResolver.java | 74 +++++++ .../junit/BaseWebAssemblyPlatformSupport.java | 2 +- .../org/teavm/junit/TestPlatformSupport.java | 10 +- .../junit/WebAssemblyGCPlatformSupport.java | 40 +++- 8 files changed, 448 insertions(+), 39 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/wasm/debug/CompositeDebugLines.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/debug/sourcemap/SourceMapBuilder.java create mode 100644 tools/core/src/main/java/org/teavm/tooling/sources/DefaultSourceFileResolver.java diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java index 63b806445..08d2282e3 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java @@ -22,9 +22,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; +import org.teavm.backend.wasm.debug.CompositeDebugLines; import org.teavm.backend.wasm.debug.DebugLines; import org.teavm.backend.wasm.debug.ExternalDebugFile; import org.teavm.backend.wasm.debug.GCDebugInfoBuilder; +import org.teavm.backend.wasm.debug.sourcemap.SourceMapBuilder; import org.teavm.backend.wasm.gc.TeaVMWasmGCHost; import org.teavm.backend.wasm.gc.WasmGCClassConsumer; import org.teavm.backend.wasm.gc.WasmGCClassConsumerContext; @@ -40,6 +42,7 @@ import org.teavm.backend.wasm.generators.gc.WasmGCCustomGenerators; import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsic; import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsicFactory; import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsics; +import org.teavm.backend.wasm.model.WasmCustomSection; import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmModule; import org.teavm.backend.wasm.model.WasmTag; @@ -77,6 +80,8 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { private boolean strict; private boolean obfuscated; private boolean debugInfo; + private SourceMapBuilder sourceMapBuilder; + private String sourceMapLocation; private WasmDebugInfoLocation debugLocation = WasmDebugInfoLocation.EXTERNAL; private WasmDebugInfoLevel debugLevel = WasmDebugInfoLevel.FULL; private List intrinsicFactories = new ArrayList<>(); @@ -107,6 +112,14 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { this.debugLocation = debugLocation; } + public void setSourceMapBuilder(SourceMapBuilder sourceMapBuilder) { + this.sourceMapBuilder = sourceMapBuilder; + } + + public void setSourceMapLocation(String sourceMapLocation) { + this.sourceMapLocation = sourceMapLocation; + } + @Override public void addIntrinsicFactory(WasmGCIntrinsicFactory intrinsicFactory) { intrinsicFactories.add(intrinsicFactory); @@ -337,7 +350,22 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { var binaryWriter = new WasmBinaryWriter(); DebugLines debugLines = null; if (debugInfo) { - debugLines = debugInfoBuilder.lines(); + if (sourceMapBuilder != null) { + debugLines = new CompositeDebugLines(debugInfoBuilder.lines(), sourceMapBuilder); + } else { + debugLines = debugInfoBuilder.lines(); + } + } else if (sourceMapBuilder != null) { + debugLines = sourceMapBuilder; + } + if (!outputName.endsWith(".wasm")) { + outputName += ".wasm"; + } + if (sourceMapBuilder != null && sourceMapLocation != null) { + var sourceMapBinding = new WasmBinaryWriter(); + sourceMapBinding.writeAsciiString(sourceMapLocation); + var sourceMapSection = new WasmCustomSection("sourceMappingURL", sourceMapBinding.getData()); + module.add(sourceMapSection); } var binaryRenderer = new WasmBinaryRenderer(binaryWriter, WasmBinaryVersion.V_0x1, obfuscated, null, null, debugLines, null, WasmBinaryStatsCollector.EMPTY); @@ -349,9 +377,6 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { binaryRenderer.render(module); } var data = binaryWriter.getData(); - if (!outputName.endsWith(".wasm")) { - outputName += ".wasm"; - } try (var output = buildTarget.createResource(outputName)) { output.write(data); } diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/CompositeDebugLines.java b/core/src/main/java/org/teavm/backend/wasm/debug/CompositeDebugLines.java new file mode 100644 index 000000000..0b919643e --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/CompositeDebugLines.java @@ -0,0 +1,59 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.backend.wasm.debug; + +import org.teavm.backend.wasm.debug.sourcemap.SourceMapBuilder; +import org.teavm.model.MethodReference; + +public class CompositeDebugLines implements DebugLines { + private DebugLines debugLinesBuilder; + private SourceMapBuilder sourceMapBuilder; + + public CompositeDebugLines(DebugLines debugLinesBuilder, SourceMapBuilder sourceMapBuilder) { + this.debugLinesBuilder = debugLinesBuilder; + this.sourceMapBuilder = sourceMapBuilder; + } + + @Override + public void advance(int ptr) { + debugLinesBuilder.advance(ptr); + sourceMapBuilder.advance(ptr); + } + + @Override + public void location(String file, int line) { + debugLinesBuilder.location(file, line); + sourceMapBuilder.location(file, line); + } + + @Override + public void emptyLocation() { + debugLinesBuilder.emptyLocation(); + sourceMapBuilder.emptyLocation(); + } + + @Override + public void start(MethodReference methodReference) { + debugLinesBuilder.start(methodReference); + sourceMapBuilder.start(methodReference); + } + + @Override + public void end() { + debugLinesBuilder.end(); + sourceMapBuilder.end(); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/debug/sourcemap/SourceMapBuilder.java b/core/src/main/java/org/teavm/backend/wasm/debug/sourcemap/SourceMapBuilder.java new file mode 100644 index 000000000..fda719b38 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/debug/sourcemap/SourceMapBuilder.java @@ -0,0 +1,205 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.backend.wasm.debug.sourcemap; + +import com.carrotsearch.hppc.ObjectIntHashMap; +import com.carrotsearch.hppc.ObjectIntMap; +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; +import java.util.Objects; +import org.teavm.backend.wasm.debug.DebugLines; +import org.teavm.common.JsonUtil; +import org.teavm.debugging.information.SourceFileResolver; +import org.teavm.model.MethodReference; + +public class SourceMapBuilder implements DebugLines { + private static final String BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + private List fileNames = new ArrayList<>(); + private ObjectIntMap fileNameIndexes = new ObjectIntHashMap<>(); + private int ptr; + private StringBuilder mappings = new StringBuilder(); + private String currentFile; + private int currentLine; + private String lastWrittenFile; + private int lastWrittenLine; + private boolean pendingLocation; + private Deque inlineStack = new ArrayDeque<>(); + private List sourceFileResolvers = new ArrayList<>(); + + private String lastFileInMappings; + private int lastLineInMappings; + private int lastPtrInMappings; + + public void addSourceResolver(SourceFileResolver sourceFileResolver) { + sourceFileResolvers.add(sourceFileResolver); + } + + public void writeSourceMap(Writer output) throws IOException { + output.write("{\"version\":3"); + + var files = resolveFiles(); + if (!files.isEmpty()) { + var commonPrefix = files.get(0); + var commonPrefixLength = commonPrefix.length(); + for (var i = 1; i < files.size(); ++i) { + var file = files.get(i); + commonPrefixLength = Math.min(file.length(), commonPrefixLength); + for (var j = 0; j < commonPrefixLength; ++j) { + if (commonPrefix.charAt(j) != file.charAt(j)) { + commonPrefixLength = j; + break; + } + } + if (commonPrefixLength == 0) { + break; + } + } + if (commonPrefixLength > 0) { + for (var i = 0; i < files.size(); ++i) { + files.set(i, files.get(i).substring(commonPrefixLength)); + } + output.write(",\"sourceRoot\":\""); + JsonUtil.writeEscapedString(output, commonPrefix.substring(0, commonPrefixLength)); + output.write("\""); + } + } + output.write(",\"sources\":["); + for (int i = 0; i < files.size(); ++i) { + if (i > 0) { + output.write(','); + } + output.write("\""); + var name = files.get(i); + JsonUtil.writeEscapedString(output, name); + output.write("\""); + } + output.write("]"); + output.write(",\"names\":[]"); + output.write(",\"mappings\":\""); + output.write(mappings.toString()); + output.write("\"}"); + } + + private List resolveFiles() throws IOException { + var result = new ArrayList(); + for (var file : fileNames) { + var resolvedFile = file; + for (var resolver : sourceFileResolvers) { + var candidate = resolver.resolveFile(file); + if (candidate != null) { + resolvedFile = candidate; + break; + } + } + result.add(resolvedFile); + } + return result; + } + + @Override + public void advance(int ptr) { + if (ptr != this.ptr) { + if (pendingLocation) { + pendingLocation = false; + if (!Objects.equals(currentFile, lastWrittenFile) || currentLine != lastWrittenLine) { + lastWrittenFile = currentFile; + lastWrittenLine = currentLine; + writeMapping(currentFile, currentLine, this.ptr); + } + } + this.ptr = ptr; + } + } + + @Override + public void location(String file, int line) { + currentFile = file; + currentLine = line; + pendingLocation = true; + } + + @Override + public void emptyLocation() { + currentLine = -1; + currentFile = null; + pendingLocation = false; + } + + @Override + public void start(MethodReference methodReference) { + inlineStack.push(new InlineState(currentFile, currentLine)); + } + + @Override + public void end() { + var state = inlineStack.pop(); + location(state.file, state.line); + } + + private void writeMapping(String file, int line, int ptr) { + if (mappings.length() > 0) { + mappings.append(","); + } + writeVLQ(ptr - lastPtrInMappings); + if (file != null && line > 0) { + --line; + var lastFileIndex = fileNameIndexes.get(lastFileInMappings); + var fileIndex = fileNameIndexes.getOrDefault(file, -1); + if (fileIndex < 0) { + fileIndex = fileNames.size(); + fileNames.add(file); + fileNameIndexes.put(file, fileIndex); + } + writeVLQ(fileIndex - lastFileIndex); + writeVLQ(line - lastLineInMappings); + writeVLQ(0); + lastLineInMappings = line; + lastFileInMappings = file; + } + lastPtrInMappings = ptr; + } + + private void writeVLQ(int number) { + if (number < 0) { + number = ((-number) << 1) | 1; + } else { + number = number << 1; + } + do { + int digit = number & 0x1F; + int next = number >>> 5; + if (next != 0) { + digit |= 0x20; + } + mappings.append(BASE64_CHARS.charAt(digit)); + number = next; + } while (number != 0); + } + + private static class InlineState { + String file; + int line; + + InlineState(String file, int line) { + this.file = file; + this.line = line; + } + } +} diff --git a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java index 1aacf1133..d5cdfc837 100644 --- a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java +++ b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java @@ -43,6 +43,7 @@ import org.teavm.backend.wasm.WasmDebugInfoLocation; import org.teavm.backend.wasm.WasmGCTarget; import org.teavm.backend.wasm.WasmRuntimeType; import org.teavm.backend.wasm.WasmTarget; +import org.teavm.backend.wasm.debug.sourcemap.SourceMapBuilder; import org.teavm.backend.wasm.render.WasmBinaryVersion; import org.teavm.cache.AlwaysStaleCacheStatus; import org.teavm.cache.CacheStatus; @@ -65,6 +66,7 @@ import org.teavm.model.PreOptimizingClassHolderSource; import org.teavm.model.ReferenceCache; import org.teavm.model.transformation.AssertionRemoval; import org.teavm.parsing.ClasspathClassHolderSource; +import org.teavm.tooling.sources.DefaultSourceFileResolver; import org.teavm.tooling.sources.SourceFileProvider; import org.teavm.vm.BuildTarget; import org.teavm.vm.DirectoryBuildTarget; @@ -121,6 +123,7 @@ public class TeaVMTool { private boolean heapDump; private boolean shortFileNames; private boolean assertionsRemoved; + private SourceMapBuilder wasmSourceMapWriter; public File getTargetDirectory() { return targetDirectory; @@ -403,6 +406,10 @@ public class TeaVMTool { target.setDebugInfo(debugInformationGenerated); target.setDebugInfoLevel(debugInformationGenerated ? WasmDebugInfoLevel.FULL : wasmDebugInfoLevel); target.setDebugInfoLocation(wasmDebugInfoLocation); + if (sourceMapsFileGenerated) { + target.setSourceMapBuilder(wasmSourceMapWriter); + target.setSourceMapLocation(getResolvedTargetFileName() + ".map"); + } return target; } @@ -527,6 +534,8 @@ public class TeaVMTool { Writer writer = new OutputStreamWriter(output, StandardCharsets.UTF_8)) { additionalJavaScriptOutput(writer); } + } else if (targetType == TeaVMTargetType.WEBASSEMBLY_GC) { + additionalWasmGCOutput(); } if (incremental) { @@ -591,42 +600,39 @@ public class TeaVMTool { } } + private void additionalWasmGCOutput() throws IOException { + if (sourceMapsFileGenerated) { + var targetDir = new File(targetDirectory, "src"); + var resolver = new DefaultSourceFileResolver(targetDir, sourceFileProviders); + resolver.setSourceFilePolicy(sourceFilePolicy); + resolver.open(); + + if (sourceFilePolicy != TeaVMSourceFilePolicy.DO_NOTHING) { + wasmSourceMapWriter.addSourceResolver(resolver); + } + var file = new File(targetDirectory, getResolvedTargetFileName() + ".map"); + try (var out = new FileOutputStream(file); + var writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) { + wasmSourceMapWriter.writeSourceMap(writer); + } + + resolver.close(); + } + } + private void writeSourceMaps(Writer out, DebugInformation debugInfo) throws IOException { var sourceMapWriter = new SourceMapsWriter(out); - for (var provider : sourceFileProviders) { - provider.open(); - } - var targetDir = new File(targetDirectory, "src"); + var resolver = new DefaultSourceFileResolver(targetDir, sourceFileProviders); + resolver.setSourceFilePolicy(sourceFilePolicy); + resolver.open(); + if (sourceFilePolicy != TeaVMSourceFilePolicy.DO_NOTHING) { - sourceMapWriter.addSourceResolver(fileName -> { - for (var provider : sourceFileProviders) { - var sourceFile = provider.getSourceFile(fileName); - if (sourceFile != null) { - if (sourceFilePolicy == TeaVMSourceFilePolicy.COPY || sourceFile.getFile() == null) { - var outputFile = new File(targetDir, fileName); - outputFile.getParentFile().mkdirs(); - try (var input = sourceFile.open(); - var output = new FileOutputStream(outputFile)) { - input.transferTo(output); - } - if (sourceFilePolicy == TeaVMSourceFilePolicy.LINK_LOCAL_FILES) { - return "file://" + outputFile.getCanonicalPath(); - } - } else { - return "file://" + sourceFile.getFile().getCanonicalPath(); - } - break; - } - } - return null; - }); + sourceMapWriter.addSourceResolver(resolver); } sourceMapWriter.write(getResolvedTargetFileName(), "src", debugInfo); - for (var provider : sourceFileProviders) { - provider.close(); - } + resolver.close(); } private void printStats() { diff --git a/tools/core/src/main/java/org/teavm/tooling/sources/DefaultSourceFileResolver.java b/tools/core/src/main/java/org/teavm/tooling/sources/DefaultSourceFileResolver.java new file mode 100644 index 000000000..a8ac82d02 --- /dev/null +++ b/tools/core/src/main/java/org/teavm/tooling/sources/DefaultSourceFileResolver.java @@ -0,0 +1,74 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.tooling.sources; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.List; +import org.teavm.debugging.information.SourceFileResolver; +import org.teavm.tooling.TeaVMSourceFilePolicy; + +public class DefaultSourceFileResolver implements SourceFileResolver { + private File targetDir; + private List sourceFileProviders; + private TeaVMSourceFilePolicy sourceFilePolicy = TeaVMSourceFilePolicy.DO_NOTHING; + + public DefaultSourceFileResolver(File targetDir, List sourceFileProviders) { + this.targetDir = targetDir; + this.sourceFileProviders = sourceFileProviders; + } + + public void setSourceFilePolicy(TeaVMSourceFilePolicy sourceFilePolicy) { + this.sourceFilePolicy = sourceFilePolicy; + } + + public void open() throws IOException { + for (var provider : sourceFileProviders) { + provider.open(); + } + } + + @Override + public String resolveFile(String file) throws IOException { + for (var provider : sourceFileProviders) { + var sourceFile = provider.getSourceFile(file); + if (sourceFile != null) { + if (sourceFilePolicy == TeaVMSourceFilePolicy.COPY || sourceFile.getFile() == null) { + var outputFile = new File(targetDir, file); + outputFile.getParentFile().mkdirs(); + try (var input = sourceFile.open(); + var output = new FileOutputStream(outputFile)) { + input.transferTo(output); + } + if (sourceFilePolicy == TeaVMSourceFilePolicy.LINK_LOCAL_FILES) { + return "file://" + outputFile.getCanonicalPath(); + } + } else { + return "file://" + sourceFile.getFile().getCanonicalPath(); + } + break; + } + } + return null; + } + + public void close() throws IOException { + for (var provider : sourceFileProviders) { + provider.close(); + } + } +} diff --git a/tools/junit/src/main/java/org/teavm/junit/BaseWebAssemblyPlatformSupport.java b/tools/junit/src/main/java/org/teavm/junit/BaseWebAssemblyPlatformSupport.java index f2845feef..7106e5a21 100644 --- a/tools/junit/src/main/java/org/teavm/junit/BaseWebAssemblyPlatformSupport.java +++ b/tools/junit/src/main/java/org/teavm/junit/BaseWebAssemblyPlatformSupport.java @@ -56,7 +56,7 @@ abstract class BaseWebAssemblyPlatformSupport extends TestPlatformSupport(); for (var tokenizer = new StringTokenizer(sourceDirs, Character.toString(File.pathSeparatorChar)); - tokenizer.hasMoreTokens();) { + tokenizer.hasMoreTokens();) { var dir = new File(tokenizer.nextToken()); if (dir.isDirectory()) { dirs.add(dir); diff --git a/tools/junit/src/main/java/org/teavm/junit/TestPlatformSupport.java b/tools/junit/src/main/java/org/teavm/junit/TestPlatformSupport.java index f5d4ede14..688956e17 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TestPlatformSupport.java +++ b/tools/junit/src/main/java/org/teavm/junit/TestPlatformSupport.java @@ -124,16 +124,18 @@ abstract class TestPlatformSupport { } protected final File getOutputFile(File path, String baseName, String suffix, String extension) { + return new File(path, getOutputSimpleNameFile(baseName, suffix, extension)); + } + + + protected final String getOutputSimpleNameFile(String baseName, String suffix, String extension) { StringBuilder simpleName = new StringBuilder(); simpleName.append(baseName); if (!suffix.isEmpty()) { simpleName.append('-').append(suffix); } - File outputFile; simpleName.append(extension); - outputFile = new File(path, simpleName.toString()); - - return outputFile; + return simpleName.toString(); } private String buildErrorMessage(TeaVM vm) { diff --git a/tools/junit/src/main/java/org/teavm/junit/WebAssemblyGCPlatformSupport.java b/tools/junit/src/main/java/org/teavm/junit/WebAssemblyGCPlatformSupport.java index 273ba92c3..a83e29a11 100644 --- a/tools/junit/src/main/java/org/teavm/junit/WebAssemblyGCPlatformSupport.java +++ b/tools/junit/src/main/java/org/teavm/junit/WebAssemblyGCPlatformSupport.java @@ -15,6 +15,7 @@ */ package org.teavm.junit; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.teavm.junit.PropertyNames.OPTIMIZED; import static org.teavm.junit.PropertyNames.SOURCE_DIRS; import static org.teavm.junit.PropertyNames.WASM_GC_ENABLED; @@ -35,20 +36,39 @@ import java.util.function.Supplier; import org.teavm.backend.wasm.WasmDebugInfoLevel; import org.teavm.backend.wasm.WasmDebugInfoLocation; import org.teavm.backend.wasm.WasmGCTarget; +import org.teavm.backend.wasm.debug.sourcemap.SourceMapBuilder; import org.teavm.backend.wasm.disasm.Disassembler; import org.teavm.backend.wasm.disasm.DisassemblyHTMLWriter; import org.teavm.browserrunner.BrowserRunner; import org.teavm.model.ClassHolderSource; import org.teavm.model.MethodReference; import org.teavm.model.ReferenceCache; +import org.teavm.tooling.TeaVMSourceFilePolicy; +import org.teavm.tooling.sources.DefaultSourceFileResolver; +import org.teavm.tooling.sources.DirectorySourceFileProvider; +import org.teavm.tooling.sources.JarSourceFileProvider; +import org.teavm.tooling.sources.SourceFileProvider; import org.teavm.vm.TeaVM; class WebAssemblyGCPlatformSupport extends TestPlatformSupport { private boolean disassembly; + private List sourceFileProviders = new ArrayList<>(); WebAssemblyGCPlatformSupport(ClassHolderSource classSource, ReferenceCache referenceCache, boolean disassembly) { super(classSource, referenceCache); this.disassembly = disassembly; + var sourceDirs = System.getProperty(SOURCE_DIRS); + if (sourceDirs != null) { + for (var tokenizer = new StringTokenizer(sourceDirs, Character.toString(File.pathSeparatorChar)); + tokenizer.hasMoreTokens();) { + var file = new File(tokenizer.nextToken()); + if (file.isDirectory()) { + sourceFileProviders.add(new DirectorySourceFileProvider(file)); + } else if (file.isFile() && file.getName().endsWith(".jar")) { + sourceFileProviders.add(new JarSourceFileProvider(file)); + } + } + } } @Override @@ -67,6 +87,8 @@ class WebAssemblyGCPlatformSupport extends TestPlatformSupport { @Override CompileResult compile(Consumer additionalProcessing, String baseName, TeaVMTestConfiguration configuration, File path, AnnotatedElement element) { + var sourceMapBuilder = new SourceMapBuilder(); + var sourceMapFile = getOutputFile(path, baseName, configuration.getSuffix(), ".wasm.map"); Supplier targetSupplier = () -> { var target = new WasmGCTarget(); target.setObfuscated(false); @@ -74,6 +96,8 @@ class WebAssemblyGCPlatformSupport extends TestPlatformSupport { target.setDebugInfo(true); target.setDebugInfoLevel(WasmDebugInfoLevel.DEOBFUSCATION); target.setDebugInfoLocation(WasmDebugInfoLocation.EMBEDDED); + target.setSourceMapBuilder(sourceMapBuilder); + target.setSourceMapLocation(getOutputSimpleNameFile(baseName, configuration.getSuffix(), ".wasm.map")); var sourceDirs = System.getProperty(SOURCE_DIRS); if (sourceDirs != null) { var dirs = new ArrayList(); @@ -87,8 +111,22 @@ class WebAssemblyGCPlatformSupport extends TestPlatformSupport { } return target; }; + CompilePostProcessor postBuild = (vm, file) -> { + var resolver = new DefaultSourceFileResolver(new File(path, "src"), sourceFileProviders); + resolver.setSourceFilePolicy(TeaVMSourceFilePolicy.LINK_LOCAL_FILES); + sourceMapBuilder.addSourceResolver(resolver); + try { + resolver.open(); + try (var sourceMapOut = new OutputStreamWriter(new FileOutputStream(sourceMapFile), UTF_8)) { + sourceMapBuilder.writeSourceMap(sourceMapOut); + } + resolver.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }; return compile(configuration, targetSupplier, TestWasmGCEntryPoint.class.getName(), path, - ".wasm", null, additionalProcessing, baseName); + ".wasm", postBuild, additionalProcessing, baseName); } @Override