mirror of
https://github.com/Ed94/Odin.git
synced 2026-07-28 18:30:06 +00:00
Merge pull request #4836 from laytan/fix-wrong-out-of-memory
fix wrong out of memory in edge cases, just try allocate from block for one source of truth
This commit is contained in:
@@ -104,13 +104,15 @@ arena_alloc :: proc(arena: ^Arena, size, alignment: uint, loc := #caller_locatio
|
|||||||
if size == 0 {
|
if size == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
needed := align_forward_uint(size, alignment)
|
prev_used := 0 if arena.curr_block == nil else arena.curr_block.used
|
||||||
if arena.curr_block == nil || (safe_add(arena.curr_block.used, needed) or_else 0) > arena.curr_block.capacity {
|
data, err = alloc_from_memory_block(arena.curr_block, size, alignment)
|
||||||
|
if err == .Out_Of_Memory {
|
||||||
if arena.minimum_block_size == 0 {
|
if arena.minimum_block_size == 0 {
|
||||||
arena.minimum_block_size = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE
|
arena.minimum_block_size = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
needed := align_forward_uint(size, alignment)
|
||||||
block_size := max(needed, arena.minimum_block_size)
|
block_size := max(needed, arena.minimum_block_size)
|
||||||
|
|
||||||
if arena.backing_allocator.procedure == nil {
|
if arena.backing_allocator.procedure == nil {
|
||||||
@@ -121,10 +123,9 @@ arena_alloc :: proc(arena: ^Arena, size, alignment: uint, loc := #caller_locatio
|
|||||||
new_block.prev = arena.curr_block
|
new_block.prev = arena.curr_block
|
||||||
arena.curr_block = new_block
|
arena.curr_block = new_block
|
||||||
arena.total_capacity += new_block.capacity
|
arena.total_capacity += new_block.capacity
|
||||||
|
prev_used = 0
|
||||||
|
data, err = alloc_from_memory_block(arena.curr_block, size, alignment)
|
||||||
}
|
}
|
||||||
|
|
||||||
prev_used := arena.curr_block.used
|
|
||||||
data, err = alloc_from_memory_block(arena.curr_block, size, alignment)
|
|
||||||
arena.total_used += arena.curr_block.used - prev_used
|
arena.total_used += arena.curr_block.used - prev_used
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,8 +107,9 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l
|
|||||||
|
|
||||||
switch arena.kind {
|
switch arena.kind {
|
||||||
case .Growing:
|
case .Growing:
|
||||||
needed := mem.align_forward_uint(size, alignment)
|
prev_used := 0 if arena.curr_block == nil else arena.curr_block.used
|
||||||
if arena.curr_block == nil || (safe_add(arena.curr_block.used, needed) or_else 0) > arena.curr_block.reserved {
|
data, err = alloc_from_memory_block(arena.curr_block, size, alignment, default_commit_size=arena.default_commit_size)
|
||||||
|
if err == .Out_Of_Memory {
|
||||||
if arena.minimum_block_size == 0 {
|
if arena.minimum_block_size == 0 {
|
||||||
arena.minimum_block_size = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE
|
arena.minimum_block_size = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE
|
||||||
arena.minimum_block_size = mem.align_forward_uint(arena.minimum_block_size, DEFAULT_PAGE_SIZE)
|
arena.minimum_block_size = mem.align_forward_uint(arena.minimum_block_size, DEFAULT_PAGE_SIZE)
|
||||||
@@ -124,6 +125,7 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l
|
|||||||
max(arena.default_commit_size, arena.minimum_block_size)
|
max(arena.default_commit_size, arena.minimum_block_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
needed := mem.align_forward_uint(size, alignment)
|
||||||
needed = max(needed, arena.default_commit_size)
|
needed = max(needed, arena.default_commit_size)
|
||||||
block_size := max(needed, arena.minimum_block_size)
|
block_size := max(needed, arena.minimum_block_size)
|
||||||
|
|
||||||
@@ -131,10 +133,10 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l
|
|||||||
new_block.prev = arena.curr_block
|
new_block.prev = arena.curr_block
|
||||||
arena.curr_block = new_block
|
arena.curr_block = new_block
|
||||||
arena.total_reserved += new_block.reserved
|
arena.total_reserved += new_block.reserved
|
||||||
}
|
|
||||||
|
|
||||||
prev_used := arena.curr_block.used
|
prev_used = 0
|
||||||
data, err = alloc_from_memory_block(arena.curr_block, size, alignment, default_commit_size=arena.default_commit_size)
|
data, err = alloc_from_memory_block(arena.curr_block, size, alignment, default_commit_size=arena.default_commit_size)
|
||||||
|
}
|
||||||
arena.total_used += arena.curr_block.used - prev_used
|
arena.total_used += arena.curr_block.used - prev_used
|
||||||
case .Static:
|
case .Static:
|
||||||
if arena.curr_block == nil {
|
if arena.curr_block == nil {
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package mem_virtual
|
||||||
|
|
||||||
|
import "core:sys/posix"
|
||||||
|
|
||||||
|
_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
|
||||||
|
result := posix.mmap(nil, size, {}, {.ANONYMOUS, .PRIVATE})
|
||||||
|
if result == posix.MAP_FAILED {
|
||||||
|
assert_contextless(posix.errno() == .ENOMEM)
|
||||||
|
return nil, .Out_Of_Memory
|
||||||
|
}
|
||||||
|
|
||||||
|
return ([^]byte)(uintptr(result))[:size], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_decommit :: proc "contextless" (data: rawptr, size: uint) {
|
||||||
|
MADV_FREE :: 5
|
||||||
|
|
||||||
|
posix.mprotect(data, size, {})
|
||||||
|
posix.posix_madvise(data, size, transmute(posix.MAdvice)i32(MADV_FREE))
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package mem_virtual
|
||||||
|
|
||||||
|
import "core:sys/posix"
|
||||||
|
|
||||||
|
_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
|
||||||
|
|
||||||
|
PROT_MAX :: proc "contextless" (flags: posix.Prot_Flags) -> posix.Prot_Flags {
|
||||||
|
_PROT_MAX_SHIFT :: 16
|
||||||
|
return transmute(posix.Prot_Flags)(transmute(i32)flags << _PROT_MAX_SHIFT)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := posix.mmap(nil, size, PROT_MAX({.READ, .WRITE, .EXEC}), {.ANONYMOUS, .PRIVATE})
|
||||||
|
if result == posix.MAP_FAILED {
|
||||||
|
assert_contextless(posix.errno() == .ENOMEM)
|
||||||
|
return nil, .Out_Of_Memory
|
||||||
|
}
|
||||||
|
|
||||||
|
return ([^]byte)(uintptr(result))[:size], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_decommit :: proc "contextless" (data: rawptr, size: uint) {
|
||||||
|
MADV_FREE :: 5
|
||||||
|
|
||||||
|
posix.mprotect(data, size, {})
|
||||||
|
posix.posix_madvise(data, size, transmute(posix.MAdvice)i32(MADV_FREE))
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package mem_virtual
|
||||||
|
|
||||||
|
import "core:sys/posix"
|
||||||
|
|
||||||
|
_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
|
||||||
|
|
||||||
|
PROT_MPROTECT :: proc "contextless" (flags: posix.Prot_Flags) -> posix.Prot_Flags {
|
||||||
|
return transmute(posix.Prot_Flags)(transmute(i32)flags << 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := posix.mmap(nil, size, PROT_MPROTECT({.READ, .WRITE, .EXEC}), {.ANONYMOUS, .PRIVATE})
|
||||||
|
if result == posix.MAP_FAILED {
|
||||||
|
assert_contextless(posix.errno() == .ENOMEM)
|
||||||
|
return nil, .Out_Of_Memory
|
||||||
|
}
|
||||||
|
|
||||||
|
return ([^]byte)(uintptr(result))[:size], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_decommit :: proc "contextless" (data: rawptr, size: uint) {
|
||||||
|
MADV_FREE :: 6
|
||||||
|
|
||||||
|
posix.mprotect(data, size, {})
|
||||||
|
posix.posix_madvise(data, size, transmute(posix.MAdvice)i32(MADV_FREE))
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package mem_virtual
|
||||||
|
|
||||||
|
import "core:sys/posix"
|
||||||
|
|
||||||
|
_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
|
||||||
|
result := posix.mmap(nil, size, {}, {.ANONYMOUS, .PRIVATE})
|
||||||
|
if result == posix.MAP_FAILED {
|
||||||
|
assert_contextless(posix.errno() == .ENOMEM)
|
||||||
|
return nil, .Out_Of_Memory
|
||||||
|
}
|
||||||
|
|
||||||
|
return ([^]byte)(uintptr(result))[:size], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_decommit :: proc "contextless" (data: rawptr, size: uint) {
|
||||||
|
MADV_FREE :: 6
|
||||||
|
|
||||||
|
posix.mprotect(data, size, {})
|
||||||
|
posix.posix_madvise(data, size, transmute(posix.MAdvice)i32(MADV_FREE))
|
||||||
|
}
|
||||||
@@ -15,7 +15,9 @@ platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (bl
|
|||||||
to_commit = clamp(to_commit, size_of(Platform_Memory_Block), total_to_reserved)
|
to_commit = clamp(to_commit, size_of(Platform_Memory_Block), total_to_reserved)
|
||||||
|
|
||||||
data := reserve(total_to_reserved) or_return
|
data := reserve(total_to_reserved) or_return
|
||||||
commit(raw_data(data), to_commit)
|
|
||||||
|
commit_err := commit(raw_data(data), to_commit)
|
||||||
|
assert_contextless(commit_err == nil)
|
||||||
|
|
||||||
block = (^Platform_Memory_Block)(raw_data(data))
|
block = (^Platform_Memory_Block)(raw_data(data))
|
||||||
block.committed = to_commit
|
block.committed = to_commit
|
||||||
|
|||||||
@@ -4,36 +4,18 @@ package mem_virtual
|
|||||||
|
|
||||||
import "core:sys/posix"
|
import "core:sys/posix"
|
||||||
|
|
||||||
// Define non-posix needed flags:
|
|
||||||
when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD {
|
|
||||||
MADV_FREE :: 5 /* pages unneeded, discard contents */
|
|
||||||
} else when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
|
|
||||||
MADV_FREE :: 6
|
|
||||||
}
|
|
||||||
|
|
||||||
_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
|
|
||||||
flags := posix.Map_Flags{ .ANONYMOUS, .PRIVATE }
|
|
||||||
result := posix.mmap(nil, size, {}, flags)
|
|
||||||
if result == posix.MAP_FAILED {
|
|
||||||
return nil, .Out_Of_Memory
|
|
||||||
}
|
|
||||||
|
|
||||||
return ([^]byte)(uintptr(result))[:size], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
|
_commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
|
||||||
if posix.mprotect(data, size, { .READ, .WRITE }) != .OK {
|
if posix.mprotect(data, size, { .READ, .WRITE }) != .OK {
|
||||||
return .Out_Of_Memory
|
#partial switch posix.errno() {
|
||||||
|
case .EACCES, .EPERM: return .Invalid_Pointer
|
||||||
|
case .ENOTSUP, .EINVAL: return .Invalid_Argument
|
||||||
|
case: return .Out_Of_Memory
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_decommit :: proc "contextless" (data: rawptr, size: uint) {
|
|
||||||
posix.mprotect(data, size, {})
|
|
||||||
posix.posix_madvise(data, size, transmute(posix.MAdvice)i32(MADV_FREE))
|
|
||||||
}
|
|
||||||
|
|
||||||
_release :: proc "contextless" (data: rawptr, size: uint) {
|
_release :: proc "contextless" (data: rawptr, size: uint) {
|
||||||
posix.munmap(data, size)
|
posix.munmap(data, size)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package test_core_mem
|
package test_core_mem
|
||||||
|
|
||||||
import "core:mem/tlsf"
|
import "core:mem/tlsf"
|
||||||
|
import "core:mem/virtual"
|
||||||
import "core:testing"
|
import "core:testing"
|
||||||
|
|
||||||
@test
|
@test
|
||||||
@@ -38,4 +39,18 @@ test_tlsf_bitscan :: proc(t: ^testing.T) {
|
|||||||
testing.expectf(t, res == test.exp, "Expected tlsf.fls_uint(0x%16x) == %v, got %v", test.v, test.exp, res)
|
testing.expectf(t, res == test.exp, "Expected tlsf.fls_uint(0x%16x) == %v, got %v", test.v, test.exp, res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_align_bumping_block_limit :: proc(t: ^testing.T) {
|
||||||
|
a: virtual.Arena
|
||||||
|
defer virtual.arena_destroy(&a)
|
||||||
|
|
||||||
|
data, err := virtual.arena_alloc(&a, 4193371, 1)
|
||||||
|
testing.expect_value(t, err, nil)
|
||||||
|
testing.expect(t, len(data) == 4193371)
|
||||||
|
|
||||||
|
data, err = virtual.arena_alloc(&a, 896, 64)
|
||||||
|
testing.expect_value(t, err, nil)
|
||||||
|
testing.expect(t, len(data) == 896)
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,6 +31,21 @@ test_temp_allocator_big_alloc_and_alignment :: proc(t: ^testing.T) {
|
|||||||
testing.expect(t, err == nil)
|
testing.expect(t, err == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_align_bumping_block_limit :: proc(t: ^testing.T) {
|
||||||
|
a: runtime.Arena
|
||||||
|
a.minimum_block_size = 8*mem.Megabyte
|
||||||
|
defer runtime.arena_destroy(&a)
|
||||||
|
|
||||||
|
data, err := runtime.arena_alloc(&a, 4193371, 1)
|
||||||
|
testing.expect_value(t, err, nil)
|
||||||
|
testing.expect(t, len(data) == 4193371)
|
||||||
|
|
||||||
|
data, err = runtime.arena_alloc(&a, 896, 64)
|
||||||
|
testing.expect_value(t, err, nil)
|
||||||
|
testing.expect(t, len(data) == 896)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_temp_allocator_returns_correct_size :: proc(t: ^testing.T) {
|
test_temp_allocator_returns_correct_size :: proc(t: ^testing.T) {
|
||||||
arena: runtime.Arena
|
arena: runtime.Arena
|
||||||
|
|||||||
Reference in New Issue
Block a user