diff --git a/lib/system/alloc.nim b/lib/system/alloc.nim index 8a29b3bf30..43f83134c4 100644 --- a/lib/system/alloc.nim +++ b/lib/system/alloc.nim @@ -845,17 +845,24 @@ when defined(heaptrack): proc heaptrack_malloc(a: pointer, size: int) {.cdecl, importc, dynlib: heaptrackLib.} proc heaptrack_free(a: pointer) {.cdecl, importc, dynlib: heaptrackLib.} -proc rawAlloc(a: var MemRegion, requestedSize: int): pointer = +proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = MemAlign, extraSize: int = 0): pointer = when defined(nimTypeNames): inc(a.allocCounter) sysAssert(allocInv(a), "rawAlloc: begin") sysAssert(roundup(65, 8) == 72, "rawAlloc: roundup broken") - var size = roundup(requestedSize, MemAlign) + + # Calculate the actual size we need to allocate + # If alignment > MemAlign, we need extra space to ensure we can align the result + let alignmentOverhead = if alignment > MemAlign: alignment - 1 + extraSize else: 0 + let totalSize = requestedSize + alignmentOverhead + + var size = roundup(totalSize, MemAlign) sysAssert(size >= sizeof(FreeCell), "rawAlloc: requested size too small") sysAssert(size >= requestedSize, "insufficient allocated size!") #c_fprintf(stdout, "alloc; size: %ld; %ld\n", requestedSize, size) - if size <= SmallChunkSize-smallChunkOverhead(): + # Force big chunk allocation for aligned allocations to avoid cell boundary issues + if size <= SmallChunkSize-smallChunkOverhead() and alignment <= MemAlign: template fetchSharedCells(tc: PSmallChunk) = # Consumes cells from (potentially) foreign threads from `a.sharedFreeLists[s]` when defined(gcDestructors): @@ -965,6 +972,16 @@ proc rawAlloc(a: var MemRegion, requestedSize: int): pointer = inc a.occ, c.size trackSize(c.size) sysAssert(isAccessible(a, result), "rawAlloc 14") + + # Handle alignment if specified + if alignment > MemAlign: + # We need to ensure that (result + extraSize) is aligned + # So find the aligned address for user data, then back up by extraSize + let userDataAddr = cast[int](result) + extraSize + let alignedUserDataAddr = (userDataAddr + alignment - 1) and not (alignment - 1) + # The result should be extraSize bytes before the aligned user data + result = cast[pointer](alignedUserDataAddr - extraSize) + sysAssert(allocInv(a), "rawAlloc: end") when logAlloc: cprintf("var pointer_%p = alloc(%ld) # %p\n", result, requestedSize, addr a) when defined(heaptrack): @@ -1067,7 +1084,12 @@ when not defined(gcDestructors): (cast[ptr FreeCell](p).zeroField >% 1) else: var c = cast[PBigChunk](c) - result = p == addr(c.data) and cast[ptr FreeCell](p).zeroField >% 1 + # For aligned allocations, p might be offset from addr(c.data), but should be within data bounds + # Note: We cannot check zeroField/refcount as it may not be initialized yet when called from rawNewObj + let dataStart = cast[int](addr(c.data)) + let dataEnd = dataStart + c.size - bigChunkOverhead() + let pAddr = cast[int](p) + result = (pAddr >= dataStart) and (pAddr < dataEnd) proc prepareForInteriorPointerChecking(a: var MemRegion) {.inline.} = a.minLargeObj = lowGauge(a.root) diff --git a/lib/system/gc.nim b/lib/system/gc.nim index 3942e5eb7f..35c9cd59b9 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -458,9 +458,12 @@ proc rawNewObj(typ: PNimType, size: int, gch: var GcHeap): pointer = sysAssert(allocInv(gch.region), "rawNewObj begin") gcAssert(typ.kind in {tyRef, tyString, tySequence}, "newObj: 1") collectCT(gch) - var res = cast[PCell](rawAlloc(gch.region, size + sizeof(Cell))) + # Use alignment from typ.base if available, otherwise use MemAlign + let alignment = if typ.base != nil: max(typ.base.align, MemAlign) else: MemAlign + var res = cast[PCell](rawAlloc(gch.region, size + sizeof(Cell), alignment, sizeof(Cell))) #gcAssert typ.kind in {tyString, tySequence} or size >= typ.base.size, "size too small" - gcAssert((cast[int](res) and (MemAlign-1)) == 0, "newObj: 2") + # Check that the user data (after the Cell header) is properly aligned + gcAssert((cast[int](cellToUsr(res)) and (alignment-1)) == 0, "newObj: 2") # now it is buffered in the ZCT res.typ = typ setFrameInfo(res) @@ -508,9 +511,12 @@ proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl, noinline, raise collectCT(gch) sysAssert(allocInv(gch.region), "newObjRC1 after collectCT") - var res = cast[PCell](rawAlloc(gch.region, size + sizeof(Cell))) + # Use alignment from typ.base if available, otherwise use MemAlign + let alignment = if typ.base != nil: max(typ.base.align, MemAlign) else: MemAlign + var res = cast[PCell](rawAlloc(gch.region, size + sizeof(Cell), alignment, sizeof(Cell))) sysAssert(allocInv(gch.region), "newObjRC1 after rawAlloc") - sysAssert((cast[int](res) and (MemAlign-1)) == 0, "newObj: 2") + # Check that the user data (after the Cell header) is properly aligned + sysAssert((cast[int](cellToUsr(res)) and (alignment-1)) == 0, "newObj: 2") # now it is buffered in the ZCT res.typ = typ setFrameInfo(res) diff --git a/tests/align/talign.nim b/tests/align/talign.nim index 08373ee497..46b8f64a29 100644 --- a/tests/align/talign.nim +++ b/tests/align/talign.nim @@ -1,5 +1,6 @@ discard """ ccodeCheck: "\\i @'NIM_ALIGN(128) NI mylocal1' .*" +matrix: "--mm:refc -d:useGcAssert; --mm:orc" targets: "c cpp" output: "align ok" """ @@ -67,3 +68,46 @@ block: # bug #22419 f()() + +type Xxx = object + v {.align: 128.}: byte + +type Yyy = object + v: byte + v2: Xxx + +for i in 0..<3: + let x = new Yyy + # echo "addr v2.v:", cast[uint](addr x.v2.v) + doAssert cast[uint](addr x.v2.v) mod 128 == 0 + +let m = new Yyy +m.v2.v = 42 +doAssert m.v2.v == 42 +m.v = 7 +doAssert m.v == 7 + + +type + MyType16 = object + a {.align(16).}: int + + +var x: array[10, ref MyType16] +for q in 0..500: + for i in 0..