implemented alignas pragma (#12643)

* implemented alignas pragma

* fix bootstrap

* generate c++ compatible syntax for alignas

* Make it work.

* Multiple alignof expressions. Implement top level alignof.
This commit is contained in:
Arne Döring 2019-11-13 09:22:41 +01:00 • committed by cooldome
commit 0496a666e2
13 changed files with 153 additions and 19 deletions

View file

@ -58,7 +58,8 @@
## Language additions ## Language additions
- `alignas` pragma can now be used similar to the `alignas`
declaration modifier in C/C++.
## Language changes ## Language changes

View file

@ -824,6 +824,7 @@ type
of skLet, skVar, skField, skForVar: of skLet, skVar, skField, skForVar:
guard*: PSym guard*: PSym
bitsize*: int bitsize*: int
alignment*: int # for alignas(X) expressions
else: nil else: nil
magic*: TMagic magic*: TMagic
typ*: PType typ*: PType

View file

@ -549,6 +549,8 @@ proc genRecordFieldsAux(m: BModule, n: PNode,
#assert(field.ast == nil) #assert(field.ast == nil)
let sname = mangleRecFieldName(m, field) let sname = mangleRecFieldName(m, field)
fillLoc(field.loc, locField, n, sname, OnUnknown) fillLoc(field.loc, locField, n, sname, OnUnknown)
if field.alignment > 0:
result.addf "alignas($1) ", [rope(field.alignment)]
# for importcpp'ed objects, we only need to set field.loc, but don't # for importcpp'ed objects, we only need to set field.loc, but don't
# have to recurse via 'getTypeDescAux'. And not doing so prevents problems # have to recurse via 'getTypeDescAux'. And not doing so prevents problems
# with heavily templatized C++ code: # with heavily templatized C++ code:

View file

@ -478,7 +478,9 @@ proc localVarDecl(p: BProc; n: PNode): Rope =
if s.loc.k == locNone: if s.loc.k == locNone:
fillLoc(s.loc, locLocalVar, n, mangleLocalName(p, s), OnStack) fillLoc(s.loc, locLocalVar, n, mangleLocalName(p, s), OnStack)
if s.kind == skLet: incl(s.loc.flags, lfNoDeepCopy) if s.kind == skLet: incl(s.loc.flags, lfNoDeepCopy)
result = getTypeDesc(p.module, s.typ) if s.kind in {skLet, skVar} and s.alignment > 0:
result.addf("alignas($1) ", [rope(s.alignment)])
result.add getTypeDesc(p.module, s.typ)
if s.constraint.isNil: if s.constraint.isNil:
if sfRegister in s.flags: add(result, " register") if sfRegister in s.flags: add(result, " register")
#elif skipTypes(s.typ, abstractInst).kind in GcTypeKinds: #elif skipTypes(s.typ, abstractInst).kind in GcTypeKinds:
@ -529,6 +531,8 @@ proc assignGlobalVar(p: BProc, n: PNode) =
var decl: Rope = nil var decl: Rope = nil
var td = getTypeDesc(p.module, s.loc.t) var td = getTypeDesc(p.module, s.loc.t)
if s.constraint.isNil: if s.constraint.isNil:
if s.alignment > 0:
decl.addf "alignas($1) ", [rope(s.alignment)]
if p.hcrOn: add(decl, "static ") if p.hcrOn: add(decl, "static ")
elif sfImportc in s.flags: add(decl, "extern ") elif sfImportc in s.flags: add(decl, "extern ")
add(decl, td) add(decl, td)
@ -1187,6 +1191,8 @@ proc genVarPrototype(m: BModule, n: PNode) =
declareThreadVar(m, sym, true) declareThreadVar(m, sym, true)
else: else:
incl(m.declaredThings, sym.id) incl(m.declaredThings, sym.id)
if sym.alignment > 0:
m.s[cfsVars].addf "alignas($1) ", [rope(sym.alignment)]
add(m.s[cfsVars], if m.hcrOn: "static " else: "extern ") add(m.s[cfsVars], if m.hcrOn: "static " else: "extern ")
add(m.s[cfsVars], getTypeDesc(m, sym.loc.t)) add(m.s[cfsVars], getTypeDesc(m, sym.loc.t))
if m.hcrOn: add(m.s[cfsVars], "*") if m.hcrOn: add(m.s[cfsVars], "*")

View file

@ -20,7 +20,7 @@ const
const const
declPragmas = {wImportc, wImportObjC, wImportCpp, wImportJs, wExportc, wExportCpp, declPragmas = {wImportc, wImportObjC, wImportCpp, wImportJs, wExportc, wExportCpp,
wExportNims, wExtern, wDeprecated, wNodecl, wError, wUsed} wExportNims, wExtern, wDeprecated, wNodecl, wError, wUsed, wAlignas}
## common pragmas for declarations, to a good approximation ## common pragmas for declarations, to a good approximation
procPragmas* = declPragmas + {FirstCallConv..LastCallConv, procPragmas* = declPragmas + {FirstCallConv..LastCallConv,
wMagic, wNoSideEffect, wSideEffect, wNoreturn, wDynlib, wHeader, wMagic, wNoSideEffect, wSideEffect, wNoreturn, wDynlib, wHeader,
@ -803,13 +803,6 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
setExternName(c, sym, name, it.info) setExternName(c, sym, name, it.info)
of wImportObjC: of wImportObjC:
processImportObjC(c, sym, getOptionalStr(c, it, "$1"), it.info) processImportObjC(c, sym, getOptionalStr(c, it, "$1"), it.info)
of wAlign:
if sym.typ == nil: invalidPragma(c, it)
var align = expectIntLit(c, it)
if (not isPowerOfTwo(align) and align != 0) or align >% high(int16):
localError(c.config, it.info, "power of two expected")
else:
sym.typ.align = align.int16
of wSize: of wSize:
if sym.typ == nil: invalidPragma(c, it) if sym.typ == nil: invalidPragma(c, it)
var size = expectIntLit(c, it) var size = expectIntLit(c, it)
@ -822,6 +815,14 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
sym.typ.align = floatInt64Align(c.config) sym.typ.align = floatInt64Align(c.config)
else: else:
localError(c.config, it.info, "size may only be 1, 2, 4 or 8") localError(c.config, it.info, "size may only be 1, 2, 4 or 8")
of wAlignas:
let alignment = expectIntLit(c, it)
if alignment == 0:
discard
elif isPowerOfTwo(alignment):
sym.alignment = max(sym.alignment, alignment)
else:
localError(c.config, it.info, "power of two or 0 expected")
of wNodecl: of wNodecl:
noVal(c, it) noVal(c, it)
incl(sym.loc.flags, lfNoDecl) incl(sym.loc.flags, lfNoDecl)

View file

@ -156,6 +156,8 @@ proc computeObjectOffsetsFoldFunction(conf: ConfigRef; n: PNode, packed: bool, a
size = n.sym.typ.size.int size = n.sym.typ.size.int
align = if packed: 1 else: n.sym.typ.align.int align = if packed: 1 else: n.sym.typ.align.int
accum.align(align) accum.align(align)
if n.sym.alignment > 0:
accum.align(n.sym.alignment)
n.sym.offset = accum.offset n.sym.offset = accum.offset
accum.inc(size) accum.inc(size)
else: else:
@ -183,6 +185,8 @@ proc computeUnionObjectOffsetsFoldFunction(conf: ConfigRef; n: PNode; accum: var
size = n.sym.typ.size.int size = n.sym.typ.size.int
align = n.sym.typ.align.int align = n.sym.typ.align.int
accum.align(align) accum.align(align)
if n.sym.alignment > 0:
accum.align(n.sym.alignment)
n.sym.offset = accum.offset n.sym.offset = accum.offset
accum.inc(size) accum.inc(size)
else: else:

View file

@ -6433,6 +6433,38 @@ generates:
unsigned int flag:1; unsigned int flag:1;
}; };
Alignas pragma
--------------
The ``alignas`` pragma is for variables and object field members. It
modifies the alignment requirement of the thing being declared. The
argument must be a constant power of 2 or 0. Valid non-zero
alignments that are weaker than another alignas pragmas on the same
declaration are ignored. Alignments that are weaker that the
alignment requirement of the type are ignored. ``alignas(0)`` is
always ignored.
.. code-block:: Nim
type
sseType = object
sseData {.alignas(16).}: array[4,float32]
# every object will be aligned to 128-byte boundary
Data = object
x: char
cacheline {.alignas(128).}: array[128, char] # over-aligned array of char,
proc main() =
echo "sizeof(Data) = ", sizeof(Data), " (1 byte + 127 bytes padding + 128-byte array)"
# output: sizeof(Data) = 256 (1 byte + 127 bytes padding + 128-byte array)
echo "alignment of sseType is ", alignof(sseType)
# output: alignment of sseType is 16
var d {.alignas(2048).}: Data # this instance of data is aligned even stricter
main()
This pragma has no effect on nimvm or the js backend.
Volatile pragma Volatile pragma
--------------- ---------------

View file

@ -256,6 +256,7 @@ __clang__
#include <limits.h> #include <limits.h>
#include <stddef.h> #include <stddef.h>
#include <stdalign.h>
/* C99 compiler? */ /* C99 compiler? */
#if (defined(__STD_VERSION__) && (__STD_VERSION__ >= 199901)) #if (defined(__STD_VERSION__) && (__STD_VERSION__ >= 199901))

1
scratch.nim Normal file
View file

@ -0,0 +1 @@

View file

@ -0,0 +1,3 @@
var myglobal1* {.alignas(128).}: int32
var myglobal2* {.alignas(128).}: int32
var myglobal3* {.alignas(128).}: int32

46
tests/misc/talignas.nim Normal file
View file

@ -0,0 +1,46 @@
discard """
ccodeCheck: "\\i @'alignas(128) NI mylocal1' .*"
target: "c cpp"
output: "alignas ok"
"""
# This is for Azure. The keyword ``alignof`` only exists in ``c++11``
# and newer. On Azure gcc does not default to c++11 yet.
when defined(cpp) and not defined(windows):
{.passC: "-std=c++11".}
import globalalignas
var toplevel1 {.alignas: 32.} : int32
var toplevel2 {.alignas: 32.} : int32
var toplevel3 {.alignas: 32.} : int32
proc foobar() =
var myvar1 {.global, alignas(64).}: int = 123
var myvar2 {.global, alignas(64).}: int = 123
var myvar3 {.global, alignas(64).}: int = 123
doAssert (cast[uint](addr(myglobal1)) and 127) == 0
doAssert (cast[uint](addr(myglobal2)) and 127) == 0
doAssert (cast[uint](addr(myglobal3)) and 127) == 0
doAssert (cast[uint](addr(myvar1)) and 63) == 0
doAssert (cast[uint](addr(myvar2)) and 63) == 0
doAssert (cast[uint](addr(myvar3)) and 63) == 0
doAssert (cast[uint](addr(toplevel1)) and 31) == 0
doAssert (cast[uint](addr(toplevel2)) and 31) == 0
doAssert (cast[uint](addr(toplevel3)) and 31) == 0
# test multiple alignas expressions
var mylocal1 {.alignas(0), alignas(128), alignas(32).}: int = 123
var mylocal2 {.alignas(128), alignas(0), alignas(32).}: int = 123
var mylocal3 {.alignas(0), alignas(32), alignas(128).}: int = 123
doAssert (cast[uint](addr(mylocal1)) and 127) == 0
doAssert (cast[uint](addr(mylocal2)) and 127) == 0
doAssert (cast[uint](addr(mylocal3)) and 127) == 0
echo "alignas ok"
foobar()

View file

@ -0,0 +1,7 @@
discard """
cmd: "nim check $options $file"
errormsg: "power of two or 0 expected"
"""
proc foobar() =
let something {.alignas(33).} = 123

View file

@ -8,8 +8,8 @@ macros api OK
''' '''
""" """
# This is for travis. The keyword ``alignof`` only exists in ``c++11`` # This is for Azure. The keyword ``alignof`` only exists in ``c++11``
# and newer. On travis gcc does not default to c++11 yet. # and newer. On Azure gcc does not default to c++11 yet.
when defined(cpp) and not defined(windows): when defined(cpp) and not defined(windows):
{.passC: "-std=c++11".} {.passC: "-std=c++11".}
@ -75,7 +75,7 @@ proc strAlign(arg: string): string =
result &= ' ' result &= ' '
macro c_offsetof(a: typed, b: untyped): int32 = macro c_offsetof(a: typed, b: untyped): int32 =
## Buffet proof implementation that works on actual offsetof operator ## Bullet proof implementation that works on actual offsetof operator
## in the c backend. Assuming of course this implementation is ## in the c backend. Assuming of course this implementation is
## correct. ## correct.
let bliteral = let bliteral =
@ -89,7 +89,7 @@ macro c_offsetof(a: typed, b: untyped): int32 =
res res
macro c_sizeof(a: typed): int32 = macro c_sizeof(a: typed): int32 =
## Buffet proof implementation that works using the sizeof operator ## Bullet proof implementation that works using the sizeof operator
## in the c backend. Assuming of course this implementation is ## in the c backend. Assuming of course this implementation is
## correct. ## correct.
result = quote do: result = quote do:
@ -98,7 +98,7 @@ macro c_sizeof(a: typed): int32 =
res res
macro c_alignof(arg: untyped): untyped = macro c_alignof(arg: untyped): untyped =
## Buffet proof implementation that works on actual alignment ## Bullet proof implementation that works on actual alignment
## behavior measured at runtime. ## behavior measured at runtime.
let typeSym = genSym(nskType, "AlignTestType"&arg.repr) let typeSym = genSym(nskType, "AlignTestType"&arg.repr)
result = quote do: result = quote do:
@ -313,7 +313,7 @@ testinstance:
b: int8 b: int8
c: int8 c: int8
PaddingOfSetEnum33 = object PaddingOfSetEnum33 {.objectconfig.} = object
cause: int8 cause: int8
theSet: set[MyEnum33] theSet: set[MyEnum33]
@ -332,10 +332,17 @@ testinstance:
c: char c: char
# from issue 4763 # from issue 4763
GenericObject[T] = object GenericObject[T] {.objectconfig.} = object
a: int32 a: int32
b: T b: T
# this type mixes `packed` with `alignas`.
MyCustomAlignPackedObject {.objectconfig.} = object
a: char
b {.alignas: 32.}: int32 # alignas overrides `packed` for this field.
c: char
d: int32 # unaligned
const trivialSize = sizeof(TrivialType) # needs to be able to evaluate at compile time const trivialSize = sizeof(TrivialType) # needs to be able to evaluate at compile time
proc main(): void = proc main(): void =
@ -350,6 +357,7 @@ testinstance:
var ro : RootObj var ro : RootObj
var go : GenericObject[int64] var go : GenericObject[int64]
var po : PaddingOfSetEnum33 var po : PaddingOfSetEnum33
var capo: MyCustomAlignPackedObject
var var
e1: Enum1 e1: Enum1
@ -368,8 +376,7 @@ testinstance:
else: else:
doAssert sizeof(SimpleAlignment) > 10 doAssert sizeof(SimpleAlignment) > 10
testSizeAlignOf(t,a,b,c,d,e,f,g,ro,go,po, e1, e2, e4, e8, eoa, eob) testSizeAlignOf(t,a,b,c,d,e,f,g,ro,go,po, e1, e2, e4, e8, eoa, eob, capo)
type type
WithBitsize {.objectconfig.} = object WithBitsize {.objectconfig.} = object
@ -433,6 +440,11 @@ testinstance:
testOffsetOf(RecursiveStuff, d1) testOffsetOf(RecursiveStuff, d1)
testOffsetOf(RecursiveStuff, d2) testOffsetOf(RecursiveStuff, d2)
testOffsetOf(MyCustomAlignPackedObject, a)
testOffsetOf(MyCustomAlignPackedObject, b)
testOffsetOf(MyCustomAlignPackedObject, c)
testOffsetOf(MyCustomAlignPackedObject, d)
echo "body executed" # sanity check to ensure this logic isn't skipped entirely echo "body executed" # sanity check to ensure this logic isn't skipped entirely
@ -482,7 +494,24 @@ type
a: int32 a: int32
b: float32 b: float32
MyCustomAlignUnion {.union.} = object
c: char
a {.alignas: 32.}: int
MyCustomAlignObject = object
c: char
a {.alignas: 32.}: int
doAssert sizeof(MyUnionType) == 4 doAssert sizeof(MyUnionType) == 4
doAssert sizeof(MyCustomAlignUnion) == 32
doAssert alignof(MyCustomAlignUnion) == 32
doAssert sizeof(MyCustomAlignObject) == 64
doAssert alignof(MyCustomAlignObject) == 32
########################################## ##########################################
# bug #9794 # bug #9794