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

View File

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

View File

@ -22,7 +22,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.interop.SupportedOn;
import org.teavm.interop.UnsupportedOn;
@ -64,6 +63,7 @@ import org.teavm.model.util.ProgramUtils;
public class ReferenceResolver {
private ClassReaderSource classSource;
private MethodReference currentMethod;
private Diagnostics diagnostics;
private Program program;
private boolean modified;
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 Set<String> platformTags = new HashSet<>();
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;
public ReferenceResolver(ClassReaderSource classSource, String[] platformTags) {
public ReferenceResolver(ClassReaderSource classSource, String[] platformTags, Diagnostics diagnostics) {
this.classSource = classSource;
this.platformTags.addAll(List.of(platformTags));
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);
if (errors != null) {
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) {
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));
}
}
private static class FieldWrapper {
final FieldReader value;

View File

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

View File

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

View File

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

View File

@ -45,4 +45,7 @@ public class ClassWithConstructor implements JSObject {
@JSTopLevel
@JSProperty
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)
@SkipJVM
@OnlyPlatform(TestPlatform.JAVASCRIPT)
@OnlyPlatform({TestPlatform.JAVASCRIPT, TestPlatform.WEBASSEMBLY_GC})
@EachTestCompiledSeparately
public class ImportClassTest {
@Test
@ -75,6 +75,13 @@ public class ImportClassTest {
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 {};")
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() {
return "top level";
}

View File

@ -44,7 +44,9 @@ window.addEventListener("message", event => {
case "WASM_GC": {
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 => {
event.source.postMessage(response, "*");
});