fix(lir): resolve all LIR backend regressions — 26/26 examples + selfhost passing
- hir_lower: add isScope=true to lowerBlock; fix resolveExprType for ekIndex, ekField on monomorphized structs, and ekTry/ekUnwrap tmpVar typing - lir_lower: add loop label stack for break/continue; pass null arg to bux_task_spawn when spawn has no arguments - lir_c_backend: multi-pass type inference for temps; include params in varTypes; handle lvkTemp in lirLoad; use inferred type in lirAlloca
This commit is contained in:
+53
-6
@@ -352,9 +352,39 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
|
|||||||
if objType.isPointer and objType.inner.len > 0:
|
if objType.isPointer and objType.inner.len > 0:
|
||||||
objType = objType.inner[0]
|
objType = objType.inner[0]
|
||||||
if objType.kind == tkNamed:
|
if objType.kind == tkNamed:
|
||||||
let sym = ctx.globalScope.lookup(objType.name)
|
var sym = ctx.globalScope.lookup(objType.name)
|
||||||
if sym != nil and sym.decl != nil and sym.decl.kind == dkStruct:
|
var decl = if sym != nil: sym.decl else: nil
|
||||||
for f in sym.decl.declStructFields:
|
# If the type is a monomorphized generic struct instance, look up the base
|
||||||
|
if decl == nil and ctx.structInstMap.hasKey(objType.name):
|
||||||
|
let (baseName, typeArgs) = ctx.structInstMap[objType.name]
|
||||||
|
let baseSym = ctx.globalScope.lookup(baseName)
|
||||||
|
if baseSym != nil and baseSym.decl != nil and baseSym.decl.kind == dkStruct:
|
||||||
|
decl = baseSym.decl
|
||||||
|
var subst = initTable[string, Type]()
|
||||||
|
for i, tp in decl.declStructTypeParams:
|
||||||
|
if i < typeArgs.len:
|
||||||
|
subst[tp.name] = typeArgs[i]
|
||||||
|
for f in decl.declStructFields:
|
||||||
|
if f.name == expr.exprFieldName:
|
||||||
|
if f.ftype != nil:
|
||||||
|
case f.ftype.kind
|
||||||
|
of tekNamed:
|
||||||
|
case f.ftype.typeName
|
||||||
|
of "int", "int32", "int64": return makeInt()
|
||||||
|
of "float64": return makeFloat64()
|
||||||
|
of "float32": return makeFloat32()
|
||||||
|
of "bool": return makeBool()
|
||||||
|
else:
|
||||||
|
if subst.hasKey(f.ftype.typeName):
|
||||||
|
return subst[f.ftype.typeName]
|
||||||
|
return makeNamed(f.ftype.typeName)
|
||||||
|
of tekOwn, tekPointer:
|
||||||
|
return substituteType(ctx, f.ftype, subst)
|
||||||
|
else: return makeUnknown()
|
||||||
|
if decl != nil:
|
||||||
|
case decl.kind
|
||||||
|
of dkStruct:
|
||||||
|
for f in decl.declStructFields:
|
||||||
if f.name == expr.exprFieldName:
|
if f.name == expr.exprFieldName:
|
||||||
if f.ftype != nil:
|
if f.ftype != nil:
|
||||||
case f.ftype.kind
|
case f.ftype.kind
|
||||||
@@ -368,6 +398,17 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
|
|||||||
of tekOwn, tekPointer:
|
of tekOwn, tekPointer:
|
||||||
return ctx.resolveTypeExpr(f.ftype)
|
return ctx.resolveTypeExpr(f.ftype)
|
||||||
else: return makeUnknown()
|
else: return makeUnknown()
|
||||||
|
of dkEnum:
|
||||||
|
# Algebraic enum fields: tag and data
|
||||||
|
if expr.exprFieldName == "tag":
|
||||||
|
return makeNamed(objType.name & "_Tag")
|
||||||
|
elif expr.exprFieldName == "data":
|
||||||
|
return makeNamed(objType.name & "_Data")
|
||||||
|
else:
|
||||||
|
# Enum variant field access: e.g., r.data.Ok_0
|
||||||
|
# We can't easily resolve this here; return unknown
|
||||||
|
return makeUnknown()
|
||||||
|
else: discard
|
||||||
return makeUnknown()
|
return makeUnknown()
|
||||||
of ekStructInit:
|
of ekStructInit:
|
||||||
if expr.exprStructInitTypeArgs.len > 0:
|
if expr.exprStructInitTypeArgs.len > 0:
|
||||||
@@ -395,6 +436,13 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
|
|||||||
return makeInt()
|
return makeInt()
|
||||||
of ekUnwrap:
|
of ekUnwrap:
|
||||||
return makeInt()
|
return makeInt()
|
||||||
|
of ekIndex:
|
||||||
|
let baseType = ctx.resolveExprType(expr.exprIndexObj)
|
||||||
|
if baseType.isSlice and baseType.inner.len > 0:
|
||||||
|
return baseType.inner[0]
|
||||||
|
if baseType.isPointer and baseType.inner.len > 0:
|
||||||
|
return baseType.inner[0]
|
||||||
|
return makeUnknown()
|
||||||
of ekBlock:
|
of ekBlock:
|
||||||
if expr.exprBlock.stmts.len > 0:
|
if expr.exprBlock.stmts.len > 0:
|
||||||
let last = expr.exprBlock.stmts[^1]
|
let last = expr.exprBlock.stmts[^1]
|
||||||
@@ -721,7 +769,7 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
|||||||
|
|
||||||
let tmpName = ctx.freshTryVar()
|
let tmpName = ctx.freshTryVar()
|
||||||
let tmpAlloca = hirAlloca(tmpName, operandType, loc)
|
let tmpAlloca = hirAlloca(tmpName, operandType, loc)
|
||||||
let tmpVar = hirVar(tmpName, makePointer(operandType), loc)
|
let tmpVar = hirVar(tmpName, operandType, loc)
|
||||||
let tmpStore = hirStore(tmpVar, operand, loc)
|
let tmpStore = hirStore(tmpVar, operand, loc)
|
||||||
|
|
||||||
let tagPtr = HirNode(kind: hFieldPtr, fieldPtrBase: tmpVar, fieldName: "tag",
|
let tagPtr = HirNode(kind: hFieldPtr, fieldPtrBase: tmpVar, fieldName: "tag",
|
||||||
@@ -1020,8 +1068,7 @@ proc lowerBlock(ctx: var LowerCtx, blk: Block): HirNode =
|
|||||||
# but for hVar/hLit/hCall etc. we could treat them as block expr.
|
# but for hVar/hLit/hCall etc. we could treat them as block expr.
|
||||||
# For now, leave as-is to avoid breaking control-flow statements.
|
# For now, leave as-is to avoid breaking control-flow statements.
|
||||||
discard
|
discard
|
||||||
return HirNode(kind: hBlock, blockStmts: stmts, blockExpr: expr,
|
return hirBlock(stmts, expr, if expr != nil: expr.typ else: makeVoid(), blk.loc, isScope = true)
|
||||||
typ: if expr != nil: expr.typ else: makeVoid(), loc: blk.loc)
|
|
||||||
|
|
||||||
proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
|
proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
|
||||||
# Set up type substitution for generic functions
|
# Set up type substitution for generic functions
|
||||||
|
|||||||
+98
-37
@@ -178,7 +178,12 @@ proc emitInstr(be: var LirCBackend, instr: LirInstr) =
|
|||||||
|
|
||||||
# ── Alloca ──
|
# ── Alloca ──
|
||||||
of lirAlloca:
|
of lirAlloca:
|
||||||
be.emitLine(&"{v(instr.src)} {v(instr.dst)};")
|
var ct = v(instr.src)
|
||||||
|
if instr.dst.strVal.len > 0 and be.tempTypes.hasKey(instr.dst.strVal):
|
||||||
|
let inferred = be.tempTypes[instr.dst.strVal]
|
||||||
|
if inferred != "" and inferred != ct:
|
||||||
|
ct = inferred
|
||||||
|
be.emitLine(&"{ct} {v(instr.dst)};")
|
||||||
|
|
||||||
# ── Pointers ──
|
# ── Pointers ──
|
||||||
of lirAddrOf:
|
of lirAddrOf:
|
||||||
@@ -236,7 +241,7 @@ proc emitInstr(be: var LirCBackend, instr: LirInstr) =
|
|||||||
|
|
||||||
# ── Function emission ──
|
# ── Function emission ──
|
||||||
|
|
||||||
proc emitFunc(be: var LirCBackend, f: LirFunc) =
|
proc emitFunc(be: var LirCBackend, f: LirFunc, funcRetTypes: Table[string, string]) =
|
||||||
var paramsStr = ""
|
var paramsStr = ""
|
||||||
for i, p in f.params:
|
for i, p in f.params:
|
||||||
if i > 0: paramsStr.add(", ")
|
if i > 0: paramsStr.add(", ")
|
||||||
@@ -247,50 +252,99 @@ proc emitFunc(be: var LirCBackend, f: LirFunc) =
|
|||||||
be.emitLine(&"{f.retType} {f.name}({paramsStr}) {{")
|
be.emitLine(&"{f.retType} {f.name}({paramsStr}) {{")
|
||||||
be.indent += 1
|
be.indent += 1
|
||||||
|
|
||||||
# First pass: declare all temp variables at the top of the function
|
# ── Pass 1: collect types from allocas, params, and instructions ──
|
||||||
var tempsSeen: seq[tuple[name: string, cType: string]] = @[]
|
var varTypes = initTable[string, string]()
|
||||||
var tempsSet: seq[string] = @[]
|
var tempsSet: seq[string] = @[]
|
||||||
|
for p in f.params:
|
||||||
|
varTypes[p.name] = p.cType
|
||||||
|
be.tempTypes[p.name] = p.cType
|
||||||
for instr in f.instrs:
|
for instr in f.instrs:
|
||||||
# Allocas are explicit declarations — mark name as declared, skip emission here
|
if instr.kind == lirAlloca and instr.dst.kind == lvkVar and instr.src.kind == lvkType:
|
||||||
if instr.kind == lirAlloca:
|
varTypes[instr.dst.strVal] = instr.src.strVal
|
||||||
if instr.dst.strVal.len > 0 and instr.dst.strVal notin tempsSet:
|
be.tempTypes[instr.dst.strVal] = instr.src.strVal
|
||||||
|
if instr.dst.strVal notin tempsSet:
|
||||||
tempsSet.add(instr.dst.strVal)
|
tempsSet.add(instr.dst.strVal)
|
||||||
|
|
||||||
|
# ── Pass 2: iterative type inference for temps ──
|
||||||
|
var changed = true
|
||||||
|
while changed:
|
||||||
|
changed = false
|
||||||
|
for instr in f.instrs:
|
||||||
|
if instr.dst.kind != lvkTemp or instr.dst.strVal.len == 0:
|
||||||
continue
|
continue
|
||||||
# Temps that are destinations of binary ops, calls, etc.
|
let name = instr.dst.strVal
|
||||||
if instr.dst.kind == lvkTemp and instr.dst.strVal.len > 0 and instr.dst.strVal notin tempsSet:
|
let oldType = if be.tempTypes.hasKey(name): be.tempTypes[name] else: ""
|
||||||
# Infer type based on instruction kind
|
var newType = oldType
|
||||||
var ct = "int"
|
|
||||||
case instr.kind
|
case instr.kind
|
||||||
of lirMov, lirLoad, lirLoadGlobal:
|
of lirStructInit:
|
||||||
ct = "int"
|
if instr.extra.len > 0 and instr.extra[0].kind == lvkType:
|
||||||
of lirAdd, lirSub, lirMul, lirDiv, lirMod, lirNeg,
|
newType = instr.extra[0].strVal
|
||||||
lirCmpEq, lirCmpNe, lirCmpLt, lirCmpLe, lirCmpGt, lirCmpGe:
|
of lirSliceInit:
|
||||||
ct = "int"
|
if instr.extra.len > 0 and instr.extra[0].kind == lvkType:
|
||||||
of lirAnd, lirOr, lirXor, lirShl, lirShr, lirNot, lirBNot:
|
newType = "Slice_" & instr.extra[0].strVal
|
||||||
ct = "int"
|
|
||||||
of lirCall, lirCallIndirect:
|
|
||||||
ct = "int" # Conservative
|
|
||||||
of lirAddrOf, lirFieldPtr, lirArrowFieldPtr, lirIndexPtr, lirPtrAdd:
|
|
||||||
ct = "void*"
|
|
||||||
of lirStructInit, lirSliceInit:
|
|
||||||
ct = "int" # Will be overwritten by the actual type in emit
|
|
||||||
of lirCast:
|
of lirCast:
|
||||||
ct = valToC(be, instr.src2)
|
if instr.src2.kind == lvkType:
|
||||||
|
newType = instr.src2.strVal
|
||||||
|
of lirCall:
|
||||||
|
if instr.src.kind == lvkGlobal and funcRetTypes.hasKey(instr.src.strVal):
|
||||||
|
newType = funcRetTypes[instr.src.strVal]
|
||||||
|
of lirCallIndirect:
|
||||||
|
# Conservative; try to infer from dst usage in later passes
|
||||||
|
discard
|
||||||
|
of lirMov:
|
||||||
|
if instr.src.kind == lvkTemp and be.tempTypes.hasKey(instr.src.strVal):
|
||||||
|
newType = be.tempTypes[instr.src.strVal]
|
||||||
|
elif instr.src.kind == lvkVar and varTypes.hasKey(instr.src.strVal):
|
||||||
|
newType = varTypes[instr.src.strVal]
|
||||||
|
of lirLoad, lirLoadGlobal:
|
||||||
|
# Try to deduce pointee type from pointer vars/temps
|
||||||
|
if instr.src.kind == lvkVar and varTypes.hasKey(instr.src.strVal):
|
||||||
|
let srcType = varTypes[instr.src.strVal]
|
||||||
|
if srcType.endsWith("*"):
|
||||||
|
newType = srcType[0 ..< srcType.len - 1]
|
||||||
|
elif srcType.startsWith("Slice_"):
|
||||||
|
newType = srcType[6 ..< srcType.len]
|
||||||
|
elif instr.src.kind == lvkTemp and be.tempTypes.hasKey(instr.src.strVal):
|
||||||
|
let srcType = be.tempTypes[instr.src.strVal]
|
||||||
|
if srcType.endsWith("*"):
|
||||||
|
newType = srcType[0 ..< srcType.len - 1]
|
||||||
|
elif srcType.startsWith("Slice_"):
|
||||||
|
newType = srcType[6 ..< srcType.len]
|
||||||
of lirSelect:
|
of lirSelect:
|
||||||
ct = "int"
|
if instr.src2.kind == lvkTemp and be.tempTypes.hasKey(instr.src2.strVal):
|
||||||
|
newType = be.tempTypes[instr.src2.strVal]
|
||||||
|
elif instr.extra.len > 0 and instr.extra[0].kind == lvkTemp and be.tempTypes.hasKey(instr.extra[0].strVal):
|
||||||
|
newType = be.tempTypes[instr.extra[0].strVal]
|
||||||
|
elif instr.src2.kind == lvkVar and varTypes.hasKey(instr.src2.strVal):
|
||||||
|
newType = varTypes[instr.src2.strVal]
|
||||||
|
of lirAddrOf, lirFieldPtr, lirArrowFieldPtr, lirIndexPtr, lirPtrAdd:
|
||||||
|
newType = "void*"
|
||||||
|
of lirAdd, lirSub, lirMul, lirDiv, lirMod, lirNeg,
|
||||||
|
lirCmpEq, lirCmpNe, lirCmpLt, lirCmpLe, lirCmpGt, lirCmpGe,
|
||||||
|
lirAnd, lirOr, lirXor, lirShl, lirShr, lirNot, lirBNot:
|
||||||
|
newType = "int"
|
||||||
else:
|
else:
|
||||||
ct = "int"
|
discard
|
||||||
tempsSeen.add((instr.dst.strVal, ct))
|
|
||||||
tempsSet.add(instr.dst.strVal)
|
|
||||||
be.tempTypes[instr.dst.strVal] = ct
|
|
||||||
|
|
||||||
# Declare all temps
|
if newType != "" and newType != oldType:
|
||||||
for t in tempsSeen:
|
be.tempTypes[name] = newType
|
||||||
# Skip if the type is a struct type (will be declared inline)
|
changed = true
|
||||||
if t.cType == "int" or t.cType == "void*" or t.cType == "double":
|
|
||||||
be.emitLine(&"{t.cType} {t.name};")
|
|
||||||
|
|
||||||
# Emit instructions in order
|
# ── Pass 3: declare temps that were inferred ──
|
||||||
|
var declared: seq[string] = @[]
|
||||||
|
for instr in f.instrs:
|
||||||
|
if instr.kind == lirAlloca and instr.dst.strVal.len > 0 and instr.dst.strVal notin declared:
|
||||||
|
declared.add(instr.dst.strVal)
|
||||||
|
continue
|
||||||
|
if instr.dst.kind == lvkTemp and instr.dst.strVal.len > 0 and instr.dst.strVal notin declared:
|
||||||
|
if be.tempTypes.hasKey(instr.dst.strVal):
|
||||||
|
let ct = be.tempTypes[instr.dst.strVal]
|
||||||
|
if ct != "":
|
||||||
|
declared.add(instr.dst.strVal)
|
||||||
|
be.emitLine(&"{ct} {instr.dst.strVal};")
|
||||||
|
|
||||||
|
# ── Pass 4: emit instructions ──
|
||||||
for instr in f.instrs:
|
for instr in f.instrs:
|
||||||
be.emitInstr(instr)
|
be.emitInstr(instr)
|
||||||
|
|
||||||
@@ -422,6 +476,13 @@ proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): s
|
|||||||
## Emit full C source from LIR builder + HIR module metadata.
|
## Emit full C source from LIR builder + HIR module metadata.
|
||||||
be.output = ""
|
be.output = ""
|
||||||
|
|
||||||
|
# Build function return type lookup table
|
||||||
|
var funcRetTypes = initTable[string, string]()
|
||||||
|
for f in module.funcs:
|
||||||
|
funcRetTypes[f.name] = typeToCStr(f.retType)
|
||||||
|
for f in module.externFuncs:
|
||||||
|
funcRetTypes[f.name] = typeToCStr(f.retType)
|
||||||
|
|
||||||
# Header
|
# Header
|
||||||
be.emitLine("/* Generated by Bux Compiler (LIR backend) */")
|
be.emitLine("/* Generated by Bux Compiler (LIR backend) */")
|
||||||
be.emitLine("#include <stdio.h>")
|
be.emitLine("#include <stdio.h>")
|
||||||
@@ -539,7 +600,7 @@ proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): s
|
|||||||
|
|
||||||
# Emit all LIR functions
|
# Emit all LIR functions
|
||||||
for f in builder.funcs:
|
for f in builder.funcs:
|
||||||
be.emitFunc(f)
|
be.emitFunc(f, funcRetTypes)
|
||||||
|
|
||||||
# C main wrapper
|
# C main wrapper
|
||||||
var hasMain = false
|
var hasMain = false
|
||||||
|
|||||||
+24
-1
@@ -16,12 +16,17 @@ type
|
|||||||
funcRetType*: string
|
funcRetType*: string
|
||||||
## Current source location for debug
|
## Current source location for debug
|
||||||
currentFile*: string
|
currentFile*: string
|
||||||
|
## Loop end labels for break/continue
|
||||||
|
loopEndLabels*: seq[string]
|
||||||
|
loopStartLabels*: seq[string]
|
||||||
|
|
||||||
proc initLowerToLirCtx*(): LowerToLirCtx =
|
proc initLowerToLirCtx*(): LowerToLirCtx =
|
||||||
result = LowerToLirCtx(
|
result = LowerToLirCtx(
|
||||||
builder: initLirBuilder(),
|
builder: initLirBuilder(),
|
||||||
varTypes: initTable[string, string](),
|
varTypes: initTable[string, string](),
|
||||||
varLirValues: initTable[string, LirValue](),
|
varLirValues: initTable[string, LirValue](),
|
||||||
|
loopEndLabels: @[],
|
||||||
|
loopStartLabels: @[],
|
||||||
)
|
)
|
||||||
|
|
||||||
# ── Helpers ──
|
# ── Helpers ──
|
||||||
@@ -360,6 +365,8 @@ proc lowerExpr(ctx: var LowerToLirCtx, node: HirNode): LirValue =
|
|||||||
var args: seq[LirValue] = @[]
|
var args: seq[LirValue] = @[]
|
||||||
if node.spawnArgs.len > 0:
|
if node.spawnArgs.len > 0:
|
||||||
args.add(lowerExpr(ctx, node.spawnArgs[0]))
|
args.add(lowerExpr(ctx, node.spawnArgs[0]))
|
||||||
|
else:
|
||||||
|
args.add(lirInt(0))
|
||||||
let t = b.freshTemp()
|
let t = b.freshTemp()
|
||||||
b.emitAlloca(t.strVal, "void*")
|
b.emitAlloca(t.strVal, "void*")
|
||||||
b.emitCall(t, "bux_task_spawn", @[lirGlobal(node.spawnCallee)] & args)
|
b.emitCall(t, "bux_task_spawn", @[lirGlobal(node.spawnCallee)] & args)
|
||||||
@@ -492,27 +499,43 @@ proc lowerStmt(ctx: var LowerToLirCtx, node: HirNode) =
|
|||||||
let bodyLbl = b.freshLabel("wbody")
|
let bodyLbl = b.freshLabel("wbody")
|
||||||
let endLbl = b.freshLabel("wend")
|
let endLbl = b.freshLabel("wend")
|
||||||
|
|
||||||
|
ctx.loopStartLabels.add(startLbl.strVal)
|
||||||
|
ctx.loopEndLabels.add(endLbl.strVal)
|
||||||
b.emitLabel(startLbl)
|
b.emitLabel(startLbl)
|
||||||
let cond = lowerExpr(ctx, node.whileCond)
|
let cond = lowerExpr(ctx, node.whileCond)
|
||||||
b.emitJz(endLbl, cond)
|
b.emitJz(endLbl, cond)
|
||||||
lowerStmt(ctx, node.whileBody)
|
lowerStmt(ctx, node.whileBody)
|
||||||
b.emitJmp(startLbl)
|
b.emitJmp(startLbl)
|
||||||
b.emitLabel(endLbl)
|
b.emitLabel(endLbl)
|
||||||
|
discard ctx.loopStartLabels.pop()
|
||||||
|
discard ctx.loopEndLabels.pop()
|
||||||
|
|
||||||
# ── Loop (infinite) ──
|
# ── Loop (infinite) ──
|
||||||
of hLoop:
|
of hLoop:
|
||||||
let startLbl = b.freshLabel("loop")
|
let startLbl = b.freshLabel("loop")
|
||||||
|
let endLbl = b.freshLabel("lend")
|
||||||
|
|
||||||
|
ctx.loopStartLabels.add(startLbl.strVal)
|
||||||
|
ctx.loopEndLabels.add(endLbl.strVal)
|
||||||
b.emitLabel(startLbl)
|
b.emitLabel(startLbl)
|
||||||
lowerStmt(ctx, node.loopBody)
|
lowerStmt(ctx, node.loopBody)
|
||||||
b.emitJmp(startLbl)
|
b.emitJmp(startLbl)
|
||||||
|
b.emitLabel(endLbl)
|
||||||
|
discard ctx.loopStartLabels.pop()
|
||||||
|
discard ctx.loopEndLabels.pop()
|
||||||
|
|
||||||
# ── Break ──
|
# ── Break ──
|
||||||
of hBreak:
|
of hBreak:
|
||||||
# We emit a break label reference; the emitter tracks the nearest loop end
|
if ctx.loopEndLabels.len > 0:
|
||||||
|
b.emitJmp(lirLabel(ctx.loopEndLabels[^1]))
|
||||||
|
else:
|
||||||
b.emitRawC("break;")
|
b.emitRawC("break;")
|
||||||
|
|
||||||
# ── Continue ──
|
# ── Continue ──
|
||||||
of hContinue:
|
of hContinue:
|
||||||
|
if ctx.loopStartLabels.len > 0:
|
||||||
|
b.emitJmp(lirLabel(ctx.loopStartLabels[^1]))
|
||||||
|
else:
|
||||||
b.emitRawC("continue;")
|
b.emitRawC("continue;")
|
||||||
|
|
||||||
# ── Alloca ──
|
# ── Alloca ──
|
||||||
|
|||||||
Reference in New Issue
Block a user