From c5011ebf6975ddabccc2d28a3b6dbdf63bfa90b7 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Mon, 14 Nov 2022 17:28:12 +0100 Subject: [PATCH] Wasm: initial skeleton implementation of DWARF generator --- .../org/teavm/backend/wasm/WasmTarget.java | 19 +- .../backend/wasm/dwarf/DwarfAbbreviation.java | 33 ++++ .../backend/wasm/dwarf/DwarfConstants.java | 30 +++ .../backend/wasm/dwarf/DwarfInfoWriter.java | 174 ++++++++++++++++++ .../backend/wasm/dwarf/DwarfPlaceholder.java | 47 +++++ .../wasm/dwarf/DwarfPlaceholderWriter.java | 22 +++ .../wasm/dwarf/blob/BinaryDataConsumer.java | 20 ++ .../teavm/backend/wasm/dwarf/blob/Blob.java | 158 ++++++++++++++++ .../backend/wasm/dwarf/blob/BlobReader.java | 61 ++++++ .../teavm/backend/wasm/dwarf/blob/Marker.java | 43 +++++ .../backend/wasm/generate/DwarfGenerator.java | 83 +++++++++ .../backend/wasm/model/WasmCustomSection.java | 35 ++++ .../teavm/backend/wasm/model/WasmModule.java | 26 +++ .../wasm/render/WasmBinaryRenderer.java | 24 +++ .../org/teavm/backend/wasm/wasm-runtime.js | 4 +- 15 files changed, 776 insertions(+), 3 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/wasm/dwarf/DwarfAbbreviation.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/dwarf/DwarfConstants.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/dwarf/DwarfInfoWriter.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/dwarf/DwarfPlaceholder.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/dwarf/DwarfPlaceholderWriter.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/dwarf/blob/BinaryDataConsumer.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/dwarf/blob/Blob.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/dwarf/blob/BlobReader.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/dwarf/blob/Marker.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/generate/DwarfGenerator.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/model/WasmCustomSection.java diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java index dfe21978f..dca54484d 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java @@ -31,6 +31,7 @@ import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Set; +import java.util.function.Supplier; import org.teavm.ast.InvocationExpr; import org.teavm.ast.decompilation.Decompiler; import org.teavm.backend.lowlevel.analyze.LowLevelInliningFilterFactory; @@ -39,6 +40,7 @@ import org.teavm.backend.lowlevel.generate.NameProvider; import org.teavm.backend.lowlevel.generate.NameProviderWithSpecialNames; import org.teavm.backend.lowlevel.transform.CoroutineTransformation; import org.teavm.backend.wasm.binary.BinaryWriter; +import org.teavm.backend.wasm.generate.DwarfGenerator; import org.teavm.backend.wasm.generate.WasmClassGenerator; import org.teavm.backend.wasm.generate.WasmDependencyListener; import org.teavm.backend.wasm.generate.WasmGenerationContext; @@ -76,6 +78,7 @@ import org.teavm.backend.wasm.intrinsics.WasmIntrinsicFactory; import org.teavm.backend.wasm.intrinsics.WasmIntrinsicFactoryContext; import org.teavm.backend.wasm.intrinsics.WasmIntrinsicManager; import org.teavm.backend.wasm.intrinsics.WasmRuntimeIntrinsic; +import org.teavm.backend.wasm.model.WasmCustomSection; import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmLocal; import org.teavm.backend.wasm.model.WasmMemorySegment; @@ -491,6 +494,10 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { classGenerator, stringPool, obfuscated); context.addIntrinsic(exceptionHandlingIntrinsic); + var dwarfGenerator = debugging ? new DwarfGenerator() : null; + if (dwarfGenerator != null) { + dwarfGenerator.begin(); + } var generator = new WasmGenerator(decompiler, classes, context, classGenerator, binaryWriter, asyncMethods::contains); @@ -539,7 +546,7 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { var writer = new WasmBinaryWriter(); var renderer = new WasmBinaryRenderer(writer, version, obfuscated); - renderer.render(module); + renderer.render(module, buildDwarf(dwarfGenerator)); try (OutputStream output = buildTarget.createResource(outputName)) { output.write(writer.getData()); @@ -558,6 +565,16 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { } } + private Supplier> buildDwarf(DwarfGenerator generator) { + if (generator == null) { + return null; + } + return () -> { + generator.end(); + return generator.createSections(); + }; + } + private WasmFunction createStartFunction(NameProvider names) { var function = new WasmFunction("teavm_start"); function.setExportName("start"); diff --git a/core/src/main/java/org/teavm/backend/wasm/dwarf/DwarfAbbreviation.java b/core/src/main/java/org/teavm/backend/wasm/dwarf/DwarfAbbreviation.java new file mode 100644 index 000000000..ce1c1a917 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/dwarf/DwarfAbbreviation.java @@ -0,0 +1,33 @@ +/* + * Copyright 2022 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.dwarf; + +import java.util.function.Consumer; +import org.teavm.backend.wasm.dwarf.blob.Blob; + +public class DwarfAbbreviation { + int tag; + boolean hasChildren; + Consumer writer; + int index; + int count; + + DwarfAbbreviation(int tag, boolean hasChildren, Consumer writer) { + this.tag = tag; + this.hasChildren = hasChildren; + this.writer = writer; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/dwarf/DwarfConstants.java b/core/src/main/java/org/teavm/backend/wasm/dwarf/DwarfConstants.java new file mode 100644 index 000000000..a9b006aa4 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/dwarf/DwarfConstants.java @@ -0,0 +1,30 @@ +/* + * Copyright 2022 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.dwarf; + +public final class DwarfConstants { + public static final int DWARF_VERSION = 5; + + public static final int DW_UT_COMPILE = 0x01; + + public static final int DW_TAG_COMPILE_UNIT = 0x11; + + public static final int DW_CHILDREN_YES = 1; + public static final int DW_CHILDREN_NO = 0; + + private DwarfConstants() { + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/dwarf/DwarfInfoWriter.java b/core/src/main/java/org/teavm/backend/wasm/dwarf/DwarfInfoWriter.java new file mode 100644 index 000000000..61038cdd2 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/dwarf/DwarfInfoWriter.java @@ -0,0 +1,174 @@ +/* + * Copyright 2022 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.dwarf; + +import static org.teavm.backend.wasm.dwarf.DwarfConstants.DW_CHILDREN_NO; +import static org.teavm.backend.wasm.dwarf.DwarfConstants.DW_CHILDREN_YES; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.function.Consumer; +import org.teavm.backend.wasm.dwarf.blob.BinaryDataConsumer; +import org.teavm.backend.wasm.dwarf.blob.Blob; +import org.teavm.backend.wasm.dwarf.blob.Marker; + +public class DwarfInfoWriter { + private Blob output = new Blob(); + private List abbreviations = new ArrayList<>(); + private List placements = new ArrayList<>(); + private Marker placeholderMarker; + + public DwarfInfoWriter write(byte[] data) { + output.write(data); + return this; + } + + public DwarfInfoWriter write(byte[] data, int offset, int limit) { + output.write(data, offset, limit); + return this; + } + + public DwarfInfoWriter writeInt(int value) { + output.writeInt(value); + return this; + } + + public DwarfInfoWriter writeShort(int value) { + output.writeShort(value); + return this; + } + + public DwarfInfoWriter writeByte(int value) { + output.write((byte) value); + return this; + } + + public DwarfInfoWriter writeLEB(int value) { + output.writeLEB(value); + return this; + } + + public DwarfAbbreviation abbreviation(int tag, boolean hasChildren, Consumer blob) { + var abbr = new DwarfAbbreviation(tag, hasChildren, blob); + abbreviations.add(abbr); + return abbr; + } + + public DwarfInfoWriter tag(DwarfAbbreviation abbreviation) { + placements.add(new Placement(output.ptr()) { + @Override + void write(Blob blob) { + blob.writeLEB(abbreviation.index); + } + }); + abbreviation.count++; + return this; + } + + public DwarfInfoWriter emptyTag() { + output.write((byte) 0); + return this; + } + + public DwarfPlaceholder placeholder(int size) { + return new DwarfPlaceholder(size); + } + + public DwarfInfoWriter ref(DwarfPlaceholder placeholder, DwarfPlaceholderWriter writer) { + placements.add(new Placement(output.ptr()) { + @Override + void write(Blob blob) { + if (placeholder.ptr >= 0) { + placeholderMarker.update(); + writer.write(blob, placeholder.ptr); + placeholderMarker.rewind(); + blob.skip(placeholder.size); + } else { + placeholder.addForwardRef(writer, blob.marker()); + blob.skip(placeholder.size); + } + } + }); + return this; + } + + public DwarfInfoWriter mark(DwarfPlaceholder placeholder) { + placements.add(new Placement(output.ptr()) { + @Override + void write(Blob blob) { + if (placeholder.ptr >= 0) { + throw new IllegalStateException(); + } + placeholder.ptr = blob.ptr(); + if (placeholder.forwardReferences != null) { + placeholderMarker.update(); + for (var forwardRef : placeholder.forwardReferences) { + forwardRef.marker.rewind(); + forwardRef.writer.write(blob, placeholder.ptr); + } + placeholder.forwardReferences = null; + placeholderMarker.rewind(); + } + } + }); + return this; + } + + public void buildAbbreviations(Blob target) { + var orderedAbbreviations = new ArrayList<>(abbreviations); + orderedAbbreviations.sort(Comparator.comparingInt(a -> -a.count)); + var sz = orderedAbbreviations.size(); + while (sz > 0 && orderedAbbreviations.get(sz - 1).count == 0) { + --sz; + } + for (var i = 0; i < sz; ++i) { + var abbrev = orderedAbbreviations.get(i); + abbrev.index = i + 1; + target.writeLEB(abbrev.index).writeLEB(abbrev.tag) + .writeByte(abbrev.hasChildren ? DW_CHILDREN_YES : DW_CHILDREN_NO); + abbrev.writer.accept(target); + target.writeByte(0).writeByte(0); + } + target.writeByte(0); + } + + public void build(Blob target) { + placeholderMarker = target.marker(); + this.targetBlob = target; + var reader = output.newReader(targetBlobWritingConsumer); + for (var placement : placements) { + reader.advance(placement.offset); + placement.write(target); + } + reader.advance(output.size()); + this.targetBlob = null; + placeholderMarker = null; + } + + private Blob targetBlob; + private BinaryDataConsumer targetBlobWritingConsumer = (data, offset, limit) -> + targetBlob.write(data, offset, limit); + + private static abstract class Placement { + int offset; + + Placement(int offset) { + this.offset = offset; + } + + abstract void write(Blob blob); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/dwarf/DwarfPlaceholder.java b/core/src/main/java/org/teavm/backend/wasm/dwarf/DwarfPlaceholder.java new file mode 100644 index 000000000..020fded80 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/dwarf/DwarfPlaceholder.java @@ -0,0 +1,47 @@ +/* + * Copyright 2022 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.dwarf; + +import java.util.ArrayList; +import java.util.List; +import org.teavm.backend.wasm.dwarf.blob.Marker; + +public class DwarfPlaceholder { + int ptr = -1; + final int size; + List forwardReferences; + + DwarfPlaceholder(int size) { + this.size = size; + } + + void addForwardRef(DwarfPlaceholderWriter writer, Marker marker) { + if (forwardReferences == null) { + forwardReferences = new ArrayList<>(); + forwardReferences.add(new ForwardRef(writer, marker)); + } + } + + static class ForwardRef { + final DwarfPlaceholderWriter writer; + final Marker marker; + + ForwardRef(DwarfPlaceholderWriter writer, Marker marker) { + this.writer = writer; + this.marker = marker; + } + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/dwarf/DwarfPlaceholderWriter.java b/core/src/main/java/org/teavm/backend/wasm/dwarf/DwarfPlaceholderWriter.java new file mode 100644 index 000000000..7099f3ae2 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/dwarf/DwarfPlaceholderWriter.java @@ -0,0 +1,22 @@ +/* + * Copyright 2022 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.dwarf; + +import org.teavm.backend.wasm.dwarf.blob.Blob; + +public interface DwarfPlaceholderWriter { + void write(Blob blob, int actualValue); +} diff --git a/core/src/main/java/org/teavm/backend/wasm/dwarf/blob/BinaryDataConsumer.java b/core/src/main/java/org/teavm/backend/wasm/dwarf/blob/BinaryDataConsumer.java new file mode 100644 index 000000000..7d61ad411 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/dwarf/blob/BinaryDataConsumer.java @@ -0,0 +1,20 @@ +/* + * Copyright 2022 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.dwarf.blob; + +public interface BinaryDataConsumer { + void accept(byte[] data, int offset, int limit); +} diff --git a/core/src/main/java/org/teavm/backend/wasm/dwarf/blob/Blob.java b/core/src/main/java/org/teavm/backend/wasm/dwarf/blob/Blob.java new file mode 100644 index 000000000..f20921c9a --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/dwarf/blob/Blob.java @@ -0,0 +1,158 @@ +/* + * Copyright 2022 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.dwarf.blob; + +import java.util.ArrayList; +import java.util.List; + +public class Blob { + private byte[] buffer = new byte[16]; + byte[] currentChunk = new byte[4096]; + List data = new ArrayList<>(); + int chunkIndex; + int posInChunk; + int ptr; + private int size; + + public Blob() { + data.add(currentChunk); + } + + public Blob write(byte[] bytes) { + return write(bytes, 0, bytes.length); + } + + public Blob write(byte[] bytes, int offset, int limit) { + if (offset == limit) { + return this; + } + if (offset + 1 == limit) { + write(bytes[offset]); + return this; + } + while (offset < limit) { + var remaining = Math.min(limit - offset, currentChunk.length - posInChunk); + System.arraycopy(bytes, offset, currentChunk, posInChunk, remaining); + posInChunk += remaining; + offset += remaining; + ptr += remaining; + nextChunkIfNeeded(); + } + size = Math.max(size, ptr); + return this; + } + + public Blob skip(int count) { + while (count > 0) { + var remaining = Math.min(count, currentChunk.length - posInChunk); + posInChunk += remaining; + ptr += remaining; + count -= remaining; + nextChunkIfNeeded(); + } + size = Math.max(size, ptr); + return this; + } + + public Blob write(byte b) { + currentChunk[posInChunk++] = b; + ptr++; + nextChunkIfNeeded(); + size = Math.max(size, ptr); + return this; + } + + public Blob writeInt(int value) { + var buffer = this.buffer; + buffer[0] = (byte) value; + buffer[1] = (byte) (value >>> 8); + buffer[2] = (byte) (value >>> 16); + buffer[3] = (byte) (value >>> 24); + return write(buffer, 0, 4); + } + + public Blob writeShort(int value) { + var buffer = this.buffer; + buffer[0] = (byte) value; + buffer[1] = (byte) (value >>> 8); + return write(buffer, 0, 2); + } + + public Blob writeByte(int value) { + return write((byte) value); + } + + public Blob writeLEB(int value) { + var ptr = 0; + var buffer = this.buffer; + while ((value & 0x7F) != value) { + buffer[ptr++] = (byte) ((value & 0x7F) | 0x80); + value >>= 7; + } + buffer[ptr++] = (byte) (value & 0x7F); + return write(buffer, 0, ptr); + } + + private void nextChunkIfNeeded() { + if (posInChunk < currentChunk.length) { + return; + } + posInChunk = 0; + if (++chunkIndex >= data.size()) { + currentChunk = new byte[currentChunk.length]; + data.add(currentChunk); + } else { + currentChunk = data.get(chunkIndex); + } + } + + public int chunkCount() { + return data.size() + 1; + } + + public BlobReader newReader(BinaryDataConsumer consumer) { + return new BlobReader(this, consumer); + } + + public Marker marker() { + return new Marker(this, chunkIndex, posInChunk, ptr); + } + + public byte[] chunkAt(int index) { + return index < data.size() ? data.get(index) : currentChunk; + } + + public int ptr() { + return ptr; + } + + public int size() { + return size; + } + + public byte[] toArray() { + var result = new byte[size]; + var ptr = 0; + for (var chunk : data) { + int bytesToCopy = Math.min(chunk.length, size - ptr); + if (bytesToCopy > 0) { + System.arraycopy(chunk, 0, result, ptr, bytesToCopy); + ptr += bytesToCopy; + } + } + return result; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/dwarf/blob/BlobReader.java b/core/src/main/java/org/teavm/backend/wasm/dwarf/blob/BlobReader.java new file mode 100644 index 000000000..7ce3d255c --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/dwarf/blob/BlobReader.java @@ -0,0 +1,61 @@ +/* + * Copyright 2022 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.dwarf.blob; + +public class BlobReader { + private Blob output; + private BinaryDataConsumer consumer; + private int ptr; + private int currentChunk; + private int offsetInChunk; + + BlobReader(Blob output, BinaryDataConsumer consumer) { + this.output = output; + this.consumer = consumer; + } + + public int position() { + return ptr; + } + + public void advance(int to) { + if (to < ptr || to > output.size()) { + throw new IllegalArgumentException(); + } + if (to == ptr) { + return; + } + + var ptr = this.ptr; + var currentChunk = this.currentChunk; + var offsetInChunk = this.offsetInChunk; + while (ptr < to) { + var chunk = output.chunkAt(currentChunk); + var limit = Math.min(ptr + chunk.length, to); + var bytesToWrite = limit - ptr; + consumer.accept(chunk, offsetInChunk, offsetInChunk + bytesToWrite); + offsetInChunk += bytesToWrite; + ptr += bytesToWrite; + if (offsetInChunk == chunk.length) { + offsetInChunk = 0; + currentChunk++; + } + } + this.ptr = ptr; + this.currentChunk = currentChunk; + this.offsetInChunk = offsetInChunk; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/dwarf/blob/Marker.java b/core/src/main/java/org/teavm/backend/wasm/dwarf/blob/Marker.java new file mode 100644 index 000000000..1e331eef7 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/dwarf/blob/Marker.java @@ -0,0 +1,43 @@ +/* + * Copyright 2022 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.dwarf.blob; + +public class Marker { + private Blob blob; + private int chunkIndex; + private int posInChunk; + private int ptr; + + Marker(Blob blob, int chunkIndex, int posInChunk, int ptr) { + this.blob = blob; + this.chunkIndex = chunkIndex; + this.posInChunk = posInChunk; + this.ptr = ptr; + } + + public void rewind() { + blob.chunkIndex = chunkIndex; + blob.posInChunk = posInChunk; + blob.ptr = ptr; + blob.currentChunk = blob.data.get(chunkIndex); + } + + public void update() { + chunkIndex = blob.chunkIndex; + posInChunk = blob.posInChunk; + ptr = blob.ptr; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/DwarfGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/DwarfGenerator.java new file mode 100644 index 000000000..ed6acf86a --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/generate/DwarfGenerator.java @@ -0,0 +1,83 @@ +/* + * Copyright 2022 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.generate; + +import static org.teavm.backend.wasm.dwarf.DwarfConstants.DWARF_VERSION; +import static org.teavm.backend.wasm.dwarf.DwarfConstants.DW_TAG_COMPILE_UNIT; +import static org.teavm.backend.wasm.dwarf.DwarfConstants.DW_UT_COMPILE; +import java.util.Arrays; +import java.util.Collection; +import org.teavm.backend.wasm.dwarf.DwarfInfoWriter; +import org.teavm.backend.wasm.dwarf.DwarfPlaceholder; +import org.teavm.backend.wasm.dwarf.blob.Blob; +import org.teavm.backend.wasm.model.WasmCustomSection; + +public class DwarfGenerator { + private DwarfInfoWriter infoWriter = new DwarfInfoWriter(); + private DwarfPlaceholder endOfSection; + + public void begin() { + endOfSection = infoWriter.placeholder(4); + emitUnitHeader(); + compilationUnit(); + } + + private void emitUnitHeader() { + // unit_length + infoWriter.ref(endOfSection, (blob, ptr) -> { + int size = ptr - blob.ptr() - 4; + blob.writeInt(size); + }); + + // version + infoWriter.writeShort(DWARF_VERSION); + + // unit_type + infoWriter.writeByte(DW_UT_COMPILE); + + // address_size + infoWriter.writeByte(4); + + // debug_abbrev_offset + infoWriter.writeInt(0); + } + + private void compilationUnit() { + infoWriter.tag(infoWriter.abbreviation(DW_TAG_COMPILE_UNIT, true, data -> { })); + } + + public void end() { + closeTag(); // compilation unit + infoWriter.mark(endOfSection); + } + + private void closeTag() { + infoWriter.writeByte(0); + } + + public Collection createSections() { + var abbreviations = new Blob(); + infoWriter.buildAbbreviations(abbreviations); + + var info = new Blob(); + infoWriter.build(info); + + return Arrays.asList( + new WasmCustomSection(".debug_abbrev", abbreviations.toArray()), + new WasmCustomSection(".debug_info", info.toArray()) + ); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/model/WasmCustomSection.java b/core/src/main/java/org/teavm/backend/wasm/model/WasmCustomSection.java new file mode 100644 index 000000000..a278b45f5 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/model/WasmCustomSection.java @@ -0,0 +1,35 @@ +/* + * Copyright 2022 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.model; + +public class WasmCustomSection { + private String name; + private byte[] data; + WasmModule module; + + public WasmCustomSection(String name, byte[] data) { + this.name = name; + this.data = data; + } + + public String getName() { + return name; + } + + public byte[] getData() { + return data; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/model/WasmModule.java b/core/src/main/java/org/teavm/backend/wasm/model/WasmModule.java index 727672092..1a80b814d 100644 --- a/core/src/main/java/org/teavm/backend/wasm/model/WasmModule.java +++ b/core/src/main/java/org/teavm/backend/wasm/model/WasmModule.java @@ -29,6 +29,8 @@ public class WasmModule { private Map readonlyFunctions = Collections.unmodifiableMap(functions); private List functionTable = new ArrayList<>(); private WasmFunction startFunction; + private Map customSections = new LinkedHashMap<>(); + private Map readonlyCustomSections = Collections.unmodifiableMap(customSections); public void add(WasmFunction function) { if (functions.containsKey(function.getName())) { @@ -53,6 +55,30 @@ public class WasmModule { return readonlyFunctions; } + public void add(WasmCustomSection customSection) { + if (customSections.containsKey(customSection.getName())) { + throw new IllegalArgumentException("Custom section " + customSection.getName() + + " already defined in this module"); + } + if (customSection.module != null) { + throw new IllegalArgumentException("Given custom section is already registered in another module"); + } + customSections.put(customSection.getName(), customSection); + customSection.module = this; + } + + public void remove(WasmCustomSection customSection) { + if (customSection.module != this) { + return; + } + customSection.module = null; + customSections.remove(customSection.getName()); + } + + public Map getCustomSections() { + return readonlyCustomSections; + } + public List getFunctionTable() { return functionTable; } diff --git a/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java index 42293ce9c..67581660d 100644 --- a/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java @@ -17,10 +17,13 @@ package org.teavm.backend.wasm.render; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Supplier; import java.util.stream.Collectors; +import org.teavm.backend.wasm.model.WasmCustomSection; import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmLocal; import org.teavm.backend.wasm.model.WasmMemorySegment; @@ -58,6 +61,10 @@ public class WasmBinaryRenderer { } public void render(WasmModule module) { + render(module, Collections::emptyList); + } + + public void render(WasmModule module, Supplier> customSectionSupplier) { output.writeInt32(0x6d736100); switch (version) { case V_0x1: @@ -78,6 +85,7 @@ public class WasmBinaryRenderer { if (!obfuscated) { renderNames(module); } + renderCustomSections(module, customSectionSupplier); } private void renderSignatures(WasmModule module) { @@ -354,6 +362,22 @@ public class WasmBinaryRenderer { writeSection(SECTION_UNKNOWN, "name", section.getData()); } + private void renderCustomSections(WasmModule module, + Supplier> sectionSupplier) { + for (var customSection : module.getCustomSections().values()) { + renderCustomSection(customSection); + } + if (sectionSupplier != null) { + for (var customSection : sectionSupplier.get()) { + renderCustomSection(customSection); + } + } + } + + private void renderCustomSection(WasmCustomSection customSection) { + writeSection(SECTION_UNKNOWN, customSection.getName(), customSection.getData()); + } + static class LocalEntry { WasmType type; int count = 1; diff --git a/core/src/main/resources/org/teavm/backend/wasm/wasm-runtime.js b/core/src/main/resources/org/teavm/backend/wasm/wasm-runtime.js index f6b938ba2..699308ac1 100644 --- a/core/src/main/resources/org/teavm/backend/wasm/wasm-runtime.js +++ b/core/src/main/resources/org/teavm/backend/wasm/wasm-runtime.js @@ -31,9 +31,9 @@ TeaVM.wasm = function() { lineBuffer += String.fromCharCode(charCode); } } - function putwchars(buffer, count) { + function putwchars(controller, buffer, count) { let instance = controller.instance; - let memory = instance.exports.memory.buffer; + let memory = new Int8Array(instance.exports.memory.buffer); for (let i = 0; i < count; ++i) { // TODO: support UTF-8 putwchar(memory[buffer++]);