Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

source2il: implement alignment support #112

Merged
merged 4 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
132 changes: 87 additions & 45 deletions passes/source2il.nim
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ using
expr: var IrNode
stmts: var seq[IrNode]

func align(val: SizeUnit, to: SizeUnit): SizeUnit =
## Rounds `val` to the multiple of `to`, the latter must be a power of two.
let mask = val - 1
(val + mask) and not mask

template `+`(t: SemType, a: set[ExprFlag]): ExprType =
## Convenience shortcut for creating an ``ExprType``.
(typ: t, attribs: a)
Expand Down Expand Up @@ -311,29 +316,27 @@ proc typeToIL(c; typ: SemType): uint32 =
of tkTuple:
let args = mapIt(typ.elems, c.typeToIL(it))
c.addType Record:
c.types.add Node(kind: Immediate, val: size(typ).uint32)
# XXX: for the sake of ease of implementation, records use the maximum
# possible alignment
c.types.add Node(kind: Immediate, val: 8)
c.types.add Node(kind: Immediate, val: paddedSize(typ).uint32)
c.types.add Node(kind: Immediate, val: alignment(typ).uint32)
var off = 0 ## the current field offset
for i, it in args.pairs:
off = align(off, alignment(typ.elems[i]))
c.types.subTree Field:
c.types.add Node(kind: Immediate, val: off.uint32)
c.types.add Node(kind: Type, val: it)
off += size(typ.elems[i])
off += paddedSize(typ.elems[i])
of tkUnion:
let args = mapIt(typ.elems, c.typeToIL(it))
let inner = c.addType Union:
c.types.add Node(kind: Immediate, val: size(typ).uint32 - 8)
c.types.add Node(kind: Immediate, val: 8)
c.types.add Node(kind: Immediate, val: innerSize(typ).uint32)
c.types.add Node(kind: Immediate, val: innerAlignment(typ).uint32)
for it in args.items:
c.types.add Node(kind: Type, val: it)

let tag = c.typeToIL(prim(tkInt))
c.addType Record:
c.types.add Node(kind: Immediate, val: size(typ).uint32)
# XXX: alignment is ignored at the moment and just set to 1
c.types.add Node(kind: Immediate, val: 8)
c.types.add Node(kind: Immediate, val: paddedSize(typ).uint32)
c.types.add Node(kind: Immediate, val: alignment(typ).uint32)
# the tag field:
c.types.subTree Field:
c.types.add Node(kind: Immediate, val: 0)
Expand All @@ -351,8 +354,8 @@ proc typeToIL(c; typ: SemType): uint32 =
lengthType = c.typeToIL(prim(tkInt))
pointerType = c.typeToIL(pointerType)
c.addType Record:
c.types.add Node(kind: Immediate, val: size(typ).uint32)
c.types.add Node(kind: Immediate, val: 8)
c.types.add Node(kind: Immediate, val: paddedSize(typ).uint32)
c.types.add Node(kind: Immediate, val: alignment(typ).uint32)
# the length field:
c.types.subTree Field:
c.types.add Node(kind: Immediate, val: 0)
Expand Down Expand Up @@ -399,6 +402,10 @@ proc genProcType(c; typ: SemType): uint32 =
result = rawGenProcType(c, typ)
c.procTypeCache[typ] = result

proc payloadAlignment(typ: SemType): SizeUnit =
## Computes the alignment for a seq payload for type `typ`.
max(alignment(prim(tkInt)), alignment(typ))

proc genPayloadType(c; typ: SemType): uint32 =
## Generates and emits the payload type for a sequence with the given
## element type (`typ`). Returns the ID of the payload type.
Expand All @@ -410,13 +417,13 @@ proc genPayloadType(c; typ: SemType): uint32 =

let arrayType = c.addType Array:
c.types.add Node(kind: Immediate, val: 1) # size
c.types.add Node(kind: Immediate, val: 8) # alignment
c.types.add Node(kind: Immediate, val: alignment(typ).uint32) # alignment
c.types.add Node(kind: Immediate, val: 0)
c.types.add Node(kind: Type, val: elem)

c.addType Record:
c.types.add Node(kind: Immediate, val: 9)
c.types.add Node(kind: Immediate, val: 8)
c.types.add Node(kind: Immediate, val: payloadAlignment(typ).uint32)
# the capacity field:
c.types.subTree Field:
c.types.add Node(kind: Immediate, val: 0)
Expand Down Expand Up @@ -448,6 +455,9 @@ proc genCapAccess(c; e: sink IrNode, typ: SemType): IrNode =
proc newIntOp(c; op: NodeKind, a, b: sink IrNode): IrNode =
newBinaryOp(op, c.typeToIL(prim(tkInt)), a, b)

