wasm gc: support passing arguments to main method

This commit is contained in:
Alexey Andreev 2024-08-12 20:28:11 +02:00
parent e61301576b
commit 73edc0cf6e
9 changed files with 248 additions and 22 deletions

View File

@ -15,20 +15,21 @@
*/ */
package org.teavm.backend.wasm; 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.WasmGCDeclarationsGenerator;
import org.teavm.backend.wasm.generate.gc.methods.WasmGCGenerationUtil;
import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmFunction;
import org.teavm.backend.wasm.model.WasmGlobal; 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.WasmCall;
import org.teavm.backend.wasm.model.expression.WasmCallReference; 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.WasmExpression;
import org.teavm.backend.wasm.model.expression.WasmFunctionReference; import org.teavm.backend.wasm.model.expression.WasmFunctionReference;
import org.teavm.backend.wasm.model.expression.WasmGetGlobal; import org.teavm.backend.wasm.model.expression.WasmGetGlobal;
import org.teavm.backend.wasm.model.expression.WasmGetLocal; 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.WasmReturn;
import org.teavm.backend.wasm.model.expression.WasmSetGlobal; import org.teavm.backend.wasm.model.expression.WasmSetGlobal;
import org.teavm.backend.wasm.runtime.WasmGCSupport;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.model.ValueType; import org.teavm.model.ValueType;
@ -56,25 +57,92 @@ public class WasmGCModuleGenerator {
public WasmFunction generateMainFunction(String entryPoint) { public WasmFunction generateMainFunction(String entryPoint) {
var mainFunction = declarationsGenerator.functions().forStaticMethod(new MethodReference(entryPoint, var mainFunction = declarationsGenerator.functions().forStaticMethod(new MethodReference(entryPoint,
"main", ValueType.parse(String[].class), ValueType.VOID)); "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); declarationsGenerator.module.functions.add(mainFunctionCaller);
mainFunctionCaller.getBody().add(callInitializer()); mainFunctionCaller.getBody().add(callInitializer());
var tempVars = new TemporaryVariablePool(mainFunctionCaller); var callToMainFunction = new WasmCall(mainFunction, new WasmGetLocal(argsLocal));
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));
mainFunctionCaller.getBody().add(callToMainFunction); mainFunctionCaller.getBody().add(callToMainFunction);
mainFunctionCaller.getBody().add(new WasmReturn()); mainFunctionCaller.getBody().add(new WasmReturn());
tempVars.release(arrayVar);
return mainFunctionCaller; 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() { private void createInitializer() {
if (initializer != null) { if (initializer != null) {
return; return;

View File

@ -75,7 +75,9 @@ public class WasmGCTarget implements TeaVMTarget {
@Override @Override
public void contributeDependencies(DependencyAnalyzer dependencyAnalyzer) { public void contributeDependencies(DependencyAnalyzer dependencyAnalyzer) {
new WasmGCDependencies(dependencyAnalyzer).contribute(); var deps = new WasmGCDependencies(dependencyAnalyzer);
deps.contribute();
deps.contributeStandardExports();
} }
@Override @Override
@ -122,9 +124,26 @@ public class WasmGCTarget implements TeaVMTarget {
); );
declarationsGenerator.setFriendlyToDebugger(controller.isFriendlyToDebugger()); declarationsGenerator.setFriendlyToDebugger(controller.isFriendlyToDebugger());
var moduleGenerator = new WasmGCModuleGenerator(declarationsGenerator); var moduleGenerator = new WasmGCModuleGenerator(declarationsGenerator);
var mainFunction = moduleGenerator.generateMainFunction(controller.getEntryPoint()); var mainFunction = moduleGenerator.generateMainFunction(controller.getEntryPoint());
mainFunction.setExportName(controller.getEntryPointName()); mainFunction.setExportName(controller.getEntryPointName());
mainFunction.setName(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(); moduleGenerator.generate();
adjustModuleMemory(module); adjustModuleMemory(module);

View File

@ -34,6 +34,24 @@ public class WasmGCDependencies {
contributeInitializerUtils(); 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() { private void contributeMathUtils() {
for (var type : Arrays.asList(int.class, long.class, float.class, double.class)) { for (var type : Arrays.asList(int.class, long.class, float.class, double.class)) {
var method = new MethodReference(WasmRuntime.class, "compare", type, type, int.class); var method = new MethodReference(WasmRuntime.class, "compare", type, type, int.class);

View File

@ -1105,6 +1105,7 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp
public void visit(ReturnStatement statement) { public void visit(ReturnStatement statement) {
if (statement.getResult() != null) { if (statement.getResult() != null) {
acceptWithType(statement.getResult(), currentMethod.getReturnType()); acceptWithType(statement.getResult(), currentMethod.getReturnType());
result = forceType(result, currentMethod.getReturnType());
} else { } else {
result = null; result = null;
} }
@ -1113,6 +1114,10 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp
resultConsumer.add(wasmStatement); resultConsumer.add(wasmStatement);
} }
protected WasmExpression forceType(WasmExpression expression, ValueType type) {
return expression;
}
@Override @Override
public void visit(InstanceOfExpr expr) { public void visit(InstanceOfExpr expr) {
acceptWithType(expr.getExpr(), expr.getType()); acceptWithType(expr.getExpr(), expr.getType());

View File

@ -17,6 +17,7 @@ package org.teavm.backend.wasm.generate.gc.methods;
import org.teavm.backend.wasm.BaseWasmFunctionRepository; import org.teavm.backend.wasm.BaseWasmFunctionRepository;
import org.teavm.backend.wasm.WasmFunctionTypes; 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.common.methods.BaseWasmGenerationContext;
import org.teavm.backend.wasm.generate.gc.classes.WasmGCClassInfoProvider; import org.teavm.backend.wasm.generate.gc.classes.WasmGCClassInfoProvider;
import org.teavm.backend.wasm.generate.gc.classes.WasmGCStandardClasses; import org.teavm.backend.wasm.generate.gc.classes.WasmGCStandardClasses;
@ -48,6 +49,7 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext {
private WasmGCSupertypeFunctionProvider supertypeFunctions; private WasmGCSupertypeFunctionProvider supertypeFunctions;
private WasmGCCustomGeneratorProvider customGenerators; private WasmGCCustomGeneratorProvider customGenerators;
private WasmGCIntrinsicProvider intrinsics; private WasmGCIntrinsicProvider intrinsics;
private WasmGCMethodReturnTypes returnTypes;
private WasmFunction npeMethod; private WasmFunction npeMethod;
private WasmFunction aaiobeMethod; private WasmFunction aaiobeMethod;
private WasmFunction cceMethod; private WasmFunction cceMethod;
@ -59,7 +61,8 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext {
ClassHierarchy hierarchy, BaseWasmFunctionRepository functions, ClassHierarchy hierarchy, BaseWasmFunctionRepository functions,
WasmGCSupertypeFunctionProvider supertypeFunctions, WasmGCClassInfoProvider classInfoProvider, WasmGCSupertypeFunctionProvider supertypeFunctions, WasmGCClassInfoProvider classInfoProvider,
WasmGCStandardClasses standardClasses, WasmGCStringProvider strings, WasmGCStandardClasses standardClasses, WasmGCStringProvider strings,
WasmGCCustomGeneratorProvider customGenerators, WasmGCIntrinsicProvider intrinsics) { WasmGCCustomGeneratorProvider customGenerators, WasmGCIntrinsicProvider intrinsics,
WasmGCMethodReturnTypes returnTypes) {
this.module = module; this.module = module;
this.virtualTables = virtualTables; this.virtualTables = virtualTables;
this.typeMapper = typeMapper; this.typeMapper = typeMapper;
@ -73,6 +76,7 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext {
this.strings = strings; this.strings = strings;
this.customGenerators = customGenerators; this.customGenerators = customGenerators;
this.intrinsics = intrinsics; this.intrinsics = intrinsics;
this.returnTypes = returnTypes;
} }
public WasmGCClassInfoProvider classInfoProvider() { public WasmGCClassInfoProvider classInfoProvider() {
@ -172,4 +176,8 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext {
public WasmGCIntrinsicProvider intrinsics() { public WasmGCIntrinsicProvider intrinsics() {
return intrinsics; return intrinsics;
} }
public WasmGCMethodReturnTypes returnTypes() {
return returnTypes;
}
} }

View File

@ -408,6 +408,7 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor {
result = invocation(expr, null, false); result = invocation(expr, null, false);
} }
@Override @Override
protected WasmExpression invocation(InvocationExpr expr, List<WasmExpression> resultConsumer, boolean willDrop) { protected WasmExpression invocation(InvocationExpr expr, List<WasmExpression> resultConsumer, boolean willDrop) {
if (expr.getType() == InvocationType.SPECIAL || expr.getType() == InvocationType.STATIC) { if (expr.getType() == InvocationType.SPECIAL || expr.getType() == InvocationType.STATIC) {
@ -435,26 +436,34 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor {
@Override @Override
protected WasmExpression mapFirstArgumentForCall(WasmExpression argument, WasmFunction function, protected WasmExpression mapFirstArgumentForCall(WasmExpression argument, WasmFunction function,
MethodReference method) { 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 actualType = typeInference.getResult();
var expectedType = function.getType().getParameterTypes().get(0);
if (actualType == expectedType || !(actualType instanceof WasmType.CompositeReference) if (actualType == expectedType || !(actualType instanceof WasmType.CompositeReference)
|| !(expectedType instanceof WasmType.CompositeReference)) { || !(expectedType instanceof WasmType.CompositeReference)) {
return argument; return expression;
} }
var actualComposite = ((WasmType.CompositeReference) actualType).composite; var actualComposite = ((WasmType.CompositeReference) actualType).composite;
var expectedComposite = ((WasmType.CompositeReference) expectedType).composite; var expectedComposite = ((WasmType.CompositeReference) expectedType).composite;
if (!(actualComposite instanceof WasmStructure) || !(expectedComposite instanceof WasmStructure)) { if (!(actualComposite instanceof WasmStructure) || !(expectedComposite instanceof WasmStructure)) {
return argument; return expression;
} }
var actualStruct = (WasmStructure) actualComposite; var actualStruct = (WasmStructure) actualComposite;
var expectedStruct = (WasmStructure) expectedComposite; var expectedStruct = (WasmStructure) expectedComposite;
if (!actualStruct.isSupertypeOf(expectedStruct)) { if (!actualStruct.isSupertypeOf(expectedStruct)) {
return argument; return expression;
} }
return new WasmCast(argument, expectedComposite.getReference()); return new WasmCast(expression, expectedComposite.getReference());
} }
@Override @Override

View File

@ -302,7 +302,8 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository {
standardClasses, standardClasses,
strings, strings,
customGenerators, customGenerators,
intrinsics intrinsics,
returnTypes
); );
} }
return context; return context;

View File

@ -85,4 +85,16 @@ public class WasmGCSupport {
private static native byte nextByte(); private static native byte nextByte();
private static native void error(); 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;
}
} }

View File

@ -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 };
}();