Skip to content

Commit 5471859

Browse files
committed
early draft of closures
1 parent 9c1c3da commit 5471859

File tree

7 files changed

+84
-109
lines changed

7 files changed

+84
-109
lines changed

src/builtins.ts

-2
Original file line numberDiff line numberDiff line change
@@ -559,8 +559,6 @@ export namespace BuiltinNames {
559559
export const rtti_base = "~lib/rt/__rtti_base";
560560
export const visit_globals = "~lib/rt/__visit_globals";
561561
export const visit_members = "~lib/rt/__visit_members";
562-
563-
// std/closure.ts
564562
export const global_closure = "~lib/closure/__global_closure";
565563

566564
// std/number.ts

src/compiler.ts

+67-74
Original file line numberDiff line numberDiff line change
@@ -412,13 +412,13 @@ export class Compiler extends DiagnosticEmitter {
412412
if (options.isWasm64) {
413413
module.addGlobal(BuiltinNames.heap_base, NativeType.I64, true, module.i64(0));
414414
module.addGlobal(BuiltinNames.rtti_base, NativeType.I64, true, module.i64(0));
415+
module.addGlobal(BuiltinNames.global_closure, NativeType.I64, true, module.i64(-1));
415416
} else {
416417
module.addGlobal(BuiltinNames.heap_base, NativeType.I32, true, module.i32(0));
417418
module.addGlobal(BuiltinNames.rtti_base, NativeType.I32, true, module.i32(0));
419+
module.addGlobal(BuiltinNames.global_closure, NativeType.I32, true, module.i32(-1));
418420
}
419421

420-
module.addGlobal(BuiltinNames.global_closure, NativeType.I32, true, module.i32(-1));
421-
422422
// compile entry file(s) while traversing reachable elements
423423
var files = program.filesByName;
424424
// TODO: for (let file of files.values()) {
@@ -1826,6 +1826,10 @@ export class Compiler extends DiagnosticEmitter {
18261826
var functionTable = this.functionTable;
18271827
var tableBase = this.options.tableBase;
18281828
if (!tableBase) tableBase = 1; // leave first elem blank
1829+
// Skip every 16th entry, which is resevered for closures
1830+
if (tableBase + functionTable.length % 16 == 0) {
1831+
functionTable.push("")
1832+
}
18291833
index = tableBase + functionTable.length;
18301834
if (!instance.is(CommonFlags.TRAMPOLINE) && instance.signature.requiredParameters < instance.signature.parameterTypes.length) {
18311835
// insert the trampoline if the function has optional parameters
@@ -3411,7 +3415,7 @@ export class Compiler extends DiagnosticEmitter {
34113415
// pretend to retain the expression immediately so the autorelease, if any, is skipped
34123416
var expr = this.compileExpression(expression, returnType, constraints | Constraints.WILL_RETAIN);
34133417
var flow = this.currentFlow;
3414-
if (returnType.isManaged || returnType.signatureReference) {
3418+
if (returnType.isManaged) {
34153419
// check if that worked, and if it didn't, keep the reference alive
34163420
if (!this.skippedAutoreleases.has(expr)) {
34173421
let index = this.tryUndoAutorelease(expr, flow);
@@ -3464,8 +3468,17 @@ export class Compiler extends DiagnosticEmitter {
34643468
}
34653469
fromType = fromType.nonNullableType;
34663470
}
3471+
// When we convert from the closure type into a function pointer, we first
3472+
// update the local copy of the scope with the newest values
34673473
if(fromType.is(TypeFlags.CLOSURE)) {
3468-
return module.block(null, [this.injectClosedLocals(expr, fromType), expr], NativeType.I32)
3474+
//Retain the Closure Class memory so it isn't freed when we change it into a function ptr
3475+
if (!this.skippedAutoreleases.has(expr)) {
3476+
let index = this.tryUndoAutorelease(expr, this.currentFlow);
3477+
if (index == -1) expr = this.makeRetain(expr);
3478+
this.skippedAutoreleases.add(expr);
3479+
}
3480+
// TODO: check first to ensure that signatures are compatible
3481+
return module.block(null, [this.injectClosedLocals(expr, fromType), expr], toType.toNativeType())
34693482
}
34703483
if (fromType.isAssignableTo(toType)) { // downcast or same
34713484
assert(fromType.kind == toType.kind);
@@ -5803,15 +5816,12 @@ export class Compiler extends DiagnosticEmitter {
58035816
// fall-through
58045817
}
58055818
case ElementKind.LOCAL:
5819+
case ElementKind.CLOSEDLOCAL:
58065820
case ElementKind.FIELD: {
58075821
targetType = (<VariableLikeElement>target).type;
58085822
if (target.hasDecorator(DecoratorFlags.UNSAFE)) this.checkUnsafe(expression);
58095823
break;
58105824
}
5811-
case ElementKind.CLOSEDLOCAL: {
5812-
targetType = Type.i32
5813-
break;
5814-
}
58155825
case ElementKind.PROPERTY_PROTOTYPE: { // static property
58165826
let propertyPrototype = <PropertyPrototype>target;
58175827
let setterPrototype = propertyPrototype.setterPrototype;
@@ -5937,21 +5947,12 @@ export class Compiler extends DiagnosticEmitter {
59375947
return this.makeLocalAssignment(local, valueExpr, valueType, tee);
59385948
}
59395949
case ElementKind.CLOSEDLOCAL: {
5950+
// TODO: ability to update closed over locals
59405951
this.error(
59415952
DiagnosticCode.Not_implemented,
59425953
valueExpression.range
59435954
);
59445955
return module.unreachable();
5945-
//return module.block(null, [
5946-
//module.store(
5947-
//1,
5948-
//this.module.global_get(BuiltinNames.global_closure, this.options.nativeSizeType),
5949-
//valueExpr,
5950-
//NativeType.I32,
5951-
//4
5952-
//),
5953-
//valueExpr
5954-
//], NativeType.I32)
59555956
}
59565957
case ElementKind.GLOBAL: {
59575958
let global = <Global>target;
@@ -6332,18 +6333,19 @@ export class Compiler extends DiagnosticEmitter {
63326333
): ExpressionRef {
63336334
var module = this.module;
63346335
var locals = type.locals!;
6335-
var exprs = new Array<ExpressionRef>();
6336-
for (let _values = Map_values(locals), i = 0, k = _values.length; i < k; ++i) {
6336+
let _values = Map_values(locals);
6337+
var exprs = new Array<ExpressionRef>(_values.length);
6338+
for (let i = 0, k = _values.length; i < k; ++i) {
63376339
let local = unchecked(_values[i]);
6338-
exprs.push(module.store(
6340+
let closureClass= type.classReference!
6341+
exprs[i] = module.store(
63396342
local.type.byteSize,
63406343
expr,
6341-
this.module.local_get(local.index, NativeType.I32),
6342-
NativeType.I32,
6343-
(i + 1) * 4
6344-
))
6344+
this.module.local_get(local.index, local.type.toNativeType()),
6345+
local.type.toNativeType(),
6346+
closureClass.offsetof(local.name)
6347+
)
63456348
}
6346-
//exprs.push(module.unreachable())
63476349
return module.block(null, exprs)
63486350
}
63496351

@@ -6424,7 +6426,6 @@ export class Compiler extends DiagnosticEmitter {
64246426

64256427
var signature: Signature | null;
64266428
var indexArg: ExpressionRef;
6427-
var closedLocal: Local | null = null;
64286429
switch (target.kind) {
64296430
// direct call: concrete function
64306431
case ElementKind.FUNCTION_PROTOTYPE: {
@@ -6451,36 +6452,34 @@ export class Compiler extends DiagnosticEmitter {
64516452
// indirect call: index argument with signature (non-generic, can't be inlined)
64526453
case ElementKind.LOCAL: {
64536454
let local = <Local>target;
6455+
signature = local.type.signatureReference;
64546456
if(local.type.is(TypeFlags.CLOSURE)) {
6455-
signature = new Signature(this.program, [], Type.i32)
6456-
flow.locals = local.type.locals!;
6457-
64586457
indexArg = module.block(null, [
64596458
this.injectClosedLocals(
64606459
this.module.local_get(local.index, this.options.nativeSizeType),
64616460
local.type
64626461
),
64636462
module.global_set(
64646463
BuiltinNames.global_closure,
6465-
this.module.local_get(local.index, NativeType.I32)
6464+
this.module.local_get(local.index, this.options.nativeSizeType)
64666465
),
64676466
module.load(
6468-
4,
6467+
local.type.byteSize,
64696468
local.type.is(TypeFlags.SIGNED),
6470-
this.module.local_get(local.index, NativeType.I32),
6471-
NativeType.I32,
6469+
this.module.local_get(local.index, this.options.nativeSizeType),
6470+
this.options.nativeSizeType,
64726471
0
64736472
)
6474-
], NativeType.I32);
6473+
], this.options.nativeSizeType);
64756474
break;
64766475
}
6477-
signature = local.type.signatureReference;
64786476
if (signature) {
64796477
if (local.is(CommonFlags.INLINED)) {
64806478
indexArg = module.i32(i64_low(local.constantIntegerValue));
64816479
} else {
64826480
indexArg = module.local_get(local.index, NativeType.I32);
64836481
}
6482+
// If the 16 is divisible by 16, then it must be a closure, so we call it as such
64846483
indexArg = module.if(
64856484
module.binary(
64866485
BinaryOp.EqI32,
@@ -6496,14 +6495,14 @@ export class Compiler extends DiagnosticEmitter {
64966495
BuiltinNames.global_closure,
64976496
indexArg
64986497
),
6499-
module.load(
6498+
module.load( //TODO: support 8 byte addrs
65006499
4,
65016500
true,
65026501
indexArg,
65036502
NativeType.I32,
65046503
0
65056504
)
6506-
], NativeType.I32),
6505+
], this.options.nativeSizeType),
65076506
indexArg
65086507
)
65096508
break;
@@ -6607,32 +6606,15 @@ export class Compiler extends DiagnosticEmitter {
66076606
return module.unreachable();
66086607
}
66096608
}
6610-
var callExpr = this.compileCallIndirect(
6609+
6610+
return this.compileCallIndirect(
66116611
assert(signature), // FIXME: asc can't see this yet
66126612
indexArg,
66136613
expression.arguments,
66146614
expression,
66156615
0,
66166616
contextualType == Type.void
66176617
);
6618-
6619-
//Closure write-back
6620-
//if (closedLocal) {
6621-
//return module.block(null, [
6622-
//callExpr,
6623-
//module.local_set(
6624-
//closedLocal.index,
6625-
//module.load(
6626-
//1,
6627-
//true,
6628-
//this.module.global_get(BuiltinNames.global_closure, NativeType.I32),
6629-
//NativeType.I32,
6630-
//4
6631-
//)
6632-
//)
6633-
//], NativeType.I32)
6634-
//}
6635-
return callExpr;
66366618
}
66376619

66386620
private compileCallExpressionBuiltin(
@@ -7129,7 +7111,6 @@ export class Compiler extends DiagnosticEmitter {
71297111
var module = this.module;
71307112
var flow = this.currentFlow;
71317113
var nativeSizeType = this.options.nativeSizeType;
7132-
assert(false)
71337114
if (alreadyRetained) {
71347115
// (t1=newExpr), __release(oldExpr), t1
71357116
// it is important that `newExpr` evaluates before `oldExpr` is released, hence the local
@@ -7196,7 +7177,6 @@ export class Compiler extends DiagnosticEmitter {
71967177
//
71977178
// callReceivingAReference((__release(t = callReturningAReference()), t))
71987179
//
7199-
assert(false)
72007180
var local = flow.getAutoreleaseLocal(type);
72017181
if (flow.isNonnull(expr, type)) flow.setLocalFlag(local.index, LocalFlags.NONNULL);
72027182
return this.module.local_tee(local.index, expr);
@@ -7723,8 +7703,8 @@ export class Compiler extends DiagnosticEmitter {
77237703
var instance: Function | null;
77247704
var contextualTypeArguments = makeMap(flow.contextualTypeArguments);
77257705

7726-
//compile according to context. this differs from a normal function in that omitted parameter
7727-
//and return types can be inferred and omitted arguments can be replaced with dummies.
7706+
// compile according to context. this differs from a normal function in that omitted parameter
7707+
// and return types can be inferred and omitted arguments can be replaced with dummies.
77287708
if (contextualSignature) {
77297709
let signatureNode = prototype.functionTypeNode;
77307710
let parameterNodes = signatureNode.parameters;
@@ -7833,28 +7813,36 @@ export class Compiler extends DiagnosticEmitter {
78337813
}
78347814

78357815
var index = this.ensureFunctionTableEntry(instance); // reports
7836-
console.log("function index" + index)
78377816

78387817
if(index < 0) return this.module.unreachable();
78397818

78407819
if(instance.closedLocals.size > 0) {
7820+
//Create field declarations for the function and each closed local
78417821
var members = Array<DeclarationStatement>(instance.closedLocals.size + 1);
78427822
members[0] = this.program.makeNativeMember("__functionPtr", "u32")
78437823
for (let _values = Map_values(instance.closedLocals), i = 0, k = _values.length; i < k; ++i) {
78447824
let local = unchecked(_values[i]);
7845-
members[i + 1] = this.program.makeNativeMember(local.name, "i32") //todo, support non-i32
7825+
members[i + 1] = this.program.makeNativeMember(local.name, local.type.intType.toString())
78467826
}
78477827

7848-
var closureClassPrototype = this.program.makeNativeClassPrototype(
7849-
"closure|" + actualFunction.nextAnonymousId.toString(),
7828+
//Create a native class prototype with a dummy syntax tree, similar to native functions
7829+
var closureClassPrototype = assert(this.program.makeNativeClassPrototype(
7830+
"closure|" + this.program.nextClassId.toString(),
78507831
members
7851-
)!;
7832+
));
7833+
7834+
//Resolve this prototype to get the class
78527835
var closureClass = this.resolver.resolveClass(closureClassPrototype, null)!;
7836+
7837+
//Compile this class to get the type
78537838
this.compileClass(closureClass);
7854-
//set the current type to be the generated type
7855-
this.currentType = closureClass.type.asClosure(instance.signature)//generatedClosureType;
7856-
//create local to hold closure
7839+
7840+
//Append the appropriate signature and flags for this closure type, then set it to currentType
7841+
this.currentType = closureClass.type.asClosure(instance.signature)
7842+
7843+
//create a local which will hold our closure class instance
78577844
var tempLocal = flow.getTempLocal(this.currentType);
7845+
78587846
//copied closed locals into type
78597847
this.currentType.locals = instance.closedLocals;
78607848

@@ -8125,13 +8113,19 @@ export class Compiler extends DiagnosticEmitter {
81258113
case ElementKind.CLOSEDLOCAL: {
81268114
let closedLocal = <ClosedLocal>target;
81278115

8128-
return module.load(
8129-
4,
8116+
// TODO: replace this with a class field access, once we are able to construct the class before
8117+
// compiling
8118+
let loadExpr = module.load(
8119+
closedLocal.type.byteSize,
81308120
true,
81318121
this.module.global_get(BuiltinNames.global_closure, NativeType.I32),
8132-
NativeType.I32,
8133-
flow.actualFunction.closedLocals.size * 4
8122+
closedLocal.type.toNativeType(),
8123+
flow.actualFunction.nextGlobalClosureOffset
81348124
);
8125+
8126+
flow.actualFunction.nextGlobalClosureOffset += closedLocal.type.byteSize;
8127+
8128+
return loadExpr
81358129
}
81368130
}
81378131
this.error(
@@ -9452,7 +9446,6 @@ export class Compiler extends DiagnosticEmitter {
94529446
break;
94539447
}
94549448
default: {
9455-
assert(false);
94569449
return module.unreachable();
94579450
}
94589451
}

src/flow.ts

-2
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,6 @@ export class Flow {
204204
inlineFunction: Function | null;
205205
/** The label we break to when encountering a return statement, when inlining. */
206206
inlineReturnLabel: string | null;
207-
locals: Map<string, Local> | null;
208207

209208
/** Creates the parent flow of the specified function. */
210209
static create(parentFunction: Function): Flow {
@@ -219,7 +218,6 @@ export class Flow {
219218
flow.localFlags = [];
220219
flow.inlineFunction = null;
221220
flow.inlineReturnLabel = null;
222-
flow.locals = null;
223221
return flow;
224222
}
225223

0 commit comments

Comments
 (0)