proc newAllocCall(size, align: sink IrNode): IrNode =
newCall(AllocProc, @[size, align])

proc genTypeBoundOp(c; op: TypeAttachedOp, typ: SemType): uint32 =
## Synthesizes and emits the `op` type-bound operator for `typ`. Returns the
## ID of the synthesized procedure; no caching is performed.
Expand Down Expand Up @@ -481,13 +491,15 @@ proc genTypeBoundOp(c; op: TypeAttachedOp, typ: SemType): uint32 =

var els = IrNode(kind: Stmts)
els.add newAsgn(dstLen, srcLen)
# the size of the payload is sizeof(capacity) + sizeof(element) * length
els.add newAsgn(newFieldExpr(dst, 1), newCall(AllocProc,
newBinaryOp(Add, c.typeToIL(prim(tkInt)),
newBinaryOp(Mul, c.typeToIL(prim(tkInt)),
# the size of the payload is:
# align(sizeof(capacity), alignment(element)) + sizeof(element) * length
els.add newAsgn(newFieldExpr(dst, 1), newAllocCall(
c.newIntOp(Add,
c.newIntOp(Mul,
srcLen,
newIntVal(size(typ.elems[0]))),
newIntVal(size(prim(tkInt))))))
newIntVal(paddedSize(typ.elems[0]))),
newIntVal(align(size(prim(tkInt)), alignment(typ.elems[0])))),
newIntVal(alignment(typ.elems[0]))))

els.add newAsgn(c.genCapAccess(dst, typ), srcLen)

Expand Down Expand Up @@ -952,7 +964,8 @@ proc callToIL(c; t; n: NodeIndex, expr; stmts): SemType =
newDeref(c.typeToIL(elem.typ),
newCall(PrepareAddProc, @[newAddr(newFieldExpr(tmp, 0)),
newAddr(newFieldExpr(tmp, 1)),
newIntVal(size(elem.typ))])),
newIntVal(paddedSize(elem.typ)),
newIntVal(alignment(elem.typ))])),
use(c, elem, stmts))

expr = tmp
Expand Down Expand Up @@ -1151,8 +1164,9 @@ proc exprToIL(c; t: InTree, n: NodeIndex, expr, stmts): ExprType =

# emit the payload field assignment:
if str.len > 0:
let size = size(prim(tkInt)) + size(elem) * str.len
stmts.add newAsgn(payloadField, newCall(AllocProc, newIntVal(size)))
let size = size(prim(tkInt)) + paddedSize(elem) * str.len
stmts.add newAsgn(payloadField,
newAllocCall(newIntVal(size), newIntVal(alignment(elem))))

let payloadExpr = newDeref(c.genPayloadType(elem), payloadField)
# emit the capacity assignment:
Expand Down Expand Up @@ -1194,9 +1208,11 @@ proc exprToIL(c; t: InTree, n: NodeIndex, expr, stmts): ExprType =

# emit the payload field assignment:
if length > 0:
let size = size(prim(tkInt)) + size(typ) * length
let size = align(size(prim(tkInt)), alignment(typ)) +
(paddedSize(typ) * length)
# the size of the payload is sizeof(capacity) + sizeof(element) * length
stmts.add newAsgn(payloadField, newCall(AllocProc, newIntVal(size)))
stmts.add newAsgn(payloadField,
newAllocCall(newIntVal(size), newIntVal(alignment(typ))))

