Skip to content

Commit 2faaf32

Browse files
committed
Add Closures
1 parent 861a97b commit 2faaf32

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+31711
-7639
lines changed

Diff for: src/common.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export enum CommonFlags {
7171
/** Is a virtual method. */
7272
VIRTUAL = 1 << 26,
7373
/** Is (part of) a closure. */
74-
CLOSURE = 1 << 27,
74+
IN_SCOPE_CLOSURE = 1 << 27,
7575

7676
// Other
7777

Diff for: src/compiler.ts

+387-55
Large diffs are not rendered by default.

Diff for: src/diagnosticMessages.generated.ts

+2
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ export enum DiagnosticCode {
173173
File_0_not_found = 6054,
174174
Numeric_separators_are_not_allowed_here = 6188,
175175
Multiple_consecutive_numeric_separators_are_not_permitted = 6189,
176+
Closure_support_is_experimental = 6190,
176177
_super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class = 17009,
177178
_super_must_be_called_before_accessing_a_property_of_super_in_the_constructor_of_a_derived_class = 17011
178179
}
@@ -346,6 +347,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
346347
case 6054: return "File '{0}' not found.";
347348
case 6188: return "Numeric separators are not allowed here.";
348349
case 6189: return "Multiple consecutive numeric separators are not permitted.";
350+
case 6190: return "Closure support is experimental.";
349351
case 17009: return "'super' must be called before accessing 'this' in the constructor of a derived class.";
350352
case 17011: return "'super' must be called before accessing a property of 'super' in the constructor of a derived class.";
351353
default: return "";

Diff for: src/diagnosticMessages.json

+1
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@
170170
"File '{0}' not found.": 6054,
171171
"Numeric separators are not allowed here.": 6188,
172172
"Multiple consecutive numeric separators are not permitted.": 6189,
173+
"Closure support is experimental.": 6190,
173174
"'super' must be called before accessing 'this' in the constructor of a derived class.": 17009,
174175
"'super' must be called before accessing a property of 'super' in the constructor of a derived class.": 17011
175176
}

Diff for: src/program.ts

+51-3
Original file line numberDiff line numberDiff line change
@@ -3275,7 +3275,9 @@ export class Local extends VariableLikeElement {
32753275
/** Parent function. */
32763276
parent: Function,
32773277
/** Declaration reference. */
3278-
declaration: VariableLikeDeclarationStatement = parent.program.makeNativeVariableDeclaration(name)
3278+
declaration: VariableLikeDeclarationStatement = parent.program.makeNativeVariableDeclaration(name),
3279+
/** Offset of this variable within closure context, if it's value is held there */
3280+
public closureContextOffset: usize = 0,
32793281
) {
32803282
super(
32813283
ElementKind.LOCAL,
@@ -3286,6 +3288,18 @@ export class Local extends VariableLikeElement {
32863288
this.index = index;
32873289
assert(type != Type.void);
32883290
this.setType(type);
3291+
this.closureContextOffset = closureContextOffset;
3292+
}
3293+
3294+
close(offset: usize): Local {
3295+
return new Local(
3296+
this.name,
3297+
this.index,
3298+
this.type,
3299+
<Function>this.parent,
3300+
<VariableLikeDeclarationStatement>this.declaration,
3301+
offset
3302+
);
32893303
}
32903304
}
32913305

@@ -3354,6 +3368,11 @@ export class FunctionPrototype extends DeclaredElement {
33543368
);
33553369
}
33563370

