From 73edc0cf6e9e50ad071a97a2ffc25057c61c50a9 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Mon, 12 Aug 2024 20:28:11 +0200 Subject: [PATCH] wasm gc: support passing arguments to main method --- .../backend/wasm/WasmGCModuleGenerator.java | 94 ++++++++++++++++--- .../org/teavm/backend/wasm/WasmGCTarget.java | 21 ++++- .../backend/wasm/gc/WasmGCDependencies.java | 18 ++++ .../methods/BaseWasmGenerationVisitor.java | 5 + .../gc/methods/WasmGCGenerationContext.java | 10 +- .../gc/methods/WasmGCGenerationVisitor.java | 21 +++-- .../gc/methods/WasmGCMethodGenerator.java | 3 +- .../backend/wasm/runtime/WasmGCSupport.java | 12 +++ .../org/teavm/backend/wasm/wasm-gc-runtime.js | 86 +++++++++++++++++ 9 files changed, 248 insertions(+), 22 deletions(-) create mode 100644 core/src/main/resources/org/teavm/backend/wasm/wasm-gc-runtime.js diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmGCModuleGenerator.java b/core/src/main/java/org/teavm/backend/wasm/WasmGCModuleGenerator.java index bcffa3be8..59f4ab05f 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmGCModuleGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmGCModuleGenerator.java @@ -15,20 +15,21 @@ */ package org.teavm.backend.wasm; -import org.teavm.backend.wasm.generate.TemporaryVariablePool; import org.teavm.backend.wasm.generate.gc.WasmGCDeclarationsGenerator; -import org.teavm.backend.wasm.generate.gc.methods.WasmGCGenerationUtil; import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmGlobal; +import org.teavm.backend.wasm.model.WasmLocal; +import org.teavm.backend.wasm.model.WasmType; import org.teavm.backend.wasm.model.expression.WasmCall; import org.teavm.backend.wasm.model.expression.WasmCallReference; +import org.teavm.backend.wasm.model.expression.WasmDrop; import org.teavm.backend.wasm.model.expression.WasmExpression; import org.teavm.backend.wasm.model.expression.WasmFunctionReference; import org.teavm.backend.wasm.model.expression.WasmGetGlobal; import org.teavm.backend.wasm.model.expression.WasmGetLocal; -import org.teavm.backend.wasm.model.expression.WasmInt32Constant; import org.teavm.backend.wasm.model.expression.WasmReturn; import org.teavm.backend.wasm.model.expression.WasmSetGlobal; +import org.teavm.backend.wasm.runtime.WasmGCSupport; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; @@ -56,25 +57,92 @@ public class WasmGCModuleGenerator { public WasmFunction generateMainFunction(String entryPoint) { var mainFunction = declarationsGenerator.functions().forStaticMethod(new MethodReference(entryPoint, "main", ValueType.parse(String[].class), ValueType.VOID)); - var mainFunctionCaller = new WasmFunction(declarationsGenerator.functionTypes.of(null)); + var stringArrayType = declarationsGenerator.typeMapper() + .mapType(ValueType.parse(String[].class)); + var mainFunctionCaller = new WasmFunction(declarationsGenerator.functionTypes.of(null, stringArrayType)); + var argsLocal = new WasmLocal(stringArrayType, "args"); declarationsGenerator.module.functions.add(mainFunctionCaller); mainFunctionCaller.getBody().add(callInitializer()); - var tempVars = new TemporaryVariablePool(mainFunctionCaller); - var genUtil = new WasmGCGenerationUtil(declarationsGenerator.classInfoProvider(), tempVars); - var stringArrayType = declarationsGenerator.typeMapper() - .mapType(ValueType.parse(String[].class)); - var arrayVar = tempVars.acquire(stringArrayType); - genUtil.allocateArray(ValueType.parse(String.class), new WasmInt32Constant(0), null, - arrayVar, mainFunctionCaller.getBody()); - var callToMainFunction = new WasmCall(mainFunction, new WasmGetLocal(arrayVar)); + var callToMainFunction = new WasmCall(mainFunction, new WasmGetLocal(argsLocal)); mainFunctionCaller.getBody().add(callToMainFunction); mainFunctionCaller.getBody().add(new WasmReturn()); - tempVars.release(arrayVar); return mainFunctionCaller; } + public WasmFunction generateCreateStringBuilderFunction() { + var function = declarationsGenerator.functions().forStaticMethod(new MethodReference( + WasmGCSupport.class, "createStringBuilder", StringBuilder.class)); + var caller = new WasmFunction(function.getType()); + caller.getBody().add(callInitializer()); + caller.getBody().add(new WasmReturn(new WasmCall(function))); + declarationsGenerator.module.functions.add(caller); + return caller; + } + + public WasmFunction generateCreateStringArrayFunction() { + var function = declarationsGenerator.functions().forStaticMethod(new MethodReference( + WasmGCSupport.class, "createStringArray", int.class, String[].class)); + var caller = new WasmFunction(function.getType()); + var sizeLocal = new WasmLocal(WasmType.INT32); + caller.add(sizeLocal); + caller.getBody().add(callInitializer()); + caller.getBody().add(new WasmReturn(new WasmCall(function, new WasmGetLocal(sizeLocal)))); + declarationsGenerator.module.functions.add(caller); + return caller; + } + + public WasmFunction generateAppendCharFunction() { + var function = declarationsGenerator.functions().forInstanceMethod(new MethodReference( + StringBuilder.class, "append", char.class, StringBuilder.class)); + var stringBuilderType = declarationsGenerator.typeMapper().mapType(ValueType.parse(StringBuilder.class)); + var caller = new WasmFunction(declarationsGenerator.functionTypes.of(null, stringBuilderType, WasmType.INT32)); + var stringBuilderLocal = new WasmLocal(stringBuilderType); + var codeLocal = new WasmLocal(WasmType.INT32); + caller.add(stringBuilderLocal); + caller.add(codeLocal); + caller.getBody().add(callInitializer()); + caller.getBody().add(new WasmDrop(new WasmCall(function, new WasmGetLocal(stringBuilderLocal), + new WasmGetLocal(codeLocal)))); + caller.getBody().add(new WasmReturn()); + declarationsGenerator.module.functions.add(caller); + return caller; + } + + public WasmFunction generateBuildStringFunction() { + var function = declarationsGenerator.functions().forInstanceMethod(new MethodReference( + StringBuilder.class, "toString", String.class)); + var stringBuilderType = declarationsGenerator.typeMapper().mapType(ValueType.parse(StringBuilder.class)); + var stringType = declarationsGenerator.typeMapper().mapType(ValueType.parse(String.class)); + var caller = new WasmFunction(declarationsGenerator.functionTypes.of(stringType, stringBuilderType)); + var stringBuilderLocal = new WasmLocal(stringBuilderType); + caller.add(stringBuilderLocal); + caller.getBody().add(callInitializer()); + caller.getBody().add(new WasmReturn(new WasmCall(function, new WasmGetLocal(stringBuilderLocal)))); + declarationsGenerator.module.functions.add(caller); + return caller; + } + + public WasmFunction generateSetToStringArrayFunction() { + var function = declarationsGenerator.functions().forStaticMethod(new MethodReference( + WasmGCSupport.class, "setToStringArray", String[].class, int.class, String.class, void.class)); + var stringArrayType = declarationsGenerator.typeMapper().mapType(ValueType.parse(String[].class)); + var stringType = declarationsGenerator.typeMapper().mapType(ValueType.parse(String.class)); + var caller = new WasmFunction(function.getType()); + var arrayLocal = new WasmLocal(stringArrayType); + var indexLocal = new WasmLocal(WasmType.INT32); + var valueLocal = new WasmLocal(stringType); + caller.add(arrayLocal); + caller.add(indexLocal); + caller.add(valueLocal); + caller.getBody().add(callInitializer()); + caller.getBody().add(new WasmReturn(new WasmCall(function, new WasmGetLocal(arrayLocal), + new WasmGetLocal(indexLocal), new WasmGetLocal(valueLocal)))); + declarationsGenerator.module.functions.add(caller); + return caller; + } + private void createInitializer() { if (initializer != null) { return; diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java index 60bdd532d..3b32c7b36 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java @@ -75,7 +75,9 @@ public class WasmGCTarget implements TeaVMTarget { @Override public void contributeDependencies(DependencyAnalyzer dependencyAnalyzer) { - new WasmGCDependencies(dependencyAnalyzer).contribute(); + var deps = new WasmGCDependencies(dependencyAnalyzer); + deps.contribute(); + deps.contributeStandardExports(); } @Override @@ -122,9 +124,26 @@ public class WasmGCTarget implements TeaVMTarget { ); declarationsGenerator.setFriendlyToDebugger(controller.isFriendlyToDebugger()); var moduleGenerator = new WasmGCModuleGenerator(declarationsGenerator); + var mainFunction = moduleGenerator.generateMainFunction(controller.getEntryPoint()); mainFunction.setExportName(controller.getEntryPointName()); mainFunction.setName(controller.getEntryPointName()); + + var stringBuilderFunction = moduleGenerator.generateCreateStringBuilderFunction(); + stringBuilderFunction.setExportName("createStringBuilder"); + + var createStringArrayFunction = moduleGenerator.generateCreateStringArrayFunction(); + createStringArrayFunction.setExportName("createStringArray"); + + var appendCharFunction = moduleGenerator.generateAppendCharFunction(); + appendCharFunction.setExportName("appendChar"); + + var buildStringFunction = moduleGenerator.generateBuildStringFunction(); + buildStringFunction.setExportName("buildString"); + + var setArrayFunction = moduleGenerator.generateSetToStringArrayFunction(); + setArrayFunction.setExportName("setToStringArray"); + moduleGenerator.generate(); adjustModuleMemory(module); diff --git a/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java b/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java index d2fecb611..c70c76794 100644 --- a/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java +++ b/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java @@ -34,6 +34,24 @@ public class WasmGCDependencies { contributeInitializerUtils(); } + public void contributeStandardExports() { + analyzer.linkMethod(new MethodReference(WasmGCSupport.class, "createStringArray", int.class, String[].class)) + .use(); + analyzer.linkMethod(new MethodReference(WasmGCSupport.class, "createStringBuilder", StringBuilder.class)) + .use(); + analyzer.linkMethod(new MethodReference(WasmGCSupport.class, "setToStringArray", String[].class, + int.class, String.class, void.class)) + .propagate(1, analyzer.getType("[java/lang/String;")) + .propagate(3, analyzer.getType("java.lang.String")) + .use(); + analyzer.linkMethod(new MethodReference(StringBuilder.class, "append", char.class, StringBuilder.class)) + .propagate(0, analyzer.getType("java.lang.StringBuilder")) + .use(); + analyzer.linkMethod(new MethodReference(StringBuilder.class, "toString", String.class)) + .propagate(0, analyzer.getType("java.lang.StringBuilder")) + .use(); + } + private void contributeMathUtils() { for (var type : Arrays.asList(int.class, long.class, float.class, double.class)) { var method = new MethodReference(WasmRuntime.class, "compare", type, type, int.class); diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/common/methods/BaseWasmGenerationVisitor.java b/core/src/main/java/org/teavm/backend/wasm/generate/common/methods/BaseWasmGenerationVisitor.java index 4c3f94b0b..780f2d2a9 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/common/methods/BaseWasmGenerationVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/common/methods/BaseWasmGenerationVisitor.java @@ -1105,6 +1105,7 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp public void visit(ReturnStatement statement) { if (statement.getResult() != null) { acceptWithType(statement.getResult(), currentMethod.getReturnType()); + result = forceType(result, currentMethod.getReturnType()); } else { result = null; } @@ -1113,6 +1114,10 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp resultConsumer.add(wasmStatement); } + protected WasmExpression forceType(WasmExpression expression, ValueType type) { + return expression; + } + @Override public void visit(InstanceOfExpr expr) { acceptWithType(expr.getExpr(), expr.getType()); diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationContext.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationContext.java index 4a243a90f..8d0d6e1d2 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationContext.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationContext.java @@ -17,6 +17,7 @@ package org.teavm.backend.wasm.generate.gc.methods; import org.teavm.backend.wasm.BaseWasmFunctionRepository; import org.teavm.backend.wasm.WasmFunctionTypes; +import org.teavm.backend.wasm.gc.WasmGCMethodReturnTypes; import org.teavm.backend.wasm.generate.common.methods.BaseWasmGenerationContext; import org.teavm.backend.wasm.generate.gc.classes.WasmGCClassInfoProvider; import org.teavm.backend.wasm.generate.gc.classes.WasmGCStandardClasses; @@ -48,6 +49,7 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext { private WasmGCSupertypeFunctionProvider supertypeFunctions; private WasmGCCustomGeneratorProvider customGenerators; private WasmGCIntrinsicProvider intrinsics; + private WasmGCMethodReturnTypes returnTypes; private WasmFunction npeMethod; private WasmFunction aaiobeMethod; private WasmFunction cceMethod; @@ -59,7 +61,8 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext { ClassHierarchy hierarchy, BaseWasmFunctionRepository functions, WasmGCSupertypeFunctionProvider supertypeFunctions, WasmGCClassInfoProvider classInfoProvider, WasmGCStandardClasses standardClasses, WasmGCStringProvider strings, - WasmGCCustomGeneratorProvider customGenerators, WasmGCIntrinsicProvider intrinsics) { + WasmGCCustomGeneratorProvider customGenerators, WasmGCIntrinsicProvider intrinsics, + WasmGCMethodReturnTypes returnTypes) { this.module = module; this.virtualTables = virtualTables; this.typeMapper = typeMapper; @@ -73,6 +76,7 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext { this.strings = strings; this.customGenerators = customGenerators; this.intrinsics = intrinsics; + this.returnTypes = returnTypes; } public WasmGCClassInfoProvider classInfoProvider() { @@ -172,4 +176,8 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext { public WasmGCIntrinsicProvider intrinsics() { return intrinsics; } + + public WasmGCMethodReturnTypes returnTypes() { + return returnTypes; + } } diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java index 8861da818..30a006670 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java @@ -408,6 +408,7 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor { result = invocation(expr, null, false); } + @Override protected WasmExpression invocation(InvocationExpr expr, List resultConsumer, boolean willDrop) { if (expr.getType() == InvocationType.SPECIAL || expr.getType() == InvocationType.STATIC) { @@ -435,26 +436,34 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor { @Override protected WasmExpression mapFirstArgumentForCall(WasmExpression argument, WasmFunction function, MethodReference method) { - argument.acceptVisitor(typeInference); + return forceType(argument, function.getType().getParameterTypes().get(0)); + } + + @Override + protected WasmExpression forceType(WasmExpression expression, ValueType type) { + return forceType(expression, mapType(context.returnTypes().returnTypeOf(currentMethod))); + } + + private WasmExpression forceType(WasmExpression expression, WasmType expectedType) { + expression.acceptVisitor(typeInference); var actualType = typeInference.getResult(); - var expectedType = function.getType().getParameterTypes().get(0); if (actualType == expectedType || !(actualType instanceof WasmType.CompositeReference) || !(expectedType instanceof WasmType.CompositeReference)) { - return argument; + return expression; } var actualComposite = ((WasmType.CompositeReference) actualType).composite; var expectedComposite = ((WasmType.CompositeReference) expectedType).composite; if (!(actualComposite instanceof WasmStructure) || !(expectedComposite instanceof WasmStructure)) { - return argument; + return expression; } var actualStruct = (WasmStructure) actualComposite; var expectedStruct = (WasmStructure) expectedComposite; if (!actualStruct.isSupertypeOf(expectedStruct)) { - return argument; + return expression; } - return new WasmCast(argument, expectedComposite.getReference()); + return new WasmCast(expression, expectedComposite.getReference()); } @Override diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCMethodGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCMethodGenerator.java index ccf9b92f4..32aabdd1e 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCMethodGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCMethodGenerator.java @@ -302,7 +302,8 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { standardClasses, strings, customGenerators, - intrinsics + intrinsics, + returnTypes ); } return context; diff --git a/core/src/main/java/org/teavm/backend/wasm/runtime/WasmGCSupport.java b/core/src/main/java/org/teavm/backend/wasm/runtime/WasmGCSupport.java index 47e601cfe..3753cdab3 100644 --- a/core/src/main/java/org/teavm/backend/wasm/runtime/WasmGCSupport.java +++ b/core/src/main/java/org/teavm/backend/wasm/runtime/WasmGCSupport.java @@ -85,4 +85,16 @@ public class WasmGCSupport { private static native byte nextByte(); private static native void error(); + + public static StringBuilder createStringBuilder() { + return new StringBuilder(); + } + + public static String[] createStringArray(int size) { + return new String[size]; + } + + public static void setToStringArray(String[] array, int index, String value) { + array[index] = value; + } } diff --git a/core/src/main/resources/org/teavm/backend/wasm/wasm-gc-runtime.js b/core/src/main/resources/org/teavm/backend/wasm/wasm-gc-runtime.js new file mode 100644 index 000000000..3ad3c2047 --- /dev/null +++ b/core/src/main/resources/org/teavm/backend/wasm/wasm-gc-runtime.js @@ -0,0 +1,86 @@ +/* + * 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. + */ + +var TeaVM = TeaVM || {}; +TeaVM.wasm = function() { + function defaults(imports) { + let stderr = ""; + let stdout = ""; + imports.teavm = { + putcharStderr(c) { + if (c === 10) { + console.error(stderr); + stderr = ""; + } else { + stderr += String.fromCharCode(c); + } + }, + putcharStdout(c) { + if (c === 10) { + console.log(stdout); + stdout = ""; + } else { + stdout += String.fromCharCode(c); + } + } + }; + } + + function load(path, options) { + if (!options) { + options = {}; + } + + const importObj = {}; + defaults(importObj); + if (typeof options.installImports !== "undefined") { + options.installImports(importObj); + } + + return WebAssembly.instantiateStreaming(fetch(path), importObj).then((obj => { + let teavm = {}; + teavm.main = createMain(obj.instance); + return teavm; + })); + } + + function createMain(instance) { + return args => { + if (typeof args === "undefined") { + args = []; + } + return new Promise((resolve, reject) => { + let exports = instance.exports; + let javaArgs = exports.createStringArray(args.length); + for (let i = 0; i < args.length; ++i) { + let arg = args[i]; + let javaArg = exports.createStringBuilder(); + for (let j = 0; j < arg.length; ++j) { + exports.appendChar(javaArg, arg.charCodeAt(j)); + } + exports.setToStringArray(javaArgs, i, exports.buildString(javaArg)); + } + try { + exports.main(javaArgs); + } catch (e) { + reject(e); + } + }); + } + } + + return { load }; +}();