let payloadExpr = newDeref(c.genPayloadType(typ), payloadField)
# emit the capacity assignment:
Expand Down Expand Up @@ -1413,10 +1429,11 @@ proc importIoProcedures(c) =
newAddr(newFieldExpr(c.genPayloadAccess(newLocal(0), stringType), 1)),
newFieldExpr(newLocal(0), 0)]))
readFileBody.add newAsgn(newFieldExpr(newLocal(1), 1),
newCall(AllocProc,
newAllocCall(
c.newIntOp(Add,
newIntVal(size(prim(tkInt))),
newFieldExpr(newLocal(1), 0))))
newFieldExpr(newLocal(1), 0)),
newIntVal(payloadAlignment(stringType.elems[0]))))
readFileBody.add newAsgn(newFieldExpr(newLocal(1), 0),
newCall(readFileHostPrc.uint32, @[
newAddr(newFieldExpr(c.genPayloadAccess(newLocal(0), stringType), 1)),
Expand Down Expand Up @@ -1501,12 +1518,23 @@ proc open*(reporter: sink(ref ReportContext[string])): ModuleCtx =
# exception/panic is raised
const AllocBody = """
(ProcDef (Type $1)
(Params (Local 0))
(Locals (Type $2) (Type $2))
(Params (Local 0) (Local 2))
(Locals (Type $2) (Type $2) (Type $2))
(Stmts
(If (Eq (Type $2) (Load (Type $2) (Copy (Global 3))) (IntVal 0))
(Asgn (Local 1) (Copy (Global 1)))
(Asgn (Local 1) (Load (Type $2) (Copy (Global 3)))))
(Asgn (Local 1)
(BitAnd (Type $2)
(Add (Type $2)
(Copy (Local 1))
(Sub (Type $2)
(Copy (Local 2))
(IntVal 1)))
(BitNot (Type $2)
(Sub (Type $2)
(Copy (Local 2))
(IntVal 1)))))
(If
(Le (Type $2)
(Add (Type $2)
Expand All @@ -1522,7 +1550,8 @@ proc open*(reporter: sink(ref ReportContext[string])): ModuleCtx =
(Return (Copy (Local 1))))
(Raise (IntVal 0)))))
"""
var procTy = SemType(kind: tkProc, elems: @[pointerType, prim(tkInt)])
var procTy = SemType(kind: tkProc,
elems: @[pointerType, prim(tkInt), prim(tkInt)])
addProc procTy, AllocBody,
[$c.genProcType(procTy), $c.typeToIL(prim(tkInt))]

Expand All @@ -1536,18 +1565,23 @@ proc open*(reporter: sink(ref ReportContext[string])): ModuleCtx =
addProc procTy, DeallocBody,
[$c.genProcType(procTy), $c.typeToIL(prim(tkInt))]

# realloc(ptr: pointer, oldsize: int, newsize: int) -> pointer
# realloc(ptr: pointer, oldsize: int, newsize: int, align: int) -> pointer
const ReallocBody = """
(ProcDef (Type $1)
(Params (Local 0) (Local 1) (Local 2))
(Locals (Type $2) (Type $2) (Type $2) (Type $2))
(Params (Local 0) (Local 1) (Local 2) (Local 4))
(Locals (Type $2) (Type $2) (Type $2) (Type $2) (Type $2))
(If
(Eq (Type $2)
(Copy (Local 0))
(IntVal 0))
(Return (Call (Proc 0) (Copy (Local 2))))
(Return (Call (Proc 0)
(Copy (Local 2))
(Copy (Local 4))))
(Stmts
(Asgn (Local 3) (Call (Proc 0) (Copy (Local 2))))
(Asgn (Local 3)
(Call (Proc 0)
(Copy (Local 2))
(Copy (Local 4))))
(Blit (Copy (Local 3)) (Copy (Local 0)) (Copy (Local 1)))
(Drop (Call (Proc 1) (Copy (Local 0))))
(Return (Copy (Local 3))))
Expand All @@ -1556,18 +1590,23 @@ proc open*(reporter: sink(ref ReportContext[string])): ModuleCtx =
"""
procTy = SemType(kind: tkProc,
elems: @[pointerType, pointerType, prim(tkInt),
prim(tkInt)])
prim(tkInt), prim(tkInt)])
addProc procTy, ReallocBody,
[$c.genProcType(procTy), $c.typeToIL(prim(tkInt))]

# grow(payload: pointer, capacity: int, stride: int) -> pointer
# grow(payload: pointer, capacity: int, stride: int, align: int) -> pointer
# reallocates the given payload if its capacity is less than the requested
# one. The new payload is returned
const GrowBody = """
(ProcDef (Type $1)
(Params (Local 0) (Local 1) (Local 2))
(Locals (Type $2) (Type $2) (Type $2))
(Params (Local 0) (Local 1) (Local 2) (Local 3))
(Locals (Type $2) (Type $2) (Type $2) (Type $2))
(Stmts
(If
(Lt (Type $2)
(Copy (Local 3))
(IntVal 8))
(Asgn (Local 3) (IntVal 8)))
(If
(Eq (Type $2)
(Copy (Local 0))
Expand All @@ -1578,7 +1617,8 @@ proc open*(reporter: sink(ref ReportContext[string])): ModuleCtx =
(IntVal $3)
(Mul (Type $2)
(Copy (Local 1))
(Copy (Local 2))))))
(Copy (Local 2))))
(Copy (Local 3))))
(If
(Not
(Lt (Type $2)
Expand All @@ -1597,7 +1637,8 @@ proc open*(reporter: sink(ref ReportContext[string])): ModuleCtx =
(IntVal $3)
(Mul (Type $2)
(Copy (Local 1))
(Copy (Local 2))))))))
(Copy (Local 2))))
(Copy (Local 3))))))
(Store (Type $2)
(Copy (Local 0))
(Copy (Local 1)))
Expand All @@ -1607,14 +1648,14 @@ proc open*(reporter: sink(ref ReportContext[string])): ModuleCtx =
[$c.genProcType(procTy), $c.typeToIL(prim(tkInt)),
$size(prim(tkInt)) #[<- offset of payload's data field]#]

# prepareAdd(lenAddr: pointer, payloadAddr: pointer, stride: int) -> pointer
# prepareAdd(lenAddr: pointer, payloadAddr: pointer, stride: int, align: int) -> pointer
# increments the length and resizes the payload (via the grow procedure)
# when needed. The implementation works with all seq types in order to not
# having to synthesize a version for each used seq type
const PrepareAddBody = """
(ProcDef (Type $1)
(Params (Local 0) (Local 1) (Local 2))
(Locals (Type $2) (Type $2) (Type $2) (Type $2))
(Params (Local 0) (Local 1) (Local 2) (Local 4))
(Locals (Type $2) (Type $2) (Type $2) (Type $2) (Type $2))
(Stmts
(Asgn (Local 3)
(Load (Type $2)
Expand All @@ -1631,7 +1672,8 @@ proc open*(reporter: sink(ref ReportContext[string])): ModuleCtx =
(Copy (Local 1)))
(Load (Type $2)
(Copy (Local 0)))
(Copy (Local 2))))
(Copy (Local 2))
(Copy (Local 4))))
(Return
(Add (Type $2)
(Add (Type $2)
Expand Down
50 changes: 48 additions & 2 deletions phy/types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ import
]

type
SizeUnit* = int
## Used for numbers that represent size and alignment values.
## 1 size-unit = 1 byte.
# TODO: make the type a distinct unsigned type

TypeKind* = enum
tkError
tkVoid
Expand Down Expand Up @@ -101,8 +106,8 @@ proc isSubtypeOf*(a, b: SemType): bool =
else:
false

proc size*(t: SemType): int =
## Computes the size-in-bytes that an instance of `t` occupies in memory.
proc size*(t: SemType): SizeUnit =
## Computes the size without padding of a location of type `t`.
case t.kind
of tkVoid: unreachable()
of tkError: 8 # TODO: return a value indicating "unknown"
Expand All @@ -122,6 +127,47 @@ proc size*(t: SemType): int =
of tkSeq:
size(prim(tkInt)) * 2 # length + pointer

proc alignment*(t: SemType): SizeUnit =
## Computes the alignment requirement for a location of type `t`.
case t.kind
of tkVoid: unreachable()
of tkError: 8
of tkUnit, tkBool, tkChar: 1
of tkInt, tkFloat: 8
of tkProc: 8
of tkTuple:
var a = 0
for it in t.elems.items:
a = max(a, alignment(it))
a
of tkUnion:
var a = 0
for it in t.elems.items:
a = max(a, alignment(it))
# the tag also contributes to the alignment
max(a, alignment(prim(tkInt)))
of tkSeq:
alignment(prim(tkInt))

proc innerSize*(t: SemType): SizeUnit =
## Computes the size without padding of a union (`t`) without the tag.
assert t.kind == tkUnion
result = 0
for it in t.elems.items:
result = max(size(it), result)

proc innerAlignment*(t: SemType): SizeUnit =
## Computes the size without padding of a union (`t`) without the tag.
assert t.kind == tkUnion
result = 0
for it in t.elems.items:
result = max(alignment(it), result)

proc paddedSize*(t: SemType): SizeUnit =
## Computes the size of an array element of type `t`.
let mask = alignment(t) - 1
(size(t) + mask) and not mask

proc commonType*(a, b: SemType): SemType =
## Finds the common type between `a` and `b`, or produces an error.
if a == b or b.isSubtypeOf(a):
Expand Down
Loading