diff --git a/core/src/main/java/org/teavm/backend/wasm/disasm/DisasmCodeSectionListener.java b/core/src/main/java/org/teavm/backend/wasm/disasm/DisasmCodeSectionListener.java new file mode 100644 index 000000000..05add906e --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/disasm/DisasmCodeSectionListener.java @@ -0,0 +1,105 @@ +/* + * 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.disasm; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.util.function.Consumer; +import org.teavm.backend.wasm.model.WasmType; +import org.teavm.backend.wasm.parser.CodeSectionListener; +import org.teavm.backend.wasm.parser.CodeSectionParser; +import org.teavm.backend.wasm.parser.ModuleParser; +import org.teavm.common.ByteArrayAsyncInputStream; + +public class DisasmCodeSectionListener implements CodeSectionListener { + private PrintWriter writer; + + public DisasmCodeSectionListener(PrintWriter writer) { + this.writer = writer; + } + + @Override + public void address(int address) { + writer.print("(; "); + for (int i = 7; i >= 0; --i) { + var digit = (address >>> (i * 4)) & 0xF; + writer.print(Character.forDigit(digit, 16)); + } + writer.print(" ;)"); + writer.println(); + } + + @Override + public void sectionStart(int functionCount) { + writer.println(" .code functions=" + functionCount); + } + + @Override + public boolean functionStart(int index, int size) { + writer.println(" function fn_" + index); + return true; + } + + @Override + public void localsStart(int count) { + writer.println(" locals " + count); + } + + @Override + public void local(int start, int count, WasmType type) { + for (int i = 0; i < count; ++i) { + writer.println(" local " + (i + start) + ": " + type); + } + } + + @Override + public void localsEnd() { + writer.println(" end_locals"); + } + + @Override + public void functionEnd() { + writer.println(" end_function"); + } + + @Override + public void sectionEnd() { + writer.println(" end_code"); + } + + public static void main(String[] args) throws IOException { + var file = new File(args[0]); + var bytes = Files.readAllBytes(file.toPath()); + var input = new ByteArrayAsyncInputStream(bytes); + var parser = new ModuleParser(input) { + @Override + protected Consumer getSectionConsumer(int code, int pos, String name) { + if (code == 10) { + return bytes -> { + var writer = new PrintWriter(System.out); + var sectionParser = new CodeSectionParser(new DisasmCodeSectionListener(writer)); + sectionParser.parse(bytes); + writer.flush(); + }; + } + return null; + } + }; + input.readFully(parser::parse); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/parser/CodeSectionListener.java b/core/src/main/java/org/teavm/backend/wasm/parser/CodeSectionListener.java new file mode 100644 index 000000000..6e7c2337b --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/parser/CodeSectionListener.java @@ -0,0 +1,36 @@ +/* + * 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.parser; + +import org.teavm.backend.wasm.model.WasmType; + +public interface CodeSectionListener { + void address(int address); + + void sectionStart(int functionCount); + + boolean functionStart(int index, int size); + + void localsStart(int count); + + void local(int start, int count, WasmType type); + + void localsEnd(); + + void functionEnd(); + + void sectionEnd(); +} diff --git a/core/src/main/java/org/teavm/backend/wasm/parser/CodeSectionParser.java b/core/src/main/java/org/teavm/backend/wasm/parser/CodeSectionParser.java new file mode 100644 index 000000000..cd12b1162 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/parser/CodeSectionParser.java @@ -0,0 +1,115 @@ +/* + * 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.parser; + +import org.teavm.backend.wasm.model.WasmType; + +public class CodeSectionParser { + private CodeSectionListener listener; + private byte[] data; + private int ptr; + private int lastReportedPtr = -1; + + public CodeSectionParser(CodeSectionListener listener) { + this.listener = listener; + } + + public void parse(byte[] data) { + this.data = data; + ptr = 0; + try { + parseFunctions(); + } finally { + this.data = null; + } + } + + private void parseFunctions() { + reportAddress(); + int count = readLEB(); + listener.sectionStart(count); + for (var i = 0; i < count; ++i) { + parseFunction(i); + } + reportAddress(); + listener.sectionEnd(); + } + + private void parseFunction(int index) { + reportAddress(); + var functionSize = readLEB(); + var end = ptr + functionSize; + if (listener.functionStart(index, functionSize)) { + parseLocals(); + } + ptr = end; + reportAddress(); + listener.functionEnd(); + } + + private void parseLocals() { + reportAddress(); + var localEntries = readLEB(); + listener.localsStart(localEntries); + var localIndex = 0; + for (int i = 0; i < localEntries; ++i) { + reportAddress(); + var countInGroup = readLEB(); + var type = readType(); + listener.local(localIndex, countInGroup, type); + localIndex += countInGroup; + } + reportAddress(); + listener.localsEnd(); + } + + private WasmType readType() { + var typeId = data[ptr]; + switch (typeId) { + case 0x7F: + return WasmType.INT32; + case 0x7E: + return WasmType.INT64; + case 0x7D: + return WasmType.FLOAT32; + case 0x7C: + return WasmType.FLOAT64; + default: + return null; + } + } + + private void reportAddress() { + if (ptr != lastReportedPtr) { + lastReportedPtr = ptr; + listener.address(ptr); + } + } + + private int readLEB() { + var result = 0; + var shift = 0; + while (true) { + var digit = data[ptr++]; + result |= (digit & 0x7F) << shift; + if ((digit & 0x80) == 0) { + break; + } + shift += 7; + } + return result; + } +}