inherent acylic data structure detection

this will become useful for the pure collector eventually
This commit is contained in:
dcode
2019-04-08 03:49:41 +02:00
parent 57c8bd01ca
commit c3ff3f2376
43 changed files with 1927 additions and 546 deletions

View File

@ -4233,7 +4233,6 @@ function typeToRuntimeFlags(type: Type, program: Program): RTTIFlags {
/** Compiles runtime type information for use by stdlib. */
export function compileRTTI(compiler: Compiler): void {
// TODO: only add this if actually accessed?
var program = compiler.program;
var module = compiler.module;
var managedClasses = program.managedClasses;
@ -4242,22 +4241,26 @@ export function compileRTTI(compiler: Compiler): void {
var data = new Uint8Array(size);
writeI32(count, data, 0);
var off = 8;
var arrayPrototype = assert(program.arrayPrototype);
var setPrototype = assert(program.setPrototype);
var mapPrototype = assert(program.mapPrototype);
var lastId = 0;
for (let [id, instance] of managedClasses) {
assert(id == ++lastId);
let flags: RTTIFlags = 0;
if (instance.prototype.extends(program.arrayPrototype)) {
let typeArguments = assert(instance.typeArguments);
if (instance.isAcyclic) flags |= RTTIFlags.ACYCLIC;
if (instance.prototype.extends(arrayPrototype)) {
let typeArguments = assert(instance.getTypeArgumentsTo(arrayPrototype));
assert(typeArguments.length == 1);
flags |= RTTIFlags.ARRAY;
flags |= RTTIFlags.VALUE_ALIGN_0 * typeToRuntimeFlags(typeArguments[0], program);
} else if (instance.prototype.extends(program.setPrototype)) {
let typeArguments = assert(instance.typeArguments);
} else if (instance.prototype.extends(setPrototype)) {
let typeArguments = assert(instance.getTypeArgumentsTo(setPrototype));
assert(typeArguments.length == 1);
flags |= RTTIFlags.SET;
flags |= RTTIFlags.VALUE_ALIGN_0 * typeToRuntimeFlags(typeArguments[0], program);
} else if (instance.prototype.extends(program.mapPrototype)) {
let typeArguments = assert(instance.typeArguments);
} else if (instance.prototype.extends(mapPrototype)) {
let typeArguments = assert(instance.getTypeArgumentsTo(mapPrototype));
assert(typeArguments.length == 2);
flags |= RTTIFlags.MAP;
flags |= RTTIFlags.KEY_ALIGN_0 * typeToRuntimeFlags(typeArguments[0], program);

View File

@ -2971,9 +2971,8 @@ export class ClassPrototype extends DeclaredElement {
/** Tests if this prototype extends the specified. */
extends(basePtototype: ClassPrototype | null): bool {
var current: ClassPrototype | null = this;
do {
if (current === basePtototype) return true;
} while (current = current.basePrototype);
do if (current === basePtototype) return true;
while (current = current.basePrototype);
return false;
}
@ -3022,6 +3021,12 @@ export class ClassPrototype extends DeclaredElement {
}
}
const enum AcyclicState {
UNKNOWN,
ACYCLIC,
NOT_ACYCLIC
}
/** A resolved class. */
export class Class extends TypedElement {
@ -3041,6 +3046,8 @@ export class Class extends TypedElement {
overloads: Map<OperatorKind,Function> | null = null;
/** Unique class id. */
private _id: u32 = 0;
/** Remembers acyclic state. */
private _acyclic: AcyclicState = AcyclicState.UNKNOWN;
/** Gets the unique runtime id of this class. Must be a managed class. */
get id(): u32 {
@ -3225,6 +3232,100 @@ export class Class extends TypedElement {
assert(false);
return 0;
}
/** Tests if this class extends the specified prototype. */
extends(prototype: ClassPrototype): bool {
return this.prototype.extends(prototype);
}
/** Gets the concrete type arguments to the specified extendend prototype. */
getTypeArgumentsTo(extendedPrototype: ClassPrototype): Type[] | null {
var current: Class | null = this;
do if (current.prototype === extendedPrototype) return current.typeArguments;
while (current = current.base);
return null;
}
/** Tests if this class is inherently acyclic. */
get isAcyclic(): bool {
var acyclic = this._acyclic;
if (acyclic == AcyclicState.UNKNOWN) {
let hasCycle = this.cyclesTo(this);
if (hasCycle) this._acyclic = acyclic = AcyclicState.NOT_ACYCLIC;
else this._acyclic = acyclic = AcyclicState.ACYCLIC;
}
return acyclic == AcyclicState.ACYCLIC;
}
/** Tests if this class potentially forms a reference cycle to another one. */
private cyclesTo(other: Class, except: Set<Class> = new Set()): bool {
if (except.has(this)) return false;
except.add(this); // don't recurse indefinitely
// Find out if any field references 'other' directly or indirectly
var current: Class | null;
var members = this.members;
if (members) {
for (let member of members.values()) {
if (
member.kind == ElementKind.FIELD &&
(current = (<Field>member).type.classReference) !== null &&
(
current === other ||
current.cyclesTo(other, except)
)
) return true;
}
}
// Do the same for non-field data
var basePrototype: ClassPrototype | null;
// Array<T->other?>
if ((basePrototype = this.program.arrayPrototype) && this.prototype.extends(basePrototype)) {
let typeArguments = assert(this.getTypeArgumentsTo(basePrototype));
assert(typeArguments.length == 1);
if (
(current = typeArguments[0].classReference) !== null &&
(
current === other ||
current.cyclesTo(other, except)
)
) return true;
// Set<K->other?>
} else if ((basePrototype = this.program.setPrototype) && this.prototype.extends(basePrototype)) {
let typeArguments = assert(this.getTypeArgumentsTo(basePrototype));
assert(typeArguments.length == 1);
if (
(current = typeArguments[0].classReference) !== null &&
(
current === other ||
current.cyclesTo(other, except)
)
) return true;
// Map<K->other?,V->other?>
} else if ((basePrototype = this.program.mapPrototype) && this.prototype.extends(basePrototype)) {
let typeArguments = assert(this.getTypeArgumentsTo(basePrototype));
assert(typeArguments.length == 2);
if (
(current = typeArguments[0].classReference) !== null &&
(
current === other ||
current.cyclesTo(other, except)
)
) return true;
if (
(current = typeArguments[1].classReference) !== null &&
(
current === other ||
current.cyclesTo(other, except)
)
) return true;
}
return false;
}
}
/** A yet unresolved interface. */