Skip to content

Commit 115c820

Browse files
authored
fix: Compile virtual dependencies before finalizing stubs (#2040)
1 parent 454529d commit 115c820

File tree

5 files changed

+216
-172
lines changed

5 files changed

+216
-172
lines changed

src/compiler.ts

Lines changed: 81 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -389,8 +389,8 @@ export class Compiler extends DiagnosticEmitter {
389389
lazyFunctions: Set<Function> = new Set();
390390
/** Pending class-specific instanceof helpers. */
391391
pendingClassInstanceOf: Set<ClassPrototype> = new Set();
392-
/** Functions potentially involving a virtual call. */
393-
virtualCalls: Set<Function> = new Set();
392+
/** Virtually called stubs that may have overloads. */
393+
virtualStubs: Set<Function> = new Set();
394394
/** Elements currently undergoing compilation. */
395395
pendingElements: Set<Element> = new Set();
396396
/** Elements, that are module exports, already processed */
@@ -450,6 +450,7 @@ export class Compiler extends DiagnosticEmitter {
450450
var options = this.options;
451451
var module = this.module;
452452
var program = this.program;
453+
var resolver = this.resolver;
453454
var hasShadowStack = options.stackSize > 0; // implies runtime=incremental
454455

455456
// initialize lookup maps, built-ins, imports, exports, etc.
@@ -530,26 +531,37 @@ export class Compiler extends DiagnosticEmitter {
530531
compileClassInstanceOf(this, prototype);
531532
}
532533

533-
// set up virtual lookup tables
534+
// set up virtual stubs
534535
var functionTable = this.functionTable;
536+
var virtualStubs = this.virtualStubs;
535537
for (let i = 0, k = functionTable.length; i < k; ++i) {
536538
let instance = functionTable[i];
537539
if (instance.is(CommonFlags.VIRTUAL)) {
538540
assert(instance.is(CommonFlags.INSTANCE));
539-
functionTable[i] = this.ensureVirtualStub(instance); // incl. varargs
540-
this.finalizeVirtualStub(instance);
541+
functionTable[i] = this.ensureVirtualStub(instance); // includes varargs stub
541542
} else if (instance.signature.requiredParameters < instance.signature.parameterTypes.length) {
542543
functionTable[i] = this.ensureVarargsStub(instance);
543544
}
544545
}
545-
var virtualCalls = this.virtualCalls;
546-
while (virtualCalls.size) {
547-
// finalizing a stub may discover more virtual calls, so do this in a loop
548-
for (let _values = Set_values(virtualCalls), i = 0, k = _values.length; i < k; ++i) {
546+
var virtualStubsSeen = new Set<Function>();
547+
do {
548+
// virtual stubs and overloads have cross-dependencies on each other, in that compiling
549+
// either may discover the respective other. do this in a loop until no more are found.
550+
resolver.discoveredOverload = false;
551+
for (let _values = Set_values(virtualStubs), i = 0, k = _values.length; i < k; ++i) {
549552
let instance = unchecked(_values[i]);
550-
this.finalizeVirtualStub(instance);
551-
virtualCalls.delete(instance);
553+
let overloadInstances = resolver.resolveOverloads(instance);
554+
if (overloadInstances) {
555+
for (let i = 0, k = overloadInstances.length; i < k; ++i) {
556+
this.compileFunction(overloadInstances[i]);
557+
}
558+
}
559+
virtualStubsSeen.add(instance);
552560
}
561+
} while (virtualStubs.size > virtualStubsSeen.size || resolver.discoveredOverload);
562+
virtualStubsSeen.clear();
563+
for (let _values = Set_values(virtualStubs), i = 0, k = _values.length; i < k; ++i) {
564+
this.finalizeVirtualStub(_values[i]);
553565
}
554566

555567
// finalize runtime features
@@ -6949,7 +6961,7 @@ export class Compiler extends DiagnosticEmitter {
69496961
null,
69506962
module.unreachable()
69516963
);
6952-
this.virtualCalls.add(original);
6964+
this.virtualStubs.add(original);
69536965
return stub;
69546966
}
69556967

@@ -6958,11 +6970,7 @@ export class Compiler extends DiagnosticEmitter {
69586970
var stub = this.ensureVirtualStub(instance);
69596971
if (stub.is(CommonFlags.COMPILED)) return;
69606972

6961-
// Wouldn't be here if there wasn't at least one overload
6962-
var overloadPrototypes = assert(instance.prototype.overloads);
6963-
69646973
assert(instance.parent.kind == ElementKind.CLASS || instance.parent.kind == ElementKind.INTERFACE);
6965-
var parentClassInstance = <Class>instance.parent;
69666974
var module = this.module;
69676975
var usizeType = this.options.usizeType;
69686976
var sizeTypeRef = usizeType.toRef();
@@ -6986,106 +6994,65 @@ export class Compiler extends DiagnosticEmitter {
69866994
TypeRef.I32
69876995
)
69886996
);
6989-
6990-
// A method's `overloads` property contains its unbound overload prototypes
6991-
// so we first have to find the concrete classes it became bound to, obtain
6992-
// their bound prototypes and make sure these are resolved and compiled as
6993-
// we are going to call them conditionally based on this's class id.
6994-
for (let _values = Set_values(overloadPrototypes), i = 0, k = _values.length; i < k; ++i) {
6995-
let unboundOverloadPrototype = _values[i];
6996-
assert(!unboundOverloadPrototype.isBound);
6997-
let unboundOverloadParent = unboundOverloadPrototype.parent;
6998-
let isProperty = unboundOverloadParent.kind == ElementKind.PROPERTY_PROTOTYPE;
6999-
let classInstances: Map<string,Class> | null;
7000-
if (isProperty) {
7001-
let propertyParent = (<PropertyPrototype>unboundOverloadParent).parent;
7002-
assert(propertyParent.kind == ElementKind.CLASS_PROTOTYPE);
7003-
classInstances = (<ClassPrototype>propertyParent).instances;
7004-
} else {
7005-
assert(unboundOverloadParent.kind == ElementKind.CLASS_PROTOTYPE);
7006-
classInstances = (<ClassPrototype>unboundOverloadParent).instances;
7007-
}
7008-
if (classInstances) {
7009-
for (let _values = Map_values(classInstances), j = 0, l = _values.length; j < l; ++j) {
7010-
let classInstance = _values[j];
7011-
// Chcek if the parent class is a subtype of instance's class
7012-
if (!classInstance.isAssignableTo(parentClassInstance)) continue;
7013-
let overloadInstance: Function | null;
7014-
if (isProperty) {
7015-
let boundProperty = assert(classInstance.members!.get(unboundOverloadParent.name));
7016-
assert(boundProperty.kind == ElementKind.PROPERTY_PROTOTYPE);
7017-
let boundPropertyInstance = this.resolver.resolveProperty(<PropertyPrototype>boundProperty);
7018-
if (!boundPropertyInstance) continue;
7019-
if (instance.is(CommonFlags.GET)) {
7020-
overloadInstance = boundPropertyInstance.getterInstance;
7021-
} else {
7022-
assert(instance.is(CommonFlags.SET));
7023-
overloadInstance = boundPropertyInstance.setterInstance;
7024-
}
7025-
} else {
7026-
let boundPrototype = assert(classInstance.members!.get(unboundOverloadPrototype.name));
7027-
assert(boundPrototype.kind == ElementKind.FUNCTION_PROTOTYPE);
7028-
overloadInstance = this.resolver.resolveFunction(<FunctionPrototype>boundPrototype, instance.typeArguments);
7029-
}
7030-
if (!overloadInstance || !this.compileFunction(overloadInstance)) continue;
7031-
let overloadType = overloadInstance.type;
7032-
let originalType = instance.type;
7033-
if (!overloadType.isAssignableTo(originalType)) {
7034-
this.error(
7035-
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
7036-
overloadInstance.identifierNode.range, overloadType.toString(), originalType.toString()
7037-
);
7038-
continue;
7039-
}
7040-
// TODO: additional optional parameters are not permitted by `isAssignableTo` yet
7041-
let overloadSignature = overloadInstance.signature;
7042-
let overloadParameterTypes = overloadSignature.parameterTypes;
7043-
let overloadNumParameters = overloadParameterTypes.length;
7044-
let paramExprs = new Array<ExpressionRef>(1 + overloadNumParameters);
7045-
paramExprs[0] = module.local_get(0, sizeTypeRef); // this
7046-
for (let n = 1; n <= numParameters; ++n) {
7047-
paramExprs[n] = module.local_get(n, parameterTypes[n - 1].toRef());
7048-
}
7049-
let needsVarargsStub = false;
7050-
for (let n = numParameters; n < overloadNumParameters; ++n) {
7051-
// TODO: inline constant initializers and skip varargs stub
7052-
paramExprs[1 + n] = this.makeZero(overloadParameterTypes[n], overloadInstance.declaration);
7053-
needsVarargsStub = true;
7054-
}
7055-
let calledName = needsVarargsStub
7056-
? this.ensureVarargsStub(overloadInstance).internalName
7057-
: overloadInstance.internalName;
7058-
let returnTypeRef = overloadSignature.returnType.toRef();
7059-
let stmts = new Array<ExpressionRef>();
7060-
if (needsVarargsStub) {
7061-
// Safe to prepend since paramExprs are local.get's
7062-
stmts.push(module.global_set(this.ensureArgumentsLength(), module.i32(numParameters)));
7063-
}
7064-
if (returnType == Type.void) {
7065-
stmts.push(
6997+
var overloadInstances = this.resolver.resolveOverloads(instance);
6998+
if (overloadInstances) {
6999+
for (let i = 0, k = overloadInstances.length; i < k; ++i) {
7000+
let overloadInstance = overloadInstances[i];
7001+
if (!overloadInstance.is(CommonFlags.COMPILED)) continue; // errored
7002+
let overloadType = overloadInstance.type;
7003+
let originalType = instance.type;
7004+
if (!overloadType.isAssignableTo(originalType)) {
7005+
this.error(
7006+
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
7007+
overloadInstance.identifierNode.range, overloadType.toString(), originalType.toString()
7008+
);
7009+
continue;
7010+
}
7011+
// TODO: additional optional parameters are not permitted by `isAssignableTo` yet
7012+
let overloadSignature = overloadInstance.signature;
7013+
let overloadParameterTypes = overloadSignature.parameterTypes;
7014+
let overloadNumParameters = overloadParameterTypes.length;
7015+
let paramExprs = new Array<ExpressionRef>(1 + overloadNumParameters);
7016+
paramExprs[0] = module.local_get(0, sizeTypeRef); // this
7017+
for (let n = 1; n <= numParameters; ++n) {
7018+
paramExprs[n] = module.local_get(n, parameterTypes[n - 1].toRef());
7019+
}
7020+
let needsVarargsStub = false;
7021+
for (let n = numParameters; n < overloadNumParameters; ++n) {
7022+
// TODO: inline constant initializers and skip varargs stub
7023+
paramExprs[1 + n] = this.makeZero(overloadParameterTypes[n], overloadInstance.declaration);
7024+
needsVarargsStub = true;
7025+
}
7026+
let calledName = needsVarargsStub
7027+
? this.ensureVarargsStub(overloadInstance).internalName
7028+
: overloadInstance.internalName;
7029+
let returnTypeRef = overloadSignature.returnType.toRef();
7030+
let stmts = new Array<ExpressionRef>();
7031+
if (needsVarargsStub) {
7032+
// Safe to prepend since paramExprs are local.get's
7033+
stmts.push(module.global_set(this.ensureArgumentsLength(), module.i32(numParameters)));
7034+
}
7035+
if (returnType == Type.void) {
7036+
stmts.push(
7037+
module.call(calledName, paramExprs, returnTypeRef)
7038+
);
7039+
stmts.push(
7040+
module.return()
7041+
);
7042+
} else {
7043+
stmts.push(
7044+
module.return(
70667045
module.call(calledName, paramExprs, returnTypeRef)
7067-
);
7068-
stmts.push(
7069-
module.return()
7070-
);
7071-
} else {
7072-
stmts.push(
7073-
module.return(
7074-
module.call(calledName, paramExprs, returnTypeRef)
7075-
)
7076-
);
7077-
}
7078-
builder.addCase(classInstance.id, stmts);
7079-
// Also alias each extendee inheriting this exact overload
7080-
let extendees = classInstance.getAllExtendees(
7081-
isProperty
7082-
? unboundOverloadParent.name
7083-
: instance.prototype.name
7046+
)
70847047
);
7085-
for (let _values = Set_values(extendees), a = 0, b = _values.length; a < b; ++a) {
7086-
let extendee = _values[a];
7087-
builder.addCase(extendee.id, stmts);
7088-
}
7048+
}
7049+
let classInstance = assert(overloadInstance.getClassOrInterface());
7050+
builder.addCase(classInstance.id, stmts);
7051+
// Also alias each extendee inheriting this exact overload
7052+
let extendees = classInstance.getAllExtendees(instance.declaration.name.text); // without get:/set:
7053+
for (let _values = Set_values(extendees), a = 0, b = _values.length; a < b; ++a) {
7054+
let extendee = _values[a];
7055+
builder.addCase(extendee.id, stmts);
70897056
}
70907057
}
70917058
}

src/program.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3456,7 +3456,7 @@ export class FunctionPrototype extends DeclaredElement {
34563456
/** Methods overloading this one, if any. These are unbound. */
34573457
overloads: Set<FunctionPrototype> | null = null;
34583458

3459-
/** Clones of this prototype that are bounds to specific classes. */
3459+
/** Clones of this prototype that are bound to specific classes. */
34603460
private boundPrototypes: Map<Class,FunctionPrototype> | null = null;
34613461

34623462
/** Constructs a new function prototype. */
@@ -3504,11 +3504,9 @@ export class FunctionPrototype extends DeclaredElement {
35043504
/** Tests if this prototype is bound to a class. */
35053505
get isBound(): bool {
35063506
var parent = this.parent;
3507-
return parent.kind == ElementKind.CLASS ||
3508-
parent.kind == ElementKind.PROPERTY_PROTOTYPE && (
3509-
parent.parent.kind == ElementKind.CLASS ||
3510-
parent.parent.kind == ElementKind.INTERFACE
3511-
);
3507+
var parentKind = parent.kind;
3508+
if (parentKind == ElementKind.PROPERTY_PROTOTYPE) parentKind = parent.parent.kind;
3509+
return parentKind == ElementKind.CLASS || parentKind == ElementKind.INTERFACE;
35123510
}
35133511

35143512
/** Creates a clone of this prototype that is bound to a concrete class instead. */
@@ -3661,6 +3659,16 @@ export class Function extends TypedElement {
36613659
: getDefaultParameterName(index);
36623660
}
36633661

3662+
/** Gets the class or interface this function belongs to, if an instance method. */
3663+
getClassOrInterface(): Class | null {
3664+
var parent = this.parent;
3665+
if (parent.kind == ElementKind.PROPERTY) parent = parent.parent;
3666+
if (parent.kind == ElementKind.CLASS || parent.kind == ElementKind.INTERFACE) {
3667+
return <Class>parent;
3668+
}
3669+
return null;
3670+
}
3671+
36643672
/** Creates a stub for use with this function, i.e. for varargs or virtual calls. */
36653673
newStub(postfix: string): Function {
36663674
var stub = new Function(

src/resolver.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ export class Resolver extends DiagnosticEmitter {
126126
currentThisExpression: Expression | null = null;
127127
/** Element expression of the previously resolved element access. */
128128
currentElementExpression : Expression | null = null;
129+
/** Whether a new overload has been discovered. */
130+
discoveredOverload: bool = false;
129131

130132
/** Constructs the resolver for the specified program. */
131133
constructor(
@@ -2757,6 +2759,20 @@ export class Resolver extends DiagnosticEmitter {
27572759
ctxTypes
27582760
);
27592761
prototype.setResolvedInstance(instanceKey, instance);
2762+
2763+
// remember discovered overloads for virtual stub finalization
2764+
if (classInstance) {
2765+
let methodOrPropertyName = instance.declaration.name.text;
2766+
let baseClass = classInstance.base;
2767+
while (baseClass) {
2768+
let baseMembers = baseClass.members;
2769+
if (baseMembers && baseMembers.has(methodOrPropertyName)) {
2770+
this.discoveredOverload = true;
2771+
break;
2772+
}
2773+
baseClass = baseClass.base;
2774+
}
2775+
}
27602776
return instance;
27612777
}
27622778

@@ -2833,6 +2849,59 @@ export class Resolver extends DiagnosticEmitter {
28332849
);
28342850
}
28352851

2852+
/** Resolves reachable overloads of the given instance method. */
2853+
resolveOverloads(instance: Function): Function[] | null {
2854+
var overloadPrototypes = instance.prototype.overloads;
2855+
if (!overloadPrototypes) return null;
2856+
2857+
var parentClassInstance = assert(instance.getClassOrInterface());
2858+
var overloads = new Set<Function>();
2859+
2860+
// A method's `overloads` property contains its unbound overload prototypes
2861+
// so we first have to find the concrete classes it became bound to, obtain
2862+
// their bound prototypes and make sure these are resolved.
2863+
for (let _values = Set_values(overloadPrototypes), i = 0, k = _values.length; i < k; ++i) {
2864+
let unboundOverloadPrototype = _values[i];
2865+
assert(!unboundOverloadPrototype.isBound);
2866+
let unboundOverloadParent = unboundOverloadPrototype.parent;
2867+
let isProperty = unboundOverloadParent.kind == ElementKind.PROPERTY_PROTOTYPE;
2868+
let classInstances: Map<string,Class> | null;
2869+
if (isProperty) {
2870+
let propertyParent = (<PropertyPrototype>unboundOverloadParent).parent;
2871+
assert(propertyParent.kind == ElementKind.CLASS_PROTOTYPE);
2872+
classInstances = (<ClassPrototype>propertyParent).instances;
2873+
} else {
2874+
assert(unboundOverloadParent.kind == ElementKind.CLASS_PROTOTYPE);
2875+
classInstances = (<ClassPrototype>unboundOverloadParent).instances;
2876+
}
2877+
if (!classInstances) continue;
2878+
for (let _values = Map_values(classInstances), j = 0, l = _values.length; j < l; ++j) {
2879+
let classInstance = _values[j];
2880+
// Check if the parent class is a subtype of instance's class
2881+
if (!classInstance.isAssignableTo(parentClassInstance)) continue;
2882+
let overloadInstance: Function | null;
2883+
if (isProperty) {
2884+
let boundProperty = assert(classInstance.members!.get(unboundOverloadParent.name));
2885+
assert(boundProperty.kind == ElementKind.PROPERTY_PROTOTYPE);
2886+
let boundPropertyInstance = this.resolveProperty(<PropertyPrototype>boundProperty);
2887+
if (!boundPropertyInstance) continue;
2888+
if (instance.is(CommonFlags.GET)) {
2889+
overloadInstance = boundPropertyInstance.getterInstance;
2890+
} else {
2891+
assert(instance.is(CommonFlags.SET));
2892+
overloadInstance = boundPropertyInstance.setterInstance;
2893+
}
2894+
} else {
2895+
let boundPrototype = assert(classInstance.members!.get(unboundOverloadPrototype.name));
2896+
assert(boundPrototype.kind == ElementKind.FUNCTION_PROTOTYPE);
2897+
overloadInstance = this.resolveFunction(<FunctionPrototype>boundPrototype, instance.typeArguments);
2898+
}
2899+
if (overloadInstance) overloads.add(overloadInstance);
2900+
}
2901+
}
2902+
return Set_values(overloads);
2903+
}
2904+
28362905
/** Currently resolving classes. */
28372906
private resolveClassPending: Set<Class> = new Set();
28382907

0 commit comments

Comments
 (0)