Phase 8.2.4: Slice fat pointer + bounds checking

- T[] slices are now fat pointers {data: T*, len: size_t} in C backend
- Added hSliceIndex HIR node for slice indexing (distinct from hIndexPtr for raw pointers)
- Added exprIndexBoundsCheck flag to AST ekIndex
- Sema sets bounds-check flag in @[Checked] functions
- C backend emits Slice_<T> typedefs and compound literals for slice init
- bux_bounds_check() called automatically on slice indexing in @[Checked] funcs
- Unchecked functions allow raw access (C behavior) for performance
This commit is contained in:
2026-06-01 11:50:45 +03:00
parent bf9e73d56e
commit cefd2a8442
6 changed files with 85 additions and 24 deletions
+1
View File
@@ -179,6 +179,7 @@ type
of ekIndex: of ekIndex:
exprIndexObj*: Expr exprIndexObj*: Expr
exprIndexIdx*: Expr exprIndexIdx*: Expr
exprIndexBoundsCheck*: bool
of ekField: of ekField:
exprFieldObj*: Expr exprFieldObj*: Expr
exprFieldName*: string exprFieldName*: string
+66 -22
View File
@@ -7,6 +7,7 @@ type
indent*: int indent*: int
varCounter*: int varCounter*: int
declaredVars*: seq[string] declaredVars*: seq[string]
sliceTypeDefs*: seq[tuple[name: string, elem: string]] ## Generated Slice_<T> typedefs
proc cEscape(s: string): string = proc cEscape(s: string): string =
## Escape a string for use as a C string literal. ## Escape a string for use as a C string literal.
@@ -45,7 +46,7 @@ proc freshVar(be: var CBackend): string =
result = &"__tmp_{be.varCounter}" result = &"__tmp_{be.varCounter}"
# Type conversion: Bux Type → C type string # Type conversion: Bux Type → C type string
proc typeToC*(typ: Type): string = proc typeToC*(be: var CBackend, typ: Type): string =
if typ == nil: return "void" if typ == nil: return "void"
case typ.kind case typ.kind
of tkVoid: return "void" of tkVoid: return "void"
@@ -71,14 +72,21 @@ proc typeToC*(typ: Type): string =
of tkFloat64: return "double" of tkFloat64: return "double"
of tkPointer, tkRef, tkMutRef: of tkPointer, tkRef, tkMutRef:
if typ.inner.len > 0: if typ.inner.len > 0:
return typeToC(typ.inner[0]) & "*" return typeToC(be, typ.inner[0]) & "*"
return "void*" return "void*"
of tkDynRef: of tkDynRef:
return typ.name & "_FatPtr" return typ.name & "_FatPtr"
of tkSlice: of tkSlice:
if typ.inner.len > 0: let elemName = if typ.inner.len > 0: typeToC(be, typ.inner[0]) else: "void"
return typeToC(typ.inner[0]) & "*" let sliceName = "Slice_" & elemName.replace(" ", "_").replace("*", "Ptr")
return "void*" var alreadyDefined = false
for d in be.sliceTypeDefs:
if d.name == sliceName:
alreadyDefined = true
break
if not alreadyDefined:
be.sliceTypeDefs.add((name: sliceName, elem: elemName))
return sliceName
of tkNamed: of tkNamed:
# Map common Bux type names to C types # Map common Bux type names to C types
case typ.name case typ.name
@@ -252,12 +260,20 @@ proc emitExpr(be: var CBackend, node: HirNode): string =
return &"(({node.structInitName}){{{fieldsStr}}})" return &"(({node.structInitName}){{{fieldsStr}}})"
of hSliceInit: of hSliceInit:
# For now, use a static array let sliceName = typeToC(be, node.typ)
var elems: seq[string] = @[] var elems: seq[string] = @[]
for e in node.sliceInitElements: for e in node.sliceInitElements:
elems.add(be.emitExpr(e)) elems.add(be.emitExpr(e))
let elemsStr = elems.join(", ") let elemsStr = elems.join(", ")
return &"{{{elemsStr}}}" let elemType = if node.typ.inner.len > 0: typeToC(be, node.typ.inner[0]) else: "void"
return &"({sliceName}){{.data = ({elemType}[]){{{elemsStr}}}, .len = {node.sliceInitLen}}}"
of hSliceIndex:
let base = be.emitExpr(node.sliceIndexBase)
let idx = be.emitExpr(node.sliceIndexIndex)
if node.sliceIndexBoundsCheck:
return &"(bux_bounds_check((size_t)({idx}), ({base}).len), ({base}).data[{idx}])"
else:
return &"({base}).data[{idx}]"
of hTupleInit: of hTupleInit:
var elems: seq[string] = @[] var elems: seq[string] = @[]
@@ -267,14 +283,14 @@ proc emitExpr(be: var CBackend, node: HirNode): string =
of hCast: of hCast:
let operand = be.emitExpr(node.castOperand) let operand = be.emitExpr(node.castOperand)
let typ = typeToC(node.castType) let typ = typeToC(be, node.castType)
return &"(({typ}){operand})" return &"(({typ}){operand})"
of hIs: of hIs:
return "true" # TODO: proper type checking return "true" # TODO: proper type checking
of hSizeOf: of hSizeOf:
let typ = typeToC(node.sizeOfType) let typ = typeToC(be, node.sizeOfType)
return &"sizeof({typ})" return &"sizeof({typ})"
of hSpawn: of hSpawn:
@@ -391,7 +407,7 @@ proc emitStmt(be: var CBackend, node: HirNode) =
be.emitLine("}") be.emitLine("}")
of hAlloca: of hAlloca:
let typ = typeToC(node.allocaType) let typ = typeToC(be, node.allocaType)
be.emitLine(&"{typ} {node.allocaName};") be.emitLine(&"{typ} {node.allocaName};")
of hStore: of hStore:
@@ -419,10 +435,10 @@ proc emitStmt(be: var CBackend, node: HirNode) =
be.emitLine(&"{expr};") be.emitLine(&"{expr};")
proc emitFunc*(be: var CBackend, hfunc: HirFunc) = proc emitFunc*(be: var CBackend, hfunc: HirFunc) =
let retType = typeToC(hfunc.retType) let retType = typeToC(be, hfunc.retType)
var params: seq[string] = @[] var params: seq[string] = @[]
for p in hfunc.params: for p in hfunc.params:
params.add(&"{typeToC(p.typ)} {p.name}") params.add(&"{typeToC(be, p.typ)} {p.name}")
if params.len == 0: if params.len == 0:
params.add("void") params.add("void")
let paramsStr = params.join(", ") let paramsStr = params.join(", ")
@@ -445,7 +461,7 @@ proc emitStruct*(be: var CBackend, name: string, fields: seq[tuple[name: string,
be.emitLine(&"typedef struct {name} {{") be.emitLine(&"typedef struct {name} {{")
inc be.indent inc be.indent
for f in fields: for f in fields:
let typ = typeToC(f.typ) let typ = typeToC(be, f.typ)
be.emitLine(&"{typ} {f.name};") be.emitLine(&"{typ} {f.name};")
dec be.indent dec be.indent
be.emitLine(&"}} {name};") be.emitLine(&"}} {name};")
@@ -492,14 +508,14 @@ proc emitEnum*(be: var CBackend, name: string, variants: seq[HirEnumVariant]) =
if v.fields.len > 0: if v.fields.len > 0:
# Positional fields # Positional fields
for i, f in v.fields: for i, f in v.fields:
let typ = typeToC(f) let typ = typeToC(be, f)
be.emitLine(&"{typ} {v.name}_{i};") be.emitLine(&"{typ} {v.name}_{i};")
elif v.namedFields.len > 0: elif v.namedFields.len > 0:
# Named fields - generate as struct # Named fields - generate as struct
be.emitLine(&"struct {{") be.emitLine(&"struct {{")
inc be.indent inc be.indent
for nf in v.namedFields: for nf in v.namedFields:
let typ = typeToC(nf.typ) let typ = typeToC(be, nf.typ)
be.emitLine(&"{typ} {nf.name};") be.emitLine(&"{typ} {nf.name};")
dec be.indent dec be.indent
be.emitLine(&"}} {v.name};") be.emitLine(&"}} {v.name};")
@@ -517,15 +533,34 @@ proc emitEnum*(be: var CBackend, name: string, variants: seq[HirEnumVariant]) =
be.emitLine("") be.emitLine("")
proc emitExternDecl*(be: var CBackend, efunc: HirFunc) = proc emitExternDecl*(be: var CBackend, efunc: HirFunc) =
let retType = typeToC(efunc.retType) let retType = typeToC(be, efunc.retType)
var params: seq[string] = @[] var params: seq[string] = @[]
for p in efunc.params: for p in efunc.params:
params.add(&"{typeToC(p.typ)} {p.name}") params.add(&"{typeToC(be, p.typ)} {p.name}")
if params.len == 0: if params.len == 0:
params.add("void") params.add("void")
let paramsStr = params.join(", ") let paramsStr = params.join(", ")
be.emitLine(&"extern {retType} {efunc.name}({paramsStr});") be.emitLine(&"extern {retType} {efunc.name}({paramsStr});")
proc collectSliceTypes(module: HirModule): seq[tuple[name: string, elem: string]] =
## Pre-pass: collect all slice types used in the module.
var dummyBe = initCBackend()
for f in module.funcs:
discard typeToC(dummyBe, f.retType)
for p in f.params:
discard typeToC(dummyBe, p.typ)
for ef in module.externFuncs:
discard typeToC(dummyBe, ef.retType)
for p in ef.params:
discard typeToC(dummyBe, p.typ)
for s in module.structs:
for f in s.fields:
discard typeToC(dummyBe, f.typ)
for c in module.consts:
if c.value != nil:
discard typeToC(dummyBe, c.value.typ)
return dummyBe.sliceTypeDefs
proc emitModule*(be: var CBackend, module: HirModule): string = proc emitModule*(be: var CBackend, module: HirModule): string =
# Header # Header
be.emitLine("/* Generated by Bux Compiler */") be.emitLine("/* Generated by Bux Compiler */")
@@ -536,6 +571,9 @@ proc emitModule*(be: var CBackend, module: HirModule): string =
be.emitLine("#include <string.h>") be.emitLine("#include <string.h>")
be.emitLine("") be.emitLine("")
# Pre-collect slice types so we can emit forward declarations early
let sliceTypes = collectSliceTypes(module)
# Forward declarations # Forward declarations
for s in module.structs: for s in module.structs:
be.emitLine(&"typedef struct {s.name} {s.name};") be.emitLine(&"typedef struct {s.name} {s.name};")
@@ -547,7 +585,6 @@ proc emitModule*(be: var CBackend, module: HirModule): string =
be.emitLine(&"typedef struct {iface.name}_FatPtr {iface.name}_FatPtr;") be.emitLine(&"typedef struct {iface.name}_FatPtr {iface.name}_FatPtr;")
if module.interfaces.len > 0: if module.interfaces.len > 0:
be.emitLine("") be.emitLine("")
# Extern function declarations # Extern function declarations
if module.externFuncs.len > 0: if module.externFuncs.len > 0:
be.emitLine("/* Extern function declarations */") be.emitLine("/* Extern function declarations */")
@@ -583,12 +620,19 @@ proc emitModule*(be: var CBackend, module: HirModule): string =
for e in module.enums: for e in module.enums:
be.emitEnum(e.name, e.variants) be.emitEnum(e.name, e.variants)
# Slice fat-pointer typedefs
if sliceTypes.len > 0:
be.emitLine("/* Slice types */")
for st in sliceTypes:
be.emitLine(&"typedef struct {{ {st.elem}* data; size_t len; }} {st.name};")
be.emitLine("")
# Forward declarations for all functions # Forward declarations for all functions
for f in module.funcs: for f in module.funcs:
let retType = typeToC(f.retType) let retType = typeToC(be, f.retType)
var params: seq[string] = @[] var params: seq[string] = @[]
for p in f.params: for p in f.params:
params.add(typeToC(p.typ) & " " & p.name) params.add(typeToC(be, p.typ) & " " & p.name)
if params.len == 0: if params.len == 0:
params.add("void") params.add("void")
be.emitLine(retType & " " & f.name & "(" & params.join(", ") & ");") be.emitLine(retType & " " & f.name & "(" & params.join(", ") & ");")
@@ -608,10 +652,10 @@ proc emitModule*(be: var CBackend, module: HirModule): string =
if i == 0: if i == 0:
paramCtypes.add("void* self") # First param is always self (erased) paramCtypes.add("void* self") # First param is always self (erased)
else: else:
paramCtypes.add(typeToC(p) & " param") paramCtypes.add(typeToC(be, p) & " param")
if paramCtypes.len == 0: if paramCtypes.len == 0:
paramCtypes.add("void") paramCtypes.add("void")
let ret = typeToC(m.ret) let ret = typeToC(be, m.ret)
let paramsStr = paramCtypes.join(", ") let paramsStr = paramCtypes.join(", ")
be.emitLine(&"{ret} (*{m.name})({paramsStr});") be.emitLine(&"{ret} (*{m.name})({paramsStr});")
dec be.indent dec be.indent
+6
View File
@@ -24,6 +24,7 @@ type
hFieldPtr hFieldPtr
hArrowField hArrowField
hIndexPtr hIndexPtr
hSliceIndex
# Functions # Functions
hCall hCall
hCallIndirect hCallIndirect
@@ -136,6 +137,11 @@ type
structInitFields*: seq[tuple[name: string, value: HirNode]] structInitFields*: seq[tuple[name: string, value: HirNode]]
of hSliceInit: of hSliceInit:
sliceInitElements*: seq[HirNode] sliceInitElements*: seq[HirNode]
sliceInitLen*: int
of hSliceIndex:
sliceIndexBase*: HirNode
sliceIndexIndex*: HirNode
sliceIndexBoundsCheck*: bool
of hRange: of hRange:
rangeLo*: HirNode rangeLo*: HirNode
rangeHi*: HirNode rangeHi*: HirNode
+9 -1
View File
@@ -625,6 +625,13 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
of ekIndex: of ekIndex:
let base = ctx.lowerExpr(expr.exprIndexObj) let base = ctx.lowerExpr(expr.exprIndexObj)
let idx = ctx.lowerExpr(expr.exprIndexIdx) let idx = ctx.lowerExpr(expr.exprIndexIdx)
let baseType = ctx.resolveExprType(expr.exprIndexObj)
if baseType.isSlice:
let sliceIdx = HirNode(kind: hSliceIndex, sliceIndexBase: base,
sliceIndexIndex: idx,
sliceIndexBoundsCheck: expr.exprIndexBoundsCheck,
typ: typ, loc: loc)
return sliceIdx
let basePtr = HirNode(kind: hIndexPtr, indexPtrBase: base, let basePtr = HirNode(kind: hIndexPtr, indexPtrBase: base,
indexPtrIndex: idx, typ: makePointer(typ), loc: loc) indexPtrIndex: idx, typ: makePointer(typ), loc: loc)
return HirNode(kind: hLoad, loadPtr: basePtr, typ: typ, loc: loc) return HirNode(kind: hLoad, loadPtr: basePtr, typ: typ, loc: loc)
@@ -655,7 +662,8 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
var elems: seq[HirNode] = @[] var elems: seq[HirNode] = @[]
for e in expr.exprSliceElements: for e in expr.exprSliceElements:
elems.add(ctx.lowerExpr(e)) elems.add(ctx.lowerExpr(e))
return HirNode(kind: hSliceInit, sliceInitElements: elems, typ: typ, loc: loc) return HirNode(kind: hSliceInit, sliceInitElements: elems,
sliceInitLen: elems.len, typ: typ, loc: loc)
of ekRange: of ekRange:
let lo = ctx.lowerExpr(expr.exprRangeLo) let lo = ctx.lowerExpr(expr.exprRangeLo)
+1 -1
View File
@@ -535,7 +535,7 @@ proc parsePostfix(p: var Parser): Expr =
discard p.advance() discard p.advance()
let idx = p.parseExpr() let idx = p.parseExpr()
discard p.expect(tkRBracket, "expected ']' to close index") discard p.expect(tkRBracket, "expected ']' to close index")
left = Expr(kind: ekIndex, loc: loc, exprIndexObj: left, exprIndexIdx: idx) left = Expr(kind: ekIndex, loc: loc, exprIndexObj: left, exprIndexIdx: idx, exprIndexBoundsCheck: false)
of tkDot: of tkDot:
# Field expression or .await # Field expression or .await
discard p.advance() discard p.advance()
+2
View File
@@ -999,6 +999,8 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
if not idx.isInteger: if not idx.isInteger:
sema.emitError(expr.loc, "index must be integer") sema.emitError(expr.loc, "index must be integer")
if obj.isSlice: if obj.isSlice:
if sema.checkedFunc:
expr.exprIndexBoundsCheck = true
return obj.inner[0] return obj.inner[0]
elif obj.isPointer: elif obj.isPointer:
return obj.inner[0] return obj.inner[0]