diff --git a/passes/source2il.nim b/passes/source2il.nim index 227d269..9863472 100644 --- a/passes/source2il.nim +++ b/passes/source2il.nim @@ -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) @@ -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) @@ -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) @@ -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. @@ -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) @@ -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. @@ -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) @@ -954,7 +966,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 @@ -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: @@ -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: @@ -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)), @@ -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) @@ -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))] @@ -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)))) @@ -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)) @@ -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) @@ -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))) @@ -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) @@ -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) diff --git a/phy/types.nim b/phy/types.nim index 9135b21..b9791c6 100644 --- a/phy/types.nim +++ b/phy/types.nim @@ -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 @@ -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" @@ -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):