From bfd2c8479ca53658dbeec44ecd7c4319b14df818 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Thu, 12 Sep 2024 20:16:01 +0200 Subject: [PATCH] wasm gc: fix bugs related to casts and implement runtime method for converting date to string --- .../org/teavm/classlib/java/util/TDate.java | 2 +- .../wasm/generate/WasmGenerationVisitor.java | 10 +++ .../methods/BaseWasmGenerationVisitor.java | 65 +++++++++++-------- .../gc/methods/WasmGCGenerationVisitor.java | 40 +++++++++++- .../gc/methods/WasmGCMethodGenerator.java | 8 ++- .../teavm/backend/wasm/model/WasmType.java | 2 +- .../org/teavm/backend/wasm/wasm-gc-runtime.js | 20 ++++-- .../classlib/java/time/TestClockTick.java | 1 + 8 files changed, 112 insertions(+), 36 deletions(-) diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TDate.java b/classlib/src/main/java/org/teavm/classlib/java/util/TDate.java index 0ca8c3fbd..bba2f652c 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TDate.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TDate.java @@ -373,7 +373,7 @@ public class TDate implements TComparable { public String toString() { if (PlatformDetector.isC()) { return toStringC(value); - } else if (PlatformDetector.isWebAssembly()) { + } else if (PlatformDetector.isWebAssembly() || PlatformDetector.isWebAssemblyGC()) { return toStringWebAssembly(value); } else { return JSDate.create(value).stringValue(); 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 18bb5b131..0dcf09c4b 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 @@ -170,6 +170,16 @@ public class WasmGenerationVisitor extends BaseWasmGenerationVisitor { return value; } + @Override + protected WasmType mapCastSourceType(WasmType type) { + return type; + } + + @Override + protected boolean validateCastTypes(WasmType sourceType, WasmType targetType, TextLocation location) { + return true; + } + @Override protected WasmExpression peekException() { return new WasmCall(context.functions().forStaticMethod(PEEK_EXCEPTION_METHOD)); 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 f980b1b5a..d3c2b626f 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 @@ -1184,40 +1184,53 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp @Override public void visit(CastExpr expr) { - if (expr.isWeak()) { - acceptWithType(expr.getValue(), expr.getTarget()); - result = generateCast(result, mapType(expr.getTarget())); - return; - } - var block = new WasmBlock(false); - var wasmTargetType = mapType(expr.getTarget()); - block.setType(wasmTargetType); - block.setLocation(expr.getLocation()); - + var wasmTargetType = (WasmType.CompositeReference) mapType(expr.getTarget()); acceptWithType(expr.getValue(), expr.getTarget()); - result.acceptVisitor(typeInference); - var wasmSourceType = typeInference.getResult(); - var valueToCast = exprCache.create(result, wasmSourceType, expr.getLocation(), block.getBody()); + if (!expr.isWeak()) { + result.acceptVisitor(typeInference); + var wasmSourceType = typeInference.getResult(); + if (wasmSourceType == null) { + return; + } - var nullCheck = new WasmBranch(genIsNull(valueToCast.expr()), block); - nullCheck.setResult(nullLiteral(wasmTargetType)); - block.getBody().add(new WasmDrop(nullCheck)); + wasmSourceType = mapCastSourceType(wasmSourceType); - var supertypeCall = generateInstanceOf(valueToCast.expr(), expr.getTarget()); + if (!validateCastTypes(wasmSourceType, wasmTargetType, expr.getLocation())) { + return; + } - var breakIfPassed = new WasmBranch(supertypeCall, block); - breakIfPassed.setResult(generateCast(valueToCast.expr(), wasmTargetType)); - block.getBody().add(new WasmDrop(breakIfPassed)); + var block = new WasmBlock(false); + block.setType(wasmSourceType); + block.setLocation(expr.getLocation()); + acceptWithType(expr.getValue(), expr.getTarget()); + var valueToCast = exprCache.create(result, wasmSourceType, expr.getLocation(), block.getBody()); - var callSiteId = generateCallSiteId(expr.getLocation()); - callSiteId.generateRegister(block.getBody(), expr.getLocation()); - generateThrowCCE(expr.getLocation(), block.getBody()); - callSiteId.generateThrow(block.getBody(), expr.getLocation()); + var nullCheck = new WasmBranch(genIsNull(valueToCast.expr()), block); + nullCheck.setResult(nullLiteral(wasmTargetType)); + block.getBody().add(new WasmDrop(nullCheck)); - valueToCast.release(); - result = block; + var supertypeCall = generateInstanceOf(valueToCast.expr(), expr.getTarget()); + + var breakIfPassed = new WasmBranch(supertypeCall, block); + breakIfPassed.setResult(valueToCast.expr()); + block.getBody().add(new WasmDrop(breakIfPassed)); + + var callSiteId = generateCallSiteId(expr.getLocation()); + callSiteId.generateRegister(block.getBody(), expr.getLocation()); + generateThrowCCE(expr.getLocation(), block.getBody()); + callSiteId.generateThrow(block.getBody(), expr.getLocation()); + + valueToCast.release(); + result = block; + } + result = generateCast(result, wasmTargetType); + result.setLocation(expr.getLocation()); } + protected abstract WasmType mapCastSourceType(WasmType type); + + protected abstract boolean validateCastTypes(WasmType sourceType, WasmType targetType, TextLocation location); + protected abstract WasmExpression generateCast(WasmExpression value, WasmType targetType); @Override 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 460a076e7..098d8a043 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 @@ -222,7 +222,7 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor { } return new WasmNullConstant(type instanceof WasmType.Reference ? (WasmType.Reference) type - : WasmType.Reference.STRUCT); + : context.classInfoProvider().getClassInfo("java.lang.Object").getType()); } @Override @@ -362,6 +362,39 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor { return new WasmCast(value, (WasmType.Reference) targetType); } + @Override + protected WasmType mapCastSourceType(WasmType type) { + if (!(type instanceof WasmType.CompositeReference)) { + return type; + } + var refType = (WasmType.CompositeReference) type; + return refType.isNullable() ? refType : refType.composite.getReference(); + } + + @Override + protected boolean validateCastTypes(WasmType sourceType, WasmType targetType, TextLocation location) { + if (!(sourceType instanceof WasmType.CompositeReference) + || !(targetType instanceof WasmType.CompositeReference)) { + return false; + } + var sourceRefType = (WasmType.CompositeReference) sourceType; + var targetRefType = (WasmType.CompositeReference) targetType; + if (sourceRefType.composite instanceof WasmStructure + && targetRefType.composite instanceof WasmStructure) { + var sourceStruct = (WasmStructure) sourceRefType.composite; + var targetStruct = (WasmStructure) targetRefType.composite; + if (targetStruct.isSupertypeOf(sourceStruct)) { + return false; + } + if (!sourceStruct.isSupertypeOf(targetStruct)) { + result = new WasmUnreachable(); + result.setLocation(location); + return false; + } + } + return true; + } + @Override protected boolean needsClassInitializer(String className) { return context.classInfoProvider().getClassInfo(className).getInitializerPointer() != null; @@ -516,6 +549,11 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor { target.acceptVisitor(typeInference); var type = (WasmType.CompositeReference) typeInference.getResult(); + if (type == null) { + result = new WasmUnreachable(); + result.setLocation(expr.getLocation()); + return; + } var struct = (WasmStructure) type.composite; var fieldIndex = context.classInfoProvider().getFieldIndex(expr.getField()); if (fieldIndex >= 0) { 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 8ba2e164c..45fa2fd7d 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 @@ -15,6 +15,8 @@ */ package org.teavm.backend.wasm.generate.gc.methods; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.ArrayDeque; import java.util.Arrays; import java.util.HashMap; @@ -209,7 +211,11 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { generateNativeMethodBody(method, function); } } catch (RuntimeException e) { - throw new RuntimeException("Failed generating method body: " + method.getReference(), e); + var buffer = new StringWriter(); + var printWriter = new PrintWriter(buffer); + e.printStackTrace(printWriter); + diagnostics.error(new CallLocation(method.getReference()), + "Failed generating method body due to internal exception: " + buffer.toString()); } } diff --git a/core/src/main/java/org/teavm/backend/wasm/model/WasmType.java b/core/src/main/java/org/teavm/backend/wasm/model/WasmType.java index fa765b644..27d4c95f0 100644 --- a/core/src/main/java/org/teavm/backend/wasm/model/WasmType.java +++ b/core/src/main/java/org/teavm/backend/wasm/model/WasmType.java @@ -67,7 +67,7 @@ public abstract class WasmType { private final boolean nullable; - public Reference(boolean nullable) { + Reference(boolean nullable) { this.nullable = nullable; } 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 aa5abcb22..bea922bd8 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 @@ -19,6 +19,7 @@ TeaVM.wasm = function() { function defaults(imports) { let stderr = ""; let stdout = ""; + let exports; imports.teavm = { putcharStderr(c) { if (c === 10) { @@ -38,6 +39,9 @@ TeaVM.wasm = function() { }, currentTimeMillis() { return new Date().getTime(); + }, + dateToString(timestamp, controller) { + return stringToJava(new Date(timestamp).toString()); } }; imports.teavmMath = Math; @@ -62,21 +66,25 @@ TeaVM.wasm = function() { })); } + function stringToJava(str) { + let sb = exports.createStringBuilder(); + for (let i = 0; i < str.length; ++i) { + exports.appendChar(sb, str.charCodeAt(i)); + } + return exports.buildString(sb); + } + function createMain(instance) { return args => { if (typeof args === "undefined") { args = []; } return new Promise((resolve, reject) => { - let exports = instance.exports; + 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)); + exports.setToStringArray(javaArgs, i, stringToJava(args[i])); } try { exports.main(javaArgs); diff --git a/tests/src/test/java/org/teavm/classlib/java/time/TestClockTick.java b/tests/src/test/java/org/teavm/classlib/java/time/TestClockTick.java index c3d5cf27d..1fb0dd384 100644 --- a/tests/src/test/java/org/teavm/classlib/java/time/TestClockTick.java +++ b/tests/src/test/java/org/teavm/classlib/java/time/TestClockTick.java @@ -154,6 +154,7 @@ public class TestClockTick extends AbstractTest { } //----------------------------------------------------------------------- + @SkipPlatform(TestPlatform.WEBASSEMBLY_GC) public void test_tickSeconds_ZoneId() throws Exception { Clock test = Clock.tickSeconds(PARIS); assertEquals(test.getZone(), PARIS);