Skip to content

Commit 4da0316

Browse files
authored
Fix premature layout in resolveClass recursion (#1032)
1 parent b37cf1a commit 4da0316

14 files changed

+6641
-96
lines changed

src/builtins.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -850,7 +850,7 @@ export function compileCall(
850850
}
851851
offset = (<Field>field).memoryOffset;
852852
} else {
853-
offset = classType.currentMemoryOffset;
853+
offset = classType.nextMemoryOffset;
854854
}
855855
if (compiler.options.isWasm64) {
856856
// implicitly wrap if contextual type is a 32-bit integer

src/compiler.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1579,7 +1579,7 @@ export class Compiler extends DiagnosticEmitter {
15791579
var runtimeHeaderSize = program.runtimeHeaderSize;
15801580
var arrayPrototype = assert(program.arrayPrototype);
15811581
var arrayInstance = assert(this.resolver.resolveClass(arrayPrototype, [ elementType ]));
1582-
var arrayInstanceSize = arrayInstance.currentMemoryOffset;
1582+
var arrayInstanceSize = arrayInstance.nextMemoryOffset;
15831583
var bufferLength = bufferSegment.buffer.length - runtimeHeaderSize;
15841584
var arrayLength = i32(bufferLength / elementType.byteSize);
15851585

@@ -9123,8 +9123,8 @@ export class Compiler extends DiagnosticEmitter {
91239123
this.compileFunction(allocInstance);
91249124
return module.call(allocInstance.internalName, [
91259125
options.isWasm64
9126-
? module.i64(classInstance.currentMemoryOffset)
9127-
: module.i32(classInstance.currentMemoryOffset),
9126+
? module.i64(classInstance.nextMemoryOffset)
9127+
: module.i32(classInstance.nextMemoryOffset),
91289128
module.i32(
91299129
classInstance.hasDecorator(DecoratorFlags.UNMANAGED)
91309130
? 0

src/program.ts

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3300,7 +3300,7 @@ export class Class extends TypedElement {
33003300
/** Contextual type arguments for fields and methods. */
33013301
contextualTypeArguments: Map<string,Type> | null = null;
33023302
/** Current member memory offset. */
3303-
currentMemoryOffset: u32 = 0;
3303+
nextMemoryOffset: u32 = 0;
33043304
/** Constructor instance. */
33053305
constructorInstance: Function | null = null;
33063306
/** Operator overloads. */
@@ -3350,8 +3350,6 @@ export class Class extends TypedElement {
33503350
prototype: ClassPrototype,
33513351
/** Concrete type arguments, if any. */
33523352
typeArguments: Type[] | null = null,
3353-
/** Base class, if derived. */
3354-
base: Class | null = null,
33553353
_isInterface: bool = false // FIXME
33563354
) {
33573355
super(
@@ -3368,26 +3366,13 @@ export class Class extends TypedElement {
33683366
this.decoratorFlags = prototype.decoratorFlags;
33693367
this.typeArguments = typeArguments;
33703368
this.setType(program.options.usizeType.asClass(this));
3371-
this.base = base;
33723369

33733370
if (!this.hasDecorator(DecoratorFlags.UNMANAGED)) {
33743371
let id = program.nextClassId++;
33753372
this._id = id;
33763373
program.managedClasses.set(id, this);
33773374
}
33783375

3379-
// inherit static members and contextual type arguments from base class
3380-
if (base) {
3381-
let inheritedTypeArguments = base.contextualTypeArguments;
3382-
if (inheritedTypeArguments) {
3383-
let contextualTypeArguments = this.contextualTypeArguments;
3384-
for (let [baseName, baseType] of inheritedTypeArguments) {
3385-
if (!contextualTypeArguments) this.contextualTypeArguments = contextualTypeArguments = new Map();
3386-
contextualTypeArguments.set(baseName, baseType);
3387-
}
3388-
}
3389-
}
3390-
33913376
// apply pre-checked instance-specific contextual type arguments
33923377
var typeParameters = prototype.typeParameterNodes;
33933378
if (typeArguments) {
@@ -3407,6 +3392,26 @@ export class Class extends TypedElement {
34073392
registerConcreteElement(program, this);
34083393
}
34093394

3395+
/** Sets the base class. */
3396+
setBase(base: Class): void {
3397+
assert(!this.base);
3398+
this.base = base;
3399+
3400+
// Inherit contextual type arguments from base class
3401+
var inheritedTypeArguments = base.contextualTypeArguments;
3402+
if (inheritedTypeArguments) {
3403+
let contextualTypeArguments = this.contextualTypeArguments;
3404+
for (let [baseName, baseType] of inheritedTypeArguments) {
3405+
if (!contextualTypeArguments) {
3406+
this.contextualTypeArguments = contextualTypeArguments = new Map();
3407+
contextualTypeArguments.set(baseName, baseType);
3408+
} else if (!contextualTypeArguments.has(baseName)) {
3409+
contextualTypeArguments.set(baseName, baseType);
3410+
}
3411+
}
3412+
}
3413+
}
3414+
34103415
/** Tests if a value of this class type is assignable to a target of the specified class type. */
34113416
isAssignableTo(target: Class): bool {
34123417
var current: Class | null = this;
@@ -3460,7 +3465,7 @@ export class Class extends TypedElement {
34603465
/** Writes a field value to a buffer and returns the number of bytes written. */
34613466
writeField<T>(name: string, value: T, buffer: Uint8Array, baseOffset: i32): i32 {
34623467
var field = this.lookupInSelf(name);
3463-
if (field && field.kind == ElementKind.FIELD) {
3468+
if (field !== null && field.kind == ElementKind.FIELD) {
34643469
let offset = baseOffset + (<Field>field).memoryOffset;
34653470
switch ((<Field>field).type.kind) {
34663471
case TypeKind.I8:
@@ -3657,14 +3662,12 @@ export class Interface extends Class { // FIXME
36573662
constructor(
36583663
nameInclTypeParameters: string,
36593664
prototype: InterfacePrototype,
3660-
typeArguments: Type[] = [],
3661-
base: Interface | null = null
3665+
typeArguments: Type[] = []
36623666
) {
36633667
super(
36643668
nameInclTypeParameters,
36653669
prototype,
36663670
typeArguments,
3667-
base,
36683671
true
36693672
);
36703673
}

src/resolver.ts

Lines changed: 70 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2729,6 +2729,9 @@ export class Resolver extends DiagnosticEmitter {
27292729
);
27302730
}
27312731

2732+
/** Currently resolving classes. */
2733+
private resolveClassPending: Class[] = [];
2734+
27322735
/** Resolves a class prototype using the specified concrete type arguments. */
27332736
resolveClass(
27342737
/** The prototype of the class. */
@@ -2742,10 +2745,19 @@ export class Resolver extends DiagnosticEmitter {
27422745
): Class | null {
27432746
var instanceKey = typeArguments ? typesToString(typeArguments) : "";
27442747

2745-
// Check if this exact instance has already been resolved
2748+
// Do not attempt to resolve the same class twice. This can return a class
2749+
// that isn't fully resolved yet, but only on deeper levels of recursion.
27462750
var instance = prototype.getResolvedInstance(instanceKey);
27472751
if (instance) return instance;
27482752

2753+
// Otherwise create
2754+
var nameInclTypeParamters = prototype.name;
2755+
if (instanceKey.length) nameInclTypeParamters += "<" + instanceKey + ">";
2756+
instance = new Class(nameInclTypeParamters, prototype, typeArguments);
2757+
prototype.setResolvedInstance(instanceKey, instance);
2758+
var pendingClasses = this.resolveClassPending;
2759+
pendingClasses.push(instance);
2760+
27492761
// Insert contextual type arguments for this operation. Internally, this method is always
27502762
// called with matching type parameter / argument counts.
27512763
if (typeArguments) {
@@ -2760,13 +2772,10 @@ export class Resolver extends DiagnosticEmitter {
27602772
let typeParameterNodes = prototype.typeParameterNodes;
27612773
assert(!(typeParameterNodes && typeParameterNodes.length));
27622774
}
2763-
2764-
// There shouldn't be an instance before resolving the base class
2765-
assert(!prototype.getResolvedInstance(instanceKey));
2775+
instance.contextualTypeArguments = ctxTypes;
27662776

27672777
// Resolve base class if applicable
27682778
var basePrototype = prototype.basePrototype;
2769-
var baseClass: Class | null = null;
27702779
if (basePrototype) {
27712780
let current: ClassPrototype | null = basePrototype;
27722781
do {
@@ -2780,70 +2789,68 @@ export class Resolver extends DiagnosticEmitter {
27802789
}
27812790
} while (current = current.basePrototype);
27822791
let extendsNode = assert(prototype.extendsNode); // must be present if it has a base prototype
2783-
baseClass = this.resolveClassInclTypeArguments(
2792+
let base = this.resolveClassInclTypeArguments(
27842793
basePrototype,
27852794
extendsNode.typeArguments,
27862795
prototype.parent, // relative to derived class
27872796
makeMap(ctxTypes), // don't inherit
27882797
extendsNode,
27892798
reportMode
27902799
);
2791-
if (!baseClass) return null;
2792-
}
2800+
if (!base) return null;
2801+
instance.setBase(base);
27932802

2794-
// Construct the instance and remember that it has been resolved already
2795-
var recursiveInstanceFromResolvingBase = prototype.getResolvedInstance(instanceKey);
2796-
if (recursiveInstanceFromResolvingBase) {
2797-
// Happens if the base class already triggers resolving this class
2798-
instance = recursiveInstanceFromResolvingBase;
2799-
} else {
2800-
let nameInclTypeParamters = prototype.name;
2801-
if (instanceKey.length) nameInclTypeParamters += "<" + instanceKey + ">";
2802-
instance = new Class(nameInclTypeParamters, prototype, typeArguments, baseClass);
2803-
prototype.setResolvedInstance(instanceKey, instance);
2803+
// If the base class is still pending, yield here and instead resolve any
2804+
// derived classes once the base class's `finishResolveClass` is done.
2805+
// This is guaranteed to never happen at the entry of the recursion, i.e.
2806+
// where `resolveClass` is called from other code.
2807+
if (pendingClasses.includes(base)) return instance;
28042808
}
2805-
instance.contextualTypeArguments = ctxTypes; // unique (as specified by the caller)
28062809

2807-
// Inherit base class members and set up the initial memory offset for own fields
2810+
// We only get here if the base class has been fully resolved already.
2811+
this.finishResolveClass(instance, reportMode);
2812+
return instance;
2813+
}
2814+
2815+
/** Finishes resolving the specified class. */
2816+
private finishResolveClass(
2817+
/** Class to finish resolving. */
2818+
instance: Class,
2819+
/** How to proceed with eventual diagnostics. */
2820+
reportMode: ReportMode
2821+
): void {
2822+
var instanceMembers = instance.members;
2823+
if (!instanceMembers) instance.members = instanceMembers = new Map();
2824+
2825+
// Alias base members
2826+
var pendingClasses = this.resolveClassPending;
28082827
var memoryOffset: u32 = 0;
2809-
if (baseClass) {
2810-
let baseMembers = baseClass.members;
2828+
var base = instance.base;
2829+
if (base) {
2830+
assert(!pendingClasses.includes(base));
2831+
let baseMembers = base.members;
28112832
if (baseMembers) {
2812-
let instanceMembers = instance.members;
2813-
if (!instanceMembers) instance.members = instanceMembers = new Map();
28142833
for (let [baseMemberName, baseMember] of baseMembers) {
28152834
instanceMembers.set(baseMemberName, baseMember);
28162835
}
28172836
}
2818-
memoryOffset = baseClass.currentMemoryOffset;
2837+
memoryOffset = base.nextMemoryOffset;
28192838
}
28202839

28212840
// Resolve instance members
2841+
var prototype = instance.prototype;
28222842
var instanceMemberPrototypes = prototype.instanceMembers;
28232843
if (instanceMemberPrototypes) {
28242844
for (let member of instanceMemberPrototypes.values()) {
28252845
switch (member.kind) {
28262846

2827-
// Lay out fields in advance
28282847
case ElementKind.FIELD_PROTOTYPE: {
2829-
let instanceMembers = instance.members;
2830-
if (!instanceMembers) instance.members = instanceMembers = new Map();
2831-
else if (instanceMembers.has(member.name)) {
2832-
let existing = instanceMembers.get(member.name)!;
2833-
this.errorRelated(
2834-
DiagnosticCode.Duplicate_identifier_0,
2835-
(<FieldPrototype>member).identifierNode.range,
2836-
existing.declaration.name.range,
2837-
member.name
2838-
);
2839-
break;
2840-
}
28412848
let fieldTypeNode = (<FieldPrototype>member).typeNode;
28422849
let fieldType: Type | null = null;
28432850
// TODO: handle duplicate non-private fields specifically?
28442851
if (!fieldTypeNode) {
2845-
if (baseClass) {
2846-
let baseMembers = baseClass.members;
2852+
if (base) {
2853+
let baseMembers = base.members;
28472854
if (baseMembers && baseMembers.has((<FieldPrototype>member).name)) {
28482855
let baseField = baseMembers.get((<FieldPrototype>member).name)!;
28492856
if (!baseField.is(CommonFlags.PRIVATE)) {
@@ -2869,13 +2876,13 @@ export class Resolver extends DiagnosticEmitter {
28692876
);
28702877
}
28712878
if (!fieldType) break; // did report above
2872-
let fieldInstance = new Field(<FieldPrototype>member, instance, fieldType);
2879+
let field = new Field(<FieldPrototype>member, instance, fieldType);
28732880
assert(isPowerOf2(fieldType.byteSize));
28742881
let mask = fieldType.byteSize - 1;
28752882
if (memoryOffset & mask) memoryOffset = (memoryOffset | mask) + 1;
2876-
fieldInstance.memoryOffset = memoryOffset;
2883+
field.memoryOffset = memoryOffset;
28772884
memoryOffset += fieldType.byteSize;
2878-
instance.add(member.name, fieldInstance); // reports
2885+
instance.add(member.name, field); // reports
28792886
break;
28802887
}
28812888
case ElementKind.FUNCTION_PROTOTYPE: {
@@ -2923,7 +2930,7 @@ export class Resolver extends DiagnosticEmitter {
29232930
}
29242931

29252932
// Finalize memory offset
2926-
instance.currentMemoryOffset = memoryOffset;
2933+
instance.nextMemoryOffset = memoryOffset;
29272934

29282935
// Link _own_ constructor if present
29292936
{
@@ -2933,7 +2940,7 @@ export class Resolver extends DiagnosticEmitter {
29332940
let ctorInstance = this.resolveFunction(
29342941
<FunctionPrototype>ctorPrototype,
29352942
null,
2936-
instance.contextualTypeArguments,
2943+
assert(instance.contextualTypeArguments),
29372944
reportMode
29382945
);
29392946
if (ctorInstance) instance.constructorInstance = <Function>ctorInstance;
@@ -3002,7 +3009,24 @@ export class Resolver extends DiagnosticEmitter {
30023009
}
30033010
}
30043011
}
3005-
return instance;
3012+
3013+
// Remove this class from pending
3014+
var pendingIndex = pendingClasses.indexOf(instance);
3015+
assert(~pendingIndex); // must be pending
3016+
pendingClasses.splice(pendingIndex, 1);
3017+
3018+
// Finish derived classes that we postponed in `resolveClass` due to the
3019+
// base class still being pending, again triggering `finishResolveClass`
3020+
// of any classes derived from those classes, ultimately leading to all
3021+
// pending classes being resolved.
3022+
var derivedPendingClasses = new Array<Class>();
3023+
for (let i = 0, k = pendingClasses.length; i < k; ++i) {
3024+
let pending = pendingClasses[i];
3025+
if (instance == pending.base) derivedPendingClasses.push(pending);
3026+
}
3027+
for (let i = 0, k = derivedPendingClasses.length; i < k; ++i) {
3028+
this.finishResolveClass(derivedPendingClasses[i], reportMode);
3029+
}
30063030
}
30073031

30083032
/** Resolves a class prototype by first resolving the specified type arguments. */

0 commit comments

Comments
 (0)