wasm gc: support emitting disassembly in HTML with link references, add setting to emit disassembly in JUnit tests

This commit is contained in:
Alexey Andreev 2024-08-29 18:41:02 +02:00
parent a8b999f8d9
commit 31d89ebec2
13 changed files with 290 additions and 38 deletions

View File

@ -102,23 +102,33 @@ public abstract class BaseDisassemblyListener {
}
protected void writeGlobalRef(int index) {
writer.startLink("g" + index);
writeRef(nameProvider.global(index), index);
writer.endLink();
}
protected void writeFunctionRef(int index) {
writer.startLink("f" + index);
writeRef(nameProvider.function(index), index);
writer.endLink();
}
protected void writeTypeRef(int index) {
writer.startLink("t" + index);
writeRef(nameProvider.type(index), index);
writer.endLink();
}
protected void writeFieldRef(int typeIndex, int index) {
writer.startLink("f" + typeIndex + "." + index);
writeRef(nameProvider.field(typeIndex, index), index);
writer.endLink();
}
protected void writeLocalRef(int functionIndex, int index) {
writer.startLink("l" + functionIndex + "." + index);
writeRef(nameProvider.local(functionIndex, index), index);
writer.endLink();
}
private void writeRef(String name, int index) {

View File

@ -16,10 +16,9 @@
package org.teavm.backend.wasm.disasm;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.nio.file.Files;
import java.util.function.Consumer;
import org.teavm.backend.wasm.parser.AddressListener;
@ -35,12 +34,10 @@ import org.teavm.common.AsyncInputStream;
import org.teavm.common.ByteArrayAsyncInputStream;
public final class Disassembler {
private PrintWriter out;
private DisassemblyWriter writer;
public Disassembler(Writer writer) {
out = new PrintWriter(writer);
this.writer = new DisassemblyWriter(out, true);
public Disassembler(DisassemblyWriter writer) {
this.writer = writer;
}
public void startModule() {
@ -52,9 +49,11 @@ public final class Disassembler {
}
public void disassemble(byte[] bytes) {
writer.prologue();
startModule();
read(bytes);
endModule();
writer.epilogue();
}
public void read(byte[] bytes) {
@ -97,12 +96,12 @@ public final class Disassembler {
};
if (code == 1) {
return bytes -> {
writer.write("(; type section size: " + bytes.length + " ;)");
writer.write("(; type section size: " + bytes.length + " ;)").eol();
var typeWriter = new DisassemblyTypeSectionListener(writer, nameProvider);
writer.setAddressOffset(pos);
var sectionParser = new TypeSectionParser(typeWriter);
sectionParser.parse(writer.addressListener, bytes);
out.flush();
writer.flush();
};
} else if (code == 2) {
return bytes -> {
@ -111,23 +110,23 @@ public final class Disassembler {
};
} else if (code == 6) {
return bytes -> {
writer.write("(; global section size: " + bytes.length + " ;)");
writer.write("(; global section size: " + bytes.length + " ;)").eol();
var globalWriter = new DisassemblyGlobalSectionListener(writer, nameProvider);
writer.setAddressOffset(pos);
var sectionParser = new GlobalSectionParser(globalWriter);
sectionParser.setFunctionIndexOffset(importListener.count);
sectionParser.parse(writer.addressListener, bytes);
out.flush();
writer.flush();
};
} else if (code == 10) {
return bytes -> {
var disassembler = new DisassemblyCodeSectionListener(writer, nameProvider);
writer.setAddressOffset(pos);
writer.write("(; code section size: " + bytes.length + " ;)");
writer.write("(; code section size: " + bytes.length + " ;)").eol();
var sectionParser = new CodeSectionParser(disassembler);
sectionParser.setFunctionIndexOffset(importListener.count);
sectionParser.parse(writer.addressListener, bytes);
out.flush();
writer.flush();
};
} else {
return null;
@ -146,9 +145,34 @@ public final class Disassembler {
}
public static void main(String[] args) throws IOException {
var file = new File(args[0]);
String fileName = null;
String outFileName = null;
var htmlMode = false;
for (var i = 0; i < args.length; i++) {
var arg = args[i];
if (arg.equals("--html")) {
htmlMode = true;
} else if (arg.equals("--output") || arg.equals("-o")) {
outFileName = args[++i];
} else {
fileName = arg;
}
}
var file = new File(fileName);
var bytes = Files.readAllBytes(file.toPath());
var disassembler = new Disassembler(new OutputStreamWriter(System.out));
var output = outFileName != null ? new FileOutputStream(outFileName) : System.out;
var writer = new PrintWriter(output);
var disassemblyWriter = htmlMode
? new DisassemblyHTMLWriter(writer)
: new DisassemblyTextWriter(writer);
disassemblyWriter.setWithAddress(true);
if (htmlMode) {
disassemblyWriter.write("<html><body><pre>").eol();
}
var disassembler = new Disassembler(disassemblyWriter);
disassembler.disassemble(bytes);
if (htmlMode) {
disassemblyWriter.write("</pre></body></html>").eol();
}
}
}

View File

@ -43,6 +43,10 @@ public class DisassemblyCodeListener extends BaseDisassemblyListener implements
this.currentFunctionId = currentFunctionId;
}
public void reset() {
blockIdGen = 0;
}
@Override
public void error(int depth) {
writer.address();
@ -56,7 +60,9 @@ public class DisassemblyCodeListener extends BaseDisassemblyListener implements
public int startBlock(boolean loop, WasmHollowType type) {
writer.address();
var label = blockIdGen++;
writer.write(loop ? "loop" : "block").write(" $label_" + label);
writer.startLinkTarget("start" + label).startLink("end" + label).write(loop ? "loop" : "block")
.endLink().endLinkTarget();
writer.write(" $label_" + label);
writeBlockType(type);
writer.indent().eol();
return label;
@ -66,7 +72,8 @@ public class DisassemblyCodeListener extends BaseDisassemblyListener implements
public int startConditionalBlock(WasmHollowType type) {
writer.address();
var label = blockIdGen++;
writer.write("if ").write(" $label_" + label);
writer.startLinkTarget("start" + label).startLink("end" + label).write("if").endLink().endLinkTarget();
writer.write(" $label_" + label);
writeBlockType(type);
writer.indent().eol();
return label;
@ -75,14 +82,16 @@ public class DisassemblyCodeListener extends BaseDisassemblyListener implements
@Override
public void startElseSection(int token) {
writer.address();
writer.outdent().write("else (; $label_" + token + " ;)").indent().eol();
writer.outdent().startLink("start" + token).write("else").endLink();
writer.write(" (; $label_" + token + " ;)").indent().eol();
}
@Override
public int startTry(WasmHollowType type) {
writer.address();
var label = blockIdGen++;
writer.write("try ").write(" $label_" + label);
writer.startLinkTarget("start" + label).startLink("end" + label).write("try").endLink().endLinkTarget();
writer.write(" $label_" + label);
writeBlockType(type);
writer.indent().eol();
return label;
@ -96,12 +105,14 @@ public class DisassemblyCodeListener extends BaseDisassemblyListener implements
@Override
public void endBlock(int token, boolean loop) {
writer.address().outdent().write("end (; $label_" + token + " ;)").eol();
writer.address().outdent();
writer.startLinkTarget("end" + token).startLink("start" + token).write("end").endLink().endLinkTarget();
writer.write(" (; $label_" + token + " ;)").eol();
}
@Override
public void branch(BranchOpcode opcode, int depth, int target) {
writer.address();
writer.address().startLink("start" + target);
switch (opcode) {
case BR:
writer.write("br");
@ -110,7 +121,7 @@ public class DisassemblyCodeListener extends BaseDisassemblyListener implements
writer.write("br_if");
break;
}
writer.write(" $label_" + target).eol();
writer.endLink().write(" $label_" + target).eol();
}
@Override
@ -202,7 +213,9 @@ public class DisassemblyCodeListener extends BaseDisassemblyListener implements
@Override
public void callReference(int typeIndex) {
writer.address();
writer.write("call_ref " + typeIndex).eol();
writer.write("call_ref ");
writeTypeRef(typeIndex);
writer.eol();
}
@Override

View File

@ -31,12 +31,13 @@ public class DisassemblyCodeSectionListener extends BaseDisassemblyListener impl
@Override
public boolean functionStart(int index, int size) {
currentFunctionId = index;
writer.address().write("(func ").write("(; " + index + " ;)");
writer.address().write("(func ");
writer.startLinkTarget("f" + index).write("(; " + index + " ;)");
var name = nameProvider.function(index);
if (name != null) {
writer.write(" $").write(name);
}
writer.indent().eol();
writer.endLinkTarget().indent().eol();
return true;
}
@ -49,11 +50,14 @@ public class DisassemblyCodeSectionListener extends BaseDisassemblyListener impl
public void local(int start, int count, WasmHollowType type) {
writer.address();
for (int i = 0; i < count; ++i) {
writer.write("(local (; " + (i + start) + " ;) ");
var name = nameProvider.local(currentFunctionId, i + start);
writer.write("(local ");
var id = i + start;
writer.startLinkTarget("l" + currentFunctionId + "." + id).write("(; " + id + " ;)");
var name = nameProvider.local(currentFunctionId, id);
if (name != null) {
writer.write("$").write(name).write(" ");
writer.write(" ").write("$").write(name);
}
writer.endLinkTarget().write(" ");
writeType(type);
writer.write(")").eol();
}
@ -62,6 +66,7 @@ public class DisassemblyCodeSectionListener extends BaseDisassemblyListener impl
@Override
public CodeListener code() {
codeListener.setCurrentFunctionId(currentFunctionId);
codeListener.reset();
return codeListener;
}

View File

@ -29,11 +29,13 @@ public class DisassemblyGlobalSectionListener extends BaseDisassemblyListener im
@Override
public CodeListener startGlobal(int index, WasmHollowType type, boolean mutable) {
writer.address().write("(global (; ").write(String.valueOf(index)).write(" ;) ");
writer.address().write("(global ");
writer.startLinkTarget("g" + index).write("(; ").write(String.valueOf(index)).write(" ;)");
var name = nameProvider.global(index);
if (name != null) {
writer.write("$").write(name).write(" ");
writer.write("$").write(name);
}
writer.endLinkTarget().write(" ");
if (mutable) {
writer.write("(mut ");
writeType(type);
@ -42,6 +44,7 @@ public class DisassemblyGlobalSectionListener extends BaseDisassemblyListener im
writeType(type);
}
writer.indent().eol();
codeListener.reset();
return codeListener;
}

View File

@ -0,0 +1,85 @@
/*
* 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.disasm;
import java.io.PrintWriter;
public class DisassemblyHTMLWriter extends DisassemblyWriter {
public DisassemblyHTMLWriter(PrintWriter out) {
super(out);
}
@Override
public DisassemblyWriter prologue() {
return writeExact("<html><body><pre>");
}
@Override
public DisassemblyWriter epilogue() {
return writeExact("</pre></body></html>");
}
@Override
public DisassemblyWriter startLink(String s) {
writeExact("<a href=\"#").writeExact(s).writeExact("\">");
return this;
}
@Override
public DisassemblyWriter endLink() {
writeExact("</a>");
return this;
}
@Override
public DisassemblyWriter startLinkTarget(String s) {
writeExact("<a name=\"").writeExact(s).writeExact("\">");
return this;
}
@Override
public DisassemblyWriter endLinkTarget() {
writeExact("</a>");
return this;
}
@Override
public DisassemblyWriter write(String s) {
StringBuilder sb = null;
var i = 0;
for (; i < s.length(); ++i) {
var c = s.charAt(i);
if (c == '<') {
sb = new StringBuilder();
sb.append(s, 0, i);
break;
}
}
if (sb != null) {
for (; i < s.length(); ++i) {
var c = s.charAt(i);
if (c == '<') {
sb.append("&lt;");
} else {
sb.append(c);
}
}
s = sb.toString();
}
writeExact(s);
return this;
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.disasm;
import java.io.PrintWriter;
public class DisassemblyTextWriter extends DisassemblyWriter {
public DisassemblyTextWriter(PrintWriter out) {
super(out);
}
@Override
public DisassemblyWriter startLink(String s) {
return this;
}
@Override
public DisassemblyWriter endLink() {
return this;
}
@Override
public DisassemblyWriter startLinkTarget(String s) {
return this;
}
@Override
public DisassemblyWriter endLinkTarget() {
return this;
}
@Override
public DisassemblyWriter prologue() {
return this;
}
@Override
public DisassemblyWriter epilogue() {
return this;
}
}

View File

@ -43,11 +43,13 @@ public class DisassemblyTypeSectionListener extends BaseDisassemblyListener impl
@Override
public void startType(int index, boolean open, int[] supertypes) {
currentTypeIndex = index;
writer.address().write("(type (; ").write(String.valueOf(index)).write(" ;) ");
writer.address().write("(type ");
writer.startLinkTarget("t" + index).write("(; ").write(String.valueOf(index)).write(" ;) ");
var name = nameProvider.type(index);
if (name != null) {
writer.write("$").write(name).write(" ");
writer.write("$").write(name);
}
writer.endLinkTarget().write(" ");
if (!open || supertypes.length > 0) {
currentTypeNeedsClosing = true;
writer.write("(sub ");
@ -81,11 +83,13 @@ public class DisassemblyTypeSectionListener extends BaseDisassemblyListener impl
public void field(WasmHollowStorageType hollowType, boolean mutable) {
writer.address().write("(field ");
if (needsFieldIndex) {
writer.write("(; " + fieldIndex++ + " ;) ");
var name = nameProvider.field(currentTypeIndex, fieldIndex);
var index = fieldIndex++;
writer.startLinkTarget("f" + currentTypeIndex + "." + index).write("(; " + index + " ;)");
var name = nameProvider.field(currentTypeIndex, index);
if (name != null) {
writer.write("$").write(name);
writer.write(" ").write("$").write(name);
}
writer.endLinkTarget().write(" ");
}
if (mutable) {
writer.write("(mut ");

View File

@ -18,7 +18,7 @@ package org.teavm.backend.wasm.disasm;
import java.io.PrintWriter;
import org.teavm.backend.wasm.parser.AddressListener;
public class DisassemblyWriter {
public abstract class DisassemblyWriter {
private PrintWriter out;
private boolean withAddress;
private int indentLevel;
@ -27,8 +27,11 @@ public class DisassemblyWriter {
private boolean lineStarted;
private int addressOffset;
public DisassemblyWriter(PrintWriter out, boolean withAddress) {
public DisassemblyWriter(PrintWriter out) {
this.out = out;
}
public void setWithAddress(boolean withAddress) {
this.withAddress = withAddress;
}
@ -84,11 +87,31 @@ public class DisassemblyWriter {
}
public DisassemblyWriter write(String s) {
return writeExact(s);
}
protected DisassemblyWriter writeExact(String s) {
startLine();
out.print(s);
return this;
}
public abstract DisassemblyWriter startLink(String s);
public abstract DisassemblyWriter endLink();
public abstract DisassemblyWriter startLinkTarget(String s);
public abstract DisassemblyWriter endLinkTarget();
public abstract DisassemblyWriter prologue();
public abstract DisassemblyWriter epilogue();
public void flush() {
out.flush();
}
public final AddressListener addressListener = new AddressListener() {
@Override
public void address(int address) {

View File

@ -61,6 +61,8 @@ tasks.test {
systemProperty("teavm.junit.wasm-gc", providers.gradleProperty("teavm.tests.wasm-gc").orElse("false").get())
systemProperty("teavm.junit.wasm-gc.runner", browser)
systemProperty("teavm.junit.wasm-gc.disasm", providers.gradleProperty("teavm.tests.wasm-gc.disasm")
.orElse("false").get())
systemProperty("teavm.junit.wasi", providers.gradleProperty("teavm.tests.wasi").orElse("true").get())
systemProperty("teavm.junit.wasi.runner", providers.gradleProperty("teavm.tests.wasi.runner")

View File

@ -26,6 +26,7 @@ final class PropertyNames {
static final String WASI_ENABLED = "teavm.junit.wasi";
static final String WASI_RUNNER = "teavm.junit.wasi.runner";
static final String WASM_GC_ENABLED = "teavm.junit.wasm-gc";
static final String WASM_GC_DISASM = "teavm.junit.wasm-gc.disasm";
static final String C_COMPILER = "teavm.junit.c.compiler";
static final String C_LINE_NUMBERS = "teavm.junit.c.lineNumbers";
static final String MINIFIED = "teavm.junit.minified";

View File

@ -106,7 +106,8 @@ public class TeaVMTestRunner extends Runner implements Filterable {
platforms.add(new JSPlatformSupport(classSource, referenceCache));
platforms.add(new WebAssemblyPlatformSupport(classSource, referenceCache));
platforms.add(new WebAssemblyGCPlatformSupport(classSource, referenceCache));
platforms.add(new WebAssemblyGCPlatformSupport(classSource, referenceCache,
Boolean.parseBoolean(System.getProperty(PropertyNames.WASM_GC_DISASM))));
platforms.add(new WasiPlatformSupport(classSource, referenceCache));
platforms.add(new CPlatformSupport(classSource, referenceCache));

View File

@ -20,8 +20,12 @@ import static org.teavm.junit.PropertyNames.SOURCE_DIRS;
import static org.teavm.junit.PropertyNames.WASM_GC_ENABLED;
import static org.teavm.junit.PropertyNames.WASM_RUNNER;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.reflect.AnnotatedElement;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -29,6 +33,8 @@ import java.util.StringTokenizer;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.teavm.backend.wasm.WasmGCTarget;
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;
@ -36,8 +42,11 @@ import org.teavm.model.ReferenceCache;
import org.teavm.vm.TeaVM;
class WebAssemblyGCPlatformSupport extends TestPlatformSupport<WasmGCTarget> {
WebAssemblyGCPlatformSupport(ClassHolderSource classSource, ReferenceCache referenceCache) {
private boolean disassembly;
WebAssemblyGCPlatformSupport(ClassHolderSource classSource, ReferenceCache referenceCache, boolean disassembly) {
super(classSource, referenceCache);
this.disassembly = disassembly;
}
@Override
@ -117,6 +126,9 @@ class WebAssemblyGCPlatformSupport extends TestPlatformSupport<WasmGCTarget> {
} catch (IOException e) {
throw new RuntimeException(e);
}
if (disassembly) {
writeDisassembly(outputPath, "classTest", configuration);
}
}
@Override
@ -130,5 +142,20 @@ class WebAssemblyGCPlatformSupport extends TestPlatformSupport<WasmGCTarget> {
} catch (IOException e) {
throw new RuntimeException(e);
}
if (disassembly) {
writeDisassembly(outputPathForMethod, "test", configuration);
}
}
private void writeDisassembly(File outputPath, String name, TeaVMTestConfiguration<?> configuration) {
var binPath = getOutputFile(outputPath, name, configuration.getSuffix(), getExtension());
var htmlPath = getOutputFile(outputPath, name, configuration.getSuffix(), ".wast.html");
try (var writer = new OutputStreamWriter(new FileOutputStream(htmlPath))) {
var disasmWriter = new DisassemblyHTMLWriter(new PrintWriter(writer));
disasmWriter.setWithAddress(true);
new Disassembler(disasmWriter).disassemble(Files.readAllBytes(binPath.toPath()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}