wasm gc: reduce number of generated virtual tables, fix non-matching types in case of null literals

This commit is contained in:
Alexey Andreev 2024-08-02 13:50:49 +02:00
parent 9b5e1e7661
commit e4fa6bd364
11 changed files with 265 additions and 44 deletions

View File

@ -15,7 +15,7 @@
*/
package org.teavm.classlib.impl.console;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
@ -30,7 +30,7 @@ public abstract class JsConsolePrintStream extends PrintStream {
private Runnable flushAction;
public JsConsolePrintStream() {
super(new ByteArrayOutputStream());
super((OutputStream) null);
}
@Override

View File

@ -103,6 +103,11 @@ public class PreciseTypeInference extends BaseTypeInference<PreciseValueType> {
return new PreciseValueType(((ValueType.Array) valueType.valueType).getItemType(), false);
}
@Override
protected PreciseValueType arrayType(PreciseValueType preciseValueType) {
return new PreciseValueType(ValueType.arrayOf(preciseValueType.valueType), false);
}
@Override
protected PreciseValueType methodReturnType(InvocationType invocationType, MethodReference methodRef) {
if (invocationType == InvocationType.SPECIAL) {

View File

@ -38,6 +38,7 @@ public class WasmGCVariableCategoryProvider implements VariableCategoryProvider
public Object[] getCategories(Program program, MethodReference method) {
inference = new PreciseTypeInference(program, method, hierarchy, returnTypes);
inference.setPhisSkipped(true);
inference.setBackPropagation(true);
var result = new Object[program.variableCount()];
for (int i = 0; i < program.variableCount(); ++i) {
var type = inference.typeOf(program.variableAt(i));

View File

@ -104,7 +104,6 @@ public class WasmGenerationVisitor extends BaseWasmGenerationVisitor {
private WasmGenerationContext context;
private WasmClassGenerator classGenerator;
private MethodReference currentMethod;
private List<ExceptionHandlerDescriptor> handlers = new ArrayList<>();
private WasmBlock lastTryBlock;
@ -118,11 +117,10 @@ public class WasmGenerationVisitor extends BaseWasmGenerationVisitor {
public WasmGenerationVisitor(WasmGenerationContext context, WasmClassGenerator classGenerator,
BinaryWriter binaryWriter, WasmFunction function, MethodReference currentMethod,
int firstVariable, boolean async) {
super(context, function, firstVariable, async);
super(context, currentMethod, function, firstVariable, async);
this.context = context;
this.classGenerator = classGenerator;
this.binaryWriter = binaryWriter;
this.currentMethod = currentMethod;
this.managed = context.characteristics.isManaged(currentMethod);
}

View File

@ -116,6 +116,7 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp
private static final int SWITCH_TABLE_THRESHOLD = 256;
private BaseWasmGenerationContext context;
protected final MethodReference currentMethod;
protected final WasmTypeInference typeInference;
protected final WasmFunction function;
private int firstVariable;
@ -131,9 +132,10 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp
protected WasmExpression result;
protected List<WasmExpression> resultConsumer;
public BaseWasmGenerationVisitor(BaseWasmGenerationContext context, WasmFunction function,
int firstVariable, boolean async) {
public BaseWasmGenerationVisitor(BaseWasmGenerationContext context, MethodReference currentMethod,
WasmFunction function, int firstVariable, boolean async) {
this.context = context;
this.currentMethod = currentMethod;
this.function = function;
this.firstVariable = firstVariable;
tempVars = new TemporaryVariablePool(function);
@ -833,6 +835,10 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp
protected abstract CallSiteIdentifier generateCallSiteId(TextLocation location);
protected void acceptWithType(Expr expr, ValueType type) {
accept(expr);
}
protected WasmExpression generateInvocation(InvocationExpr expr, CallSiteIdentifier callSiteId) {
if (expr.getType() == InvocationType.STATIC || expr.getType() == InvocationType.SPECIAL) {
var method = context.classes().resolve(expr.getMethod());
@ -842,8 +848,13 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp
: context.functions().forInstanceMethod(reference);
var call = new WasmCall(function);
for (var argument : expr.getArguments()) {
accept(argument);
var arguments = expr.getArguments();
for (int i = 0, argumentsSize = arguments.size(); i < argumentsSize; i++) {
var argument = arguments.get(i);
var type = expr.getType() == InvocationType.STATIC
? reference.parameterType(i)
: i == 0 ? ValueType.object(reference.getClassName()) : reference.parameterType(i - 1);
acceptWithType(argument, type);
call.getArguments().add(result);
}
if (expr.getType() == InvocationType.SPECIAL) {
@ -866,8 +877,12 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp
var function = context.functions().forInstanceMethod(expr.getMethod());
var call = new WasmCall(function);
call.getArguments().add(new WasmGetLocal(tmp));
for (var argument : expr.getArguments()) {
accept(argument);
var arguments = expr.getArguments();
acceptWithType(arguments.get(0), ValueType.object(expr.getMethod().getClassName()));
call.getArguments().add(result);
for (int i = 1; i < arguments.size(); i++) {
var argument = arguments.get(i);
acceptWithType(argument, expr.getMethod().parameterType(i));
call.getArguments().add(result);
}
if (callSiteId != null) {
@ -881,7 +896,7 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp
return block;
} else {
var reference = expr.getMethod();
accept(expr.getArguments().get(0));
acceptWithType(expr.getArguments().get(0), ValueType.object(expr.getMethod().getClassName()));
var instance = result;
var block = new WasmBlock(false);
block.setType(mapType(reference.getReturnType()));
@ -893,7 +908,7 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp
var arguments = new ArrayList<WasmExpression>();
arguments.add(instance);
for (int i = 1; i < expr.getArguments().size(); ++i) {
accept(expr.getArguments().get(i));
acceptWithType(expr.getArguments().get(i), expr.getMethod().parameterType(i - 1));
arguments.add(result);
}
if (callSiteId != null) {
@ -1091,7 +1106,7 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp
@Override
public void visit(ReturnStatement statement) {
if (statement.getResult() != null) {
accept(statement.getResult());
acceptWithType(statement.getResult(), currentMethod.getReturnType());
} else {
result = null;
}
@ -1102,7 +1117,7 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp
@Override
public void visit(InstanceOfExpr expr) {
accept(expr.getExpr());
acceptWithType(expr.getExpr(), expr.getType());
var block = new WasmBlock(false);
block.setType(WasmType.INT32);
@ -1143,7 +1158,7 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp
block.setType(wasmTargetType);
block.setLocation(expr.getLocation());
accept(expr.getValue());
acceptWithType(expr.getValue(), expr.getTarget());
result.acceptVisitor(typeInference);
var wasmSourceType = typeInference.getResult();
var valueToCast = exprCache.create(result, wasmSourceType, expr.getLocation(), block.getBody());

View File

@ -20,10 +20,12 @@ import com.carrotsearch.hppc.ObjectIntMap;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.function.Consumer;
import org.teavm.backend.lowlevel.generate.NameProvider;
import org.teavm.backend.wasm.BaseWasmFunctionRepository;
@ -182,6 +184,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
classInfo = new WasmGCClassInfo(type);
classInfoQueue.add(classInfo);
classInfoMap.put(type, classInfo);
VirtualTable virtualTable = null;
if (!(type instanceof ValueType.Primitive)) {
var name = type instanceof ValueType.Object
? ((ValueType.Object) type).getClassName()
@ -189,6 +192,10 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
classInfo.structure = new WasmStructure(name != null ? names.forClass(name) : null);
if (name != null) {
var classReader = classSource.get(name);
if (classReader == null || !classReader.hasModifier(ElementModifier.ABSTRACT)
&& !classReader.hasModifier(ElementModifier.INTERFACE)) {
virtualTable = virtualTables.lookup(name);
}
if (classReader != null && classReader.getParent() != null) {
classInfo.structure.setSupertype(getClassInfo(classReader.getParent()).structure);
}
@ -199,7 +206,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
fillFields(classInfo, type);
}
var pointerName = names.forClassInstance(type);
var classStructure = type instanceof ValueType.Object
var classStructure = virtualTable != null && virtualTable.hasValidEntries()
? initRegularClassStructure(((ValueType.Object) type).getClassName())
: standardClasses.classClass().getStructure();
classInfo.virtualTableStructure = classStructure;
@ -213,7 +220,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
} else if (type instanceof ValueType.Array) {
initArrayClass(classInfo, (ValueType.Array) type);
} else if (type instanceof ValueType.Object) {
initRegularClass(classInfo, classStructure, ((ValueType.Object) type).getClassName());
initRegularClass(classInfo, virtualTable, classStructure, ((ValueType.Object) type).getClassName());
}
}
return classInfo;
@ -276,7 +283,8 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
};
}
private void initRegularClass(WasmGCClassInfo classInfo, WasmStructure classStructure, String name) {
private void initRegularClass(WasmGCClassInfo classInfo, VirtualTable virtualTable, WasmStructure classStructure,
String name) {
var cls = classSource.get(name);
if (classInitializerInfo.isDynamicInitializer(name)) {
if (cls != null && cls.getMethod(CLINIT_METHOD_DESC) != null) {
@ -290,35 +298,33 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
var ranges = tagRegistry.getRanges(name);
int tag = ranges.stream().mapToInt(range -> range.lower).min().orElse(0);
target.add(setClassField(classInfo, classTagOffset, new WasmInt32Constant(tag)));
var metadataReg = metadataRequirements.getInfo(name);
if (metadataReg.name()) {
var metadataReq = metadataRequirements.getInfo(name);
if (metadataReq.name()) {
var namePtr = strings.getStringConstant(name).global;
target.add(setClassField(classInfo, classNameOffset, new WasmGetGlobal(namePtr)));
}
if (cls != null) {
if (metadataReg.simpleName() && cls.getSimpleName() != null) {
if (metadataReq.simpleName() && cls.getSimpleName() != null) {
var namePtr = strings.getStringConstant(cls.getSimpleName()).global;
target.add(setClassField(classInfo, classNameOffset, new WasmGetGlobal(namePtr)));
}
if (cls.getParent() != null) {
if (cls.getParent() != null && metadataReq.superclass()) {
var parent = getClassInfo(cls.getParent());
target.add(setClassField(classInfo, classParentOffset, new WasmGetGlobal(parent.pointer)));
}
}
var virtualTable = virtualTables.lookup(name);
fillVirtualTableMethods(target, classStructure, classInfo.pointer, virtualTable, virtualTableFieldOffset,
name);
if (virtualTable != null && virtualTable.hasValidEntries()) {
fillVirtualTableMethods(target, classStructure, classInfo.pointer, virtualTable,
virtualTableFieldOffset, name, new HashSet<>());
}
};
}
private int fillVirtualTableMethods(List<WasmExpression> target, WasmStructure structure, WasmGlobal global,
VirtualTable virtualTable, int index, String origin) {
if (virtualTable.getParent() != null) {
index = fillVirtualTableMethods(target, structure, global, virtualTable.getParent(), index, origin);
}
VirtualTable virtualTable, int index, String origin, Set<MethodDescriptor> filled) {
for (var method : virtualTable.getMethods()) {
var entry = virtualTable.getEntry(method);
if (entry != null && entry.getImplementor() != null) {
if (entry != null && entry.getImplementor() != null && filled.add(method)) {
var function = functionProvider.forInstanceMethod(entry.getImplementor());
if (!origin.equals(entry.getImplementor().getClassName())) {
var functionType = getFunctionType(virtualTable.getClassName(), method);
@ -341,6 +347,10 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
target.add(new WasmStructSet(structure, new WasmGetGlobal(global), index, ref));
}
}
if (virtualTable.getParent() != null) {
index = fillVirtualTableMethods(target, structure, global, virtualTable.getParent(), index, origin,
filled);
}
return index;
}

View File

@ -62,14 +62,32 @@ import org.teavm.model.ValueType;
public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor {
private WasmGCGenerationContext context;
private WasmGCGenerationUtil generationUtil;
private WasmType expectedType;
public WasmGCGenerationVisitor(WasmGCGenerationContext context, WasmFunction function,
int firstVariable, boolean async) {
super(context, function, firstVariable, async);
public WasmGCGenerationVisitor(WasmGCGenerationContext context, MethodReference currentMethod,
WasmFunction function, int firstVariable, boolean async) {
super(context, currentMethod, function, firstVariable, async);
this.context = context;
generationUtil = new WasmGCGenerationUtil(context.classInfoProvider(), tempVars);
}
@Override
protected void accept(Expr expr) {
accept(expr, null);
}
protected void accept(Expr expr, WasmType type) {
var previousExpectedType = expectedType;
expectedType = type;
super.accept(expr);
expectedType = previousExpectedType;
}
@Override
protected void acceptWithType(Expr expr, ValueType type) {
accept(expr, mapType(type));
}
@Override
protected boolean isManaged() {
return false;
@ -137,24 +155,23 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor {
@Override
protected void storeField(Expr qualified, FieldReference field, Expr value, TextLocation location) {
if (qualified == null) {
accept(value);
var wasmValue = result;
var global = context.classInfoProvider().getStaticFieldLocation(field);
accept(value, global.getType());
var wasmValue = result;
var result = new WasmSetGlobal(global, wasmValue);
result.setLocation(location);
resultConsumer.add(result);
} else {
accept(qualified);
acceptWithType(qualified, ValueType.object(field.getClassName()));
var target = result;
accept(value);
var wasmValue = result;
target.acceptVisitor(typeInference);
var type = (WasmType.CompositeReference) typeInference.getResult();
var struct = (WasmStructure) type.composite;
var fieldIndex = context.classInfoProvider().getFieldIndex(field);
accept(value, struct.getFields().get(fieldIndex).asUnpackedType());
var wasmValue = result;
var expr = new WasmStructSet(struct, target, fieldIndex, wasmValue);
expr.setLocation(location);
resultConsumer.add(expr);
@ -175,7 +192,9 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor {
@Override
protected WasmExpression nullLiteral() {
return new WasmNullConstant(WasmType.Reference.STRUCT);
return new WasmNullConstant(expectedType instanceof WasmType.Reference
? (WasmType.Reference) expectedType
: WasmType.Reference.STRUCT);
}
@Override
@ -404,7 +423,7 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor {
result = new WasmGetGlobal(global);
result.setLocation(expr.getLocation());
} else {
accept(expr.getQualified());
acceptWithType(expr.getQualified(), ValueType.object(expr.getField().getClassName()));
var target = result;
target.acceptVisitor(typeInference);

View File

@ -249,7 +249,8 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository {
}
addInitializerErase(method, function);
var visitor = new WasmGCGenerationVisitor(getGenerationContext(), function, firstVar, false);
var visitor = new WasmGCGenerationVisitor(getGenerationContext(), method.getReference(),
function, firstVar, false);
visitor.generate(ast.getBody(), function.getBody());
}

View File

@ -17,6 +17,7 @@ package org.teavm.model.analysis;
import com.carrotsearch.hppc.IntStack;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Objects;
import org.teavm.common.Graph;
import org.teavm.common.GraphBuilder;
@ -39,6 +40,7 @@ import org.teavm.model.instructions.ConstructArrayInstruction;
import org.teavm.model.instructions.ConstructInstruction;
import org.teavm.model.instructions.ConstructMultiArrayInstruction;
import org.teavm.model.instructions.DoubleConstantInstruction;
import org.teavm.model.instructions.ExitInstruction;
import org.teavm.model.instructions.FloatConstantInstruction;
import org.teavm.model.instructions.GetElementInstruction;
import org.teavm.model.instructions.GetFieldInstruction;
@ -51,6 +53,7 @@ import org.teavm.model.instructions.NegateInstruction;
import org.teavm.model.instructions.NullCheckInstruction;
import org.teavm.model.instructions.NullConstantInstruction;
import org.teavm.model.instructions.NumericOperandType;
import org.teavm.model.instructions.PutFieldInstruction;
import org.teavm.model.instructions.StringConstantInstruction;
import org.teavm.model.instructions.UnwrapArrayInstruction;
@ -62,6 +65,7 @@ public abstract class BaseTypeInference<T> {
private Graph arrayGraph;
private Graph arrayUnwrapGraph;
private boolean phisSkipped;
private boolean backPropagation;
public BaseTypeInference(Program program, MethodReference reference) {
this.program = program;
@ -72,6 +76,10 @@ public abstract class BaseTypeInference<T> {
this.phisSkipped = phisSkipped;
}
public void setBackPropagation(boolean backPropagation) {
this.backPropagation = backPropagation;
}
private void prepare() {
types = new Object[program.variableCount()];
var visitor = new InitialTypeVisitor(program.variableCount());
@ -153,10 +161,80 @@ public abstract class BaseTypeInference<T> {
}
}
private void propagateBack() {
if (!backPropagation) {
return;
}
var hasNullTypes = false;
for (var type : types) {
if (type == null) {
hasNullTypes = true;
break;
}
}
if (!hasNullTypes) {
return;
}
var nullTypes = new boolean[program.variableCount()];
for (var i = 0; i < types.length; ++i) {
nullTypes[i] = types[i] == null;
}
var stack = new IntStack();
var typeStack = new ArrayDeque<T>();
for (var i = 0; i < types.length; ++i) {
if (nullTypes[i]) {
for (var j : graph.outgoingEdges(i)) {
if (!nullTypes[j]) {
typeStack.push((T) types[j]);
stack.push(i);
}
}
}
}
var visitor = new BackPropagationVisitor(nullTypes, stack, typeStack);
for (var block : program.getBasicBlocks()) {
for (var insn : block) {
insn.acceptVisitor(visitor);
}
}
while (!stack.isEmpty()) {
var variable = stack.pop();
var type = typeStack.pop();
var formerType = (T) types[variable];
if (Objects.equals(formerType, type)) {
continue;
}
type = doMerge(type, formerType);
if (Objects.equals(type, formerType) || type == null) {
continue;
}
types[variable] = type;
for (var pred : graph.incomingEdges(variable)) {
if (nullTypes[pred] && !Objects.equals(types[pred], type)) {
stack.push(pred);
typeStack.push(type);
}
}
if (arrayGraph.incomingEdgesCount(variable) > 0) {
var arrayType = arrayType(type);
for (var pred : arrayGraph.incomingEdges(variable)) {
if (nullTypes[pred] && !Objects.equals(types[pred], arrayType)) {
stack.push(pred);
typeStack.push(arrayType);
}
}
}
}
}
public void ensure() {
if (types == null) {
prepare();
propagate();
propagateBack();
}
}
@ -185,6 +263,10 @@ public abstract class BaseTypeInference<T> {
protected abstract T elementType(T t);
protected T arrayType(T t) {
throw new UnsupportedOperationException();
}
protected T methodReturnType(InvocationType invocationType, MethodReference methodRef) {
return mapType(methodRef.getReturnType());
}
@ -380,4 +462,55 @@ public abstract class BaseTypeInference<T> {
}
}
}
private class BackPropagationVisitor extends AbstractInstructionVisitor {
private boolean[] nullTypes;
private IntStack stack;
private Deque<T> typeStack;
BackPropagationVisitor(boolean[] nullTypes, IntStack stack, Deque<T> typeStack) {
this.nullTypes = nullTypes;
this.stack = stack;
this.typeStack = typeStack;
}
@Override
public void visit(ExitInstruction insn) {
if (insn.getValueToReturn() != null) {
push(insn.getValueToReturn(), reference.getReturnType());
}
}
@Override
public void visit(InvokeInstruction insn) {
if (insn.getInstance() != null) {
push(insn.getInstance(), ValueType.object(reference.getClassName()));
}
for (var i = 0; i < insn.getArguments().size(); ++i) {
push(insn.getArguments().get(i), reference.parameterType(i));
}
}
@Override
public void visit(GetFieldInstruction insn) {
if (insn.getInstance() != null) {
push(insn.getInstance(), ValueType.object(reference.getClassName()));
}
}
@Override
public void visit(PutFieldInstruction insn) {
if (insn.getInstance() != null) {
push(insn.getInstance(), ValueType.object(reference.getClassName()));
}
push(insn.getValue(), insn.getFieldType());
}
private void push(Variable variable, ValueType type) {
if (nullTypes[variable.getIndex()]) {
stack.push(variable.getIndex());
typeStack.push(mapType(type));
}
}
}
}

View File

@ -25,6 +25,8 @@ public class ClassMetadataRequirements {
private static final MethodReference GET_NAME_METHOD = new MethodReference(Class.class, "getName", String.class);
private static final MethodReference GET_SIMPLE_NAME_METHOD = new MethodReference(Class.class,
"getSimpleName", String.class);
private static final MethodReference GET_SUPERCLASS_METHOD = new MethodReference(Class.class, "getSuperclass",
Class.class);
private static final MethodReference GET_DECLARING_CLASS_METHOD = new MethodReference(Class.class,
"getDeclaringClass", Class.class);
private static final MethodReference GET_ENCLOSING_CLASS_METHOD = new MethodReference(Class.class,
@ -49,6 +51,14 @@ public class ClassMetadataRequirements {
}
}
var getSuperclassMethod = dependencyInfo.getMethod(GET_SUPERCLASS_METHOD);
if (getSuperclassMethod != null) {
String[] classNames = getSuperclassMethod.getVariable(0).getClassValueNode().getTypes();
for (String className : classNames) {
requirements.computeIfAbsent(className, k -> new ClassInfo()).declaringClass = true;
}
}
MethodDependencyInfo getDeclaringClassMethod = dependencyInfo.getMethod(GET_DECLARING_CLASS_METHOD);
if (getDeclaringClassMethod != null) {
String[] classNames = getDeclaringClassMethod.getVariable(0).getClassValueNode().getTypes();
@ -95,6 +105,7 @@ public class ClassMetadataRequirements {
boolean simpleName;
boolean declaringClass;
boolean enclosingClass;
boolean superclass;
@Override
public boolean name() {
@ -115,6 +126,11 @@ public class ClassMetadataRequirements {
public boolean enclosingClass() {
return enclosingClass;
}
@Override
public boolean superclass() {
return superclass;
}
}
public interface Info {
@ -125,5 +141,7 @@ public class ClassMetadataRequirements {
boolean declaringClass();
boolean enclosingClass();
boolean superclass();
}
}

View File

@ -26,6 +26,8 @@ public class VirtualTable {
private List<? extends MethodDescriptor> methods;
private Set<MethodDescriptor> methodSet;
private Map<MethodDescriptor, VirtualTableEntry> entryMap;
private boolean hasValidEntries;
private boolean hasValidEntriesComputed;
VirtualTable(String className, VirtualTable parent, List<? extends MethodDescriptor> methods,
Set<MethodDescriptor> methodSet, Map<MethodDescriptor, VirtualTableEntry> entryMap) {
@ -70,4 +72,23 @@ public class VirtualTable {
public int size() {
return methods.size() + (parent != null ? parent.size() : 0);
}
public boolean hasValidEntries() {
if (!hasValidEntriesComputed) {
hasValidEntriesComputed = true;
hasValidEntries = false;
if (entryMap != null) {
for (var entry : entryMap.values()) {
if (entry.getImplementor() != null) {
hasValidEntries = true;
break;
}
}
}
if (parent != null && parent.hasValidEntries()) {
hasValidEntries = true;
}
}
return hasValidEntries;
}
}