Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
46c0026
Implement closure support for captured variables
BlobMaster41 Dec 10, 2025
03f4b7e
Fix closure environment slot calculation and test coverage
BlobMaster41 Dec 10, 2025
e333207
Improve closure environment handling and alignment
BlobMaster41 Dec 10, 2025
99edd36
Handle function expressions in 'new' constructor args
BlobMaster41 Dec 11, 2025
b085c6a
Add closure class compiler tests
BlobMaster41 Dec 11, 2025
cc0f4ec
Add closures feature flag and enablement checks
BlobMaster41 Dec 11, 2025
fe1a286
Simplify indirect call handling without closures
BlobMaster41 Dec 11, 2025
28a09b1
Improve closure environment handling and parent pointer support
BlobMaster41 Dec 11, 2025
2615b37
Update closure environment layout in WAT tests
BlobMaster41 Dec 11, 2025
c693621
Add Anakun to NOTICE contributors list
BlobMaster41 Dec 11, 2025
fdc21ce
Handle 'this' capture in closures and methods
BlobMaster41 Dec 11, 2025
001873a
Refactor closures to capture 'this' directly
BlobMaster41 Dec 11, 2025
d1ce5c9
Recompiled tests
BlobMaster41 Dec 11, 2025
ab763df
Merge pull request #1 from btc-vision/closures
BlobMaster41 Dec 11, 2025
1b07be0
Add closure capture support for more statement types
BlobMaster41 Dec 12, 2025
17e08b1
Nitpick: extra $
BlobMaster41 Dec 12, 2025
8379613
Fix closure capture for default parameter values
BlobMaster41 Dec 12, 2025
9675719
Refactor closure capture analysis and remove collectCapturedNames
BlobMaster41 Dec 12, 2025
daced3a
Merge pull request #3 from btc-vision/closures
BlobMaster41 Dec 12, 2025
61ff8b0
Refactor closure capture logic and add closures test config
BlobMaster41 Dec 12, 2025
d534476
Merge pull request #4 from btc-vision/closures
BlobMaster41 Dec 12, 2025
80aa6ab
Refactor environment slot allocation logic
BlobMaster41 Dec 12, 2025
bc725a4
Refactor closure capture analysis and minor cleanups
BlobMaster41 Dec 12, 2025
16737cb
Merge pull request #5 from btc-vision/closures
BlobMaster41 Dec 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ under the licensing terms detailed in LICENSE:
* Kam Chehresa <kaz.che@gmail.com>
* Mopsgamer <79159094+Mopsgamer@users.noreply.github.com>
* EDM115 <github@edm115.dev>
* Anakun <anakun@opnet.org>

