wasm gc: fix running tests in JUnit, fix remaining errors so that *few* tests pass

This commit is contained in:
Alexey Andreev 2024-08-21 14:29:56 +02:00
parent 1aebe51256
commit 5d109236d9
15 changed files with 255 additions and 69 deletions

View File

@ -0,0 +1,30 @@
/*
* Copyright 2024 konsoletyper.
*
* 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.classlib.impl.console;
public class JSConsoleStringPrintStream extends JsConsolePrintStream {
private StringBuilder sb = new StringBuilder();
@Override
public void print(String s) {
sb.append(s);
}
@Override
public String toString() {
return sb.toString();
}
}

View File

@ -143,6 +143,35 @@ public class WasmGCModuleGenerator {
return caller;
}
public WasmFunction generateStringLengthFunction() {
var function = declarationsGenerator.functions().forInstanceMethod(new MethodReference(
String.class, "length", int.class));
var stringType = declarationsGenerator.typeMapper().mapType(ValueType.parse(String.class));
var caller = new WasmFunction(function.getType());
var stringLocal = new WasmLocal(stringType);
caller.add(stringLocal);
caller.getBody().add(callInitializer());
caller.getBody().add(new WasmReturn(new WasmCall(function, new WasmGetLocal(stringLocal))));
declarationsGenerator.module.functions.add(caller);
return caller;
}
public WasmFunction generateCharAtFunction() {
var function = declarationsGenerator.functions().forInstanceMethod(new MethodReference(
String.class, "charAt", int.class, char.class));
var stringType = declarationsGenerator.typeMapper().mapType(ValueType.parse(String.class));
var caller = new WasmFunction(function.getType());
var stringLocal = new WasmLocal(stringType);
var indexLocal = new WasmLocal(WasmType.INT32);
caller.add(stringLocal);
caller.add(indexLocal);
caller.getBody().add(callInitializer());
caller.getBody().add(new WasmReturn(new WasmCall(function, new WasmGetLocal(stringLocal),
new WasmGetLocal(indexLocal))));
declarationsGenerator.module.functions.add(caller);
return caller;
}
private void createInitializer() {
if (initializer != null) {
return;

View File

@ -146,6 +146,12 @@ public class WasmGCTarget implements TeaVMTarget {
var setArrayFunction = moduleGenerator.generateSetToStringArrayFunction();
setArrayFunction.setExportName("setToStringArray");
var stringLengthFunction = moduleGenerator.generateStringLengthFunction();
stringLengthFunction.setExportName("stringLength");
var charAtFunction = moduleGenerator.generateCharAtFunction();
charAtFunction.setExportName("charAt");
moduleGenerator.generate();
adjustModuleMemory(module);

View File

@ -52,6 +52,12 @@ public class WasmGCDependencies {
analyzer.linkMethod(new MethodReference(StringBuilder.class, "toString", String.class))
.propagate(0, analyzer.getType("java.lang.StringBuilder"))
.use();
analyzer.linkMethod(new MethodReference(String.class, "length", int.class))
.propagate(0, analyzer.getType("java.lang.String"))
.use();
analyzer.linkMethod(new MethodReference(String.class, "charAt", int.class, char.class))
.propagate(0, analyzer.getType("java.lang.String"))
.use();
}
private void contributeWasmRuntime() {

View File

@ -175,6 +175,9 @@ class WasmGCVirtualTableBuilder {
for (var method : table.cls.getMethods()) {
if (!method.hasModifier(ElementModifier.STATIC) && !method.hasModifier(ElementModifier.ABSTRACT)) {
if (method.getProgram() == null && !method.hasModifier(ElementModifier.NATIVE)) {
continue;
}
var index = indexes.getOrDefault(method.getDescriptor(), -1);
if (index >= 0) {
table.implementors.set(index, method.getReference());

View File

@ -127,7 +127,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
strings = new WasmGCStringPool(standardClasses, module, functionProvider);
supertypeGenerator = new WasmGCSupertypeFunctionGenerator(module, this, names, tagRegistry, functionTypes);
newArrayGenerator = new WasmGCNewArrayFunctionGenerator(module, functionTypes, this);
typeMapper = new WasmGCTypeMapper(this, functionTypes, module);
typeMapper = new WasmGCTypeMapper(classSource, this, functionTypes, module);
}
public WasmGCSupertypeFunctionProvider getSupertypeProvider() {
@ -490,11 +490,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
@Override
public int getFieldIndex(FieldReference fieldRef) {
getClassInfo(fieldRef.getClassName());
var result = fieldIndexes.getOrDefault(fieldRef, -1);
if (result < 0) {
throw new IllegalStateException("Can't get offset of field " + fieldRef);
}
return result;
return fieldIndexes.getOrDefault(fieldRef, -1);
}
@Override

View File

@ -22,16 +22,19 @@ import org.teavm.backend.wasm.model.WasmModule;
import org.teavm.backend.wasm.model.WasmPackedType;
import org.teavm.backend.wasm.model.WasmStorageType;
import org.teavm.backend.wasm.model.WasmType;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.ValueType;
public class WasmGCTypeMapper {
private ClassReaderSource classes;
private WasmGCClassInfoProvider classInfoProvider;
private WasmFunctionTypes functionTypes;
private WasmModule module;
WasmGCTypeMapper(WasmGCClassInfoProvider classInfoProvider, WasmFunctionTypes functionTypes,
WasmModule module) {
WasmGCTypeMapper(ClassReaderSource classes, WasmGCClassInfoProvider classInfoProvider,
WasmFunctionTypes functionTypes, WasmModule module) {
this.classes = classes;
this.classInfoProvider = classInfoProvider;
this.functionTypes = functionTypes;
this.module = module;
@ -82,8 +85,32 @@ public class WasmGCTypeMapper {
}
} else if (type instanceof ValueType.Void) {
return null;
} else {
} else if (type instanceof ValueType.Object) {
var className = ((ValueType.Object) type).getClassName();
var cls = classes.get(className);
if (cls == null) {
className = "java.lang.Object";
}
return classInfoProvider.getClassInfo(className).getType();
} else if (type instanceof ValueType.Array) {
var degree = 0;
while (type instanceof ValueType.Array) {
type = ((ValueType.Array) type).getItemType();
++degree;
}
if (type instanceof ValueType.Object) {
var className = ((ValueType.Object) type).getClassName();
var cls = classes.get(className);
if (cls != null) {
className = "java.lang.Object";
}
}
while (degree-- > 0) {
type = ValueType.arrayOf(type);
}
return classInfoProvider.getClassInfo(type).getType();
} else {
throw new IllegalArgumentException();
}
}

View File

@ -172,13 +172,17 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor {
var type = (WasmType.CompositeReference) typeInference.getResult();
var struct = (WasmStructure) type.composite;
var fieldIndex = context.classInfoProvider().getFieldIndex(field);
if (fieldIndex >= 0) {
accept(value, struct.getFields().get(fieldIndex).getUnpackedType());
var wasmValue = result;
var expr = new WasmStructSet(struct, target, fieldIndex, wasmValue);
expr.setLocation(location);
resultConsumer.add(expr);
} else {
accept(value);
resultConsumer.add(result);
}
}
}
@ -495,6 +499,7 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor {
var type = (WasmType.CompositeReference) typeInference.getResult();
var struct = (WasmStructure) type.composite;
var fieldIndex = context.classInfoProvider().getFieldIndex(expr.getField());
if (fieldIndex >= 0) {
var structGet = new WasmStructGet(struct, target, fieldIndex);
var cls = context.classes().get(expr.getField().getClassName());
if (cls != null) {
@ -520,6 +525,10 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor {
}
structGet.setLocation(expr.getLocation());
result = structGet;
} else {
result = new WasmUnreachable();
result.setLocation(expr.getLocation());
}
}
}

View File

@ -19,6 +19,7 @@ import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import org.teavm.ast.decompilation.Decompiler;
@ -192,6 +193,7 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository {
}
private void generateMethodBody(MethodHolder method, WasmFunction function) {
try {
var customGenerator = customGenerators.get(method.getReference());
if (customGenerator != null) {
generateCustomMethodBody(customGenerator, method.getReference(), function);
@ -200,6 +202,9 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository {
} else {
generateNativeMethodBody(method, function);
}
} catch (RuntimeException e) {
throw new RuntimeException("Failed generating method body: " + method.getReference(), e);
}
}
private void generateCustomMethodBody(WasmGCCustomGenerator customGenerator, MethodReference method,
@ -208,6 +213,7 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository {
}
private void generateRegularMethodBody(MethodHolder method, WasmFunction function) {
Objects.requireNonNull(method.getProgram());
var decompiler = getDecompiler();
var categoryProvider = new WasmGCVariableCategoryProvider(hierarchy);
var allocator = new RegisterAllocator(categoryProvider);

View File

@ -82,7 +82,7 @@ public class Devirtualization {
BasicBlock block = program.basicBlockAt(i);
for (Instruction insn : block) {
if (insn instanceof InvokeInstruction) {
applyToInvoke(methodDep, (InvokeInstruction) insn);
applyToInvoke(methodDep, program, (InvokeInstruction) insn);
} else if (insn instanceof CastInstruction) {
applyToCast(methodDep, (CastInstruction) insn);
}
@ -94,7 +94,7 @@ public class Devirtualization {
}
}
private void applyToInvoke(MethodDependencyInfo methodDep, InvokeInstruction invoke) {
private void applyToInvoke(MethodDependencyInfo methodDep, Program program, InvokeInstruction invoke) {
if (invoke.getType() != InvocationType.VIRTUAL) {
return;
}
@ -112,6 +112,16 @@ public class Devirtualization {
}
System.out.println();
}
if (!resolvedImplementaiton.getClassName().equals(invoke.getMethod().getClassName())) {
var cast = new CastInstruction();
cast.setValue(invoke.getInstance());
cast.setTargetType(ValueType.object(resolvedImplementaiton.getClassName()));
cast.setWeak(true);
cast.setReceiver(program.createVariable());
cast.setLocation(invoke.getLocation());
invoke.insertPrevious(cast);
invoke.setInstance(cast.getReceiver());
}
invoke.setType(InvocationType.SPECIAL);
invoke.setMethod(resolvedImplementaiton);
directCallSites++;

View File

@ -53,6 +53,7 @@ TeaVM.wasm = function() {
return WebAssembly.instantiateStreaming(fetch(path), importObj).then((obj => {
let teavm = {};
teavm.main = createMain(obj.instance);
teavm.instance = obj.instance;
return teavm;
}));
}

View File

@ -45,7 +45,7 @@ window.addEventListener("message", event => {
case "WASM_GC": {
const runtimeFile = request.file.path + "-runtime.js";
appendFiles([{ path: runtimeFile, type: "regular" }], 0, () => {
launchWasmTest(request.file, request.argument, response => {
launchWasmGCTest(request.file, request.argument, response => {
event.source.postMessage(response, "*");
});
}, error => {
@ -186,6 +186,62 @@ function launchWasmTest(file, argument, callback) {
})
}
function launchWasmGCTest(file, argument, callback) {
let outputBuffer = "";
let outputBufferStderr = "";
function putchar(charCode) {
if (charCode === 10) {
log.push({ message: outputBuffer, type: "stdout" });
outputBuffer = "";
} else {
outputBuffer += String.fromCharCode(charCode);
}
}
function putcharStderr(charCode) {
if (charCode === 10) {
log.push({ message: outputBufferStderr, type: "stderr" });
outputBufferStderr = "";
} else {
outputBufferStderr += String.fromCharCode(charCode);
}
}
let instance = null;
TeaVM.wasm.load(file.path, {
installImports: function(o) {
o.teavm.putcharStdout = putchar;
o.teavm.putcharStderr = putcharStderr;
o.teavmTest = {
success() {
callback(wrapResponse({ status: "OK" }));
},
failure(javaString) {
let jsString = "";
let length = instance.exports.stringLength(javaString);
for (let i = 0; i < length; ++i) {
jsString += String.fromCharCode(instance.exports.charAt(javaString, i));
}
callback(wrapResponse({
status: "failed",
errorMessage: jsString
}));
}
};
}
}).then(teavm => {
instance = teavm.instance;
return teavm.main(argument ? [argument] : []);
}).catch(err => {
callback(wrapResponse({
status: "failed",
errorMessage: err.message + '\n' + err.stack
}));
})
}
function start() {
window.parent.postMessage("ready", "*");
}

View File

@ -303,9 +303,9 @@ public class TeaVMTestRunner extends Runner implements Filterable {
if (success && outputDir != null) {
List<TestRun> runs = new ArrayList<>();
try {
if (isWholeClassCompilation) {
if (!classCompilationOk) {
notifier.fireTestFinished(description);
notifier.fireTestFailure(new Failure(description,
new AssertionError("Could not compile test class")));
} else {
@ -323,7 +323,9 @@ public class TeaVMTestRunner extends Runner implements Filterable {
break;
}
}
} finally {
notifier.fireTestFinished(description);
}
} else {
if (!ran) {
notifier.fireTestIgnored(description);
@ -378,7 +380,6 @@ public class TeaVMTestRunner extends Runner implements Filterable {
}
} catch (Throwable e) {
notifier.fireTestFailure(new Failure(describeChild(child), e));
notifier.fireTestFinished(describeChild(child));
}
}
@ -710,7 +711,6 @@ public class TeaVMTestRunner extends Runner implements Filterable {
if (!result.success) {
notifier.fireTestFailure(createFailure(description, result));
notifier.fireTestFinished(description);
return null;
}

View File

@ -15,7 +15,8 @@
*/
package org.teavm.junit;
import org.teavm.classlib.impl.console.JSStdoutPrintStream;
import org.teavm.classlib.impl.console.JSConsoleStringPrintStream;
import org.teavm.interop.Import;
final class TestWasmGCEntryPoint {
private TestWasmGCEntryPoint() {
@ -24,12 +25,18 @@ final class TestWasmGCEntryPoint {
public static void main(String[] args) throws Throwable {
try {
TestEntryPoint.run(args.length > 0 ? args[0] : null);
new JSStdoutPrintStream().println("SUCCESS");
reportSuccess();
} catch (Throwable e) {
var out = new JSStdoutPrintStream();
var out = new JSConsoleStringPrintStream();
e.printStackTrace(out);
out.println("FAILURE");
reportFailure(out.toString());
}
TestEntryPoint.run(args.length > 0 ? args[0] : null);
}
@Import(module = "teavmTest", name = "success")
private static native void reportSuccess();
@Import(module = "teavmTest", name = "failure")
private static native void reportFailure(String message);
}

View File

@ -49,7 +49,7 @@ class WebAssemblyGCPlatformSupport extends TestPlatformSupport<WasmGCTarget> {
TestRunStrategy createRunStrategy(File outputDir) {
var runStrategyName = System.getProperty(WASM_RUNNER);
return runStrategyName != null
? new BrowserRunStrategy(outputDir, "WASM", BrowserRunner.pickBrowser(runStrategyName))
? new BrowserRunStrategy(outputDir, "WASM_GC", BrowserRunner.pickBrowser(runStrategyName))
: null;
}