diff --git a/classlib/src/main/java/org/teavm/classlib/impl/console/JSConsoleStringPrintStream.java b/classlib/src/main/java/org/teavm/classlib/impl/console/JSConsoleStringPrintStream.java new file mode 100644 index 000000000..114304b74 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/impl/console/JSConsoleStringPrintStream.java @@ -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(); + } +} 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 59f4ab05f..9483035f3 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmGCModuleGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmGCModuleGenerator.java @@ -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; 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 f4920357e..cf00546f4 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java @@ -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); 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 721da8436..bcd779a87 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 @@ -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() { diff --git a/core/src/main/java/org/teavm/backend/wasm/gc/vtable/WasmGCVirtualTableBuilder.java b/core/src/main/java/org/teavm/backend/wasm/gc/vtable/WasmGCVirtualTableBuilder.java index 429cf6278..d644e6360 100644 --- a/core/src/main/java/org/teavm/backend/wasm/gc/vtable/WasmGCVirtualTableBuilder.java +++ b/core/src/main/java/org/teavm/backend/wasm/gc/vtable/WasmGCVirtualTableBuilder.java @@ -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()); diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java index 21e894aff..f63ef2109 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java @@ -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 diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCTypeMapper.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCTypeMapper.java index 2e00283d6..8ffe1b50f 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCTypeMapper.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCTypeMapper.java @@ -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(); } } 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 47df8e791..125f647d2 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 @@ -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; - accept(value, struct.getFields().get(fieldIndex).getUnpackedType()); - var wasmValue = result; - - var expr = new WasmStructSet(struct, target, fieldIndex, wasmValue); - expr.setLocation(location); - resultConsumer.add(expr); + var expr = new WasmStructSet(struct, target, fieldIndex, wasmValue); + expr.setLocation(location); + resultConsumer.add(expr); + } else { + accept(value); + resultConsumer.add(result); + } } } @@ -495,31 +499,36 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor { var type = (WasmType.CompositeReference) typeInference.getResult(); var struct = (WasmStructure) type.composite; var fieldIndex = context.classInfoProvider().getFieldIndex(expr.getField()); - var structGet = new WasmStructGet(struct, target, fieldIndex); - var cls = context.classes().get(expr.getField().getClassName()); - if (cls != null) { - var field = cls.getField(expr.getField().getFieldName()); - if (field != null) { - var fieldType = field.getType(); - if (fieldType instanceof ValueType.Primitive) { - switch (((ValueType.Primitive) fieldType).getKind()) { - case BYTE: - structGet.setSignedType(WasmSignedType.SIGNED); - break; - case SHORT: - structGet.setSignedType(WasmSignedType.SIGNED); - break; - case CHARACTER: - structGet.setSignedType(WasmSignedType.UNSIGNED); - break; - default: - break; + if (fieldIndex >= 0) { + var structGet = new WasmStructGet(struct, target, fieldIndex); + var cls = context.classes().get(expr.getField().getClassName()); + if (cls != null) { + var field = cls.getField(expr.getField().getFieldName()); + if (field != null) { + var fieldType = field.getType(); + if (fieldType instanceof ValueType.Primitive) { + switch (((ValueType.Primitive) fieldType).getKind()) { + case BYTE: + structGet.setSignedType(WasmSignedType.SIGNED); + break; + case SHORT: + structGet.setSignedType(WasmSignedType.SIGNED); + break; + case CHARACTER: + structGet.setSignedType(WasmSignedType.UNSIGNED); + break; + default: + break; + } } } } + structGet.setLocation(expr.getLocation()); + result = structGet; + } else { + result = new WasmUnreachable(); + result.setLocation(expr.getLocation()); } - structGet.setLocation(expr.getLocation()); - result = structGet; } } 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 f57f24f80..8d3eff452 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 @@ -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,13 +193,17 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { } private void generateMethodBody(MethodHolder method, WasmFunction function) { - var customGenerator = customGenerators.get(method.getReference()); - if (customGenerator != null) { - generateCustomMethodBody(customGenerator, method.getReference(), function); - } else if (!method.hasModifier(ElementModifier.NATIVE)) { - generateRegularMethodBody(method, function); - } else { - generateNativeMethodBody(method, function); + try { + var customGenerator = customGenerators.get(method.getReference()); + if (customGenerator != null) { + generateCustomMethodBody(customGenerator, method.getReference(), function); + } else if (!method.hasModifier(ElementModifier.NATIVE)) { + generateRegularMethodBody(method, function); + } else { + generateNativeMethodBody(method, function); + } + } catch (RuntimeException e) { + throw new RuntimeException("Failed generating method body: " + method.getReference(), e); } } @@ -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); diff --git a/core/src/main/java/org/teavm/model/optimization/Devirtualization.java b/core/src/main/java/org/teavm/model/optimization/Devirtualization.java index f6672a1c2..31c2297db 100644 --- a/core/src/main/java/org/teavm/model/optimization/Devirtualization.java +++ b/core/src/main/java/org/teavm/model/optimization/Devirtualization.java @@ -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++; 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 index 3ad3c2047..06eae4dcb 100644 --- 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 @@ -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; })); } diff --git a/tools/browser-runner/src/main/resources/test-server/frame.js b/tools/browser-runner/src/main/resources/test-server/frame.js index b1015e199..c932810d3 100644 --- a/tools/browser-runner/src/main/resources/test-server/frame.js +++ b/tools/browser-runner/src/main/resources/test-server/frame.js @@ -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", "*"); } diff --git a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java index 1c4b6b364..e7a4a8569 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java +++ b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java @@ -303,27 +303,29 @@ public class TeaVMTestRunner extends Runner implements Filterable { if (success && outputDir != null) { List runs = new ArrayList<>(); - if (isWholeClassCompilation) { - if (!classCompilationOk) { - notifier.fireTestFinished(description); - notifier.fireTestFailure(new Failure(description, - new AssertionError("Could not compile test class"))); + try { + if (isWholeClassCompilation) { + if (!classCompilationOk) { + notifier.fireTestFailure(new Failure(description, + new AssertionError("Could not compile test class"))); + } else { + prepareTestsFromWholeClass(child, runs); + } } else { - prepareTestsFromWholeClass(child, runs); + prepareCompiledTest(child, notifier, runs); } - } else { - prepareCompiledTest(child, notifier, runs); - } - for (var run : runs) { - try { - submitRun(run); - } catch (Throwable e) { - notifier.fireTestFailure(new Failure(description, e)); - break; + for (var run : runs) { + try { + submitRun(run); + } catch (Throwable e) { + notifier.fireTestFailure(new Failure(description, e)); + break; + } } + } finally { + notifier.fireTestFinished(description); } - 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; } diff --git a/tools/junit/src/main/java/org/teavm/junit/TestWasmGCEntryPoint.java b/tools/junit/src/main/java/org/teavm/junit/TestWasmGCEntryPoint.java index 236c82569..681f17434 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TestWasmGCEntryPoint.java +++ b/tools/junit/src/main/java/org/teavm/junit/TestWasmGCEntryPoint.java @@ -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); } diff --git a/tools/junit/src/main/java/org/teavm/junit/WebAssemblyGCPlatformSupport.java b/tools/junit/src/main/java/org/teavm/junit/WebAssemblyGCPlatformSupport.java index 5e31b0d5e..a13c9421f 100644 --- a/tools/junit/src/main/java/org/teavm/junit/WebAssemblyGCPlatformSupport.java +++ b/tools/junit/src/main/java/org/teavm/junit/WebAssemblyGCPlatformSupport.java @@ -49,7 +49,7 @@ class WebAssemblyGCPlatformSupport extends TestPlatformSupport { 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; }