Portions of this software are derived from third-party works licensed under
the following terms:
Expand Down
1 change: 1 addition & 0 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ export namespace CommonNames {
export const ASC_FEATURE_RELAXED_SIMD = "ASC_FEATURE_RELAXED_SIMD";
export const ASC_FEATURE_EXTENDED_CONST = "ASC_FEATURE_EXTENDED_CONST";
export const ASC_FEATURE_STRINGREF = "ASC_FEATURE_STRINGREF";
export const ASC_FEATURE_CLOSURES = "ASC_FEATURE_CLOSURES";
export const ASC_VERSION_MAJOR = "ASC_VERSION_MAJOR";
export const ASC_VERSION_MINOR = "ASC_VERSION_MINOR";
export const ASC_VERSION_PATCH = "ASC_VERSION_PATCH";
Expand Down
1,925 changes: 1,774 additions & 151 deletions src/compiler.ts

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions src/flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,17 @@ export class Flow {
return null;
}

/** Looks up a local in outer function scopes (for closures). */
lookupLocalInOuter(name: string): Local | null {
let outerFlow: Flow | null = this.outer;
while (outerFlow) {
let local = outerFlow.lookupLocal(name);
if (local) return local;
outerFlow = outerFlow.outer;
}
return null;
}

/** Looks up the element with the specified name relative to the scope of this flow. */
lookup(name: string): Element | null {
let element = this.lookupLocal(name);
Expand Down
2 changes: 2 additions & 0 deletions src/index-wasm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ export const FEATURE_RELAXED_SIMD = Feature.RelaxedSimd;
export const FEATURE_EXTENDED_CONST = Feature.ExtendedConst;
/** String references. */
export const FEATURE_STRINGREF = Feature.Stringref;
/** Closures. */
export const FEATURE_CLOSURES = Feature.Closures;
/** All features. */
export const FEATURES_ALL = Feature.All;
/** Default features. */
Expand Down
37 changes: 37 additions & 0 deletions src/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,8 @@ export class Program extends DiagnosticEmitter {
i64_new(options.hasFeature(Feature.ExtendedConst) ? 1 : 0, 0));
this.registerConstantInteger(CommonNames.ASC_FEATURE_STRINGREF, Type.bool,
i64_new(options.hasFeature(Feature.Stringref) ? 1 : 0, 0));
this.registerConstantInteger(CommonNames.ASC_FEATURE_CLOSURES, Type.bool,
i64_new(options.hasFeature(Feature.Closures) ? 1 : 0, 0));

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

/** Whether this local is captured by a closure. */
isCaptured: bool = false;

/** Environment slot index if captured, -1 otherwise. */
envSlotIndex: i32 = -1;

/** The function whose environment this local is stored in. Set when captured. */
envOwner: Function | null = null;

/** Constructs a new local variable. */
constructor(
/** Simple name. */
Expand Down Expand Up @@ -3785,6 +3796,32 @@ export class Function extends TypedElement {
/** Counting id of anonymous inner functions. */
nextAnonymousId: i32 = 0;

// Closure support

/** Set of locals from outer scopes that this function captures. Maps Local to slot index. */
capturedLocals: Map<Local, i32> | null = null;

/** The environment class for this function's captured locals, if any. */
envClass: Class | null = null;

/** The local variable holding the environment pointer in outer function. */
envLocal: Local | null = null;

/** The outer function whose environment this closure accesses. */
outerFunction: Function | null = null;

/** Local variable in a closure function that caches the environment pointer from the global.
* This is needed because indirect calls can overwrite the global. */
closureEnvLocal: Local | null = null;

/** Pre-scanned names of captured variables (set before compilation, used to mark locals). */
preCapturedNames: Set<string> | null = null;

/** Whether this function requires an environment (is a closure). */
get needsEnvironment(): bool {
return this.capturedLocals != null && this.capturedLocals.size > 0;
}

/** Constructs a new concrete function. */
constructor(
/** Name incl. type parameters, i.e. `foo<i32>`. */
Expand Down
10 changes: 10 additions & 0 deletions src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2175,6 +2175,16 @@ export class Resolver extends DiagnosticEmitter {
return thisLocal;
}
}
// Check for captured 'this' in closures - look up in outer flow chain
let thisLocal = ctxFlow.lookupLocal(CommonNames.this_);
if (!thisLocal) {
thisLocal = ctxFlow.lookupLocalInOuter(CommonNames.this_);
}
if (thisLocal) {
this.currentThisExpression = null;
this.currentElementExpression = null;
return thisLocal;
}
let parent = ctxFlow.sourceFunction.parent;
if (parent) {
this.currentThisExpression = null;
Expand Down
5 changes: 4 additions & 1 deletion std/assembly/shared/feature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ export const enum Feature {
ExtendedConst = 1 << 13, // see: https://github.com/WebAssembly/extended-const
/** Reference typed strings. */
Stringref = 1 << 14, // see: https://github.com/WebAssembly/stringref
/** Closures. */
Closures = 1 << 15,
/** All features. */
All = (1 << 15) - 1
All = (1 << 16) - 1
}

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