3371+
get hasNestedDefinition(): bool {
3372+
var parent = this.parent;
3373+
return parent.kind == ElementKind.FUNCTION;
3374+
}
3375+
33573376
/** Creates a clone of this prototype that is bound to a concrete class instead. */
33583377
toBound(classInstance: Class): FunctionPrototype {
33593378
assert(this.is(CommonFlags.INSTANCE));
@@ -3413,6 +3432,12 @@ export class Function extends TypedElement {
34133432
additionalLocals: Type[] = [];
34143433
/** Concrete type arguments. */
34153434
typeArguments: Type[] | null;
3435+
/** List of all closed locals discovered so far */
3436+
closedLocals: Map<string, Local> = new Map();
3437+
/** Next global closure offset to use, assuming that classes are packed as c-structs in the order given */
3438+
/** This is temporary- once we have a ScopeAnalyzer, then the closure class will be defined before we */
3439+
/** start compiling, and we can just insert a field access */
3440+
nextGlobalClosureOffset: u32 = 4;
34163441
/** Contextual type arguments. */
34173442
contextualTypeArguments: Map<string,Type> | null;
34183443
/** Default control flow. */
@@ -3466,11 +3491,11 @@ export class Function extends TypedElement {
34663491
this.type = program.options.usizeType.asFunction(signature);
34673492
if (!prototype.is(CommonFlags.AMBIENT)) {
34683493
let localIndex = 0;
3469-
if (this.is(CommonFlags.INSTANCE)) {
3494+
if (signature.thisType !== null) {
34703495
let local = new Local(
34713496
CommonNames.this_,
34723497
localIndex++,
3473-
assert(signature.thisType),
3498+
signature.thisType!, // asc can't see the !== null above
34743499
this
34753500
);
34763501
this.localsByName.set(CommonNames.this_, local);
@@ -3536,6 +3561,29 @@ export class Function extends TypedElement {
35363561
lookup(name: string): Element | null {
35373562
var locals = this.localsByName;
35383563
if (locals.has(name)) return assert(locals.get(name));
3564+
if (this.parent.kind == ElementKind.FUNCTION) {
3565+
var parentFunction = <Function>this.parent;
3566+
var parentResult = parentFunction.flow.lookup(name);
3567+
if (parentResult === null) return null;
3568+
if (parentFunction.closedLocals.size > 0) { //TODO allow nested closure definitions
3569+
this.program.error(
3570+
DiagnosticCode.Not_implemented,
3571+
this.identifierNode.range, this.identifierNode.text
3572+
);
3573+
return null;
3574+
}
3575+
if (parentResult.kind == ElementKind.LOCAL) {
3576+
let local = changetype<Local>(parentResult);
3577+
if (this.closedLocals.has(local.name)) return assert(this.closedLocals.get(local.name));
3578+
let mask = local.type.byteSize - 1;
3579+
let memoryOffset = this.nextGlobalClosureOffset;
3580+
if (memoryOffset & mask) memoryOffset = (memoryOffset | mask) + 1;
3581+
var closedLocal = local.close(memoryOffset);
3582+
this.nextGlobalClosureOffset = memoryOffset + local.type.byteSize;
3583+
this.closedLocals.set(local.name, closedLocal);
3584+
return closedLocal;
3585+
}
3586+
}
35393587
return this.parent.lookup(name);
35403588
}
35413589

Diff for: src/resolver.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2750,6 +2750,7 @@ export class Resolver extends DiagnosticEmitter {
27502750
var signature = new Signature(this.program, parameterTypes, returnType, thisType);
27512751
signature.parameterNames = parameterNames;
27522752
signature.requiredParameters = requiredParameters;
2753+
if (prototype.hasNestedDefinition) signature = signature.toClosureSignature();
27532754

27542755
var nameInclTypeParameters = prototype.name;
27552756
if (instanceKey.length) nameInclTypeParameters += "<" + instanceKey + ">";

Diff for: src/types.ts

+51-14
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
Class,
88
FunctionTarget,
99
Program,
10-
DecoratorFlags
10+
DecoratorFlags,
11+
Local
1112
} from "./program";
1213

1314
import {
@@ -95,7 +96,9 @@ export const enum TypeFlags {
9596
/** Is a vector type. */
9697
VECTOR = 1 << 10,
9798
/** Is a host type. */
98-
HOST = 1 << 11
99+
HOST = 1 << 11,
100+
/** Is a closure type. */
101+
IN_SCOPE_CLOSURE = 1 << 12
99102
}
100103

101104
/** Represents a resolved type. */
@@ -113,6 +116,8 @@ export class Type {
113116
classReference: Class | null;
114117
/** Underlying signature reference, if a function type. */
115118
signatureReference: Signature | null;
119+
/** closed over locals */
120+
locals: Map<string, Local> | null;
116121
/** Respective non-nullable type, if nullable. */
117122
nonNullableType: Type;
118123
/** Cached nullable type, if non-nullable. */
@@ -127,6 +132,7 @@ export class Type {
127132
this.classReference = null;
128133
this.signatureReference = null;
129134
this.nonNullableType = this;
135+
this.locals = null;
130136
}
131137

132138
/** Returns the closest int type representing this type. */
@@ -163,6 +169,7 @@ export class Type {
163169

164170
/** Tests if this is a managed type that needs GC hooks. */
165171
get isManaged(): bool {
172+
if (this.isFunctionIndex) return true;
166173
if (this.is(TypeFlags.INTEGER | TypeFlags.REFERENCE)) {
167174
let classReference = this.classReference;
168175
if (classReference) return !classReference.hasDecorator(DecoratorFlags.UNMANAGED);
@@ -177,6 +184,10 @@ export class Type {
177184
return classReference !== null && classReference.hasDecorator(DecoratorFlags.UNMANAGED);
178185
}
179186

187+
get isFunctionIndex(): bool {
188+
return this.signatureReference !== null && !this.is(TypeFlags.IN_SCOPE_CLOSURE);
189+
}
190+
180191
/** Computes the sign-extending shift in the target type. */
181192
computeSmallIntegerShift(targetType: Type): i32 {
182193
return targetType.size - this.size;
@@ -517,6 +528,13 @@ export class Type {
517528

518529
/** Alias of i32 indicating type inference of locals and globals with just an initializer. */
519530
static readonly auto: Type = new Type(Type.i32.kind, Type.i32.flags, Type.i32.size);
531+
532+
/** Type of an in-context local */
533+
static readonly closure32: Type = new Type(Type.i32.kind,
534+
TypeFlags.IN_SCOPE_CLOSURE |
535+
TypeFlags.REFERENCE,
536+
Type.i32.size
537+
);
520538
}
521539

522540
/** Converts an array of types to an array of native types. */
@@ -626,18 +644,8 @@ export class Signature {
626644
: getDefaultParameterName(index);
627645
}
628646

629-
/** Tests if this signature equals the specified. */
630-
equals(other: Signature): bool {
631-
632-
// check `this` type
633-
var thisThisType = this.thisType;
634-
var otherThisType = other.thisType;
635-
if (thisThisType !== null) {
636-
if (otherThisType === null || !thisThisType.equals(otherThisType)) return false;
637-
} else if (otherThisType) {
638-
return false;
639-
}
640-
647+
// Check to see if this signature is equivalent to the caller, ignoring the this type
648+
externalEquals(other: Signature): bool {
641649
// check rest parameter
642650
if (this.hasRest != other.hasRest) return false;
643651

@@ -654,6 +662,20 @@ export class Signature {
654662
return this.returnType.equals(other.returnType);
655663
}
656664

665+
/** Tests if this signature equals the specified. */
666+
equals(other: Signature): bool {
667+
// check `this` type
668+
var thisThisType = this.thisType;
669+
var otherThisType = other.thisType;
670+
if (thisThisType !== null) {
671+
if (otherThisType === null || !thisThisType.equals(otherThisType)) return false;
672+
} else if (otherThisType !== null) {
673+
return false;
674+
}
675+
676+
return this.externalEquals(other);
677+
}
678+
657679
/** Tests if a value of this function type is assignable to a target of the specified function type. */
658680
isAssignableTo(target: Signature, requireSameSize: bool = false): bool {
659681

@@ -720,6 +742,20 @@ export class Signature {
720742
return sb.join("");
721743
}
722744

745+
toClosureSignature(): Signature {
746+
var closureSignature = this.clone();
747+
closureSignature.thisType = this.program.options.usizeType;
748+
return closureSignature;
749+
}
750+
751+
// Reverses toClosureSignature, for when we recompile a function with the context argument
752+
// Not convinced this is the right way to go about getting the original unmodified signature, but it works
753+
toAnonymousSignature(): Signature {
754+
var normalSignature = this.clone();
755+
normalSignature.thisType = null;
756+
return normalSignature;
757+
}
758+
723759
/** Creates a clone of this signature that is safe to modify. */
724760
clone(): Signature {
725761
var parameterTypes = this.parameterTypes;
@@ -738,6 +774,7 @@ export class Signature {
738774
}
739775
clone.parameterNames = cloneParameterNames;
740776
}
777+
clone.requiredParameters = this.requiredParameters;
741778
return clone;
742779
}
743780
}

0 commit comments

Comments
 (0)