nim-ffi/ffi/alloc.nim

67 lines
2.3 KiB
Nim
Raw Normal View History

2026-05-20 14:14:42 -03:00
## Cross-thread allocation helpers backed by libc `malloc`/`free`.
##
## We deliberately avoid Nim's `allocShared`/`deallocShared` here. Under
## `--mm:orc` they delegate to the per-thread `allocator` MemRegion stored
## in TLS; freeing such a buffer from a different thread later walks
## `chunk.owner` back to that MemRegion. If the original thread has exited
## by then (e.g. a `std::async` worker that produced the FFI request and
## was destroyed before the FFI thread ran `deleteRequest`), `chunk.owner`
## dangles into reclaimed TLS and `addToSharedFreeList` segfaults — TSan on
## ARM reproduces this from `TimerE2E.ThreadedHammer`. `malloc`/`free` are
## process-global and thread-lifetime-independent, so freeing on a different
## thread is safe.
import system/ansi_c
2025-08-09 22:56:44 +02:00
## Can be shared safely between threads
type SharedSeq*[T] = tuple[data: ptr UncheckedArray[T], len: int]
proc alloc*(str: cstring): cstring =
2026-05-20 14:14:42 -03:00
## Allocates a fresh null-terminated copy of `str` via `c_malloc`. The
## returned pointer must be released with `dealloc(cstring)`.
2025-08-09 22:56:44 +02:00
if str.isNil():
2026-05-20 14:14:42 -03:00
var ret = cast[cstring](c_malloc(1))
ret[0] = '\0'
2025-08-09 22:56:44 +02:00
return ret
2026-05-20 14:14:42 -03:00
let ret = cast[cstring](c_malloc(csize_t(len(str) + 1)))
2025-08-09 22:56:44 +02:00
copyMem(ret, str, len(str) + 1)
return ret
proc alloc*(str: string): cstring =
2026-05-20 14:14:42 -03:00
## Allocates a fresh null-terminated copy of `str` via `c_malloc`. The
## returned pointer must be released with `dealloc(cstring)`.
var ret = cast[cstring](c_malloc(csize_t(str.len + 1)))
2025-08-09 22:56:44 +02:00
let s = cast[seq[char]](str)
for i in 0 ..< str.len:
ret[i] = s[i]
ret[str.len] = '\0'
return ret
2026-05-20 14:14:42 -03:00
proc dealloc*(p: cstring) {.inline.} =
## Frees a buffer obtained from one of the `alloc(...)` overloads above.
## Nil-safe.
if not p.isNil():
c_free(cast[pointer](p))
2026-05-20 14:14:42 -03:00
proc allocSharedSeq*[T](s: seq[T]): SharedSeq[T] =
if s.len == 0:
return (cast[ptr UncheckedArray[T]](nil), 0)
2026-05-20 14:14:42 -03:00
let data = c_malloc(csize_t(sizeof(T) * s.len))
copyMem(data, unsafeAddr s[0], sizeof(T) * s.len)
2025-08-09 22:56:44 +02:00
return (cast[ptr UncheckedArray[T]](data), s.len)
proc deallocSharedSeq*[T](s: var SharedSeq[T]) =
if not s.data.isNil():
2026-05-20 14:14:42 -03:00
c_free(s.data)
2025-08-09 22:56:44 +02:00
s.len = 0
proc toSeq*[T](s: SharedSeq[T]): seq[T] =
## Creates a seq[T] from a SharedSeq[T]. No explicit dealloc is required
## as req[T] is a GC managed type.
var ret = newSeq[T]()
for i in 0 ..< s.len:
ret.add(s.data[i])
return ret