closures with captures: env struct + global instance + body rewriting
- AST: captureCount + captureName0..7 + captureType0..7 in Expr - Sema: Scope_LookupUpTo + closureScope for capture analysis - HIR Lower: env struct generation, capture assignments in skLet, body rewriting (ekIdent -> hFieldAccess on env instance) - C Backend: env struct def + global instance emission for closures - Bootstrap: full capture support in sema, hir_lower, lir_lower, lir_c_backend - Selfhost loop remains deterministic
This commit is contained in:
@@ -233,6 +233,9 @@ type
|
||||
exprClosureParams*: seq[Param]
|
||||
exprClosureBody*: Block
|
||||
exprClosureReturnType*: TypeExpr
|
||||
captureCount*: int
|
||||
captureNames*: seq[string]
|
||||
captureTypeKinds*: seq[int]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Statements
|
||||
|
||||
@@ -272,6 +272,10 @@ proc emitExpr(be: var CBackend, node: HirNode): string =
|
||||
let base = be.emitExpr(node.fieldPtrBase)
|
||||
return &"(&({base}.{node.fieldName}))"
|
||||
|
||||
of hFieldAccess:
|
||||
let base = be.emitExpr(node.fieldAccessBase)
|
||||
return &"({base}.{node.fieldAccessName})"
|
||||
|
||||
of hArrowField:
|
||||
let base = be.emitExpr(node.arrowFieldBase)
|
||||
return &"(&({base}->{node.arrowFieldName}))"
|
||||
@@ -476,6 +480,18 @@ proc emitStmt(be: var CBackend, node: HirNode) =
|
||||
be.emitLine(&"{expr};")
|
||||
|
||||
proc emitFunc*(be: var CBackend, hfunc: HirFunc) =
|
||||
# Emit env struct and global instance for closures with captures
|
||||
if hfunc.captureNames.len > 0 and hfunc.envStructName != "":
|
||||
be.emitLine(&"struct {hfunc.envStructName} {{")
|
||||
inc be.indent
|
||||
for i in 0 ..< hfunc.captureNames.len:
|
||||
let capName = hfunc.captureNames[i]
|
||||
let capType = if i < hfunc.captureTypes.len: typeToC(be, hfunc.captureTypes[i]) else: "int"
|
||||
be.emitLine(&"{capType} {capName};")
|
||||
dec be.indent
|
||||
be.emitLine("};")
|
||||
be.emitLine(&"static struct {hfunc.envStructName} {hfunc.envInstanceName};")
|
||||
be.emitLine("")
|
||||
let retType = typeToC(be, hfunc.retType)
|
||||
var params: seq[string] = @[]
|
||||
for p in hfunc.params:
|
||||
|
||||
@@ -23,6 +23,7 @@ type
|
||||
hLoad
|
||||
hStore
|
||||
hFieldPtr
|
||||
hFieldAccess
|
||||
hArrowField
|
||||
hIndexPtr
|
||||
hSliceIndex
|
||||
@@ -98,6 +99,9 @@ type
|
||||
of hFieldPtr:
|
||||
fieldPtrBase*: HirNode
|
||||
fieldName*: string
|
||||
of hFieldAccess:
|
||||
fieldAccessBase*: HirNode
|
||||
fieldAccessName*: string
|
||||
of hArrowField:
|
||||
arrowFieldBase*: HirNode
|
||||
arrowFieldName*: string
|
||||
@@ -166,6 +170,11 @@ type
|
||||
retType*: Type
|
||||
body*: HirNode
|
||||
isPublic*: bool
|
||||
# Closure capture metadata
|
||||
captureNames*: seq[string]
|
||||
captureTypes*: seq[Type]
|
||||
envStructName*: string
|
||||
envInstanceName*: string
|
||||
|
||||
HirEnumVariant* = object
|
||||
name*: string
|
||||
@@ -219,6 +228,9 @@ proc hirAlloca*(name: string, typ: Type, loc: SourceLocation): HirNode =
|
||||
proc hirStore*(ptrNode, value: HirNode, loc: SourceLocation): HirNode =
|
||||
HirNode(kind: hStore, storePtr: ptrNode, storeValue: value, typ: makeVoid(), loc: loc)
|
||||
|
||||
proc hirAssign*(target, value: HirNode, loc: SourceLocation): HirNode =
|
||||
HirNode(kind: hAssign, assignOp: tkAssign, assignTarget: target, assignValue: value, typ: makeVoid(), loc: loc)
|
||||
|
||||
proc hirLoad*(ptrNode: HirNode, typ: Type, loc: SourceLocation): HirNode =
|
||||
HirNode(kind: hLoad, loadPtr: ptrNode, typ: typ, loc: loc)
|
||||
|
||||
|
||||
+43
-1
@@ -22,6 +22,9 @@ type
|
||||
generatedFuncInsts*: Table[string, bool] # Track generated function instantiations
|
||||
extraFuncs*: seq[HirFunc] # Monomorphized generic methods
|
||||
varTypeExprs*: Table[string, TypeExpr] # Track variable names -> type expr for generic method inference
|
||||
closureDepth*: int
|
||||
currentClosureExpr*: Expr
|
||||
envInstanceName*: string
|
||||
|
||||
proc freshName(ctx: var LowerCtx): string =
|
||||
inc ctx.varCounter
|
||||
@@ -648,6 +651,13 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
|
||||
of ekIdent:
|
||||
let name = expr.exprIdent
|
||||
# Capture rewriting: if inside closure and ident is captured
|
||||
if ctx.closureDepth > 0 and ctx.currentClosureExpr != nil and ctx.envInstanceName != "":
|
||||
let idx = ctx.currentClosureExpr.captureNames.find(name)
|
||||
if idx >= 0:
|
||||
let capType = if idx < ctx.currentClosureExpr.captureTypeKinds.len: Type(kind: TypeKind(ctx.currentClosureExpr.captureTypeKinds[idx])) else: makeInt()
|
||||
let base = hirVar(ctx.envInstanceName, makeNamed(""), loc)
|
||||
return HirNode(kind: hFieldAccess, fieldAccessName: name, fieldAccessBase: base, typ: capType, loc: loc)
|
||||
if ctx.importTable.hasKey(name):
|
||||
return hirVar(ctx.importTable[name], typ, loc)
|
||||
return hirVar(name, typ, loc)
|
||||
@@ -1202,6 +1212,22 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
||||
if initHir != nil:
|
||||
let store = hirStore(varNode, initHir, loc)
|
||||
stmts.add(store)
|
||||
# If init is a closure with captures, emit capture assignments
|
||||
if stmt.stmtLetInit != nil and stmt.stmtLetInit.kind == ekClosure and stmt.stmtLetInit.captureCount > 0:
|
||||
let closureIdx = ctx.varCounter - 1
|
||||
let envInst = "__closure_env_instance_" & $closureIdx
|
||||
var capStmts: seq[HirNode] = @[]
|
||||
for i in 0 ..< stmt.stmtLetInit.captureCount:
|
||||
let capName = stmt.stmtLetInit.captureNames[i]
|
||||
let capType = if i < stmt.stmtLetInit.captureTypeKinds.len: Type(kind: TypeKind(stmt.stmtLetInit.captureTypeKinds[i])) else: makeInt()
|
||||
let base = hirVar(envInst, makeNamed(""), loc)
|
||||
let field = HirNode(kind: hFieldAccess, fieldAccessName: capName, fieldAccessBase: base, typ: capType, loc: loc)
|
||||
let val = hirVar(capName, capType, loc)
|
||||
capStmts.add(hirAssign(field, val, loc))
|
||||
# Prepend capture assignments before the let
|
||||
var allStmts = capStmts
|
||||
allStmts.add(stmts)
|
||||
return hirBlock(allStmts, nil, makeVoid(), loc)
|
||||
return hirBlock(stmts, nil, makeVoid(), loc)
|
||||
|
||||
of skReturn:
|
||||
@@ -1491,6 +1517,13 @@ proc lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc =
|
||||
let name = "__closure_" & $ctx.varCounter
|
||||
inc ctx.varCounter
|
||||
var f = HirFunc(name: name, isPublic: false)
|
||||
# Copy capture metadata
|
||||
if expr.captureCount > 0:
|
||||
f.captureNames = expr.captureNames
|
||||
for tk in expr.captureTypeKinds:
|
||||
f.captureTypes.add(Type(kind: TypeKind(tk)))
|
||||
f.envStructName = "__closure_env_" & $(ctx.varCounter - 1)
|
||||
f.envInstanceName = "__closure_env_instance_" & $(ctx.varCounter - 1)
|
||||
# Params
|
||||
for p in expr.exprClosureParams:
|
||||
f.params.add((name: p.name, typ: if p.ptype != nil: ctx.resolveTypeExpr(p.ptype) else: makeUnknown()))
|
||||
@@ -1499,9 +1532,18 @@ proc lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc =
|
||||
f.retType = ctx.resolveTypeExpr(expr.exprClosureReturnType)
|
||||
else:
|
||||
f.retType = makeVoid()
|
||||
# Body
|
||||
# Body with closure rewriting
|
||||
let savedDepth = ctx.closureDepth
|
||||
let savedExpr = ctx.currentClosureExpr
|
||||
let savedEnv = ctx.envInstanceName
|
||||
ctx.closureDepth = ctx.closureDepth + 1
|
||||
ctx.currentClosureExpr = expr
|
||||
ctx.envInstanceName = f.envInstanceName
|
||||
if expr.exprClosureBody != nil:
|
||||
f.body = ctx.lowerBlock(expr.exprClosureBody)
|
||||
ctx.closureDepth = savedDepth
|
||||
ctx.currentClosureExpr = savedExpr
|
||||
ctx.envInstanceName = savedEnv
|
||||
ctx.extraFuncs.add(f)
|
||||
return f
|
||||
|
||||
|
||||
@@ -626,6 +626,20 @@ proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): s
|
||||
be.emitLine("};")
|
||||
be.emitLine("")
|
||||
|
||||
# Emit env structs for closures with captures
|
||||
for f in module.funcs:
|
||||
if f.captureNames.len > 0 and f.envStructName != "":
|
||||
be.emitLine(&"struct {f.envStructName} {{")
|
||||
be.indent += 1
|
||||
for i in 0 ..< f.captureNames.len:
|
||||
let capName = f.captureNames[i]
|
||||
let capType = if i < f.captureTypes.len: typeToCStr(f.captureTypes[i]) else: "int"
|
||||
be.emitLine(&"{capType} {capName};")
|
||||
be.indent -= 1
|
||||
be.emitLine("};")
|
||||
be.emitLine(&"static struct {f.envStructName} {f.envInstanceName};")
|
||||
be.emitLine("")
|
||||
|
||||
# Emit all LIR functions
|
||||
for f in builder.funcs:
|
||||
be.emitFunc(f, funcRetTypes, funcPtrTypes)
|
||||
|
||||
@@ -345,6 +345,14 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
|
||||
b.emitRawC(&"{t.strVal} = (void*)&({lirValToC(base)}.{node.fieldName});")
|
||||
return t
|
||||
|
||||
of hFieldAccess:
|
||||
let base = lowerExpr(ctx, node.fieldAccessBase)
|
||||
let cType = hirTypeToC(ctx, node)
|
||||
let t = b.freshTemp()
|
||||
b.emitAlloca(t.strVal, cType)
|
||||
b.emitRawC(&"{t.strVal} = {lirValToC(base)}.{node.fieldAccessName};")
|
||||
return t
|
||||
|
||||
of hArrowField:
|
||||
let base = lowerExpr(ctx, node.arrowFieldBase)
|
||||
let t = b.freshTemp()
|
||||
@@ -549,6 +557,9 @@ proc buildLval(ctx: var LowerToLirCtx, n: HirNode): string =
|
||||
of hArrowField:
|
||||
let baseStr = buildLval(ctx, n.arrowFieldBase)
|
||||
return baseStr & "->" & n.arrowFieldName
|
||||
of hFieldAccess:
|
||||
let baseStr = buildLval(ctx, n.fieldAccessBase)
|
||||
return baseStr & "." & n.fieldAccessName
|
||||
of hIndexPtr:
|
||||
let baseStr = buildLval(ctx, n.indexPtrBase)
|
||||
let idx = lowerExpr(ctx, n.indexPtrIndex)
|
||||
@@ -669,6 +680,9 @@ proc lowerStmt(ctx: var LowerToLirCtx, node: HirNode) =
|
||||
of hFieldPtr:
|
||||
let lval = buildLval(ctx, node.assignTarget)
|
||||
b.emitRawC(&"{lval} = {lirValToC(value)};")
|
||||
of hFieldAccess:
|
||||
let lval = buildLval(ctx, node.assignTarget)
|
||||
b.emitRawC(&"{lval} = {lirValToC(value)};")
|
||||
of hArrowField:
|
||||
let lval = buildLval(ctx, node.assignTarget)
|
||||
b.emitRawC(&"{lval} = {lirValToC(value)};")
|
||||
|
||||
@@ -43,4 +43,14 @@ proc lookup*(scope: Scope, name: string): Symbol =
|
||||
proc lookupLocal*(scope: Scope, name: string): Symbol =
|
||||
if scope.table.hasKey(name):
|
||||
return scope.table[name]
|
||||
return nil
|
||||
|
||||
proc lookupUpTo*(scope: Scope, name: string, limit: Scope): Symbol =
|
||||
var cur = scope
|
||||
while cur != nil:
|
||||
if cur.table.hasKey(name):
|
||||
return cur.table[name]
|
||||
if cur == limit:
|
||||
break
|
||||
cur = cur.parent
|
||||
return nil
|
||||
@@ -45,6 +45,9 @@ type
|
||||
currentFuncIsAsync*: bool ## true inside async func
|
||||
movedVars*: seq[string] ## variables moved in current checked function
|
||||
currentRetType*: Type ## return type of the function being checked
|
||||
closureDepth*: int ## nesting depth inside closures
|
||||
currentClosureExpr*: Expr ## current closure being analyzed
|
||||
closureScope*: Scope ## scope at which the current closure was entered
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
@@ -816,6 +819,14 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
return makeUnknown()
|
||||
if sym.typ == nil:
|
||||
return makeUnknown()
|
||||
# Capture tracking
|
||||
if sema.closureDepth > 0 and sema.currentClosureExpr != nil and sema.closureScope != nil:
|
||||
let localSym = scope.lookupUpTo(expr.exprIdent, sema.closureScope)
|
||||
if localSym == nil and sym.kind == skVar:
|
||||
if expr.exprIdent notin sema.currentClosureExpr.captureNames:
|
||||
sema.currentClosureExpr.captureNames.add(expr.exprIdent)
|
||||
sema.currentClosureExpr.captureTypeKinds.add(sym.typ.kind.int)
|
||||
sema.currentClosureExpr.captureCount = sema.currentClosureExpr.captureNames.len
|
||||
return sym.typ
|
||||
of ekSelf:
|
||||
let sym = scope.lookup("self")
|
||||
@@ -1340,7 +1351,16 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
return makeStr()
|
||||
of ekClosure:
|
||||
let savedRetType = sema.currentRetType
|
||||
let savedClosureDepth = sema.closureDepth
|
||||
let savedClosureExpr = sema.currentClosureExpr
|
||||
let savedClosureScope = sema.closureScope
|
||||
let childScope = Scope(parent: scope)
|
||||
sema.closureDepth = sema.closureDepth + 1
|
||||
sema.currentClosureExpr = expr
|
||||
sema.closureScope = childScope
|
||||
expr.captureCount = 0
|
||||
expr.captureNames = @[]
|
||||
expr.captureTypeKinds = @[]
|
||||
sema.currentRetType = if expr.exprClosureReturnType != nil: sema.resolveType(expr.exprClosureReturnType) else: makeUnknown()
|
||||
# Register params
|
||||
for p in expr.exprClosureParams:
|
||||
@@ -1351,6 +1371,9 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
for stmt in expr.exprClosureBody.stmts:
|
||||
discard sema.checkStmt(stmt, childScope)
|
||||
sema.currentRetType = savedRetType
|
||||
sema.closureDepth = savedClosureDepth
|
||||
sema.currentClosureExpr = savedClosureExpr
|
||||
sema.closureScope = savedClosureScope
|
||||
# Build function type
|
||||
var params: seq[Type] = @[]
|
||||
for p in expr.exprClosureParams:
|
||||
|
||||
Reference in New Issue
Block a user