Wasm: generate DWARF line numbers

Despite generated DWARF sections pass different verifications, they don't work in chrome or wasmtime.
This commit is contained in:
Alexey Andreev 2022-11-19 11:52:43 +01:00
parent c5011ebf69
commit f06ba832d7
11 changed files with 425 additions and 24 deletions

View File

@ -545,7 +545,7 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost {
} }
var writer = new WasmBinaryWriter(); var writer = new WasmBinaryWriter();
var renderer = new WasmBinaryRenderer(writer, version, obfuscated); var renderer = new WasmBinaryRenderer(writer, version, obfuscated, dwarfGenerator);
renderer.render(module, buildDwarf(dwarfGenerator)); renderer.render(module, buildDwarf(dwarfGenerator));
try (OutputStream output = buildTarget.createResource(outputName)) { try (OutputStream output = buildTarget.createResource(outputName)) {

View File

@ -22,9 +22,38 @@ public final class DwarfConstants {
public static final int DW_TAG_COMPILE_UNIT = 0x11; public static final int DW_TAG_COMPILE_UNIT = 0x11;
public static final int DW_AT_NAME = 0x03;
public static final int DW_AT_STMT_LIST = 0x10;
public static final int DW_AT_LOW_PC = 0x11;
public static final int DW_AT_HIGH_PC = 0x12;
public static final int DW_AT_PRODUCER = 0x25;
public static final int DW_CHILDREN_YES = 1; public static final int DW_CHILDREN_YES = 1;
public static final int DW_CHILDREN_NO = 0; public static final int DW_CHILDREN_NO = 0;
public static final int DW_LNCT_PATH = 0x1;
public static final int DW_LNCT_DIRECTORY_INDEX = 0x2;
public static final int DW_FORM_ADDR = 0x01;
public static final int DW_FORM_DATA2 = 0x05;
public static final int DW_FORM_DATA4 = 0x06;
public static final int DW_FORM_STRP = 0x0E;
public static final int DW_FORM_SEC_OFFSET = 0x17;
public static final int DW_FORM_LINE_STRP = 0x1F;
public static final int DW_LNS_COPY = 0x01;
public static final int DW_LNS_ADVANCE_PC = 0x02;
public static final int DW_LNS_ADVANCE_LINE = 0x03;
public static final int DW_LNS_SET_FILE = 0x04;
public static final int DW_LNS_SET_COLUMN = 0x05;
public static final int DW_LNS_NEGATE_STMT = 0x06;
public static final int DW_LNS_SET_BASIC_BLOCK = 0x07;
public static final int DW_LNS_CONST_ADD_PC = 0x08;
public static final int DW_LNS_FIXED_ADVANCE_PC = 0x09;
public static final int DW_LNS_SET_PROLOGUE_END = 0x0A;
public static final int DW_LNS_SET_EPILOGUE_BEGIN = 0x0B;
public static final int DW_LNS_SET_ISA = 0x0C;
private DwarfConstants() { private DwarfConstants() {
} }
} }

View File

@ -61,6 +61,15 @@ public class DwarfInfoWriter {
return this; return this;
} }
public DwarfInfoWriter skip(int count) {
output.skip(count);
return this;
}
public Marker marker() {
return output.marker();
}
public DwarfAbbreviation abbreviation(int tag, boolean hasChildren, Consumer<Blob> blob) { public DwarfAbbreviation abbreviation(int tag, boolean hasChildren, Consumer<Blob> blob) {
var abbr = new DwarfAbbreviation(tag, hasChildren, blob); var abbr = new DwarfAbbreviation(tag, hasChildren, blob);
abbreviations.add(abbr); abbreviations.add(abbr);

View File

@ -100,12 +100,30 @@ public class Blob {
var buffer = this.buffer; var buffer = this.buffer;
while ((value & 0x7F) != value) { while ((value & 0x7F) != value) {
buffer[ptr++] = (byte) ((value & 0x7F) | 0x80); buffer[ptr++] = (byte) ((value & 0x7F) | 0x80);
value >>= 7; value >>>= 7;
} }
buffer[ptr++] = (byte) (value & 0x7F); buffer[ptr++] = (byte) (value & 0x7F);
return write(buffer, 0, ptr); return write(buffer, 0, ptr);
} }
public Blob writeSLEB(int value) {
var ptr = 0;
var buffer = this.buffer;
var sign = value >>> 31;
while (true) {
var digit = value & 0x7F;
value >>= 7;
var more = value != 0 && value != -1 || digit >> 6 != sign;
if (more) {
buffer[ptr++] = (byte) (digit | 0x80);
} else {
buffer[ptr++] = (byte) digit;
break;
}
}
return write(buffer, 0, ptr);
}
private void nextChunkIfNeeded() { private void nextChunkIfNeeded() {
if (posInChunk < currentChunk.length) { if (posInChunk < currentChunk.length) {
return; return;
@ -127,6 +145,10 @@ public class Blob {
return new BlobReader(this, consumer); return new BlobReader(this, consumer);
} }
public BinaryDataConsumer writer() {
return this::write;
}
public Marker marker() { public Marker marker() {
return new Marker(this, chunkIndex, posInChunk, ptr); return new Marker(this, chunkIndex, posInChunk, ptr);
} }

View File

@ -16,14 +16,14 @@
package org.teavm.backend.wasm.dwarf.blob; package org.teavm.backend.wasm.dwarf.blob;
public class BlobReader { public class BlobReader {
private Blob output; private Blob blob;
private BinaryDataConsumer consumer; private BinaryDataConsumer consumer;
private int ptr; private int ptr;
private int currentChunk; private int currentChunk;
private int offsetInChunk; private int offsetInChunk;
BlobReader(Blob output, BinaryDataConsumer consumer) { BlobReader(Blob blob, BinaryDataConsumer consumer) {
this.output = output; this.blob = blob;
this.consumer = consumer; this.consumer = consumer;
} }
@ -31,8 +31,12 @@ public class BlobReader {
return ptr; return ptr;
} }
public void readRemaining() {
advance(blob.size());
}
public void advance(int to) { public void advance(int to) {
if (to < ptr || to > output.size()) { if (to < ptr || to > blob.size()) {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
if (to == ptr) { if (to == ptr) {
@ -43,7 +47,7 @@ public class BlobReader {
var currentChunk = this.currentChunk; var currentChunk = this.currentChunk;
var offsetInChunk = this.offsetInChunk; var offsetInChunk = this.offsetInChunk;
while (ptr < to) { while (ptr < to) {
var chunk = output.chunkAt(currentChunk); var chunk = blob.chunkAt(currentChunk);
var limit = Math.min(ptr + chunk.length, to); var limit = Math.min(ptr + chunk.length, to);
var bytesToWrite = limit - ptr; var bytesToWrite = limit - ptr;
consumer.accept(chunk, offsetInChunk, offsetInChunk + bytesToWrite); consumer.accept(chunk, offsetInChunk, offsetInChunk + bytesToWrite);

View File

@ -40,4 +40,8 @@ public class Marker {
posInChunk = blob.posInChunk; posInChunk = blob.posInChunk;
ptr = blob.ptr; ptr = blob.ptr;
} }
public int ptr() {
return ptr;
}
} }

View File

@ -18,19 +18,26 @@ package org.teavm.backend.wasm.generate;
import static org.teavm.backend.wasm.dwarf.DwarfConstants.DWARF_VERSION; 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_TAG_COMPILE_UNIT;
import static org.teavm.backend.wasm.dwarf.DwarfConstants.DW_UT_COMPILE; import static org.teavm.backend.wasm.dwarf.DwarfConstants.DW_UT_COMPILE;
import java.util.Arrays; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import org.teavm.backend.wasm.dwarf.DwarfConstants;
import org.teavm.backend.wasm.dwarf.DwarfInfoWriter; import org.teavm.backend.wasm.dwarf.DwarfInfoWriter;
import org.teavm.backend.wasm.dwarf.DwarfPlaceholder; import org.teavm.backend.wasm.dwarf.DwarfPlaceholder;
import org.teavm.backend.wasm.dwarf.blob.Blob; import org.teavm.backend.wasm.dwarf.blob.Blob;
import org.teavm.backend.wasm.dwarf.blob.Marker;
import org.teavm.backend.wasm.model.WasmCustomSection; import org.teavm.backend.wasm.model.WasmCustomSection;
public class DwarfGenerator { public class DwarfGenerator {
private DwarfInfoWriter infoWriter = new DwarfInfoWriter(); private DwarfInfoWriter infoWriter = new DwarfInfoWriter();
private DwarfPlaceholder endOfSection; private DwarfPlaceholder endOfSection;
private DwarfStrings strings = new DwarfStrings();
private DwarfStrings lineStrings = new DwarfStrings();
private DwarfLinesGenerator lines = new DwarfLinesGenerator(lineStrings);
private Marker highPcMarker;
public void begin() { public void begin() {
endOfSection = infoWriter.placeholder(4); endOfSection = infoWriter.placeholder(4);
lines.begin();
emitUnitHeader(); emitUnitHeader();
compilationUnit(); compilationUnit();
} }
@ -56,28 +63,60 @@ public class DwarfGenerator {
} }
private void compilationUnit() { private void compilationUnit() {
infoWriter.tag(infoWriter.abbreviation(DW_TAG_COMPILE_UNIT, true, data -> { })); infoWriter.tag(infoWriter.abbreviation(DW_TAG_COMPILE_UNIT, true, data -> {
data.writeLEB(DwarfConstants.DW_AT_PRODUCER).writeLEB(DwarfConstants.DW_FORM_STRP);
data.writeLEB(DwarfConstants.DW_AT_NAME).writeLEB(DwarfConstants.DW_FORM_STRP);
data.writeLEB(DwarfConstants.DW_AT_STMT_LIST).writeLEB(DwarfConstants.DW_FORM_SEC_OFFSET);
data.writeLEB(DwarfConstants.DW_AT_LOW_PC).writeLEB(DwarfConstants.DW_FORM_ADDR);
data.writeLEB(DwarfConstants.DW_AT_HIGH_PC).writeLEB(DwarfConstants.DW_FORM_ADDR);
}));
infoWriter.writeInt(strings.stringRef("TeaVM"));
infoWriter.writeInt(strings.stringRef("classes.wasm"));
infoWriter.writeInt(0);
infoWriter.writeInt(0);
highPcMarker = infoWriter.marker();
infoWriter.skip(4);
} }
public void end() { public void end() {
closeTag(); // compilation unit closeTag(); // compilation unit
infoWriter.mark(endOfSection); infoWriter.mark(endOfSection);
lines.end();
} }
private void closeTag() { private void closeTag() {
infoWriter.writeByte(0); infoWriter.writeByte(0);
} }
public void setCodeSize(int codeSize) {
highPcMarker.rewind();
infoWriter.writeInt(codeSize);
}
public Collection<? extends WasmCustomSection> createSections() { public Collection<? extends WasmCustomSection> createSections() {
var sections = new ArrayList<WasmCustomSection>();
var abbreviations = new Blob(); var abbreviations = new Blob();
infoWriter.buildAbbreviations(abbreviations); infoWriter.buildAbbreviations(abbreviations);
sections.add(new WasmCustomSection(".debug_abbrev", abbreviations.toArray()));
var info = new Blob(); var info = new Blob();
infoWriter.build(info); infoWriter.build(info);
sections.add(new WasmCustomSection(".debug_info", info.toArray()));
return Arrays.asList( if (strings.blob.size() > 0) {
new WasmCustomSection(".debug_abbrev", abbreviations.toArray()), sections.add(new WasmCustomSection(".debug_str", strings.blob.toArray()));
new WasmCustomSection(".debug_info", info.toArray()) }
); if (lineStrings.blob.size() > 0) {
sections.add(new WasmCustomSection(".debug_line_str", lineStrings.blob.toArray()));
}
sections.add(new WasmCustomSection(".debug_line", lines.blob.toArray()));
return sections;
}
public void lineNumber(int address, String fileName, int lineNumber) {
lines.lineNumber(address, fileName, lineNumber);
} }
} }

View File

@ -0,0 +1,207 @@
/*
* 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.DW_FORM_DATA2;
import static org.teavm.backend.wasm.dwarf.DwarfConstants.DW_FORM_LINE_STRP;
import static org.teavm.backend.wasm.dwarf.DwarfConstants.DW_LNCT_DIRECTORY_INDEX;
import static org.teavm.backend.wasm.dwarf.DwarfConstants.DW_LNCT_PATH;
import static org.teavm.backend.wasm.dwarf.DwarfConstants.DW_LNS_ADVANCE_LINE;
import static org.teavm.backend.wasm.dwarf.DwarfConstants.DW_LNS_ADVANCE_PC;
import static org.teavm.backend.wasm.dwarf.DwarfConstants.DW_LNS_COPY;
import static org.teavm.backend.wasm.dwarf.DwarfConstants.DW_LNS_SET_FILE;
import com.carrotsearch.hppc.ObjectIntHashMap;
import com.carrotsearch.hppc.ObjectIntMap;
import org.teavm.backend.wasm.dwarf.blob.Blob;
import org.teavm.backend.wasm.dwarf.blob.Marker;
class DwarfLinesGenerator {
private static final int MIN_INSN_LEN = 1;
private static final int LINE_BASE = -3;
private static final int LINE_RANGE = 8;
private static final int OPCODE_BASE = 13;
Blob blob = new Blob();
private DwarfStrings strings;
private Blob instructionsBlob = new Blob();
private Marker unitLengthMarker;
private Marker headerLengthMarker;
private Blob filesBlob = new Blob();
private ObjectIntMap<String> fileIndexes = new ObjectIntHashMap<>();
private Blob dirsBlob = new Blob();
private ObjectIntMap<String> dirIndexes = new ObjectIntHashMap<>();
private int address;
private int file = 1;
private int line = 1;
DwarfLinesGenerator(DwarfStrings strings) {
this.strings = strings;
}
void begin() {
emitLinesHeader();
}
private void emitLinesHeader() {
// length
unitLengthMarker = blob.marker();
blob.skip(4);
// version
blob.writeShort(5);
// address_size
blob.writeByte(4);
// segment_selector_size
blob.writeByte(0);
// header_length
headerLengthMarker = blob.marker();
blob.skip(4);
// minimum_instruction_length
blob.writeByte(MIN_INSN_LEN);
// maximum_operations_per_instruction
blob.writeByte(1);
// default_is_stmt
blob.writeByte(1);
blob.writeByte(LINE_BASE).writeByte(LINE_RANGE).writeByte(OPCODE_BASE);
// standard_opcode_lengths
blob.writeByte(0); // DW_LNS_COPY
blob.writeByte(1); // DW_LNS_ADVANCE_PC
blob.writeByte(1); // DW_LNS_ADVANCE_LINE
blob.writeByte(1); // DW_LNS_SET_FILE
blob.writeByte(1); // DW_LNS_SET_COLUMN
blob.writeByte(0); // DW_LNS_NEGATE_STMT
blob.writeByte(0); // DW_LNS_SET_BASIC_BLOCK
blob.writeByte(0); // DW_LNS_CONST_ADD_PC
blob.writeByte(1); // DW_LNS_FIXED_ADVANCE_PC
blob.writeByte(0); // DW_LNS_SET_PROLOGUE_END
blob.writeByte(0); // DW_LNS_SET_EPILOGUE_BEGIN
blob.writeByte(1); // DW_LNS_SET_ISA
}
void end() {
emitDirsAndFiles();
finishHeader();
instructionsBlob.newReader(blob.writer()).readRemaining();
var length = blob.ptr() - unitLengthMarker.ptr() - 4;
unitLengthMarker.rewind();
blob.writeInt(length);
}
private void emitDirsAndFiles() {
blob.writeByte(1); // directory_entry_format_count
blob.writeByte(DW_LNCT_PATH);
blob.writeByte(DW_FORM_LINE_STRP);
blob.writeLEB(dirIndexes.size());
dirsBlob.newReader(blob.writer()).readRemaining();
blob.writeByte(2); // file_name_entry_format_count
blob.writeByte(DW_LNCT_DIRECTORY_INDEX);
blob.writeByte(DW_FORM_DATA2);
blob.writeByte(DW_LNCT_PATH);
blob.writeByte(DW_FORM_LINE_STRP);
blob.writeLEB(fileIndexes.size());
filesBlob.newReader(blob.writer()).readRemaining();
}
private void finishHeader() {
var marker = blob.marker();
var headerLength = blob.ptr() - headerLengthMarker.ptr() - 4;
headerLengthMarker.rewind();
blob.writeInt(headerLength);
marker.rewind();
}
private int fileRef(String path) {
var ref = fileIndexes.getOrDefault(path, -1);
if (ref < 0) {
var nameIndex = path.lastIndexOf('/') + 1;
var name = path.substring(nameIndex);
var dir = path.substring(0, Math.max(0, nameIndex - 1));
var dirPtr = dirRef(dir);
ref = fileIndexes.size();
fileIndexes.put(path, ref);
filesBlob.writeShort(dirPtr);
filesBlob.writeInt(strings.stringRef(name));
}
return ref;
}
private int dirRef(String path) {
var ref = dirIndexes.getOrDefault(path, -1);
if (ref < 0) {
ref = dirIndexes.size();
dirIndexes.put(path, ref);
dirsBlob.writeInt(strings.stringRef(path));
}
return ref;
}
void lineNumber(int address, String file, int line) {
var changed = false;
var fileRef = fileRef(file);
if (fileRef != this.file) {
this.file = fileRef;
instructionsBlob.writeByte(DW_LNS_SET_FILE);
instructionsBlob.writeLEB(fileRef);
changed = true;
}
if (address != this.address || line != this.line) {
int lineIncrement = line - this.line;
int addressIncrement = address - this.address;
if (!tryEmitSpecial(lineIncrement, addressIncrement)) {
if (lineIncrement != 0) {
instructionsBlob.writeByte(DW_LNS_ADVANCE_LINE);
instructionsBlob.writeSLEB(lineIncrement);
}
if (addressIncrement != 0) {
instructionsBlob.writeByte(DW_LNS_ADVANCE_PC);
instructionsBlob.writeSLEB(addressIncrement);
}
}
changed = true;
this.line = line;
this.address = address;
}
if (changed) {
instructionsBlob.writeByte(DW_LNS_COPY);
}
}
private boolean tryEmitSpecial(int lineIncrement, int addressIncrement) {
if (lineIncrement < LINE_BASE || lineIncrement >= LINE_BASE + LINE_RANGE) {
return false;
}
int opcode = lineIncrement - LINE_BASE + (LINE_RANGE * addressIncrement) + OPCODE_BASE;
if (opcode <= OPCODE_BASE || opcode > 255) {
return false;
}
instructionsBlob.writeByte(opcode);
return true;
}
}

View File

@ -0,0 +1,38 @@
/*
* 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 com.carrotsearch.hppc.ObjectIntHashMap;
import com.carrotsearch.hppc.ObjectIntMap;
import java.nio.charset.StandardCharsets;
import org.teavm.backend.wasm.dwarf.blob.Blob;
class DwarfStrings {
final Blob blob = new Blob();
private ObjectIntMap<String> offsets = new ObjectIntHashMap<>();
int stringRef(String s) {
int ptr = offsets.getOrDefault(s, -1);
if (ptr < 0) {
ptr = blob.size();
offsets.put(s, ptr);
var bytes = s.getBytes(StandardCharsets.UTF_8);
blob.write(bytes);
blob.writeByte(0);
}
return ptr;
}
}

View File

@ -23,6 +23,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.teavm.backend.wasm.generate.DwarfGenerator;
import org.teavm.backend.wasm.model.WasmCustomSection; import org.teavm.backend.wasm.model.WasmCustomSection;
import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmFunction;
import org.teavm.backend.wasm.model.WasmLocal; import org.teavm.backend.wasm.model.WasmLocal;
@ -53,11 +54,14 @@ public class WasmBinaryRenderer {
private Map<WasmSignature, Integer> signatureIndexes = new HashMap<>(); private Map<WasmSignature, Integer> signatureIndexes = new HashMap<>();
private Map<String, Integer> functionIndexes = new HashMap<>(); private Map<String, Integer> functionIndexes = new HashMap<>();
private boolean obfuscated; private boolean obfuscated;
private DwarfGenerator dwarfGenerator;
public WasmBinaryRenderer(WasmBinaryWriter output, WasmBinaryVersion version, boolean obfuscated) { public WasmBinaryRenderer(WasmBinaryWriter output, WasmBinaryVersion version, boolean obfuscated,
DwarfGenerator dwarfGenerator) {
this.output = output; this.output = output;
this.version = version; this.version = version;
this.obfuscated = obfuscated; this.obfuscated = obfuscated;
this.dwarfGenerator = dwarfGenerator;
} }
public void render(WasmModule module) { public void render(WasmModule module) {
@ -256,24 +260,28 @@ public class WasmBinaryRenderer {
} }
private void renderCode(WasmModule module) { private void renderCode(WasmModule module) {
WasmBinaryWriter section = new WasmBinaryWriter(); var section = new WasmBinaryWriter();
List<WasmFunction> functions = module.getFunctions().values().stream() var functions = module.getFunctions().values().stream()
.filter(function -> function.getImportName() == null) .filter(function -> function.getImportName() == null)
.collect(Collectors.toList()); .collect(Collectors.toList());
section.writeLEB(functions.size()); section.writeLEB(functions.size());
for (WasmFunction function : functions) { for (var function : functions) {
byte[] body = renderFunction(function); var body = renderFunction(function, section.getPosition());
section.writeLEB(body.length); section.writeLEB(body.length);
section.writeBytes(body); section.writeBytes(body);
} }
if (dwarfGenerator != null) {
dwarfGenerator.setCodeSize(section.getPosition());
}
writeSection(SECTION_CODE, "code", section.getData()); writeSection(SECTION_CODE, "code", section.getData());
} }
private byte[] renderFunction(WasmFunction function) { private byte[] renderFunction(WasmFunction function, int offset) {
WasmBinaryWriter code = new WasmBinaryWriter(); var code = new WasmBinaryWriter();
List<WasmLocal> localVariables = function.getLocalVariables(); List<WasmLocal> localVariables = function.getLocalVariables();
int parameterCount = Math.min(function.getParameters().size(), localVariables.size()); int parameterCount = Math.min(function.getParameters().size(), localVariables.size());
@ -301,9 +309,9 @@ public class WasmBinaryRenderer {
} }
} }
Map<String, Integer> importIndexes = this.functionIndexes; var importIndexes = this.functionIndexes;
WasmBinaryRenderingVisitor visitor = new WasmBinaryRenderingVisitor(code, version, functionIndexes, var visitor = new WasmBinaryRenderingVisitor(code, version, functionIndexes, importIndexes,
importIndexes, signatureIndexes); signatureIndexes, dwarfGenerator, offset);
for (WasmExpression part : function.getBody()) { for (WasmExpression part : function.getBody()) {
part.acceptVisitor(visitor); part.acceptVisitor(visitor);
} }

View File

@ -17,6 +17,7 @@ package org.teavm.backend.wasm.render;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.teavm.backend.wasm.generate.DwarfGenerator;
import org.teavm.backend.wasm.model.WasmType; import org.teavm.backend.wasm.model.WasmType;
import org.teavm.backend.wasm.model.expression.WasmBlock; import org.teavm.backend.wasm.model.expression.WasmBlock;
import org.teavm.backend.wasm.model.expression.WasmBranch; import org.teavm.backend.wasm.model.expression.WasmBranch;
@ -57,16 +58,21 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor {
private Map<String, Integer> functionIndexes; private Map<String, Integer> functionIndexes;
private Map<String, Integer> importedIndexes; private Map<String, Integer> importedIndexes;
private Map<WasmSignature, Integer> signatureIndexes; private Map<WasmSignature, Integer> signatureIndexes;
private DwarfGenerator dwarfGenerator;
private int addressOffset;
private int depth; private int depth;
private Map<WasmBlock, Integer> blockDepths = new HashMap<>(); private Map<WasmBlock, Integer> blockDepths = new HashMap<>();
WasmBinaryRenderingVisitor(WasmBinaryWriter writer, WasmBinaryVersion version, Map<String, Integer> functionIndexes, WasmBinaryRenderingVisitor(WasmBinaryWriter writer, WasmBinaryVersion version, Map<String, Integer> functionIndexes,
Map<String, Integer> importedIndexes, Map<WasmSignature, Integer> signatureIndexes) { Map<String, Integer> importedIndexes, Map<WasmSignature, Integer> signatureIndexes,
DwarfGenerator dwarfGenerator, int addressOffset) {
this.writer = writer; this.writer = writer;
this.version = version; this.version = version;
this.functionIndexes = functionIndexes; this.functionIndexes = functionIndexes;
this.importedIndexes = importedIndexes; this.importedIndexes = importedIndexes;
this.signatureIndexes = signatureIndexes; this.signatureIndexes = signatureIndexes;
this.dwarfGenerator = dwarfGenerator;
this.addressOffset = addressOffset;
} }
@Override @Override
@ -95,6 +101,7 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor {
} }
expression.getCondition().acceptVisitor(this); expression.getCondition().acceptVisitor(this);
emitLocation(expression);
writer.writeByte(0x0D); writer.writeByte(0x0D);
writeLabel(expression.getTarget()); writeLabel(expression.getTarget());
@ -106,6 +113,7 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor {
expression.getResult().acceptVisitor(this); expression.getResult().acceptVisitor(this);
} }
emitLocation(expression);
writer.writeByte(0x0C); writer.writeByte(0x0C);
writeLabel(expression.getTarget()); writeLabel(expression.getTarget());
@ -115,6 +123,7 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor {
public void visit(WasmSwitch expression) { public void visit(WasmSwitch expression) {
expression.getSelector().acceptVisitor(this); expression.getSelector().acceptVisitor(this);
emitLocation(expression);
writer.writeByte(0x0E); writer.writeByte(0x0E);
writer.writeLEB(expression.getTargets().size()); writer.writeLEB(expression.getTargets().size());
@ -132,6 +141,7 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor {
@Override @Override
public void visit(WasmConditional expression) { public void visit(WasmConditional expression) {
expression.getCondition().acceptVisitor(this); expression.getCondition().acceptVisitor(this);
emitLocation(expression);
writer.writeByte(0x04); writer.writeByte(0x04);
writeBlockType(expression.getType()); writeBlockType(expression.getType());
@ -160,40 +170,47 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor {
if (expression.getValue() != null) { if (expression.getValue() != null) {
expression.getValue().acceptVisitor(this); expression.getValue().acceptVisitor(this);
} }
emitLocation(expression);
writer.writeByte(0x0F); writer.writeByte(0x0F);
} }
@Override @Override
public void visit(WasmUnreachable expression) { public void visit(WasmUnreachable expression) {
emitLocation(expression);
writer.writeByte(0x0); writer.writeByte(0x0);
} }
@Override @Override
public void visit(WasmInt32Constant expression) { public void visit(WasmInt32Constant expression) {
emitLocation(expression);
writer.writeByte(0x41); writer.writeByte(0x41);
writer.writeSignedLEB(expression.getValue()); writer.writeSignedLEB(expression.getValue());
} }
@Override @Override
public void visit(WasmInt64Constant expression) { public void visit(WasmInt64Constant expression) {
emitLocation(expression);
writer.writeByte(0x42); writer.writeByte(0x42);
writer.writeSignedLEB(expression.getValue()); writer.writeSignedLEB(expression.getValue());
} }
@Override @Override
public void visit(WasmFloat32Constant expression) { public void visit(WasmFloat32Constant expression) {
emitLocation(expression);
writer.writeByte(0x43); writer.writeByte(0x43);
writer.writeFixed(Float.floatToRawIntBits(expression.getValue())); writer.writeFixed(Float.floatToRawIntBits(expression.getValue()));
} }
@Override @Override
public void visit(WasmFloat64Constant expression) { public void visit(WasmFloat64Constant expression) {
emitLocation(expression);
writer.writeByte(0x44); writer.writeByte(0x44);
writer.writeFixed(Double.doubleToRawLongBits(expression.getValue())); writer.writeFixed(Double.doubleToRawLongBits(expression.getValue()));
} }
@Override @Override
public void visit(WasmGetLocal expression) { public void visit(WasmGetLocal expression) {
emitLocation(expression);
writer.writeByte(0x20); writer.writeByte(0x20);
writer.writeLEB(expression.getLocal().getIndex()); writer.writeLEB(expression.getLocal().getIndex());
} }
@ -201,6 +218,7 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor {
@Override @Override
public void visit(WasmSetLocal expression) { public void visit(WasmSetLocal expression) {
expression.getValue().acceptVisitor(this); expression.getValue().acceptVisitor(this);
emitLocation(expression);
writer.writeByte(0x21); writer.writeByte(0x21);
writer.writeLEB(expression.getLocal().getIndex()); writer.writeLEB(expression.getLocal().getIndex());
} }
@ -209,6 +227,7 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor {
public void visit(WasmIntBinary expression) { public void visit(WasmIntBinary expression) {
expression.getFirst().acceptVisitor(this); expression.getFirst().acceptVisitor(this);
expression.getSecond().acceptVisitor(this); expression.getSecond().acceptVisitor(this);
emitLocation(expression);
render0xD(expression); render0xD(expression);
} }
@ -379,6 +398,7 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor {
public void visit(WasmFloatBinary expression) { public void visit(WasmFloatBinary expression) {
expression.getFirst().acceptVisitor(this); expression.getFirst().acceptVisitor(this);
expression.getSecond().acceptVisitor(this); expression.getSecond().acceptVisitor(this);
emitLocation(expression);
render0xD(expression); render0xD(expression);
} }
@ -503,6 +523,7 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor {
@Override @Override
public void visit(WasmFloatUnary expression) { public void visit(WasmFloatUnary expression) {
expression.getOperand().acceptVisitor(this); expression.getOperand().acceptVisitor(this);
emitLocation(expression);
render0xD(expression); render0xD(expression);
} }
@ -570,6 +591,7 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor {
@Override @Override
public void visit(WasmConversion expression) { public void visit(WasmConversion expression) {
expression.getOperand().acceptVisitor(this); expression.getOperand().acceptVisitor(this);
emitLocation(expression);
switch (expression.getSourceType()) { switch (expression.getSourceType()) {
case INT32: case INT32:
switch (expression.getTargetType()) { switch (expression.getTargetType()) {
@ -658,6 +680,7 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor {
Integer functionIndex = !expression.isImported() Integer functionIndex = !expression.isImported()
? functionIndexes.get(expression.getFunctionName()) ? functionIndexes.get(expression.getFunctionName())
: importedIndexes.get(expression.getFunctionName()); : importedIndexes.get(expression.getFunctionName());
emitLocation(expression);
if (functionIndex == null) { if (functionIndex == null) {
writer.writeByte(0x00); writer.writeByte(0x00);
return; return;
@ -688,12 +711,14 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor {
@Override @Override
public void visit(WasmDrop expression) { public void visit(WasmDrop expression) {
expression.getOperand().acceptVisitor(this); expression.getOperand().acceptVisitor(this);
emitLocation(expression);
writer.writeByte(0x1A); writer.writeByte(0x1A);
} }
@Override @Override
public void visit(WasmLoadInt32 expression) { public void visit(WasmLoadInt32 expression) {
expression.getIndex().acceptVisitor(this); expression.getIndex().acceptVisitor(this);
emitLocation(expression);
switch (expression.getConvertFrom()) { switch (expression.getConvertFrom()) {
case INT8: case INT8:
writer.writeByte(0x2C); writer.writeByte(0x2C);
@ -718,6 +743,7 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor {
@Override @Override
public void visit(WasmLoadInt64 expression) { public void visit(WasmLoadInt64 expression) {
expression.getIndex().acceptVisitor(this); expression.getIndex().acceptVisitor(this);
emitLocation(expression);
switch (expression.getConvertFrom()) { switch (expression.getConvertFrom()) {
case INT8: case INT8:
writer.writeByte(0x30); writer.writeByte(0x30);
@ -748,6 +774,7 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor {
@Override @Override
public void visit(WasmLoadFloat32 expression) { public void visit(WasmLoadFloat32 expression) {
expression.getIndex().acceptVisitor(this); expression.getIndex().acceptVisitor(this);
emitLocation(expression);
writer.writeByte(0x2A); writer.writeByte(0x2A);
writer.writeByte(alignment(expression.getAlignment())); writer.writeByte(alignment(expression.getAlignment()));
writer.writeLEB(expression.getOffset()); writer.writeLEB(expression.getOffset());
@ -756,6 +783,7 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor {
@Override @Override
public void visit(WasmLoadFloat64 expression) { public void visit(WasmLoadFloat64 expression) {
expression.getIndex().acceptVisitor(this); expression.getIndex().acceptVisitor(this);
emitLocation(expression);
writer.writeByte(0x2B); writer.writeByte(0x2B);
writer.writeByte(alignment(expression.getAlignment())); writer.writeByte(alignment(expression.getAlignment()));
writer.writeLEB(expression.getOffset()); writer.writeLEB(expression.getOffset());
@ -765,6 +793,7 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor {
public void visit(WasmStoreInt32 expression) { public void visit(WasmStoreInt32 expression) {
expression.getIndex().acceptVisitor(this); expression.getIndex().acceptVisitor(this);
expression.getValue().acceptVisitor(this); expression.getValue().acceptVisitor(this);
emitLocation(expression);
switch (expression.getConvertTo()) { switch (expression.getConvertTo()) {
case INT8: case INT8:
case UINT8: case UINT8:
@ -786,6 +815,7 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor {
public void visit(WasmStoreInt64 expression) { public void visit(WasmStoreInt64 expression) {
expression.getIndex().acceptVisitor(this); expression.getIndex().acceptVisitor(this);
expression.getValue().acceptVisitor(this); expression.getValue().acceptVisitor(this);
emitLocation(expression);
switch (expression.getConvertTo()) { switch (expression.getConvertTo()) {
case INT8: case INT8:
case UINT8: case UINT8:
@ -811,6 +841,7 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor {
public void visit(WasmStoreFloat32 expression) { public void visit(WasmStoreFloat32 expression) {
expression.getIndex().acceptVisitor(this); expression.getIndex().acceptVisitor(this);
expression.getValue().acceptVisitor(this); expression.getValue().acceptVisitor(this);
emitLocation(expression);
writer.writeByte(0x38); writer.writeByte(0x38);
writer.writeByte(alignment(expression.getAlignment())); writer.writeByte(alignment(expression.getAlignment()));
writer.writeLEB(expression.getOffset()); writer.writeLEB(expression.getOffset());
@ -820,6 +851,7 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor {
public void visit(WasmStoreFloat64 expression) { public void visit(WasmStoreFloat64 expression) {
expression.getIndex().acceptVisitor(this); expression.getIndex().acceptVisitor(this);
expression.getValue().acceptVisitor(this); expression.getValue().acceptVisitor(this);
emitLocation(expression);
writer.writeByte(0x39); writer.writeByte(0x39);
writer.writeByte(alignment(expression.getAlignment())); writer.writeByte(alignment(expression.getAlignment()));
writer.writeLEB(expression.getOffset()); writer.writeLEB(expression.getOffset());
@ -828,6 +860,7 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor {
@Override @Override
public void visit(WasmMemoryGrow expression) { public void visit(WasmMemoryGrow expression) {
expression.getAmount().acceptVisitor(this); expression.getAmount().acceptVisitor(this);
emitLocation(expression);
writer.writeByte(0x40); writer.writeByte(0x40);
writer.writeByte(0); writer.writeByte(0);
} }
@ -840,4 +873,12 @@ class WasmBinaryRenderingVisitor implements WasmExpressionVisitor {
int blockDepth = blockDepths.get(target); int blockDepth = blockDepths.get(target);
writer.writeLEB(depth - blockDepth); writer.writeLEB(depth - blockDepth);
} }
private void emitLocation(WasmExpression expression) {
if (dwarfGenerator == null || expression.getLocation() == null) {
return;
}
dwarfGenerator.lineNumber(writer.getPosition() + addressOffset, expression.getLocation().getFileName(),
expression.getLocation().getLine());
}
} }