From 0041130d003083451051386c18b041004b05dcb9 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Thu, 6 Oct 2016 15:49:58 +0300 Subject: [PATCH] WASM: support multianewarray, fix jbox2d benchmark for WebAssembly --- .../org/teavm/backend/wasm/WasmTarget.java | 7 +- .../wasm/generate/WasmGenerationVisitor.java | 31 +++++++- .../backend/wasm/generate/WasmGenerator.java | 7 +- ...ceptionHandlingShadowStackContributor.java | 5 +- .../lowlevel/GCShadowStackContributor.java | 2 + .../java/org/teavm/runtime/Allocator.java | 15 ++++ samples/benchmark/pom.xml | 4 ++ .../benchmark/teavm/WasmBenchmarkStarter.java | 8 +-- samples/benchmark/src/main/webapp/index.html | 1 + .../benchmark/src/main/webapp/teavm-wasm.js | 72 ++++++++++++++----- 10 files changed, 123 insertions(+), 29 deletions(-) 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 c480682ec..22b3954ae 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java @@ -225,6 +225,8 @@ public class WasmTarget implements TeaVMTarget { RuntimeClass.class, Address.class), null).use(); dependencyChecker.linkMethod(new MethodReference(Allocator.class, "allocateArray", RuntimeClass.class, int.class, Address.class), null).use(); + dependencyChecker.linkMethod(new MethodReference(Allocator.class, "allocateMultiArray", + RuntimeClass.class, Address.class, int.class, RuntimeArray.class), null).use(); dependencyChecker.linkMethod(new MethodReference(Allocator.class, "", void.class), null).use(); @@ -311,10 +313,9 @@ public class WasmTarget implements TeaVMTarget { classGenerator); context.addIntrinsic(exceptionHandlingIntrinsic); - WasmGenerator generator = new WasmGenerator(decompiler, classes, - context, classGenerator); + WasmGenerator generator = new WasmGenerator(decompiler, classes, context, classGenerator, binaryWriter); - module.setMemorySize(64); + module.setMemorySize(128); generateMethods(classes, context, generator, module); exceptionHandlingIntrinsic.postProcess(shadowStackTransformer.getCallSites()); generateIsSupertypeFunctions(tagRegistry, module, classGenerator); diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerationVisitor.java b/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerationVisitor.java index 034af0397..d9bcea9e7 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerationVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerationVisitor.java @@ -64,6 +64,8 @@ import org.teavm.ast.UnwrapArrayExpr; import org.teavm.ast.VariableExpr; import org.teavm.ast.WhileStatement; import org.teavm.backend.wasm.WasmRuntime; +import org.teavm.backend.wasm.binary.BinaryWriter; +import org.teavm.backend.wasm.binary.DataPrimitives; import org.teavm.backend.wasm.intrinsics.WasmIntrinsic; import org.teavm.backend.wasm.intrinsics.WasmIntrinsicManager; import org.teavm.backend.wasm.model.WasmFunction; @@ -129,12 +131,14 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { private Set usedBlocks = new HashSet<>(); private List> temporaryVariablesByType = new ArrayList<>(); private WasmLocal stackVariable; + private BinaryWriter binaryWriter; WasmExpression result; WasmGenerationVisitor(WasmGenerationContext context, WasmClassGenerator classGenerator, - WasmFunction function, int firstVariable) { + BinaryWriter binaryWriter, WasmFunction function, int firstVariable) { this.context = context; this.classGenerator = classGenerator; + this.binaryWriter = binaryWriter; this.function = function; this.firstVariable = firstVariable; int typeCount = WasmType.values().length; @@ -1128,6 +1132,31 @@ class WasmGenerationVisitor implements StatementVisitor, ExprVisitor { @Override public void visit(NewMultiArrayExpr expr) { + ValueType type = expr.getType(); + + WasmBlock block = new WasmBlock(false); + int dimensionList = -1; + for (Expr dimension : expr.getDimensions()) { + int dimensionAddress = binaryWriter.append(DataPrimitives.INT.createValue()); + if (dimensionList < 0) { + dimensionList = dimensionAddress; + } + accept(dimension); + block.getBody().add(new WasmStoreInt32(4, new WasmInt32Constant(dimensionAddress), result, + WasmInt32Subtype.INT32)); + } + + int classPointer = classGenerator.getClassPointer(ValueType.arrayOf(type)); + String allocName = WasmMangling.mangleMethod(new MethodReference(Allocator.class, "allocateMultiArray", + RuntimeClass.class, Address.class, int.class, RuntimeArray.class)); + WasmCall call = new WasmCall(allocName); + call.getArguments().add(new WasmInt32Constant(classPointer)); + call.getArguments().add(new WasmInt32Constant(dimensionList)); + call.getArguments().add(new WasmInt32Constant(expr.getDimensions().size())); + call.setLocation(expr.getLocation()); + + block.getBody().add(call); + result = block; } @Override diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerator.java index c6eb374e0..f805dd1f4 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerator.java @@ -18,6 +18,7 @@ package org.teavm.backend.wasm.generate; import org.teavm.ast.RegularMethodNode; import org.teavm.ast.VariableNode; import org.teavm.ast.decompilation.Decompiler; +import org.teavm.backend.wasm.binary.BinaryWriter; import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmLocal; import org.teavm.backend.wasm.model.WasmType; @@ -35,13 +36,15 @@ public class WasmGenerator { private ClassHolderSource classSource; private WasmGenerationContext context; private WasmClassGenerator classGenerator; + private BinaryWriter binaryWriter; public WasmGenerator(Decompiler decompiler, ClassHolderSource classSource, - WasmGenerationContext context, WasmClassGenerator classGenerator) { + WasmGenerationContext context, WasmClassGenerator classGenerator, BinaryWriter binaryWriter) { this.decompiler = decompiler; this.classSource = classSource; this.context = context; this.classGenerator = classGenerator; + this.binaryWriter = binaryWriter; } public WasmFunction generate(MethodReference methodReference, MethodHolder bodyMethod) { @@ -66,7 +69,7 @@ public class WasmGenerator { function.setResult(WasmGeneratorUtil.mapType(methodReference.getReturnType())); } - WasmGenerationVisitor visitor = new WasmGenerationVisitor(context, classGenerator, function, + WasmGenerationVisitor visitor = new WasmGenerationVisitor(context, classGenerator, binaryWriter, function, firstVariable); methodAst.getBody().acceptVisitor(visitor); function.getBody().add(visitor.result); diff --git a/core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlingShadowStackContributor.java b/core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlingShadowStackContributor.java index 720d384a3..0c3b9fe71 100644 --- a/core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlingShadowStackContributor.java +++ b/core/src/main/java/org/teavm/model/lowlevel/ExceptionHandlingShadowStackContributor.java @@ -37,6 +37,7 @@ import org.teavm.model.instructions.BinaryBranchingInstruction; import org.teavm.model.instructions.CloneArrayInstruction; import org.teavm.model.instructions.ConstructArrayInstruction; import org.teavm.model.instructions.ConstructInstruction; +import org.teavm.model.instructions.ConstructMultiArrayInstruction; import org.teavm.model.instructions.DoubleConstantInstruction; import org.teavm.model.instructions.ExitInstruction; import org.teavm.model.instructions.FloatConstantInstruction; @@ -209,8 +210,8 @@ public class ExceptionHandlingShadowStackContributor { private boolean isCallInstruction(Instruction insn) { if (insn instanceof InitClassInstruction || insn instanceof ConstructInstruction - || insn instanceof ConstructArrayInstruction || insn instanceof CloneArrayInstruction - || insn instanceof RaiseInstruction) { + || insn instanceof ConstructArrayInstruction || insn instanceof ConstructMultiArrayInstruction + || insn instanceof CloneArrayInstruction || insn instanceof RaiseInstruction) { return true; } else if (insn instanceof InvokeInstruction) { return managedMethodRepository.isManaged(((InvokeInstruction) insn).getMethod()); diff --git a/core/src/main/java/org/teavm/model/lowlevel/GCShadowStackContributor.java b/core/src/main/java/org/teavm/model/lowlevel/GCShadowStackContributor.java index 8314abf21..8995e7008 100644 --- a/core/src/main/java/org/teavm/model/lowlevel/GCShadowStackContributor.java +++ b/core/src/main/java/org/teavm/model/lowlevel/GCShadowStackContributor.java @@ -41,6 +41,7 @@ import org.teavm.model.Variable; import org.teavm.model.instructions.CloneArrayInstruction; import org.teavm.model.instructions.ConstructArrayInstruction; import org.teavm.model.instructions.ConstructInstruction; +import org.teavm.model.instructions.ConstructMultiArrayInstruction; import org.teavm.model.instructions.InitClassInstruction; import org.teavm.model.instructions.IntegerConstantInstruction; import org.teavm.model.instructions.InvocationType; @@ -149,6 +150,7 @@ public class GCShadowStackContributor { } if (insn instanceof InvokeInstruction || insn instanceof InitClassInstruction || insn instanceof ConstructInstruction || insn instanceof ConstructArrayInstruction + || insn instanceof ConstructMultiArrayInstruction || insn instanceof CloneArrayInstruction || insn instanceof RaiseInstruction) { if (insn instanceof InvokeInstruction && !managedMethodRepository.isManaged(((InvokeInstruction) insn).getMethod())) { diff --git a/core/src/main/java/org/teavm/runtime/Allocator.java b/core/src/main/java/org/teavm/runtime/Allocator.java index 3f53b8b9a..353014a5b 100644 --- a/core/src/main/java/org/teavm/runtime/Allocator.java +++ b/core/src/main/java/org/teavm/runtime/Allocator.java @@ -47,6 +47,21 @@ public final class Allocator { return result; } + public static RuntimeArray allocateMultiArray(RuntimeClass tag, Address dimensions, int dimensionCount) { + int size = dimensions.getInt(); + RuntimeArray array = allocateArray(tag, dimensions.getInt()).toStructure(); + if (dimensionCount > 1) { + Address arrayData = Structure.add(RuntimeArray.class, array, 1).toAddress(); + arrayData = Address.align(arrayData, Address.sizeOf()); + for (int i = 0; i < size; ++i) { + RuntimeArray innerArray = allocateMultiArray(tag.itemType, dimensions.add(4), dimensionCount - 1); + arrayData.putAddress(innerArray.toAddress()); + arrayData = arrayData.add(Address.sizeOf()); + } + } + return array; + } + @Unmanaged public static native void fillZero(Address address, int count); diff --git a/samples/benchmark/pom.xml b/samples/benchmark/pom.xml index ff924dae5..c6d0cc3a2 100644 --- a/samples/benchmark/pom.xml +++ b/samples/benchmark/pom.xml @@ -103,6 +103,9 @@ ${project.build.directory}/generated/js + + ${project.build.directory}/generated/wasm + @@ -126,6 +129,7 @@ **/gwt/* **/teavm/* + **/benchmark/jvm/* diff --git a/samples/benchmark/src/main/java/org/teavm/samples/benchmark/teavm/WasmBenchmarkStarter.java b/samples/benchmark/src/main/java/org/teavm/samples/benchmark/teavm/WasmBenchmarkStarter.java index dc1764a52..53f86c0b6 100644 --- a/samples/benchmark/src/main/java/org/teavm/samples/benchmark/teavm/WasmBenchmarkStarter.java +++ b/samples/benchmark/src/main/java/org/teavm/samples/benchmark/teavm/WasmBenchmarkStarter.java @@ -30,7 +30,7 @@ public final class WasmBenchmarkStarter { private static Scene scene = new Scene(); private static int currentSecond; private static long startMillisecond; - private static int timeSpentCalculating; + private static double timeSpentCalculating; private WasmBenchmarkStarter() { } @@ -43,17 +43,15 @@ public final class WasmBenchmarkStarter { @Export(name = "tick") public static void tick() { double start = performanceTime(); - System.out.println("About to calculate frame"); scene.calculate(); - System.out.println("Frame calculated successfully"); double end = performanceTime(); int second = (int) ((System.currentTimeMillis() - startMillisecond) / 1000); if (second > currentSecond) { - reportPerformance(second, timeSpentCalculating); + reportPerformance(second, (int) timeSpentCalculating); timeSpentCalculating = 0; currentSecond = second; } - timeSpentCalculating += (int) (end - start); + timeSpentCalculating += end - start; render(); repeatAfter(scene.timeUntilNextStep()); } diff --git a/samples/benchmark/src/main/webapp/index.html b/samples/benchmark/src/main/webapp/index.html index f5f789deb..52e39734b 100644 --- a/samples/benchmark/src/main/webapp/index.html +++ b/samples/benchmark/src/main/webapp/index.html @@ -25,6 +25,7 @@
  • TeaVM
  • GWT
  • Bck2Brwsr VM
  • +
  • TeaVM (experimental WebAssembly backend)
  • \ No newline at end of file diff --git a/samples/benchmark/src/main/webapp/teavm-wasm.js b/samples/benchmark/src/main/webapp/teavm-wasm.js index 72838e620..329e39bbe 100644 --- a/samples/benchmark/src/main/webapp/teavm-wasm.js +++ b/samples/benchmark/src/main/webapp/teavm-wasm.js @@ -19,17 +19,17 @@ var Benchmark = function() { this.canvas = canvas; this.module = null; this.line = ""; + this.resultTableBody = document.getElementById("result-table-body"); } Benchmark.prototype.runAll = function() { load(this, function() { this.module.exports.main(); }.bind(this)); - } + }; function tick(benchmark) { var exports = benchmark.module.exports; exports.tick(); - console.log("tick"); var exception = exports.sys$catchException(); - if (exception != null) { + if (exception !== 0) { console.log("Exception: " + exception); } } @@ -49,41 +49,79 @@ var Benchmark = function() { function load(benchmark, callback) { var xhr = new XMLHttpRequest(); + xhr.responseType = "arraybuffer"; xhr.open("GET", "teavm-wasm/classes.wasm"); - xhr.onreadystatechange = function() { + xhr.onload = function() { var response = xhr.response; if (!response) { return; } + var canvas = benchmark.canvas; var importObj = { runtime: { currentTimeMillis: currentTimeMillis, isNaN: isNaN, isFinite: isFinite, getNaN: function() { return NaN; }, - putchar: function() { putchar(benchmark); } + putchar: function(code) { putchar(benchmark, code); } }, benchmark: { performanceTime: function() { return window.performance.now() || 0; }, reportPerformance: function(second, timeSpentComputing) { - console.log("Second: " + second + ", time: " + timeSpentComputing); + var row = document.createElement("tr"); + benchmark.resultTableBody.appendChild(row); + var secondCell = document.createElement("td"); + row.appendChild(secondCell); + secondCell.appendChild(document.createTextNode(second.toString())); + var timeCell = document.createElement("td"); + row.appendChild(timeCell); + timeCell.appendChild(document.createTextNode(timeSpentComputing.toString())); }, repeatAfter: function(time) { - console.log("repeatAfter"); - setTimeout(tick.bind(benchmark), time); + setTimeout(tick.bind(null, benchmark), time); }, setupCanvas: function() { - var canvas = benchmark.canvas; - canvas.setFillStyle("white"); - context.setStrokeStyle("grey"); + canvas.fillStyle = "white"; + canvas.strokeStyle = "grey"; canvas.fillRect(0, 0, 600, 600); canvas.translate(0, 600); canvas.scale(1, -1); canvas.scale(100, 100); - canvas.setLineWidth(0.01); + canvas.lineWidth = 0.01; + } + }, + canvas: { + save: function() { + canvas.save(); + }, + restore: function() { + canvas.restore(); + }, + beginPath: function() { + canvas.beginPath(); + }, + closePath: function() { + canvas.closePath(); + }, + stroke: function() { + canvas.stroke(); + }, + moveTo: function(x, y) { + canvas.moveTo(x, y); + }, + lineTo: function(x, y) { + canvas.lineTo(x, y); + }, + translate: function(x, y) { + canvas.translate(x, y); + }, + rotate: function(angle) { + canvas.rotate(angle); + }, + arc: function(cx, cy, radius, startAngle, endAngle, counterClockwise) { + canvas.arc(cx, cy, radius, startAngle, endAngle, counterClockwise); } }, - canvas: benchmark.canvas, math: Math, debug: { traceMemoryAccess: function(callSite, address) { @@ -94,11 +132,13 @@ var Benchmark = function() { } } }; - benchmark.module = Wasm.instantiateModule(new Uint8Array(response), importObj) - callback(); + WebAssembly.compile(response).then(function(module) { + benchmark.module = new WebAssembly.Instance(module, importObj); + callback(); + }); }; xhr.send(); } return Benchmark; -}(); +}(); \ No newline at end of file