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 6d8f4f1d3..b688f15f7 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java @@ -17,8 +17,6 @@ package org.teavm.backend.wasm; import java.io.IOException; import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -66,7 +64,8 @@ import org.teavm.backend.wasm.model.expression.WasmReturn; import org.teavm.backend.wasm.model.expression.WasmSetLocal; import org.teavm.backend.wasm.model.expression.WasmStoreInt32; import org.teavm.backend.wasm.patches.ClassPatch; -import org.teavm.backend.wasm.render.WasmCRenderer; +import org.teavm.backend.wasm.render.WasmBinaryRenderer; +import org.teavm.backend.wasm.render.WasmBinaryWriter; import org.teavm.dependency.ClassDependency; import org.teavm.dependency.DependencyChecker; import org.teavm.dependency.DependencyListener; @@ -272,14 +271,13 @@ public class WasmTarget implements TeaVMTarget { module.getFunctionTable().add(module.getFunctions().get(function)); } - WasmCRenderer renderer = new WasmCRenderer(); - renderer.setOutputLineNumbers(debugging); + WasmBinaryWriter writer = new WasmBinaryWriter(); + WasmBinaryRenderer renderer = new WasmBinaryRenderer(writer); renderer.render(module); try { - Writer writer = new OutputStreamWriter(output, "UTF-8"); - writer.append(renderer.toString()); - writer.flush(); + output.write(writer.getData()); + output.flush(); } catch (IOException e) { throw new RuntimeException(e); } 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 new file mode 100644 index 000000000..1bfc15fa0 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderer.java @@ -0,0 +1,290 @@ +/* + * Copyright 2016 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.render; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.teavm.backend.wasm.model.WasmFunction; +import org.teavm.backend.wasm.model.WasmMemorySegment; +import org.teavm.backend.wasm.model.WasmModule; +import org.teavm.backend.wasm.model.WasmType; +import org.teavm.backend.wasm.model.expression.WasmExpression; + +public class WasmBinaryRenderer { + private WasmBinaryWriter output; + private List signatures = new ArrayList<>(); + private Map signatureIndexes = new HashMap<>(); + private Map importIndexes = new HashMap<>(); + private Map functionIndexes = new HashMap<>(); + + public WasmBinaryRenderer(WasmBinaryWriter output) { + this.output = output; + } + + public void render(WasmModule module) { + output.writeInt32(0x6d736100); + output.writeInt32(11); + renderSignatures(module); + renderImports(module); + renderFunctions(module); + renderTable(module); + renderMemory(module); + renderExport(module); + renderStart(module); + renderCode(module); + renderData(module); + } + + private void renderSignatures(WasmModule module) { + WasmBinaryWriter section = new WasmBinaryWriter(); + WasmSignatureCollector signatureCollector = new WasmSignatureCollector(this::registerSignature); + + for (WasmFunction function : module.getFunctions().values()) { + registerSignature(WasmSignature.fromFunction(function)); + for (WasmExpression part : function.getBody()) { + part.acceptVisitor(signatureCollector); + } + } + + section.writeLEB(signatures.size()); + for (WasmSignature signature : signatures) { + section.writeByte(0x40); + section.writeLEB(signature.types.length - 1); + for (int i = 1; i < signature.types.length; ++i) { + section.writeType(signature.types[i]); + } + if (signature.types[0] != null) { + section.writeByte(1); + section.writeType(signature.types[0]); + } else { + section.writeByte(0); + } + } + + writeSection("type", section.getData()); + } + + private void renderImports(WasmModule module) { + int index = 0; + List functions = new ArrayList<>(); + for (WasmFunction function : module.getFunctions().values()) { + if (function.getImportName() == null) { + continue; + } + functions.add(function); + importIndexes.put(function.getName(), index++); + } + if (functions.isEmpty()) { + return; + } + + WasmBinaryWriter section = new WasmBinaryWriter(); + + section.writeLEB(functions.size()); + for (WasmFunction function : functions) { + WasmSignature signature = WasmSignature.fromFunction(function); + section.writeLEB(signatureIndexes.get(signature)); + + String moduleName = function.getImportModule(); + if (moduleName == null) { + moduleName = ""; + } + section.writeAsciiString(moduleName); + + section.writeAsciiString(function.getImportName()); + } + + writeSection("import", section.getData()); + } + + private void renderFunctions(WasmModule module) { + WasmBinaryWriter section = new WasmBinaryWriter(); + + int index = 0; + List functions = module.getFunctions().values().stream() + .filter(function -> function.getImportName() == null) + .collect(Collectors.toList()); + for (WasmFunction function : functions) { + functionIndexes.put(function.getName(), index++); + } + + section.writeLEB(functions.size()); + for (WasmFunction function : functions) { + WasmSignature signature = WasmSignature.fromFunction(function); + section.writeLEB(signatureIndexes.get(signature)); + } + + writeSection("function", section.getData()); + } + + private void renderTable(WasmModule module) { + if (module.getFunctionTable().isEmpty()) { + return; + } + + WasmBinaryWriter section = new WasmBinaryWriter(); + + section.writeLEB(module.getFunctionTable().size()); + for (WasmFunction function : module.getFunctionTable()) { + section.writeLEB(functionIndexes.get(function.getName())); + } + + writeSection("table", section.getData()); + } + + private void renderMemory(WasmModule module) { + WasmBinaryWriter section = new WasmBinaryWriter(); + + section.writeLEB(module.getMemorySize()); + section.writeLEB(module.getMemorySize()); + section.writeByte(0); + + writeSection("memory", section.getData()); + } + + private void renderExport(WasmModule module) { + List functions = module.getFunctions().values().stream() + .filter(function -> function.getExportName() != null) + .collect(Collectors.toList()); + if (functions.isEmpty()) { + return; + } + + WasmBinaryWriter section = new WasmBinaryWriter(); + + section.writeLEB(functions.size()); + for (WasmFunction function : functions) { + section.writeLEB(functionIndexes.get(function.getName())); + + section.writeAsciiString(function.getExportName()); + } + + writeSection("export", section.getData()); + } + + private void renderStart(WasmModule module) { + if (module.getStartFunction() == null) { + return; + } + + WasmBinaryWriter section = new WasmBinaryWriter(); + section.writeLEB(functionIndexes.get(module.getStartFunction().getName())); + + writeSection("start", section.getData()); + } + + private void renderCode(WasmModule module) { + WasmBinaryWriter section = new WasmBinaryWriter(); + + List functions = module.getFunctions().values().stream() + .filter(function -> function.getImportName() == null) + .collect(Collectors.toList()); + + section.writeLEB(functions.size()); + for (WasmFunction function : functions) { + if (function.getLocalVariables().isEmpty()) { + section.writeLEB(0); + } else { + List localEntries = new ArrayList<>(); + LocalEntry currentEntry = new LocalEntry(function.getLocalVariables().get(0).getType()); + for (int i = 1; i < function.getLocalVariables().size(); ++i) { + WasmType type = function.getLocalVariables().get(i).getType(); + if (currentEntry.type == type) { + currentEntry.count++; + } else { + localEntries.add(currentEntry); + currentEntry = new LocalEntry(type); + } + } + localEntries.add(currentEntry); + + section.writeLEB(localEntries.size()); + for (LocalEntry entry : localEntries) { + section.writeLEB(entry.count); + section.writeType(entry.type); + } + } + + System.out.println(function.getName() + ": " + (output.getPosition() + section.getPosition())); + + byte[] body = renderFunction(function); + section.writeLEB(body.length); + section.writeBytes(body); + } + + writeSection("code", section.getData()); + } + + private byte[] renderFunction(WasmFunction function) { + WasmBinaryWriter code = new WasmBinaryWriter(); + + WasmBinaryRenderingVisitor visitor = new WasmBinaryRenderingVisitor(code, functionIndexes, importIndexes, + signatureIndexes); + for (WasmExpression part : function.getBody()) { + part.acceptVisitor(visitor); + } + + return code.getData(); + } + + private void renderData(WasmModule module) { + if (module.getSegments().isEmpty()) { + return; + } + + WasmBinaryWriter section = new WasmBinaryWriter(); + + section.writeLEB(module.getSegments().size()); + for (WasmMemorySegment segment : module.getSegments()) { + section.writeLEB(segment.getOffset()); + section.writeLEB(segment.getLength()); + int chunkSize = 65536; + for (int i = 0; i < segment.getLength(); i += chunkSize) { + int next = Math.min(i + chunkSize, segment.getLength()); + section.writeBytes(segment.getData(i, next - i)); + } + } + + writeSection("data", section.getData()); + } + + static class LocalEntry { + WasmType type; + int count = 1; + + public LocalEntry(WasmType type) { + this.type = type; + } + } + + private void registerSignature(WasmSignature signature) { + signatureIndexes.computeIfAbsent(signature, key -> { + int result = signatures.size(); + signatures.add(key); + return result; + }); + } + + private void writeSection(String id, byte[] data) { + output.writeAsciiString(id); + + output.writeLEB(data.length); + output.writeBytes(data); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderingVisitor.java b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderingVisitor.java new file mode 100644 index 000000000..0cae7546d --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryRenderingVisitor.java @@ -0,0 +1,784 @@ +/* + * Copyright 2016 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.render; + +import java.util.HashMap; +import java.util.Map; +import org.teavm.backend.wasm.model.WasmType; +import org.teavm.backend.wasm.model.expression.WasmBlock; +import org.teavm.backend.wasm.model.expression.WasmBranch; +import org.teavm.backend.wasm.model.expression.WasmBreak; +import org.teavm.backend.wasm.model.expression.WasmCall; +import org.teavm.backend.wasm.model.expression.WasmConditional; +import org.teavm.backend.wasm.model.expression.WasmConversion; +import org.teavm.backend.wasm.model.expression.WasmDrop; +import org.teavm.backend.wasm.model.expression.WasmExpression; +import org.teavm.backend.wasm.model.expression.WasmExpressionVisitor; +import org.teavm.backend.wasm.model.expression.WasmFloat32Constant; +import org.teavm.backend.wasm.model.expression.WasmFloat64Constant; +import org.teavm.backend.wasm.model.expression.WasmFloatBinary; +import org.teavm.backend.wasm.model.expression.WasmFloatUnary; +import org.teavm.backend.wasm.model.expression.WasmGetLocal; +import org.teavm.backend.wasm.model.expression.WasmIndirectCall; +import org.teavm.backend.wasm.model.expression.WasmInt32Constant; +import org.teavm.backend.wasm.model.expression.WasmInt64Constant; +import org.teavm.backend.wasm.model.expression.WasmIntBinary; +import org.teavm.backend.wasm.model.expression.WasmIntUnary; +import org.teavm.backend.wasm.model.expression.WasmLoadFloat32; +import org.teavm.backend.wasm.model.expression.WasmLoadFloat64; +import org.teavm.backend.wasm.model.expression.WasmLoadInt32; +import org.teavm.backend.wasm.model.expression.WasmLoadInt64; +import org.teavm.backend.wasm.model.expression.WasmReturn; +import org.teavm.backend.wasm.model.expression.WasmSetLocal; +import org.teavm.backend.wasm.model.expression.WasmStoreFloat32; +import org.teavm.backend.wasm.model.expression.WasmStoreFloat64; +import org.teavm.backend.wasm.model.expression.WasmStoreInt32; +import org.teavm.backend.wasm.model.expression.WasmStoreInt64; +import org.teavm.backend.wasm.model.expression.WasmSwitch; +import org.teavm.backend.wasm.model.expression.WasmUnreachable; + +class WasmBinaryRenderingVisitor implements WasmExpressionVisitor { + private WasmBinaryWriter writer; + private Map functionIndexes; + private Map importedIndexes; + private Map signatureIndexes; + private int depth; + private Map blockDepths = new HashMap<>(); + + WasmBinaryRenderingVisitor(WasmBinaryWriter writer, Map functionIndexes, + Map importedIndexes, Map signatureIndexes) { + this.writer = writer; + this.functionIndexes = functionIndexes; + this.importedIndexes = importedIndexes; + this.signatureIndexes = signatureIndexes; + } + + @Override + public void visit(WasmBlock expression) { + ++depth; + blockDepths.put(expression, depth); + writer.writeByte(0x01); + for (WasmExpression part : expression.getBody()) { + part.acceptVisitor(this); + } + writer.writeByte(0x0F); + blockDepths.remove(expression); + --depth; + } + + @Override + public void visit(WasmBranch expression) { + if (expression.getResult() != null) { + expression.getResult().acceptVisitor(this); + } + expression.getCondition().acceptVisitor(this); + writer.writeByte(0x07); + writer.writeByte(expression.getResult() != null ? 1 : 0); + writeLabel(expression.getTarget()); + } + + @Override + public void visit(WasmBreak expression) { + if (expression.getResult() != null) { + expression.getResult().acceptVisitor(this); + } + writer.writeByte(0x06); + writer.writeByte(expression.getResult() != null ? 1 : 0); + writeLabel(expression.getTarget()); + } + + @Override + public void visit(WasmSwitch expression) { + expression.getSelector().acceptVisitor(this); + writer.writeByte(0x08); + writer.writeByte(0); + writer.writeLEB(expression.getTargets().size()); + expression.getTargets().forEach(this::writeLabel); + writeLabel(expression.getDefaultTarget()); + } + + @Override + public void visit(WasmConditional expression) { + expression.getCondition().acceptVisitor(this); + writer.writeByte(0x03); + + ++depth; + blockDepths.put(expression.getThenBlock(), depth); + for (WasmExpression part : expression.getThenBlock().getBody()) { + part.acceptVisitor(this); + } + blockDepths.remove(expression.getThenBlock()); + + if (!expression.getElseBlock().getBody().isEmpty()) { + writer.writeByte(0x04); + blockDepths.put(expression.getElseBlock(), depth); + for (WasmExpression part : expression.getElseBlock().getBody()) { + part.acceptVisitor(this); + } + blockDepths.remove(expression.getElseBlock()); + } + --depth; + + writer.writeByte(0x0F); + } + + @Override + public void visit(WasmReturn expression) { + if (expression.getValue() != null) { + expression.getValue().acceptVisitor(this); + } + writer.writeByte(0x09); + writer.writeByte(expression.getValue() != null ? 1 : 0); + } + + @Override + public void visit(WasmUnreachable expression) { + writer.writeByte(0x0A); + } + + @Override + public void visit(WasmInt32Constant expression) { + writer.writeByte(0x10); + writer.writeSignedLEB(expression.getValue()); + } + + @Override + public void visit(WasmInt64Constant expression) { + writer.writeByte(0x11); + writer.writeSignedLEB(expression.getValue()); + } + + @Override + public void visit(WasmFloat32Constant expression) { + writer.writeByte(0x13); + writer.writeLEB(Float.floatToRawIntBits(expression.getValue())); + } + + @Override + public void visit(WasmFloat64Constant expression) { + writer.writeByte(0x12); + writer.writeLEB(Double.doubleToRawLongBits(expression.getValue())); + } + + @Override + public void visit(WasmGetLocal expression) { + writer.writeByte(0x14); + writer.writeLEB(expression.getLocal().getIndex()); + } + + @Override + public void visit(WasmSetLocal expression) { + expression.getValue().acceptVisitor(this); + writer.writeByte(0x15); + writer.writeLEB(expression.getLocal().getIndex()); + } + + @Override + public void visit(WasmIntBinary expression) { + expression.getFirst().acceptVisitor(this); + expression.getSecond().acceptVisitor(this); + switch (expression.getType()) { + case INT32: + switch (expression.getOperation()) { + case ADD: + writer.writeByte(0x40); + break; + case SUB: + writer.writeByte(0x41); + break; + case MUL: + writer.writeByte(0x42); + break; + case DIV_SIGNED: + writer.writeByte(0x43); + break; + case DIV_UNSIGNED: + writer.writeByte(0x44); + break; + case REM_SIGNED: + writer.writeByte(0x45); + break; + case REM_UNSIGNED: + writer.writeByte(0x46); + break; + case AND: + writer.writeByte(0x47); + break; + case OR: + writer.writeByte(0x48); + break; + case XOR: + writer.writeByte(0x49); + break; + case SHL: + writer.writeByte(0x4A); + break; + case SHR_UNSIGNED: + writer.writeByte(0x4B); + break; + case SHR_SIGNED: + writer.writeByte(0x4C); + break; + case ROTR: + writer.writeByte(0xB6); + break; + case ROTL: + writer.writeByte(0xB7); + break; + case EQ: + writer.writeByte(0x4D); + break; + case NE: + writer.writeByte(0x4E); + break; + case LT_SIGNED: + writer.writeByte(0x4F); + break; + case LE_SIGNED: + writer.writeByte(0x50); + break; + case LT_UNSIGNED: + writer.writeByte(0x51); + break; + case LE_UNSIGNED: + writer.writeByte(0x52); + break; + case GT_SIGNED: + writer.writeByte(0x53); + break; + case GE_SIGNED: + writer.writeByte(0x54); + break; + case GT_UNSIGNED: + writer.writeByte(0x55); + break; + case GE_UNSIGNED: + writer.writeByte(0x56); + break; + } + break; + case INT64: + switch (expression.getOperation()) { + case ADD: + writer.writeByte(0x5B); + break; + case SUB: + writer.writeByte(0x5C); + break; + case MUL: + writer.writeByte(0x5D); + break; + case DIV_SIGNED: + writer.writeByte(0x5E); + break; + case DIV_UNSIGNED: + writer.writeByte(0x5F); + break; + case REM_SIGNED: + writer.writeByte(0x60); + break; + case REM_UNSIGNED: + writer.writeByte(0x61); + break; + case AND: + writer.writeByte(0x62); + break; + case OR: + writer.writeByte(0x63); + break; + case XOR: + writer.writeByte(0x64); + break; + case SHL: + writer.writeByte(0x65); + break; + case SHR_UNSIGNED: + writer.writeByte(0x66); + break; + case SHR_SIGNED: + writer.writeByte(0x67); + break; + case ROTR: + writer.writeByte(0xB8); + break; + case ROTL: + writer.writeByte(0xB9); + break; + case EQ: + writer.writeByte(0x68); + break; + case NE: + writer.writeByte(0x69); + break; + case LT_SIGNED: + writer.writeByte(0x6A); + break; + case LE_SIGNED: + writer.writeByte(0x6B); + break; + case LT_UNSIGNED: + writer.writeByte(0x6C); + break; + case LE_UNSIGNED: + writer.writeByte(0x6D); + break; + case GT_SIGNED: + writer.writeByte(0x6E); + break; + case GE_SIGNED: + writer.writeByte(0x6F); + break; + case GT_UNSIGNED: + writer.writeByte(0x70); + break; + case GE_UNSIGNED: + writer.writeByte(0x71); + break; + } + break; + } + } + + @Override + public void visit(WasmFloatBinary expression) { + expression.getFirst().acceptVisitor(this); + expression.getSecond().acceptVisitor(this); + switch (expression.getType()) { + case FLOAT32: + switch (expression.getOperation()) { + case ADD: + writer.writeByte(0x75); + break; + case SUB: + writer.writeByte(0x76); + break; + case MUL: + writer.writeByte(0x77); + break; + case DIV: + writer.writeByte(0x78); + break; + case MIN: + writer.writeByte(0x79); + break; + case MAX: + writer.writeByte(0x7A); + break; + case EQ: + writer.writeByte(0x83); + break; + case NE: + writer.writeByte(0x84); + break; + case LT: + writer.writeByte(0x85); + break; + case LE: + writer.writeByte(0x86); + break; + case GT: + writer.writeByte(0x87); + break; + case GE: + writer.writeByte(0x88); + break; + } + break; + case FLOAT64: + switch (expression.getOperation()) { + case ADD: + writer.writeByte(0x89); + break; + case SUB: + writer.writeByte(0x8A); + break; + case MUL: + writer.writeByte(0x8B); + break; + case DIV: + writer.writeByte(0x8C); + break; + case MIN: + writer.writeByte(0x8D); + break; + case MAX: + writer.writeByte(0x8E); + break; + case EQ: + writer.writeByte(0x97); + break; + case NE: + writer.writeByte(0x98); + break; + case LT: + writer.writeByte(0x99); + break; + case LE: + writer.writeByte(0x9A); + break; + case GT: + writer.writeByte(0x9B); + break; + case GE: + writer.writeByte(0x9C); + break; + } + break; + } + } + + @Override + public void visit(WasmIntUnary expression) { + expression.getOperand().acceptVisitor(this); + switch (expression.getType()) { + case INT32: + switch (expression.getOperation()) { + case CLZ: + writer.writeByte(0x57); + break; + case CTZ: + writer.writeByte(0x58); + break; + case POPCNT: + writer.writeByte(0x59); + break; + } + break; + case INT64: + switch (expression.getOperation()) { + case CLZ: + writer.writeByte(0x72); + break; + case CTZ: + writer.writeByte(0x73); + break; + case POPCNT: + writer.writeByte(0x74); + break; + } + break; + } + } + + @Override + public void visit(WasmFloatUnary expression) { + expression.getOperand().acceptVisitor(this); + switch (expression.getType()) { + case FLOAT32: + switch (expression.getOperation()) { + case ABS: + writer.writeByte(0x7B); + break; + case NEG: + writer.writeByte(0x7C); + break; + case COPYSIGN: + writer.writeByte(0x7D); + break; + case CEIL: + writer.writeByte(0x7E); + break; + case FLOOR: + writer.writeByte(0x7F); + break; + case TRUNC: + writer.writeByte(0x80); + break; + case NEAREST: + writer.writeByte(0x81); + break; + case SQRT: + writer.writeByte(0x82); + break; + } + break; + case FLOAT64: + switch (expression.getOperation()) { + case ABS: + writer.writeByte(0x8F); + break; + case NEG: + writer.writeByte(0x90); + break; + case COPYSIGN: + writer.writeByte(0x91); + break; + case CEIL: + writer.writeByte(0x92); + break; + case FLOOR: + writer.writeByte(0x93); + break; + case TRUNC: + writer.writeByte(0x94); + break; + case NEAREST: + writer.writeByte(0x95); + break; + case SQRT: + writer.writeByte(0x96); + break; + } + break; + } + } + + @Override + public void visit(WasmConversion expression) { + expression.getOperand().acceptVisitor(this); + + switch (expression.getSourceType()) { + case INT32: + switch (expression.getTargetType()) { + case INT32: + break; + case INT64: + writer.writeByte(expression.isSigned() ? 0xA6 : 0xA7); + break; + case FLOAT32: + writer.writeByte(expression.isSigned() ? 0xA8 : 0xA9); + break; + case FLOAT64: + writer.writeByte(expression.isSigned() ? 0xAE : 0xAF); + break; + } + break; + case INT64: + switch (expression.getTargetType()) { + case INT32: + writer.writeByte(0xA1); + break; + case INT64: + break; + case FLOAT32: + writer.writeByte(expression.isSigned() ? 0xAA : 0xAB); + break; + case FLOAT64: + writer.writeByte(expression.isSigned() ? 0xB0 : 0xB1); + break; + } + break; + case FLOAT32: + switch (expression.getTargetType()) { + case INT32: + writer.writeByte(expression.isSigned() ? 0x9D : 0x9F); + break; + case INT64: + writer.writeByte(expression.isSigned() ? 0xA2 : 0xA4); + break; + case FLOAT32: + break; + case FLOAT64: + writer.writeByte(0xB2); + break; + } + break; + case FLOAT64: + switch (expression.getTargetType()) { + case INT32: + writer.writeByte(expression.isSigned() ? 0x9E : 0xA0); + break; + case INT64: + writer.writeByte(expression.isSigned() ? 0xA3 : 0xA5); + break; + case FLOAT32: + writer.writeByte(0xAC); + break; + case FLOAT64: + break; + } + break; + } + } + + @Override + public void visit(WasmCall expression) { + for (WasmExpression argument : expression.getArguments()) { + argument.acceptVisitor(this); + } + writer.writeByte(!expression.isImported() ? 0x16 : 0x18); + writer.writeLEB(expression.getArguments().size()); + writer.writeLEB(!expression.isImported() + ? functionIndexes.get(expression.getFunctionName()) + : importedIndexes.get(expression.getFunctionName())); + + } + + @Override + public void visit(WasmIndirectCall expression) { + expression.getSelector().acceptVisitor(this); + for (WasmExpression argument : expression.getArguments()) { + argument.acceptVisitor(this); + } + writer.writeByte(0x17); + writer.writeLEB(expression.getArguments().size()); + + WasmType[] signatureTypes = new WasmType[expression.getParameterTypes().size() + 1]; + signatureTypes[0] = expression.getReturnType(); + for (int i = 0; i < expression.getParameterTypes().size(); ++i) { + signatureTypes[i + 1] = expression.getParameterTypes().get(i); + } + writer.writeLEB(signatureIndexes.get(new WasmSignature(signatureTypes))); + } + + @Override + public void visit(WasmDrop expression) { + expression.getOperand().acceptVisitor(this); + } + + @Override + public void visit(WasmLoadInt32 expression) { + expression.getIndex().acceptVisitor(this); + switch (expression.getConvertFrom()) { + case INT8: + writer.writeByte(0x20); + break; + case UINT8: + writer.writeByte(0x21); + break; + case INT16: + writer.writeByte(0x22); + break; + case UINT16: + writer.writeByte(0x23); + break; + case INT32: + writer.writeByte(0x2A); + break; + } + writer.writeByte(getAlignment(expression.getAlignment())); + writer.writeByte(0); + } + + @Override + public void visit(WasmLoadInt64 expression) { + expression.getIndex().acceptVisitor(this); + switch (expression.getConvertFrom()) { + case INT8: + writer.writeByte(0x24); + break; + case UINT8: + writer.writeByte(0x25); + break; + case INT16: + writer.writeByte(0x26); + break; + case UINT16: + writer.writeByte(0x27); + break; + case INT32: + writer.writeByte(0x28); + break; + case UINT32: + writer.writeByte(0x29); + break; + case INT64: + writer.writeByte(0x2B); + break; + } + writer.writeByte(getAlignment(expression.getAlignment())); + writer.writeByte(0); + } + + @Override + public void visit(WasmLoadFloat32 expression) { + expression.getIndex().acceptVisitor(this); + writer.writeByte(0x2C); + writer.writeByte(getAlignment(expression.getAlignment())); + writer.writeByte(0); + } + + @Override + public void visit(WasmLoadFloat64 expression) { + expression.getIndex().acceptVisitor(this); + writer.writeByte(0x2D); + writer.writeByte(getAlignment(expression.getAlignment())); + writer.writeByte(0); + } + + @Override + public void visit(WasmStoreInt32 expression) { + expression.getIndex().acceptVisitor(this); + expression.getValue().acceptVisitor(this); + switch (expression.getConvertTo()) { + case INT8: + case UINT8: + writer.writeByte(0x2E); + break; + case INT16: + case UINT16: + writer.writeByte(0x2F); + break; + case INT32: + writer.writeByte(0x33); + break; + } + writer.writeByte(getAlignment(expression.getAlignment())); + writer.writeByte(0); + } + + @Override + public void visit(WasmStoreInt64 expression) { + expression.getIndex().acceptVisitor(this); + expression.getValue().acceptVisitor(this); + switch (expression.getConvertTo()) { + case INT8: + case UINT8: + writer.writeByte(0x30); + break; + case INT16: + case UINT16: + writer.writeByte(0x31); + break; + case INT32: + case UINT32: + writer.writeByte(0x32); + break; + case INT64: + writer.writeByte(0x34); + break; + } + writer.writeByte(getAlignment(expression.getAlignment())); + writer.writeByte(0); + } + + @Override + public void visit(WasmStoreFloat32 expression) { + expression.getIndex().acceptVisitor(this); + expression.getValue().acceptVisitor(this); + writer.writeByte(0x35); + writer.writeByte(getAlignment(expression.getAlignment())); + writer.writeByte(0); + } + + @Override + public void visit(WasmStoreFloat64 expression) { + expression.getIndex().acceptVisitor(this); + expression.getValue().acceptVisitor(this); + writer.writeByte(0x36); + writer.writeByte(getAlignment(expression.getAlignment())); + writer.writeByte(0); + } + + private int getAlignment(int alignment) { + return 31 - Integer.numberOfLeadingZeros(Math.min(1, alignment)); + } + + private void writeLabel(WasmBlock target) { + int blockDepth = blockDepths.get(target); + writer.writeLEB(depth - blockDepth); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryWriter.java b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryWriter.java new file mode 100644 index 000000000..8669a4f25 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmBinaryWriter.java @@ -0,0 +1,157 @@ +/* + * Copyright 2016 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.render; + +import java.util.Arrays; +import org.teavm.backend.wasm.model.WasmType; + +public class WasmBinaryWriter { + private byte[] data = new byte[1024]; + private int pointer; + + public void writeByte(int v) { + alloc(1); + data[pointer++] = (byte) v; + } + + public void writeType(WasmType type) { + switch (type) { + case INT32: + writeByte(1); + break; + case INT64: + writeByte(2); + break; + case FLOAT32: + writeByte(3); + break; + case FLOAT64: + writeByte(4); + break; + } + } + + public int getPosition() { + return pointer; + } + + public void writeBytes(byte[] bytes) { + alloc(bytes.length); + System.arraycopy(bytes, 0, data, pointer, bytes.length); + pointer += bytes.length; + } + + public void writeAsciiString(String str) { + writeLEB(str.length()); + byte[] bytes = new byte[str.length()]; + for (int i = 0; i < str.length(); ++i) { + bytes[i] = (byte) str.charAt(i); + } + writeBytes(bytes); + } + + public void writeInt32(int v) { + alloc(4); + for (int i = 0; i < 4; ++i) { + data[pointer++] = (byte) (v & 0xFF); + v >>>= 8; + } + } + + public void writeLEB(int v) { + alloc(5); + while (true) { + int digit = v & 0x7F; + int next = v >>> 7; + boolean last = next == 0; + if (!last) { + digit |= 0xFFFFFF80; + } + data[pointer++] = (byte) digit; + if (last) { + break; + } + v = next; + } + } + + public void writeSignedLEB(int v) { + alloc(5); + boolean negative = v < 0; + while (true) { + int digit = (!negative ? v : (v << 25 >> 25)) & 0x7F; + int next = v >>> 7; + boolean last = next == 0; + if (!last) { + digit |= 0xFFFFFF80; + } + data[pointer++] = (byte) digit; + if (last) { + break; + } + v = next; + } + } + + public void writeLEB(long v) { + alloc(10); + while (true) { + int digit = (int) (v & 0x7F); + long next = v >>> 7; + boolean last = next == 0; + if (!last) { + digit |= 0xFFFFFF80; + } + data[pointer++] = (byte) digit; + if (last) { + break; + } + v = next; + } + } + + public void writeSignedLEB(long v) { + alloc(10); + boolean negative = v < 0; + while (true) { + int digit = (int) ((!negative ? v : (v << 25 >> 25)) & 0x7F); + long next = v >>> 7; + boolean last = next == 0; + if (!last) { + digit |= 0xFFFFFF80; + } + data[pointer++] = (byte) digit; + if (last) { + break; + } + v = next; + } + } + + private void alloc(int size) { + if (data.length - pointer < size) { + int newLength = data.length * 2; + if (newLength < pointer + size) { + newLength = (pointer + size) * 2; + } + data = Arrays.copyOf(data, newLength); + } + } + + public byte[] getData() { + return Arrays.copyOf(data, pointer); + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/render/WasmRenderer.java b/core/src/main/java/org/teavm/backend/wasm/render/WasmRenderer.java index 926bf674e..327ecdb28 100644 --- a/core/src/main/java/org/teavm/backend/wasm/render/WasmRenderer.java +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmRenderer.java @@ -20,7 +20,6 @@ import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmLocal; import org.teavm.backend.wasm.model.WasmMemorySegment; import org.teavm.backend.wasm.model.WasmModule; -import org.teavm.backend.wasm.model.WasmType; import org.teavm.backend.wasm.model.expression.WasmExpression; public class WasmRenderer { @@ -143,23 +142,14 @@ public class WasmRenderer { } private void renderSignature(WasmFunction function) { - WasmSignature signature = signatureFromFunction(function); + WasmSignature signature = WasmSignature.fromFunction(function); visitor.append(" ").open().append("type $type" + visitor.getSignatureIndex(signature)).close(); } - private WasmSignature signatureFromFunction(WasmFunction function) { - WasmType[] types = new WasmType[function.getParameters().size() + 1]; - types[0] = function.getResult(); - for (int i = 0; i < function.getParameters().size(); ++i) { - types[i + 1] = function.getParameters().get(i); - } - return new WasmSignature(types); - } - private void renderTypes(WasmModule module) { - WasmSignatureCollector signatureCollector = new WasmSignatureCollector(visitor); + WasmSignatureCollector signatureCollector = new WasmSignatureCollector(visitor::getSignatureIndex); for (WasmFunction function : module.getFunctions().values()) { - visitor.getSignatureIndex(signatureFromFunction(function)); + visitor.getSignatureIndex(WasmSignature.fromFunction(function)); for (WasmExpression part : function.getBody()) { part.acceptVisitor(signatureCollector); } diff --git a/core/src/main/java/org/teavm/backend/wasm/render/WasmSignature.java b/core/src/main/java/org/teavm/backend/wasm/render/WasmSignature.java index 637615ab9..66cc96863 100644 --- a/core/src/main/java/org/teavm/backend/wasm/render/WasmSignature.java +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmSignature.java @@ -16,12 +16,13 @@ package org.teavm.backend.wasm.render; import java.util.Arrays; +import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmType; -class WasmSignature { +final class WasmSignature { WasmType[] types; - public WasmSignature(WasmType[] types) { + WasmSignature(WasmType[] types) { this.types = types; } @@ -41,4 +42,13 @@ class WasmSignature { public int hashCode() { return Arrays.hashCode(types); } + + public static WasmSignature fromFunction(WasmFunction function) { + WasmType[] types = new WasmType[function.getParameters().size() + 1]; + types[0] = function.getResult(); + for (int i = 0; i < function.getParameters().size(); ++i) { + types[i + 1] = function.getParameters().get(i); + } + return new WasmSignature(types); + } } diff --git a/core/src/main/java/org/teavm/backend/wasm/render/WasmSignatureCollector.java b/core/src/main/java/org/teavm/backend/wasm/render/WasmSignatureCollector.java index cc3c6212d..68352c1ab 100644 --- a/core/src/main/java/org/teavm/backend/wasm/render/WasmSignatureCollector.java +++ b/core/src/main/java/org/teavm/backend/wasm/render/WasmSignatureCollector.java @@ -15,15 +15,16 @@ */ package org.teavm.backend.wasm.render; +import java.util.function.Consumer; import org.teavm.backend.wasm.model.WasmType; import org.teavm.backend.wasm.model.expression.WasmDefaultExpressionVisitor; import org.teavm.backend.wasm.model.expression.WasmIndirectCall; class WasmSignatureCollector extends WasmDefaultExpressionVisitor { - WasmRenderingVisitor renderingVisitor; + private Consumer consumer; - public WasmSignatureCollector(WasmRenderingVisitor renderingVisitor) { - this.renderingVisitor = renderingVisitor; + public WasmSignatureCollector(Consumer consumer) { + this.consumer = consumer; } @Override @@ -34,6 +35,6 @@ class WasmSignatureCollector extends WasmDefaultExpressionVisitor { types[i + 1] = expression.getParameterTypes().get(i); } - renderingVisitor.getSignatureIndex(new WasmSignature(types)); + consumer.accept(new WasmSignature(types)); } }