wasm gc: fix support of legacy Object.cast method

This commit is contained in:
Alexey Andreev 2024-10-02 19:27:43 +02:00
parent 551f0505c7
commit 0bd7bc6ca9
11 changed files with 88 additions and 15 deletions

View File

@ -265,7 +265,7 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
dep.used = false; dep.used = false;
lock(dep, false); lock(dep, false);
deferredTasks.add(() -> { deferredTasks.add(() -> {
classSource.getReferenceResolver().use(dep.method.getReference(), diagnostics); classSource.getReferenceResolver().use(dep.method.getReference());
processMethod(dep); processMethod(dep);
dep.used = true; dep.used = true;
}); });
@ -476,9 +476,9 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
abstract DependencyNode createClassValueNode(int degree, DependencyNode parent); abstract DependencyNode createClassValueNode(int degree, DependencyNode parent);
void scheduleMethodAnalysis(MethodDependency dep) { void scheduleMethodAnalysis(MethodDependency dep) {
classSource.getReferenceResolver().use(dep.getReference(), diagnostics); classSource.getReferenceResolver().use(dep.getReference());
deferredTasks.add(() -> { deferredTasks.add(() -> {
classSource.getReferenceResolver().use(dep.getReference(), diagnostics); classSource.getReferenceResolver().use(dep.getReference());
processMethod(dep); processMethod(dep);
}); });
} }

View File

