Skip to content

Commit ab763df

Browse files
authored
Merge pull request #1 from btc-vision/closures
feat: Add experimental closures support with feature flag
2 parents 3daa41b + d1ce5c9 commit ab763df

20 files changed

+53665
-206
lines changed

NOTICE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ under the licensing terms detailed in LICENSE:
6262
* Kam Chehresa <kaz.che@gmail.com>
6363
* Mopsgamer <79159094+Mopsgamer@users.noreply.github.com>
6464
* EDM115 <github@edm115.dev>
65+
* Anakun <anakun@opnet.org>
6566

6667
Portions of this software are derived from third-party works licensed under
6768
the following terms:

src/common.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ export namespace CommonNames {
191191
export const ASC_FEATURE_RELAXED_SIMD = "ASC_FEATURE_RELAXED_SIMD";
192192
export const ASC_FEATURE_EXTENDED_CONST = "ASC_FEATURE_EXTENDED_CONST";
193193
export const ASC_FEATURE_STRINGREF = "ASC_FEATURE_STRINGREF";
194+
export const ASC_FEATURE_CLOSURES = "ASC_FEATURE_CLOSURES";
194195
export const ASC_VERSION_MAJOR = "ASC_VERSION_MAJOR";
195196
export const ASC_VERSION_MINOR = "ASC_VERSION_MINOR";
196197
export const ASC_VERSION_PATCH = "ASC_VERSION_PATCH";

src/compiler.ts

Lines changed: 1548 additions & 163 deletions
Large diffs are not rendered by default.

src/flow.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,17 @@ export class Flow {
536536
return null;
537537
}
538538

539+
/** Looks up a local in outer function scopes (for closures). */
540+
lookupLocalInOuter(name: string): Local | null {
541+
let outerFlow: Flow | null = this.outer;
542+
while (outerFlow) {
543+
let local = outerFlow.lookupLocal(name);
544+
if (local) return local;
545+
outerFlow = outerFlow.outer;
546+
}
547+
return null;
548+
}
549+
539550
/** Looks up the element with the specified name relative to the scope of this flow. */
540551
lookup(name: string): Element | null {
541552
let element = this.lookupLocal(name);

src/index-wasm.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ export const FEATURE_RELAXED_SIMD = Feature.RelaxedSimd;
202202
export const FEATURE_EXTENDED_CONST = Feature.ExtendedConst;
203203
/** String references. */
204204
export const FEATURE_STRINGREF = Feature.Stringref;
205+
/** Closures. */
206+
export const FEATURE_CLOSURES = Feature.Closures;
205207
/** All features. */
206208
export const FEATURES_ALL = Feature.All;
207209
/** Default features. */

src/program.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,8 @@ export class Program extends DiagnosticEmitter {
10861086
i64_new(options.hasFeature(Feature.ExtendedConst) ? 1 : 0, 0));
10871087
this.registerConstantInteger(CommonNames.ASC_FEATURE_STRINGREF, Type.bool,
10881088
i64_new(options.hasFeature(Feature.Stringref) ? 1 : 0, 0));
1089+
this.registerConstantInteger(CommonNames.ASC_FEATURE_CLOSURES, Type.bool,
1090+
i64_new(options.hasFeature(Feature.Closures) ? 1 : 0, 0));
10891091

10901092
// remember deferred elements
10911093
let queuedImports = new Array<QueuedImport>();
@@ -3628,6 +3630,15 @@ export class Local extends VariableLikeElement {
36283630
/** Original name of the (temporary) local. */
36293631
private originalName: string;
36303632

3633+
/** Whether this local is captured by a closure. */
3634+
isCaptured: bool = false;
3635+
3636+
/** Environment slot index if captured, -1 otherwise. */
3637+
envSlotIndex: i32 = -1;
3638+
3639+
/** The function whose environment this local is stored in. Set when captured. */
3640+
envOwner: Function | null = null;
3641+
36313642
/** Constructs a new local variable. */
36323643
constructor(
36333644
/** Simple name. */
@@ -3785,6 +3796,32 @@ export class Function extends TypedElement {
37853796
/** Counting id of anonymous inner functions. */
37863797
nextAnonymousId: i32 = 0;
37873798

3799+
// Closure support
3800+
3801+
/** Set of locals from outer scopes that this function captures. Maps Local to slot index. */
3802+
capturedLocals: Map<Local, i32> | null = null;
3803+
3804+
/** The environment class for this function's captured locals, if any. */
3805+
envClass: Class | null = null;
3806+
3807+
/** The local variable holding the environment pointer in outer function. */
3808+
envLocal: Local | null = null;
3809+
3810+
/** The outer function whose environment this closure accesses. */
3811+
outerFunction: Function | null = null;
3812+
3813+
/** Local variable in a closure function that caches the environment pointer from the global.
3814+
* This is needed because indirect calls can overwrite the global. */
3815+
closureEnvLocal: Local | null = null;
3816+
3817+
/** Pre-scanned names of captured variables (set before compilation, used to mark locals). */
3818+
preCapturedNames: Set<string> | null = null;
3819+
3820+
/** Whether this function requires an environment (is a closure). */
3821+
get needsEnvironment(): bool {
3822+
return this.capturedLocals != null && this.capturedLocals.size > 0;
3823+
}
3824+
37883825
/** Constructs a new concrete function. */
37893826
constructor(
37903827
/** Name incl. type parameters, i.e. `foo<i32>`. */

src/resolver.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2175,6 +2175,16 @@ export class Resolver extends DiagnosticEmitter {
21752175
return thisLocal;
21762176
}
21772177
}
2178+
// Check for captured 'this' in closures - look up in outer flow chain
2179+
let thisLocal = ctxFlow.lookupLocal(CommonNames.this_);
2180+
if (!thisLocal) {
2181+
thisLocal = ctxFlow.lookupLocalInOuter(CommonNames.this_);
2182+
}
2183+
if (thisLocal) {
2184+
this.currentThisExpression = null;
2185+
this.currentElementExpression = null;
2186+
return thisLocal;
2187+
}
21782188
let parent = ctxFlow.sourceFunction.parent;
21792189
if (parent) {
21802190
this.currentThisExpression = null;

std/assembly/shared/feature.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ export const enum Feature {
3434
ExtendedConst = 1 << 13, // see: https://github.com/WebAssembly/extended-const
3535
/** Reference typed strings. */
3636
Stringref = 1 << 14, // see: https://github.com/WebAssembly/stringref
37+
/** Closures. */
38+
Closures = 1 << 15,
3739
/** All features. */
38-
All = (1 << 15) - 1
40+
All = (1 << 16) - 1
3941
}
4042

4143
/** Gets the name of the specified feature one would specify on the command line. */
@@ -56,6 +58,7 @@ export function featureToString(feature: Feature): string {
5658
case Feature.RelaxedSimd: return "relaxed-simd";
5759
case Feature.ExtendedConst: return "extended-const";
5860
case Feature.Stringref: return "stringref";
61+
case Feature.Closures: return "closures";
5962
}
6063
assert(false);
6164
return "";

0 commit comments

Comments
 (0)