mirror of
https://github.com/nim-lang/Nim.git
synced 2026-08-26 08:31:44 +00:00
Merge branch 'devel' into pr_leak2_re
This commit is contained in:
@@ -937,3 +937,47 @@ proc mainRegen() =
|
||||
doAssert b.a.c == right
|
||||
|
||||
mainRegen()
|
||||
|
||||
|
||||
from std/typetraits import distinctBase, supportsCopyMem
|
||||
|
||||
block: # bug #26025
|
||||
type
|
||||
M[B] = distinct seq[B]
|
||||
W = object
|
||||
g: U # `U` is only declared below, so it used to be a `tyForward`
|
||||
# here and `W` ended up without `tfHasAsgn`
|
||||
U = M[uint64]
|
||||
|
||||
doAssert not supportsCopyMem(W)
|
||||
|
||||
var h: M[W]
|
||||
seq[W](h).add W(g: U(@[1'u64]))
|
||||
var copied = h
|
||||
for it in items(distinctBase(copied)):
|
||||
doAssert seq[uint64](it.g) == @[1'u64]
|
||||
doAssert seq[uint64](seq[W](h)[0].g) == @[1'u64]
|
||||
|
||||
block: # bug #26025, the propagation has to reach the indirect owners too
|
||||
type
|
||||
M2[B] = distinct seq[B]
|
||||
|
||||
ViaArray = object
|
||||
g: array[2, Late] # the forward type sits inside the field's type
|
||||
|
||||
Outer = object # `Inner` is forward here...
|
||||
a: Inner
|
||||
Inner = object
|
||||
b: Late
|
||||
Late = M2[uint64]
|
||||
|
||||
Reader = object # ...whereas `Outer` is already reified but its
|
||||
z: Outer # own flags were still provisional
|
||||
|
||||
AsTuple = tuple[a: Late]
|
||||
|
||||
doAssert not supportsCopyMem(ViaArray)
|
||||
doAssert not supportsCopyMem(Inner)
|
||||
doAssert not supportsCopyMem(Outer)
|
||||
doAssert not supportsCopyMem(Reader)
|
||||
doAssert not supportsCopyMem(AsTuple)
|
||||
|
||||
16
tests/ccgbugs/t26104.nim
Normal file
16
tests/ccgbugs/t26104.nim
Normal file
@@ -0,0 +1,16 @@
|
||||
discard """
|
||||
action: compile
|
||||
ccodeCheck: "@'((*Result).f);' .*"
|
||||
"""
|
||||
|
||||
# bug #26104: a compile-time-only `typeof` argument was treated as a runtime
|
||||
# alias of the result field, forcing an unnecessarily large temporary and copy.
|
||||
type
|
||||
B = array[131072, byte]
|
||||
Y = object
|
||||
f: B
|
||||
|
||||
proc fill(_: type B): B = discard
|
||||
proc make(): Y = result.f = fill(typeof(result.f))
|
||||
|
||||
discard make()
|
||||
35
tests/ccgbugs/t26112.nim
Normal file
35
tests/ccgbugs/t26112.nim
Normal file
@@ -0,0 +1,35 @@
|
||||
discard """
|
||||
matrix: "--mm:refc; --mm:orc"
|
||||
ccodeCheck: "'result.fromScalar = x_p0;'"
|
||||
ccodeCheck: "'result.fromObject = x_p0.fromObject;'"
|
||||
ccodeCheck: "'result.nested.fromNested = x_p0.fromNested;'"
|
||||
"""
|
||||
|
||||
# bug #26112: unrelated parameters were considered potential aliases of the
|
||||
# result location when their types could be contained in the returned object.
|
||||
|
||||
type
|
||||
Inner = object
|
||||
fromNested: int
|
||||
P = object
|
||||
fromScalar: int
|
||||
fromObject: int
|
||||
nested: Inner
|
||||
|
||||
func fromScalar(x: int): P =
|
||||
P(fromScalar: x)
|
||||
|
||||
proc fromObject(x: P): P =
|
||||
P(fromObject: x.fromObject)
|
||||
|
||||
func fromNested(x: Inner): P =
|
||||
P(nested: Inner(fromNested: x.fromNested))
|
||||
|
||||
proc selfAlias(): P =
|
||||
result.fromScalar = 42
|
||||
result = P(fromScalar: result.fromScalar)
|
||||
|
||||
doAssert fromScalar(1).fromScalar == 1
|
||||
doAssert fromObject(P(fromObject: 2)).fromObject == 2
|
||||
doAssert fromNested(Inner(fromNested: 3)).nested.fromNested == 3
|
||||
doAssert selfAlias().fromScalar == 42
|
||||
56
tests/gc/tmove_case_object.nim
Normal file
56
tests/gc/tmove_case_object.nim
Normal file
@@ -0,0 +1,56 @@
|
||||
discard """
|
||||
matrix: "--mm:refc; --mm:orc"
|
||||
"""
|
||||
|
||||
type
|
||||
A = object of RootObj
|
||||
|
||||
V = object
|
||||
case g: bool
|
||||
of true:
|
||||
v: A
|
||||
of false:
|
||||
e: string
|
||||
|
||||
var r = V(g: true, v: A())
|
||||
discard move r
|
||||
GC_fullCollect()
|
||||
|
||||
type
|
||||
Kind = enum nested, other
|
||||
Nested = object
|
||||
case kind: Kind
|
||||
of nested:
|
||||
case enabled: bool
|
||||
of true: payload: A
|
||||
of false: message: string
|
||||
of other:
|
||||
discard
|
||||
|
||||
var n = Nested(kind: nested, enabled: true, payload: A())
|
||||
discard move n
|
||||
GC_fullCollect()
|
||||
|
||||
# Moving from the other branch must keep its value alive and leave the source
|
||||
# in the default state.
|
||||
var s = V(g: false, e: "hello")
|
||||
let moved = move s
|
||||
doAssert moved.e == "hello"
|
||||
doAssert not s.g
|
||||
doAssert s.e.len == 0
|
||||
|
||||
# Reinitializing the zeroed value must also restore embedded object type
|
||||
# headers.
|
||||
type W = object
|
||||
a: A
|
||||
value: V
|
||||
text: string
|
||||
|
||||
var w = W(a: A(), value: V(g: true, v: A()), text: "content")
|
||||
let movedW = move w
|
||||
doAssert movedW.text == "content"
|
||||
doAssert cast[ptr pointer](addr w.a)[] != nil
|
||||
doAssert not w.value.g
|
||||
doAssert w.value.e.len == 0
|
||||
doAssert w.text.len == 0
|
||||
GC_fullCollect()
|
||||
59
tests/threads/tthreadallocatorforeignpool.nim
Normal file
59
tests/threads/tthreadallocatorforeignpool.nim
Normal file
@@ -0,0 +1,59 @@
|
||||
discard """
|
||||
matrix: "--mm:arc --threads:on --tlsEmulation:off; --mm:orc --threads:on --tlsEmulation:off"
|
||||
disabled: "windows"
|
||||
output: "ok"
|
||||
timeout: "30"
|
||||
"""
|
||||
|
||||
import std/posix
|
||||
|
||||
var
|
||||
escaped: pointer
|
||||
reused: pointer
|
||||
|
||||
proc allocateOnForeignThread(_: pointer): pointer {.noconv.} =
|
||||
setupForeignThreadGc()
|
||||
escaped = allocShared(96)
|
||||
cast[ptr int](escaped)[] = 73
|
||||
tearDownForeignThreadGc()
|
||||
result = nil
|
||||
|
||||
proc reuseOnForeignThread(_: pointer): pointer {.noconv.} =
|
||||
setupForeignThreadGc()
|
||||
doAssert cast[ptr int](escaped)[] == 73
|
||||
deallocShared(escaped)
|
||||
reused = allocShared(96)
|
||||
doAssert reused == escaped
|
||||
deallocShared(reused)
|
||||
tearDownForeignThreadGc()
|
||||
result = nil
|
||||
|
||||
proc consumeDeferredFree(_: pointer): pointer {.noconv.} =
|
||||
setupForeignThreadGc()
|
||||
let first = allocShared(96)
|
||||
let second = allocShared(96)
|
||||
# The first allocation advances the active chunk and collects its deferred
|
||||
# foreign frees. The next allocation reuses the remotely returned cell.
|
||||
doAssert second == escaped
|
||||
deallocShared(first)
|
||||
deallocShared(second)
|
||||
tearDownForeignThreadGc()
|
||||
result = nil
|
||||
|
||||
proc run(worker: proc(_: pointer): pointer {.noconv.}) =
|
||||
var thread: Pthread
|
||||
doAssert pthread_create(addr thread, nil, worker, nil) == 0
|
||||
doAssert pthread_join(thread, nil) == 0
|
||||
|
||||
# setup/teardown is the checkout/return boundary. A distinct native thread can
|
||||
# safely inherit the allocator even while one of its allocations is still live.
|
||||
run(allocateOnForeignThread)
|
||||
run(reuseOnForeignThread)
|
||||
|
||||
# A free that arrives while the allocator is idle is queued on its handle and
|
||||
# consumed after that allocator is handed to another foreign thread.
|
||||
run(allocateOnForeignThread)
|
||||
deallocShared(escaped)
|
||||
run(consumeDeferredFree)
|
||||
|
||||
echo "ok"
|
||||
64
tests/threads/tthreadallocatorhandoffrace.nim
Normal file
64
tests/threads/tthreadallocatorhandoffrace.nim
Normal file
@@ -0,0 +1,64 @@
|
||||
discard """
|
||||
matrix: "--mm:arc --threads:on; --mm:orc --threads:on"
|
||||
output: "ok"
|
||||
timeout: "30"
|
||||
"""
|
||||
|
||||
import std/[atomics, typedthreads]
|
||||
|
||||
const
|
||||
pointerCount = 512
|
||||
drainCount = 2048
|
||||
iterations {.intdefine.} = 200
|
||||
sizes = [16, 64, 4000, 4096, 8192]
|
||||
|
||||
var
|
||||
pointers: array[pointerCount, pointer]
|
||||
mayExit: Atomic[bool]
|
||||
|
||||
proc owner() {.thread.} =
|
||||
for i in 0..<pointers.len:
|
||||
let size = sizes[i mod sizes.len]
|
||||
pointers[i] = allocShared(size)
|
||||
cast[ptr byte](pointers[i])[] = byte(i)
|
||||
|
||||
proc borrower() {.thread.} =
|
||||
while not mayExit.load(moAcquire):
|
||||
discard
|
||||
|
||||
proc drain() {.thread.} =
|
||||
var drained: array[drainCount, pointer]
|
||||
for i in 0..<drained.len:
|
||||
drained[i] = allocShared(sizes[i mod sizes.len])
|
||||
for p in drained:
|
||||
deallocShared(p)
|
||||
let occupied = getOccupiedMem()
|
||||
doAssert occupied == 0, "allocator retained " & $occupied & " bytes"
|
||||
|
||||
for _ in 0..<iterations:
|
||||
# The owner retires with live small and big allocations. The borrower checks
|
||||
# out that region while this already-running main thread returns the cells.
|
||||
# This races foreign queue publication against both directions of the
|
||||
# MemRegion handoff without creating an unbounded number of regions.
|
||||
block:
|
||||
var thread: Thread[void]
|
||||
createThread(thread, owner)
|
||||
joinThread(thread)
|
||||
|
||||
mayExit.store(false, moRelaxed)
|
||||
var borrowerThread: Thread[void]
|
||||
createThread(borrowerThread, borrower)
|
||||
for i, p in pointers:
|
||||
if i == pointers.len div 2:
|
||||
# Let the borrower tear the allocator down while the second half of the
|
||||
# foreign publications are still in flight.
|
||||
mayExit.store(true, moRelease)
|
||||
deallocShared(p)
|
||||
joinThread(borrowerThread)
|
||||
|
||||
block:
|
||||
var thread: Thread[void]
|
||||
createThread(thread, drain)
|
||||
joinThread(thread)
|
||||
|
||||
echo "ok"
|
||||
92
tests/threads/tthreadallocatorpool.nim
Normal file
92
tests/threads/tthreadallocatorpool.nim
Normal file
@@ -0,0 +1,92 @@
|
||||
discard """
|
||||
matrix: "--mm:arc --threads:on; --mm:orc --threads:on"
|
||||
output: "ok"
|
||||
timeout: "30"
|
||||
"""
|
||||
|
||||
import std/[atomics, typedthreads]
|
||||
|
||||
const concurrentThreads = 4
|
||||
|
||||
var
|
||||
escaped: pointer
|
||||
reused: pointer
|
||||
bigEscaped: pointer
|
||||
roundAddresses: array[2, array[concurrentThreads, pointer]]
|
||||
ready: Atomic[int]
|
||||
mayExit: Atomic[bool]
|
||||
|
||||
proc allocateEscaped() {.thread.} =
|
||||
escaped = allocShared(64)
|
||||
cast[ptr int](escaped)[] = 42
|
||||
|
||||
proc consumeAfterHandoff() {.thread.} =
|
||||
doAssert cast[ptr int](escaped)[] == 42
|
||||
deallocShared(escaped)
|
||||
reused = allocShared(64)
|
||||
doAssert reused == escaped
|
||||
deallocShared(reused)
|
||||
|
||||
# A live allocation can outlast its original thread. The next thread receives
|
||||
# the same allocator and its stable handle makes the deallocation local again.
|
||||
block:
|
||||
var thread: Thread[void]
|
||||
createThread(thread, allocateEscaped)
|
||||
joinThread(thread)
|
||||
createThread(thread, consumeAfterHandoff)
|
||||
joinThread(thread)
|
||||
|
||||
proc allocateBigEscaped() {.thread.} =
|
||||
bigEscaped = allocShared(8192)
|
||||
cast[ptr int](bigEscaped)[] = 91
|
||||
|
||||
proc consumeBigAfterHandoff() {.thread.} =
|
||||
doAssert cast[ptr int](bigEscaped)[] == 91
|
||||
deallocShared(bigEscaped)
|
||||
let p = allocShared(8192)
|
||||
doAssert p == bigEscaped
|
||||
deallocShared(p)
|
||||
|
||||
# Big chunks use a separate deferred-free queue on the stable handle.
|
||||
block:
|
||||
var thread: Thread[void]
|
||||
createThread(thread, allocateBigEscaped)
|
||||
joinThread(thread)
|
||||
createThread(thread, consumeBigAfterHandoff)
|
||||
joinThread(thread)
|
||||
|
||||
proc allocateConcurrently(arg: tuple[round, index: int]) {.thread.} =
|
||||
let p = allocShared(80)
|
||||
roundAddresses[arg.round][arg.index] = p
|
||||
deallocShared(p)
|
||||
discard ready.fetchAdd(1, moRelease)
|
||||
while not mayExit.load(moAcquire):
|
||||
discard
|
||||
|
||||
proc runConcurrentRound(round: int) =
|
||||
var threads: array[concurrentThreads, Thread[tuple[round, index: int]]]
|
||||
ready.store(0, moRelaxed)
|
||||
mayExit.store(false, moRelaxed)
|
||||
for i in 0..<threads.len:
|
||||
createThread(threads[i], allocateConcurrently, (round, i))
|
||||
while ready.load(moAcquire) != concurrentThreads:
|
||||
discard
|
||||
mayExit.store(true, moRelease)
|
||||
for thread in threads.mitems:
|
||||
joinThread(thread)
|
||||
|
||||
# The first round establishes the peak number of simultaneous allocators. The
|
||||
# following rounds must reuse those regions instead of reserving one region per
|
||||
# new thread.
|
||||
runConcurrentRound(0)
|
||||
for _ in 0..<32:
|
||||
runConcurrentRound(1)
|
||||
for p in roundAddresses[1]:
|
||||
var found = false
|
||||
for old in roundAddresses[0]:
|
||||
if p == old:
|
||||
found = true
|
||||
break
|
||||
doAssert found
|
||||
|
||||
echo "ok"
|
||||
56
tests/threads/tthreadallocatorpoolrace.nim
Normal file
56
tests/threads/tthreadallocatorpoolrace.nim
Normal file
@@ -0,0 +1,56 @@
|
||||
discard """
|
||||
matrix: "--mm:arc --threads:on; --mm:orc --threads:on"
|
||||
output: "ok"
|
||||
timeout: "30"
|
||||
"""
|
||||
|
||||
import std/[atomics, typedthreads]
|
||||
|
||||
const
|
||||
pointerCount = 1024
|
||||
iterations = 100
|
||||
|
||||
var
|
||||
pointers: array[pointerCount, pointer]
|
||||
drainPointers: array[pointerCount, pointer]
|
||||
ready: Atomic[bool]
|
||||
start: Atomic[bool]
|
||||
remoteDone: Atomic[bool]
|
||||
|
||||
proc owner() {.thread.} =
|
||||
for i in 0..<pointers.len:
|
||||
pointers[i] = allocShared(16 + (i mod 8) * 16)
|
||||
ready.store(true, moRelease)
|
||||
while not start.load(moAcquire):
|
||||
discard
|
||||
|
||||
# Race allocator activity against remote frees. The owner can consume cells
|
||||
# while the remote thread is still publishing entries to its handle.
|
||||
while not remoteDone.load(moAcquire):
|
||||
for i in 0..<8:
|
||||
let p = allocShared(16 + i * 16)
|
||||
deallocShared(p)
|
||||
|
||||
# Exhaust local free lists so all remaining remote lists are consumed before
|
||||
# this allocator is returned to the pool.
|
||||
for i in 0..<drainPointers.len:
|
||||
drainPointers[i] = allocShared(16 + (i mod 8) * 16)
|
||||
for p in drainPointers:
|
||||
deallocShared(p)
|
||||
doAssert getOccupiedMem() == 0
|
||||
|
||||
for _ in 0..<iterations:
|
||||
ready.store(false, moRelaxed)
|
||||
start.store(false, moRelaxed)
|
||||
remoteDone.store(false, moRelaxed)
|
||||
var thread: Thread[void]
|
||||
createThread(thread, owner)
|
||||
while not ready.load(moAcquire):
|
||||
discard
|
||||
start.store(true, moRelease)
|
||||
for p in pointers:
|
||||
deallocShared(p)
|
||||
remoteDone.store(true, moRelease)
|
||||
joinThread(thread)
|
||||
|
||||
echo "ok"
|
||||
Reference in New Issue
Block a user