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:
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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()
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
Reference in New Issue
Block a user