@ -72,7 +72,7 @@ class DependencyClassSource implements ClassHolderSource {
this.diagnostics = diagnostics; this.diagnostics = diagnostics;
innerHierarchy = new ClassHierarchy(innerSource); innerHierarchy = new ClassHierarchy(innerSource);
this.dependencyRegistration = dependencyRegistration; this.dependencyRegistration = dependencyRegistration;
referenceResolver = new ReferenceResolver(this, platformTags); referenceResolver = new ReferenceResolver(this, platformTags, diagnostics);
classInitInsertion = new ClassInitInsertion(this); classInitInsertion = new ClassInitInsertion(this);
} }
@ -125,6 +125,9 @@ class DependencyClassSource implements ClassHolderSource {
if (method.getProgram() != null) { if (method.getProgram() != null) {
var program = method.getProgram(); var program = method.getProgram();
method.setProgramSupplier(m -> { method.setProgramSupplier(m -> {
if (disposed) {
return null;
}
referenceResolver.resolve(m, program); referenceResolver.resolve(m, program);
classInitInsertion.apply(m, program); classInitInsertion.apply(m, program);
return program; return program;

View File

@ -22,7 +22,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer;
import org.teavm.diagnostics.Diagnostics; import org.teavm.diagnostics.Diagnostics;
import org.teavm.interop.SupportedOn; import org.teavm.interop.SupportedOn;
import org.teavm.interop.UnsupportedOn; import org.teavm.interop.UnsupportedOn;
@ -64,6 +63,7 @@ import org.teavm.model.util.ProgramUtils;
public class ReferenceResolver { public class ReferenceResolver {
private ClassReaderSource classSource; private ClassReaderSource classSource;
private MethodReference currentMethod; private MethodReference currentMethod;
private Diagnostics diagnostics;
private Program program; private Program program;
private boolean modified; private boolean modified;
private List<Instruction> instructionsToAdd = new ArrayList<>(); private List<Instruction> instructionsToAdd = new ArrayList<>();
@ -71,20 +71,23 @@ public class ReferenceResolver {
private Map<String, Map<MethodDescriptor, Optional<MethodReader>>> methodCache = new HashMap<>(1000, 0.5f); private Map<String, Map<MethodDescriptor, Optional<MethodReader>>> methodCache = new HashMap<>(1000, 0.5f);
private Set<String> platformTags = new HashSet<>(); private Set<String> platformTags = new HashSet<>();
private UnreachableBasicBlockEliminator unreachableBlockEliminator; private UnreachableBasicBlockEliminator unreachableBlockEliminator;
private Map<MethodReference, List<Consumer<Diagnostics>>> pendingErrors = new HashMap<>(); private Map<MethodReference, List<Runnable>> pendingErrors = new HashMap<>();
private Set<MethodReference> usedMethods = new HashSet<>();
private boolean shouldStop; private boolean shouldStop;
public ReferenceResolver(ClassReaderSource classSource, String[] platformTags) { public ReferenceResolver(ClassReaderSource classSource, String[] platformTags, Diagnostics diagnostics) {
this.classSource = classSource; this.classSource = classSource;
this.platformTags.addAll(List.of(platformTags)); this.platformTags.addAll(List.of(platformTags));
unreachableBlockEliminator = new UnreachableBasicBlockEliminator(); unreachableBlockEliminator = new UnreachableBasicBlockEliminator();
this.diagnostics = diagnostics;
} }
public void use(MethodReference method, Diagnostics diagnostics) { public void use(MethodReference method) {
usedMethods.add(method);
var errors = pendingErrors.remove(method); var errors = pendingErrors.remove(method);
if (errors != null) { if (errors != null) {
for (var error : errors) { for (var error : errors) {
error.accept(diagnostics); error.run();
} }
} }
} }
@ -394,9 +397,13 @@ public class ReferenceResolver {
private void reportError(TextLocation location, String message, Object param) { private void reportError(TextLocation location, String message, Object param) {
var method = currentMethod; var method = currentMethod;
pendingErrors.computeIfAbsent(method, k -> new ArrayList<>()).add(diagnostics -> if (usedMethods.contains(method)) {
diagnostics.error(new CallLocation(method, location), message, param);
} else {
pendingErrors.computeIfAbsent(method, k -> new ArrayList<>()).add(() ->
diagnostics.error(new CallLocation(method, location), message, param)); diagnostics.error(new CallLocation(method, location), message, param));
} }
}
private static class FieldWrapper { private static class FieldWrapper {
final FieldReader value; final FieldReader value;

View File

@ -20,6 +20,9 @@ TeaVM.wasm = function() {
let getGlobalName = function(name) { let getGlobalName = function(name) {
return eval(name); return eval(name);
} }
let setGlobalName = function(name, value) {
new Function("value", name + " = value;")(value);
}
function defaults(imports) { function defaults(imports) {
dateImports(imports); dateImports(imports);
@ -146,6 +149,13 @@ TeaVM.wasm = function() {
function isIdentifierPart(s) { function isIdentifierPart(s) {
return isIdentifierStart(s) || s >= '0' && s <= '9'; return isIdentifierStart(s) || s >= '0' && s <= '9';
} }
function setProperty(obj, prop, value) {
if (obj === null) {
setGlobalName(prop, value);
} else {
obj[prop] = value;
}
}
imports.teavmJso = { imports.teavmJso = {
emptyString: () => "", emptyString: () => "",
stringFromCharCode: code => String.fromCharCode(code), stringFromCharCode: code => String.fromCharCode(code),
@ -158,8 +168,8 @@ TeaVM.wasm = function() {
wrapBoolean: value => !!value, wrapBoolean: value => !!value,
getProperty: (obj, prop) => obj !== null ? obj[prop] : getGlobalName(prop), getProperty: (obj, prop) => obj !== null ? obj[prop] : getGlobalName(prop),
getPropertyPure: (obj, prop) => obj !== null ? obj[prop] : getGlobalName(prop), getPropertyPure: (obj, prop) => obj !== null ? obj[prop] : getGlobalName(prop),
setProperty: (obj, prop, value) => obj[prop] = value, setProperty: setProperty,
setPropertyPure: (obj, prop) => obj[prop] = value, setPropertyPure: setProperty,
global: getGlobalName, global: getGlobalName,
createClass(name) { createClass(name) {
let fn = new Function( let fn = new Function(

View File

@ -60,6 +60,7 @@ package org.teavm.jso;
*/ */
public interface JSObject { public interface JSObject {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Deprecated
default <T extends JSObject> T cast() { default <T extends JSObject> T cast() {
return (T) this; return (T) this;
} }

View File

@ -376,6 +376,9 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
} }
private boolean isJavaScriptClass(ClassReader cls) { private boolean isJavaScriptClass(ClassReader cls) {
if (typeHelper.isJavaScriptClass(cls.getName())) {
return true;
}
if (cls.getParent() != null && typeHelper.isJavaScriptClass(cls.getParent())) { if (cls.getParent() != null && typeHelper.isJavaScriptClass(cls.getParent())) {
return true; return true;
} }

View File

@ -45,4 +45,7 @@ public class ClassWithConstructor implements JSObject {
@JSTopLevel @JSTopLevel
@JSProperty @JSProperty
public static native void setTopLevelProperty(String value); public static native void setTopLevelProperty(String value);
@JSTopLevel
public static native JSObject createClass(boolean subclass);
} }

View File

@ -30,7 +30,7 @@ import org.teavm.junit.TestPlatform;
@RunWith(TeaVMTestRunner.class) @RunWith(TeaVMTestRunner.class)
@SkipJVM @SkipJVM
@OnlyPlatform(TestPlatform.JAVASCRIPT) @OnlyPlatform({TestPlatform.JAVASCRIPT, TestPlatform.WEBASSEMBLY_GC})
@EachTestCompiledSeparately @EachTestCompiledSeparately
public class ImportClassTest { public class ImportClassTest {
@Test @Test
@ -75,6 +75,13 @@ public class ImportClassTest {
assertEquals("update2", ClassWithConstructor.getTopLevelProperty()); assertEquals("update2", ClassWithConstructor.getTopLevelProperty());
} }
@Test
@AttachJavaScript("org/teavm/jso/test/classWithConstructor.js")
public void legacyCastMethod() {
SubclassWithConstructor o = ClassWithConstructor.createClass(true).cast();
assertEquals("subclass", o.baz());
}
@JSBody(script = "return {};") @JSBody(script = "return {};")
private static native O create(); private static native O create();

View File

@ -0,0 +1,23 @@
/*
* 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.
*/
package org.teavm.jso.test;
import org.teavm.jso.JSClass;
@JSClass
public class SubclassWithConstructor extends ClassWithConstructor {
public native String baz();
}

View File

@ -32,6 +32,20 @@ class ClassWithConstructor {
} }
} }
class SubclassWithConstructor extends ClassWithConstructor {
constructor(foo) {
super(foo);
}
baz() {
return "subclass";
}
}
function createClass(subclass) {
return subclass ? new SubclassWithConstructor(23) : new ClassWithConstructor(42);
}
function topLevelFunction() { function topLevelFunction() {
return "top level"; return "top level";
} }

View File

@ -44,7 +44,9 @@ window.addEventListener("message", event => {
case "WASM_GC": { case "WASM_GC": {
const runtimeFile = request.file.path + "-runtime.js"; const runtimeFile = request.file.path + "-runtime.js";
appendFiles([{ path: runtimeFile, type: "regular" }], 0, () => { const runtimeFileObj = { path: runtimeFile, type: "regular" };
const files = request.additionalFiles ? [...request.additionalFiles, runtimeFileObj] : [runtimeFileObj]
appendFiles(files, 0, () => {
launchWasmGCTest(request.file, request.argument, response => { launchWasmGCTest(request.file, request.argument, response => {
event.source.postMessage(response, "*"); event.source.postMessage(response, "*");
}); });