diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1a1d18231..8ae39667b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -75,32 +75,35 @@ jobs:
fail-fast: false
matrix:
# MacOS 13 runs on Intel, 14 runs on ARM
- os: [ubuntu-latest, macos-13, macos-14]
+ os: [macos-13, macos-14, ubuntu-latest]
runs-on: ${{ matrix.os }}
- name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel' || 'Ubuntu') }} Build, Check, and Test
+ name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel') || (matrix.os == 'ubuntu-latest' && 'Ubuntu') }} Build, Check, and Test
timeout-minutes: 15
steps:
- - uses: actions/checkout@v4
- - name: Download LLVM (Linux)
- if: matrix.os == 'ubuntu-latest'
- run: |
- wget https://apt.llvm.org/llvm.sh
- chmod +x llvm.sh
- sudo ./llvm.sh 20
- echo "/usr/lib/llvm-20/bin" >> $GITHUB_PATH
+ - uses: actions/checkout@v4
- name: Download LLVM (MacOS Intel)
if: matrix.os == 'macos-13'
run: |
brew update
brew install llvm@20 lua@5.4 lld
+ echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH
- name: Download LLVM (MacOS ARM)
if: matrix.os == 'macos-14'
run: |
brew update
brew install llvm@20 wasmtime lua@5.4 lld
+ echo "$(brew --prefix llvm@20)/bin" >> $GITHUB_PATH
+
+ - name: Download LLVM (Ubuntu)
+ if: matrix.os == 'ubuntu-latest'
+ run: |
+ wget https://apt.llvm.org/llvm.sh
+ chmod +x llvm.sh
+ sudo ./llvm.sh 20
+ echo "/usr/lib/llvm-20/bin" >> $GITHUB_PATH
- name: Build Odin
run: ./build_odin.sh release
@@ -124,53 +127,52 @@ jobs:
- name: Odin check vendor/sdl3
run: ./odin check vendor/sdl3 -strict-style -vet -disallow-do -no-entry-point
- name: Normal Core library tests
- run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+ run: ./odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Optimized Core library tests
- run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+ run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Vendor library tests
- run: ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+ run: ./odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Internals tests
- run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+ run: ./odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: GitHub Issue tests
run: |
cd tests/issues
./run.sh
- - name: Check benchmarks
- run: ./odin check tests/benchmark -vet -strict-style -no-entry-point
- - name: Odin check examples/all for Linux i386
- run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_i386
- if: matrix.os == 'ubuntu-latest'
- - name: Odin check examples/all for Linux arm64
- run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_arm64
- if: matrix.os == 'ubuntu-latest'
- - name: Odin check examples/all for FreeBSD amd64
- run: ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
- if: matrix.os == 'ubuntu-latest'
- - name: Odin check examples/all for OpenBSD amd64
- run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64
- if: matrix.os == 'ubuntu-latest'
-
- - name: Odin check vendor/sdl3 for Linux i386
- run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_i386
- if: matrix.os == 'ubuntu-latest'
- - name: Odin check vendor/sdl3 for Linux arm64
- run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_arm64
- if: matrix.os == 'ubuntu-latest'
- - name: Odin check vendor/sdl3 for FreeBSD amd64
- run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:freebsd_amd64
- if: matrix.os == 'ubuntu-latest'
- - name: Odin check vendor/sdl3 for OpenBSD amd64
- run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:openbsd_amd64
- if: matrix.os == 'ubuntu-latest'
-
-
- name: Run demo on WASI WASM32
run: |
./odin build examples/demo -target:wasi_wasm32 -vet -strict-style -disallow-do -out:demo
wasmtime ./demo.wasm
if: matrix.os == 'macos-14'
+ - name: Check benchmarks
+ run: ./odin check tests/benchmark -vet -strict-style -no-entry-point
+ - name: Odin check examples/all for Linux i386
+ if: matrix.os == 'ubuntu-latest'
+ run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_i386
+ - name: Odin check examples/all for Linux arm64
+ if: matrix.os == 'ubuntu-latest'
+ run: ./odin check examples/all -vet -strict-style -disallow-do -target:linux_arm64
+ - name: Odin check examples/all for FreeBSD amd64
+ if: matrix.os == 'ubuntu-latest'
+ run: ./odin check examples/all -vet -strict-style -disallow-do -target:freebsd_amd64
+ - name: Odin check examples/all for OpenBSD amd64
+ if: matrix.os == 'ubuntu-latest'
+ run: ./odin check examples/all -vet -strict-style -disallow-do -target:openbsd_amd64
+
+ - name: Odin check vendor/sdl3 for Linux i386
+ if: matrix.os == 'ubuntu-latest'
+ run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_i386
+ - name: Odin check vendor/sdl3 for Linux arm64
+ if: matrix.os == 'ubuntu-latest'
+ run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:linux_arm64
+ - name: Odin check vendor/sdl3 for FreeBSD amd64
+ if: matrix.os == 'ubuntu-latest'
+ run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:freebsd_amd64
+ - name: Odin check vendor/sdl3 for OpenBSD amd64
+ if: matrix.os == 'ubuntu-latest'
+ run: ./odin check vendor/sdl3 -vet -strict-style -disallow-do -no-entry-point -target:openbsd_amd64
+
build_windows:
name: Windows Build, Check, and Test
runs-on: windows-2022
@@ -215,23 +217,23 @@ jobs:
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
- odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+ odin test tests/core/normal.odin -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Optimized core library tests
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
- odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+ odin test tests/core/speed.odin -o:speed -file -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Vendor library tests
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
copy vendor\lua\5.4\windows\*.dll .
- odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+ odin test tests/vendor -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Odin internals tests
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
- odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true
+ odin test tests/internal -all-packages -vet -strict-style -disallow-do -define:ODIN_TEST_FANCY=false -define:ODIN_TEST_FAIL_ON_BAD_MEMORY=true -sanitize:address
- name: Check issues
shell: cmd
run: |
diff --git a/.gitignore b/.gitignore
index 75bb61c43..fdb030309 100644
--- a/.gitignore
+++ b/.gitignore
@@ -293,6 +293,7 @@ build.sh
# RAD debugger project file
*.raddbg
-
+*.rdi
+tests/issues/build/*
misc/featuregen/featuregen
codegen/build/gen_src.map
diff --git a/README.md b/README.md
index 7703c0a7d..7c39e273b 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,7 @@ There were additions made for quality of life reasons:
-
+
diff --git a/base/builtin/builtin.odin b/base/builtin/builtin.odin
index 227ceeb49..14da9603d 100644
--- a/base/builtin/builtin.odin
+++ b/base/builtin/builtin.odin
@@ -119,7 +119,8 @@ jmag :: proc(value: Quaternion) -> Float ---
kmag :: proc(value: Quaternion) -> Float ---
conj :: proc(value: Complex_Or_Quaternion) -> Complex_Or_Quaternion ---
-expand_values :: proc(value: Struct_Or_Array) -> (A, B, C, ...) ---
+expand_values :: proc(value: Struct_Or_Array) -> (A, B, C, ...) ---
+compress_values :: proc(values: ...) -> Struct_Or_Array_Like_Type ---
min :: proc(values: ..T) -> T ---
max :: proc(values: ..T) -> T ---
diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin
index bec452007..46e39c8d1 100644
--- a/base/intrinsics/intrinsics.odin
+++ b/base/intrinsics/intrinsics.odin
@@ -221,6 +221,9 @@ type_map_cell_info :: proc($T: typeid) -> ^runtime.Map_Cell_Info ---
type_convert_variants_to_pointers :: proc($T: typeid) -> typeid where type_is_union(T) ---
type_merge :: proc($U, $V: typeid) -> typeid where type_is_union(U), type_is_union(V) ---
+type_integer_to_unsigned :: proc($T: typeid) -> type where type_is_integer(T), !type_is_unsigned(T) ---
+type_integer_to_signed :: proc($T: typeid) -> type where type_is_integer(T), type_is_unsigned(T) ---
+
type_has_shared_fields :: proc($U, $V: typeid) -> bool where type_is_struct(U), type_is_struct(V) ---
constant_utf16_cstring :: proc($literal: string) -> [^]u16 ---
@@ -274,8 +277,12 @@ simd_lanes_ge :: proc(a, b: #simd[N]T) -> #simd[N]Integer ---
simd_extract :: proc(a: #simd[N]T, idx: uint) -> T ---
simd_replace :: proc(a: #simd[N]T, idx: uint, elem: T) -> #simd[N]T ---
+simd_reduce_add_bisect :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
+simd_reduce_mul_bisect :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_add_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_mul_ordered :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
+simd_reduce_add_pairs :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
+simd_reduce_mul_pairs :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_min :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_max :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
simd_reduce_and :: proc(a: #simd[N]T) -> T where type_is_integer(T) || type_is_float(T)---
@@ -298,7 +305,7 @@ simd_masked_store :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U)
simd_masked_expand_load :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) -> #simd[N]T where type_is_integer(U) || type_is_boolean(U) ---
simd_masked_compress_store :: proc(ptr: rawptr, val: #simd[N]T, mask: #simd[N]U) where type_is_integer(U) || type_is_boolean(U) ---
-
+simd_indices :: proc($T: typeid/#simd[$N]$E) -> T where type_is_numeric(T) ---
simd_shuffle :: proc(a, b: #simd[N]T, indices: ..int) -> #simd[len(indices)]T ---
simd_select :: proc(cond: #simd[N]boolean_or_integer, true, false: #simd[N]T) -> #simd[N]T ---
@@ -353,15 +360,18 @@ x86_xgetbv :: proc(cx: u32) -> (eax, edx: u32) ---
objc_object :: struct{}
objc_selector :: struct{}
objc_class :: struct{}
+objc_ivar :: struct{}
+
objc_id :: ^objc_object
objc_SEL :: ^objc_selector
objc_Class :: ^objc_class
+objc_Ivar :: ^objc_ivar
objc_find_selector :: proc($name: string) -> objc_SEL ---
objc_register_selector :: proc($name: string) -> objc_SEL ---
objc_find_class :: proc($name: string) -> objc_Class ---
objc_register_class :: proc($name: string) -> objc_Class ---
-
+objc_ivar_get :: proc(self: ^$T) -> ^$U ---
valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2, a3, a4: uintptr) -> uintptr ---
diff --git a/base/runtime/core.odin b/base/runtime/core.odin
index cd7d35540..1dadbbf6f 100644
--- a/base/runtime/core.odin
+++ b/base/runtime/core.odin
@@ -110,7 +110,6 @@ Type_Info_Parameters :: struct { // Only used for procedures parameters and resu
types: []^Type_Info,
names: []string,
}
-Type_Info_Tuple :: Type_Info_Parameters // Will be removed eventually
Type_Info_Struct_Flags :: distinct bit_set[Type_Info_Struct_Flag; u8]
Type_Info_Struct_Flag :: enum u8 {
@@ -559,10 +558,14 @@ ALL_ODIN_OS_TYPES :: Odin_OS_Types{
Odin_Platform_Subtarget_Type :: enum int {
Default,
iOS,
+ Android,
}
*/
Odin_Platform_Subtarget_Type :: type_of(ODIN_PLATFORM_SUBTARGET)
+Odin_Platform_Subtarget_Types :: bit_set[Odin_Platform_Subtarget_Type]
+
+
/*
// Defined internally by the compiler
Odin_Sanitizer_Flag :: enum u32 {
diff --git a/base/runtime/default_temp_allocator_arena.odin b/base/runtime/default_temp_allocator_arena.odin
index 15c27e316..5700fec44 100644
--- a/base/runtime/default_temp_allocator_arena.odin
+++ b/base/runtime/default_temp_allocator_arena.odin
@@ -1,6 +1,7 @@
package runtime
import "base:intrinsics"
+import "base:sanitizer"
DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: uint(DEFAULT_TEMP_ALLOCATOR_BACKING_SIZE)
@@ -43,6 +44,8 @@ memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint
block.base = ([^]byte)(uintptr(block) + base_offset)
block.capacity = uint(end - uintptr(block.base))
+ sanitizer.address_poison(block.base, block.capacity)
+
// Should be zeroed
assert(block.used == 0)
assert(block.prev == nil)
@@ -52,6 +55,7 @@ memory_block_alloc :: proc(allocator: Allocator, capacity: uint, alignment: uint
memory_block_dealloc :: proc(block_to_free: ^Memory_Block, loc := #caller_location) {
if block_to_free != nil {
allocator := block_to_free.allocator
+ sanitizer.address_unpoison(block_to_free.base, block_to_free.capacity)
mem_free(block_to_free, allocator, loc)
}
}
@@ -83,6 +87,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint)
return
}
data = block.base[block.used+alignment_offset:][:min_size]
+ sanitizer.address_unpoison(block.base[block.used:block.used+size])
block.used += size
return
}
@@ -164,6 +169,7 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
if arena.curr_block != nil {
intrinsics.mem_zero(arena.curr_block.base, arena.curr_block.used)
arena.curr_block.used = 0
+ sanitizer.address_poison(arena.curr_block.base, arena.curr_block.capacity)
}
arena.total_used = 0
}
@@ -228,6 +234,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
// grow data in-place, adjusting next allocation
block.used = uint(new_end)
data = block.base[start:new_end]
+ sanitizer.address_unpoison(data)
return
}
}
@@ -301,6 +308,7 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
assert(block.used >= temp.used, "out of order use of arena_temp_end", loc)
amount_to_zero := block.used-temp.used
intrinsics.mem_zero(block.base[temp.used:], amount_to_zero)
+ sanitizer.address_poison(block.base[temp.used:block.capacity])
block.used = temp.used
arena.total_used -= amount_to_zero
}
diff --git a/base/runtime/dynamic_map_internal.odin b/base/runtime/dynamic_map_internal.odin
index ea3ecd986..d63c24355 100644
--- a/base/runtime/dynamic_map_internal.odin
+++ b/base/runtime/dynamic_map_internal.odin
@@ -1033,3 +1033,32 @@ default_hasher_cstring :: proc "contextless" (data: rawptr, seed: uintptr) -> ui
h &= HASH_MASK
return uintptr(h) | uintptr(uintptr(h) == 0)
}
+
+default_hasher_f64 :: proc "contextless" (f: f64, seed: uintptr) -> uintptr {
+ f := f
+ buf: [size_of(f)]u8
+ if f == 0 {
+ return default_hasher(&buf, seed, size_of(buf))
+ }
+ if f != f {
+ // TODO(bill): What should the logic be for NaNs?
+ return default_hasher(&f, seed, size_of(f))
+ }
+ return default_hasher(&f, seed, size_of(f))
+}
+
+default_hasher_complex128 :: proc "contextless" (x, y: f64, seed: uintptr) -> uintptr {
+ seed := seed
+ seed = default_hasher_f64(x, seed)
+ seed = default_hasher_f64(y, seed)
+ return seed
+}
+
+default_hasher_quaternion256 :: proc "contextless" (x, y, z, w: f64, seed: uintptr) -> uintptr {
+ seed := seed
+ seed = default_hasher_f64(x, seed)
+ seed = default_hasher_f64(y, seed)
+ seed = default_hasher_f64(z, seed)
+ seed = default_hasher_f64(w, seed)
+ return seed
+}
\ No newline at end of file
diff --git a/base/runtime/heap_allocator_windows.odin b/base/runtime/heap_allocator_windows.odin
index e07df7559..04a6f149b 100644
--- a/base/runtime/heap_allocator_windows.odin
+++ b/base/runtime/heap_allocator_windows.odin
@@ -1,5 +1,7 @@
package runtime
+import "../sanitizer"
+
foreign import kernel32 "system:Kernel32.lib"
@(private="file")
@@ -16,7 +18,10 @@ foreign kernel32 {
_heap_alloc :: proc "contextless" (size: int, zero_memory := true) -> rawptr {
HEAP_ZERO_MEMORY :: 0x00000008
- return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size))
+ ptr := HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY if zero_memory else 0, uint(size))
+ // NOTE(lucas): asan not guarunteed to unpoison win32 heap out of the box, do it ourselves
+ sanitizer.address_unpoison(ptr, size)
+ return ptr
}
_heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
if new_size == 0 {
@@ -28,7 +33,10 @@ _heap_resize :: proc "contextless" (ptr: rawptr, new_size: int) -> rawptr {
}
HEAP_ZERO_MEMORY :: 0x00000008
- return HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size))
+ new_ptr := HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, uint(new_size))
+ // NOTE(lucas): asan not guarunteed to unpoison win32 heap out of the box, do it ourselves
+ sanitizer.address_unpoison(new_ptr, new_size)
+ return new_ptr
}
_heap_free :: proc "contextless" (ptr: rawptr) {
if ptr == nil {
diff --git a/base/runtime/internal.odin b/base/runtime/internal.odin
index 59811a525..38b7f662c 100644
--- a/base/runtime/internal.odin
+++ b/base/runtime/internal.odin
@@ -1106,3 +1106,11 @@ __read_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uin
dst[j>>3] |= the_bit<<(j&7)
}
}
+
+when .Address in ODIN_SANITIZER_FLAGS {
+ foreign {
+ @(require)
+ __asan_unpoison_memory_region :: proc "system" (address: rawptr, size: uint) ---
+ }
+}
+
diff --git a/base/runtime/procs_darwin.odin b/base/runtime/procs_darwin.odin
index c3fc46af1..0aec57e80 100644
--- a/base/runtime/procs_darwin.odin
+++ b/base/runtime/procs_darwin.odin
@@ -2,21 +2,34 @@
package runtime
@(priority_index=-1e6)
-foreign import "system:Foundation.framework"
+foreign import ObjC "system:objc"
import "base:intrinsics"
-objc_id :: ^intrinsics.objc_object
+objc_id :: ^intrinsics.objc_object
objc_Class :: ^intrinsics.objc_class
-objc_SEL :: ^intrinsics.objc_selector
+objc_SEL :: ^intrinsics.objc_selector
+objc_Ivar :: ^intrinsics.objc_ivar
+objc_BOOL :: bool
-foreign Foundation {
- objc_lookUpClass :: proc "c" (name: cstring) -> objc_Class ---
+
+objc_IMP :: proc "c" (object: objc_id, sel: objc_SEL, #c_vararg args: ..any) -> objc_id
+
+foreign ObjC {
sel_registerName :: proc "c" (name: cstring) -> objc_SEL ---
- objc_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class ---
objc_msgSend :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
objc_msgSend_fpret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> f64 ---
objc_msgSend_fp2ret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) -> complex128 ---
objc_msgSend_stret :: proc "c" (self: objc_id, op: objc_SEL, #c_vararg args: ..any) ---
+
+ objc_lookUpClass :: proc "c" (name: cstring) -> objc_Class ---
+ objc_allocateClassPair :: proc "c" (superclass: objc_Class, name: cstring, extraBytes: uint) -> objc_Class ---
+ objc_registerClassPair :: proc "c" (cls : objc_Class) ---
+ class_addMethod :: proc "c" (cls: objc_Class, name: objc_SEL, imp: objc_IMP, types: cstring) -> objc_BOOL ---
+ class_addIvar :: proc "c" (cls: objc_Class, name: cstring, size: uint, alignment: u8, types: cstring) -> objc_BOOL ---
+ class_getInstanceVariable :: proc "c" (cls : objc_Class, name: cstring) -> objc_Ivar ---
+ class_getInstanceSize :: proc "c" (cls : objc_Class) -> uint ---
+ ivar_getOffset :: proc "c" (v: objc_Ivar) -> uintptr ---
}
+
diff --git a/base/sanitizer/address.odin b/base/sanitizer/address.odin
new file mode 100644
index 000000000..edfdfc172
--- /dev/null
+++ b/base/sanitizer/address.odin
@@ -0,0 +1,601 @@
+#+no-instrumentation
+package sanitizer
+
+Address_Death_Callback :: #type proc "c" (pc: rawptr, bp: rawptr, sp: rawptr, addr: rawptr, is_write: i32, access_size: uint)
+
+@(private="file")
+ASAN_ENABLED :: .Address in ODIN_SANITIZER_FLAGS
+
+@(private="file")
+@(default_calling_convention="system")
+foreign {
+ __asan_poison_memory_region :: proc(address: rawptr, size: uint) ---
+ __asan_unpoison_memory_region :: proc(address: rawptr, size: uint) ---
+ __sanitizer_set_death_callback :: proc(callback: Address_Death_Callback) ---
+ __asan_region_is_poisoned :: proc(begin: rawptr, size: uint) -> rawptr ---
+ __asan_address_is_poisoned :: proc(addr: rawptr) -> i32 ---
+ __asan_describe_address :: proc(addr: rawptr) ---
+ __asan_report_present :: proc() -> i32 ---
+ __asan_get_report_pc :: proc() -> rawptr ---
+ __asan_get_report_bp :: proc() -> rawptr ---
+ __asan_get_report_sp :: proc() -> rawptr ---
+ __asan_get_report_address :: proc() -> rawptr ---
+ __asan_get_report_access_type :: proc() -> i32 ---
+ __asan_get_report_access_size :: proc() -> uint ---
+ __asan_get_report_description :: proc() -> cstring ---
+ __asan_locate_address :: proc(addr: rawptr, name: rawptr, name_size: uint, region_address: ^rawptr, region_size: ^uint) -> cstring ---
+ __asan_get_alloc_stack :: proc(addr: rawptr, trace: rawptr, size: uint, thread_id: ^i32) -> uint ---
+ __asan_get_free_stack :: proc(addr: rawptr, trace: rawptr, size: uint, thread_id: ^i32) -> uint ---
+ __asan_get_shadow_mapping :: proc(shadow_scale: ^uint, shadow_offset: ^uint) ---
+ __asan_print_accumulated_stats :: proc() ---
+ __asan_get_current_fake_stack :: proc() -> rawptr ---
+ __asan_addr_is_in_fake_stack :: proc(fake_stack: rawptr, addr: rawptr, beg: ^rawptr, end: ^rawptr) -> rawptr ---
+ __asan_handle_no_return :: proc() ---
+ __asan_update_allocation_context :: proc(addr: rawptr) -> i32 ---
+}
+
+Address_Access_Type :: enum {
+ none,
+ read,
+ write,
+}
+
+Address_Located_Address :: struct {
+ category: string,
+ name: string,
+ region: []byte,
+}
+
+Address_Shadow_Mapping :: struct {
+ scale: uint,
+ offset: uint,
+}
+
+/*
+Marks a slice as unaddressable
+
+Code instrumented with `-sanitize:address` is forbidden from accessing any address
+within the slice. This procedure is not thread-safe because no two threads can
+poison or unpoison memory in the same memory region region simultaneously.
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
+address_poison_slice :: proc "contextless" (region: $T/[]$E) {
+ when ASAN_ENABLED {
+ __asan_poison_memory_region(raw_data(region), size_of(E) * len(region))
+ }
+}
+
+/*
+Marks a slice as addressable
+
+Code instrumented with `-sanitize:address` is allowed to access any address
+within the slice again. This procedure is not thread-safe because no two threads
+can poison or unpoison memory in the same memory region region simultaneously.
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
+address_unpoison_slice :: proc "contextless" (region: $T/[]$E) {
+ when ASAN_ENABLED {
+ __asan_unpoison_memory_region(raw_data(region), size_of(E) * len(region))
+ }
+}
+
+/*
+Marks a pointer as unaddressable
+
+Code instrumented with `-sanitize:address` is forbidden from accessing any address
+within the region the pointer points to. This procedure is not thread-safe because no
+two threads can poison or unpoison memory in the same memory region region simultaneously.
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
+address_poison_ptr :: proc "contextless" (ptr: ^$T) {
+ when ASAN_ENABLED {
+ __asan_poison_memory_region(ptr, size_of(T))
+ }
+}
+
+/*
+Marks a pointer as addressable
+
+Code instrumented with `-sanitize:address` is allowed to access any address
+within the region the pointer points to again. This procedure is not thread-safe
+because no two threads can poison or unpoison memory in the same memory region
+region simultaneously.
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
+address_unpoison_ptr :: proc "contextless" (ptr: ^$T) {
+ when ASAN_ENABLED {
+ __asan_unpoison_memory_region(ptr, size_of(T))
+ }
+}
+
+/*
+Marks the region covering `[ptr, ptr+len)` as unaddressable
+
+Code instrumented with `-sanitize:address` is forbidden from accessing any address
+within the region. This procedure is not thread-safe because no two threads can
+poison or unpoison memory in the same memory region region simultaneously.
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
+address_poison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
+ when ASAN_ENABLED {
+ assert_contextless(len >= 0)
+ __asan_poison_memory_region(ptr, uint(len))
+ }
+}
+
+/*
+Marks the region covering `[ptr, ptr+len)` as unaddressable
+
+Code instrumented with `-sanitize:address` is forbidden from accessing any address
+within the region. This procedure is not thread-safe because no two threads can
+poison or unpoison memory in the same memory region region simultaneously.
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
+address_poison_rawptr_uint :: proc "contextless" (ptr: rawptr, len: uint) {
+ when ASAN_ENABLED {
+ __asan_poison_memory_region(ptr, len)
+ }
+}
+
+/*
+Marks the region covering `[ptr, ptr+len)` as addressable
+
+Code instrumented with `-sanitize:address` is allowed to access any address
+within the region again. This procedure is not thread-safe because no two
+threads can poison or unpoison memory in the same memory region region simultaneously.
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
+address_unpoison_rawptr :: proc "contextless" (ptr: rawptr, len: int) {
+ when ASAN_ENABLED {
+ assert_contextless(len >= 0)
+ __asan_unpoison_memory_region(ptr, uint(len))
+ }
+}
+
+/*
+Marks the region covering `[ptr, ptr+len)` as addressable
+
+Code instrumented with `-sanitize:address` is allowed to access any address
+within the region again. This procedure is not thread-safe because no two
+threads can poison or unpoison memory in the same memory region region simultaneously.
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
+address_unpoison_rawptr_uint :: proc "contextless" (ptr: rawptr, len: uint) {
+ when ASAN_ENABLED {
+ __asan_unpoison_memory_region(ptr, len)
+ }
+}
+
+address_poison :: proc {
+ address_poison_slice,
+ address_poison_ptr,
+ address_poison_rawptr,
+ address_poison_rawptr_uint,
+}
+
+address_unpoison :: proc {
+ address_unpoison_slice,
+ address_unpoison_ptr,
+ address_unpoison_rawptr,
+ address_unpoison_rawptr_uint,
+}
+
+/*
+Registers a callback to be run when asan detects a memory error right before terminating
+the process.
+
+This can be used for logging and/or debugging purposes.
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
+address_set_death_callback :: proc "contextless" (callback: Address_Death_Callback) {
+ when ASAN_ENABLED {
+ __sanitizer_set_death_callback(callback)
+ }
+}
+
+/*
+Checks if the memory region covered by the slice is poisoned.
+
+If it is poisoned this procedure returns the address which would result
+in an asan error.
+
+When asan is not enabled this procedure returns `nil`.
+*/
+@(no_sanitize_address)
+address_region_is_poisoned_slice :: proc "contextless" (region: $T/[]$E) -> rawptr {
+ when ASAN_ENABLED {
+ return __asan_region_is_poisoned(raw_data(region), size_of(E) * len(region))
+ } else {
+ return nil
+ }
+}
+
+/*
+Checks if the memory region pointed to by the pointer is poisoned.
+
+If it is poisoned this procedure returns the address which would result
+in an asan error.
+
+When asan is not enabled this procedure returns `nil`.
+*/
+@(no_sanitize_address)
+address_region_is_poisoned_ptr :: proc "contextless" (ptr: ^$T) -> rawptr {
+ when ASAN_ENABLED {
+ return __asan_region_is_poisoned(ptr, size_of(T))
+ } else {
+ return nil
+ }
+}
+
+/*
+Checks if the memory region covered by `[ptr, ptr+len)` is poisoned.
+
+If it is poisoned this procedure returns the address which would result
+in an asan error.
+
+When asan is not enabled this procedure returns `nil`.
+*/
+@(no_sanitize_address)
+address_region_is_poisoned_rawptr :: proc "contextless" (region: rawptr, len: int) -> rawptr {
+ when ASAN_ENABLED {
+ assert_contextless(len >= 0)
+ return __asan_region_is_poisoned(region, uint(len))
+ } else {
+ return nil
+ }
+}
+
+/*
+Checks if the memory region covered by `[ptr, ptr+len)` is poisoned.
+
+If it is poisoned this procedure returns the address which would result
+in an asan error.
+
+When asan is not enabled this procedure returns `nil`.
+*/
+@(no_sanitize_address)
+address_region_is_poisoned_rawptr_uint :: proc "contextless" (region: rawptr, len: uint) -> rawptr {
+ when ASAN_ENABLED {
+ return __asan_region_is_poisoned(region, len)
+ } else {
+ return nil
+ }
+}
+
+
+address_region_is_poisoned :: proc {
+ address_region_is_poisoned_slice,
+ address_region_is_poisoned_ptr,
+ address_region_is_poisoned_rawptr,
+ address_region_is_poisoned_rawptr_uint,
+}
+
+/*
+Checks if the address is poisoned.
+
+If it is poisoned this procedure returns `true`, otherwise it returns
+`false`.
+
+When asan is not enabled this procedure returns `false`.
+*/
+@(no_sanitize_address)
+address_is_poisoned :: proc "contextless" (address: rawptr) -> bool {
+ when ASAN_ENABLED {
+ return __asan_address_is_poisoned(address) != 0
+ } else {
+ return false
+ }
+}
+
+/*
+Describes the sanitizer state for an address.
+
+This procedure prints the description out to `stdout`.
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
+address_describe_address :: proc "contextless" (address: rawptr) {
+ when ASAN_ENABLED {
+ __asan_describe_address(address)
+ }
+}
+
+/*
+Returns `true` if an asan error has occured, otherwise it returns
+`false`.
+
+When asan is not enabled this procedure returns `false`.
+*/
+@(no_sanitize_address)
+address_report_present :: proc "contextless" () -> bool {
+ when ASAN_ENABLED {
+ return __asan_report_present() != 0
+ } else {
+ return false
+ }
+}
+
+/*
+Returns the program counter register value of an asan error.
+
+If no asan error has occurd `nil` is returned.
+
+When asan is not enabled this procedure returns `nil`.
+*/
+@(no_sanitize_address)
+address_get_report_pc :: proc "contextless" () -> rawptr {
+ when ASAN_ENABLED {
+ return __asan_get_report_pc()
+ } else {
+ return nil
+ }
+}
+
+/*
+Returns the base pointer register value of an asan error.
+
+If no asan error has occurd `nil` is returned.
+
+When asan is not enabled this procedure returns `nil`.
+*/
+@(no_sanitize_address)
+address_get_report_bp :: proc "contextless" () -> rawptr {
+ when ASAN_ENABLED {
+ return __asan_get_report_bp()
+ } else {
+ return nil
+ }
+}
+
+/*
+Returns the stack pointer register value of an asan error.
+
+If no asan error has occurd `nil` is returned.
+
+When asan is not enabled this procedure returns `nil`.
+*/
+@(no_sanitize_address)
+address_get_report_sp :: proc "contextless" () -> rawptr {
+ when ASAN_ENABLED {
+ return __asan_get_report_sp()
+ } else {
+ return nil
+ }
+}
+
+/*
+Returns the report buffer address of an asan error.
+
+If no asan error has occurd `nil` is returned.
+
+When asan is not enabled this procedure returns `nil`.
+*/
+@(no_sanitize_address)
+address_get_report_address :: proc "contextless" () -> rawptr {
+ when ASAN_ENABLED {
+ return __asan_get_report_address()
+ } else {
+ return nil
+ }
+}
+
+/*
+Returns the address access type of an asan error.
+
+If no asan error has occurd `.none` is returned.
+
+When asan is not enabled this procedure returns `.none`.
+*/
+@(no_sanitize_address)
+address_get_report_access_type :: proc "contextless" () -> Address_Access_Type {
+ when ASAN_ENABLED {
+ if ! address_report_present() {
+ return .none
+ }
+ return __asan_get_report_access_type() == 0 ? .read : .write
+ } else {
+ return .none
+ }
+}
+
+/*
+Returns the access size of an asan error.
+
+If no asan error has occurd `0` is returned.
+
+When asan is not enabled this procedure returns `0`.
+*/
+@(no_sanitize_address)
+address_get_report_access_size :: proc "contextless" () -> uint {
+ when ASAN_ENABLED {
+ return __asan_get_report_access_size()
+ } else {
+ return 0
+ }
+}
+
+/*
+Returns the bug description of an asan error.
+
+If no asan error has occurd an empty string is returned.
+
+When asan is not enabled this procedure returns an empty string.
+*/
+@(no_sanitize_address)
+address_get_report_description :: proc "contextless" () -> string {
+ when ASAN_ENABLED {
+ return string(__asan_get_report_description())
+ } else {
+ return ""
+ }
+}
+
+/*
+Returns asan information about the address provided, writing the category into `data`.
+
+The information provided include:
+* The category of the address, i.e. stack, global, heap, etc.
+* The name of the variable this address belongs to
+* The memory region of the address
+
+When asan is not enabled this procedure returns zero initialised values.
+*/
+@(no_sanitize_address)
+address_locate_address :: proc "contextless" (addr: rawptr, data: []byte) -> Address_Located_Address {
+ when ASAN_ENABLED {
+ out_addr: rawptr
+ out_size: uint
+ str := __asan_locate_address(addr, raw_data(data), len(data), &out_addr, &out_size)
+ return { string(str), string(cstring(raw_data(data))), (cast([^]byte)out_addr)[:out_size] },
+ } else {
+ return { "", "", {} }
+ }
+}
+
+/*
+Returns the allocation stack trace and thread id for a heap address.
+
+The stack trace is filled into the `data` slice.
+
+When asan is not enabled this procedure returns a zero initialised value.
+*/
+@(no_sanitize_address)
+address_get_alloc_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) {
+ when ASAN_ENABLED {
+ out_thread: i32
+ __asan_get_alloc_stack(addr, raw_data(data), len(data), &out_thread)
+ return data, int(out_thread)
+ } else {
+ return {}, 0
+ }
+}
+
+/*
+Returns the free stack trace and thread id for a heap address.
+
+The stack trace is filled into the `data` slice.
+
+When asan is not enabled this procedure returns zero initialised values.
+*/
+@(no_sanitize_address)
+address_get_free_stack_trace :: proc "contextless" (addr: rawptr, data: []rawptr) -> ([]rawptr, int) {
+ when ASAN_ENABLED {
+ out_thread: i32
+ __asan_get_free_stack(addr, raw_data(data), len(data), &out_thread)
+ return data, int(out_thread)
+ } else {
+ return {}, 0
+ }
+}
+
+/*
+Returns the current asan shadow memory mapping.
+
+When asan is not enabled this procedure returns a zero initialised value.
+*/
+@(no_sanitize_address)
+address_get_shadow_mapping :: proc "contextless" () -> Address_Shadow_Mapping {
+ when ASAN_ENABLED {
+ result: Address_Shadow_Mapping
+ __asan_get_shadow_mapping(&result.scale, &result.offset)
+ return result
+ } else {
+ return {}
+ }
+}
+
+/*
+Prints asan statistics to `stderr`
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
+address_print_accumulated_stats :: proc "contextless" () {
+ when ASAN_ENABLED {
+ __asan_print_accumulated_stats()
+ }
+}
+
+/*
+Returns the address of the current fake stack used by asan.
+
+This pointer can be then used for `address_is_in_fake_stack`.
+
+When asan is not enabled this procedure returns `nil`.
+*/
+@(no_sanitize_address)
+address_get_current_fake_stack :: proc "contextless" () -> rawptr {
+ when ASAN_ENABLED {
+ return __asan_get_current_fake_stack()
+ } else {
+ return nil
+ }
+}
+
+/*
+Returns if an address belongs to a given fake stack and if so the region of the fake frame.
+
+When asan is not enabled this procedure returns zero initialised values.
+*/
+@(no_sanitize_address)
+address_is_in_fake_stack :: proc "contextless" (fake_stack: rawptr, addr: rawptr) -> ([]byte, bool) {
+ when ASAN_ENABLED {
+ begin: rawptr
+ end: rawptr
+ if __asan_addr_is_in_fake_stack(fake_stack, addr, &begin, &end) == nil {
+ return {}, false
+ }
+ return ((cast([^]byte)begin)[:uintptr(end)-uintptr(begin)]), true
+ } else {
+ return {}, false
+ }
+}
+
+/*
+Performs shadow memory cleanup for the current thread before a procedure with no return is called
+i.e. a procedure such as `panic` and `os.exit`.
+
+When asan is not enabled this procedure does nothing.
+*/
+@(no_sanitize_address)
+address_handle_no_return :: proc "contextless" () {
+ when ASAN_ENABLED {
+ __asan_handle_no_return()
+ }
+}
+
+/*
+Updates the allocation stack trace for the given address.
+
+Returns `true` if successful, otherwise it returns `false`.
+
+When asan is not enabled this procedure returns `false`.
+*/
+@(no_sanitize_address)
+address_update_allocation_context :: proc "contextless" (addr: rawptr) -> bool {
+ when ASAN_ENABLED {
+ return __asan_update_allocation_context(addr) != 0
+ } else {
+ return false
+ }
+}
+
diff --git a/base/sanitizer/doc.odin b/base/sanitizer/doc.odin
new file mode 100644
index 000000000..707f41ce0
--- /dev/null
+++ b/base/sanitizer/doc.odin
@@ -0,0 +1,38 @@
+/*
+The `sanitizer` package implements various procedures for interacting with sanitizers
+from user code.
+
+An odin project can be linked with various sanitizers to help identify various different
+bugs. These sanitizers are:
+
+## Address
+
+Enabled with `-sanitize:address` when building an odin project.
+
+The address sanitizer (asan) is a runtime memory error detector used to help find common memory
+related bugs. Typically asan interacts with libc but Odin code can be marked up to interact
+with the asan runtime to extend the memory error detection outside of libc using this package.
+For more information about asan see: https://clang.llvm.org/docs/AddressSanitizer.html
+
+Procedures can be made exempt from asan when marked up with @(no_sanitize_address)
+
+## Memory
+
+Enabled with `-sanitize:memory` when building an odin project.
+
+The memory sanitizer is another runtime memory error detector with the sole purpose to catch the
+use of uninitialized memory. This is not a very common bug in Odin as by default everything is
+set to zero when initialised (ZII).
+For more information about the memory sanitizer see: https://clang.llvm.org/docs/MemorySanitizer.html
+
+## Thread
+
+Enabled with `-sanitize:thread` when building an odin project.
+
+The thread sanitizer is a runtime data race detector. It can be used to detect if multiple threads
+are concurrently writing and accessing a memory location without proper syncronisation.
+For more information about the thread sanitizer see: https://clang.llvm.org/docs/ThreadSanitizer.html
+
+*/
+package sanitizer
+
diff --git a/bin/llvm/windows/clang_rt.asan-x86_64.lib b/bin/llvm/windows/clang_rt.asan-x86_64.lib
index d7dfcbb1c..0b209dc8d 100644
Binary files a/bin/llvm/windows/clang_rt.asan-x86_64.lib and b/bin/llvm/windows/clang_rt.asan-x86_64.lib differ
diff --git a/build.bat b/build.bat
index 4c015e133..b1ff9b173 100644
--- a/build.bat
+++ b/build.bat
@@ -19,16 +19,27 @@ if "%VSCMD_ARG_TGT_ARCH%" neq "x64" (
)
)
+where /Q git.exe || goto skip_git_hash
+if not exist .git\ goto skip_git_hash
+for /f "tokens=1,2" %%i IN ('git show "--pretty=%%cd %%h" "--date=format:%%Y-%%m-%%d" --no-patch --no-notes HEAD') do (
+ set CURR_DATE_TIME=%%i
+ set GIT_SHA=%%j
+)
+if %ERRORLEVEL% equ 0 (
+ goto have_git_hash_and_date
+)
+:skip_git_hash
pushd misc
cl /nologo get-date.c
-popd
-
-for /f %%i in ('misc\get-date') do (
+for /f %%i in ('get-date') do (
set CURR_DATE_TIME=%%i
+ rem Don't set GIT_SHA
)
+popd
+:have_git_hash_and_date
set curr_year=%CURR_DATE_TIME:~0,4%
-set curr_month=%CURR_DATE_TIME:~4,2%
-set curr_day=%CURR_DATE_TIME:~6,2%
+set curr_month=%CURR_DATE_TIME:~5,2%
+set curr_day=%CURR_DATE_TIME:~8,2%
:: Make sure this is a decent name and not generic
set exe_name=odin.exe
@@ -61,31 +72,14 @@ if %release_mode% equ 0 (
set V4=0
set odin_version_full="%V1%.%V2%.%V3%.%V4%"
set odin_version_raw="dev-%V1%-%V2%"
-
set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -EHsc- -GR- -GF
rem Parse source code as utf-8 even on shift-jis and other codepages
rem See https://learn.microsoft.com/en-us/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170
set compiler_flags= %compiler_flags% /utf-8
-set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\"
+set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\" -DGIT_SHA=\"%GIT_SHA%\"
rem fileversion is defined as {Major,Minor,Build,Private: u16} so a bit limited
-set rc_flags=-nologo ^
--DV1=%V1% -DV2=%V2% -DV3=%V3% -DV4=%V4% ^
--DVF=%odin_version_full% -DNIGHTLY=%nightly%
-
-where /Q git.exe || goto skip_git_hash
-if not exist .git\ goto skip_git_hash
-for /f "tokens=1,2" %%i IN ('git show "--pretty=%%cd %%h" "--date=format:%%Y-%%m" --no-patch --no-notes HEAD') do (
- set odin_version_raw=dev-%%i
- set GIT_SHA=%%j
-)
-if %ERRORLEVEL% equ 0 (
- set compiler_defines=%compiler_defines% -DGIT_SHA=\"%GIT_SHA%\"
- set rc_flags=%rc_flags% -DGIT_SHA=%GIT_SHA% -DVP=%odin_version_raw%:%GIT_SHA%
-) else (
- set rc_flags=%rc_flags% -DVP=%odin_version_raw%
-)
-:skip_git_hash
+set rc_flags="-DGIT_SHA=%GIT_SHA% -DVP=dev-%V1%-%V2%:%GIT_SHA% nologo -DV1=%V1% -DV2=%V2% -DV3=%V3% -DV4=%V4% -DVF=%odin_version_full% -DNIGHTLY=%nightly%"
if %nightly% equ 1 set compiler_defines=%compiler_defines% -DNIGHTLY
@@ -138,6 +132,7 @@ del *.ilk > NUL 2> NUL
rc %rc_flags% %odin_rc%
cl %compiler_settings% "src\main.cpp" "src\libtommath.cpp" /link %linker_settings% -OUT:%exe_name%
+if %errorlevel% neq 0 goto end_of_build
mt -nologo -inputresource:%exe_name%;#1 -manifest misc\odin.manifest -outputresource:%exe_name%;#1 -validate_manifest -identity:"odin, processorArchitecture=amd64, version=%odin_version_full%, type=win32"
if %errorlevel% neq 0 goto end_of_build
@@ -152,4 +147,4 @@ if %release_mode% EQU 0 echo: & echo Debug compiler built. Note: run "build.bat
del *.obj > NUL 2> NUL
-:end_of_build
+:end_of_build
\ No newline at end of file
diff --git a/build_odin.sh b/build_odin.sh
index 19bb82a11..0d7e8a26e 100755
--- a/build_odin.sh
+++ b/build_odin.sh
@@ -6,7 +6,6 @@ set -eu
: ${LDFLAGS=}
: ${LLVM_CONFIG=}
-CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$(date +"%Y-%m")\""
CXXFLAGS="$CXXFLAGS -std=c++14"
DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value"
LDFLAGS="$LDFLAGS -pthread -lm"
@@ -15,8 +14,12 @@ OS_NAME="$(uname -s)"
if [ -d ".git" ] && [ -n "$(command -v git)" ]; then
GIT_SHA=$(git show --pretty='%h' --no-patch --no-notes HEAD)
+ GIT_DATE=$(git show "--pretty=%cd" "--date=format:%Y-%m" --no-patch --no-notes HEAD)
CPPFLAGS="$CPPFLAGS -DGIT_SHA=\"$GIT_SHA\""
+else
+ GIT_DATE=$(date +"%Y-%m")
fi
+CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$GIT_DATE\""
error() {
printf "ERROR: %s\n" "$1"
diff --git a/core/bufio/reader.odin b/core/bufio/reader.odin
index a875c732d..b78cac6e1 100644
--- a/core/bufio/reader.odin
+++ b/core/bufio/reader.odin
@@ -257,7 +257,7 @@ reader_read_rune :: proc(b: ^Reader) -> (r: rune, size: int, err: io.Error) {
for b.r+utf8.UTF_MAX > b.w &&
!utf8.full_rune(b.buf[b.r:b.w]) &&
b.err == nil &&
- b.w-b.w < len(b.buf) {
+ b.w-b.r < len(b.buf) {
_reader_read_new_chunk(b) or_return
}
diff --git a/core/container/intrusive/list/intrusive_list.odin b/core/container/intrusive/list/intrusive_list.odin
index 5b29efb22..1e116ef18 100644
--- a/core/container/intrusive/list/intrusive_list.odin
+++ b/core/container/intrusive/list/intrusive_list.odin
@@ -278,19 +278,19 @@ Example:
iterate_next_example :: proc() {
l: list.List
- one := My_Struct{value=1}
- two := My_Struct{value=2}
+ one := My_Next_Struct{value=1}
+ two := My_Next_Struct{value=2}
list.push_back(&l, &one.node)
list.push_back(&l, &two.node)
- it := list.iterator_head(l, My_Struct, "node")
+ it := list.iterator_head(l, My_Next_Struct, "node")
for num in list.iterate_next(&it) {
fmt.println(num.value)
}
}
- My_Struct :: struct {
+ My_Next_Struct :: struct {
node : list.Node,
value: int,
}
@@ -325,22 +325,22 @@ Example:
import "core:fmt"
import "core:container/intrusive/list"
- iterate_next_example :: proc() {
+ iterate_prev_example :: proc() {
l: list.List
- one := My_Struct{value=1}
- two := My_Struct{value=2}
+ one := My_Prev_Struct{value=1}
+ two := My_Prev_Struct{value=2}
list.push_back(&l, &one.node)
list.push_back(&l, &two.node)
- it := list.iterator_tail(l, My_Struct, "node")
+ it := list.iterator_tail(l, My_Prev_Struct, "node")
for num in list.iterate_prev(&it) {
fmt.println(num.value)
}
}
- My_Struct :: struct {
+ My_Prev_Struct :: struct {
node : list.Node,
value: int,
}
diff --git a/core/container/lru/lru_cache.odin b/core/container/lru/lru_cache.odin
index f8aa55dc2..d95e8e1d2 100644
--- a/core/container/lru/lru_cache.odin
+++ b/core/container/lru/lru_cache.odin
@@ -129,7 +129,7 @@ remove :: proc(c: ^$C/Cache($Key, $Value), key: Key) -> bool {
return false
}
_remove_node(c, e)
- free(node, c.node_allocator)
+ free(e, c.node_allocator)
c.count -= 1
return true
}
diff --git a/core/container/priority_queue/priority_queue.odin b/core/container/priority_queue/priority_queue.odin
index 8a6d77288..c62a821f4 100644
--- a/core/container/priority_queue/priority_queue.odin
+++ b/core/container/priority_queue/priority_queue.odin
@@ -1,6 +1,7 @@
package container_priority_queue
import "base:builtin"
+import "base:runtime"
Priority_Queue :: struct($T: typeid) {
queue: [dynamic]T,
@@ -17,13 +18,14 @@ default_swap_proc :: proc($T: typeid) -> proc(q: []T, i, j: int) {
}
}
-init :: proc(pq: ^$Q/Priority_Queue($T), less: proc(a, b: T) -> bool, swap: proc(q: []T, i, j: int), capacity := DEFAULT_CAPACITY, allocator := context.allocator) {
+init :: proc(pq: ^$Q/Priority_Queue($T), less: proc(a, b: T) -> bool, swap: proc(q: []T, i, j: int), capacity := DEFAULT_CAPACITY, allocator := context.allocator) -> (err: runtime.Allocator_Error) {
if pq.queue.allocator.procedure == nil {
pq.queue.allocator = allocator
}
- reserve(pq, capacity)
+ reserve(pq, capacity) or_return
pq.less = less
pq.swap = swap
+ return .None
}
init_from_dynamic_array :: proc(pq: ^$Q/Priority_Queue($T), queue: [dynamic]T, less: proc(a, b: T) -> bool, swap: proc(q: []T, i, j: int)) {
@@ -41,8 +43,8 @@ destroy :: proc(pq: ^$Q/Priority_Queue($T)) {
delete(pq.queue)
}
-reserve :: proc(pq: ^$Q/Priority_Queue($T), capacity: int) {
- builtin.reserve(&pq.queue, capacity)
+reserve :: proc(pq: ^$Q/Priority_Queue($T), capacity: int) -> (err: runtime.Allocator_Error) {
+ return builtin.reserve(&pq.queue, capacity)
}
clear :: proc(pq: ^$Q/Priority_Queue($T)) {
builtin.clear(&pq.queue)
@@ -103,9 +105,10 @@ fix :: proc(pq: ^$Q/Priority_Queue($T), i: int) {
}
}
-push :: proc(pq: ^$Q/Priority_Queue($T), value: T) {
- append(&pq.queue, value)
+push :: proc(pq: ^$Q/Priority_Queue($T), value: T) -> (err: runtime.Allocator_Error) {
+ append(&pq.queue, value) or_return
_shift_up(pq, builtin.len(pq.queue)-1)
+ return .None
}
pop :: proc(pq: ^$Q/Priority_Queue($T), loc := #caller_location) -> (value: T) {
@@ -130,12 +133,10 @@ pop_safe :: proc(pq: ^$Q/Priority_Queue($T), loc := #caller_location) -> (value:
remove :: proc(pq: ^$Q/Priority_Queue($T), i: int) -> (value: T, ok: bool) {
n := builtin.len(pq.queue)
if 0 <= i && i < n {
- if n != i {
- pq.swap(pq.queue[:], i, n)
- _shift_down(pq, i, n)
- _shift_up(pq, i)
- }
- value, ok = builtin.pop_safe(&pq.queue)
+ pq.swap(pq.queue[:], i, n-1)
+ _shift_down(pq, i, n-1)
+ _shift_up(pq, i)
+ value, ok = builtin.pop(&pq.queue), true
}
return
}
diff --git a/core/container/small_array/doc.odin b/core/container/small_array/doc.odin
new file mode 100644
index 000000000..f3e9acd57
--- /dev/null
+++ b/core/container/small_array/doc.odin
@@ -0,0 +1,55 @@
+/*
+Package small_array implements a dynamic array like
+interface on a stack-allocated, fixed-size array.
+
+The Small_Array type is optimal for scenarios where you need
+a container for a fixed number of elements of a specific type,
+with the total number known at compile time but the exact
+number to be used determined at runtime.
+
+Example:
+ import "core:fmt"
+ import "core:container/small_array"
+
+ create :: proc() -> (result: small_array.Small_Array(10, rune)) {
+ // appending single elements
+ small_array.push(&result, 'e')
+ // pushing a bunch of elements at once
+ small_array.push(&result, 'l', 'i', 'x', '-', 'e')
+ // pre-pending
+ small_array.push_front(&result, 'H')
+ // removing elements
+ small_array.ordered_remove(&result, 4)
+ // resizing to the desired length (the capacity will stay unchanged)
+ small_array.resize(&result, 7)
+ // inserting elements
+ small_array.inject_at(&result, 'p', 5)
+ // updating elements
+ small_array.set(&result, 3, 'l')
+ // getting pointers to elements
+ o := small_array.get_ptr(&result, 4)
+ o^ = 'o'
+ // and much more ....
+ return
+ }
+
+ // the Small_Array can be an ordinary parameter 'generic' over
+ // the actual length to be usable with different sizes
+ print_elements :: proc(arr: ^small_array.Small_Array($N, rune)) {
+ for r in small_array.slice(arr) {
+ fmt.print(r)
+ }
+ }
+
+ main :: proc() {
+ arr := create()
+ // ...
+ print_elements(&arr)
+ }
+
+Output:
+
+ Hellope
+
+*/
+package container_small_array
diff --git a/core/container/small_array/small_array.odin b/core/container/small_array/small_array.odin
index 77bb21cbc..49d441079 100644
--- a/core/container/small_array/small_array.odin
+++ b/core/container/small_array/small_array.odin
@@ -4,36 +4,171 @@ import "base:builtin"
import "base:runtime"
_ :: runtime
+/*
+A fixed-size stack-allocated array operated on in a dynamic fashion.
+
+Fields:
+- `data`: The underlying array
+- `len`: Amount of items that the `Small_Array` currently holds
+
+Example:
+
+ import "core:container/small_array"
+
+ example :: proc() {
+ a: small_array.Small_Array(100, int)
+ small_array.push_back(&a, 10)
+ }
+*/
Small_Array :: struct($N: int, $T: typeid) where N >= 0 {
data: [N]T,
len: int,
}
+/*
+Returns the amount of items in the small-array.
+**Inputs**
+- `a`: The small-array
+
+**Returns**
+- the amount of items in the array
+*/
len :: proc "contextless" (a: $A/Small_Array) -> int {
return a.len
}
+/*
+Returns the capacity of the small-array.
+
+**Inputs**
+- `a`: The small-array
+
+**Returns** the capacity
+*/
cap :: proc "contextless" (a: $A/Small_Array) -> int {
return builtin.len(a.data)
}
+/*
+Returns how many more items the small-array could fit.
+
+**Inputs**
+- `a`: The small-array
+
+**Returns**
+- the number of unused slots
+*/
space :: proc "contextless" (a: $A/Small_Array) -> int {
return builtin.len(a.data) - a.len
}
+/*
+Returns a slice of the data.
+
+**Inputs**
+- `a`: The pointer to the small-array
+
+**Returns**
+- the slice
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ slice_example :: proc() {
+ print :: proc(a: ^small_array.Small_Array($N, int)) {
+ for item in small_array.slice(a) {
+ fmt.println(item)
+ }
+ }
+
+ a: small_array.Small_Array(5, int)
+ small_array.push_back(&a, 1)
+ small_array.push_back(&a, 2)
+ print(&a)
+ }
+
+Output:
+
+ 1
+ 2
+*/
slice :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> []T {
return a.data[:a.len]
}
+/*
+Get a copy of the item at the specified position.
+This operation assumes that the small-array is large enough.
+This will result in:
+ - the value if 0 <= index < len
+ - the zero value of the type if len < index < capacity
+ - 'crash' if capacity < index or index < 0
+
+**Inputs**
+- `a`: The small-array
+- `index`: The position of the item to get
+
+**Returns**
+- the element at the specified position
+*/
get :: proc "contextless" (a: $A/Small_Array($N, $T), index: int) -> T {
return a.data[index]
}
+
+/*
+Get a pointer to the item at the specified position.
+This operation assumes that the small-array is large enough.
+
+This will result in:
+ - the pointer if 0 <= index < len
+ - the pointer to the zero value if len < index < capacity
+ - 'crash' if capacity < index or index < 0
+
+**Inputs**
+- `a`: A pointer to the small-array
+- `index`: The position of the item to get
+
+**Returns**
+- the pointer to the element at the specified position
+*/
get_ptr :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int) -> ^T {
return &a.data[index]
}
+/*
+Attempt to get a copy of the item at the specified position.
+
+**Inputs**
+- `a`: The small-array
+- `index`: The position of the item to get
+
+**Returns**
+- the element at the specified position
+- true if element exists, false otherwise
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ get_safe_example :: proc() {
+ a: small_array.Small_Array(5, rune)
+ small_array.push_back(&a, 'A')
+
+ fmt.println(small_array.get_safe(a, 0) or_else 'x')
+ fmt.println(small_array.get_safe(a, 1) or_else 'x')
+ }
+
+Output:
+
+ A
+ x
+
+*/
get_safe :: proc(a: $A/Small_Array($N, $T), index: int) -> (T, bool) #no_bounds_check {
if index < 0 || index >= a.len {
return {}, false
@@ -41,6 +176,17 @@ get_safe :: proc(a: $A/Small_Array($N, $T), index: int) -> (T, bool) #no_bounds_
return a.data[index], true
}
+/*
+Get a pointer to the item at the specified position.
+
+**Inputs**
+- `a`: A pointer to the small-array
+- `index`: The position of the item to get
+
+**Returns**
+- the pointer to the element at the specified position
+- true if element exists, false otherwise
+*/
get_ptr_safe :: proc(a: ^$A/Small_Array($N, $T), index: int) -> (^T, bool) #no_bounds_check {
if index < 0 || index >= a.len {
return {}, false
@@ -48,15 +194,128 @@ get_ptr_safe :: proc(a: ^$A/Small_Array($N, $T), index: int) -> (^T, bool) #no_b
return &a.data[index], true
}
+/*
+Set the element at the specified position to the given value.
+This operation assumes that the small-array is large enough.
+
+This will result in:
+ - the value being set if 0 <= index < capacity
+ - 'crash' otherwise
+
+**Inputs**
+- `a`: A pointer to the small-array
+- `index`: The position of the item to set
+- `value`: The value to set the element to
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ set_example :: proc() {
+ a: small_array.Small_Array(5, rune)
+ small_array.push_back(&a, 'A')
+ small_array.push_back(&a, 'B')
+ fmt.println(small_array.slice(&a))
+
+ // updates index 0
+ small_array.set(&a, 0, 'Z')
+ fmt.println(small_array.slice(&a))
+
+ // updates to a position x, where
+ // len <= x < cap are not visible since
+ // the length of the small-array remains unchanged
+ small_array.set(&a, 2, 'X')
+ small_array.set(&a, 3, 'Y')
+ small_array.set(&a, 4, 'Z')
+ fmt.println(small_array.slice(&a))
+
+ // resizing makes the change visible
+ small_array.resize(&a, 100)
+ fmt.println(small_array.slice(&a))
+ }
+
+Output:
+
+ [A, B]
+ [Z, B]
+ [Z, B]
+ [Z, B, X, Y, Z]
+
+*/
set :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, item: T) {
a.data[index] = item
}
+/*
+Tries to resize the small-array to the specified length.
+
+The new length will be:
+ - `length` if `length` <= capacity
+ - capacity if length > capacity
+
+**Inputs**
+- `a`: A pointer to the small-array
+- `length`: The new desired length
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ resize_example :: proc() {
+ a: small_array.Small_Array(5, int)
+
+ small_array.push_back(&a, 1)
+ small_array.push_back(&a, 2)
+ fmt.println(small_array.slice(&a))
+
+ small_array.resize(&a, 1)
+ fmt.println(small_array.slice(&a))
+
+ small_array.resize(&a, 100)
+ fmt.println(small_array.slice(&a))
+ }
+
+Output:
+
+ [1, 2]
+ [1]
+ [1, 2, 0, 0, 0]
+*/
resize :: proc "contextless" (a: ^$A/Small_Array, length: int) {
a.len = min(length, builtin.len(a.data))
}
+/*
+Attempts to add the given element to the end.
+**Inputs**
+- `a`: A pointer to the small-array
+- `item`: The item to append
+
+**Returns**
+- true if there was enough space to fit the element, false otherwise
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ push_back_example :: proc() {
+ a: small_array.Small_Array(2, int)
+
+ assert(small_array.push_back(&a, 1), "this should fit")
+ assert(small_array.push_back(&a, 2), "this should fit")
+ assert(!small_array.push_back(&a, 3), "this should not fit")
+
+ fmt.println(small_array.slice(&a))
+ }
+
+Output:
+
+ [1, 2]
+*/
push_back :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool {
if a.len < cap(a^) {
a.data[a.len] = item
@@ -66,6 +325,39 @@ push_back :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool {
return false
}
+/*
+Attempts to add the given element at the beginning.
+This operation assumes that the small-array is not empty.
+
+Note: Performing this operation will cause pointers obtained
+through get_ptr(_save) to reference incorrect elements.
+
+**Inputs**
+- `a`: A pointer to the small-array
+- `item`: The item to append
+
+**Returns**
+- true if there was enough space to fit the element, false otherwise
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ push_front_example :: proc() {
+ a: small_array.Small_Array(2, int)
+
+ assert(small_array.push_front(&a, 2), "this should fit")
+ assert(small_array.push_front(&a, 1), "this should fit")
+ assert(!small_array.push_back(&a, 0), "this should not fit")
+
+ fmt.println(small_array.slice(&a))
+ }
+
+Output:
+
+ [1, 2]
+*/
push_front :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool {
if a.len < cap(a^) {
a.len += 1
@@ -77,6 +369,35 @@ push_front :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool {
return false
}
+/*
+Removes and returns the last element of the small-array.
+This operation assumes that the small-array is not empty.
+
+**Inputs**
+- `a`: A pointer to the small-array
+
+**Returns**
+- a copy of the element removed from the end of the small-array
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ pop_back_example :: proc() {
+ a: small_array.Small_Array(5, int)
+ small_array.push(&a, 0, 1, 2)
+
+ fmt.println("BEFORE:", small_array.slice(&a))
+ small_array.pop_back(&a)
+ fmt.println("AFTER: ", small_array.slice(&a))
+ }
+
+Output:
+
+ BEFORE: [0, 1, 2]
+ AFTER: [0, 1]
+*/
pop_back :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) -> T {
assert(condition=(N > 0 && a.len > 0), loc=loc)
item := a.data[a.len-1]
@@ -84,6 +405,38 @@ pop_back :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) ->
return item
}
+/*
+Removes and returns the first element of the small-array.
+This operation assumes that the small-array is not empty.
+
+Note: Performing this operation will cause pointers obtained
+through get_ptr(_save) to reference incorrect elements.
+
+**Inputs**
+- `a`: A pointer to the small-array
+
+**Returns**
+- a copy of the element removed from the beginning of the small-array
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ pop_front_example :: proc() {
+ a: small_array.Small_Array(5, int)
+ small_array.push(&a, 0, 1, 2)
+
+ fmt.println("BEFORE:", small_array.slice(&a))
+ small_array.pop_front(&a)
+ fmt.println("AFTER: ", small_array.slice(&a))
+ }
+
+Output:
+
+ BEFORE: [0, 1, 2]
+ AFTER: [1, 2]
+*/
pop_front :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) -> T {
assert(condition=(N > 0 && a.len > 0), loc=loc)
item := a.data[0]
@@ -93,6 +446,32 @@ pop_front :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) -
return item
}
+/*
+Attempts to remove and return the last element of the small array.
+Unlike `pop_back`, it does not assume that the array is non-empty.
+
+**Inputs**
+- `a`: A pointer to the small-array
+
+**Returns**
+- a copy of the element removed from the end of the small-array
+- true if the small-array was not empty, false otherwise
+
+Example:
+
+ import "core:container/small_array"
+
+ pop_back_safe_example :: proc() {
+ a: small_array.Small_Array(3, int)
+ small_array.push(&a, 1)
+
+ el, ok := small_array.pop_back_safe(&a)
+ assert(ok, "there was an element in the array")
+
+ el, ok = small_array.pop_back_safe(&a)
+ assert(!ok, "there was NO element in the array")
+ }
+*/
pop_back_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) {
if N > 0 && a.len > 0 {
item = a.data[a.len-1]
@@ -102,6 +481,35 @@ pop_back_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, ok
return
}
+/*
+Attempts to remove and return the first element of the small array.
+Unlike `pop_front`, it does not assume that the array is non-empty.
+
+Note: Performing this operation will cause pointers obtained
+through get_ptr(_save) to reference incorrect elements.
+
+**Inputs**
+- `a`: A pointer to the small-array
+
+**Returns**
+- a copy of the element removed from the beginning of the small-array
+- true if the small-array was not empty, false otherwise
+
+Example:
+
+ import "core:container/small_array"
+
+ pop_front_safe_example :: proc() {
+ a: small_array.Small_Array(3, int)
+ small_array.push(&a, 1)
+
+ el, ok := small_array.pop_front_safe(&a)
+ assert(ok, "there was an element in the array")
+
+ el, ok = small_array.pop_front_(&a)
+ assert(!ok, "there was NO element in the array")
+ }
+*/
pop_front_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) {
if N > 0 && a.len > 0 {
item = a.data[0]
@@ -113,11 +521,70 @@ pop_front_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, o
return
}
+/*
+Decreases the length of the small-array by the given amount.
+The elements are therefore not really removed and can be
+recovered by calling `resize`.
+
+Note: This procedure assumes that the array has a sufficient length.
+
+**Inputs**
+- `a`: A pointer to the small-array
+- `count`: The amount the length should be reduced by
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ consume_example :: proc() {
+ a: small_array.Small_Array(3, int)
+ small_array.push(&a, 0, 1, 2)
+
+ fmt.println("BEFORE:", small_array.slice(&a))
+ small_array.consume(&a, 2)
+ fmt.println("AFTER :", small_array.slice(&a))
+ }
+
+Output:
+
+ BEFORE: [0, 1, 2]
+ AFTER : [0]
+*/
consume :: proc "odin" (a: ^$A/Small_Array($N, $T), count: int, loc := #caller_location) {
assert(condition=a.len >= count, loc=loc)
a.len -= count
}
+/*
+Removes the element at the specified index while retaining order.
+
+Note: Performing this operation will cause pointers obtained
+through get_ptr(_save) to reference incorrect elements.
+
+**Inputs**
+- `a`: A pointer to the small-array
+- `index`: The position of the element to remove
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ ordered_remove_example :: proc() {
+ a: small_array.Small_Array(4, int)
+ small_array.push(&a, 0, 1, 2, 3)
+
+ fmt.println("BEFORE:", small_array.slice(&a))
+ small_array.ordered_remove(&a, 1)
+ fmt.println("AFTER :", small_array.slice(&a))
+ }
+
+Output:
+
+ BEFORE: [0, 1, 2, 3]
+ AFTER : [0, 2, 3]
+*/
ordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check {
runtime.bounds_check_error_loc(loc, index, a.len)
if index+1 < a.len {
@@ -126,6 +593,32 @@ ordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, lo
a.len -= 1
}
+/*
+Removes the element at the specified index without retaining order.
+
+**Inputs**
+- `a`: A pointer to the small-array
+- `index`: The position of the element to remove
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ unordered_remove_example :: proc() {
+ a: small_array.Small_Array(4, int)
+ small_array.push(&a, 0, 1, 2, 3)
+
+ fmt.println("BEFORE:", small_array.slice(&a))
+ small_array.unordered_remove(&a, 1)
+ fmt.println("AFTER :", small_array.slice(&a))
+ }
+
+Output:
+
+ BEFORE: [0, 1, 2, 3]
+ AFTER : [0, 3, 2]
+*/
unordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check {
runtime.bounds_check_error_loc(loc, index, a.len)
n := a.len-1
@@ -135,10 +628,63 @@ unordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int,
a.len -= 1
}
+/*
+Sets the length of the small-array to 0.
+
+**Inputs**
+- `a`: A pointer to the small-array
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ clear_example :: proc() {
+ a: small_array.Small_Array(4, int)
+ small_array.push(&a, 0, 1, 2, 3)
+
+ fmt.println("BEFORE:", small_array.slice(&a))
+ small_array.clear(&a)
+ fmt.println("AFTER :", small_array.slice(&a))
+ }
+
+Output:
+
+ BEFORE: [0, 1, 2, 3]
+ AFTER : []
+
+*/
clear :: proc "contextless" (a: ^$A/Small_Array($N, $T)) {
resize(a, 0)
}
+/*
+Attempts to append all elements to the small-array returning
+false if there is not enough space to fit all of them.
+
+**Inputs**
+- `a`: A pointer to the small-array
+- `item`: The item to append
+- ..:
+
+**Returns**
+- true if there was enough space to fit the element, false otherwise
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ push_back_elems_example :: proc() {
+ a: small_array.Small_Array(100, int)
+ small_array.push_back_elems(&a, 0, 1, 2, 3, 4)
+ fmt.println(small_array.slice(&a))
+ }
+
+Output:
+
+ [0, 1, 2, 3, 4]
+*/
push_back_elems :: proc "contextless" (a: ^$A/Small_Array($N, $T), items: ..T) -> bool {
if a.len + builtin.len(items) <= cap(a^) {
n := copy(a.data[a.len:], items[:])
@@ -148,6 +694,36 @@ push_back_elems :: proc "contextless" (a: ^$A/Small_Array($N, $T), items: ..T) -
return false
}
+/*
+Tries to insert an element at the specified position.
+
+Note: Performing this operation will cause pointers obtained
+through get_ptr(_save) to reference incorrect elements.
+
+**Inputs**
+- `a`: A pointer to the small-array
+- `item`: The item to insert
+- `index`: The index to insert the item at
+
+**Returns**
+- true if there was enough space to fit the element, false otherwise
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ inject_at_example :: proc() {
+ arr: small_array.Small_Array(100, rune)
+ small_array.push(&arr, 'A', 'C', 'D')
+ small_array.inject_at(&arr, 'B', 1)
+ fmt.println(small_array.slice(&arr))
+ }
+
+Output:
+
+ [A, B, C, D]
+*/
inject_at :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T, index: int) -> bool #no_bounds_check {
if a.len < cap(a^) && index >= 0 && index <= len(a^) {
a.len += 1
@@ -160,7 +736,38 @@ inject_at :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T, index: int
return false
}
+// Alias for `push_back`
append_elem :: push_back
+// Alias for `push_back_elems`
append_elems :: push_back_elems
+
+/*
+Tries to append the element(s) to the small-array.
+
+**Inputs**
+- `a`: A pointer to the small-array
+- `item`: The item to append
+- ..:
+
+**Returns**
+- true if there was enough space to fit the element, false otherwise
+
+Example:
+
+ import "core:container/small_array"
+ import "core:fmt"
+
+ push_example :: proc() {
+ a: small_array.Small_Array(100, int)
+ small_array.push(&a, 0)
+ small_array.push(&a, 1, 2, 3, 4)
+ fmt.println(small_array.slice(&a))
+ }
+
+Output:
+
+ [0, 1, 2, 3, 4]
+*/
push :: proc{push_back, push_back_elems}
+// Alias for `push`
append :: proc{push_back, push_back_elems}
diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin
index 4ea5c610e..b6fcfe5b6 100644
--- a/core/encoding/cbor/unmarshal.odin
+++ b/core/encoding/cbor/unmarshal.odin
@@ -29,6 +29,7 @@ an input.
unmarshal :: proc {
unmarshal_from_reader,
unmarshal_from_string,
+ unmarshal_from_bytes,
}
unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
@@ -51,6 +52,11 @@ unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, all
return
}
+// Unmarshals from a slice of bytes, see docs on the proc group `Unmarshal` for more info.
+unmarshal_from_bytes :: proc(bytes: []byte, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
+ return unmarshal_from_string(string(bytes), ptr, flags, allocator, temp_allocator, loc)
+}
+
unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) {
d := d
@@ -487,7 +493,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header
data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, allocator=allocator, loc=loc) or_return
defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) }
- da := mem.Raw_Dynamic_Array{raw_data(data), 0, length, context.allocator }
+ da := mem.Raw_Dynamic_Array{raw_data(data), 0, scap, context.allocator }
assign_array(d, &da, t.elem, length) or_return
diff --git a/core/encoding/entity/entity.odin b/core/encoding/entity/entity.odin
index d2f1d46b2..cb8fa8611 100644
--- a/core/encoding/entity/entity.odin
+++ b/core/encoding/entity/entity.odin
@@ -108,7 +108,7 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
it couldn't have been part of an XML tag body to be decoded here.
Keep in mind that we could already *be* inside a CDATA tag.
- If so, write `>` as a literal and continue.
+ If so, write `<` as a literal and continue.
*/
if in_data {
write_rune(&builder, '<')
@@ -119,11 +119,9 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator :=
case ']':
// If we're unboxing _and_ decoding CDATA, we'll have to check for the end tag.
if in_data {
- if t.read_offset + len(CDATA_END) < len(t.src) {
- if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
- in_data = false
- t.read_offset += len(CDATA_END) - 1
- }
+ if strings.has_prefix(t.src[t.offset:], CDATA_END) {
+ in_data = false
+ t.read_offset += len(CDATA_END) - 1
}
continue
} else {
@@ -297,40 +295,40 @@ _handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: X
assert(t != nil && t.r == '<')
if t.read_offset + len(CDATA_START) >= len(t.src) { return false, .None }
- if string(t.src[t.offset:][:len(CDATA_START)]) == CDATA_START {
- t.read_offset += len(CDATA_START) - 1
-
+ s := string(t.src[t.offset:])
+ if strings.has_prefix(s, CDATA_START) {
if .Unbox_CDATA in options && .Decode_CDATA in options {
// We're unboxing _and_ decoding CDATA
+ t.read_offset += len(CDATA_START) - 1
return true, .None
}
- // CDATA is passed through.
- offset := t.offset
-
- // Scan until end of CDATA.
+ // CDATA is passed through. Scan until end of CDATA.
+ start_offset := t.offset
+ t.read_offset += len(CDATA_START)
for {
- advance(t) or_return
- if t.r < 0 { return true, .CDATA_Not_Terminated }
+ advance(t)
+ if t.r < 0 {
+ // error(t, offset, "[scan_string] CDATA was not terminated\n")
+ return true, .CDATA_Not_Terminated
+ }
- if t.read_offset + len(CDATA_END) < len(t.src) {
- if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
- t.read_offset += len(CDATA_END) - 1
+ // Scan until the end of a CDATA tag.
+ if s = string(t.src[t.read_offset:]); strings.has_prefix(s, CDATA_END) {
+ t.read_offset += len(CDATA_END)
+ cdata := string(t.src[start_offset:t.read_offset])
- cdata := string(t.src[offset : t.read_offset])
-
- if .Unbox_CDATA in options {
- cdata = cdata[len(CDATA_START):]
- cdata = cdata[:len(cdata) - len(CDATA_END)]
- }
-
- write_string(builder, cdata)
- return false, .None
+ if .Unbox_CDATA in options {
+ cdata = cdata[len(CDATA_START):]
+ cdata = cdata[:len(cdata) - len(CDATA_END)]
}
+ write_string(builder, cdata)
+ return false, .None
}
}
- } else if string(t.src[t.offset:][:len(COMMENT_START)]) == COMMENT_START {
+
+ } else if strings.has_prefix(s, COMMENT_START) {
t.read_offset += len(COMMENT_START)
// Comment is passed through by default.
offset := t.offset
diff --git a/core/encoding/hxa/read.odin b/core/encoding/hxa/read.odin
index a679946f8..6dde16848 100644
--- a/core/encoding/hxa/read.odin
+++ b/core/encoding/hxa/read.odin
@@ -79,7 +79,6 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato
read_meta :: proc(r: ^Reader, capacity: u32le, allocator := context.allocator, loc := #caller_location) -> (meta_data: []Meta, err: Read_Error) {
meta_data = make([]Meta, int(capacity), allocator=allocator)
count := 0
- defer meta_data = meta_data[:count]
for &m in meta_data {
m.name = read_name(r) or_return
@@ -105,6 +104,7 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato
count += 1
}
+ meta_data = meta_data[:count]
return
}
@@ -112,7 +112,6 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato
stack_count := read_value(r, u32le) or_return
layer_count := 0
layers = make(Layer_Stack, stack_count, allocator=allocator, loc=loc)
- defer layers = layers[:layer_count]
for &layer in layers {
layer.name = read_name(r) or_return
layer.components = read_value(r, u8) or_return
@@ -136,6 +135,7 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato
layer_count += 1
}
+ layers = layers[:layer_count]
return
}
diff --git a/core/encoding/json/tokenizer.odin b/core/encoding/json/tokenizer.odin
index e46d879a7..ad928b7d9 100644
--- a/core/encoding/json/tokenizer.odin
+++ b/core/encoding/json/tokenizer.odin
@@ -101,7 +101,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
}
}
- scan_espace :: proc(t: ^Tokenizer) -> bool {
+ scan_escape :: proc(t: ^Tokenizer) -> bool {
switch t.r {
case '"', '\'', '\\', '/', 'b', 'n', 'r', 't', 'f':
next_rune(t)
@@ -310,7 +310,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
break
}
if r == '\\' {
- scan_espace(t)
+ scan_escape(t)
}
}
diff --git a/core/encoding/json/unmarshal.odin b/core/encoding/json/unmarshal.odin
index f5862aca4..09f7b4f55 100644
--- a/core/encoding/json/unmarshal.odin
+++ b/core/encoding/json/unmarshal.odin
@@ -406,6 +406,9 @@ unmarshal_expect_token :: proc(p: ^Parser, kind: Token_Kind, loc := #caller_loca
return prev
}
+// Struct tags can include not only the name of the JSON key, but also a tag such as `omitempty`.
+// Example: `json:"key_name,omitempty"`
+// This returns the first field as `json_name`, and the rest are returned as `extra`.
@(private)
json_name_from_tag_value :: proc(value: string) -> (json_name, extra: string) {
json_name = value
@@ -441,12 +444,6 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
defer delete(key, p.allocator)
unmarshal_expect_token(p, .Colon)
-
- field_test :: #force_inline proc "contextless" (field_used: [^]byte, offset: uintptr) -> bool {
- prev_set := field_used[offset/8] & byte(offset&7) != 0
- field_used[offset/8] |= byte(offset&7)
- return prev_set
- }
field_used_bytes := (reflect.size_of_typeid(ti.id)+7)/8
field_used := intrinsics.alloca(field_used_bytes + 1, 1) // + 1 to not overflow on size_of 0 types.
@@ -465,7 +462,9 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
if use_field_idx < 0 {
for field, field_idx in fields {
- if key == field.name {
+ tag_value := reflect.struct_tag_get(field.tag, "json")
+ json_name, _ := json_name_from_tag_value(tag_value)
+ if json_name == "" && key == field.name {
use_field_idx = field_idx
break
}
@@ -486,7 +485,9 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
}
}
- if field.name == key || (field.tag != "" && reflect.struct_tag_get(field.tag, "json") == key) {
+ tag_value := reflect.struct_tag_get(field.tag, "json")
+ json_name, _ := json_name_from_tag_value(tag_value)
+ if (json_name == "" && field.name == key) || json_name == key {
offset = field.offset
type = field.type
found = true
@@ -508,6 +509,11 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
}
if field_found {
+ field_test :: #force_inline proc "contextless" (field_used: [^]byte, offset: uintptr) -> bool {
+ prev_set := field_used[offset/8] & byte(offset&7) != 0
+ field_used[offset/8] |= byte(offset&7)
+ return prev_set
+ }
if field_test(field_used, offset) {
return .Multiple_Use_Field
}
diff --git a/core/encoding/uuid/generation.odin b/core/encoding/uuid/generation.odin
index 7c9d4b80c..b210f6a52 100644
--- a/core/encoding/uuid/generation.odin
+++ b/core/encoding/uuid/generation.odin
@@ -240,7 +240,7 @@ Example:
import "core:encoding/uuid"
import "core:fmt"
- main :: proc() {
+ generate_v8_hash_bytes_example :: proc() {
my_uuid := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.odin-lang.org", .SHA256)
my_uuid_string := uuid.to_string(my_uuid, context.temp_allocator)
fmt.println(my_uuid_string)
@@ -306,7 +306,7 @@ Example:
import "core:encoding/uuid"
import "core:fmt"
- main :: proc() {
+ generate_v8_hash_string_example :: proc() {
my_uuid := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.odin-lang.org", .SHA256)
my_uuid_string := uuid.to_string(my_uuid, context.temp_allocator)
fmt.println(my_uuid_string)
diff --git a/core/encoding/xml/tokenizer.odin b/core/encoding/xml/tokenizer.odin
index a2bbaf28e..3ef9a6388 100644
--- a/core/encoding/xml/tokenizer.odin
+++ b/core/encoding/xml/tokenizer.odin
@@ -16,6 +16,7 @@ package encoding_xml
import "core:fmt"
import "core:unicode"
import "core:unicode/utf8"
+import "core:strings"
Error_Handler :: #type proc(pos: Pos, fmt: string, args: ..any)
@@ -121,7 +122,7 @@ default_error_handler :: proc(pos: Pos, msg: string, args: ..any) {
error :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) {
pos := offset_to_pos(t, offset)
if t.err != nil {
- t.err(pos, msg, ..args)
+ t.err(pos=pos, fmt=msg, args=args)
}
t.error_count += 1
}
@@ -268,32 +269,27 @@ scan_comment :: proc(t: ^Tokenizer) -> (comment: string, err: Error) {
// Skip CDATA
skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) {
- if t.read_offset + len(CDATA_START) >= len(t.src) {
- // Can't be the start of a CDATA tag.
+ if s := string(t.src[t.offset:]); !strings.has_prefix(s, CDATA_START) {
return .None
}
- if string(t.src[t.offset:][:len(CDATA_START)]) == CDATA_START {
- t.read_offset += len(CDATA_START)
- offset := t.offset
+ t.read_offset += len(CDATA_START)
+ offset := t.offset
- cdata_scan: for {
- advance_rune(t)
- if t.ch < 0 {
- error(t, offset, "[scan_string] CDATA was not terminated\n")
- return .Premature_EOF
- }
+ cdata_scan: for {
+ advance_rune(t)
+ if t.ch < 0 {
+ error(t, offset, "[scan_string] CDATA was not terminated\n")
+ return .Premature_EOF
+ }
- // Scan until the end of a CDATA tag.
- if t.read_offset + len(CDATA_END) < len(t.src) {
- if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END {
- t.read_offset += len(CDATA_END)
- break cdata_scan
- }
- }
+ // Scan until the end of a CDATA tag.
+ if s := string(t.src[t.read_offset:]); strings.has_prefix(s, CDATA_END) {
+ t.read_offset += len(CDATA_END)
+ break cdata_scan
}
}
- return
+ return .None
}
@(optimization_mode="favor_size")
@@ -393,6 +389,8 @@ scan :: proc(t: ^Tokenizer, multiline_string := false) -> Token {
case '/': kind = .Slash
case '-': kind = .Dash
case ':': kind = .Colon
+ case '[': kind = .Open_Bracket
+ case ']': kind = .Close_Bracket
case '"', '\'':
kind = .Invalid
diff --git a/core/encoding/xml/xml_reader.odin b/core/encoding/xml/xml_reader.odin
index b8c8b13a4..d616be9dc 100644
--- a/core/encoding/xml/xml_reader.odin
+++ b/core/encoding/xml/xml_reader.odin
@@ -56,7 +56,7 @@ Option_Flag :: enum {
Option_Flags :: bit_set[Option_Flag; u16]
Document :: struct {
- elements: [dynamic]Element,
+ elements: [dynamic]Element `fmt:"v,element_count"`,
element_count: Element_ID,
prologue: Attributes,
@@ -70,15 +70,15 @@ Document :: struct {
// If we encounter comments before the root node, and the option to intern comments is given, this is where they'll live.
// Otherwise they'll be in the element tree.
- comments: [dynamic]string,
+ comments: [dynamic]string `fmt:"-"`,
// Internal
- tokenizer: ^Tokenizer,
- allocator: mem.Allocator,
+ tokenizer: ^Tokenizer `fmt:"-"`,
+ allocator: mem.Allocator `fmt:"-"`,
// Input. Either the original buffer, or a copy if `.Input_May_Be_Modified` isn't specified.
- input: []u8,
- strings_to_free: [dynamic]string,
+ input: []u8 `fmt:"-"`,
+ strings_to_free: [dynamic]string `fmt:"-"`,
}
Element :: struct {
@@ -195,7 +195,6 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
loop: for {
skip_whitespace(t)
- // NOTE(Jeroen): This is faster as a switch.
switch t.ch {
case '<':
// Consume peeked `<`
@@ -306,9 +305,17 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
}
}
+ case .Open_Bracket:
+ // This could be a CDATA tag part of a tag's body. Unread the ` (attr: Attribute, offset: int, err: E
t := doc.tokenizer
key := expect(t, .Ident) or_return
- offset = t.offset - len(key.text)
-
_ = expect(t, .Eq) or_return
value := expect(t, .String, multiline_string=true) or_return
@@ -591,6 +565,47 @@ parse_doctype :: proc(doc: ^Document) -> (err: Error) {
return .None
}
+parse_body :: proc(doc: ^Document, element: Element_ID, opts: Options) -> (err: Error) {
+ assert(doc != nil)
+ context.allocator = doc.allocator
+ t := doc.tokenizer
+
+ body_text := scan_string(t, t.offset) or_return
+ needs_processing := .Unbox_CDATA in opts.flags
+ needs_processing |= .Decode_SGML_Entities in opts.flags
+
+ if !needs_processing {
+ append(&doc.elements[element].value, body_text)
+ return
+ }
+
+ decode_opts := entity.XML_Decode_Options{}
+ if .Keep_Tag_Body_Comments not_in opts.flags {
+ decode_opts += { .Comment_Strip }
+ }
+
+ if .Decode_SGML_Entities not_in opts.flags {
+ decode_opts += { .No_Entity_Decode }
+ }
+
+ if .Unbox_CDATA in opts.flags {
+ decode_opts += { .Unbox_CDATA }
+ if .Decode_SGML_Entities in opts.flags {
+ decode_opts += { .Decode_CDATA }
+ }
+ }
+
+ decoded, decode_err := entity.decode_xml(body_text, decode_opts)
+ if decode_err == .None {
+ append(&doc.elements[element].value, decoded)
+ append(&doc.strings_to_free, decoded)
+ } else {
+ append(&doc.elements[element].value, body_text)
+ }
+
+ return
+}
+
Element_ID :: u32
new_element :: proc(doc: ^Document) -> (id: Element_ID) {
@@ -609,4 +624,4 @@ new_element :: proc(doc: ^Document) -> (id: Element_ID) {
cur := doc.element_count
doc.element_count += 1
return cur
-}
+}
\ No newline at end of file
diff --git a/core/flags/util.odin b/core/flags/util.odin
index c182289be..ce7e2e36c 100644
--- a/core/flags/util.odin
+++ b/core/flags/util.odin
@@ -95,7 +95,7 @@ Example:
import "core:flags"
import "core:fmt"
- subtag_example :: proc() {
+ get_subtag_example :: proc() {
args_tag := "precision=3,signed"
precision, has_precision := flags.get_subtag(args_tag, "precision")
diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin
index 826a21ee9..9c07847dd 100644
--- a/core/fmt/fmt.odin
+++ b/core/fmt/fmt.odin
@@ -1449,9 +1449,12 @@ fmt_float :: proc(fi: ^Info, v: f64, bit_size: int, verb: rune) {
_fmt_float_as(fi, v, bit_size, verb, 'g', -1)
case 'f', 'F':
_fmt_float_as(fi, v, bit_size, verb, 'f', 3)
- case 'e', 'E':
+ case 'e':
// BUG(): "%.3e" returns "3.000e+00"
_fmt_float_as(fi, v, bit_size, verb, 'e', 6)
+ case 'E':
+ // BUG(): "%.3E" returns "3.000E+00"
+ _fmt_float_as(fi, v, bit_size, verb, 'E', 6)
case 'h', 'H':
prev_fi := fi^
@@ -1802,11 +1805,8 @@ fmt_bit_set :: proc(fi: ^Info, v: any, name: string = "", verb: rune = 'v') {
e, is_enum := et.variant.(runtime.Type_Info_Enum)
commas := 0
- loop: for i in 0 ..< bit_size {
- if bits & (1< 0 {
io.write_string(fi.writer, ", ", &fi.n)
}
@@ -1829,8 +1829,7 @@ fmt_bit_set :: proc(fi: ^Info, v: any, name: string = "", verb: rune = 'v') {
}
}
}
- v := i64(i) + info.lower
- io.write_i64(fi.writer, v, 10, &fi.n)
+ io.write_i64(fi.writer, i, 10, &fi.n)
commas += 1
}
}
diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin
index 6d93fb879..0fe5c3477 100644
--- a/core/log/file_console_logger.odin
+++ b/core/log/file_console_logger.odin
@@ -2,10 +2,12 @@
#+build !orca
package log
-import "core:encoding/ansi"
+import "base:runtime"
import "core:fmt"
import "core:strings"
import "core:os"
+import "core:terminal"
+import "core:terminal/ansi"
import "core:time"
Level_Headers := [?]string{
@@ -37,11 +39,36 @@ File_Console_Logger_Data :: struct {
ident: string,
}
+@(private) global_subtract_stdout_options: Options
+@(private) global_subtract_stderr_options: Options
+
+@(init, private)
+init_standard_stream_status :: proc() {
+ // NOTE(Feoramund): While it is technically possible for these streams to
+ // be redirected during the runtime of the program, the cost of checking on
+ // every single log message is not worth it to support such an
+ // uncommonly-used feature.
+ if terminal.color_enabled {
+ // This is done this way because it's possible that only one of these
+ // streams could be redirected to a file.
+ if !terminal.is_terminal(os.stdout) {
+ global_subtract_stdout_options = {.Terminal_Color}
+ }
+ if !terminal.is_terminal(os.stderr) {
+ global_subtract_stderr_options = {.Terminal_Color}
+ }
+ } else {
+ // Override any terminal coloring.
+ global_subtract_stdout_options = {.Terminal_Color}
+ global_subtract_stderr_options = {.Terminal_Color}
+ }
+}
+
create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger {
data := new(File_Console_Logger_Data, allocator)
data.file_handle = h
data.ident = ident
- return Logger{file_console_logger_proc, data, lowest, opt}
+ return Logger{file_logger_proc, data, lowest, opt}
}
destroy_file_logger :: proc(log: Logger, allocator := context.allocator) {
@@ -56,19 +83,15 @@ create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logg
data := new(File_Console_Logger_Data, allocator)
data.file_handle = os.INVALID_HANDLE
data.ident = ident
- return Logger{file_console_logger_proc, data, lowest, opt}
+ return Logger{console_logger_proc, data, lowest, opt}
}
destroy_console_logger :: proc(log: Logger, allocator := context.allocator) {
free(log.data, allocator)
}
-file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
- data := cast(^File_Console_Logger_Data)logger_data
- h: os.Handle = os.stdout if level <= Level.Error else os.stderr
- if data.file_handle != os.INVALID_HANDLE {
- h = data.file_handle
- }
+@(private)
+_file_console_logger_proc :: proc(h: os.Handle, ident: string, level: Level, text: string, options: Options, location: runtime.Source_Code_Location) {
backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths.
buf := strings.builder_from_bytes(backing[:])
@@ -86,13 +109,32 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string
fmt.sbprintf(&buf, "[{}] ", os.current_thread_id())
}
- if data.ident != "" {
- fmt.sbprintf(&buf, "[%s] ", data.ident)
+ if ident != "" {
+ fmt.sbprintf(&buf, "[%s] ", ident)
}
//TODO(Hoej): When we have better atomics and such, make this thread-safe
fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text)
}
+file_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
+ data := cast(^File_Console_Logger_Data)logger_data
+ _file_console_logger_proc(data.file_handle, data.ident, level, text, options, location)
+}
+
+console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
+ options := options
+ data := cast(^File_Console_Logger_Data)logger_data
+ h: os.Handle = ---
+ if level < Level.Error {
+ h = os.stdout
+ options -= global_subtract_stdout_options
+ } else {
+ h = os.stderr
+ options -= global_subtract_stderr_options
+ }
+ _file_console_logger_proc(h, data.ident, level, text, options, location)
+}
+
do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) {
RESET :: ansi.CSI + ansi.RESET + ansi.SGR
diff --git a/core/math/math.odin b/core/math/math.odin
index 934842318..b99a97bc1 100644
--- a/core/math/math.odin
+++ b/core/math/math.odin
@@ -2296,7 +2296,7 @@ nextafter_f16 :: proc "contextless" (x, y: f16) -> (r: f16) {
case x == y:
r = x
case x == 0:
- r = copy_sign_f16(1, y)
+ r = copy_sign_f16(transmute(f16)u16(1), y)
case (y > x) == (x > 0):
r = transmute(f16)(transmute(u16)x + 1)
case:
@@ -2312,7 +2312,7 @@ nextafter_f32 :: proc "contextless" (x, y: f32) -> (r: f32) {
case x == y:
r = x
case x == 0:
- r = copy_sign_f32(1, y)
+ r = copy_sign_f32(transmute(f32)u32(1), y)
case (y > x) == (x > 0):
r = transmute(f32)(transmute(u32)x + 1)
case:
@@ -2328,7 +2328,7 @@ nextafter_f64 :: proc "contextless" (x, y: f64) -> (r: f64) {
case x == y:
r = x
case x == 0:
- r = copy_sign_f64(1, y)
+ r = copy_sign_f64(transmute(f64)u64(1), y)
case (y > x) == (x > 0):
r = transmute(f64)(transmute(u64)x + 1)
case:
@@ -2349,32 +2349,6 @@ nextafter :: proc{
nextafter_f64, nextafter_f64le, nextafter_f64be,
}
-@(require_results)
-signbit_f16 :: proc "contextless" (x: f16) -> bool {
- return (transmute(u16)x)&(1<<15) != 0
-}
-@(require_results)
-signbit_f32 :: proc "contextless" (x: f32) -> bool {
- return (transmute(u32)x)&(1<<31) != 0
-}
-@(require_results)
-signbit_f64 :: proc "contextless" (x: f64) -> bool {
- return (transmute(u64)x)&(1<<63) != 0
-}
-@(require_results) signbit_f16le :: proc "contextless" (x: f16le) -> bool { return signbit_f16(f16(x)) }
-@(require_results) signbit_f32le :: proc "contextless" (x: f32le) -> bool { return signbit_f32(f32(x)) }
-@(require_results) signbit_f64le :: proc "contextless" (x: f64le) -> bool { return signbit_f64(f64(x)) }
-@(require_results) signbit_f16be :: proc "contextless" (x: f16be) -> bool { return signbit_f16(f16(x)) }
-@(require_results) signbit_f32be :: proc "contextless" (x: f32be) -> bool { return signbit_f32(f32(x)) }
-@(require_results) signbit_f64be :: proc "contextless" (x: f64be) -> bool { return signbit_f64(f64(x)) }
-
-signbit :: proc{
- signbit_f16, signbit_f16le, signbit_f16be,
- signbit_f32, signbit_f32le, signbit_f32be,
- signbit_f64, signbit_f64le, signbit_f64be,
-}
-
-
@(require_results)
hypot_f16 :: proc "contextless" (x, y: f16) -> (r: f16) {
p, q := abs(x), abs(y)
diff --git a/core/math/math_gamma.odin b/core/math/math_gamma.odin
index 9f5a364d3..9e8c2a909 100644
--- a/core/math/math_gamma.odin
+++ b/core/math/math_gamma.odin
@@ -132,7 +132,7 @@ gamma_f64 :: proc "contextless" (x: f64) -> f64 {
case is_inf(x, 1):
return inf_f64(1)
case x == 0:
- if signbit(x) {
+ if sign_bit(x) {
return inf_f64(-1)
}
return inf_f64(1)
diff --git a/core/math/rand/rand.odin b/core/math/rand/rand.odin
index 537256d32..56208931f 100644
--- a/core/math/rand/rand.odin
+++ b/core/math/rand/rand.odin
@@ -33,7 +33,7 @@ Example:
import "core:math/rand"
import "core:fmt"
- set_global_seed_example :: proc() {
+ reset_example :: proc() {
rand.reset(1)
fmt.println(rand.uint64())
}
@@ -432,7 +432,7 @@ Example:
Possible Output:
15.312
- 673.130
+ 273.15
*/
@(require_results) float64_range :: proc(low, high: f64, gen := context.random_generator) -> (val: f64) {
@@ -467,7 +467,7 @@ Example:
Possible Output:
15.312
- 273.130
+ 273.15
*/
@(require_results) float32_range :: proc(low, high: f32, gen := context.random_generator) -> (val: f32) {
diff --git a/core/mem/allocators.odin b/core/mem/allocators.odin
index 028be58e3..1bb887212 100644
--- a/core/mem/allocators.odin
+++ b/core/mem/allocators.odin
@@ -2,6 +2,7 @@ package mem
import "base:intrinsics"
import "base:runtime"
+import "base:sanitizer"
/*
Nil allocator.
@@ -138,6 +139,7 @@ arena_init :: proc(a: ^Arena, data: []byte) {
a.offset = 0
a.peak_used = 0
a.temp_count = 0
+ sanitizer.address_poison(a.data)
}
/*
@@ -224,7 +226,9 @@ arena_alloc_bytes_non_zeroed :: proc(
}
a.offset += total_size
a.peak_used = max(a.peak_used, a.offset)
- return byte_slice(ptr, size), nil
+ result := byte_slice(ptr, size)
+ sanitizer.address_unpoison(result)
+ return result, nil
}
/*
@@ -232,6 +236,7 @@ Free all memory to an arena.
*/
arena_free_all :: proc(a: ^Arena) {
a.offset = 0
+ sanitizer.address_poison(a.data)
}
arena_allocator_proc :: proc(
@@ -309,6 +314,7 @@ allocations *inside* the temporary memory region will be freed to the arena.
end_arena_temp_memory :: proc(tmp: Arena_Temp_Memory) {
assert(tmp.arena.offset >= tmp.prev_offset)
assert(tmp.arena.temp_count > 0)
+ sanitizer.address_poison(tmp.arena.data[tmp.prev_offset:tmp.arena.offset])
tmp.arena.offset = tmp.prev_offset
tmp.arena.temp_count -= 1
}
@@ -363,6 +369,7 @@ scratch_init :: proc(s: ^Scratch, size: int, backup_allocator := context.allocat
s.prev_allocation = nil
s.backup_allocator = backup_allocator
s.leaked_allocations.allocator = backup_allocator
+ sanitizer.address_poison(s.data)
return nil
}
@@ -377,6 +384,7 @@ scratch_destroy :: proc(s: ^Scratch) {
free_bytes(ptr, s.backup_allocator)
}
delete(s.leaked_allocations)
+ sanitizer.address_unpoison(s.data)
delete(s.data, s.backup_allocator)
s^ = {}
}
@@ -472,7 +480,9 @@ scratch_alloc_bytes_non_zeroed :: proc(
ptr := align_forward_uintptr(offset+start, uintptr(alignment))
s.prev_allocation = rawptr(ptr)
s.curr_offset = int(offset) + size
- return byte_slice(rawptr(ptr), size), nil
+ result := byte_slice(rawptr(ptr), size)
+ sanitizer.address_unpoison(result)
+ return result, nil
} else {
a := s.backup_allocator
if a.procedure == nil {
@@ -516,6 +526,7 @@ scratch_free :: proc(s: ^Scratch, ptr: rawptr, loc := #caller_location) -> Alloc
old_ptr := uintptr(ptr)
if s.prev_allocation == ptr {
s.curr_offset = int(uintptr(s.prev_allocation) - start)
+ sanitizer.address_poison(s.data[s.curr_offset:])
s.prev_allocation = nil
return nil
}
@@ -546,6 +557,7 @@ scratch_free_all :: proc(s: ^Scratch, loc := #caller_location) {
free_bytes(ptr, s.backup_allocator, loc)
}
clear(&s.leaked_allocations)
+ sanitizer.address_poison(s.data)
}
/*
@@ -675,7 +687,9 @@ scratch_resize_bytes_non_zeroed :: proc(
old_ptr := uintptr(old_memory)
if begin <= old_ptr && old_ptr < end && old_ptr+uintptr(size) < end {
s.curr_offset = int(old_ptr-begin)+size
- return byte_slice(old_memory, size), nil
+ result := byte_slice(old_memory, size)
+ sanitizer.address_unpoison(result)
+ return result, nil
}
data, err := scratch_alloc_bytes_non_zeroed(s, size, alignment, loc)
if err != nil {
@@ -776,6 +790,7 @@ stack_init :: proc(s: ^Stack, data: []byte) {
s.prev_offset = 0
s.curr_offset = 0
s.peak_used = 0
+ sanitizer.address_poison(data)
}
/*
@@ -861,15 +876,19 @@ stack_alloc_bytes_non_zeroed :: proc(
if s.curr_offset + padding + size > len(s.data) {
return nil, .Out_Of_Memory
}
+ old_offset := s.prev_offset
s.prev_offset = s.curr_offset
s.curr_offset += padding
next_addr := curr_addr + uintptr(padding)
header := (^Stack_Allocation_Header)(next_addr - size_of(Stack_Allocation_Header))
+ sanitizer.address_unpoison(header)
header.padding = padding
- header.prev_offset = s.prev_offset
+ header.prev_offset = old_offset
s.curr_offset += size
s.peak_used = max(s.peak_used, s.curr_offset)
- return byte_slice(rawptr(next_addr), size), nil
+ result := byte_slice(rawptr(next_addr), size)
+ sanitizer.address_unpoison(result)
+ return result, nil
}
/*
@@ -902,12 +921,15 @@ stack_free :: proc(
}
header := (^Stack_Allocation_Header)(curr_addr - size_of(Stack_Allocation_Header))
old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data)))
- if old_offset != header.prev_offset {
+ if old_offset != s.prev_offset {
// panic("Out of order stack allocator free");
return .Invalid_Pointer
}
- s.curr_offset = old_offset
+
s.prev_offset = header.prev_offset
+ sanitizer.address_poison(s.data[old_offset:s.curr_offset])
+ s.curr_offset = old_offset
+
return nil
}
@@ -917,6 +939,7 @@ Free all allocations to the stack.
stack_free_all :: proc(s: ^Stack, loc := #caller_location) {
s.prev_offset = 0
s.curr_offset = 0
+ sanitizer.address_poison(s.data)
}
/*
@@ -1076,7 +1099,9 @@ stack_resize_bytes_non_zeroed :: proc(
if diff > 0 {
zero(rawptr(curr_addr + uintptr(diff)), diff)
}
- return byte_slice(old_memory, size), nil
+ result := byte_slice(old_memory, size)
+ sanitizer.address_unpoison(result)
+ return result, nil
}
stack_allocator_proc :: proc(
@@ -1144,6 +1169,7 @@ small_stack_init :: proc(s: ^Small_Stack, data: []byte) {
s.data = data
s.offset = 0
s.peak_used = 0
+ sanitizer.address_poison(data)
}
/*
@@ -1252,10 +1278,13 @@ small_stack_alloc_bytes_non_zeroed :: proc(
s.offset += padding
next_addr := curr_addr + uintptr(padding)
header := (^Small_Stack_Allocation_Header)(next_addr - size_of(Small_Stack_Allocation_Header))
+ sanitizer.address_unpoison(header)
header.padding = auto_cast padding
s.offset += size
s.peak_used = max(s.peak_used, s.offset)
- return byte_slice(rawptr(next_addr), size), nil
+ result := byte_slice(rawptr(next_addr), size)
+ sanitizer.address_unpoison(result)
+ return result, nil
}
/*
@@ -1289,6 +1318,7 @@ small_stack_free :: proc(
}
header := (^Small_Stack_Allocation_Header)(curr_addr - size_of(Small_Stack_Allocation_Header))
old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data)))
+ sanitizer.address_poison(s.data[old_offset:s.offset])
s.offset = old_offset
return nil
}
@@ -1298,6 +1328,7 @@ Free all memory to small stack.
*/
small_stack_free_all :: proc(s: ^Small_Stack) {
s.offset = 0
+ sanitizer.address_poison(s.data)
}
/*
@@ -1442,7 +1473,9 @@ small_stack_resize_bytes_non_zeroed :: proc(
return nil, nil
}
if old_size == size {
- return byte_slice(old_memory, size), nil
+ result := byte_slice(old_memory, size)
+ sanitizer.address_unpoison(result)
+ return result, nil
}
data, err := small_stack_alloc_bytes_non_zeroed(s, size, alignment, loc)
if err == nil {
diff --git a/core/mem/rollback_stack_allocator.odin b/core/mem/rollback_stack_allocator.odin
index 43ef10fe9..a00131b7f 100644
--- a/core/mem/rollback_stack_allocator.odin
+++ b/core/mem/rollback_stack_allocator.odin
@@ -1,6 +1,7 @@
package mem
import "base:runtime"
+import "base:sanitizer"
/*
Rollback stack default block size.
@@ -47,14 +48,14 @@ Rollback_Stack :: struct {
block_allocator: Allocator,
}
-@(private="file", require_results)
+@(private="file", require_results, no_sanitize_address)
rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool {
start := raw_data(block.buffer)
end := start[block.offset:]
return start < ptr && ptr <= end
}
-@(private="file", require_results)
+@(private="file", require_results, no_sanitize_address)
rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
parent: ^Rollback_Stack_Block,
block: ^Rollback_Stack_Block,
@@ -71,7 +72,7 @@ rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
return nil, nil, nil, .Invalid_Pointer
}
-@(private="file", require_results)
+@(private="file", require_results, no_sanitize_address)
rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
block: ^Rollback_Stack_Block,
header: ^Rollback_Stack_Header,
@@ -86,9 +87,10 @@ rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> (
return nil, nil, false
}
-@(private="file")
+@(private="file", no_sanitize_address)
rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_Header) {
header := header
+
for block.offset > 0 && header.is_free {
block.offset = header.prev_offset
block.last_alloc = raw_data(block.buffer)[header.prev_ptr:]
@@ -99,9 +101,10 @@ rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_
/*
Free memory to a rollback stack allocator.
*/
-@(private="file", require_results)
+@(private="file", require_results, no_sanitize_address)
rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error {
parent, block, header := rb_find_ptr(stack, ptr) or_return
+
if header.is_free {
return .Invalid_Pointer
}
@@ -120,7 +123,7 @@ rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error {
/*
Free all memory owned by the rollback stack allocator.
*/
-@(private="file")
+@(private="file", no_sanitize_address)
rb_free_all :: proc(stack: ^Rollback_Stack) {
for block := stack.head.next_block; block != nil; /**/ {
next_block := block.next_block
@@ -131,12 +134,13 @@ rb_free_all :: proc(stack: ^Rollback_Stack) {
stack.head.next_block = nil
stack.head.last_alloc = nil
stack.head.offset = 0
+ sanitizer.address_poison(stack.head.buffer)
}
/*
Allocate memory using the rollback stack allocator.
*/
-@(require_results)
+@(require_results, no_sanitize_address)
rb_alloc :: proc(
stack: ^Rollback_Stack,
size: int,
@@ -153,7 +157,7 @@ rb_alloc :: proc(
/*
Allocate memory using the rollback stack allocator.
*/
-@(require_results)
+@(require_results, no_sanitize_address)
rb_alloc_bytes :: proc(
stack: ^Rollback_Stack,
size: int,
@@ -170,7 +174,7 @@ rb_alloc_bytes :: proc(
/*
Allocate non-initialized memory using the rollback stack allocator.
*/
-@(require_results)
+@(require_results, no_sanitize_address)
rb_alloc_non_zeroed :: proc(
stack: ^Rollback_Stack,
size: int,
@@ -184,7 +188,7 @@ rb_alloc_non_zeroed :: proc(
/*
Allocate non-initialized memory using the rollback stack allocator.
*/
-@(require_results)
+@(require_results, no_sanitize_address)
rb_alloc_bytes_non_zeroed :: proc(
stack: ^Rollback_Stack,
size: int,
@@ -194,6 +198,7 @@ rb_alloc_bytes_non_zeroed :: proc(
assert(size >= 0, "Size must be positive or zero.", loc)
assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", loc)
parent: ^Rollback_Stack_Block
+
for block := stack.head; /**/; block = block.next_block {
when !ODIN_DISABLE_ASSERT {
allocated_new_block: bool
@@ -235,7 +240,9 @@ rb_alloc_bytes_non_zeroed :: proc(
// Prevent any further allocations on it.
block.offset = cast(uintptr)len(block.buffer)
}
- #no_bounds_check return ptr[:size], nil
+ res := ptr[:size]
+ sanitizer.address_unpoison(res)
+ return res, nil
}
return nil, .Out_Of_Memory
}
@@ -243,7 +250,7 @@ rb_alloc_bytes_non_zeroed :: proc(
/*
Resize an allocation owned by rollback stack allocator.
*/
-@(require_results)
+@(require_results, no_sanitize_address)
rb_resize :: proc(
stack: ^Rollback_Stack,
old_ptr: rawptr,
@@ -266,7 +273,7 @@ rb_resize :: proc(
/*
Resize an allocation owned by rollback stack allocator.
*/
-@(require_results)
+@(require_results, no_sanitize_address)
rb_resize_bytes :: proc(
stack: ^Rollback_Stack,
old_memory: []byte,
@@ -289,7 +296,7 @@ rb_resize_bytes :: proc(
Resize an allocation owned by rollback stack allocator without explicit
zero-initialization.
*/
-@(require_results)
+@(require_results, no_sanitize_address)
rb_resize_non_zeroed :: proc(
stack: ^Rollback_Stack,
old_ptr: rawptr,
@@ -306,7 +313,7 @@ rb_resize_non_zeroed :: proc(
Resize an allocation owned by rollback stack allocator without explicit
zero-initialization.
*/
-@(require_results)
+@(require_results, no_sanitize_address)
rb_resize_bytes_non_zeroed :: proc(
stack: ^Rollback_Stack,
old_memory: []byte,
@@ -330,7 +337,9 @@ rb_resize_bytes_non_zeroed :: proc(
if len(block.buffer) <= stack.block_size {
block.offset += cast(uintptr)size - cast(uintptr)old_size
}
- #no_bounds_check return (ptr)[:size], nil
+ res := (ptr)[:size]
+ sanitizer.address_unpoison(res)
+ #no_bounds_check return res, nil
}
}
}
@@ -340,7 +349,7 @@ rb_resize_bytes_non_zeroed :: proc(
return
}
-@(private="file", require_results)
+@(private="file", require_results, no_sanitize_address)
rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) {
buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return
block = cast(^Rollback_Stack_Block)raw_data(buffer)
@@ -351,6 +360,7 @@ rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stac
/*
Initialize the rollback stack allocator using a fixed backing buffer.
*/
+@(no_sanitize_address)
rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, location := #caller_location) {
MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr)
assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.", location)
@@ -365,6 +375,7 @@ rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, loc
/*
Initialize the rollback stack alocator using a backing block allocator.
*/
+@(no_sanitize_address)
rollback_stack_init_dynamic :: proc(
stack: ^Rollback_Stack,
block_size : int = ROLLBACK_STACK_DEFAULT_BLOCK_SIZE,
@@ -396,6 +407,7 @@ rollback_stack_init :: proc {
/*
Destroy a rollback stack.
*/
+@(no_sanitize_address)
rollback_stack_destroy :: proc(stack: ^Rollback_Stack) {
if stack.block_allocator.procedure != nil {
rb_free_all(stack)
@@ -435,7 +447,7 @@ from the last allocation backwards.
Each allocation has an overhead of 8 bytes and any extra bytes to satisfy
the requested alignment.
*/
-@(require_results)
+@(require_results, no_sanitize_address)
rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator {
return Allocator {
data = stack,
@@ -443,7 +455,7 @@ rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator {
}
}
-@(require_results)
+@(require_results, no_sanitize_address)
rollback_stack_allocator_proc :: proc(
allocator_data: rawptr,
mode: Allocator_Mode,
diff --git a/core/mem/tlsf/tlsf.odin b/core/mem/tlsf/tlsf.odin
index 8ec5f52b7..0ae8c28e0 100644
--- a/core/mem/tlsf/tlsf.odin
+++ b/core/mem/tlsf/tlsf.odin
@@ -10,6 +10,7 @@
// package mem_tlsf implements a Two Level Segregated Fit memory allocator.
package mem_tlsf
+import "base:intrinsics"
import "base:runtime"
Error :: enum byte {
@@ -21,7 +22,6 @@ Error :: enum byte {
Backing_Allocator_Error = 5,
}
-
Allocator :: struct {
// Empty lists point at this block to indicate they are free.
block_null: Block_Header,
@@ -39,12 +39,13 @@ Allocator :: struct {
// statistics like how much memory is still available,
// fragmentation, etc.
pool: Pool,
+
+ // If we're expected to grow when we run out of memory,
+ // how much should we ask the backing allocator for?
+ new_pool_size: uint,
}
#assert(size_of(Allocator) % ALIGN_SIZE == 0)
-
-
-
@(require_results)
allocator :: proc(t: ^Allocator) -> runtime.Allocator {
return runtime.Allocator{
@@ -53,6 +54,21 @@ allocator :: proc(t: ^Allocator) -> runtime.Allocator {
}
}
+// Tries to estimate a pool size sufficient for `count` allocations, each of `size` and with `alignment`.
+estimate_pool_from_size_alignment :: proc(count: int, size: int, alignment: int) -> (pool_size: int) {
+ per_allocation := align_up(uint(size + alignment) + BLOCK_HEADER_OVERHEAD, ALIGN_SIZE)
+ return count * int(per_allocation) + int(INITIAL_POOL_OVERHEAD)
+}
+
+// Tries to estimate a pool size sufficient for `count` allocations of `type`.
+estimate_pool_from_typeid :: proc(count: int, type: typeid) -> (pool_size: int) {
+ ti := type_info_of(type)
+ return estimate_pool_size(count, ti.size, ti.align)
+}
+
+estimate_pool_size :: proc{estimate_pool_from_size_alignment, estimate_pool_from_typeid}
+
+
@(require_results)
init_from_buffer :: proc(control: ^Allocator, buf: []byte) -> Error {
assert(control != nil)
@@ -60,21 +76,25 @@ init_from_buffer :: proc(control: ^Allocator, buf: []byte) -> Error {
return .Invalid_Alignment
}
- pool_bytes := align_down(len(buf) - POOL_OVERHEAD, ALIGN_SIZE)
+ pool_bytes := align_down(len(buf) - INITIAL_POOL_OVERHEAD, ALIGN_SIZE)
if pool_bytes < BLOCK_SIZE_MIN {
return .Backing_Buffer_Too_Small
} else if pool_bytes > BLOCK_SIZE_MAX {
return .Backing_Buffer_Too_Large
}
- clear(control)
- return pool_add(control, buf[:])
+ control.pool = Pool{
+ data = buf,
+ allocator = {},
+ }
+
+ return free_all(control)
}
@(require_results)
init_from_allocator :: proc(control: ^Allocator, backing: runtime.Allocator, initial_pool_size: int, new_pool_size := 0) -> Error {
assert(control != nil)
- pool_bytes := align_up(uint(initial_pool_size) + POOL_OVERHEAD, ALIGN_SIZE)
+ pool_bytes := uint(estimate_pool_size(1, initial_pool_size, ALIGN_SIZE))
if pool_bytes < BLOCK_SIZE_MIN {
return .Backing_Buffer_Too_Small
} else if pool_bytes > BLOCK_SIZE_MAX {
@@ -85,12 +105,15 @@ init_from_allocator :: proc(control: ^Allocator, backing: runtime.Allocator, ini
if backing_err != nil {
return .Backing_Allocator_Error
}
- err := init_from_buffer(control, buf)
+
control.pool = Pool{
data = buf,
allocator = backing,
}
- return err
+
+ control.new_pool_size = uint(new_pool_size)
+
+ return free_all(control)
}
init :: proc{init_from_buffer, init_from_allocator}
@@ -103,8 +126,6 @@ destroy :: proc(control: ^Allocator) {
// No need to call `pool_remove` or anything, as they're they're embedded in the backing memory.
// We do however need to free the `Pool` tracking entities and the backing memory itself.
- // As `Allocator` is embedded in the first backing slice, the `control` pointer will be
- // invalid after this call.
for p := control.pool.next; p != nil; {
next := p.next
@@ -136,9 +157,8 @@ allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode,
return nil, nil
case .Free_All:
- // NOTE: this doesn't work right at the moment, Jeroen has it on his to-do list :)
- // clear(control)
- return nil, .Mode_Not_Implemented
+ free_all(control)
+ return nil, nil
case .Resize:
return resize(control, old_memory, uint(old_size), uint(size), uint(alignment))
@@ -159,3 +179,23 @@ allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode,
return nil, nil
}
+
+// Exported solely to facilitate testing
+@(require_results)
+ffs :: proc "contextless" (word: u32) -> (bit: i32) {
+ return -1 if word == 0 else i32(intrinsics.count_trailing_zeros(word))
+}
+
+// Exported solely to facilitate testing
+@(require_results)
+fls :: proc "contextless" (word: u32) -> (bit: i32) {
+ N :: (size_of(u32) * 8) - 1
+ return i32(N - intrinsics.count_leading_zeros(word))
+}
+
+// Exported solely to facilitate testing
+@(require_results)
+fls_uint :: proc "contextless" (size: uint) -> (bit: i32) {
+ N :: (size_of(uint) * 8) - 1
+ return i32(N - intrinsics.count_leading_zeros(size))
+}
diff --git a/core/mem/tlsf/tlsf_internal.odin b/core/mem/tlsf/tlsf_internal.odin
index 66141fcbb..89b875679 100644
--- a/core/mem/tlsf/tlsf_internal.odin
+++ b/core/mem/tlsf/tlsf_internal.odin
@@ -7,12 +7,11 @@
Jeroen van Rijn: Source port
*/
-
package mem_tlsf
import "base:intrinsics"
+import "base:sanitizer"
import "base:runtime"
-// import "core:fmt"
// log2 of number of linear subdivisions of block sizes.
// Larger values require more memory in the control structure.
@@ -58,7 +57,6 @@ Pool :: struct {
next: ^Pool,
}
-
/*
Block header structure.
@@ -95,6 +93,7 @@ The `prev_phys_block` field is stored *inside* the previous free block.
BLOCK_HEADER_OVERHEAD :: uint(size_of(uint))
POOL_OVERHEAD :: 2 * BLOCK_HEADER_OVERHEAD
+INITIAL_POOL_OVERHEAD :: 48
// User data starts directly after the size field in a used block.
BLOCK_START_OFFSET :: offset_of(Block_Header, size) + size_of(Block_Header{}.size)
@@ -107,436 +106,12 @@ bits for `FL_INDEX`.
BLOCK_SIZE_MIN :: uint(size_of(Block_Header) - size_of(^Block_Header))
BLOCK_SIZE_MAX :: uint(1) << FL_INDEX_MAX
-/*
- TLSF achieves O(1) cost for `alloc` and `free` operations by limiting
- the search for a free block to a free list of guaranteed size
- adequate to fulfill the request, combined with efficient free list
- queries using bitmasks and architecture-specific bit-manipulation
- routines.
-
- NOTE: TLSF spec relies on ffs/fls returning value 0..31.
-*/
-
-@(require_results)
-ffs :: proc "contextless" (word: u32) -> (bit: i32) {
- return -1 if word == 0 else i32(intrinsics.count_trailing_zeros(word))
-}
-
-@(require_results)
-fls :: proc "contextless" (word: u32) -> (bit: i32) {
- N :: (size_of(u32) * 8) - 1
- return i32(N - intrinsics.count_leading_zeros(word))
-}
-
-@(require_results)
-fls_uint :: proc "contextless" (size: uint) -> (bit: i32) {
- N :: (size_of(uint) * 8) - 1
- return i32(N - intrinsics.count_leading_zeros(size))
-}
-
-@(require_results)
-block_size :: proc "contextless" (block: ^Block_Header) -> (size: uint) {
- return block.size &~ (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE)
-}
-
-block_set_size :: proc "contextless" (block: ^Block_Header, size: uint) {
- old_size := block.size
- block.size = size | (old_size & (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE))
-}
-
-@(require_results)
-block_is_last :: proc "contextless" (block: ^Block_Header) -> (is_last: bool) {
- return block_size(block) == 0
-}
-
-@(require_results)
-block_is_free :: proc "contextless" (block: ^Block_Header) -> (is_free: bool) {
- return (block.size & BLOCK_HEADER_FREE) == BLOCK_HEADER_FREE
-}
-
-block_set_free :: proc "contextless" (block: ^Block_Header) {
- block.size |= BLOCK_HEADER_FREE
-}
-
-block_set_used :: proc "contextless" (block: ^Block_Header) {
- block.size &~= BLOCK_HEADER_FREE
-}
-
-@(require_results)
-block_is_prev_free :: proc "contextless" (block: ^Block_Header) -> (is_prev_free: bool) {
- return (block.size & BLOCK_HEADER_PREV_FREE) == BLOCK_HEADER_PREV_FREE
-}
-
-block_set_prev_free :: proc "contextless" (block: ^Block_Header) {
- block.size |= BLOCK_HEADER_PREV_FREE
-}
-
-block_set_prev_used :: proc "contextless" (block: ^Block_Header) {
- block.size &~= BLOCK_HEADER_PREV_FREE
-}
-
-@(require_results)
-block_from_ptr :: proc(ptr: rawptr) -> (block_ptr: ^Block_Header) {
- return (^Block_Header)(uintptr(ptr) - BLOCK_START_OFFSET)
-}
-
-@(require_results)
-block_to_ptr :: proc(block: ^Block_Header) -> (ptr: rawptr) {
- return rawptr(uintptr(block) + BLOCK_START_OFFSET)
-}
-
-// Return location of next block after block of given size.
-@(require_results)
-offset_to_block :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) {
- return (^Block_Header)(uintptr(ptr) + uintptr(size))
-}
-
-@(require_results)
-offset_to_block_backwards :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) {
- return (^Block_Header)(uintptr(ptr) - uintptr(size))
-}
-
-// Return location of previous block.
-@(require_results)
-block_prev :: proc(block: ^Block_Header) -> (prev: ^Block_Header) {
- assert(block_is_prev_free(block), "previous block must be free")
- return block.prev_phys_block
-}
-
-// Return location of next existing block.
-@(require_results)
-block_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) {
- return offset_to_block(block_to_ptr(block), block_size(block) - BLOCK_HEADER_OVERHEAD)
-}
-
-// Link a new block with its physical neighbor, return the neighbor.
-@(require_results)
-block_link_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) {
- next = block_next(block)
- next.prev_phys_block = block
- return
-}
-
-block_mark_as_free :: proc(block: ^Block_Header) {
- // Link the block to the next block, first.
- next := block_link_next(block)
- block_set_prev_free(next)
- block_set_free(block)
-}
-
-block_mark_as_used :: proc(block: ^Block_Header) {
- next := block_next(block)
- block_set_prev_used(next)
- block_set_used(block)
-}
-
-@(require_results)
-align_up :: proc(x, align: uint) -> (aligned: uint) {
- assert(0 == (align & (align - 1)), "must align to a power of two")
- return (x + (align - 1)) &~ (align - 1)
-}
-
-@(require_results)
-align_down :: proc(x, align: uint) -> (aligned: uint) {
- assert(0 == (align & (align - 1)), "must align to a power of two")
- return x - (x & (align - 1))
-}
-
-@(require_results)
-align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) {
- assert(0 == (align & (align - 1)), "must align to a power of two")
- align_mask := uintptr(align) - 1
- _ptr := uintptr(ptr)
- _aligned := (_ptr + align_mask) &~ (align_mask)
- return rawptr(_aligned)
-}
-
-// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
-@(require_results)
-adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) {
- if size == 0 {
- return 0
- }
-
- // aligned size must not exceed `BLOCK_SIZE_MAX`, or we'll go out of bounds on `sl_bitmap`.
- if aligned := align_up(size, align); aligned < BLOCK_SIZE_MAX {
- adjusted = max(aligned, BLOCK_SIZE_MIN)
- }
- return
-}
-
-// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
-@(require_results)
-adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: runtime.Allocator_Error) {
- if size == 0 {
- return 0, nil
- }
-
- // aligned size must not exceed `BLOCK_SIZE_MAX`, or we'll go out of bounds on `sl_bitmap`.
- if aligned := align_up(size, align); aligned < BLOCK_SIZE_MAX {
- adjusted = min(aligned, BLOCK_SIZE_MAX)
- } else {
- err = .Out_Of_Memory
- }
- return
-}
-
-// TLSF utility functions. In most cases these are direct translations of
-// the documentation in the research paper.
-
-@(optimization_mode="favor_size", require_results)
-mapping_insert :: proc(size: uint) -> (fl, sl: i32) {
- if size < SMALL_BLOCK_SIZE {
- // Store small blocks in first list.
- sl = i32(size) / (SMALL_BLOCK_SIZE / SL_INDEX_COUNT)
- } else {
- fl = fls_uint(size)
- sl = i32(size >> (uint(fl) - TLSF_SL_INDEX_COUNT_LOG2)) ~ (1 << TLSF_SL_INDEX_COUNT_LOG2)
- fl -= (FL_INDEX_SHIFT - 1)
- }
- return
-}
-
-@(optimization_mode="favor_size", require_results)
-mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) {
- rounded = size
- if size >= SMALL_BLOCK_SIZE {
- round := uint(1 << (uint(fls_uint(size) - TLSF_SL_INDEX_COUNT_LOG2))) - 1
- rounded += round
- }
- return
-}
-
-// This version rounds up to the next block size (for allocations)
-@(optimization_mode="favor_size", require_results)
-mapping_search :: proc(size: uint) -> (fl, sl: i32) {
- return mapping_insert(mapping_round(size))
-}
-
-@(require_results)
-search_suitable_block :: proc(control: ^Allocator, fli, sli: ^i32) -> (block: ^Block_Header) {
- // First, search for a block in the list associated with the given fl/sl index.
- fl := fli^; sl := sli^
-
- sl_map := control.sl_bitmap[fli^] & (~u32(0) << uint(sl))
- if sl_map == 0 {
- // No block exists. Search in the next largest first-level list.
- fl_map := control.fl_bitmap & (~u32(0) << uint(fl + 1))
- if fl_map == 0 {
- // No free blocks available, memory has been exhausted.
- return {}
- }
-
- fl = ffs(fl_map)
- fli^ = fl
- sl_map = control.sl_bitmap[fl]
- }
- assert(sl_map != 0, "internal error - second level bitmap is null")
- sl = ffs(sl_map)
- sli^ = sl
-
- // Return the first block in the free list.
- return control.blocks[fl][sl]
-}
-
-// Remove a free block from the free list.
-remove_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) {
- prev := block.prev_free
- next := block.next_free
- assert(prev != nil, "prev_free can not be nil")
- assert(next != nil, "next_free can not be nil")
- next.prev_free = prev
- prev.next_free = next
-
- // If this block is the head of the free list, set new head.
- if control.blocks[fl][sl] == block {
- control.blocks[fl][sl] = next
-
- // If the new head is nil, clear the bitmap
- if next == &control.block_null {
- control.sl_bitmap[fl] &~= (u32(1) << uint(sl))
-
- // If the second bitmap is now empty, clear the fl bitmap
- if control.sl_bitmap[fl] == 0 {
- control.fl_bitmap &~= (u32(1) << uint(fl))
- }
- }
- }
-}
-
-// Insert a free block into the free block list.
-insert_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) {
- current := control.blocks[fl][sl]
- assert(current != nil, "free lists cannot have a nil entry")
- assert(block != nil, "cannot insert a nil entry into the free list")
- block.next_free = current
- block.prev_free = &control.block_null
- current.prev_free = block
-
- assert(block_to_ptr(block) == align_ptr(block_to_ptr(block), ALIGN_SIZE), "block not properly aligned")
-
- // Insert the new block at the head of the list, and mark the first- and second-level bitmaps appropriately.
- control.blocks[fl][sl] = block
- control.fl_bitmap |= (u32(1) << uint(fl))
- control.sl_bitmap[fl] |= (u32(1) << uint(sl))
-}
-
-// Remove a given block from the free list.
-block_remove :: proc(control: ^Allocator, block: ^Block_Header) {
- fl, sl := mapping_insert(block_size(block))
- remove_free_block(control, block, fl, sl)
-}
-
-// Insert a given block into the free list.
-block_insert :: proc(control: ^Allocator, block: ^Block_Header) {
- fl, sl := mapping_insert(block_size(block))
- insert_free_block(control, block, fl, sl)
-}
-
-@(require_results)
-block_can_split :: proc(block: ^Block_Header, size: uint) -> (can_split: bool) {
- return block_size(block) >= size_of(Block_Header) + size
-}
-
-// Split a block into two, the second of which is free.
-@(require_results)
-block_split :: proc(block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) {
- // Calculate the amount of space left in the remaining block.
- remaining = offset_to_block(block_to_ptr(block), size - BLOCK_HEADER_OVERHEAD)
-
- remain_size := block_size(block) - (size + BLOCK_HEADER_OVERHEAD)
-
- assert(block_to_ptr(remaining) == align_ptr(block_to_ptr(remaining), ALIGN_SIZE),
- "remaining block not aligned properly")
-
- assert(block_size(block) == remain_size + size + BLOCK_HEADER_OVERHEAD)
- block_set_size(remaining, remain_size)
- assert(block_size(remaining) >= BLOCK_SIZE_MIN, "block split with invalid size")
-
- block_set_size(block, size)
- block_mark_as_free(remaining)
-
- return remaining
-}
-
-// Absorb a free block's storage into an adjacent previous free block.
-@(require_results)
-block_absorb :: proc(prev: ^Block_Header, block: ^Block_Header) -> (absorbed: ^Block_Header) {
- assert(!block_is_last(prev), "previous block can't be last")
- // Note: Leaves flags untouched.
- prev.size += block_size(block) + BLOCK_HEADER_OVERHEAD
- _ = block_link_next(prev)
- return prev
-}
-
-// Merge a just-freed block with an adjacent previous free block.
-@(require_results)
-block_merge_prev :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) {
- merged = block
- if (block_is_prev_free(block)) {
- prev := block_prev(block)
- assert(prev != nil, "prev physical block can't be nil")
- assert(block_is_free(prev), "prev block is not free though marked as such")
- block_remove(control, prev)
- merged = block_absorb(prev, block)
- }
- return merged
-}
-
-// Merge a just-freed block with an adjacent free block.
-@(require_results)
-block_merge_next :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) {
- merged = block
- next := block_next(block)
- assert(next != nil, "next physical block can't be nil")
-
- if (block_is_free(next)) {
- assert(!block_is_last(block), "previous block can't be last")
- block_remove(control, next)
- merged = block_absorb(block, next)
- }
- return merged
-}
-
-// Trim any trailing block space off the end of a free block, return to pool.
-block_trim_free :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
- assert(block_is_free(block), "block must be free")
- if (block_can_split(block, size)) {
- remaining_block := block_split(block, size)
- _ = block_link_next(block)
- block_set_prev_free(remaining_block)
- block_insert(control, remaining_block)
- }
-}
-
-// Trim any trailing block space off the end of a used block, return to pool.
-block_trim_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
- assert(!block_is_free(block), "Block must be used")
- if (block_can_split(block, size)) {
- // If the next block is free, we must coalesce.
- remaining_block := block_split(block, size)
- block_set_prev_used(remaining_block)
-
- remaining_block = block_merge_next(control, remaining_block)
- block_insert(control, remaining_block)
- }
-}
-
-// Trim leading block space, return to pool.
-@(require_results)
-block_trim_free_leading :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) {
- remaining = block
- if block_can_split(block, size) {
- // We want the 2nd block.
- remaining = block_split(block, size - BLOCK_HEADER_OVERHEAD)
- block_set_prev_free(remaining)
-
- _ = block_link_next(block)
- block_insert(control, block)
- }
- return remaining
-}
-
-@(require_results)
-block_locate_free :: proc(control: ^Allocator, size: uint) -> (block: ^Block_Header) {
- fl, sl: i32
- if size != 0 {
- fl, sl = mapping_search(size)
-
- /*
- `mapping_search` can futz with the size, so for excessively large sizes it can sometimes wind up
- with indices that are off the end of the block array. So, we protect against that here,
- since this is the only call site of `mapping_search`. Note that we don't need to check `sl`,
- as it comes from a modulo operation that guarantees it's always in range.
- */
- if fl < FL_INDEX_COUNT {
- block = search_suitable_block(control, &fl, &sl)
- }
- }
-
- if block != nil {
- assert(block_size(block) >= size)
- remove_free_block(control, block, fl, sl)
- }
- return block
-}
-
-@(require_results)
-block_prepare_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (res: []byte, err: runtime.Allocator_Error) {
- if block != nil {
- assert(size != 0, "Size must be non-zero")
- block_trim_free(control, block, size)
- block_mark_as_used(block)
- res = ([^]byte)(block_to_ptr(block))[:size]
- }
- return
-}
-
// Clear control structure and point all empty lists at the null block
-clear :: proc(control: ^Allocator) {
+@(private)
+free_all :: proc(control: ^Allocator) -> (err: Error) {
+ // Clear internal structures
control.block_null.next_free = &control.block_null
control.block_null.prev_free = &control.block_null
-
control.fl_bitmap = 0
for i in 0.. (err: Error) {
assert(uintptr(raw_data(pool)) % ALIGN_SIZE == 0, "Added memory must be aligned")
@@ -574,9 +155,11 @@ pool_add :: proc(control: ^Allocator, pool: []u8) -> (err: Error) {
block_set_size(next, 0)
block_set_used(next)
block_set_prev_free(next)
+
return
}
+@(private)
pool_remove :: proc(control: ^Allocator, pool: []u8) {
block := offset_to_block_backwards(raw_data(pool), BLOCK_HEADER_OVERHEAD)
@@ -588,7 +171,7 @@ pool_remove :: proc(control: ^Allocator, pool: []u8) {
remove_free_block(control, block, fl, sl)
}
-@(require_results)
+@(private, require_results)
alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) {
assert(control != nil)
adjust := adjust_request_size(size, ALIGN_SIZE)
@@ -601,9 +184,56 @@ alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) ->
return nil, .Out_Of_Memory
}
- block := block_locate_free(control, aligned_size)
+ block := block_locate_free(control, aligned_size)
if block == nil {
- return nil, .Out_Of_Memory
+ // OOM: Couldn't find block of `aligned_size` bytes.
+ if control.new_pool_size > 0 && control.pool.allocator.procedure != nil {
+ // TLSF is configured to grow.
+
+ /*
+ This implementation doesn't allow for out-of-band allocations to be passed through, as it's not designed to
+ track those. Nor is it able to signal those allocations then need to be freed on the backing allocator,
+ as opposed to regular allocations handled for you when you `destroy` the TLSF instance.
+
+ So if we're asked for more than we're configured to grow by, we can fail with an OOM error early, without adding a new pool.
+ */
+ if aligned_size > control.new_pool_size {
+ return nil, .Out_Of_Memory
+ }
+
+ // Trying to allocate a new pool of `control.new_pool_size` bytes.
+ new_pool_buf := runtime.make_aligned([]byte, control.new_pool_size, ALIGN_SIZE, control.pool.allocator) or_return
+
+ // Add new pool to control structure
+ if pool_add_err := pool_add(control, new_pool_buf); pool_add_err != .None {
+ delete(new_pool_buf, control.pool.allocator)
+ return nil, .Out_Of_Memory
+ }
+
+ sanitizer.address_poison(new_pool_buf)
+
+ // Allocate a new link in the `control.pool` tracking structure.
+ new_pool := new_clone(Pool{
+ data = new_pool_buf,
+ allocator = control.pool.allocator,
+ next = nil,
+ }, control.pool.allocator) or_return
+
+ p := &control.pool
+ for p.next != nil {
+ p = p.next
+ }
+ p.next = new_pool
+
+ // Try again to find free block
+ block = block_locate_free(control, aligned_size)
+ if block == nil {
+ return nil, .Out_Of_Memory
+ }
+ } else {
+ // TLSF is non-growing. We're done.
+ return nil, .Out_Of_Memory
+ }
}
ptr := block_to_ptr(block)
aligned := align_ptr(ptr, align)
@@ -627,7 +257,7 @@ alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) ->
return block_prepare_used(control, block, adjust)
}
-@(require_results)
+@(private, require_results, no_sanitize_address)
alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) {
res, err = alloc_bytes_non_zeroed(control, size, align)
if err == nil {
@@ -646,6 +276,7 @@ free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) {
block := block_from_ptr(ptr)
assert(!block_is_free(block), "block already marked as free") // double free
+ sanitizer.address_poison(ptr, block.size)
block_mark_as_free(block)
block = block_merge_prev(control, block)
block = block_merge_next(control, block)
@@ -653,7 +284,7 @@ free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) {
}
-@(require_results)
+@(private, require_results)
resize :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, alignment: uint) -> (res: []byte, err: runtime.Allocator_Error) {
assert(control != nil)
if ptr != nil && new_size == 0 {
@@ -689,6 +320,7 @@ resize :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, align
block_trim_used(control, block, adjust)
res = ([^]byte)(ptr)[:new_size]
+ sanitizer.address_unpoison(res)
if min_size < new_size {
to_zero := ([^]byte)(ptr)[min_size:new_size]
@@ -697,7 +329,7 @@ resize :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, align
return
}
-@(require_results)
+@(private, require_results)
resize_non_zeroed :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, alignment: uint) -> (res: []byte, err: runtime.Allocator_Error) {
assert(control != nil)
if ptr != nil && new_size == 0 {
@@ -736,3 +368,427 @@ resize_non_zeroed :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size:
res = ([^]byte)(ptr)[:new_size]
return
}
+
+/*
+ TLSF achieves O(1) cost for `alloc` and `free` operations by limiting
+ the search for a free block to a free list of guaranteed size
+ adequate to fulfill the request, combined with efficient free list
+ queries using bitmasks and architecture-specific bit-manipulation
+ routines.
+
+ NOTE: TLSF spec relies on ffs/fls returning a value in the range 0..31.
+*/
+
+@(private, require_results, no_sanitize_address)
+block_size :: proc "contextless" (block: ^Block_Header) -> (size: uint) {
+ return block.size &~ (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE)
+}
+
+@(private, no_sanitize_address)
+block_set_size :: proc "contextless" (block: ^Block_Header, size: uint) {
+ old_size := block.size
+ block.size = size | (old_size & (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE))
+}
+
+@(private, require_results, no_sanitize_address)
+block_is_last :: proc "contextless" (block: ^Block_Header) -> (is_last: bool) {
+ return block_size(block) == 0
+}
+
+@(private, require_results, no_sanitize_address)
+block_is_free :: proc "contextless" (block: ^Block_Header) -> (is_free: bool) {
+ return (block.size & BLOCK_HEADER_FREE) == BLOCK_HEADER_FREE
+}
+
+@(private, no_sanitize_address)
+block_set_free :: proc "contextless" (block: ^Block_Header) {
+ block.size |= BLOCK_HEADER_FREE
+}
+
+@(private, no_sanitize_address)
+block_set_used :: proc "contextless" (block: ^Block_Header) {
+ block.size &~= BLOCK_HEADER_FREE
+}
+
+@(private, require_results, no_sanitize_address)
+block_is_prev_free :: proc "contextless" (block: ^Block_Header) -> (is_prev_free: bool) {
+ return (block.size & BLOCK_HEADER_PREV_FREE) == BLOCK_HEADER_PREV_FREE
+}
+
+@(private, no_sanitize_address)
+block_set_prev_free :: proc "contextless" (block: ^Block_Header) {
+ block.size |= BLOCK_HEADER_PREV_FREE
+}
+
+@(private, no_sanitize_address)
+block_set_prev_used :: proc "contextless" (block: ^Block_Header) {
+ block.size &~= BLOCK_HEADER_PREV_FREE
+}
+
+@(private, require_results, no_sanitize_address)
+block_from_ptr :: proc(ptr: rawptr) -> (block_ptr: ^Block_Header) {
+ return (^Block_Header)(uintptr(ptr) - BLOCK_START_OFFSET)
+}
+
+@(private, require_results, no_sanitize_address)
+block_to_ptr :: proc(block: ^Block_Header) -> (ptr: rawptr) {
+ return rawptr(uintptr(block) + BLOCK_START_OFFSET)
+}
+
+// Return location of next block after block of given size.
+@(private, require_results, no_sanitize_address)
+offset_to_block :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) {
+ return (^Block_Header)(uintptr(ptr) + uintptr(size))
+}
+
+@(private, require_results, no_sanitize_address)
+offset_to_block_backwards :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) {
+ return (^Block_Header)(uintptr(ptr) - uintptr(size))
+}
+
+// Return location of previous block.
+@(private, require_results, no_sanitize_address)
+block_prev :: proc(block: ^Block_Header) -> (prev: ^Block_Header) {
+ assert(block_is_prev_free(block), "previous block must be free")
+
+ return block.prev_phys_block
+}
+
+// Return location of next existing block.
+@(private, require_results, no_sanitize_address)
+block_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) {
+ return offset_to_block(block_to_ptr(block), block_size(block) - BLOCK_HEADER_OVERHEAD)
+}
+
+// Link a new block with its physical neighbor, return the neighbor.
+@(private, require_results, no_sanitize_address)
+block_link_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) {
+ next = block_next(block)
+ next.prev_phys_block = block
+ return
+}
+
+@(private, no_sanitize_address)
+block_mark_as_free :: proc(block: ^Block_Header) {
+ // Link the block to the next block, first.
+ next := block_link_next(block)
+ block_set_prev_free(next)
+ block_set_free(block)
+}
+
+@(private, no_sanitize_address)
+block_mark_as_used :: proc(block: ^Block_Header, ) {
+ next := block_next(block)
+ block_set_prev_used(next)
+ block_set_used(block)
+}
+
+@(private, require_results, no_sanitize_address)
+align_up :: proc(x, align: uint) -> (aligned: uint) {
+ assert(0 == (align & (align - 1)), "must align to a power of two")
+ return (x + (align - 1)) &~ (align - 1)
+}
+
+@(private, require_results, no_sanitize_address)
+align_down :: proc(x, align: uint) -> (aligned: uint) {
+ assert(0 == (align & (align - 1)), "must align to a power of two")
+ return x - (x & (align - 1))
+}
+
+@(private, require_results, no_sanitize_address)
+align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) {
+ assert(0 == (align & (align - 1)), "must align to a power of two")
+ align_mask := uintptr(align) - 1
+ _ptr := uintptr(ptr)
+ _aligned := (_ptr + align_mask) &~ (align_mask)
+ return rawptr(_aligned)
+}
+
+// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
+@(private, require_results, no_sanitize_address)
+adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) {
+ if size == 0 {
+ return 0
+ }
+
+ // aligned size must not exceed `BLOCK_SIZE_MAX`, or we'll go out of bounds on `sl_bitmap`.
+ if aligned := align_up(size, align); aligned < BLOCK_SIZE_MAX {
+ adjusted = max(aligned, BLOCK_SIZE_MIN)
+ }
+ return
+}
+
+// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum.
+@(private, require_results, no_sanitize_address)
+adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: runtime.Allocator_Error) {
+ if size == 0 {
+ return 0, nil
+ }
+
+ // aligned size must not exceed `BLOCK_SIZE_MAX`, or we'll go out of bounds on `sl_bitmap`.
+ if aligned := align_up(size, align); aligned < BLOCK_SIZE_MAX {
+ adjusted = min(aligned, BLOCK_SIZE_MAX)
+ } else {
+ err = .Out_Of_Memory
+ }
+ return
+}
+
+// TLSF utility functions. In most cases these are direct translations of
+// the documentation in the research paper.
+
+@(optimization_mode="favor_size", private, require_results, no_sanitize_address)
+mapping_insert :: proc(size: uint) -> (fl, sl: i32) {
+ if size < SMALL_BLOCK_SIZE {
+ // Store small blocks in first list.
+ sl = i32(size) / (SMALL_BLOCK_SIZE / SL_INDEX_COUNT)
+ } else {
+ fl = fls_uint(size)
+ sl = i32(size >> (uint(fl) - TLSF_SL_INDEX_COUNT_LOG2)) ~ (1 << TLSF_SL_INDEX_COUNT_LOG2)
+ fl -= (FL_INDEX_SHIFT - 1)
+ }
+ return
+}
+
+@(optimization_mode="favor_size", private, require_results, no_sanitize_address)
+mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) {
+ rounded = size
+ if size >= SMALL_BLOCK_SIZE {
+ round := uint(1 << (uint(fls_uint(size) - TLSF_SL_INDEX_COUNT_LOG2))) - 1
+ rounded += round
+ }
+ return
+}
+
+// This version rounds up to the next block size (for allocations)
+@(optimization_mode="favor_size", private, require_results, no_sanitize_address)
+mapping_search :: proc(size: uint) -> (fl, sl: i32) {
+ return mapping_insert(mapping_round(size))
+}
+
+@(private, require_results, no_sanitize_address)
+search_suitable_block :: proc(control: ^Allocator, fli, sli: ^i32) -> (block: ^Block_Header) {
+ // First, search for a block in the list associated with the given fl/sl index.
+ fl := fli^; sl := sli^
+
+ sl_map := control.sl_bitmap[fli^] & (~u32(0) << uint(sl))
+ if sl_map == 0 {
+ // No block exists. Search in the next largest first-level list.
+ fl_map := control.fl_bitmap & (~u32(0) << uint(fl + 1))
+ if fl_map == 0 {
+ // No free blocks available, memory has been exhausted.
+ return {}
+ }
+
+ fl = ffs(fl_map)
+ fli^ = fl
+ sl_map = control.sl_bitmap[fl]
+ }
+ assert(sl_map != 0, "internal error - second level bitmap is null")
+ sl = ffs(sl_map)
+ sli^ = sl
+
+ // Return the first block in the free list.
+ return control.blocks[fl][sl]
+}
+
+// Remove a free block from the free list.
+@(private, no_sanitize_address)
+remove_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) {
+ prev := block.prev_free
+ next := block.next_free
+ assert(prev != nil, "prev_free can not be nil")
+ assert(next != nil, "next_free can not be nil")
+ next.prev_free = prev
+ prev.next_free = next
+
+ // If this block is the head of the free list, set new head.
+ if control.blocks[fl][sl] == block {
+ control.blocks[fl][sl] = next
+
+ // If the new head is nil, clear the bitmap
+ if next == &control.block_null {
+ control.sl_bitmap[fl] &~= (u32(1) << uint(sl))
+
+ // If the second bitmap is now empty, clear the fl bitmap
+ if control.sl_bitmap[fl] == 0 {
+ control.fl_bitmap &~= (u32(1) << uint(fl))
+ }
+ }
+ }
+}
+
+// Insert a free block into the free block list.
+@(private, no_sanitize_address)
+insert_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) {
+ current := control.blocks[fl][sl]
+ assert(current != nil, "free lists cannot have a nil entry")
+ assert(block != nil, "cannot insert a nil entry into the free list")
+ block.next_free = current
+ block.prev_free = &control.block_null
+ current.prev_free = block
+
+ assert(block_to_ptr(block) == align_ptr(block_to_ptr(block), ALIGN_SIZE), "block not properly aligned")
+
+ // Insert the new block at the head of the list, and mark the first- and second-level bitmaps appropriately.
+ control.blocks[fl][sl] = block
+ control.fl_bitmap |= (u32(1) << uint(fl))
+ control.sl_bitmap[fl] |= (u32(1) << uint(sl))
+}
+
+// Remove a given block from the free list.
+@(private, no_sanitize_address)
+block_remove :: proc(control: ^Allocator, block: ^Block_Header) {
+ fl, sl := mapping_insert(block_size(block))
+ remove_free_block(control, block, fl, sl)
+}
+
+// Insert a given block into the free list.
+@(private, no_sanitize_address)
+block_insert :: proc(control: ^Allocator, block: ^Block_Header) {
+ fl, sl := mapping_insert(block_size(block))
+ insert_free_block(control, block, fl, sl)
+}
+
+@(private, require_results, no_sanitize_address)
+block_can_split :: proc(block: ^Block_Header, size: uint) -> (can_split: bool) {
+ return block_size(block) >= size_of(Block_Header) + size
+}
+
+// Split a block into two, the second of which is free.
+@(private, require_results, no_sanitize_address)
+block_split :: proc(block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) {
+ // Calculate the amount of space left in the remaining block.
+ remaining = offset_to_block(block_to_ptr(block), size - BLOCK_HEADER_OVERHEAD)
+
+ remain_size := block_size(block) - (size + BLOCK_HEADER_OVERHEAD)
+
+ assert(block_to_ptr(remaining) == align_ptr(block_to_ptr(remaining), ALIGN_SIZE),
+ "remaining block not aligned properly")
+
+ assert(block_size(block) == remain_size + size + BLOCK_HEADER_OVERHEAD)
+ block_set_size(remaining, remain_size)
+ assert(block_size(remaining) >= BLOCK_SIZE_MIN, "block split with invalid size")
+
+ block_set_size(block, size)
+ block_mark_as_free(remaining)
+
+ return remaining
+}
+
+// Absorb a free block's storage into an adjacent previous free block.
+@(private, require_results, no_sanitize_address)
+block_absorb :: proc(prev: ^Block_Header, block: ^Block_Header) -> (absorbed: ^Block_Header) {
+ assert(!block_is_last(prev), "previous block can't be last")
+
+ // Note: Leaves flags untouched.
+ prev.size += block_size(block) + BLOCK_HEADER_OVERHEAD
+ _ = block_link_next(prev)
+ return prev
+}
+
+// Merge a just-freed block with an adjacent previous free block.
+@(private, require_results, no_sanitize_address)
+block_merge_prev :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) {
+ merged = block
+ if (block_is_prev_free(block)) {
+ prev := block_prev(block)
+ assert(prev != nil, "prev physical block can't be nil")
+ assert(block_is_free(prev), "prev block is not free though marked as such")
+ block_remove(control, prev)
+ merged = block_absorb(prev, block)
+ }
+ return merged
+}
+
+// Merge a just-freed block with an adjacent free block.
+@(private, require_results, no_sanitize_address)
+block_merge_next :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) {
+ merged = block
+ next := block_next(block)
+ assert(next != nil, "next physical block can't be nil")
+
+ if (block_is_free(next)) {
+ assert(!block_is_last(block), "previous block can't be last")
+ block_remove(control, next)
+ merged = block_absorb(block, next)
+ }
+ return merged
+}
+
+// Trim any trailing block space off the end of a free block, return to pool.
+@(private, no_sanitize_address)
+block_trim_free :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
+ assert(block_is_free(block), "block must be free")
+ if (block_can_split(block, size)) {
+ remaining_block := block_split(block, size)
+ _ = block_link_next(block)
+ block_set_prev_free(remaining_block)
+ block_insert(control, remaining_block)
+ }
+}
+
+// Trim any trailing block space off the end of a used block, return to pool.
+@(private, no_sanitize_address)
+block_trim_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) {
+ assert(!block_is_free(block), "Block must be used")
+ if (block_can_split(block, size)) {
+ // If the next block is free, we must coalesce.
+ remaining_block := block_split(block, size)
+ block_set_prev_used(remaining_block)
+
+ remaining_block = block_merge_next(control, remaining_block)
+ block_insert(control, remaining_block)
+ }
+}
+
+// Trim leading block space, return to pool.
+@(private, require_results, no_sanitize_address)
+block_trim_free_leading :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) {
+ remaining = block
+ if block_can_split(block, size) {
+ // We want the 2nd block.
+ remaining = block_split(block, size - BLOCK_HEADER_OVERHEAD)
+ block_set_prev_free(remaining)
+
+ _ = block_link_next(block)
+ block_insert(control, block)
+ }
+ return remaining
+}
+
+@(private, require_results, no_sanitize_address)
+block_locate_free :: proc(control: ^Allocator, size: uint) -> (block: ^Block_Header) {
+ fl, sl: i32
+ if size != 0 {
+ fl, sl = mapping_search(size)
+
+ /*
+ `mapping_search` can futz with the size, so for excessively large sizes it can sometimes wind up
+ with indices that are off the end of the block array. So, we protect against that here,
+ since this is the only call site of `mapping_search`. Note that we don't need to check `sl`,
+ as it comes from a modulo operation that guarantees it's always in range.
+ */
+ if fl < FL_INDEX_COUNT {
+ block = search_suitable_block(control, &fl, &sl)
+ }
+ }
+
+ if block != nil {
+ assert(block_size(block) >= size)
+ remove_free_block(control, block, fl, sl)
+ }
+ return block
+}
+
+@(private, require_results, no_sanitize_address)
+block_prepare_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (res: []byte, err: runtime.Allocator_Error) {
+ if block != nil {
+ assert(size != 0, "Size must be non-zero")
+ block_trim_free(control, block, size)
+ block_mark_as_used(block)
+ res = ([^]byte)(block_to_ptr(block))[:size]
+ sanitizer.address_unpoison(res)
+ }
+ return
+}
diff --git a/core/mem/tracking_allocator.odin b/core/mem/tracking_allocator.odin
index 263a1ca63..2250b566a 100644
--- a/core/mem/tracking_allocator.odin
+++ b/core/mem/tracking_allocator.odin
@@ -64,6 +64,7 @@ This procedure initializes the tracking allocator `t` with a backing allocator
specified with `backing_allocator`. The `internals_allocator` will used to
allocate the tracked data.
*/
+@(no_sanitize_address)
tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) {
t.backing = backing_allocator
t.allocation_map.allocator = internals_allocator
@@ -77,6 +78,7 @@ tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Alloc
/*
Destroy the tracking allocator.
*/
+@(no_sanitize_address)
tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) {
delete(t.allocation_map)
delete(t.bad_free_array)
@@ -90,6 +92,7 @@ This procedure clears the tracked data from a tracking allocator.
**Note**: This procedure clears only the current allocation data while keeping
the totals intact.
*/
+@(no_sanitize_address)
tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
sync.mutex_lock(&t.mutex)
clear(&t.allocation_map)
@@ -103,6 +106,7 @@ Reset the tracking allocator.
Reset all of a Tracking Allocator's allocation data back to zero.
*/
+@(no_sanitize_address)
tracking_allocator_reset :: proc(t: ^Tracking_Allocator) {
sync.mutex_lock(&t.mutex)
clear(&t.allocation_map)
@@ -124,6 +128,7 @@ Override Tracking_Allocator.bad_free_callback to have something else happen. For
example, you can use tracking_allocator_bad_free_callback_add_to_array to return
the tracking allocator to the old behavior, where the bad_free_array was used.
*/
+@(no_sanitize_address)
tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) {
runtime.print_caller_location(location)
runtime.print_string(" Tracking allocator error: Bad free of pointer ")
@@ -136,6 +141,7 @@ tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memor
Alternative behavior for a bad free: Store in `bad_free_array`. If you use this,
then you must make sure to check Tracking_Allocator.bad_free_array at some point.
*/
+@(no_sanitize_address)
tracking_allocator_bad_free_callback_add_to_array :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) {
append(&t.bad_free_array, Tracking_Allocator_Bad_Free_Entry {
memory = memory,
@@ -175,7 +181,7 @@ Example:
}
}
*/
-@(require_results)
+@(require_results, no_sanitize_address)
tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
return Allocator{
data = data,
@@ -183,6 +189,7 @@ tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
}
}
+@(no_sanitize_address)
tracking_allocator_proc :: proc(
allocator_data: rawptr,
mode: Allocator_Mode,
@@ -191,6 +198,7 @@ tracking_allocator_proc :: proc(
old_size: int,
loc := #caller_location,
) -> (result: []byte, err: Allocator_Error) {
+ @(no_sanitize_address)
track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
data.total_memory_allocated += i64(entry.size)
data.total_allocation_count += 1
@@ -200,6 +208,7 @@ tracking_allocator_proc :: proc(
}
}
+ @(no_sanitize_address)
track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) {
data.total_memory_freed += i64(entry.size)
data.total_free_count += 1
diff --git a/core/mem/virtual/arena.odin b/core/mem/virtual/arena.odin
index 4fc2e0e35..4e1cc2466 100644
--- a/core/mem/virtual/arena.odin
+++ b/core/mem/virtual/arena.odin
@@ -3,6 +3,8 @@ package mem_virtual
import "core:mem"
import "core:sync"
+import "base:sanitizer"
+
Arena_Kind :: enum uint {
Growing = 0, // Chained memory blocks (singly linked list).
Static = 1, // Fixed reservation sized.
@@ -43,7 +45,7 @@ DEFAULT_ARENA_STATIC_RESERVE_SIZE :: mem.Gigabyte when size_of(uintptr) == 8 els
// Initialization of an `Arena` to be a `.Growing` variant.
// A growing arena is a linked list of `Memory_Block`s allocated with virtual memory.
-@(require_results)
+@(require_results, no_sanitize_address)
arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (err: Allocator_Error) {
arena.kind = .Growing
arena.curr_block = memory_block_alloc(0, reserved, {}) or_return
@@ -53,24 +55,26 @@ arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING
if arena.minimum_block_size == 0 {
arena.minimum_block_size = reserved
}
+ sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
return
}
// Initialization of an `Arena` to be a `.Static` variant.
// A static arena contains a single `Memory_Block` allocated with virtual memory.
-@(require_results)
+@(require_results, no_sanitize_address)
arena_init_static :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_STATIC_RESERVE_SIZE, commit_size: uint = DEFAULT_ARENA_STATIC_COMMIT_SIZE) -> (err: Allocator_Error) {
arena.kind = .Static
arena.curr_block = memory_block_alloc(commit_size, reserved, {}) or_return
arena.total_used = 0
arena.total_reserved = arena.curr_block.reserved
+ sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
return
}
// Initialization of an `Arena` to be a `.Buffer` variant.
// A buffer arena contains single `Memory_Block` created from a user provided []byte.
-@(require_results)
+@(require_results, no_sanitize_address)
arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Error) {
if len(buffer) < size_of(Memory_Block) {
return .Out_Of_Memory
@@ -78,7 +82,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
arena.kind = .Buffer
- mem.zero_slice(buffer)
+ sanitizer.address_poison(buffer[:])
block_base := raw_data(buffer)
block := (^Memory_Block)(block_base)
@@ -94,7 +98,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
}
// Allocates memory from the provided arena.
-@(require_results)
+@(require_results, no_sanitize_address)
arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc)
@@ -158,10 +162,13 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l
data, err = alloc_from_memory_block(arena.curr_block, size, alignment, default_commit_size=0)
arena.total_used = arena.curr_block.used
}
+
+ sanitizer.address_unpoison(data)
return
}
// Resets the memory of a Static or Buffer arena to a specific `position` (offset) and zeroes the previously used memory.
+@(no_sanitize_address)
arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) -> bool {
sync.mutex_guard(&arena.mutex)
@@ -175,6 +182,7 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location)
mem.zero_slice(arena.curr_block.base[arena.curr_block.used:][:prev_pos-pos])
}
arena.total_used = arena.curr_block.used
+ sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
return true
} else if pos == 0 {
arena.total_used = 0
@@ -184,6 +192,7 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location)
}
// Frees the last memory block of a Growing Arena
+@(no_sanitize_address)
arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) {
if free_block := arena.curr_block; free_block != nil {
assert(arena.kind == .Growing, "expected a .Growing arena", loc)
@@ -191,11 +200,13 @@ arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_locat
arena.total_reserved -= free_block.reserved
arena.curr_block = free_block.prev
+ sanitizer.address_poison(free_block.base[:free_block.committed])
memory_block_dealloc(free_block)
}
}
// Deallocates all but the first memory block of the arena and resets the allocator's usage to 0.
+@(no_sanitize_address)
arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
switch arena.kind {
case .Growing:
@@ -208,7 +219,9 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
if arena.curr_block != nil {
curr_block_used := int(arena.curr_block.used)
arena.curr_block.used = 0
+ sanitizer.address_unpoison(arena.curr_block.base[:curr_block_used])
mem.zero(arena.curr_block.base, curr_block_used)
+ sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed])
}
arena.total_used = 0
case .Static, .Buffer:
@@ -219,6 +232,7 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
// Frees all of the memory allocated by the arena and zeros all of the values of an arena.
// A buffer based arena does not `delete` the provided `[]byte` bufffer.
+@(no_sanitize_address)
arena_destroy :: proc(arena: ^Arena, loc := #caller_location) {
sync.mutex_guard(&arena.mutex)
switch arena.kind {
@@ -250,7 +264,7 @@ arena_static_bootstrap_new :: proc{
}
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
-@(require_results)
+@(require_results, no_sanitize_address)
arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
bootstrap: Arena
bootstrap.kind = .Growing
@@ -266,13 +280,13 @@ arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintp
}
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
-@(require_results)
+@(require_results, no_sanitize_address)
arena_growing_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
return arena_growing_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), minimum_block_size)
}
// Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy.
-@(require_results)
+@(require_results, no_sanitize_address)
arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
bootstrap: Arena
bootstrap.kind = .Static
@@ -288,19 +302,20 @@ arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintpt
}
// Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy.
-@(require_results)
+@(require_results, no_sanitize_address)
arena_static_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
return arena_static_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), reserved)
}
// Create an `Allocator` from the provided `Arena`
-@(require_results)
+@(require_results, no_sanitize_address)
arena_allocator :: proc(arena: ^Arena) -> mem.Allocator {
return mem.Allocator{arena_allocator_proc, arena}
}
// The allocator procedure used by an `Allocator` produced by `arena_allocator`
+@(no_sanitize_address)
arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
size, alignment: int,
old_memory: rawptr, old_size: int,
@@ -334,6 +349,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
if size < old_size {
// shrink data in-place
data = old_data[:size]
+ sanitizer.address_poison(old_data[size:old_size])
return
}
@@ -347,6 +363,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
_ = alloc_from_memory_block(block, new_end - old_end, 1, default_commit_size=arena.default_commit_size) or_return
arena.total_used += block.used - prev_used
data = block.base[start:new_end]
+ sanitizer.address_unpoison(data)
return
}
}
@@ -357,6 +374,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
return
}
copy(new_memory, old_data[:old_size])
+ sanitizer.address_poison(old_data[:old_size])
return new_memory, nil
case .Query_Features:
set := (^mem.Allocator_Mode_Set)(old_memory)
@@ -382,7 +400,7 @@ Arena_Temp :: struct {
}
// Begins the section of temporary arena memory.
-@(require_results)
+@(require_results, no_sanitize_address)
arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) {
assert(arena != nil, "nil arena", loc)
sync.mutex_guard(&arena.mutex)
@@ -397,6 +415,7 @@ arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena
}
// Ends the section of temporary arena memory by resetting the memory to the stored position.
+@(no_sanitize_address)
arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
assert(temp.arena != nil, "nil arena", loc)
arena := temp.arena
@@ -432,6 +451,7 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
}
// Ignore the use of a `arena_temp_begin` entirely by __not__ resetting to the stored position.
+@(no_sanitize_address)
arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
assert(temp.arena != nil, "nil arena", loc)
arena := temp.arena
@@ -442,6 +462,7 @@ arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
}
// Asserts that all uses of `Arena_Temp` has been used by an `Arena`
+@(no_sanitize_address)
arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) {
assert(arena.temp_count == 0, "Arena_Temp not been ended", loc)
}
diff --git a/core/mem/virtual/virtual.odin b/core/mem/virtual/virtual.odin
index 4afc33813..031fb721a 100644
--- a/core/mem/virtual/virtual.odin
+++ b/core/mem/virtual/virtual.odin
@@ -2,6 +2,7 @@ package mem_virtual
import "core:mem"
import "base:intrinsics"
+import "base:sanitizer"
import "base:runtime"
_ :: runtime
@@ -14,27 +15,33 @@ platform_memory_init :: proc() {
Allocator_Error :: mem.Allocator_Error
-@(require_results)
+@(require_results, no_sanitize_address)
reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
return _reserve(size)
}
+@(no_sanitize_address)
commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
+ sanitizer.address_unpoison(data, size)
return _commit(data, size)
}
-@(require_results)
+@(require_results, no_sanitize_address)
reserve_and_commit :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
data = reserve(size) or_return
commit(raw_data(data), size) or_return
return
}
+@(no_sanitize_address)
decommit :: proc "contextless" (data: rawptr, size: uint) {
+ sanitizer.address_poison(data, size)
_decommit(data, size)
}
+@(no_sanitize_address)
release :: proc "contextless" (data: rawptr, size: uint) {
+ sanitizer.address_unpoison(data, size)
_release(data, size)
}
@@ -46,13 +53,11 @@ Protect_Flag :: enum u32 {
Protect_Flags :: distinct bit_set[Protect_Flag; u32]
Protect_No_Access :: Protect_Flags{}
+@(no_sanitize_address)
protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool {
return _protect(data, size, flags)
}
-
-
-
Memory_Block :: struct {
prev: ^Memory_Block,
base: [^]byte,
@@ -66,13 +71,13 @@ Memory_Block_Flag :: enum u32 {
Memory_Block_Flags :: distinct bit_set[Memory_Block_Flag; u32]
-@(private="file", require_results)
+@(private="file", require_results, no_sanitize_address)
align_formula :: #force_inline proc "contextless" (size, align: uint) -> uint {
result := size + align-1
return result - result%align
}
-@(require_results)
+@(require_results, no_sanitize_address)
memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags: Memory_Block_Flags = {}) -> (block: ^Memory_Block, err: Allocator_Error) {
page_size := DEFAULT_PAGE_SIZE
assert(mem.is_power_of_two(uintptr(page_size)))
@@ -116,8 +121,9 @@ memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags
return &pmblock.block, nil
}
-@(require_results)
+@(require_results, no_sanitize_address)
alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint, default_commit_size: uint = 0) -> (data: []byte, err: Allocator_Error) {
+ @(no_sanitize_address)
calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint {
alignment_offset := uint(0)
ptr := uintptr(block.base[block.used:])
@@ -128,6 +134,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint,
return alignment_offset
}
+ @(no_sanitize_address)
do_commit_if_necessary :: proc(block: ^Memory_Block, size: uint, default_commit_size: uint) -> (err: Allocator_Error) {
if block.committed - block.used < size {
pmblock := (^Platform_Memory_Block)(block)
@@ -172,10 +179,12 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint,
data = block.base[block.used+alignment_offset:][:min_size]
block.used += size
+ sanitizer.address_unpoison(data)
return
}
+@(no_sanitize_address)
memory_block_dealloc :: proc(block_to_free: ^Memory_Block) {
if block := (^Platform_Memory_Block)(block_to_free); block != nil {
platform_memory_free(block)
diff --git a/core/mem/virtual/virtual_platform.odin b/core/mem/virtual/virtual_platform.odin
index 31e9cfca8..c9dde4e9d 100644
--- a/core/mem/virtual/virtual_platform.odin
+++ b/core/mem/virtual/virtual_platform.odin
@@ -7,6 +7,7 @@ Platform_Memory_Block :: struct {
reserved: uint,
}
+@(no_sanitize_address)
platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (block: ^Platform_Memory_Block, err: Allocator_Error) {
to_commit, to_reserve := to_commit, to_reserve
to_reserve = max(to_commit, to_reserve)
@@ -26,12 +27,14 @@ platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (bl
}
+@(no_sanitize_address)
platform_memory_free :: proc "contextless" (block: ^Platform_Memory_Block) {
if block != nil {
release(block, block.reserved)
}
}
+@(no_sanitize_address)
platform_memory_commit :: proc "contextless" (block: ^Platform_Memory_Block, to_commit: uint) -> (err: Allocator_Error) {
if to_commit < block.committed {
return nil
diff --git a/core/mem/virtual/virtual_windows.odin b/core/mem/virtual/virtual_windows.odin
index acd30ae33..0da8498d5 100644
--- a/core/mem/virtual/virtual_windows.odin
+++ b/core/mem/virtual/virtual_windows.odin
@@ -83,6 +83,8 @@ foreign Kernel32 {
dwNumberOfBytesToMap: uint,
) -> rawptr ---
}
+
+@(no_sanitize_address)
_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
result := VirtualAlloc(nil, size, MEM_RESERVE, PAGE_READWRITE)
if result == nil {
@@ -93,6 +95,7 @@ _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Err
return
}
+@(no_sanitize_address)
_commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
result := VirtualAlloc(data, size, MEM_COMMIT, PAGE_READWRITE)
if result == nil {
@@ -107,12 +110,18 @@ _commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
}
return nil
}
+
+@(no_sanitize_address)
_decommit :: proc "contextless" (data: rawptr, size: uint) {
VirtualFree(data, size, MEM_DECOMMIT)
}
+
+@(no_sanitize_address)
_release :: proc "contextless" (data: rawptr, size: uint) {
VirtualFree(data, 0, MEM_RELEASE)
}
+
+@(no_sanitize_address)
_protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool {
pflags: u32
pflags = PAGE_NOACCESS
@@ -136,7 +145,7 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags)
}
-
+@(no_sanitize_address)
_platform_memory_init :: proc() {
sys_info: SYSTEM_INFO
GetSystemInfo(&sys_info)
@@ -147,6 +156,7 @@ _platform_memory_init :: proc() {
}
+@(no_sanitize_address)
_map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) {
page_flags: u32
if flags == {.Read} {
diff --git a/core/net/common.odin b/core/net/common.odin
index 12add8225..fb6b17511 100644
--- a/core/net/common.odin
+++ b/core/net/common.odin
@@ -53,8 +53,6 @@ ODIN_NET_TCP_NODELAY_DEFAULT :: #config(ODIN_NET_TCP_NODELAY_DEFAULT, true)
Maybe :: runtime.Maybe
Network_Error :: union #shared_nil {
- General_Error,
- Platform_Error,
Create_Socket_Error,
Dial_Error,
Listen_Error,
@@ -65,6 +63,7 @@ Network_Error :: union #shared_nil {
TCP_Recv_Error,
UDP_Recv_Error,
Shutdown_Error,
+ Interfaces_Error,
Socket_Option_Error,
Set_Blocking_Error,
Parse_Endpoint_Error,
@@ -74,14 +73,13 @@ Network_Error :: union #shared_nil {
#assert(size_of(Network_Error) == 8)
-General_Error :: enum u32 {
- None = 0,
- Unable_To_Enumerate_Network_Interfaces = 1,
+Interfaces_Error :: enum u32 {
+ None,
+ Unable_To_Enumerate_Network_Interfaces,
+ Allocation_Failure,
+ Unknown,
}
-// `Platform_Error` is used to wrap errors returned by the different platforms that don't fit a common error.
-Platform_Error :: enum u32 {}
-
Parse_Endpoint_Error :: enum u32 {
None = 0,
Bad_Port = 1,
@@ -109,7 +107,7 @@ TCP_Options :: struct {
no_delay: bool,
}
-default_tcp_options := TCP_Options {
+DEFAULT_TCP_OPTIONS :: TCP_Options {
no_delay = ODIN_NET_TCP_NODELAY_DEFAULT,
}
diff --git a/core/net/errors.odin b/core/net/errors.odin
new file mode 100644
index 000000000..53c936a66
--- /dev/null
+++ b/core/net/errors.odin
@@ -0,0 +1,275 @@
+package net
+
+/*
+Retrieve a platform specific error code, for when the categorized cross-platform errors are not enough.
+
+Platforms specific returns:
+- Darwin: `posix.Errno` (`core:sys/posix`)
+- Linux: `linux.Errno` (`core:sys/linux`)
+- FreeBSD: `freebsd.Errno` (`core:sys/freebsd`)
+- Windows: `windows.System_Error` (`core:sys/windows`)
+*/
+@(require_results)
+last_platform_error :: proc() -> i32 {
+ return _last_platform_error()
+}
+
+/*
+Retrieve a stringified version of the last platform error.
+*/
+@(require_results)
+last_platform_error_string :: proc() -> string {
+ return _last_platform_error_string()
+}
+
+set_last_platform_error :: proc(err: i32) {
+ _set_last_platform_error(err)
+}
+
+Create_Socket_Error :: enum i32 {
+ None,
+ // No network connection, or the network stack is not initialized.
+ Network_Unreachable,
+ // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
+ Insufficient_Resources,
+ // Invalid/unsupported family or protocol.
+ Invalid_Argument,
+ // The user has no permission to create a socket of this type and/or protocol.
+ Insufficient_Permissions,
+
+ // An error unable to be categorized in above categories, `last_platform_error` may have more info.
+ Unknown,
+}
+
+Dial_Error :: enum i32 {
+ None,
+ // No network connection, or the network stack is not initialized.
+ Network_Unreachable,
+ // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
+ Insufficient_Resources,
+ // Invalid endpoint and/or options.
+ Invalid_Argument,
+ // An attempt was made to connect to a broadcast socket on a socket that doesn't support it.
+ Broadcast_Not_Supported,
+ // The socket is already connected.
+ Already_Connected,
+ // The socket is already in the progress of making a connection.
+ Already_Connecting,
+ // The address is already in use.
+ Address_In_Use,
+ // Could not reach the remote host.
+ Host_Unreachable,
+ // The remote host refused the connection or isn't listening.
+ Refused,
+ // The connection was reset by the remote host.
+ Reset,
+ // Timed out before making a connection.
+ Timeout,
+ // Non-blocking socket that would need to block waiting to connect.
+ Would_Block,
+ // Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
+ Interrupted,
+ // Endpoint given without a port, which is required.
+ Port_Required,
+
+ // An error unable to be categorized in above categories, `last_platform_error` may have more info.
+ Unknown,
+}
+
+Bind_Error :: enum i32 {
+ None,
+ // No network connection, or the network stack is not initialized.
+ Network_Unreachable,
+ // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
+ Insufficient_Resources,
+ // Invalid socket or endpoint, or invalid combination of the two.
+ Invalid_Argument,
+ // The socket is already bound to an address.
+ Already_Bound,
+ // The address is protected and the current user has insufficient permissions to access it.
+ Insufficient_Permissions_For_Address,
+ // The address is already in use.
+ Address_In_Use,
+
+ // An error unable to be categorized in above categories, `last_platform_error` may have more info.
+ Unknown,
+}
+
+Listen_Error :: enum i32 {
+ None,
+ // No network connection, or the network stack is not initialized.
+ Network_Unreachable,
+ // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
+ Insufficient_Resources,
+ // The socket or backlog is invalid.
+ Invalid_Argument,
+ // The socket is valid, but does not support listening.
+ Unsupported_Socket,
+ // The socket is already connected.
+ Already_Connected,
+ // The address is already in use.
+ Address_In_Use,
+
+ // An error unable to be categorized in above categories, `last_platform_error` may have more info.
+ Unknown,
+}
+
+Accept_Error :: enum i32 {
+ None,
+ // No network connection, or the network stack is not initialized.
+ Network_Unreachable,
+ // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
+ Insufficient_Resources,
+ // Invalid socket, or options.
+ Invalid_Argument,
+ // The given socket does not support accepting connections.
+ Unsupported_Socket,
+ // accept called on a socket which is not listening.
+ Not_Listening,
+ // A connection arrived but was closed while in the listen queue.
+ Aborted,
+ // Timed out before being able to accept a connection.
+ Timeout,
+ // Non-blocking socket that would need to block waiting for a connection.
+ Would_Block,
+ // Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
+ Interrupted,
+
+ // An error unable to be categorized in above categories, `last_platform_error` may have more info.
+ Unknown,
+}
+
+TCP_Recv_Error :: enum i32 {
+ None,
+ // No network connection, or the network stack is not initialized.
+ Network_Unreachable,
+ // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
+ Insufficient_Resources,
+ // Invalid socket or buffer given.
+ Invalid_Argument,
+ // The socket is not connected.
+ Not_Connected,
+ // Connection was closed/broken/shutdown while receiving data.
+ Connection_Closed,
+ // Timed out before being able to receive any data.
+ Timeout,
+ // Non-blocking socket that would need to block waiting on data.
+ Would_Block,
+ // Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
+ Interrupted,
+
+ // An error unable to be categorized in above categories, `last_platform_error` may have more info.
+ Unknown,
+}
+
+UDP_Recv_Error :: enum i32 {
+ None,
+ // No network connection, or the network stack is not initialized.
+ Network_Unreachable,
+ // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
+ Insufficient_Resources,
+ // Invalid socket or buffer given.
+ Invalid_Argument,
+ // "Connection" was refused by remote, or closed/broken/shutdown while receiving data.
+ Connection_Refused,
+ // Timed out before being able to receive any data.
+ Timeout,
+ // Non-blocking socket that would need to block waiting on data.
+ Would_Block,
+ // Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
+ Interrupted,
+ // Linux and UDP only: indicates the buffer was too small to receive all data, and the excess is truncated and discarded.
+ Excess_Truncated,
+
+ // An error unable to be categorized in above categories, `last_platform_error` may have more info.
+ Unknown,
+}
+
+TCP_Send_Error :: enum i32 {
+ None,
+ // No network connection, or the network stack is not initialized.
+ Network_Unreachable,
+ // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
+ Insufficient_Resources,
+ // Invalid socket or buffer given.
+ Invalid_Argument,
+ // Connection was closed/broken/shutdown while receiving data.
+ Connection_Closed,
+ // The socket is not connected.
+ Not_Connected,
+ // Could not reach the remote host.
+ Host_Unreachable,
+ // Timed out before being able to send any data.
+ Timeout,
+ // Non-blocking socket that would need to block waiting on the remote to be able to receive the data.
+ Would_Block,
+ // Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
+ Interrupted,
+
+ // An error unable to be categorized in above categories, `last_platform_error` may have more info.
+ Unknown,
+}
+
+UDP_Send_Error :: enum i32 {
+ None,
+ // No network connection, or the network stack is not initialized.
+ Network_Unreachable,
+ // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
+ Insufficient_Resources,
+ // Invalid socket or buffer given.
+ Invalid_Argument,
+ // Could not reach the remote host.
+ Host_Unreachable,
+ // "Connection" was refused by remote, or closed/broken/shutdown while sending data.
+ Connection_Refused,
+ // Timed out before being able to send any data.
+ Timeout,
+ // Non-blocking socket that would need to block waiting on the remote to be able to receive the data.
+ Would_Block,
+ // Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows.
+ Interrupted,
+
+ // An error unable to be categorized in above categories, `last_platform_error` may have more info.
+ Unknown,
+}
+
+Shutdown_Error :: enum i32 {
+ None,
+ // No network connection, or the network stack is not initialized.
+ Network_Unreachable,
+ // Socket is invalid or not connected, or the manner given is invalid.
+ Invalid_Argument,
+ // Connection was closed/aborted/shutdown.
+ Connection_Closed,
+
+ // An error unable to be categorized in above categories, `last_platform_error` may have more info.
+ Unknown,
+}
+
+Socket_Option_Error :: enum i32 {
+ None,
+ // No network connection, or the network stack is not initialized.
+ Network_Unreachable,
+ // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given.
+ Insufficient_Resources,
+ // Socket is invalid, not connected, or the connection has been closed/reset/shutdown.
+ Invalid_Socket,
+ // Unknown or unsupported option for the socket.
+ Invalid_Option,
+ // Invalid level or value.
+ Invalid_Value,
+
+ // An error unable to be categorized in above categories, `last_platform_error` may have more info.
+ Unknown,
+}
+
+Set_Blocking_Error :: enum i32 {
+ None,
+ // No network connection, or the network stack is not initialized.
+ Network_Unreachable,
+ // Socket is invalid.
+ Invalid_Argument,
+
+ // An error unable to be categorized in above categories, `last_platform_error` may have more info.
+ Unknown,
+}
diff --git a/core/net/errors_darwin.odin b/core/net/errors_darwin.odin
index ccf1e0f7f..7aaa220e9 100644
--- a/core/net/errors_darwin.odin
+++ b/core/net/errors_darwin.odin
@@ -20,192 +20,232 @@ package net
Feoramund: FreeBSD platform code
*/
-import "core:c"
+import "core:reflect"
import "core:sys/posix"
-@(private)
-ESHUTDOWN :: 58
-
-Create_Socket_Error :: enum c.int {
- None = 0,
- Family_Not_Supported_For_This_Socket = c.int(posix.EAFNOSUPPORT),
- No_Socket_Descriptors_Available = c.int(posix.EMFILE),
- No_Buffer_Space_Available = c.int(posix.ENOBUFS),
- No_Memory_Available = c.int(posix.ENOMEM),
- Protocol_Unsupported_By_System = c.int(posix.EPROTONOSUPPORT),
- Wrong_Protocol_For_Socket = c.int(posix.EPROTONOSUPPORT),
- Family_And_Socket_Type_Mismatch = c.int(posix.EPROTONOSUPPORT),
+_last_platform_error :: proc() -> i32 {
+ return i32(posix.errno())
}
-Dial_Error :: enum c.int {
- None = 0,
- Port_Required = -1, // Attempted to dial an endpointing without a port being set.
-
- Address_In_Use = c.int(posix.EADDRINUSE),
- In_Progress = c.int(posix.EINPROGRESS),
- Cannot_Use_Any_Address = c.int(posix.EADDRNOTAVAIL),
- Wrong_Family_For_Socket = c.int(posix.EAFNOSUPPORT),
- Refused = c.int(posix.ECONNREFUSED),
- Is_Listening_Socket = c.int(posix.EACCES),
- Already_Connected = c.int(posix.EISCONN),
- Network_Unreachable = c.int(posix.ENETUNREACH), // Device is offline
- Host_Unreachable = c.int(posix.EHOSTUNREACH), // Remote host cannot be reached
- No_Buffer_Space_Available = c.int(posix.ENOBUFS),
- Not_Socket = c.int(posix.ENOTSOCK),
- Timeout = c.int(posix.ETIMEDOUT),
-
- // TODO: we may need special handling for this; maybe make a socket a struct with metadata?
- Would_Block = c.int(posix.EWOULDBLOCK),
+_last_platform_error_string :: proc() -> string {
+ description, _ := reflect.enum_name_from_value(posix.errno())
+ return description
}
-Bind_Error :: enum c.int {
- None = 0,
- Privileged_Port_Without_Root = -1, // Attempted to bind to a port less than 1024 without root access.
-
- Address_In_Use = c.int(posix.EADDRINUSE), // Another application is currently bound to this endpoint.
- Given_Nonlocal_Address = c.int(posix.EADDRNOTAVAIL), // The address is not a local address on this machine.
- Broadcast_Disabled = c.int(posix.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
- Address_Family_Mismatch = c.int(posix.EFAULT), // The address family of the address does not match that of the socket.
- Already_Bound = c.int(posix.EINVAL), // The socket is already bound to an address.
- No_Ports_Available = c.int(posix.ENOBUFS), // There are not enough ephemeral ports available.
+_set_last_platform_error :: proc(err: i32) {
+ posix.errno(posix.Errno(err))
}
-Listen_Error :: enum c.int {
- None = 0,
- Address_In_Use = c.int(posix.EADDRINUSE),
- Already_Connected = c.int(posix.EISCONN),
- No_Socket_Descriptors_Available = c.int(posix.EMFILE),
- No_Buffer_Space_Available = c.int(posix.ENOBUFS),
- Nonlocal_Address = c.int(posix.EADDRNOTAVAIL),
- Not_Socket = c.int(posix.ENOTSOCK),
- Listening_Not_Supported_For_This_Socket = c.int(posix.EOPNOTSUPP),
+_create_socket_error :: proc() -> Create_Socket_Error {
+ #partial switch posix.errno() {
+ case .EMFILE, .ENOBUFS, .ENOMEM, .EPROTONOSUPPORT, .EISCONN, .ENFILE:
+ return .Insufficient_Resources
+ case .EAFNOSUPPORT, .EPROTOTYPE:
+ return .Invalid_Argument
+ case .EACCES:
+ return .Insufficient_Permissions
+ case:
+ return .Unknown
+ }
}
-Accept_Error :: enum c.int {
- None = 0,
- // TODO(tetra): Is this error actually possible here? Or is like Linux, in which case we can remove it.
- Reset = c.int(posix.ECONNRESET),
- Not_Listening = c.int(posix.EINVAL),
- No_Socket_Descriptors_Available_For_Client_Socket = c.int(posix.EMFILE),
- No_Buffer_Space_Available = c.int(posix.ENOBUFS),
- Not_Socket = c.int(posix.ENOTSOCK),
- Not_Connection_Oriented_Socket = c.int(posix.EOPNOTSUPP),
-
- // TODO: we may need special handling for this; maybe make a socket a struct with metadata?
- Would_Block = c.int(posix.EWOULDBLOCK),
+_dial_error :: proc() -> Dial_Error {
+ #partial switch posix.errno() {
+ case .ENOBUFS:
+ return .Insufficient_Resources
+ case .EAFNOSUPPORT, .EBADF, .EFAULT, .EINVAL, .ENOTSOCK, .EPROTOTYPE, .EADDRNOTAVAIL:
+ return .Invalid_Argument
+ case .EISCONN:
+ return .Already_Connected
+ case .EALREADY:
+ return .Already_Connecting
+ case .EADDRINUSE:
+ return .Address_In_Use
+ case .ENETDOWN:
+ return .Network_Unreachable
+ case .EHOSTUNREACH:
+ return .Host_Unreachable
+ case .ECONNREFUSED:
+ return .Refused
+ case .ECONNRESET:
+ return .Reset
+ case .ETIMEDOUT:
+ return .Timeout
+ case .EINPROGRESS:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case .EACCES:
+ return .Broadcast_Not_Supported
+ case:
+ return .Unknown
+ }
}
-TCP_Recv_Error :: enum c.int {
- None = 0,
- Shutdown = ESHUTDOWN,
- Not_Connected = c.int(posix.ENOTCONN),
-
- // TODO(tetra): Is this error actually possible here?
- Connection_Broken = c.int(posix.ENETRESET),
- Not_Socket = c.int(posix.ENOTSOCK),
- Aborted = c.int(posix.ECONNABORTED),
-
- // TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them?
- Connection_Closed = c.int(posix.ECONNRESET),
- Offline = c.int(posix.ENETDOWN),
- Host_Unreachable = c.int(posix.EHOSTUNREACH),
- Interrupted = c.int(posix.EINTR),
-
- // NOTE: No, really. Presumably this means something different for nonblocking sockets...
- Timeout = c.int(posix.EWOULDBLOCK),
+_bind_error :: proc() -> Bind_Error {
+ #partial switch posix.errno() {
+ case .EADDRNOTAVAIL, .EAFNOSUPPORT, .EBADF, .EDESTADDRREQ, .EFAULT, .ENOTSOCK, .EOPNOTSUPP:
+ return .Invalid_Argument
+ case .EINVAL:
+ return .Already_Bound
+ case .EACCES:
+ return .Insufficient_Permissions_For_Address
+ case .EADDRINUSE:
+ return .Address_In_Use
+ case:
+ return .Unknown
+ }
}
-UDP_Recv_Error :: enum c.int {
- None = 0,
- Buffer_Too_Small = c.int(posix.EMSGSIZE), // The buffer is too small to fit the entire message, and the message was truncated. When this happens, the rest of message is lost.
- Not_Socket = c.int(posix.ENOTSOCK), // The so-called socket is not an open socket.
- Not_Descriptor = c.int(posix.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
- Bad_Buffer = c.int(posix.EFAULT), // The buffer did not point to a valid location in memory.
- Interrupted = c.int(posix.EINTR), // A signal occurred before any data was transmitted. See signal(7).
-
- // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
- // NOTE: No, really. Presumably this means something different for nonblocking sockets...
- Timeout = c.int(posix.EWOULDBLOCK),
- Socket_Not_Bound = c.int(posix.EINVAL), // The socket must be bound for this operation, but isn't.
+_listen_error :: proc() -> Listen_Error {
+ #partial switch posix.errno() {
+ case .EBADF, .ENOTSOCK:
+ return .Invalid_Argument
+ case .EDESTADDRREQ, .EOPNOTSUPP:
+ return .Unsupported_Socket
+ case .EINVAL:
+ return .Already_Connected
+ case:
+ return .Unknown
+ }
}
-TCP_Send_Error :: enum c.int {
- None = 0,
-
- Aborted = c.int(posix.ECONNABORTED),
- Connection_Closed = c.int(posix.ECONNRESET),
- Not_Connected = c.int(posix.ENOTCONN),
- Shutdown = ESHUTDOWN,
-
- // The send queue was full.
- // This is usually a transient issue.
- //
- // This also shouldn't normally happen on Linux, as data is dropped if it
- // doesn't fit in the send queue.
- No_Buffer_Space_Available = c.int(posix.ENOBUFS),
- Offline = c.int(posix.ENETDOWN),
- Host_Unreachable = c.int(posix.EHOSTUNREACH),
- Interrupted = c.int(posix.EINTR), // A signal occurred before any data was transmitted. See signal(7).
-
- // NOTE: No, really. Presumably this means something different for nonblocking sockets...
- // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
- Timeout = c.int(posix.EWOULDBLOCK),
- Not_Socket = c.int(posix.ENOTSOCK), // The so-called socket is not an open socket.
+_accept_error :: proc() -> Accept_Error {
+ #partial switch posix.errno() {
+ case .EMFILE, .ENFILE, .ENOMEM:
+ return .Insufficient_Resources
+ case .EBADF, .ENOTSOCK, .EFAULT:
+ return .Invalid_Argument
+ case .EOPNOTSUPP:
+ return .Unsupported_Socket
+ case .ECONNABORTED:
+ return .Aborted
+ case .EWOULDBLOCK:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case:
+ return .Unknown
+ }
}
-// TODO
-UDP_Send_Error :: enum c.int {
- None = 0,
- Message_Too_Long = c.int(posix.EMSGSIZE), // The message is larger than the maximum UDP packet size. No data was sent.
-
- // TODO: not sure what the exact circumstances for this is yet
- Network_Unreachable = c.int(posix.ENETUNREACH),
- No_Outbound_Ports_Available = c.int(posix.EAGAIN), // There are no more emphemeral outbound ports available to bind the socket to, in order to send.
-
- // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
- // NOTE: No, really. Presumably this means something different for nonblocking sockets...
- Timeout = c.int(posix.EWOULDBLOCK),
- Not_Socket = c.int(posix.ENOTSOCK), // The so-called socket is not an open socket.
- Not_Descriptor = c.int(posix.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
- Bad_Buffer = c.int(posix.EFAULT), // The buffer did not point to a valid location in memory.
- Interrupted = c.int(posix.EINTR), // A signal occurred before any data was transmitted. See signal(7).
-
- // The send queue was full.
- // This is usually a transient issue.
- //
- // This also shouldn't normally happen on Linux, as data is dropped if it
- // doesn't fit in the send queue.
- No_Buffer_Space_Available = c.int(posix.ENOBUFS),
- No_Memory_Available = c.int(posix.ENOMEM), // No memory was available to properly manage the send queue.
+_tcp_recv_error :: proc() -> TCP_Recv_Error {
+ #partial switch posix.errno() {
+ case .EBADF, .EFAULT, .EINVAL, .ENOTSOCK, .EOPNOTSUPP:
+ return .Invalid_Argument
+ case .ENOBUFS:
+ return .Insufficient_Resources
+ case .ENOTCONN:
+ return .Not_Connected
+ case .ECONNRESET:
+ return .Connection_Closed
+ case .ETIMEDOUT:
+ return .Timeout
+ case .EAGAIN:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case:
+ return .Unknown
+ }
}
-Shutdown_Manner :: enum c.int {
- Receive = c.int(posix.SHUT_RD),
- Send = c.int(posix.SHUT_WR),
- Both = c.int(posix.SHUT_RDWR),
+_udp_recv_error :: proc() -> UDP_Recv_Error {
+ #partial switch posix.errno() {
+ case .EBADF, .EFAULT, .EINVAL, .ENOTSOCK, .EOPNOTSUPP, .EMSGSIZE:
+ return .Invalid_Argument
+ case .ENOBUFS, .ENOMEM:
+ return .Insufficient_Resources
+ case .ECONNRESET, .ENOTCONN:
+ return .Connection_Refused
+ case .ETIMEDOUT:
+ return .Timeout
+ case .EAGAIN:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case:
+ return .Unknown
+ }
}
-Shutdown_Error :: enum c.int {
- None = 0,
- Aborted = c.int(posix.ECONNABORTED),
- Reset = c.int(posix.ECONNRESET),
- Offline = c.int(posix.ENETDOWN),
- Not_Connected = c.int(posix.ENOTCONN),
- Not_Socket = c.int(posix.ENOTSOCK),
- Invalid_Manner = c.int(posix.EINVAL),
+_tcp_send_error :: proc() -> TCP_Send_Error {
+ #partial switch posix.errno() {
+ case .EACCES, .EBADF, .EFAULT, .EMSGSIZE, .ENOTSOCK, .EOPNOTSUPP:
+ return .Invalid_Argument
+ case .ENOBUFS:
+ return .Insufficient_Resources
+ case .ECONNRESET, .EPIPE:
+ return .Connection_Closed
+ case .ENOTCONN:
+ return .Not_Connected
+ case .EHOSTUNREACH:
+ return .Host_Unreachable
+ case .ENETDOWN, .ENETUNREACH:
+ return .Network_Unreachable
+ case .ETIMEDOUT:
+ return .Timeout
+ case .EAGAIN:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case:
+ return .Unknown
+ }
}
-Socket_Option_Error :: enum c.int {
- None = 0,
- Offline = c.int(posix.ENETDOWN),
- Timeout_When_Keepalive_Set = c.int(posix.ENETRESET),
- Invalid_Option_For_Socket = c.int(posix.ENOPROTOOPT),
- Reset_When_Keepalive_Set = c.int(posix.ENOTCONN),
- Not_Socket = c.int(posix.ENOTSOCK),
+_udp_send_error :: proc() -> UDP_Send_Error {
+ #partial switch posix.errno() {
+ case .EACCES, .EBADF, .EFAULT, .EMSGSIZE, .ENOTSOCK, .EOPNOTSUPP, .EAFNOSUPPORT, .EDESTADDRREQ:
+ return .Invalid_Argument
+ case .ENOBUFS, .ENOMEM:
+ return .Insufficient_Resources
+ case .ECONNRESET, .EPIPE:
+ return .Connection_Refused
+ case .EHOSTUNREACH:
+ return .Host_Unreachable
+ case .ENETDOWN, .ENETUNREACH:
+ return .Network_Unreachable
+ case .ETIMEDOUT:
+ return .Timeout
+ case .EAGAIN:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case:
+ return .Unknown
+ }
}
-Set_Blocking_Error :: enum c.int {
- None = 0,
-
- // TODO: Add errors for `set_blocking`
+_shutdown_error :: proc() -> Shutdown_Error {
+ #partial switch posix.errno() {
+ case .EBADF, .EINVAL, .ENOTSOCK, .ENOTCONN:
+ return .Invalid_Argument
+ case:
+ return .Unknown
+ }
+}
+
+_socket_option_error :: proc() -> Socket_Option_Error {
+ #partial switch posix.errno() {
+ case .ENOBUFS:
+ return .Insufficient_Resources
+ case .EBADF, .ENOTSOCK, .EISCONN:
+ return .Invalid_Socket
+ case .EINVAL, .ENOPROTOOPT:
+ return .Invalid_Option
+ case .EFAULT, .EDOM:
+ return .Invalid_Value
+ case:
+ return .Unknown
+ }
+}
+
+_set_blocking_error :: proc() -> Set_Blocking_Error {
+ #partial switch posix.errno() {
+ case .EBADF:
+ return .Invalid_Argument
+ case:
+ return .Unknown
+ }
}
diff --git a/core/net/errors_freebsd.odin b/core/net/errors_freebsd.odin
index 486732a95..707ffd0dd 100644
--- a/core/net/errors_freebsd.odin
+++ b/core/net/errors_freebsd.odin
@@ -20,198 +20,267 @@ package net
Feoramund: FreeBSD platform code
*/
-import "core:c"
+import "core:reflect"
import "core:sys/freebsd"
-Create_Socket_Error :: enum c.int {
- None = 0,
- Access_Denied = cast(c.int)freebsd.Errno.EACCES,
- Family_Not_Supported_For_This_Socket = cast(c.int)freebsd.Errno.EAFNOSUPPORT,
- Full_Per_Process_Descriptor_Table = cast(c.int)freebsd.Errno.EMFILE,
- Full_System_File_Table = cast(c.int)freebsd.Errno.ENFILE,
- No_Buffer_Space_Available = cast(c.int)freebsd.Errno.ENOBUFS,
- Insufficient_Permission = cast(c.int)freebsd.Errno.EPERM,
- Protocol_Unsupported_In_Family = cast(c.int)freebsd.Errno.EPROTONOSUPPORT,
- Socket_Type_Unsupported_By_Protocol = cast(c.int)freebsd.Errno.EPROTOTYPE,
+@(private="file", thread_local)
+_last_error: freebsd.Errno
+
+_last_platform_error :: proc() -> i32 {
+ return i32(_last_error)
}
-Dial_Error :: enum c.int {
- None = 0,
- Port_Required = -1,
- Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
- Invalid_Namelen = cast(c.int)freebsd.Errno.EINVAL,
- Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
- Address_Unavailable = cast(c.int)freebsd.Errno.EADDRNOTAVAIL,
- Wrong_Family_For_Socket = cast(c.int)freebsd.Errno.EAFNOSUPPORT,
- Already_Connected = cast(c.int)freebsd.Errno.EISCONN,
- Timeout = cast(c.int)freebsd.Errno.ETIMEDOUT,
- Refused_By_Remote_Host = cast(c.int)freebsd.Errno.ECONNREFUSED,
- // `Refused` alias for `core:net` tests.
- // The above default name `Refused_By_Remote_Host` is more explicit.
- Refused = Refused_By_Remote_Host,
- Reset_By_Remote_Host = cast(c.int)freebsd.Errno.ECONNRESET,
- Network_Unreachable = cast(c.int)freebsd.Errno.ENETUNREACH,
- Host_Unreachable = cast(c.int)freebsd.Errno.EHOSTUNREACH,
- Address_In_Use = cast(c.int)freebsd.Errno.EADDRINUSE,
- Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT,
- In_Progress = cast(c.int)freebsd.Errno.EINPROGRESS,
- Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR,
- Previous_Attempt_Incomplete = cast(c.int)freebsd.Errno.EALREADY,
- Broadcast_Unavailable = cast(c.int)freebsd.Errno.EACCES,
- Auto_Port_Unavailable = cast(c.int)freebsd.Errno.EAGAIN,
-
- // NOTE: There are additional connect() error possibilities, but they are
- // strictly for addresses in the UNIX domain.
+_last_platform_error_string :: proc() -> string {
+ description, _ := reflect.enum_name_from_value(_last_error)
+ return description
}
-Bind_Error :: enum c.int {
- None = 0,
- Kernel_Resources_Unavailable = cast(c.int)freebsd.Errno.EAGAIN,
- Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
-
- // NOTE: bind() can also return EINVAL if the underlying `addrlen` is an
- // invalid length for the address family. This shouldn't happen for the net
- // package, but it's worth noting.
- Already_Bound = cast(c.int)freebsd.Errno.EINVAL,
- Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
- Given_Nonlocal_Address = cast(c.int)freebsd.Errno.EADDRNOTAVAIL,
- Address_In_Use = cast(c.int)freebsd.Errno.EADDRINUSE,
- Address_Family_Mismatch = cast(c.int)freebsd.Errno.EAFNOSUPPORT,
- Protected_Address = cast(c.int)freebsd.Errno.EACCES,
- Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT,
-
- // NOTE: There are additional bind() error possibilities, but they are
- // strictly for addresses in the UNIX domain.
+_set_last_platform_error :: proc(err: i32) {
+ _last_error = freebsd.Errno(err)
}
-Listen_Error :: enum c.int {
- None = 0,
- Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
- Socket_Not_Bound = cast(c.int)freebsd.Errno.EDESTADDRREQ,
- Already_Connected = cast(c.int)freebsd.Errno.EINVAL,
- Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
- Listening_Not_Supported_For_This_Socket = cast(c.int)freebsd.Errno.EOPNOTSUPP,
+_create_socket_error :: proc(errno: freebsd.Errno) -> Create_Socket_Error {
+ assert(errno != nil)
+ _last_error = errno
+
+ #partial switch errno {
+ case .EMFILE, .ENFILE, .ENOBUFS, .EPROTONOSUPPORT:
+ return .Insufficient_Resources
+ case .EAFNOSUPPORT, .EPROTOTYPE:
+ return .Invalid_Argument
+ case .EACCES, .EPERM:
+ return .Insufficient_Permissions
+ case:
+ return .Unknown
+ }
}
-Accept_Error :: enum c.int {
- None = 0,
- Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
- Interrupted = cast(c.int)freebsd.Errno.EINTR,
- Full_Per_Process_Descriptor_Table = cast(c.int)freebsd.Errno.EMFILE,
- Full_System_File_Table = cast(c.int)freebsd.Errno.ENFILE,
- Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
- Listen_Not_Called_On_Socket_Yet = cast(c.int)freebsd.Errno.EINVAL,
- Address_Not_Writable = cast(c.int)freebsd.Errno.EFAULT,
+_dial_error :: proc(errno: freebsd.Errno) -> Dial_Error {
+ assert(errno != nil)
+ _last_error = errno
- // NOTE: This is the same as EWOULDBLOCK.
- No_Connections_Available = cast(c.int)freebsd.Errno.EAGAIN,
- // `Would_Block` alias for `core:net` tests.
- Would_Block = cast(c.int)freebsd.Errno.EAGAIN,
-
- New_Connection_Aborted = cast(c.int)freebsd.Errno.ECONNABORTED,
+ #partial switch errno {
+ case .EBADF, .EINVAL, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT, .EAGAIN:
+ return .Invalid_Argument
+ case .EISCONN:
+ return .Already_Connected
+ case .EALREADY:
+ return .Already_Connecting
+ case .EADDRINUSE:
+ return .Address_In_Use
+ case .ENETUNREACH:
+ return .Network_Unreachable
+ case .EHOSTUNREACH:
+ return .Host_Unreachable
+ case .ECONNREFUSED:
+ return .Refused
+ case .ECONNRESET:
+ return .Reset
+ case .ETIMEDOUT:
+ return .Timeout
+ case .EINPROGRESS:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case .EACCES:
+ return .Broadcast_Not_Supported
+ case:
+ return .Unknown
+ }
}
-TCP_Recv_Error :: enum c.int {
- None = 0,
- Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
- Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET,
- Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN,
- Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
+_bind_error :: proc(errno: freebsd.Errno) -> Bind_Error {
+ assert(errno != nil)
+ _last_error = errno
- // NOTE(Feoramund): The next two errors are only relevant for recvmsg(),
- // but I'm including them for completeness's sake.
- Full_Table_And_Pending_Data = cast(c.int)freebsd.Errno.EMFILE,
- Invalid_Message_Size = cast(c.int)freebsd.Errno.EMSGSIZE,
-
- Timeout = cast(c.int)freebsd.Errno.EAGAIN,
- Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR,
- Buffer_Pointer_Outside_Address_Space = cast(c.int)freebsd.Errno.EFAULT,
+ #partial switch errno {
+ case .EAGAIN, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT:
+ return .Insufficient_Resources
+ case .EBADF:
+ return .Invalid_Argument
+ case .EINVAL:
+ return .Already_Bound
+ case .EACCES:
+ return .Insufficient_Permissions_For_Address
+ case .EADDRINUSE:
+ return .Address_In_Use
+ case:
+ return .Unknown
+ }
}
-UDP_Recv_Error :: enum c.int {
- None = 0,
- Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
- Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET,
- Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN,
- Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
+_listen_error :: proc(errno: freebsd.Errno) -> Listen_Error {
+ assert(errno != nil)
+ _last_error = errno
- // NOTE(Feoramund): The next two errors are only relevant for recvmsg(),
- // but I'm including them for completeness's sake.
- Full_Table_And_Data_Discarded = cast(c.int)freebsd.Errno.EMFILE,
- Invalid_Message_Size = cast(c.int)freebsd.Errno.EMSGSIZE,
-
- Timeout = cast(c.int)freebsd.Errno.EAGAIN,
- Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR,
- Buffer_Pointer_Outside_Address_Space = cast(c.int)freebsd.Errno.EFAULT,
+ #partial switch errno {
+ case .EBADF, .ENOTSOCK:
+ return .Invalid_Argument
+ case .EDESTADDRREQ, .EOPNOTSUPP:
+ return .Unsupported_Socket
+ case .EINVAL:
+ return .Already_Connected
+ case:
+ return .Unknown
+ }
}
-TCP_Send_Error :: enum c.int {
- None = 0,
- Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET,
- Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
- Broadcast_Status_Mismatch = cast(c.int)freebsd.Errno.EACCES,
- Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN,
- Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
- Argument_In_Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT,
+_accept_error :: proc(errno: freebsd.Errno) -> Accept_Error {
+ assert(errno != nil)
+ _last_error = errno
- Message_Size_Breaks_Atomicity = cast(c.int)freebsd.Errno.EMSGSIZE,
-
- /* The socket is marked non-blocking, or MSG_DONTWAIT is
- specified, and the requested operation would block. */
- Would_Block = cast(c.int)freebsd.Errno.EAGAIN,
-
- /* NOTE: This error arises for two distinct reasons:
-
- 1. The system was unable to allocate an internal buffer.
- The operation may succeed when buffers become available.
-
- 2. The output queue for a network interface was full.
- This generally indicates that the interface has stopped
- sending, but may be caused by transient congestion.
- */
- No_Buffer_Space_Available = cast(c.int)freebsd.Errno.ENOBUFS,
-
- Host_Unreachable = cast(c.int)freebsd.Errno.EHOSTUNREACH,
- Already_Connected = cast(c.int)freebsd.Errno.EISCONN,
- ICMP_Unreachable = cast(c.int)freebsd.Errno.ECONNREFUSED,
- Host_Down = cast(c.int)freebsd.Errno.EHOSTDOWN,
- Network_Down = cast(c.int)freebsd.Errno.ENETDOWN,
- Jailed_Socket_Tried_To_Escape = cast(c.int)freebsd.Errno.EADDRNOTAVAIL,
- Cannot_Send_More_Data = cast(c.int)freebsd.Errno.EPIPE,
+ #partial switch errno {
+ case .EMFILE, .ENFILE:
+ return .Insufficient_Resources
+ case .EBADF, .ENOTSOCK, .EFAULT:
+ return .Invalid_Argument
+ case .EINVAL:
+ return .Not_Listening
+ case .ECONNABORTED:
+ return .Aborted
+ case .EWOULDBLOCK:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case:
+ return .Unknown
+ }
}
-// NOTE(Feoramund): The same as TCP errors go, as far as I'm aware.
-UDP_Send_Error :: distinct TCP_Send_Error
+_tcp_recv_error :: proc(errno: freebsd.Errno) -> TCP_Recv_Error {
+ assert(errno != nil)
+ _last_error = errno
-Shutdown_Manner :: enum c.int {
- Receive = cast(c.int)freebsd.Shutdown_Method.RD,
- Send = cast(c.int)freebsd.Shutdown_Method.WR,
- Both = cast(c.int)freebsd.Shutdown_Method.RDWR,
+ #partial switch errno {
+ case .EBADF, .ENOTSOCK, .EFAULT:
+ return .Invalid_Argument
+ case .ENOTCONN:
+ return .Not_Connected
+ case .ECONNRESET:
+ return .Connection_Closed
+ case .ETIMEDOUT:
+ return .Timeout
+ case .EAGAIN:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case:
+ return .Unknown
+ }
}
-Shutdown_Error :: enum c.int {
- None = 0,
- Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
- Invalid_Manner = cast(c.int)freebsd.Errno.EINVAL,
- Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN,
- Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
+_udp_recv_error :: proc(errno: freebsd.Errno) -> UDP_Recv_Error {
+ assert(errno != nil)
+ _last_error = errno
+
+ #partial switch errno {
+ case .EBADF, .ENOTSOCK, .EFAULT:
+ return .Invalid_Argument
+ case .ECONNRESET, .ENOTCONN:
+ return .Connection_Refused
+ case .ETIMEDOUT:
+ return .Timeout
+ case .EAGAIN:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case:
+ return .Unknown
+ }
}
-Socket_Option_Error :: enum c.int {
- None = 0,
- Value_Out_Of_Range = -1,
- Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
- Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK,
- Unknown_Option_For_Level = cast(c.int)freebsd.Errno.ENOPROTOOPT,
- Argument_In_Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT,
- // This error can arise for many different reasons.
- Invalid_Value = cast(c.int)freebsd.Errno.EINVAL,
- System_Memory_Allocation_Failed = cast(c.int)freebsd.Errno.ENOMEM,
- Insufficient_System_Resources = cast(c.int)freebsd.Errno.ENOBUFS,
+_tcp_send_error :: proc(errno: freebsd.Errno) -> TCP_Send_Error {
+ assert(errno != nil)
+ _last_error = errno
+
+ #partial switch errno {
+ case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE:
+ return .Invalid_Argument
+ case .ENOBUFS:
+ return .Insufficient_Resources
+ case .ECONNRESET, .EPIPE:
+ return .Connection_Closed
+ case .ENOTCONN:
+ return .Not_Connected
+ case .EHOSTUNREACH:
+ return .Host_Unreachable
+ case .EHOSTDOWN:
+ return .Host_Unreachable
+ case .ENETDOWN:
+ return .Network_Unreachable
+ case .EAGAIN:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case:
+ return .Unknown
+ }
}
-Set_Blocking_Error :: enum c.int {
- None = 0,
- Not_Descriptor = cast(c.int)freebsd.Errno.EBADF,
- Wrong_Descriptor = cast(c.int)freebsd.Errno.ENOTTY,
+_udp_send_error :: proc(errno: freebsd.Errno) -> UDP_Send_Error {
+ assert(errno != nil)
+ _last_error = errno
+
+ #partial switch errno {
+ case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE:
+ return .Invalid_Argument
+ case .ENOBUFS:
+ return .Insufficient_Resources
+ case .ECONNRESET, .EPIPE:
+ return .Connection_Refused
+ case .EHOSTUNREACH:
+ return .Host_Unreachable
+ case .EHOSTDOWN:
+ return .Host_Unreachable
+ case .ENETDOWN:
+ return .Network_Unreachable
+ case .EAGAIN:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case:
+ return .Unknown
+ }
+}
+
+_shutdown_error :: proc(errno: freebsd.Errno) -> Shutdown_Error {
+ assert(errno != nil)
+ _last_error = errno
+
+ #partial switch errno {
+ case .EBADF, .EINVAL, .ENOTSOCK, .ENOTCONN:
+ return .Invalid_Argument
+ case:
+ return .Unknown
+ }
+}
+
+_socket_option_error :: proc(errno: freebsd.Errno) -> Socket_Option_Error {
+ assert(errno != nil)
+ _last_error = errno
+
+ #partial switch errno {
+ case .ENOMEM, .ENOBUFS:
+ return .Insufficient_Resources
+ case .EBADF, .ENOTSOCK:
+ return .Invalid_Socket
+ case .ENOPROTOOPT:
+ return .Invalid_Option
+ case .EINVAL, .EFAULT:
+ return .Invalid_Value
+ case:
+ return .Unknown
+ }
+}
+
+_set_blocking_error :: proc(errno: freebsd.Errno) -> Set_Blocking_Error {
+ assert(errno != nil)
+ _last_error = errno
+
+ #partial switch errno {
+ case .EBADF, .ENOTTY:
+ return .Invalid_Argument
+ case:
+ return .Unknown
+ }
}
diff --git a/core/net/errors_linux.odin b/core/net/errors_linux.odin
index 3cd51e6fd..237579f28 100644
--- a/core/net/errors_linux.odin
+++ b/core/net/errors_linux.odin
@@ -21,181 +21,269 @@ package net
Feoramund: FreeBSD platform code
*/
-import "core:c"
+import "core:reflect"
import "core:sys/linux"
-Create_Socket_Error :: enum c.int {
- None = 0,
- Family_Not_Supported_For_This_Socket = c.int(linux.Errno.EAFNOSUPPORT),
- No_Socket_Descriptors_Available = c.int(linux.Errno.EMFILE),
- No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
- No_Memory_Available_Available = c.int(linux.Errno.ENOMEM),
- Protocol_Unsupported_By_System = c.int(linux.Errno.EPROTONOSUPPORT),
- Wrong_Protocol_For_Socket = c.int(linux.Errno.EPROTONOSUPPORT),
- Family_And_Socket_Type_Mismatch = c.int(linux.Errno.EPROTONOSUPPORT),
+@(private="file", thread_local)
+_last_error: linux.Errno
+
+_last_platform_error :: proc() -> i32 {
+ return i32(_last_error)
}
-Dial_Error :: enum c.int {
- None = 0,
- Port_Required = -1,
-
- Address_In_Use = c.int(linux.Errno.EADDRINUSE),
- In_Progress = c.int(linux.Errno.EINPROGRESS),
- Cannot_Use_Any_Address = c.int(linux.Errno.EADDRNOTAVAIL),
- Wrong_Family_For_Socket = c.int(linux.Errno.EAFNOSUPPORT),
- Refused = c.int(linux.Errno.ECONNREFUSED),
- Is_Listening_Socket = c.int(linux.Errno.EACCES),
- Already_Connected = c.int(linux.Errno.EISCONN),
- Network_Unreachable = c.int(linux.Errno.ENETUNREACH), // Device is offline
- Host_Unreachable = c.int(linux.Errno.EHOSTUNREACH), // Remote host cannot be reached
- No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
- Not_Socket = c.int(linux.Errno.ENOTSOCK),
- Timeout = c.int(linux.Errno.ETIMEDOUT),
-
- // TODO: we may need special handling for this; maybe make a socket a struct with metadata?
- Would_Block = c.int(linux.Errno.EWOULDBLOCK),
+_last_platform_error_string :: proc() -> string {
+ description, _ := reflect.enum_name_from_value(_last_error)
+ return description
}
-Bind_Error :: enum c.int {
- None = 0,
- Address_In_Use = c.int(linux.Errno.EADDRINUSE), // Another application is currently bound to this endpoint.
- Given_Nonlocal_Address = c.int(linux.Errno.EADDRNOTAVAIL), // The address is not a local address on this machine.
- Broadcast_Disabled = c.int(linux.Errno.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
- Address_Family_Mismatch = c.int(linux.Errno.EFAULT), // The address family of the address does not match that of the socket.
- Already_Bound = c.int(linux.Errno.EINVAL), // The socket is already bound to an address.
- No_Ports_Available = c.int(linux.Errno.ENOBUFS), // There are not enough ephemeral ports available.
+_set_last_platform_error :: proc(err: i32) {
+ _last_error = linux.Errno(err)
}
-Listen_Error :: enum c.int {
- None = 0,
- Address_In_Use = c.int(linux.Errno.EADDRINUSE),
- Already_Connected = c.int(linux.Errno.EISCONN),
- No_Socket_Descriptors_Available = c.int(linux.Errno.EMFILE),
- No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
- Nonlocal_Address = c.int(linux.Errno.EADDRNOTAVAIL),
- Not_Socket = c.int(linux.Errno.ENOTSOCK),
- Listening_Not_Supported_For_This_Socket = c.int(linux.Errno.EOPNOTSUPP),
+_create_socket_error :: proc(errno: linux.Errno) -> Create_Socket_Error {
+ assert(errno != nil)
+ _last_error = errno
+
+ #partial switch errno {
+ case .EMFILE, .ENFILE, .ENOBUFS, .EPROTONOSUPPORT:
+ return .Insufficient_Resources
+ case .EAFNOSUPPORT, .EPROTOTYPE:
+ return .Invalid_Argument
+ case .EACCES, .EPERM:
+ return .Insufficient_Permissions
+ case:
+ return .Unknown
+ }
}
-Accept_Error :: enum c.int {
- None = 0,
- Not_Listening = c.int(linux.Errno.EINVAL),
- No_Socket_Descriptors_Available_For_Client_Socket = c.int(linux.Errno.EMFILE),
- No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
- Not_Socket = c.int(linux.Errno.ENOTSOCK),
- Not_Connection_Oriented_Socket = c.int(linux.Errno.EOPNOTSUPP),
+_dial_error :: proc(errno: linux.Errno) -> Dial_Error {
+ assert(errno != nil)
+ _last_error = errno
- // TODO: we may need special handling for this; maybe make a socket a struct with metadata?
- Would_Block = c.int(linux.Errno.EWOULDBLOCK),
+ #partial switch errno {
+ case .EAGAIN:
+ return .Insufficient_Resources
+ case .EBADF, .EINVAL, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT:
+ return .Invalid_Argument
+ case .EISCONN:
+ return .Already_Connected
+ case .EALREADY:
+ return .Already_Connecting
+ case .EADDRINUSE:
+ return .Address_In_Use
+ case .ENETUNREACH:
+ return .Network_Unreachable
+ case .EHOSTUNREACH:
+ return .Host_Unreachable
+ case .ECONNREFUSED:
+ return .Refused
+ case .ECONNRESET:
+ return .Reset
+ case .ETIMEDOUT:
+ return .Timeout
+ case .EINPROGRESS:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case .EACCES:
+ return .Broadcast_Not_Supported
+ case:
+ return .Unknown
+ }
}
-TCP_Recv_Error :: enum c.int {
- None = 0,
- Shutdown = c.int(linux.Errno.ESHUTDOWN),
- Not_Connected = c.int(linux.Errno.ENOTCONN),
- Connection_Broken = c.int(linux.Errno.ENETRESET),
- Not_Socket = c.int(linux.Errno.ENOTSOCK),
- Aborted = c.int(linux.Errno.ECONNABORTED),
+_bind_error :: proc(errno: linux.Errno) -> Bind_Error {
+ assert(errno != nil)
+ _last_error = errno
- // TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them?
- Connection_Closed = c.int(linux.Errno.ECONNRESET),
- Offline = c.int(linux.Errno.ENETDOWN),
- Host_Unreachable = c.int(linux.Errno.EHOSTUNREACH),
- Interrupted = c.int(linux.Errno.EINTR),
- Timeout = c.int(linux.Errno.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets...
+ #partial switch errno {
+ case .EAGAIN, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT:
+ return .Insufficient_Resources
+ case .EINVAL:
+ return .Already_Bound
+ case .EBADF:
+ return .Invalid_Argument
+ case .EACCES:
+ return .Insufficient_Permissions_For_Address
+ case .EADDRINUSE:
+ return .Address_In_Use
+ case:
+ return .Unknown
+ }
}
-UDP_Recv_Error :: enum c.int {
- None = 0,
+_listen_error :: proc(errno: linux.Errno) -> Listen_Error {
+ assert(errno != nil)
+ _last_error = errno
- Buffer_Too_Small = c.int(linux.Errno.EMSGSIZE), // The buffer is too small to fit the entire message, and the message was truncated. When this happens, the rest of message is lost.
- Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket.
- Not_Descriptor = c.int(linux.Errno.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
- Bad_Buffer = c.int(linux.Errno.EFAULT), // The buffer did not point to a valid location in memory.
- Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7).
-
- // The send timeout duration passed before all data was received. See Socket_Option.Receive_Timeout.
- // NOTE: No, really. Presumably this means something different for nonblocking sockets...
- Timeout = c.int(linux.Errno.EWOULDBLOCK),
- Socket_Not_Bound = c.int(linux.Errno.EINVAL), // The socket must be bound for this operation, but isn't.
+ #partial switch errno {
+ case .EBADF, .ENOTSOCK:
+ return .Invalid_Argument
+ case .EDESTADDRREQ, .EOPNOTSUPP:
+ return .Unsupported_Socket
+ case .EINVAL:
+ return .Already_Connected
+ case:
+ return .Unknown
+ }
}
-TCP_Send_Error :: enum c.int {
- None = 0,
- Aborted = c.int(linux.Errno.ECONNABORTED),
- Connection_Closed = c.int(linux.Errno.ECONNRESET),
- Not_Connected = c.int(linux.Errno.ENOTCONN),
- Shutdown = c.int(linux.Errno.ESHUTDOWN),
+_accept_error :: proc(errno: linux.Errno) -> Accept_Error {
+ assert(errno != nil)
+ _last_error = errno
- // The send queue was full.
- // This is usually a transient issue.
- //
- // This also shouldn't normally happen on Linux, as data is dropped if it
- // doesn't fit in the send queue.
- No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
- Offline = c.int(linux.Errno.ENETDOWN),
- Host_Unreachable = c.int(linux.Errno.EHOSTUNREACH),
- Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7).
- Timeout = c.int(linux.Errno.EWOULDBLOCK), // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
- Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket.
+ #partial switch errno {
+ case .EMFILE, .ENFILE, .ENOBUFS, .ENOMEM:
+ return .Insufficient_Resources
+ case .EBADF, .ENOTSOCK, .EFAULT:
+ return .Invalid_Argument
+ case .EINVAL:
+ return .Not_Listening
+ case .ECONNABORTED:
+ return .Aborted
+ case .EWOULDBLOCK:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case:
+ return .Unknown
+ }
}
-// TODO
-UDP_Send_Error :: enum c.int {
- None = 0,
- Message_Too_Long = c.int(linux.Errno.EMSGSIZE), // The message is larger than the maximum UDP packet size. No data was sent.
+_tcp_recv_error :: proc(errno: linux.Errno) -> TCP_Recv_Error {
+ assert(errno != nil)
+ _last_error = errno
- // TODO: not sure what the exact circumstances for this is yet
- Network_Unreachable = c.int(linux.Errno.ENETUNREACH),
- No_Outbound_Ports_Available = c.int(linux.Errno.EAGAIN), // There are no more emphemeral outbound ports available to bind the socket to, in order to send.
-
- // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
- // NOTE: No, really. Presumably this means something different for nonblocking sockets...
- Timeout = c.int(linux.Errno.EWOULDBLOCK),
- Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket.
- Not_Descriptor = c.int(linux.Errno.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
- Bad_Buffer = c.int(linux.Errno.EFAULT), // The buffer did not point to a valid location in memory.
- Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7).
-
- // The send queue was full.
- // This is usually a transient issue.
- //
- // This also shouldn't normally happen on Linux, as data is dropped if it
- // doesn't fit in the send queue.
- No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS),
- No_Memory_Available = c.int(linux.Errno.ENOMEM), // No memory was available to properly manage the send queue.
+ #partial switch errno {
+ case .EBADF, .ENOTSOCK, .EFAULT:
+ return .Invalid_Argument
+ case .ENOTCONN:
+ return .Not_Connected
+ case .ECONNREFUSED, .ECONNRESET:
+ return .Connection_Closed
+ case .ETIMEDOUT:
+ return .Timeout
+ case .EAGAIN:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case:
+ return .Unknown
+ }
}
-// TODO(flysand): slight regression
-Shutdown_Manner :: enum c.int {
- Receive = c.int(linux.Shutdown_How.RD),
- Send = c.int(linux.Shutdown_How.WR),
- Both = c.int(linux.Shutdown_How.RDWR),
+_udp_recv_error :: proc(errno: linux.Errno) -> UDP_Recv_Error {
+ assert(errno != nil)
+ _last_error = errno
+
+ #partial switch errno {
+ case .EBADF, .ENOTSOCK, .EFAULT:
+ return .Invalid_Argument
+ case .ECONNREFUSED, .ENOTCONN, .ECONNRESET:
+ return .Connection_Refused
+ case .ETIMEDOUT:
+ return .Timeout
+ case .EAGAIN:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case:
+ return .Unknown
+ }
}
-Shutdown_Error :: enum c.int {
- None = 0,
- Aborted = c.int(linux.Errno.ECONNABORTED),
- Reset = c.int(linux.Errno.ECONNRESET),
- Offline = c.int(linux.Errno.ENETDOWN),
- Not_Connected = c.int(linux.Errno.ENOTCONN),
- Not_Socket = c.int(linux.Errno.ENOTSOCK),
- Invalid_Manner = c.int(linux.Errno.EINVAL),
+_tcp_send_error :: proc(errno: linux.Errno) -> TCP_Send_Error {
+ assert(errno != nil)
+ _last_error = errno
+
+ #partial switch errno {
+ case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE, .EDESTADDRREQ, .EINVAL, .EISCONN, .EOPNOTSUPP:
+ return .Invalid_Argument
+ case .ENOBUFS, .ENOMEM:
+ return .Insufficient_Resources
+ case .ECONNRESET, .EPIPE:
+ return .Connection_Closed
+ case .ENOTCONN:
+ return .Not_Connected
+ case .EHOSTUNREACH:
+ return .Host_Unreachable
+ case .EHOSTDOWN:
+ return .Host_Unreachable
+ case .ENETDOWN:
+ return .Network_Unreachable
+ case .EAGAIN:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case:
+ return .Unknown
+ }
}
-Socket_Option_Error :: enum c.int {
- None = 0,
- Offline = c.int(linux.Errno.ENETDOWN),
- Timeout_When_Keepalive_Set = c.int(linux.Errno.ENETRESET),
- Invalid_Option_For_Socket = c.int(linux.Errno.ENOPROTOOPT),
- Reset_When_Keepalive_Set = c.int(linux.Errno.ENOTCONN),
- Not_Socket = c.int(linux.Errno.ENOTSOCK),
+_udp_send_error :: proc(errno: linux.Errno) -> UDP_Send_Error {
+ assert(errno != nil)
+ _last_error = errno
+
+ #partial switch errno {
+ case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE, .EDESTADDRREQ, .EINVAL, .EISCONN, .EOPNOTSUPP:
+ return .Invalid_Argument
+ case .ENOBUFS, .ENOMEM:
+ return .Insufficient_Resources
+ case .ECONNRESET, .EPIPE:
+ return .Connection_Refused
+ case .EHOSTUNREACH:
+ return .Host_Unreachable
+ case .EHOSTDOWN:
+ return .Host_Unreachable
+ case .ENETDOWN:
+ return .Network_Unreachable
+ case .EAGAIN:
+ return .Would_Block
+ case .EINTR:
+ return .Interrupted
+ case:
+ return .Unknown
+ }
}
-Set_Blocking_Error :: enum c.int {
- None = 0,
+_shutdown_error :: proc(errno: linux.Errno) -> Shutdown_Error {
+ assert(errno != nil)
+ _last_error = errno
- // TODO: add errors occuring on followig calls:
- // flags, _ := linux.Errno.fcntl(sd, linux.Errno.F_GETFL, 0)
- // linux.Errno.fcntl(sd, linux.Errno.F_SETFL, flags | int(linux.Errno.O_NONBLOCK))
-}
\ No newline at end of file
+ #partial switch errno {
+ case .EBADF, .EINVAL, .ENOTSOCK, .ENOTCONN:
+ return .Invalid_Argument
+ case:
+ return .Unknown
+ }
+}
+
+_socket_option_error :: proc(errno: linux.Errno) -> Socket_Option_Error {
+ assert(errno != nil)
+ _last_error = errno
+
+ #partial switch errno {
+ case .ENOMEM, .ENOBUFS:
+ return .Insufficient_Resources
+ case .EBADF, .ENOTSOCK:
+ return .Invalid_Socket
+ case .ENOPROTOOPT, .EINVAL:
+ return .Invalid_Option
+ case .EFAULT, .EDOM:
+ return .Invalid_Value
+ case:
+ return .Unknown
+ }
+}
+
+_set_blocking_error :: proc(errno: linux.Errno) -> Set_Blocking_Error {
+ assert(errno != nil)
+ _last_error = errno
+
+ #partial switch errno {
+ case .EBADF:
+ return .Invalid_Argument
+ case:
+ return .Unknown
+ }
+}
diff --git a/core/net/errors_others.odin b/core/net/errors_others.odin
new file mode 100644
index 000000000..bda0fd28f
--- /dev/null
+++ b/core/net/errors_others.odin
@@ -0,0 +1,20 @@
+#+build !darwin
+#+build !linux
+#+build !freebsd
+#+build !windows
+package net
+
+@(private="file", thread_local)
+_last_error: i32
+
+_last_platform_error :: proc() -> i32 {
+ return _last_error
+}
+
+_last_platform_error_string :: proc() -> string {
+ return ""
+}
+
+_set_last_platform_error :: proc(err: i32) {
+ _last_error = err
+}
diff --git a/core/net/errors_windows.odin b/core/net/errors_windows.odin
index f41bcf888..b30046a17 100644
--- a/core/net/errors_windows.odin
+++ b/core/net/errors_windows.odin
@@ -20,250 +20,242 @@ package net
Feoramund: FreeBSD platform code
*/
-import "core:c"
+import "core:reflect"
import win "core:sys/windows"
-Create_Socket_Error :: enum c.int {
- None = 0,
- Network_Subsystem_Failure = win.WSAENETDOWN,
- Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT,
- No_Socket_Descriptors_Available = win.WSAEMFILE,
- No_Buffer_Space_Available = win.WSAENOBUFS,
- Protocol_Unsupported_By_System = win.WSAEPROTONOSUPPORT,
- Wrong_Protocol_For_Socket = win.WSAEPROTOTYPE,
- Family_And_Socket_Type_Mismatch = win.WSAESOCKTNOSUPPORT,
+_last_platform_error :: proc() -> i32 {
+ return i32(win.WSAGetLastError())
}
-Dial_Error :: enum c.int {
- None = 0,
- Port_Required = -1,
- Address_In_Use = win.WSAEADDRINUSE,
- In_Progress = win.WSAEALREADY,
- Cannot_Use_Any_Address = win.WSAEADDRNOTAVAIL,
- Wrong_Family_For_Socket = win.WSAEAFNOSUPPORT,
- Refused = win.WSAECONNREFUSED,
- Is_Listening_Socket = win.WSAEINVAL,
- Already_Connected = win.WSAEISCONN,
- Network_Unreachable = win.WSAENETUNREACH, // Device is offline
- Host_Unreachable = win.WSAEHOSTUNREACH, // Remote host cannot be reached
- No_Buffer_Space_Available = win.WSAENOBUFS,
- Not_Socket = win.WSAENOTSOCK,
- Timeout = win.WSAETIMEDOUT,
- Would_Block = win.WSAEWOULDBLOCK, // TODO: we may need special handling for this; maybe make a socket a struct with metadata?
+_last_platform_error_string :: proc() -> string {
+ description, _ := reflect.enum_name_from_value(win.System_Error(win.WSAGetLastError()))
+ return description
}
-Bind_Error :: enum c.int {
- None = 0,
- Address_In_Use = win.WSAEADDRINUSE, // Another application is currently bound to this endpoint.
- Given_Nonlocal_Address = win.WSAEADDRNOTAVAIL, // The address is not a local address on this machine.
- Broadcast_Disabled = win.WSAEACCES, // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
- Address_Family_Mismatch = win.WSAEFAULT, // The address family of the address does not match that of the socket.
- Already_Bound = win.WSAEINVAL, // The socket is already bound to an address.
- No_Ports_Available = win.WSAENOBUFS, // There are not enough ephemeral ports available.
+_set_last_platform_error :: proc(err: i32) {
+ win.WSASetLastError(err)
}
-Listen_Error :: enum c.int {
- None = 0,
- Address_In_Use = win.WSAEADDRINUSE,
- Already_Connected = win.WSAEISCONN,
- No_Socket_Descriptors_Available = win.WSAEMFILE,
- No_Buffer_Space_Available = win.WSAENOBUFS,
- Nonlocal_Address = win.WSAEADDRNOTAVAIL,
- Not_Socket = win.WSAENOTSOCK,
- Listening_Not_Supported_For_This_Socket = win.WSAEOPNOTSUPP,
+_create_socket_error :: proc() -> Create_Socket_Error {
+ #partial switch win.System_Error(win.WSAGetLastError()) {
+ case .WSANOTINITIALISED, .WSAENETDOWN, .WSAEINVALIDPROVIDER, .WSAEINVALIDPROCTABLE, .WSAEPROVIDERFAILEDINIT:
+ return .Network_Unreachable
+ case .WSAEAFNOSUPPORT, .WSAEINPROGRESS, .WSAEINVAL, .WSAEPROTOTYPE, .WSAESOCKTNOSUPPORT:
+ return .Invalid_Argument
+ case .WSAEMFILE, .WSAENOBUFS, .WSAEPROTONOSUPPORT:
+ return .Insufficient_Resources
+ case:
+ return .Unknown
+ }
}
-Accept_Error :: enum c.int {
- None = 0,
- Not_Listening = win.WSAEINVAL,
- No_Socket_Descriptors_Available_For_Client_Socket = win.WSAEMFILE,
- No_Buffer_Space_Available = win.WSAENOBUFS,
- Not_Socket = win.WSAENOTSOCK,
- Not_Connection_Oriented_Socket = win.WSAEOPNOTSUPP,
-
- // TODO: we may need special handling for this; maybe make a socket a struct with metadata?
- Would_Block = win.WSAEWOULDBLOCK,
+_dial_error :: proc() -> Dial_Error {
+ #partial switch win.System_Error(win.WSAGetLastError()) {
+ case .WSANOTINITIALISED, .WSAENETDOWN:
+ return .Network_Unreachable
+ case .WSAEADDRINUSE:
+ return .Address_In_Use
+ case .WSAEINTR:
+ return .Interrupted
+ case .WSAEWOULDBLOCK:
+ return .Would_Block
+ case .WSAEALREADY:
+ return .Already_Connecting
+ case .WSAEADDRNOTAVAIL, .WSAEAFNOSUPPORT, .WSAEFAULT, .WSAENOTSOCK, .WSAEINPROGRESS, .WSAEINVAL:
+ return .Invalid_Argument
+ case .WSAECONNREFUSED:
+ return .Refused
+ case .WSAEISCONN:
+ return .Already_Connected
+ case .WSAEHOSTUNREACH:
+ return .Host_Unreachable
+ case .WSAENOBUFS:
+ return .Insufficient_Resources
+ case .WSAETIMEDOUT:
+ return .Timeout
+ case .WSAEACCES:
+ return .Broadcast_Not_Supported
+ case:
+ return .Unknown
+ }
}
-TCP_Recv_Error :: enum c.int {
- None = 0,
- Network_Subsystem_Failure = win.WSAENETDOWN,
- Not_Connected = win.WSAENOTCONN,
- Bad_Buffer = win.WSAEFAULT,
- Keepalive_Failure = win.WSAENETRESET,
- Not_Socket = win.WSAENOTSOCK,
- Shutdown = win.WSAESHUTDOWN,
- Would_Block = win.WSAEWOULDBLOCK,
- Aborted = win.WSAECONNABORTED,
- Timeout = win.WSAETIMEDOUT,
-
- // TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them?
- Connection_Closed = win.WSAECONNRESET,
-
- // TODO: verify can actually happen
- Host_Unreachable = win.WSAEHOSTUNREACH,
+_bind_error :: proc() -> Bind_Error {
+ #partial switch win.System_Error(win.WSAGetLastError()) {
+ case .WSANOTINITIALISED, .WSAENETDOWN:
+ return .Network_Unreachable
+ case .WSAEADDRINUSE:
+ return .Address_In_Use
+ case .WSAEADDRNOTAVAIL, .WSAEFAULT, .WSAEINPROGRESS, .WSAEACCES, .WSAEINVAL, .WSAENOTSOCK:
+ return .Invalid_Argument
+ case:
+ return .Unknown
+ }
}
-UDP_Recv_Error :: enum c.int {
- None = 0,
- Network_Subsystem_Failure = win.WSAENETDOWN,
- Aborted = win.WSAECONNABORTED,
- Buffer_Too_Small = win.WSAEMSGSIZE, // The buffer is too small to fit the entire message, and the message was truncated. When this happens, the rest of message is lost.
- Remote_Not_Listening = win.WSAECONNRESET, // The machine at the remote endpoint doesn't have the given port open to receiving UDP data.
- Shutdown = win.WSAESHUTDOWN,
- Broadcast_Disabled = win.WSAEACCES, // A broadcast address was specified, but the .Broadcast socket option isn't set.
- Bad_Buffer = win.WSAEFAULT,
- No_Buffer_Space_Available = win.WSAENOBUFS,
- Not_Socket = win.WSAENOTSOCK, // The socket is not valid socket handle.
- Would_Block = win.WSAEWOULDBLOCK,
- Host_Unreachable = win.WSAEHOSTUNREACH, // The remote host cannot be reached from this host at this time.
- Offline = win.WSAENETUNREACH, // The network cannot be reached from this host at this time.
- Timeout = win.WSAETIMEDOUT,
-
- // TODO: can this actually happen? The socket isn't bound; an unknown flag specified; or MSG_OOB specified with SO_OOBINLINE enabled.
- Incorrectly_Configured = win.WSAEINVAL,
- TTL_Expired = win.WSAENETRESET, // The message took more hops than was allowed (the Time To Live) to reach the remote endpoint.
+_listen_error :: proc() -> Listen_Error {
+ #partial switch win.System_Error(win.WSAGetLastError()) {
+ case .WSANOTINITIALISED, .WSAENETDOWN:
+ return .Network_Unreachable
+ case .WSAEMFILE, .WSAENOBUFS:
+ return .Insufficient_Resources
+ case .WSAEADDRINUSE:
+ return .Address_In_Use
+ case .WSAEINPROGRESS, .WSAENOTSOCK:
+ return .Invalid_Argument
+ case .WSAEISCONN:
+ return .Already_Connected
+ case .WSAEOPNOTSUPP, .WSAEINVAL:
+ return .Unsupported_Socket
+ case:
+ return .Unknown
+ }
}
-// TODO: consider merging some errors to make handling them easier
-// TODO: verify once more what errors to actually expose
-TCP_Send_Error :: enum c.int {
- None = 0,
-
- Aborted = win.WSAECONNABORTED,
- Not_Connected = win.WSAENOTCONN,
- Shutdown = win.WSAESHUTDOWN,
- Connection_Closed = win.WSAECONNRESET,
- No_Buffer_Space_Available = win.WSAENOBUFS,
- Network_Subsystem_Failure = win.WSAENETDOWN,
- Host_Unreachable = win.WSAEHOSTUNREACH,
- Would_Block = win.WSAEWOULDBLOCK,
-
- // TODO: verify possible, as not mentioned in docs
- Offline = win.WSAENETUNREACH,
- Timeout = win.WSAETIMEDOUT,
-
- // A broadcast address was specified, but the .Broadcast socket option isn't set.
- Broadcast_Disabled = win.WSAEACCES,
- Bad_Buffer = win.WSAEFAULT,
-
- // Connection is broken due to keepalive activity detecting a failure during the operation.
- Keepalive_Failure = win.WSAENETRESET, // TODO: not functionally different from Reset; merge?
- Not_Socket = win.WSAENOTSOCK, // The so-called socket is not an open socket.
+_accept_error :: proc() -> Accept_Error {
+ #partial switch win.System_Error(win.WSAGetLastError()) {
+ case .WSANOTINITIALISED, .WSAENETDOWN:
+ return .Network_Unreachable
+ case .WSAEMFILE, .WSAENOBUFS:
+ return .Insufficient_Resources
+ case .WSAECONNRESET:
+ return .Aborted
+ case .WSAEFAULT, .WSAEINPROGRESS, .WSAENOTSOCK:
+ return .Invalid_Argument
+ case .WSAEINTR:
+ return .Interrupted
+ case .WSAEINVAL:
+ return .Not_Listening
+ case .WSAEWOULDBLOCK:
+ return .Would_Block
+ case .WSAEOPNOTSUPP:
+ return .Unsupported_Socket
+ case:
+ return .Unknown
+ }
}
-UDP_Send_Error :: enum c.int {
- None = 0,
- Network_Subsystem_Failure = win.WSAENETDOWN,
-
- Aborted = win.WSAECONNABORTED,
- Message_Too_Long = win.WSAEMSGSIZE, // The message is larger than the maximum UDP packet size.
- Remote_Not_Listening = win.WSAECONNRESET, // The machine at the remote endpoint doesn't have the given port open to receiving UDP data.
- Shutdown = win.WSAESHUTDOWN, // A broadcast address was specified, but the .Broadcast socket option isn't set.
- Broadcast_Disabled = win.WSAEACCES,
- Bad_Buffer = win.WSAEFAULT, // Connection is broken due to keepalive activity detecting a failure during the operation.
-
- // TODO: not functionally different from Reset; merge?
- Keepalive_Failure = win.WSAENETRESET,
- No_Buffer_Space_Available = win.WSAENOBUFS,
- Not_Socket = win.WSAENOTSOCK, // The socket is not valid socket handle.
-
- // This socket is unidirectional and cannot be used to send any data.
- // TODO: verify possible; decide whether to keep if not
- Receive_Only = win.WSAEOPNOTSUPP,
- Would_Block = win.WSAEWOULDBLOCK,
- Host_Unreachable = win.WSAEHOSTUNREACH, // The remote host cannot be reached from this host at this time.
- Cannot_Use_Any_Address = win.WSAEADDRNOTAVAIL, // Attempt to send to the Any address.
- Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT, // The address is of an incorrect address family for this socket.
- Offline = win.WSAENETUNREACH, // The network cannot be reached from this host at this time.
- Timeout = win.WSAETIMEDOUT,
+_tcp_recv_error :: proc() -> TCP_Recv_Error {
+ #partial switch win.System_Error(win.WSAGetLastError()) {
+ case .WSANOTINITIALISED, .WSAENETDOWN:
+ return .Network_Unreachable
+ case .WSAEFAULT, .WSAEINPROGRESS, .WSAENOTSOCK, .WSAEMSGSIZE, .WSAEINVAL, .WSAEOPNOTSUPP:
+ return .Invalid_Argument
+ case .WSAENOTCONN:
+ return .Not_Connected
+ case .WSAEINTR:
+ return .Interrupted
+ case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNABORTED, .WSAECONNRESET:
+ return .Connection_Closed
+ case .WSAEWOULDBLOCK:
+ return .Would_Block
+ case .WSAETIMEDOUT:
+ return .Timeout
+ case:
+ return .Unknown
+ }
}
-Shutdown_Manner :: enum c.int {
- Receive = win.SD_RECEIVE,
- Send = win.SD_SEND,
- Both = win.SD_BOTH,
+_udp_recv_error :: proc() -> UDP_Recv_Error {
+ #partial switch win.System_Error(win.WSAGetLastError()) {
+ case .WSANOTINITIALISED, .WSAENETDOWN:
+ return .Network_Unreachable
+ case .WSAEFAULT, .WSAEINPROGRESS, .WSAEINVAL, .WSAEISCONN, .WSAENOTSOCK, .WSAEOPNOTSUPP, .WSAEMSGSIZE:
+ return .Invalid_Argument
+ case .WSAEINTR:
+ return .Interrupted
+ case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNRESET:
+ return .Connection_Refused
+ case .WSAEWOULDBLOCK:
+ return .Would_Block
+ case .WSAETIMEDOUT:
+ return .Timeout
+ case:
+ return .Unknown
+ }
}
-Shutdown_Error :: enum c.int {
- None = 0,
- Aborted = win.WSAECONNABORTED,
- Reset = win.WSAECONNRESET,
- Offline = win.WSAENETDOWN,
- Not_Connected = win.WSAENOTCONN,
- Not_Socket = win.WSAENOTSOCK,
- Invalid_Manner = win.WSAEINVAL,
+_tcp_send_error :: proc() -> TCP_Send_Error {
+ #partial switch win.System_Error(win.WSAGetLastError()) {
+ case .WSANOTINITIALISED, .WSAENETDOWN:
+ return .Network_Unreachable
+ case .WSAENOBUFS:
+ return .Insufficient_Resources
+ case .WSAEACCES, .WSAEINPROGRESS, .WSAEFAULT, .WSAENOTSOCK, .WSAEOPNOTSUPP, .WSAEMSGSIZE, .WSAEINVAL:
+ return .Invalid_Argument
+ case .WSAEINTR:
+ return .Interrupted
+ case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNABORTED, .WSAECONNRESET:
+ return .Connection_Closed
+ case .WSAENOTCONN:
+ return .Not_Connected
+ case .WSAEWOULDBLOCK:
+ return .Would_Block
+ case .WSAETIMEDOUT:
+ return .Timeout
+ case .WSAEHOSTUNREACH:
+ return .Host_Unreachable
+ case:
+ return .Unknown
+ }
}
-Socket_Option :: enum c.int {
- // bool: Whether the address that this socket is bound to can be reused by other sockets.
- // This allows you to bypass the cooldown period if a program dies while the socket is bound.
- Reuse_Address = win.SO_REUSEADDR,
-
- // bool: Whether other programs will be inhibited from binding the same endpoint as this socket.
- Exclusive_Addr_Use = win.SO_EXCLUSIVEADDRUSE,
-
- // bool: When true, keepalive packets will be automatically be sent for this connection. TODO: verify this understanding
- Keep_Alive = win.SO_KEEPALIVE,
-
- // bool: When true, client connections will immediately be sent a TCP/IP RST response, rather than being accepted.
- Conditional_Accept = win.SO_CONDITIONAL_ACCEPT,
-
- // bool: If true, when the socket is closed, but data is still waiting to be sent, discard that data.
- Dont_Linger = win.SO_DONTLINGER,
-
- // bool: When true, 'out-of-band' data sent over the socket will be read by a normal net.recv() call, the same as normal 'in-band' data.
- Out_Of_Bounds_Data_Inline = win.SO_OOBINLINE,
-
- // bool: When true, disables send-coalescing, therefore reducing latency.
- TCP_Nodelay = win.TCP_NODELAY,
-
- // win.LINGER: Customizes how long (if at all) the socket will remain open when there
- // is some remaining data waiting to be sent, and net.close() is called.
- Linger = win.SO_LINGER,
-
- // win.DWORD: The size, in bytes, of the OS-managed receive-buffer for this socket.
- Receive_Buffer_Size = win.SO_RCVBUF,
-
- // win.DWORD: The size, in bytes, of the OS-managed send-buffer for this socket.
- Send_Buffer_Size = win.SO_SNDBUF,
-
- // win.DWORD: For blocking sockets, the time in milliseconds to wait for incoming data to be received, before giving up and returning .Timeout.
- // For non-blocking sockets, ignored.
- // Use a value of zero to potentially wait forever.
- Receive_Timeout = win.SO_RCVTIMEO,
-
- // win.DWORD: For blocking sockets, the time in milliseconds to wait for outgoing data to be sent, before giving up and returning .Timeout.
- // For non-blocking sockets, ignored.
- // Use a value of zero to potentially wait forever.
- Send_Timeout = win.SO_SNDTIMEO,
-
- // bool: Allow sending to, receiving from, and binding to, a broadcast address.
- Broadcast = win.SO_BROADCAST,
+_udp_send_error :: proc() -> UDP_Send_Error {
+ #partial switch win.System_Error(win.WSAGetLastError()) {
+ case .WSANOTINITIALISED, .WSAENETDOWN, .WSAENETUNREACH:
+ return .Network_Unreachable
+ case .WSAENOBUFS:
+ return .Insufficient_Resources
+ case .WSAEACCES, .WSAEINVAL, .WSAEINPROGRESS, .WSAEFAULT, .WSAENOTCONN, .WSAENOTSOCK, .WSAEOPNOTSUPP, .WSAEADDRNOTAVAIL, .WSAEAFNOSUPPORT, .WSAEDESTADDRREQ:
+ return .Invalid_Argument
+ case .WSAEINTR:
+ return .Interrupted
+ case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNRESET:
+ return .Connection_Refused
+ case .WSAEWOULDBLOCK:
+ return .Would_Block
+ case .WSAETIMEDOUT:
+ return .Timeout
+ case:
+ return .Unknown
+ }
}
-Socket_Option_Error :: enum c.int {
- None = 0,
- Linger_Only_Supports_Whole_Seconds = 1,
-
- // The given value is too big or small to be given to the OS.
- Value_Out_Of_Range,
-
- Network_Subsystem_Failure = win.WSAENETDOWN,
- Timeout_When_Keepalive_Set = win.WSAENETRESET,
- Invalid_Option_For_Socket = win.WSAENOPROTOOPT,
- Reset_When_Keepalive_Set = win.WSAENOTCONN,
- Not_Socket = win.WSAENOTSOCK,
+_shutdown_error :: proc() -> Shutdown_Error {
+ #partial switch win.System_Error(win.WSAGetLastError()) {
+ case .WSAENETDOWN, .WSANOTINITIALISED:
+ return .Network_Unreachable
+ case .WSAECONNABORTED, .WSAECONNRESET:
+ return .Connection_Closed
+ case .WSAEINPROGRESS, .WSAEINVAL, .WSAENOTCONN, .WSAENOTSOCK:
+ return .Invalid_Argument
+ case:
+ return .Unknown
+ }
}
-Set_Blocking_Error :: enum c.int {
- None = 0,
+_socket_option_error :: proc() -> Socket_Option_Error {
+ #partial switch win.System_Error(win.WSAGetLastError()) {
+ case .WSAENETDOWN, .WSANOTINITIALISED:
+ return .Network_Unreachable
+ case .WSAEFAULT, .WSAEINVAL:
+ return .Invalid_Value
+ case .WSAENETRESET, .WSAENOTCONN, .WSAENOTSOCK:
+ return .Invalid_Socket
+ case .WSAENOPROTOOPT:
+ return .Invalid_Option
+ case:
+ return .Unknown
+ }
+}
- Network_Subsystem_Failure = win.WSAENETDOWN,
- Blocking_Call_In_Progress = win.WSAEINPROGRESS,
- Not_Socket = win.WSAENOTSOCK,
-
- // TODO: are those errors possible?
- Network_Subsystem_Not_Initialized = win.WSAENOTINITIALISED,
- Invalid_Argument_Pointer = win.WSAEFAULT,
-}
\ No newline at end of file
+_set_blocking_error :: proc() -> Set_Blocking_Error {
+ #partial switch win.System_Error(win.WSAGetLastError()) {
+ case .WSAENETDOWN, .WSANOTINITIALISED:
+ return .Network_Unreachable
+ case .WSAEINPROGRESS, .WSAENOTSOCK, .WSAEFAULT:
+ return .Invalid_Argument
+ case:
+ return .Unknown
+ }
+}
diff --git a/core/net/interface.odin b/core/net/interface.odin
index 775a812f3..4d499a008 100644
--- a/core/net/interface.odin
+++ b/core/net/interface.odin
@@ -27,7 +27,7 @@ MAX_INTERFACE_ENUMERATION_TRIES :: 3
/*
`enumerate_interfaces` retrieves a list of network interfaces with their associated properties.
*/
-enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
+enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) {
return _enumerate_interfaces(allocator)
}
diff --git a/core/net/interface_darwin.odin b/core/net/interface_darwin.odin
index 9aa6cbd1a..f189e5844 100644
--- a/core/net/interface_darwin.odin
+++ b/core/net/interface_darwin.odin
@@ -26,7 +26,7 @@ import "core:sys/posix"
foreign import lib "system:System.framework"
@(private)
-_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
+_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) {
context.allocator = allocator
head: ^ifaddrs
@@ -47,7 +47,7 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []
iface.adapter_name = key_ptr^
}
if mem_err != nil {
- return {}, .Unable_To_Enumerate_Network_Interfaces
+ return {}, .Allocation_Failure
}
address: Address
diff --git a/core/net/interface_freebsd.odin b/core/net/interface_freebsd.odin
index 50e2d1a96..90a538a04 100644
--- a/core/net/interface_freebsd.odin
+++ b/core/net/interface_freebsd.odin
@@ -25,7 +25,7 @@ import "core:strings"
import "core:sys/freebsd"
@(private)
-_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
+_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) {
// This is a simplified implementation of `getifaddrs` from the FreeBSD
// libc using only Odin and syscalls.
context.allocator = allocator
@@ -50,7 +50,7 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []
// Allocate and get the entries.
buf, alloc_err := make([]byte, needed)
if alloc_err != nil {
- return nil, .Unable_To_Enumerate_Network_Interfaces
+ return nil, .Allocation_Failure
}
defer delete(buf)
diff --git a/core/net/interface_linux.odin b/core/net/interface_linux.odin
index 28724735b..e329803c5 100644
--- a/core/net/interface_linux.odin
+++ b/core/net/interface_linux.odin
@@ -30,7 +30,7 @@ package net
// NOTE(flysand): https://man7.org/linux/man-pages/man7/netlink.7.html
// apparently musl libc uses this to enumerate network interfaces
@(private)
-_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
+_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) {
context.allocator = allocator
// head: ^os.ifaddrs
@@ -143,4 +143,4 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []
// }
// return _interfaces[:], {}
return nil, {}
-}
\ No newline at end of file
+}
diff --git a/core/net/interface_windows.odin b/core/net/interface_windows.odin
index a6eb72846..571fb322f 100644
--- a/core/net/interface_windows.odin
+++ b/core/net/interface_windows.odin
@@ -23,7 +23,7 @@ package net
import sys "core:sys/windows"
import strings "core:strings"
-_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
+_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) {
context.allocator = allocator
buf: []u8
@@ -52,7 +52,8 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []
case 0:
break gaa
case:
- return {}, Platform_Error(res)
+ set_last_platform_error(i32(res))
+ return {}, .Unknown
}
}
@@ -63,13 +64,13 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []
_interfaces := make([dynamic]Network_Interface, 0, allocator)
for adapter := (^sys.IP_Adapter_Addresses)(raw_data(buf)); adapter != nil; adapter = adapter.Next {
friendly_name, err1 := sys.wstring_to_utf8(sys.wstring(adapter.FriendlyName), 256, allocator)
- if err1 != nil { return {}, Platform_Error(err1) }
+ if err1 != nil { return {}, .Allocation_Failure }
description, err2 := sys.wstring_to_utf8(sys.wstring(adapter.Description), 256, allocator)
- if err2 != nil { return {}, Platform_Error(err2) }
+ if err2 != nil { return {}, .Allocation_Failure }
dns_suffix, err3 := sys.wstring_to_utf8(sys.wstring(adapter.DnsSuffix), 256, allocator)
- if err3 != nil { return {}, Platform_Error(err3) }
+ if err3 != nil { return {}, .Allocation_Failure }
interface := Network_Interface{
adapter_name = strings.clone(string(adapter.AdapterName)),
@@ -176,4 +177,4 @@ parse_socket_address :: proc(addr_in: sys.SOCKET_ADDRESS) -> (addr: Endpoint) {
case: return // Empty or invalid address type
}
unreachable()
-}
\ No newline at end of file
+}
diff --git a/core/net/socket.odin b/core/net/socket.odin
index c5ea11e11..801693962 100644
--- a/core/net/socket.odin
+++ b/core/net/socket.odin
@@ -35,8 +35,10 @@ any_socket_to_socket :: proc "contextless" (socket: Any_Socket) -> Socket {
`a.host.name:9999`, or as `1.2.3.4:9999`, or IP6 equivalent.
Calls `parse_hostname_or_endpoint` and `dial_tcp_from_host_or_endpoint`.
+
+ Errors that can be returned: `Parse_Endpoint_Error`, `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error`
*/
-dial_tcp_from_hostname_and_port_string :: proc(hostname_and_port: string, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
+dial_tcp_from_hostname_and_port_string :: proc(hostname_and_port: string, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {
target := parse_hostname_or_endpoint(hostname_and_port) or_return
return dial_tcp_from_host_or_endpoint(target, options)
@@ -47,8 +49,10 @@ dial_tcp_from_hostname_and_port_string :: proc(hostname_and_port: string, option
`parse_hostname_or_endpoint` is called and the `hostname` will be resolved into an IP.
If a `hostname` of form `a.host.name:9999` is given, the port will be ignored in favor of the explicit `port` param.
+
+ Errors that can be returned: `Parse_Endpoint_Error`, `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error`
*/
-dial_tcp_from_hostname_with_port_override :: proc(hostname: string, port: int, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
+dial_tcp_from_hostname_with_port_override :: proc(hostname: string, port: int, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {
target := parse_hostname_or_endpoint(hostname) or_return
switch &t in target {
case Endpoint:
@@ -62,8 +66,10 @@ dial_tcp_from_hostname_with_port_override :: proc(hostname: string, port: int, o
/*
Expects the `host` as Host.
+
+ Errors that can be returned: `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error`
*/
-dial_tcp_from_host :: proc(host: Host, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
+dial_tcp_from_host :: proc(host: Host, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {
if host.port == 0 {
return 0, .Port_Required
}
@@ -76,8 +82,10 @@ dial_tcp_from_host :: proc(host: Host, options := default_tcp_options) -> (socke
/*
Expects the `target` as a Host_OrEndpoint.
Unwraps the underlying type and calls `dial_tcp_from_host` or `dial_tcp_from_endpoint`.
+
+ Errors that can be returned: `Parse_Endpoint_Error`, `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error`
*/
-dial_tcp_from_host_or_endpoint :: proc(target: Host_Or_Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
+dial_tcp_from_host_or_endpoint :: proc(target: Host_Or_Endpoint, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {
switch t in target {
case Endpoint:
return dial_tcp_from_endpoint(t, options)
@@ -87,12 +95,21 @@ dial_tcp_from_host_or_endpoint :: proc(target: Host_Or_Endpoint, options := defa
unreachable()
}
-// Dial from an Address
-dial_tcp_from_address_and_port :: proc(address: Address, port: int, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
+/*
+ Dial from an Address.
+
+ Errors that can be returned: `Create_Socket_Error`, or `Dial_Error`
+*/
+dial_tcp_from_address_and_port :: proc(address: Address, port: int, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {
return dial_tcp_from_endpoint({address, port}, options)
}
-dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
+/*
+ Dial from an Endpoint.
+
+ Errors that can be returned: `Create_Socket_Error`, or `Dial_Error`
+*/
+dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {
return _dial_tcp_from_endpoint(endpoint, options)
}
@@ -105,11 +122,11 @@ dial_tcp :: proc{
dial_tcp_from_host_or_endpoint,
}
-create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
+create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) {
return _create_socket(family, protocol)
}
-bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
+bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Bind_Error) {
return _bind(socket, ep)
}
@@ -119,7 +136,7 @@ bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
This is like a client TCP socket, except that it can send data to any remote endpoint without needing to establish a connection first.
*/
-make_unbound_udp_socket :: proc(family: Address_Family) -> (socket: UDP_Socket, err: Network_Error) {
+make_unbound_udp_socket :: proc(family: Address_Family) -> (socket: UDP_Socket, err: Create_Socket_Error) {
sock := create_socket(family, .UDP) or_return
socket = sock.(UDP_Socket)
return
@@ -131,6 +148,8 @@ make_unbound_udp_socket :: proc(family: Address_Family) -> (socket: UDP_Socket,
This is like a listening TCP socket, except that data packets can be sent and received without needing to establish a connection first.
The `bound_address` is the address of the network interface that you want to use, or a loopback address if you don't care which to use.
+
+ Errors that can be returned: `Parse_Endpoint_Error`, `Create_Socket_Error`, or `Bind_Error`
*/
make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (socket: UDP_Socket, err: Network_Error) {
if bound_address == nil {
@@ -141,6 +160,11 @@ make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (socket: UDP
return
}
+/*
+ Creates a TCP socket and starts listening on the given endpoint.
+
+ Errors that can be returned: `Create_Socket_Error`, `Bind_Error`, or `Listen_Error`
+*/
listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, err: Network_Error) {
assert(backlog > 0 && backlog < int(max(i32)))
@@ -150,11 +174,11 @@ listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TC
/*
Returns the endpoint that the given socket is listening / bound on.
*/
-bound_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Network_Error) {
+bound_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Listen_Error) {
return _bound_endpoint(socket)
}
-accept_tcp :: proc(socket: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
+accept_tcp :: proc(socket: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) {
return _accept_tcp(socket, options)
}
@@ -162,11 +186,11 @@ close :: proc(socket: Any_Socket) {
_close(socket)
}
-recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
+recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) {
return _recv_tcp(socket, buf)
}
-recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
+recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) {
return _recv_udp(socket, buf)
}
@@ -175,6 +199,8 @@ recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_en
Note: `remote_endpoint` parameter is non-nil only if the socket type is UDP. On TCP sockets it
will always return `nil`.
+
+ Errors that can be returned: `TCP_Recv_Error`, or `UDP_Recv_Error`
*/
recv_any :: proc(socket: Any_Socket, buf: []byte) -> (
bytes_read: int,
@@ -197,7 +223,7 @@ recv :: proc{recv_tcp, recv_udp, recv_any}
Repeatedly sends data until the entire buffer is sent.
If a send fails before all data is sent, returns the amount sent up to that point.
*/
-send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
+send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) {
return _send_tcp(socket, buf)
}
@@ -207,10 +233,15 @@ send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: N
Datagrams are limited in size; attempting to send more than this limit at once will result in a Message_Too_Long error.
UDP packets are not guarenteed to be received in order.
*/
-send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
+send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) {
return _send_udp(socket, buf, to)
}
+/*
+ Sends data over the socket.
+
+ Errors that can be returned: `TCP_Send_Error`, or `UDP_Send_Error`
+*/
send_any :: proc(socket: Any_Socket, buf: []byte, to: Maybe(Endpoint) = nil) -> (
bytes_written: int,
err: Network_Error,
@@ -226,14 +257,14 @@ send_any :: proc(socket: Any_Socket, buf: []byte, to: Maybe(Endpoint) = nil) ->
send :: proc{send_tcp, send_udp, send_any}
-shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
+shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) {
return _shutdown(socket, manner)
}
-set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
+set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error {
return _set_option(socket, option, value, loc)
}
-set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
+set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) {
return _set_blocking(socket, should_block)
}
diff --git a/core/net/socket_darwin.odin b/core/net/socket_darwin.odin
index a132a6a95..e63f1844a 100644
--- a/core/net/socket_darwin.odin
+++ b/core/net/socket_darwin.odin
@@ -37,8 +37,14 @@ Socket_Option :: enum c.int {
Send_Timeout = c.int(posix.Sock_Option.SNDTIMEO),
}
+Shutdown_Manner :: enum c.int {
+ Receive = c.int(posix.SHUT_RD),
+ Send = c.int(posix.SHUT_WR),
+ Both = c.int(posix.SHUT_RDWR),
+}
+
@(private)
-_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
+_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) {
c_type: posix.Sock
c_protocol: posix.Protocol
c_family: posix.AF
@@ -59,7 +65,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so
sock := posix.socket(c_family, c_type, c_protocol)
if sock < 0 {
- err = Create_Socket_Error(posix.errno())
+ err = _create_socket_error()
return
}
@@ -72,7 +78,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so
}
@(private)
-_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) {
+_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := DEFAULT_TCP_OPTIONS) -> (skt: TCP_Socket, err: Network_Error) {
if endpoint.port == 0 {
return 0, .Port_Required
}
@@ -88,28 +94,19 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
sockaddr := _endpoint_to_sockaddr(endpoint)
if posix.connect(posix.FD(skt), (^posix.sockaddr)(&sockaddr), posix.socklen_t(sockaddr.ss_len)) != .OK {
- errno := posix.errno()
+ err = _dial_error()
close(skt)
- return {}, Dial_Error(errno)
}
return
}
-// On Darwin, any port below 1024 is 'privileged' - which means that you need root access in order to use it.
-MAX_PRIVILEGED_PORT :: 1023
-
@(private)
-_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
+_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Bind_Error) {
sockaddr := _endpoint_to_sockaddr(ep)
s := any_socket_to_socket(skt)
if posix.bind(posix.FD(s), (^posix.sockaddr)(&sockaddr), posix.socklen_t(sockaddr.ss_len)) != .OK {
- errno := posix.errno()
- if errno == .EACCES && ep.port <= MAX_PRIVILEGED_PORT {
- err = .Privileged_Port_Without_Root
- } else {
- err = Bind_Error(errno)
- }
+ err = _bind_error()
}
return
@@ -128,25 +125,23 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_
// bypass the cooldown period, and allow the next run of the program to
// use the same address immediately.
//
- // TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address!
- set_option(sock, .Reuse_Address, true) or_return
+ _ = set_option(sock, .Reuse_Address, true)
bind(sock, interface_endpoint) or_return
if posix.listen(posix.FD(skt), i32(backlog)) != .OK {
- err = Listen_Error(posix.errno())
- return
+ err = _listen_error()
}
return
}
@(private)
-_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) {
+_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) {
addr: posix.sockaddr_storage
addr_len := posix.socklen_t(size_of(addr))
if posix.getsockname(posix.FD(any_socket_to_socket(sock)), (^posix.sockaddr)(&addr), &addr_len) != .OK {
- err = Listen_Error(posix.errno())
+ err = _listen_error()
return
}
@@ -155,12 +150,12 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error)
}
@(private)
-_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
+_accept_tcp :: proc(sock: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) {
addr: posix.sockaddr_storage
addr_len := posix.socklen_t(size_of(addr))
client_sock := posix.accept(posix.FD(sock), (^posix.sockaddr)(&addr), &addr_len)
if client_sock < 0 {
- err = Accept_Error(posix.errno())
+ err = _accept_error()
return
}
@@ -176,14 +171,14 @@ _close :: proc(skt: Any_Socket) {
}
@(private)
-_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
+_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) {
if len(buf) <= 0 {
return
}
res := posix.recv(posix.FD(skt), raw_data(buf), len(buf), {})
if res < 0 {
- err = TCP_Recv_Error(posix.errno())
+ err = _tcp_recv_error()
return
}
@@ -191,7 +186,7 @@ _recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Networ
}
@(private)
-_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
+_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) {
if len(buf) <= 0 {
return
}
@@ -200,7 +195,7 @@ _recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endp
fromsize := posix.socklen_t(size_of(from))
res := posix.recvfrom(posix.FD(skt), raw_data(buf), len(buf), {}, (^posix.sockaddr)(&from), &fromsize)
if res < 0 {
- err = UDP_Recv_Error(posix.errno())
+ err = _udp_recv_error()
return
}
@@ -210,20 +205,13 @@ _recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endp
}
@(private)
-_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
+_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) {
for bytes_written < len(buf) {
limit := min(int(max(i32)), len(buf) - bytes_written)
remaining := buf[bytes_written:][:limit]
res := posix.send(posix.FD(skt), raw_data(remaining), len(remaining), {.NOSIGNAL})
if res < 0 {
- errno := posix.errno()
- if errno == .EPIPE {
- // EPIPE arises if the socket has been closed remotely.
- err = TCP_Send_Error.Connection_Closed
- return
- }
-
- err = TCP_Send_Error(errno)
+ err = _tcp_send_error()
return
}
@@ -233,21 +221,14 @@ _send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Net
}
@(private)
-_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
+_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) {
toaddr := _endpoint_to_sockaddr(to)
for bytes_written < len(buf) {
limit := min(1<<31, len(buf) - bytes_written)
remaining := buf[bytes_written:][:limit]
res := posix.sendto(posix.FD(skt), raw_data(remaining), len(remaining), {.NOSIGNAL}, (^posix.sockaddr)(&toaddr), posix.socklen_t(toaddr.ss_len))
if res < 0 {
- errno := posix.errno()
- if errno == .EPIPE {
- // EPIPE arises if the socket has been closed remotely.
- err = UDP_Send_Error.Not_Socket
- return
- }
-
- err = UDP_Send_Error(errno)
+ err = _udp_send_error()
return
}
@@ -257,16 +238,16 @@ _send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written:
}
@(private)
-_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
+_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) {
s := any_socket_to_socket(skt)
if posix.shutdown(posix.FD(s), posix.Shut(manner)) != .OK {
- err = Shutdown_Error(posix.errno())
+ err = _shutdown_error()
}
return
}
@(private)
-_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
+_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error {
level := posix.SOL_SOCKET if option != .TCP_Nodelay else posix.IPPROTO_TCP
// NOTE(tetra, 2022-02-15): On Linux, you cannot merely give a single byte for a bool;
@@ -337,19 +318,19 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
skt := any_socket_to_socket(s)
if posix.setsockopt(posix.FD(skt), i32(level), posix.Sock_Option(option), ptr, len) != .OK {
- return Socket_Option_Error(posix.errno())
+ return _socket_option_error()
}
return nil
}
@(private)
-_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
+_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) {
socket := any_socket_to_socket(socket)
flags_ := posix.fcntl(posix.FD(socket), .GETFL, 0)
if flags_ < 0 {
- return Set_Blocking_Error(posix.errno())
+ return _set_blocking_error()
}
flags := transmute(posix.O_Flags)flags_
@@ -360,7 +341,7 @@ _set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_E
}
if posix.fcntl(posix.FD(socket), .SETFL, flags) < 0 {
- return Set_Blocking_Error(posix.errno())
+ return _set_blocking_error()
}
return nil
diff --git a/core/net/socket_freebsd.odin b/core/net/socket_freebsd.odin
index 3a3774007..b510346ba 100644
--- a/core/net/socket_freebsd.odin
+++ b/core/net/socket_freebsd.odin
@@ -54,8 +54,14 @@ Socket_Option :: enum c.int {
Receive_Timeout = cast(c.int)freebsd.Socket_Option.RCVTIMEO,
}
+Shutdown_Manner :: enum c.int {
+ Receive = cast(c.int)freebsd.Shutdown_Method.RD,
+ Send = cast(c.int)freebsd.Shutdown_Method.WR,
+ Both = cast(c.int)freebsd.Shutdown_Method.RDWR,
+}
+
@(private)
-_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
+_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) {
sys_family: freebsd.Protocol_Family = ---
sys_protocol: freebsd.Protocol = ---
sys_socket_type: freebsd.Socket_Type = ---
@@ -72,24 +78,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so
new_socket, errno := freebsd.socket(sys_family, sys_socket_type, sys_protocol)
if errno != nil {
- err = cast(Create_Socket_Error)errno
- return
- }
-
- // NOTE(Feoramund): By default, FreeBSD will generate SIGPIPE if an EPIPE
- // error is raised during the writing of a socket that may be closed.
- // This behavior is unlikely to be expected by general users.
- //
- // There are two workarounds. One is to apply the .NOSIGNAL flag when using
- // the `sendto` syscall. However, that would prevent users of this library
- // from re-enabling the SIGPIPE-raising functionality, if they really
- // wanted it.
- //
- // So I have disabled it here with this socket option for all sockets.
- truth: b32 = true
- errno = freebsd.setsockopt(new_socket, .SOCKET, .NOSIGPIPE, &truth, size_of(truth))
- if errno != nil {
- err = cast(Socket_Option_Error)errno
+ err = _create_socket_error(errno)
return
}
@@ -102,7 +91,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so
}
@(private)
-_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
+_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {
if endpoint.port == 0 {
return 0, .Port_Required
}
@@ -115,19 +104,19 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
errno := freebsd.connect(cast(Fd)socket, &sockaddr, cast(freebsd.socklen_t)sockaddr.len)
if errno != nil {
close(socket)
- return {}, cast(Dial_Error)errno
+ return {}, _dial_error(errno)
}
return
}
@(private)
-_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
+_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Bind_Error) {
sockaddr := _endpoint_to_sockaddr(ep)
real_socket := any_socket_to_socket(socket)
errno := freebsd.bind(cast(Fd)real_socket, &sockaddr, cast(freebsd.socklen_t)sockaddr.len)
if errno != nil {
- err = cast(Bind_Error)errno
+ err = _bind_error(errno)
}
return
}
@@ -143,7 +132,7 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
errno := freebsd.listen(cast(Fd)socket, backlog)
if errno != nil {
- err = cast(Listen_Error)errno
+ err = _listen_error(errno)
return
}
@@ -151,12 +140,12 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
}
@(private)
-_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) {
+_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) {
sockaddr: freebsd.Socket_Address_Storage
errno := freebsd.getsockname(cast(Fd)any_socket_to_socket(sock), &sockaddr)
if errno != nil {
- err = cast(Listen_Error)errno
+ err = _listen_error(errno)
return
}
@@ -165,12 +154,12 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error)
}
@(private)
-_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
+_accept_tcp :: proc(sock: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) {
sockaddr: freebsd.Socket_Address_Storage
result, errno := freebsd.accept(cast(Fd)sock, &sockaddr)
if errno != nil {
- err = cast(Accept_Error)errno
+ err = _accept_error(errno)
return
}
@@ -187,20 +176,20 @@ _close :: proc(socket: Any_Socket) {
}
@(private)
-_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
+_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) {
if len(buf) == 0 {
return
}
result, errno := freebsd.recv(cast(Fd)socket, buf, .NONE)
if errno != nil {
- err = cast(TCP_Recv_Error)errno
+ err = _tcp_recv_error(errno)
return
}
return result, nil
}
@(private)
-_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
+_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) {
if len(buf) == 0 {
return
}
@@ -208,21 +197,21 @@ _recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_e
result, errno := freebsd.recvfrom(cast(Fd)socket, buf, .NONE, &from)
if errno != nil {
- err = cast(UDP_Recv_Error)errno
+ err = _udp_recv_error(errno)
return
}
return result, _sockaddr_to_endpoint(&from), nil
}
@(private)
-_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
+_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) {
for bytes_written < len(buf) {
limit := min(int(max(i32)), len(buf) - bytes_written)
remaining := buf[bytes_written:][:limit]
- result, errno := freebsd.send(cast(Fd)socket, remaining, .NONE)
+ result, errno := freebsd.send(cast(Fd)socket, remaining, .NOSIGNAL)
if errno != nil {
- err = cast(TCP_Send_Error)errno
+ err = _tcp_send_error(errno)
return
}
bytes_written += result
@@ -231,15 +220,15 @@ _send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err:
}
@(private)
-_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
+_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) {
toaddr := _endpoint_to_sockaddr(to)
for bytes_written < len(buf) {
limit := min(int(max(i32)), len(buf) - bytes_written)
remaining := buf[bytes_written:][:limit]
- result, errno := freebsd.sendto(cast(Fd)socket, remaining, .NONE, &toaddr)
+ result, errno := freebsd.sendto(cast(Fd)socket, remaining, .NOSIGNAL, &toaddr)
if errno != nil {
- err = cast(UDP_Send_Error)errno
+ err = _udp_send_error(errno)
return
}
bytes_written += result
@@ -248,17 +237,17 @@ _send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_writt
}
@(private)
-_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
+_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) {
real_socket := cast(Fd)any_socket_to_socket(socket)
errno := freebsd.shutdown(real_socket, cast(freebsd.Shutdown_Method)manner)
if errno != nil {
- return cast(Shutdown_Error)errno
+ return _shutdown_error(errno)
}
return
}
@(private)
-_set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
+_set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error {
// NOTE(Feoramund): I found that FreeBSD, like Linux, requires at least 32
// bits for a boolean socket option value. Nothing less will work.
bool_value: b32
@@ -315,25 +304,25 @@ _set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc :
case u16: int_value = cast(i32)real
case i32: int_value = real
case u32:
- if real > u32(max(i32)) { return .Value_Out_Of_Range }
+ if real > u32(max(i32)) { return .Invalid_Value }
int_value = cast(i32)real
case i64:
- if real > i64(max(i32)) || real < i64(min(i32)) { return .Value_Out_Of_Range }
+ if real > i64(max(i32)) || real < i64(min(i32)) { return .Invalid_Value }
int_value = cast(i32)real
case u64:
- if real > u64(max(i32)) { return .Value_Out_Of_Range }
+ if real > u64(max(i32)) { return .Invalid_Value }
int_value = cast(i32)real
case i128:
- if real > i128(max(i32)) || real < i128(min(i32)) { return .Value_Out_Of_Range }
+ if real > i128(max(i32)) || real < i128(min(i32)) { return .Invalid_Value }
int_value = cast(i32)real
case u128:
- if real > u128(max(i32)) { return .Value_Out_Of_Range }
+ if real > u128(max(i32)) { return .Invalid_Value }
int_value = cast(i32)real
case int:
- if real > int(max(i32)) || real < int(min(i32)) { return .Value_Out_Of_Range }
+ if real > int(max(i32)) || real < int(min(i32)) { return .Invalid_Value }
int_value = cast(i32)real
case uint:
- if real > uint(max(i32)) { return .Value_Out_Of_Range }
+ if real > uint(max(i32)) { return .Invalid_Value }
int_value = cast(i32)real
case:
panic("set_option() value must be an integer here", loc)
@@ -347,19 +336,19 @@ _set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc :
real_socket := any_socket_to_socket(socket)
errno := freebsd.setsockopt(cast(Fd)real_socket, .SOCKET, cast(freebsd.Socket_Option)option, ptr, len)
if errno != nil {
- return cast(Socket_Option_Error)errno
+ return _socket_option_error(errno)
}
return nil
}
@(private)
-_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
+_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) {
real_socket := any_socket_to_socket(socket)
flags, errno := freebsd.fcntl_getfl(cast(freebsd.Fd)real_socket)
if errno != nil {
- return cast(Set_Blocking_Error)errno
+ return _set_blocking_error(errno)
}
if should_block {
@@ -370,7 +359,7 @@ _set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_E
errno = freebsd.fcntl_setfl(cast(freebsd.Fd)real_socket, flags)
if errno != nil {
- return cast(Set_Blocking_Error)errno
+ return _set_blocking_error(errno)
}
return
diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin
index cafec747d..3ec3521f0 100644
--- a/core/net/socket_linux.odin
+++ b/core/net/socket_linux.odin
@@ -38,15 +38,21 @@ Socket_Option :: enum c.int {
Broadcast = c.int(linux.Socket_Option.BROADCAST),
}
+Shutdown_Manner :: enum c.int {
+ Receive = c.int(linux.Shutdown_How.RD),
+ Send = c.int(linux.Shutdown_How.WR),
+ Both = c.int(linux.Shutdown_How.RDWR),
+}
+
// Wrappers and unwrappers for system-native types
@(private="file")
-_unwrap_os_socket :: proc "contextless" (sock: Any_Socket)->linux.Fd {
+_unwrap_os_socket :: proc "contextless" (sock: Any_Socket) -> linux.Fd {
return linux.Fd(any_socket_to_socket(sock))
}
@(private="file")
-_wrap_os_socket :: proc "contextless" (sock: linux.Fd, protocol: Socket_Protocol)->Any_Socket {
+_wrap_os_socket :: proc "contextless" (sock: linux.Fd, protocol: Socket_Protocol) -> Any_Socket {
switch protocol {
case .TCP: return TCP_Socket(Socket(sock))
case .UDP: return UDP_Socket(Socket(sock))
@@ -56,7 +62,7 @@ _wrap_os_socket :: proc "contextless" (sock: linux.Fd, protocol: Socket_Protocol
}
@(private="file")
-_unwrap_os_family :: proc "contextless" (family: Address_Family)->linux.Address_Family {
+_unwrap_os_family :: proc "contextless" (family: Address_Family) -> linux.Address_Family {
switch family {
case .IP4: return .INET
case .IP6: return .INET6
@@ -66,7 +72,7 @@ _unwrap_os_family :: proc "contextless" (family: Address_Family)->linux.Address_
}
@(private="file")
-_unwrap_os_proto_socktype :: proc "contextless" (protocol: Socket_Protocol)->(linux.Protocol, linux.Socket_Type) {
+_unwrap_os_proto_socktype :: proc "contextless" (protocol: Socket_Protocol) -> (linux.Protocol, linux.Socket_Type) {
switch protocol {
case .TCP: return .TCP, .STREAM
case .UDP: return .UDP, .DGRAM
@@ -76,7 +82,7 @@ _unwrap_os_proto_socktype :: proc "contextless" (protocol: Socket_Protocol)->(li
}
@(private="file")
-_unwrap_os_addr :: proc "contextless" (endpoint: Endpoint)->(linux.Sock_Addr_Any) {
+_unwrap_os_addr :: proc "contextless" (endpoint: Endpoint) -> linux.Sock_Addr_Any {
switch address in endpoint.address {
case IP4_Address:
return {
@@ -100,7 +106,7 @@ _unwrap_os_addr :: proc "contextless" (endpoint: Endpoint)->(linux.Sock_Addr_Any
}
@(private="file")
-_wrap_os_addr :: proc "contextless" (addr: linux.Sock_Addr_Any)->(Endpoint) {
+_wrap_os_addr :: proc "contextless" (addr: linux.Sock_Addr_Any) -> Endpoint {
#partial switch addr.family {
case .INET:
return {
@@ -117,18 +123,18 @@ _wrap_os_addr :: proc "contextless" (addr: linux.Sock_Addr_Any)->(Endpoint) {
}
}
-_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (Any_Socket, Network_Error) {
+_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (Any_Socket, Create_Socket_Error) {
family := _unwrap_os_family(family)
proto, socktype := _unwrap_os_proto_socktype(protocol)
sock, errno := linux.socket(family, socktype, {.CLOEXEC}, proto)
if errno != .NONE {
- return {}, Create_Socket_Error(errno)
+ return {}, _create_socket_error(errno)
}
return _wrap_os_socket(sock, protocol), nil
}
@(private)
-_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (TCP_Socket, Network_Error) {
+_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := DEFAULT_TCP_OPTIONS) -> (TCP_Socket, Network_Error) {
errno: linux.Errno
if endpoint.port == 0 {
return 0, .Port_Required
@@ -138,7 +144,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
os_sock, errno = linux.socket(_unwrap_os_family(family_from_endpoint(endpoint)), .STREAM, {.CLOEXEC}, .TCP)
if errno != .NONE {
// TODO(flysand): should return invalid file descriptor here casted as TCP_Socket
- return {}, Create_Socket_Error(errno)
+ return {}, _create_socket_error(errno)
}
// NOTE(tetra): This is so that if we crash while the socket is open, we can
// bypass the cooldown period, and allow the next run of the program to
@@ -149,7 +155,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
errno = linux.connect(linux.Fd(os_sock), &addr)
if errno != .NONE {
close(cast(TCP_Socket) os_sock)
- return {}, Dial_Error(errno)
+ return {}, _dial_error(errno)
}
// NOTE(tetra): Not vital to succeed; error ignored
no_delay: b32 = cast(b32) options.no_delay
@@ -158,11 +164,11 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
}
@(private)
-_bind :: proc(sock: Any_Socket, endpoint: Endpoint) -> (Network_Error) {
+_bind :: proc(sock: Any_Socket, endpoint: Endpoint) -> (Bind_Error) {
addr := _unwrap_os_addr(endpoint)
errno := linux.bind(_unwrap_os_socket(sock), &addr)
if errno != .NONE {
- return Bind_Error(errno)
+ return _bind_error(errno)
}
return nil
}
@@ -180,7 +186,7 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket,
os_sock: linux.Fd
os_sock, errno = linux.socket(ep_family, .STREAM, {.CLOEXEC}, .TCP)
if errno != .NONE {
- err = Create_Socket_Error(errno)
+ err = _create_socket_error(errno)
return
}
socket = cast(TCP_Socket)os_sock
@@ -193,31 +199,30 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket,
// TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address!
do_reuse_addr: b32 = true
if errno = linux.setsockopt(os_sock, linux.SOL_SOCKET, linux.Socket_Option.REUSEADDR, &do_reuse_addr); errno != .NONE {
- err = Listen_Error(errno)
+ err = _listen_error(errno)
return
}
// Bind the socket to endpoint address
if errno = linux.bind(os_sock, &ep_address); errno != .NONE {
- err = Bind_Error(errno)
+ err = _bind_error(errno)
return
}
// Listen on bound socket
if errno = linux.listen(os_sock, cast(i32) backlog); errno != .NONE {
- err = Listen_Error(errno)
- return
+ err = _listen_error(errno)
}
return
}
@(private)
-_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) {
+_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) {
addr: linux.Sock_Addr_Any
errno := linux.getsockname(_unwrap_os_socket(sock), &addr)
if errno != .NONE {
- err = Listen_Error(errno)
+ err = _listen_error(errno)
return
}
@@ -226,11 +231,11 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error)
}
@(private)
-_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (tcp_client: TCP_Socket, endpoint: Endpoint, err: Network_Error) {
+_accept_tcp :: proc(sock: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (tcp_client: TCP_Socket, endpoint: Endpoint, err: Accept_Error) {
addr: linux.Sock_Addr_Any
client_sock, errno := linux.accept(linux.Fd(sock), &addr)
if errno != .NONE {
- return {}, {}, Accept_Error(errno)
+ return {}, {}, _accept_error(errno)
}
// NOTE(tetra): Not vital to succeed; error ignored
val: b32 = cast(b32) options.no_delay
@@ -244,19 +249,19 @@ _close :: proc(sock: Any_Socket) {
}
@(private)
-_recv_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) {
+_recv_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, TCP_Recv_Error) {
if len(buf) <= 0 {
return 0, nil
}
bytes_read, errno := linux.recv(linux.Fd(tcp_sock), buf, {})
if errno != .NONE {
- return 0, TCP_Recv_Error(errno)
+ return 0, _tcp_recv_error(errno)
}
return int(bytes_read), nil
}
@(private)
-_recv_udp :: proc(udp_sock: UDP_Socket, buf: []byte) -> (int, Endpoint, Network_Error) {
+_recv_udp :: proc(udp_sock: UDP_Socket, buf: []byte) -> (int, Endpoint, UDP_Recv_Error) {
if len(buf) <= 0 {
// NOTE(flysand): It was returning no error, I didn't change anything
return 0, {}, {}
@@ -268,28 +273,24 @@ _recv_udp :: proc(udp_sock: UDP_Socket, buf: []byte) -> (int, Endpoint, Network_
from_addr: linux.Sock_Addr_Any
bytes_read, errno := linux.recvfrom(linux.Fd(udp_sock), buf, {.TRUNC}, &from_addr)
if errno != .NONE {
- return 0, {}, UDP_Recv_Error(errno)
+ return 0, {}, _udp_recv_error(errno)
}
if bytes_read > len(buf) {
// NOTE(tetra): The buffer has been filled, with a partial message.
- return len(buf), {}, .Buffer_Too_Small
+ return len(buf), {}, .Excess_Truncated
}
return bytes_read, _wrap_os_addr(from_addr), nil
}
@(private)
-_send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) {
+_send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, TCP_Send_Error) {
total_written := 0
for total_written < len(buf) {
limit := min(int(max(i32)), len(buf) - total_written)
remaining := buf[total_written:][:limit]
res, errno := linux.send(linux.Fd(tcp_sock), remaining, {.NOSIGNAL})
- if errno == .EPIPE {
- // If the peer is disconnected when we are trying to send we will get an `EPIPE` error,
- // so we turn that into a clearer error
- return total_written, TCP_Send_Error.Connection_Closed
- } else if errno != .NONE {
- return total_written, TCP_Send_Error(errno)
+ if errno != .NONE {
+ return total_written, _tcp_send_error(errno)
}
total_written += int(res)
}
@@ -297,28 +298,28 @@ _send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) {
}
@(private)
-_send_udp :: proc(udp_sock: UDP_Socket, buf: []byte, to: Endpoint) -> (int, Network_Error) {
+_send_udp :: proc(udp_sock: UDP_Socket, buf: []byte, to: Endpoint) -> (int, UDP_Send_Error) {
to_addr := _unwrap_os_addr(to)
bytes_written, errno := linux.sendto(linux.Fd(udp_sock), buf, {}, &to_addr)
if errno != .NONE {
- return bytes_written, UDP_Send_Error(errno)
+ return bytes_written, _udp_send_error(errno)
}
return int(bytes_written), nil
}
@(private)
-_shutdown :: proc(sock: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
+_shutdown :: proc(sock: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) {
os_sock := _unwrap_os_socket(sock)
errno := linux.shutdown(os_sock, cast(linux.Shutdown_How) manner)
if errno != .NONE {
- return Shutdown_Error(errno)
+ return _shutdown_error(errno)
}
return nil
}
// TODO(flysand): Figure out what we want to do with this on core:sys/ level.
@(private)
-_set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
+_set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error {
level: int
if option == .TCP_Nodelay {
level = int(linux.SOL_TCP)
@@ -388,19 +389,19 @@ _set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc :=
errno = linux.setsockopt(os_sock, level, int(option), &int_value)
}
if errno != .NONE {
- return Socket_Option_Error(errno)
+ return _socket_option_error(errno)
}
return nil
}
@(private)
-_set_blocking :: proc(sock: Any_Socket, should_block: bool) -> (err: Network_Error) {
+_set_blocking :: proc(sock: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) {
errno: linux.Errno
flags: linux.Open_Flags
os_sock := _unwrap_os_socket(sock)
flags, errno = linux.fcntl(os_sock, linux.F_GETFL)
if errno != .NONE {
- return Set_Blocking_Error(errno)
+ return _set_blocking_error(errno)
}
if should_block {
flags -= {.NONBLOCK}
@@ -409,7 +410,7 @@ _set_blocking :: proc(sock: Any_Socket, should_block: bool) -> (err: Network_Err
}
errno = linux.fcntl(os_sock, linux.F_SETFL, flags)
if errno != .NONE {
- return Set_Blocking_Error(errno)
+ return _set_blocking_error(errno)
}
return nil
}
diff --git a/core/net/socket_windows.odin b/core/net/socket_windows.odin
index f19be536a..4576149de 100644
--- a/core/net/socket_windows.odin
+++ b/core/net/socket_windows.odin
@@ -24,13 +24,67 @@ import "core:c"
import win "core:sys/windows"
import "core:time"
+Socket_Option :: enum c.int {
+ // bool: Whether the address that this socket is bound to can be reused by other sockets.
+ // This allows you to bypass the cooldown period if a program dies while the socket is bound.
+ Reuse_Address = win.SO_REUSEADDR,
+
+ // bool: Whether other programs will be inhibited from binding the same endpoint as this socket.
+ Exclusive_Addr_Use = win.SO_EXCLUSIVEADDRUSE,
+
+ // bool: When true, keepalive packets will be automatically be sent for this connection. TODO: verify this understanding
+ Keep_Alive = win.SO_KEEPALIVE,
+
+ // bool: When true, client connections will immediately be sent a TCP/IP RST response, rather than being accepted.
+ Conditional_Accept = win.SO_CONDITIONAL_ACCEPT,
+
+ // bool: If true, when the socket is closed, but data is still waiting to be sent, discard that data.
+ Dont_Linger = win.SO_DONTLINGER,
+
+ // bool: When true, 'out-of-band' data sent over the socket will be read by a normal net.recv() call, the same as normal 'in-band' data.
+ Out_Of_Bounds_Data_Inline = win.SO_OOBINLINE,
+
+ // bool: When true, disables send-coalescing, therefore reducing latency.
+ TCP_Nodelay = win.TCP_NODELAY,
+
+ // win.LINGER: Customizes how long (if at all) the socket will remain open when there
+ // is some remaining data waiting to be sent, and net.close() is called.
+ Linger = win.SO_LINGER,
+
+ // win.DWORD: The size, in bytes, of the OS-managed receive-buffer for this socket.
+ Receive_Buffer_Size = win.SO_RCVBUF,
+
+ // win.DWORD: The size, in bytes, of the OS-managed send-buffer for this socket.
+ Send_Buffer_Size = win.SO_SNDBUF,
+
+ // win.DWORD: For blocking sockets, the time in milliseconds to wait for incoming data to be received, before giving up and returning .Timeout.
+ // For non-blocking sockets, ignored.
+ // Use a value of zero to potentially wait forever.
+ Receive_Timeout = win.SO_RCVTIMEO,
+
+ // win.DWORD: For blocking sockets, the time in milliseconds to wait for outgoing data to be sent, before giving up and returning .Timeout.
+ // For non-blocking sockets, ignored.
+ // Use a value of zero to potentially wait forever.
+ Send_Timeout = win.SO_SNDTIMEO,
+
+ // bool: Allow sending to, receiving from, and binding to, a broadcast address.
+ Broadcast = win.SO_BROADCAST,
+}
+
+
+Shutdown_Manner :: enum c.int {
+ Receive = win.SD_RECEIVE,
+ Send = win.SD_SEND,
+ Both = win.SD_BOTH,
+}
+
@(init, private)
ensure_winsock_initialized :: proc() {
win.ensure_winsock_initialized()
}
@(private)
-_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
+_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) {
c_type, c_protocol, c_family: c.int
switch family {
@@ -49,7 +103,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so
sock := win.socket(c_family, c_type, c_protocol)
if sock == win.INVALID_SOCKET {
- err = Create_Socket_Error(win.WSAGetLastError())
+ err = _create_socket_error()
return
}
@@ -62,7 +116,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so
}
@(private)
-_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
+_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) {
if endpoint.port == 0 {
err = .Port_Required
return
@@ -80,7 +134,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
sockaddr := _endpoint_to_sockaddr(endpoint)
res := win.connect(win.SOCKET(socket), &sockaddr, size_of(sockaddr))
if res < 0 {
- err = Dial_Error(win.WSAGetLastError())
+ err = _dial_error()
close(socket)
return {}, err
}
@@ -93,12 +147,12 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio
}
@(private)
-_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
+_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Bind_Error) {
sockaddr := _endpoint_to_sockaddr(ep)
sock := any_socket_to_socket(socket)
res := win.bind(win.SOCKET(sock), &sockaddr, size_of(sockaddr))
if res < 0 {
- err = Bind_Error(win.WSAGetLastError())
+ err = _bind_error()
}
return
}
@@ -117,17 +171,17 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T
bind(sock, interface_endpoint) or_return
if res := win.listen(win.SOCKET(socket), i32(backlog)); res == win.SOCKET_ERROR {
- err = Listen_Error(win.WSAGetLastError())
+ err = _listen_error()
}
return
}
@(private)
-_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) {
+_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Listen_Error) {
sockaddr: win.SOCKADDR_STORAGE_LH
sockaddrlen := c.int(size_of(sockaddr))
if win.getsockname(win.SOCKET(any_socket_to_socket(sock)), &sockaddr, &sockaddrlen) == win.SOCKET_ERROR {
- err = Listen_Error(win.WSAGetLastError())
+ err = _listen_error()
return
}
@@ -136,7 +190,7 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error)
}
@(private)
-_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
+_accept_tcp :: proc(sock: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) {
for {
sockaddr: win.SOCKADDR_STORAGE_LH
sockaddrlen := c.int(size_of(sockaddr))
@@ -150,7 +204,7 @@ _accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client
// can do this to match the behaviour.
continue
}
- err = Accept_Error(e)
+ err = _accept_error()
return
}
client = TCP_Socket(client_sock)
@@ -170,20 +224,20 @@ _close :: proc(socket: Any_Socket) {
}
@(private)
-_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
+_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) {
if len(buf) <= 0 {
return
}
res := win.recv(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0)
if res < 0 {
- err = TCP_Recv_Error(win.WSAGetLastError())
+ err = _tcp_recv_error()
return
}
return int(res), nil
}
@(private)
-_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
+_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) {
if len(buf) <= 0 {
return
}
@@ -192,7 +246,7 @@ _recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_e
fromsize := c.int(size_of(from))
res := win.recvfrom(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &from, &fromsize)
if res < 0 {
- err = UDP_Recv_Error(win.WSAGetLastError())
+ err = _udp_recv_error()
return
}
@@ -202,13 +256,13 @@ _recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_e
}
@(private)
-_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
+_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) {
for bytes_written < len(buf) {
limit := min(int(max(i32)), len(buf) - bytes_written)
remaining := buf[bytes_written:]
res := win.send(win.SOCKET(socket), raw_data(remaining), c.int(limit), 0)
if res < 0 {
- err = TCP_Send_Error(win.WSAGetLastError())
+ err = _tcp_send_error()
return
}
bytes_written += int(res)
@@ -217,34 +271,34 @@ _send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err:
}
@(private)
-_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
- if len(buf) > int(max(c.int)) {
- // NOTE(tetra): If we don't guard this, we'll return (0, nil) instead, which is misleading.
- err = .Message_Too_Long
- return
- }
+_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) {
toaddr := _endpoint_to_sockaddr(to)
- res := win.sendto(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &toaddr, size_of(toaddr))
- if res < 0 {
- err = UDP_Send_Error(win.WSAGetLastError())
- return
+ for bytes_written < len(buf) {
+ limit := min(int(max(i32)), len(buf) - bytes_written)
+ remaining := buf[bytes_written:]
+ res := win.sendto(win.SOCKET(socket), raw_data(remaining), c.int(limit), 0, &toaddr, size_of(toaddr))
+ if res < 0 {
+ err = _udp_send_error()
+ return
+ }
+
+ bytes_written += int(res)
}
- bytes_written = int(res)
return
}
@(private)
-_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
+_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) {
s := any_socket_to_socket(socket)
res := win.shutdown(win.SOCKET(s), c.int(manner))
if res < 0 {
- return Shutdown_Error(win.WSAGetLastError())
+ return _shutdown_error()
}
return
}
@(private)
-_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
+_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error {
level := win.SOL_SOCKET if option != .TCP_Nodelay else win.IPPROTO_TCP
bool_value: b32
@@ -283,11 +337,8 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
t := value.(time.Duration) or_else panic("set_option() value must be a time.Duration here", loc)
num_secs := i64(time.duration_seconds(t))
- if time.Duration(num_secs * 1e9) != t {
- return .Linger_Only_Supports_Whole_Seconds
- }
if num_secs > i64(max(u16)) {
- return .Value_Out_Of_Range
+ return .Invalid_Value
}
linger_value.l_onoff = 1
linger_value.l_linger = c.ushort(num_secs)
@@ -323,19 +374,19 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca
socket := any_socket_to_socket(s)
res := win.setsockopt(win.SOCKET(socket), c.int(level), c.int(option), ptr, len)
if res < 0 {
- return Socket_Option_Error(win.WSAGetLastError())
+ return _socket_option_error()
}
return nil
}
@(private)
-_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
+_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) {
socket := any_socket_to_socket(socket)
arg: win.DWORD = 0 if should_block else 1
res := win.ioctlsocket(win.SOCKET(socket), transmute(win.c_long)win.FIONBIO, &arg)
if res == win.SOCKET_ERROR {
- return Set_Blocking_Error(win.WSAGetLastError())
+ return _set_blocking_error()
}
return nil
diff --git a/core/odin/doc-format/doc_format.odin b/core/odin/doc-format/doc_format.odin
index c2d86a0ba..e6804c981 100644
--- a/core/odin/doc-format/doc_format.odin
+++ b/core/odin/doc-format/doc_format.odin
@@ -180,7 +180,7 @@ Type_Kind :: enum u32le {
Struct = 10,
Union = 11,
Enum = 12,
- Tuple = 13,
+ Parameters = 13,
Proc = 14,
Bit_Set = 15,
Simd_Vector = 16,
@@ -256,10 +256,10 @@ Type :: struct {
types: Array(Type_Index),
// Used by:
- // .Named - 1 field for the definition
- // .Struct - fields
- // .Enum - fields
- // .Tuple - parameters (procedures only)
+ // .Named - 1 field for the definition
+ // .Struct - fields
+ // .Enum - fields
+ // .Parameters - parameters (procedures only)
entities: Array(Entity_Index),
// Used By: .Struct, .Union
diff --git a/core/odin/parser/file_tags.odin b/core/odin/parser/file_tags.odin
index c5c6637c3..24aea3b9e 100644
--- a/core/odin/parser/file_tags.odin
+++ b/core/odin/parser/file_tags.odin
@@ -30,14 +30,27 @@ File_Tags :: struct {
}
@require_results
-get_build_os_from_string :: proc(str: string) -> runtime.Odin_OS_Type {
+get_build_os_from_string :: proc(str: string) -> (found_os: runtime.Odin_OS_Type, found_subtarget: runtime.Odin_Platform_Subtarget_Type) {
+ str_os, _, str_subtarget := strings.partition(str, ":")
+
fields := reflect.enum_fields_zipped(runtime.Odin_OS_Type)
for os in fields {
- if strings.equal_fold(os.name, str) {
- return runtime.Odin_OS_Type(os.value)
+ if strings.equal_fold(os.name, str_os) {
+ found_os = runtime.Odin_OS_Type(os.value)
+ break
}
}
- return .Unknown
+ if str_subtarget != "" {
+ st_fields := reflect.enum_fields_zipped(runtime.Odin_Platform_Subtarget_Type)
+ for subtarget in st_fields {
+ if strings.equal_fold(subtarget.name, str_subtarget) {
+ found_subtarget = runtime.Odin_Platform_Subtarget_Type(subtarget.value)
+ break
+ }
+ }
+ }
+
+ return
}
@require_results
get_build_arch_from_string :: proc(str: string) -> runtime.Odin_Arch_Type {
@@ -187,7 +200,8 @@ parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags
if value == "ignore" {
tags.ignore = true
- } else if os := get_build_os_from_string(value); os != .Unknown {
+ } else if os, subtarget := get_build_os_from_string(value); os != .Unknown {
+ _ = subtarget // TODO(bill): figure out how to handle the subtarget logic
if is_notted {
os_negative += {os}
} else {
diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin
index 63c7e388f..7f1f4ca87 100644
--- a/core/odin/parser/parser.odin
+++ b/core/odin/parser/parser.odin
@@ -1276,28 +1276,28 @@ parse_unrolled_for_loop :: proc(p: ^Parser, inline_tok: tokenizer.Token) -> ^ast
args = make([dynamic]^ast.Expr)
for p.curr_tok.kind != .Close_Paren &&
p.curr_tok.kind != .EOF {
- arg := parse_value(p)
+ arg := parse_value(p)
- if p.curr_tok.kind == .Eq {
- eq := expect_token(p, .Eq)
- if arg != nil {
- if _, ok := arg.derived.(^ast.Ident); !ok {
- error(p, arg.pos, "expected an identifier for 'key=value'")
- }
- }
- value := parse_value(p)
- fv := ast.new(ast.Field_Value, arg.pos, value)
- fv.field = arg
- fv.sep = eq.pos
- fv.value = value
+ if p.curr_tok.kind == .Eq {
+ eq := expect_token(p, .Eq)
+ if arg != nil {
+ if _, ok := arg.derived.(^ast.Ident); !ok {
+ error(p, arg.pos, "expected an identifier for 'key=value'")
+ }
+ }
+ value := parse_value(p)
+ fv := ast.new(ast.Field_Value, arg.pos, value)
+ fv.field = arg
+ fv.sep = eq.pos
+ fv.value = value
- arg = fv
- }
+ arg = fv
+ }
- append(&args, arg)
+ append(&args, arg)
allow_token(p, .Comma) or_break
- }
+ }
}
p.expr_level -= 1
diff --git a/core/os/os2/allocators.odin b/core/os/os2/allocators.odin
index 864532850..cedfbdee1 100644
--- a/core/os/os2/allocators.odin
+++ b/core/os/os2/allocators.odin
@@ -8,43 +8,13 @@ file_allocator :: proc() -> runtime.Allocator {
return heap_allocator()
}
-temp_allocator_proc :: runtime.arena_allocator_proc
-
@(private="file")
MAX_TEMP_ARENA_COUNT :: 2
-
+@(private="file")
+MAX_TEMP_ARENA_COLLISIONS :: MAX_TEMP_ARENA_COUNT - 1
@(private="file", thread_local)
global_default_temp_allocator_arenas: [MAX_TEMP_ARENA_COUNT]runtime.Arena
-@(private="file", thread_local)
-global_default_temp_allocator_index: uint
-
-
-@(require_results)
-temp_allocator :: proc() -> runtime.Allocator {
- arena := &global_default_temp_allocator_arenas[global_default_temp_allocator_index]
- if arena.backing_allocator.procedure == nil {
- arena.backing_allocator = heap_allocator()
- }
-
- return runtime.Allocator{
- procedure = temp_allocator_proc,
- data = arena,
- }
-}
-
-
-
-@(require_results)
-temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: runtime.Arena_Temp) {
- temp = runtime.arena_temp_begin(&global_default_temp_allocator_arenas[global_default_temp_allocator_index], loc)
- return
-}
-
-temp_allocator_temp_end :: proc(temp: runtime.Arena_Temp, loc := #caller_location) {
- runtime.arena_temp_end(temp, loc)
-}
-
@(fini, private)
temp_allocator_fini :: proc() {
for &arena in global_default_temp_allocator_arenas {
@@ -53,18 +23,49 @@ temp_allocator_fini :: proc() {
global_default_temp_allocator_arenas = {}
}
-TEMP_ALLOCATOR_GUARD_END :: proc(temp: runtime.Arena_Temp, loc := #caller_location) {
- runtime.arena_temp_end(temp, loc)
- if temp.arena != nil {
- global_default_temp_allocator_index = (global_default_temp_allocator_index-1)%MAX_TEMP_ARENA_COUNT
- }
+Temp_Allocator :: struct {
+ using arena: ^runtime.Arena,
+ using allocator: runtime.Allocator,
+ tmp: runtime.Arena_Temp,
+ loc: runtime.Source_Code_Location,
+}
+
+TEMP_ALLOCATOR_GUARD_END :: proc(temp: Temp_Allocator) {
+ runtime.arena_temp_end(temp.tmp, temp.loc)
}
@(deferred_out=TEMP_ALLOCATOR_GUARD_END)
-TEMP_ALLOCATOR_GUARD :: #force_inline proc(loc := #caller_location) -> (runtime.Arena_Temp, runtime.Source_Code_Location) {
- global_default_temp_allocator_index = (global_default_temp_allocator_index+1)%MAX_TEMP_ARENA_COUNT
- tmp := temp_allocator_temp_begin(loc)
- return tmp, loc
+TEMP_ALLOCATOR_GUARD :: #force_inline proc(collisions: []runtime.Allocator, loc := #caller_location) -> Temp_Allocator {
+ assert(len(collisions) <= MAX_TEMP_ARENA_COLLISIONS, "Maximum collision count exceeded. MAX_TEMP_ARENA_COUNT must be increased!")
+ good_arena: ^runtime.Arena
+ for i in 0.. (runtime.Arena_Temp) {
+ return temp_allocator_begin(tmp.arena)
+}
+@(private="file")
+_temp_allocator_end :: proc(tmp: runtime.Arena_Temp) {
+ temp_allocator_end(tmp)
}
@(init, private)
diff --git a/core/os/os2/dir.odin b/core/os/os2/dir.odin
index 4a7762ded..10b06a8ce 100644
--- a/core/os/os2/dir.odin
+++ b/core/os/os2/dir.odin
@@ -2,6 +2,7 @@ package os2
import "base:runtime"
import "core:slice"
+import "core:strings"
read_dir :: read_directory
@@ -18,12 +19,12 @@ read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files
size = 100
}
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
it := read_directory_iterator_create(f)
defer _read_directory_iterator_destroy(&it)
- dfi := make([dynamic]File_Info, 0, size, temp_allocator())
+ dfi := make([dynamic]File_Info, 0, size, temp_allocator)
defer if err != nil {
for fi in dfi {
file_info_delete(fi, allocator)
@@ -192,3 +193,56 @@ read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info,
return _read_directory_iterator(it)
}
+
+// Recursively copies a directory to `dst` from `src`
+copy_directory_all :: proc(dst, src: string, dst_perm := 0o755) -> Error {
+ when #defined(_copy_directory_all_native) {
+ return _copy_directory_all_native(dst, src, dst_perm)
+ } else {
+ return _copy_directory_all(dst, src, dst_perm)
+ }
+}
+
+@(private)
+_copy_directory_all :: proc(dst, src: string, dst_perm := 0o755) -> Error {
+ err := make_directory(dst, dst_perm)
+ if err != nil && err != .Exist {
+ return err
+ }
+
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+
+ abs_src := get_absolute_path(src, temp_allocator) or_return
+ abs_dst := get_absolute_path(dst, temp_allocator) or_return
+
+ dst_buf := make([dynamic]byte, 0, len(abs_dst) + 256, temp_allocator) or_return
+
+ w: Walker
+ walker_init_path(&w, src)
+ defer walker_destroy(&w)
+
+ for info in walker_walk(&w) {
+ _ = walker_error(&w) or_break
+
+ rel := strings.trim_prefix(info.fullpath, abs_src)
+
+ non_zero_resize(&dst_buf, 0)
+ reserve(&dst_buf, len(abs_dst) + len(Path_Separator_String) + len(rel)) or_return
+ append(&dst_buf, abs_dst)
+ append(&dst_buf, Path_Separator_String)
+ append(&dst_buf, rel)
+
+ if info.type == .Directory {
+ err = make_directory(string(dst_buf[:]), dst_perm)
+ if err != nil && err != .Exist {
+ return err
+ }
+ } else {
+ copy_file(string(dst_buf[:]), info.fullpath) or_return
+ }
+ }
+
+ _ = walker_error(&w) or_return
+
+ return nil
+}
diff --git a/core/os/os2/dir_linux.odin b/core/os/os2/dir_linux.odin
index a868a02c4..34346c02f 100644
--- a/core/os/os2/dir_linux.odin
+++ b/core/os/os2/dir_linux.odin
@@ -78,7 +78,8 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info
it.impl.prev_fi = fi
if err != nil {
- path, _ := _get_full_path(entry_fd, temp_allocator())
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ path, _ := _get_full_path(entry_fd, temp_allocator)
read_directory_iterator_set_error(it, path, err)
}
diff --git a/core/os/os2/dir_posix_darwin.odin b/core/os/os2/dir_posix_darwin.odin
new file mode 100644
index 000000000..3cae50d25
--- /dev/null
+++ b/core/os/os2/dir_posix_darwin.odin
@@ -0,0 +1,17 @@
+#+private
+package os2
+
+import "core:sys/darwin"
+
+_copy_directory_all_native :: proc(dst, src: string, dst_perm := 0o755) -> (err: Error) {
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+
+ csrc := clone_to_cstring(src, temp_allocator) or_return
+ cdst := clone_to_cstring(dst, temp_allocator) or_return
+
+ if darwin.copyfile(csrc, cdst, nil, darwin.COPYFILE_ALL + {.RECURSIVE}) < 0 {
+ err = _get_platform_error()
+ }
+
+ return
+}
diff --git a/core/os/os2/dir_windows.odin b/core/os/os2/dir_windows.odin
index d592e8036..4cf1f8396 100644
--- a/core/os/os2/dir_windows.odin
+++ b/core/os/os2/dir_windows.odin
@@ -14,7 +14,9 @@ find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, al
if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 {
return
}
- path := concatenate({base_path, `\`, win32_utf16_to_utf8(d.cFileName[:], temp_allocator()) or_else ""}, allocator) or_return
+
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+ path := concatenate({base_path, `\`, win32_wstring_to_utf8(raw_data(d.cFileName[:]), temp_allocator) or_else ""}, allocator) or_return
handle := win32.HANDLE(_open_internal(path, {.Read}, 0o666) or_else 0)
defer win32.CloseHandle(handle)
@@ -49,8 +51,6 @@ Read_Directory_Iterator_Impl :: struct {
@(require_results)
_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) {
- TEMP_ALLOCATOR_GUARD()
-
for !it.impl.no_more_files {
err: Error
file_info_delete(it.impl.prev_fi, file_allocator())
@@ -116,9 +116,9 @@ _read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) {
wpath = impl.wname[:i]
}
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
- wpath_search := make([]u16, len(wpath)+3, temp_allocator())
+ wpath_search := make([]u16, len(wpath)+3, temp_allocator)
copy(wpath_search, wpath)
wpath_search[len(wpath)+0] = '\\'
wpath_search[len(wpath)+1] = '*'
diff --git a/core/os/os2/env_posix.odin b/core/os/os2/env_posix.odin
index 9661768b4..13682f76b 100644
--- a/core/os/os2/env_posix.odin
+++ b/core/os/os2/env_posix.odin
@@ -12,9 +12,9 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
return
}
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
- ckey := strings.clone_to_cstring(key, temp_allocator())
+ ckey := strings.clone_to_cstring(key, temp_allocator)
cval := posix.getenv(ckey)
if cval == nil {
return
@@ -27,10 +27,10 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
}
_set_env :: proc(key, value: string) -> (err: Error) {
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
- ckey := strings.clone_to_cstring(key, temp_allocator()) or_return
- cval := strings.clone_to_cstring(key, temp_allocator()) or_return
+ ckey := strings.clone_to_cstring(key, temp_allocator) or_return
+ cval := strings.clone_to_cstring(value, temp_allocator) or_return
if posix.setenv(ckey, cval, true) != nil {
err = _get_platform_error_from_errno()
@@ -39,9 +39,9 @@ _set_env :: proc(key, value: string) -> (err: Error) {
}
_unset_env :: proc(key: string) -> (ok: bool) {
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
- ckey := strings.clone_to_cstring(key, temp_allocator())
+ ckey := strings.clone_to_cstring(key, temp_allocator)
ok = posix.unsetenv(ckey) == .OK
return
diff --git a/core/os/os2/env_wasi.odin b/core/os/os2/env_wasi.odin
index 305192c92..faa54e36b 100644
--- a/core/os/os2/env_wasi.odin
+++ b/core/os/os2/env_wasi.odin
@@ -39,9 +39,9 @@ build_env :: proc() -> (err: Error) {
g_env_buf = make([]byte, size_of_envs, file_allocator()) or_return
defer if err != nil { delete(g_env_buf, file_allocator()) }
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
- envs := make([]cstring, num_envs, temp_allocator()) or_return
+ envs := make([]cstring, num_envs, temp_allocator) or_return
_err = wasi.environ_get(raw_data(envs), raw_data(g_env_buf))
if _err != nil {
diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin
index 3ac26a261..6bfde34bb 100644
--- a/core/os/os2/env_windows.odin
+++ b/core/os/os2/env_windows.odin
@@ -8,8 +8,8 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
if key == "" {
return
}
- TEMP_ALLOCATOR_GUARD()
- wkey, _ := win32_utf8_to_wstring(key, temp_allocator())
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+ wkey, _ := win32_utf8_to_wstring(key, temp_allocator)
n := win32.GetEnvironmentVariableW(wkey, nil, 0)
if n == 0 {
@@ -20,7 +20,7 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
return "", true
}
- b := make([]u16, n+1, temp_allocator())
+ b := make([]u16, n+1, temp_allocator)
n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b)))
if n == 0 {
@@ -37,9 +37,9 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string
}
_set_env :: proc(key, value: string) -> Error {
- TEMP_ALLOCATOR_GUARD()
- k := win32_utf8_to_wstring(key, temp_allocator()) or_return
- v := win32_utf8_to_wstring(value, temp_allocator()) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ k := win32_utf8_to_wstring(key, temp_allocator) or_return
+ v := win32_utf8_to_wstring(value, temp_allocator) or_return
if !win32.SetEnvironmentVariableW(k, v) {
return _get_platform_error()
@@ -48,14 +48,14 @@ _set_env :: proc(key, value: string) -> Error {
}
_unset_env :: proc(key: string) -> bool {
- TEMP_ALLOCATOR_GUARD()
- k, _ := win32_utf8_to_wstring(key, temp_allocator())
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ k, _ := win32_utf8_to_wstring(key, temp_allocator)
return bool(win32.SetEnvironmentVariableW(k, nil))
}
_clear_env :: proc() {
- TEMP_ALLOCATOR_GUARD()
- envs, _ := environ(temp_allocator())
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ envs, _ := environ(temp_allocator)
for env in envs {
for j in 1.. string {
}
print_error :: proc(f: ^File, ferr: Error, msg: string) {
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
err_str := error_string(ferr)
// msg + ": " + err_str + '\n'
length := len(msg) + 2 + len(err_str) + 1
- buf := make([]u8, length, temp_allocator())
+ buf := make([]u8, length, temp_allocator)
copy(buf, msg)
buf[len(msg)] = ':'
diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin
index 28d2bc69b..a9878a563 100644
--- a/core/os/os2/file.odin
+++ b/core/os/os2/file.odin
@@ -291,8 +291,8 @@ exists :: proc(path: string) -> bool {
@(require_results)
is_file :: proc(path: string) -> bool {
- TEMP_ALLOCATOR_GUARD()
- fi, err := stat(path, temp_allocator())
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ fi, err := stat(path, temp_allocator)
if err != nil {
return false
}
@@ -303,8 +303,8 @@ is_dir :: is_directory
@(require_results)
is_directory :: proc(path: string) -> bool {
- TEMP_ALLOCATOR_GUARD()
- fi, err := stat(path, temp_allocator())
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ fi, err := stat(path, temp_allocator)
if err != nil {
return false
}
@@ -313,6 +313,15 @@ is_directory :: proc(path: string) -> bool {
copy_file :: proc(dst_path, src_path: string) -> Error {
+ when #defined(_copy_file_native) {
+ return _copy_file_native(dst_path, src_path)
+ } else {
+ return _copy_file(dst_path, src_path)
+ }
+}
+
+@(private)
+_copy_file :: proc(dst_path, src_path: string) -> Error {
src := open(src_path) or_return
defer close(src)
diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin
index 811ee7055..a1ead7f9f 100644
--- a/core/os/os2/file_linux.odin
+++ b/core/os/os2/file_linux.odin
@@ -66,8 +66,8 @@ _standard_stream_init :: proc() {
}
_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) {
- TEMP_ALLOCATOR_GUARD()
- name_cstr := temp_cstring(name) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ name_cstr := clone_to_cstring(name, temp_allocator) or_return
// Just default to using O_NOCTTY because needing to open a controlling
// terminal would be incredibly rare. This has no effect on files while
@@ -299,8 +299,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
}
_remove :: proc(name: string) -> Error {
- TEMP_ALLOCATOR_GUARD()
- name_cstr := temp_cstring(name) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ name_cstr := clone_to_cstring(name, temp_allocator) or_return
if fd, errno := linux.open(name_cstr, _OPENDIR_FLAGS + {.NOFOLLOW}); errno == .NONE {
linux.close(fd)
@@ -311,25 +311,25 @@ _remove :: proc(name: string) -> Error {
}
_rename :: proc(old_name, new_name: string) -> Error {
- TEMP_ALLOCATOR_GUARD()
- old_name_cstr := temp_cstring(old_name) or_return
- new_name_cstr := temp_cstring(new_name) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return
+ new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return
return _get_platform_error(linux.rename(old_name_cstr, new_name_cstr))
}
_link :: proc(old_name, new_name: string) -> Error {
- TEMP_ALLOCATOR_GUARD()
- old_name_cstr := temp_cstring(old_name) or_return
- new_name_cstr := temp_cstring(new_name) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return
+ new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return
return _get_platform_error(linux.link(old_name_cstr, new_name_cstr))
}
_symlink :: proc(old_name, new_name: string) -> Error {
- TEMP_ALLOCATOR_GUARD()
- old_name_cstr := temp_cstring(old_name) or_return
- new_name_cstr := temp_cstring(new_name) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return
+ new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return
return _get_platform_error(linux.symlink(old_name_cstr, new_name_cstr))
}
@@ -352,14 +352,14 @@ _read_link_cstr :: proc(name_cstr: cstring, allocator: runtime.Allocator) -> (st
}
_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, e: Error) {
- TEMP_ALLOCATOR_GUARD()
- name_cstr := temp_cstring(name) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+ name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _read_link_cstr(name_cstr, allocator)
}
_chdir :: proc(name: string) -> Error {
- TEMP_ALLOCATOR_GUARD()
- name_cstr := temp_cstring(name) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _get_platform_error(linux.chdir(name_cstr))
}
@@ -369,8 +369,8 @@ _fchdir :: proc(f: ^File) -> Error {
}
_chmod :: proc(name: string, mode: int) -> Error {
- TEMP_ALLOCATOR_GUARD()
- name_cstr := temp_cstring(name) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode))))
}
@@ -381,15 +381,15 @@ _fchmod :: proc(f: ^File, mode: int) -> Error {
// NOTE: will throw error without super user priviledges
_chown :: proc(name: string, uid, gid: int) -> Error {
- TEMP_ALLOCATOR_GUARD()
- name_cstr := temp_cstring(name) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _get_platform_error(linux.chown(name_cstr, linux.Uid(uid), linux.Gid(gid)))
}
// NOTE: will throw error without super user priviledges
_lchown :: proc(name: string, uid, gid: int) -> Error {
- TEMP_ALLOCATOR_GUARD()
- name_cstr := temp_cstring(name) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _get_platform_error(linux.lchown(name_cstr, linux.Uid(uid), linux.Gid(gid)))
}
@@ -400,8 +400,8 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
}
_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
- TEMP_ALLOCATOR_GUARD()
- name_cstr := temp_cstring(name) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ name_cstr := clone_to_cstring(name, temp_allocator) or_return
times := [2]linux.Time_Spec {
{
uint(atime._nsec) / uint(time.Second),
@@ -431,8 +431,8 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
}
_exists :: proc(name: string) -> bool {
- TEMP_ALLOCATOR_GUARD()
- name_cstr, _ := temp_cstring(name)
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ name_cstr, _ := clone_to_cstring(name, temp_allocator)
return linux.access(name_cstr, linux.F_OK) == .NONE
}
@@ -440,8 +440,8 @@ _exists :: proc(name: string) -> bool {
_read_entire_pseudo_file :: proc { _read_entire_pseudo_file_string, _read_entire_pseudo_file_cstring }
_read_entire_pseudo_file_string :: proc(name: string, allocator: runtime.Allocator) -> (b: []u8, e: Error) {
- TEMP_ALLOCATOR_GUARD()
- name_cstr := clone_to_cstring(name, temp_allocator()) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+ name_cstr := clone_to_cstring(name, temp_allocator) or_return
return _read_entire_pseudo_file_cstring(name_cstr, allocator)
}
diff --git a/core/os/os2/file_posix.odin b/core/os/os2/file_posix.odin
index 43d5866b1..2d74618ee 100644
--- a/core/os/os2/file_posix.odin
+++ b/core/os/os2/file_posix.odin
@@ -69,8 +69,8 @@ _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Err
if .Trunc in flags { sys_flags += {.TRUNC} }
if .Inheritable in flags { sys_flags -= {.CLOEXEC} }
- TEMP_ALLOCATOR_GUARD()
- cname := temp_cstring(name)
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ cname := clone_to_cstring(name, temp_allocator) or_return
fd := posix.open(cname, sys_flags, transmute(posix.mode_t)posix._mode_t(perm))
if fd < 0 {
@@ -183,39 +183,39 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
return nil
}
-_remove :: proc(name: string) -> Error {
- TEMP_ALLOCATOR_GUARD()
- cname := temp_cstring(name)
+_remove :: proc(name: string) -> (err: Error) {
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ cname := clone_to_cstring(name, temp_allocator) or_return
if posix.remove(cname) != 0 {
return _get_platform_error()
}
return nil
}
-_rename :: proc(old_path, new_path: string) -> Error {
- TEMP_ALLOCATOR_GUARD()
- cold := temp_cstring(old_path)
- cnew := temp_cstring(new_path)
+_rename :: proc(old_path, new_path: string) -> (err: Error) {
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ cold := clone_to_cstring(old_path, temp_allocator) or_return
+ cnew := clone_to_cstring(new_path, temp_allocator) or_return
if posix.rename(cold, cnew) != 0 {
return _get_platform_error()
}
return nil
}
-_link :: proc(old_name, new_name: string) -> Error {
- TEMP_ALLOCATOR_GUARD()
- cold := temp_cstring(old_name)
- cnew := temp_cstring(new_name)
+_link :: proc(old_name, new_name: string) -> (err: Error) {
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ cold := clone_to_cstring(old_name, temp_allocator) or_return
+ cnew := clone_to_cstring(new_name, temp_allocator) or_return
if posix.link(cold, cnew) != .OK {
return _get_platform_error()
}
return nil
}
-_symlink :: proc(old_name, new_name: string) -> Error {
- TEMP_ALLOCATOR_GUARD()
- cold := temp_cstring(old_name)
- cnew := temp_cstring(new_name)
+_symlink :: proc(old_name, new_name: string) -> (err: Error) {
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ cold := clone_to_cstring(old_name, temp_allocator) or_return
+ cnew := clone_to_cstring(new_name, temp_allocator) or_return
if posix.symlink(cold, cnew) != .OK {
return _get_platform_error()
}
@@ -223,8 +223,8 @@ _symlink :: proc(old_name, new_name: string) -> Error {
}
_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) {
- TEMP_ALLOCATOR_GUARD()
- cname := temp_cstring(name)
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+ cname := clone_to_cstring(name, temp_allocator) or_return
buf: [dynamic]byte
buf.allocator = allocator
@@ -268,9 +268,9 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
}
}
-_chdir :: proc(name: string) -> Error {
- TEMP_ALLOCATOR_GUARD()
- cname := temp_cstring(name)
+_chdir :: proc(name: string) -> (err: Error) {
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ cname := clone_to_cstring(name, temp_allocator) or_return
if posix.chdir(cname) != .OK {
return _get_platform_error()
}
@@ -291,9 +291,9 @@ _fchmod :: proc(f: ^File, mode: int) -> Error {
return nil
}
-_chmod :: proc(name: string, mode: int) -> Error {
- TEMP_ALLOCATOR_GUARD()
- cname := temp_cstring(name)
+_chmod :: proc(name: string, mode: int) -> (err: Error) {
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ cname := clone_to_cstring(name, temp_allocator) or_return
if posix.chmod(cname, transmute(posix.mode_t)posix._mode_t(mode)) != .OK {
return _get_platform_error()
}
@@ -307,9 +307,9 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
return nil
}
-_chown :: proc(name: string, uid, gid: int) -> Error {
- TEMP_ALLOCATOR_GUARD()
- cname := temp_cstring(name)
+_chown :: proc(name: string, uid, gid: int) -> (err: Error) {
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ cname := clone_to_cstring(name, temp_allocator) or_return
if posix.chown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK {
return _get_platform_error()
}
@@ -317,15 +317,15 @@ _chown :: proc(name: string, uid, gid: int) -> Error {
}
_lchown :: proc(name: string, uid, gid: int) -> Error {
- TEMP_ALLOCATOR_GUARD()
- cname := temp_cstring(name)
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ cname := clone_to_cstring(name, temp_allocator) or_return
if posix.lchown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK {
return _get_platform_error()
}
return nil
}
-_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
+_chtimes :: proc(name: string, atime, mtime: time.Time) -> (err: Error) {
times := [2]posix.timeval{
{
tv_sec = posix.time_t(atime._nsec/1e9), /* seconds */
@@ -337,8 +337,8 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
},
}
- TEMP_ALLOCATOR_GUARD()
- cname := temp_cstring(name)
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ cname := clone_to_cstring(name, temp_allocator) or_return
if posix.utimes(cname, ×) != .OK {
return _get_platform_error()
@@ -365,8 +365,9 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
}
_exists :: proc(path: string) -> bool {
- TEMP_ALLOCATOR_GUARD()
- cpath := temp_cstring(path)
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ cpath, err := clone_to_cstring(path, temp_allocator)
+ if err != nil { return false }
return posix.access(cpath) == .OK
}
diff --git a/core/os/os2/file_posix_darwin.odin b/core/os/os2/file_posix_darwin.odin
index 920a63a71..aed3e56f5 100644
--- a/core/os/os2/file_posix_darwin.odin
+++ b/core/os/os2/file_posix_darwin.odin
@@ -3,6 +3,7 @@ package os2
import "base:runtime"
+import "core:sys/darwin"
import "core:sys/posix"
_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) {
@@ -16,3 +17,30 @@ _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allo
return clone_to_cstring(string(cstring(&buf[0])), allocator)
}
+
+_copy_file_native :: proc(dst_path, src_path: string) -> (err: Error) {
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+
+ csrc := clone_to_cstring(src_path, temp_allocator) or_return
+ cdst := clone_to_cstring(dst_path, temp_allocator) or_return
+
+ // Disallow directories, as specified by the generic implementation.
+
+ stat: posix.stat_t
+ if posix.stat(csrc, &stat) != .OK {
+ err = _get_platform_error()
+ return
+ }
+
+ if posix.S_ISDIR(stat.st_mode) {
+ err = .Invalid_File
+ return
+ }
+
+ ret := darwin.copyfile(csrc, cdst, nil, darwin.COPYFILE_ALL)
+ if ret < 0 {
+ err = _get_platform_error()
+ }
+
+ return
+}
diff --git a/core/os/os2/file_posix_other.odin b/core/os/os2/file_posix_other.odin
index 74b6374ec..d2946098b 100644
--- a/core/os/os2/file_posix_other.odin
+++ b/core/os/os2/file_posix_other.odin
@@ -7,8 +7,8 @@ import "base:runtime"
import "core:sys/posix"
_posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) {
- TEMP_ALLOCATOR_GUARD()
- cname := temp_cstring(name)
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+ cname := clone_to_cstring(name, temp_allocator)
buf: [posix.PATH_MAX]byte
path = posix.realpath(cname, raw_data(buf[:]))
diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin
index b123330e0..40d012183 100644
--- a/core/os/os2/file_windows.odin
+++ b/core/os/os2/file_windows.odin
@@ -86,9 +86,9 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: u
err = .Not_Exist
return
}
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
- path := _fix_long_path(name, temp_allocator()) or_return
+ path := _fix_long_path(name, temp_allocator) or_return
access: u32
switch flags & {.Read, .Write} {
case {.Read}: access = win32.FILE_GENERIC_READ
@@ -506,10 +506,16 @@ _write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error)
_file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) {
length: win32.LARGE_INTEGER
- if f.kind == .Pipe {
- return 0, .No_Size
- }
handle := _handle(&f.file)
+ if f.kind == .Pipe {
+ bytes_available: u32
+ if win32.PeekNamedPipe(handle, nil, 0, nil, &bytes_available, nil) {
+ return i64(bytes_available), nil
+ } else {
+ err = _get_platform_error()
+ return
+ }
+ }
if !win32.GetFileSizeEx(handle, &length) {
err = _get_platform_error()
}
@@ -551,8 +557,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error {
}
_remove :: proc(name: string) -> Error {
- TEMP_ALLOCATOR_GUARD()
- p := _fix_long_path(name, temp_allocator()) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ p := _fix_long_path(name, temp_allocator) or_return
err, err1: Error
if !win32.DeleteFileW(p) {
err = _get_platform_error()
@@ -589,9 +595,9 @@ _remove :: proc(name: string) -> Error {
}
_rename :: proc(old_path, new_path: string) -> Error {
- TEMP_ALLOCATOR_GUARD()
- from := _fix_long_path(old_path, temp_allocator()) or_return
- to := _fix_long_path(new_path, temp_allocator()) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ from := _fix_long_path(old_path, temp_allocator) or_return
+ to := _fix_long_path(new_path, temp_allocator) or_return
if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) {
return nil
}
@@ -600,9 +606,9 @@ _rename :: proc(old_path, new_path: string) -> Error {
}
_link :: proc(old_name, new_name: string) -> Error {
- TEMP_ALLOCATOR_GUARD()
- o := _fix_long_path(old_name, temp_allocator()) or_return
- n := _fix_long_path(new_name, temp_allocator()) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ o := _fix_long_path(old_name, temp_allocator) or_return
+ n := _fix_long_path(new_name, temp_allocator) or_return
if win32.CreateHardLinkW(n, o, nil) {
return nil
}
@@ -663,9 +669,9 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st
return "", _get_platform_error()
}
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
- buf := make([]u16, n+1, temp_allocator())
+ buf := make([]u16, n+1, temp_allocator)
n = win32.GetFinalPathNameByHandleW(handle, raw_data(buf), u32(len(buf)), win32.VOLUME_NAME_DOS)
if n == 0 {
return "", _get_platform_error()
@@ -689,9 +695,9 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er
@thread_local
rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
- p := _fix_long_path(name, temp_allocator()) or_return
+ p := _fix_long_path(name, temp_allocator) or_return
handle := _open_sym_link(p) or_return
defer win32.CloseHandle(handle)
@@ -766,8 +772,8 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error {
}
_chdir :: proc(name: string) -> Error {
- TEMP_ALLOCATOR_GUARD()
- p := _fix_long_path(name, temp_allocator()) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ p := _fix_long_path(name, temp_allocator) or_return
if !win32.SetCurrentDirectoryW(p) {
return _get_platform_error()
}
@@ -794,19 +800,11 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error {
defer close(f)
return _fchtimes(f, atime, mtime)
}
+
_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
if f == nil || f.impl == nil {
return nil
}
- d: win32.BY_HANDLE_FILE_INFORMATION
- if !win32.GetFileInformationByHandle(_handle(f), &d) {
- return _get_platform_error()
- }
-
- to_windows_time :: #force_inline proc(t: time.Time) -> win32.LARGE_INTEGER {
- // a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC)
- return win32.LARGE_INTEGER(time.time_to_unix_nano(t) * 100 + 116444736000000000)
- }
atime, mtime := atime, mtime
if time.time_to_unix_nano(atime) < time.time_to_unix_nano(mtime) {
@@ -814,17 +812,17 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error {
}
info: win32.FILE_BASIC_INFO
- info.LastAccessTime = to_windows_time(atime)
- info.LastWriteTime = to_windows_time(mtime)
- if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(d)) {
+ info.LastAccessTime = time_as_filetime(atime)
+ info.LastWriteTime = time_as_filetime(mtime)
+ if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(info)) {
return _get_platform_error()
}
return nil
}
_exists :: proc(path: string) -> bool {
- TEMP_ALLOCATOR_GUARD()
- wpath, _ := _fix_long_path(path, temp_allocator())
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ wpath, _ := _fix_long_path(path, temp_allocator)
attribs := win32.GetFileAttributesW(wpath)
return attribs != win32.INVALID_FILE_ATTRIBUTES
}
diff --git a/core/os/os2/internal_util.odin b/core/os/os2/internal_util.odin
index ce253d17b..9616af8b0 100644
--- a/core/os/os2/internal_util.odin
+++ b/core/os/os2/internal_util.odin
@@ -43,11 +43,6 @@ clone_to_cstring :: proc(s: string, allocator: runtime.Allocator) -> (res: cstri
return cstring(&buf[0]), nil
}
-@(require_results)
-temp_cstring :: proc(s: string) -> (cstring, runtime.Allocator_Error) #optional_allocator_error {
- return clone_to_cstring(s, temp_allocator())
-}
-
@(require_results)
string_from_null_terminated_bytes :: proc(b: []byte) -> (res: string) {
s := string(b)
diff --git a/core/os/os2/path.odin b/core/os/os2/path.odin
index 47ac0236d..e12aa3c9c 100644
--- a/core/os/os2/path.odin
+++ b/core/os/os2/path.odin
@@ -119,11 +119,11 @@ clean_path :: proc(path: string, allocator: runtime.Allocator) -> (cleaned: stri
return strings.clone(".", allocator)
}
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
// The extra byte is to simplify appending path elements by letting the
// loop to end each with a separator. We'll trim the last one when we're done.
- buffer := make([]u8, len(path) + 1, temp_allocator()) or_return
+ buffer := make([]u8, len(path) + 1, temp_allocator) or_return
// This is the only point where Windows and POSIX differ, as Windows has
// alphabet-based volumes for root paths.
@@ -326,8 +326,8 @@ For example, `join_path({"/home", "foo", "bar.txt"})` will result in `"/home/foo
join_path :: proc(elems: []string, allocator: runtime.Allocator) -> (joined: string, err: Error) {
for e, i in elems {
if e != "" {
- TEMP_ALLOCATOR_GUARD()
- p := strings.join(elems[i:], Path_Separator_String, temp_allocator()) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+ p := strings.join(elems[i:], Path_Separator_String, temp_allocator) or_return
return clean_path(p, allocator)
}
}
diff --git a/core/os/os2/path_freebsd.odin b/core/os/os2/path_freebsd.odin
index 577108eca..e7e4f63c9 100644
--- a/core/os/os2/path_freebsd.odin
+++ b/core/os/os2/path_freebsd.odin
@@ -25,5 +25,5 @@ _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err
return
}
- return string(buf[:size]), nil
+ return string(buf[:size-1]), nil
}
diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin
index 410b4cb28..8b185f419 100644
--- a/core/os/os2/path_linux.odin
+++ b/core/os/os2/path_linux.odin
@@ -18,8 +18,8 @@ _is_path_separator :: proc(c: byte) -> bool {
}
_mkdir :: proc(path: string, perm: int) -> Error {
- TEMP_ALLOCATOR_GUARD()
- path_cstr := temp_cstring(path) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ path_cstr := clone_to_cstring(path, temp_allocator) or_return
return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)u32(perm)))
}
@@ -52,9 +52,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
}
return _get_platform_error(errno)
}
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
// need something we can edit, and use to generate cstrings
- path_bytes := make([]u8, len(path) + 1, temp_allocator())
+ path_bytes := make([]u8, len(path) + 1, temp_allocator)
// zero terminate the byte slice to make it a valid cstring
copy(path_bytes, path)
@@ -129,8 +129,8 @@ _remove_all :: proc(path: string) -> Error {
return nil
}
- TEMP_ALLOCATOR_GUARD()
- path_cstr := temp_cstring(path) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ path_cstr := clone_to_cstring(path, temp_allocator) or_return
fd, errno := linux.open(path_cstr, _OPENDIR_FLAGS)
#partial switch errno {
@@ -168,14 +168,16 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (string, Error)
}
_set_working_directory :: proc(dir: string) -> Error {
- dir_cstr := temp_cstring(dir) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+
+ dir_cstr := clone_to_cstring(dir, temp_allocator) or_return
return _get_platform_error(linux.chdir(dir_cstr))
}
_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
- buf := make([dynamic]byte, 1024, temp_allocator()) or_return
+ buf := make([dynamic]byte, 1024, temp_allocator) or_return
for {
n, errno := linux.readlink("/proc/self/exe", buf[:])
if errno != .NONE {
@@ -205,3 +207,21 @@ _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath:
}
return
}
+
+_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
+ rel := path
+ if rel == "" {
+ rel = "."
+ }
+
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+
+ fd, errno := linux.open(clone_to_cstring(path, temp_allocator) or_return, {})
+ if errno != nil {
+ err = _get_platform_error(errno)
+ return
+ }
+ defer linux.close(fd)
+
+ return _get_full_path(fd, allocator)
+}
diff --git a/core/os/os2/path_netbsd.odin b/core/os/os2/path_netbsd.odin
index f56a91fd6..815102dea 100644
--- a/core/os/os2/path_netbsd.odin
+++ b/core/os/os2/path_netbsd.odin
@@ -5,9 +5,9 @@ import "base:runtime"
import "core:sys/posix"
_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
- buf := make([dynamic]byte, 1024, temp_allocator()) or_return
+ buf := make([dynamic]byte, 1024, temp_allocator) or_return
for {
n := posix.readlink("/proc/curproc/exe", raw_data(buf), len(buf))
if n < 0 {
diff --git a/core/os/os2/path_openbsd.odin b/core/os/os2/path_openbsd.odin
index 37b5de927..cbc0346d4 100644
--- a/core/os/os2/path_openbsd.odin
+++ b/core/os/os2/path_openbsd.odin
@@ -35,11 +35,11 @@ _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err
return real(arg, allocator)
}
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
- buf := strings.builder_make(temp_allocator())
+ buf := strings.builder_make(temp_allocator)
- paths := get_env("PATH", temp_allocator())
+ paths := get_env("PATH", temp_allocator)
for dir in strings.split_iterator(&paths, ":") {
strings.builder_reset(&buf)
strings.write_string(&buf, dir)
diff --git a/core/os/os2/path_posix.odin b/core/os/os2/path_posix.odin
index 39bd0a188..f22cd446b 100644
--- a/core/os/os2/path_posix.odin
+++ b/core/os/os2/path_posix.odin
@@ -14,9 +14,9 @@ _is_path_separator :: proc(c: byte) -> bool {
return c == _Path_Separator
}
-_mkdir :: proc(name: string, perm: int) -> Error {
- TEMP_ALLOCATOR_GUARD()
- cname := temp_cstring(name)
+_mkdir :: proc(name: string, perm: int) -> (err: Error) {
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ cname := clone_to_cstring(name, temp_allocator) or_return
if posix.mkdir(cname, transmute(posix.mode_t)posix._mode_t(perm)) != .OK {
return _get_platform_error()
}
@@ -28,13 +28,13 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
return .Invalid_Path
}
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
if exists(path) {
return .Exist
}
- clean_path := clean_path(path, temp_allocator()) or_return
+ clean_path := clean_path(path, temp_allocator) or_return
return internal_mkdir_all(clean_path, perm)
internal_mkdir_all :: proc(path: string, perm: int) -> Error {
@@ -52,9 +52,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
}
}
-_remove_all :: proc(path: string) -> Error {
- TEMP_ALLOCATOR_GUARD()
- cpath := temp_cstring(path)
+_remove_all :: proc(path: string) -> (err: Error) {
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ cpath := clone_to_cstring(path, temp_allocator) or_return
dir := posix.opendir(cpath)
if dir == nil {
@@ -78,7 +78,7 @@ _remove_all :: proc(path: string) -> Error {
continue
}
- fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator())
+ fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator)
if entry.d_type == .DIR {
_remove_all(fullpath[:len(fullpath)-1]) or_return
} else {
@@ -95,10 +95,10 @@ _remove_all :: proc(path: string) -> Error {
}
_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
buf: [dynamic]byte
- buf.allocator = temp_allocator()
+ buf.allocator = temp_allocator
size := uint(posix.PATH_MAX)
cwd: cstring
@@ -116,10 +116,27 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, er
}
_set_working_directory :: proc(dir: string) -> (err: Error) {
- TEMP_ALLOCATOR_GUARD()
- cdir := temp_cstring(dir)
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ cdir := clone_to_cstring(dir, temp_allocator) or_return
if posix.chdir(cdir) != .OK {
err = _get_platform_error()
}
return
}
+
+_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
+ rel := path
+ if rel == "" {
+ rel = "."
+ }
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+ rel_cstr := clone_to_cstring(rel, temp_allocator) or_return
+ path_ptr := posix.realpath(rel_cstr, nil)
+ if path_ptr == nil {
+ return "", Platform_Error(posix.errno())
+ }
+ defer posix.free(path_ptr)
+
+ path_str := clone_string(string(path_ptr), allocator) or_return
+ return path_str, nil
+}
diff --git a/core/os/os2/path_posixfs.odin b/core/os/os2/path_posixfs.odin
index 8f9d43d63..0736e73d1 100644
--- a/core/os/os2/path_posixfs.odin
+++ b/core/os/os2/path_posixfs.odin
@@ -4,10 +4,6 @@ package os2
// This implementation is for all systems that have POSIX-compliant filesystem paths.
-import "base:runtime"
-import "core:strings"
-import "core:sys/posix"
-
_are_paths_identical :: proc(a, b: string) -> (identical: bool) {
return a == b
}
@@ -26,23 +22,6 @@ _is_absolute_path :: proc(path: string) -> bool {
return len(path) > 0 && _is_path_separator(path[0])
}
-_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) {
- rel := path
- if rel == "" {
- rel = "."
- }
- TEMP_ALLOCATOR_GUARD()
- rel_cstr := strings.clone_to_cstring(rel, temp_allocator())
- path_ptr := posix.realpath(rel_cstr, nil)
- if path_ptr == nil {
- return "", Platform_Error(posix.errno())
- }
- defer posix.free(path_ptr)
-
- path_str := strings.clone(string(path_ptr), allocator)
- return path_str, nil
-}
-
_get_relative_path_handle_start :: proc(base, target: string) -> bool {
base_rooted := len(base) > 0 && _is_path_separator(base[0])
target_rooted := len(target) > 0 && _is_path_separator(target[0])
diff --git a/core/os/os2/path_wasi.odin b/core/os/os2/path_wasi.odin
index 7aee8fcc0..b8240e188 100644
--- a/core/os/os2/path_wasi.odin
+++ b/core/os/os2/path_wasi.odin
@@ -28,13 +28,13 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
return .Invalid_Path
}
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
if exists(path) {
return .Exist
}
- clean_path := clean_path(path, temp_allocator())
+ clean_path := clean_path(path, temp_allocator)
return internal_mkdir_all(clean_path)
internal_mkdir_all :: proc(path: string) -> Error {
diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin
index dd9b7748c..c2e51040f 100644
--- a/core/os/os2/path_windows.odin
+++ b/core/os/os2/path_windows.odin
@@ -14,8 +14,8 @@ _is_path_separator :: proc(c: byte) -> bool {
}
_mkdir :: proc(name: string, perm: int) -> Error {
- TEMP_ALLOCATOR_GUARD()
- if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator()) or_return, nil) {
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator) or_return, nil) {
return _get_platform_error()
}
return nil
@@ -33,9 +33,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
return p, false, nil
}
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
- dir_stat, err := stat(path, temp_allocator())
+ dir_stat, err := stat(path, temp_allocator)
if err == nil {
if dir_stat.type == .Directory {
return nil
@@ -63,7 +63,7 @@ _mkdir_all :: proc(path: string, perm: int) -> Error {
err = mkdir(path, perm)
if err != nil {
- new_dir_stat, err1 := lstat(path, temp_allocator())
+ new_dir_stat, err1 := lstat(path, temp_allocator)
if err1 == nil && new_dir_stat.type == .Directory {
return nil
}
@@ -82,8 +82,8 @@ _remove_all :: proc(path: string) -> Error {
return nil
}
- TEMP_ALLOCATOR_GUARD()
- dir := win32_utf8_to_wstring(path, temp_allocator()) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ dir := win32_utf8_to_wstring(path, temp_allocator) or_return
empty: [1]u16
@@ -109,10 +109,10 @@ _remove_all :: proc(path: string) -> Error {
_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
win32.AcquireSRWLockExclusive(&cwd_lock)
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
- dir_buf_wstr := make([]u16, sz_utf16, temp_allocator()) or_return
+ dir_buf_wstr := make([]u16, sz_utf16, temp_allocator) or_return
sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr))
assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL.
@@ -123,8 +123,8 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, er
}
_set_working_directory :: proc(dir: string) -> (err: Error) {
- TEMP_ALLOCATOR_GUARD()
- wstr := win32_utf8_to_wstring(dir, temp_allocator()) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ wstr := win32_utf8_to_wstring(dir, temp_allocator) or_return
win32.AcquireSRWLockExclusive(&cwd_lock)
@@ -138,9 +138,9 @@ _set_working_directory :: proc(dir: string) -> (err: Error) {
}
_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) {
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
- buf := make([dynamic]u16, 512, temp_allocator()) or_return
+ buf := make([dynamic]u16, 512, temp_allocator) or_return
for {
ret := win32.GetModuleFileNameW(nil, raw_data(buf), win32.DWORD(len(buf)))
if ret == 0 {
@@ -187,7 +187,6 @@ init_long_path_support :: proc() {
if value == 1 {
can_use_long_paths = true
}
-
}
@(require_results)
@@ -222,10 +221,10 @@ _fix_long_path_internal :: proc(path: string) -> string {
return path
}
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
PREFIX :: `\\?`
- path_buf := make([]byte, len(PREFIX)+len(path)+1, temp_allocator())
+ path_buf := make([]byte, len(PREFIX)+len(path)+1, temp_allocator)
copy(path_buf, PREFIX)
n := len(path)
r, w := 0, len(PREFIX)
@@ -271,6 +270,11 @@ _clean_path_handle_start :: proc(path: string, buffer: []u8) -> (rooted: bool, s
start += 1
}
copy(buffer, path[:start])
+ for n in 0.. (absol
if rel == "" {
rel = "."
}
- TEMP_ALLOCATOR_GUARD()
- rel_utf16 := win32.utf8_to_utf16(rel, temp_allocator())
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+ rel_utf16 := win32.utf8_to_utf16(rel, temp_allocator)
n := win32.GetFullPathNameW(raw_data(rel_utf16), 0, nil, nil)
if n == 0 {
return "", Platform_Error(win32.GetLastError())
}
- buf := make([]u16, n, temp_allocator()) or_return
+ buf := make([]u16, n, temp_allocator) or_return
n = win32.GetFullPathNameW(raw_data(rel_utf16), u32(n), raw_data(buf), nil)
if n == 0 {
return "", Platform_Error(win32.GetLastError())
diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin
index c90e3add2..3c84f3539 100644
--- a/core/os/os2/process.odin
+++ b/core/os/os2/process.odin
@@ -264,7 +264,7 @@ specific process, even after it has died.
**Note(linux)**: The `handle` will be referring to pidfd.
*/
Process :: struct {
- pid: int,
+ pid: int,
handle: uintptr,
}
@@ -290,21 +290,10 @@ process_open :: proc(pid: int, flags := Process_Open_Flags {}) -> (Process, Erro
return _process_open(pid, flags)
}
-
-/*
-OS-specific process attributes.
-*/
-Process_Attributes :: struct {
- sys_attr: _Sys_Process_Attributes,
-}
-
/*
The description of how a process should be created.
*/
Process_Desc :: struct {
- // OS-specific attributes.
- sys_attr: Process_Attributes,
-
// The working directory of the process. If the string has length 0, the
// working directory is assumed to be the current working directory of the
// current process.
@@ -407,11 +396,9 @@ process_exec :: proc(
{
stdout_b: [dynamic]byte
stdout_b.allocator = allocator
- defer stdout = stdout_b[:]
stderr_b: [dynamic]byte
stderr_b.allocator = allocator
- defer stderr = stderr_b[:]
buf: [1024]u8 = ---
@@ -450,6 +437,9 @@ process_exec :: proc(
}
}
}
+
+ stdout = stdout_b[:]
+ stderr = stderr_b[:]
}
if err != nil {
diff --git a/core/os/os2/process_linux.odin b/core/os/os2/process_linux.odin
index 6d654008b..170f0ea1a 100644
--- a/core/os/os2/process_linux.odin
+++ b/core/os/os2/process_linux.odin
@@ -50,7 +50,7 @@ _get_ppid :: proc() -> int {
@(private="package")
_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) {
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
dir_fd, errno := linux.open("/proc/", _OPENDIR_FLAGS)
#partial switch errno {
@@ -68,9 +68,9 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error)
}
defer linux.close(dir_fd)
- dynamic_list := make([dynamic]int, temp_allocator()) or_return
+ dynamic_list := make([dynamic]int, temp_allocator) or_return
- buf := make([dynamic]u8, 128, 128, temp_allocator()) or_return
+ buf := make([dynamic]u8, 128, 128, temp_allocator) or_return
loop: for {
buflen: int
buflen, errno = linux.getdents(dir_fd, buf[:])
@@ -100,7 +100,7 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error)
@(private="package")
_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
info.pid = pid
@@ -126,7 +126,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
passwd_bytes: []u8
passwd_err: Error
- passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator())
+ passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator)
if passwd_err != nil {
err = passwd_err
break username_if
@@ -162,13 +162,13 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
}
}
- cmdline_if: if selection & {.Working_Dir, .Command_Line, .Command_Args, .Executable_Path} != {} {
+ cmdline_if: if selection & {.Working_Dir, .Command_Line, .Command_Args} != {} {
strings.builder_reset(&path_builder)
strings.write_string(&path_builder, "/proc/")
strings.write_int(&path_builder, pid)
strings.write_string(&path_builder, "/cmdline")
- cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator())
+ cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
if cmdline_err != nil || len(cmdline_bytes) == 0 {
err = cmdline_err
break cmdline_if
@@ -178,18 +178,18 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
terminator := strings.index_byte(cmdline, 0)
assert(terminator > 0)
- command_line_exec := cmdline[:terminator]
+ // command_line_exec := cmdline[:terminator]
// Still need cwd if the execution on the command line is relative.
cwd: string
cwd_err: Error
- if .Working_Dir in selection || (.Executable_Path in selection && command_line_exec[0] != '/') {
+ if .Working_Dir in selection {
strings.builder_reset(&path_builder)
strings.write_string(&path_builder, "/proc/")
strings.write_int(&path_builder, pid)
strings.write_string(&path_builder, "/cwd")
- cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder) or_return, temp_allocator()) // allowed to fail
+ cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder) or_return, temp_allocator) // allowed to fail
if cwd_err == nil && .Working_Dir in selection {
info.working_dir = strings.clone(cwd, allocator) or_return
info.fields += {.Working_Dir}
@@ -199,18 +199,6 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
}
}
- if .Executable_Path in selection {
- if cmdline[0] == '/' {
- info.executable_path = strings.clone(cmdline[:terminator], allocator) or_return
- info.fields += {.Executable_Path}
- } else if cwd_err == nil {
- info.executable_path = join_path({ cwd, cmdline[:terminator] }, allocator) or_return
- info.fields += {.Executable_Path}
- } else {
- break cmdline_if
- }
- }
-
if selection & {.Command_Line, .Command_Args} != {} {
// skip to first arg
//cmdline = cmdline[terminator + 1:]
@@ -257,7 +245,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
strings.write_int(&path_builder, pid)
strings.write_string(&path_builder, "/stat")
- proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator())
+ proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
if stat_err != nil {
err = stat_err
break stat_if
@@ -296,7 +284,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
Nice,
//... etc,
}
- stat_fields := strings.split(stats, " ", temp_allocator()) or_return
+ stat_fields := strings.split(stats, " ", temp_allocator) or_return
if len(stat_fields) <= int(Fields.Nice) {
break stat_if
@@ -323,13 +311,37 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
}
}
+ if .Executable_Path in selection {
+ /*
+ NOTE(Jeroen):
+
+ The old version returned the wrong executable path for things like `bash` or `sh`,
+ for whom `/proc//cmdline` will just report "bash" or "sh",
+ resulting in misleading paths like `$PWD/sh`, even though that executable doesn't exist there.
+
+ Thanks to Yawning for suggesting `/proc/self/exe`.
+ */
+
+ strings.builder_reset(&path_builder)
+ strings.write_string(&path_builder, "/proc/")
+ strings.write_int(&path_builder, pid)
+ strings.write_string(&path_builder, "/exe")
+
+ if exe_bytes, exe_err := _read_link(strings.to_string(path_builder), temp_allocator); exe_err == nil {
+ info.executable_path = strings.clone(string(exe_bytes), allocator) or_return
+ info.fields += {.Executable_Path}
+ } else {
+ err = exe_err
+ }
+ }
+
if .Environment in selection {
strings.builder_reset(&path_builder)
strings.write_string(&path_builder, "/proc/")
strings.write_int(&path_builder, pid)
strings.write_string(&path_builder, "/environ")
- if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator()); env_err == nil {
+ if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator); env_err == nil {
env := string(env_bytes)
env_list := make([dynamic]string, allocator) or_return
@@ -378,12 +390,9 @@ _process_open :: proc(pid: int, _: Process_Open_Flags) -> (process: Process, err
return
}
-@(private="package")
-_Sys_Process_Attributes :: struct {}
-
@(private="package")
_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
if len(desc.command) == 0 {
return process, .Invalid_Command
@@ -392,7 +401,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
dir_fd := linux.AT_FDCWD
errno: linux.Errno
if desc.working_dir != "" {
- dir_cstr := temp_cstring(desc.working_dir) or_return
+ dir_cstr := clone_to_cstring(desc.working_dir, temp_allocator) or_return
if dir_fd, errno = linux.open(dir_cstr, _OPENDIR_FLAGS); errno != .NONE {
return process, _get_platform_error(errno)
}
@@ -405,10 +414,10 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
exe_path: cstring
executable_name := desc.command[0]
if strings.index_byte(executable_name, '/') < 0 {
- path_env := get_env("PATH", temp_allocator())
- path_dirs := split_path_list(path_env, temp_allocator()) or_return
+ path_env := get_env("PATH", temp_allocator)
+ path_dirs := split_path_list(path_env, temp_allocator) or_return
- exe_builder := strings.builder_make(temp_allocator()) or_return
+ exe_builder := strings.builder_make(temp_allocator) or_return
found: bool
for dir in path_dirs {
@@ -435,7 +444,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
}
}
} else {
- exe_path = temp_cstring(executable_name) or_return
+ exe_path = clone_to_cstring(executable_name, temp_allocator) or_return
if linux.access(exe_path, linux.X_OK) != .NONE {
return process, .Not_Exist
}
@@ -443,20 +452,20 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
// args and environment need to be a list of cstrings
// that are terminated by a nil pointer.
- cargs := make([]cstring, len(desc.command) + 1, temp_allocator()) or_return
+ cargs := make([]cstring, len(desc.command) + 1, temp_allocator) or_return
for command, i in desc.command {
- cargs[i] = temp_cstring(command) or_return
+ cargs[i] = clone_to_cstring(command, temp_allocator) or_return
}
// Use current process' environment if description didn't provide it.
env: [^]cstring
if desc.env == nil {
// take this process's current environment
- env = raw_data(export_cstring_environment(temp_allocator()))
+ env = raw_data(export_cstring_environment(temp_allocator))
} else {
- cenv := make([]cstring, len(desc.env) + 1, temp_allocator()) or_return
+ cenv := make([]cstring, len(desc.env) + 1, temp_allocator) or_return
for env, i in desc.env {
- cenv[i] = temp_cstring(env) or_return
+ cenv[i] = clone_to_cstring(env, temp_allocator) or_return
}
env = &cenv[0]
}
@@ -584,7 +593,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
}
_process_state_update_times :: proc(state: ^Process_State) -> (err: Error) {
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
stat_path_buf: [48]u8
path_builder := strings.builder_from_bytes(stat_path_buf[:])
@@ -593,7 +602,7 @@ _process_state_update_times :: proc(state: ^Process_State) -> (err: Error) {
strings.write_string(&path_builder, "/stat")
stat_buf: []u8
- stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator())
+ stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator)
if err != nil {
return
}
diff --git a/core/os/os2/process_posix.odin b/core/os/os2/process_posix.odin
index cd451781f..fcacdf654 100644
--- a/core/os/os2/process_posix.odin
+++ b/core/os/os2/process_posix.odin
@@ -46,22 +46,20 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime
return _process_info_by_pid(_get_pid(), selection, allocator)
}
-_Sys_Process_Attributes :: struct {}
-
_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
if len(desc.command) == 0 {
err = .Invalid_Path
return
}
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
// search PATH if just a plain name is provided.
- exe_builder := strings.builder_make(temp_allocator())
+ exe_builder := strings.builder_make(temp_allocator)
exe_name := desc.command[0]
if strings.index_byte(exe_name, '/') < 0 {
- path_env := get_env("PATH", temp_allocator())
- path_dirs := split_path_list(path_env, temp_allocator()) or_return
+ path_env := get_env("PATH", temp_allocator)
+ path_dirs := split_path_list(path_env, temp_allocator) or_return
found: bool
for dir in path_dirs {
@@ -110,12 +108,12 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
}
cwd: cstring; if desc.working_dir != "" {
- cwd = temp_cstring(desc.working_dir)
+ cwd = clone_to_cstring(desc.working_dir, temp_allocator) or_return
}
- cmd := make([]cstring, len(desc.command) + 1, temp_allocator())
+ cmd := make([]cstring, len(desc.command) + 1, temp_allocator)
for part, i in desc.command {
- cmd[i] = temp_cstring(part)
+ cmd[i] = clone_to_cstring(part, temp_allocator) or_return
}
env: [^]cstring
@@ -123,9 +121,9 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
// take this process's current environment
env = posix.environ
} else {
- cenv := make([]cstring, len(desc.env) + 1, temp_allocator())
+ cenv := make([]cstring, len(desc.env) + 1, temp_allocator)
for env, i in desc.env {
- cenv[i] = temp_cstring(env)
+ cenv[i] = clone_to_cstring(env, temp_allocator) or_return
}
env = raw_data(cenv)
}
diff --git a/core/os/os2/process_posix_darwin.odin b/core/os/os2/process_posix_darwin.odin
index 0ea1f643c..ac2d4b78c 100644
--- a/core/os/os2/process_posix_darwin.odin
+++ b/core/os/os2/process_posix_darwin.odin
@@ -50,6 +50,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
}
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
info.pid = pid
// Thought on errors is: allocation failures return immediately (also why the non-allocation stuff is done first),
@@ -127,7 +128,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
break args
}
- buf := runtime.make_aligned([]byte, length, 4, temp_allocator())
+ buf := runtime.make_aligned([]byte, length, 4, temp_allocator)
if sysctl(raw_data(mib), 3, raw_data(buf), &length, nil, 0) != .OK {
if err == nil {
err = _get_platform_error()
@@ -239,9 +240,9 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error)
return
}
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
- buffer := make([]i32, ret, temp_allocator())
+ buffer := make([]i32, ret, temp_allocator)
ret = darwin.proc_listallpids(raw_data(buffer), ret*size_of(i32))
if ret < 0 {
err = _get_platform_error()
diff --git a/core/os/os2/process_wasi.odin b/core/os/os2/process_wasi.odin
index 6ebfe3788..9f4d61649 100644
--- a/core/os/os2/process_wasi.odin
+++ b/core/os/os2/process_wasi.odin
@@ -44,8 +44,6 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime
return
}
-_Sys_Process_Attributes :: struct {}
-
_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
err = .Unsupported
return
diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin
index 536930ee1..199e5ad74 100644
--- a/core/os/os2/process_windows.odin
+++ b/core/os/os2/process_windows.odin
@@ -162,9 +162,10 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
if err != nil {
break read_peb
}
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
if selection >= {.Command_Line, .Command_Args} {
- TEMP_ALLOCATOR_GUARD()
- cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return
+ temp_allocator_scope(temp_allocator)
+ cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
if err != nil {
break read_peb
@@ -179,9 +180,9 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
}
}
if .Environment in selection {
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator_scope(temp_allocator)
env_len := process_params.EnvironmentSize / 2
- envs_w := make([]u16, env_len, temp_allocator()) or_return
+ envs_w := make([]u16, env_len, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
if err != nil {
break read_peb
@@ -190,8 +191,8 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
info.fields += {.Environment}
}
if .Working_Dir in selection {
- TEMP_ALLOCATOR_GUARD()
- cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return
+ temp_allocator_scope(temp_allocator)
+ cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
if err != nil {
break read_peb
@@ -272,9 +273,10 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
if err != nil {
break read_peb
}
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
if selection >= {.Command_Line, .Command_Args} {
- TEMP_ALLOCATOR_GUARD()
- cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return
+ temp_allocator_scope(temp_allocator)
+ cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
if err != nil {
break read_peb
@@ -289,9 +291,9 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
}
}
if .Environment in selection {
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator_scope(temp_allocator)
env_len := process_params.EnvironmentSize / 2
- envs_w := make([]u16, env_len, temp_allocator()) or_return
+ envs_w := make([]u16, env_len, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
if err != nil {
break read_peb
@@ -300,8 +302,8 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
info.fields += {.Environment}
}
if .Working_Dir in selection {
- TEMP_ALLOCATOR_GUARD()
- cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return
+ temp_allocator_scope(temp_allocator)
+ cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return
_, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
if err != nil {
break read_peb
@@ -417,35 +419,64 @@ _process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process,
return
}
-@(private="package")
-_Sys_Process_Attributes :: struct {}
-
@(private="package")
_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
- TEMP_ALLOCATOR_GUARD()
- command_line := _build_command_line(desc.command, temp_allocator())
- command_line_w := win32_utf8_to_wstring(command_line, temp_allocator()) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ command_line := _build_command_line(desc.command, temp_allocator)
+ command_line_w := win32_utf8_to_wstring(command_line, temp_allocator) or_return
environment := desc.env
if desc.env == nil {
- environment = environ(temp_allocator()) or_return
+ environment = environ(temp_allocator) or_return
}
- environment_block := _build_environment_block(environment, temp_allocator())
- environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator()) or_return
- stderr_handle := win32.GetStdHandle(win32.STD_ERROR_HANDLE)
- stdout_handle := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE)
- stdin_handle := win32.GetStdHandle(win32.STD_INPUT_HANDLE)
+ environment_block := _build_environment_block(environment, temp_allocator)
+ environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator) or_return
- if desc.stdout != nil {
+ stderr_handle: win32.HANDLE
+ stdout_handle: win32.HANDLE
+ stdin_handle: win32.HANDLE
+
+ null_handle: win32.HANDLE
+ if desc.stdout == nil || desc.stderr == nil || desc.stdin == nil {
+ null_handle = win32.CreateFileW(
+ win32.L("NUL"),
+ win32.GENERIC_READ|win32.GENERIC_WRITE,
+ win32.FILE_SHARE_READ|win32.FILE_SHARE_WRITE,
+ &win32.SECURITY_ATTRIBUTES{
+ nLength = size_of(win32.SECURITY_ATTRIBUTES),
+ bInheritHandle = true,
+ },
+ win32.OPEN_EXISTING,
+ win32.FILE_ATTRIBUTE_NORMAL,
+ nil,
+ )
+ // Opening NUL should always succeed.
+ assert(null_handle != nil)
+ }
+ // NOTE(laytan): I believe it is fine to close this handle right after CreateProcess,
+ // and we don't have to hold onto this until the process exits.
+ defer if null_handle != nil {
+ win32.CloseHandle(null_handle)
+ }
+
+ if desc.stdout == nil {
+ stdout_handle = null_handle
+ } else {
stdout_handle = win32.HANDLE((^File_Impl)(desc.stdout.impl).fd)
}
- if desc.stderr != nil {
+
+ if desc.stderr == nil {
+ stderr_handle = null_handle
+ } else {
stderr_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd)
}
- if desc.stdin != nil {
+
+ if desc.stdin == nil {
+ stdin_handle = null_handle
+ } else {
stdin_handle = win32.HANDLE((^File_Impl)(desc.stdin.impl).fd)
}
- working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator()) or_else nil) if len(desc.working_dir) > 0 else nil
+ working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator) or_else nil) if len(desc.working_dir) > 0 else nil
process_info: win32.PROCESS_INFORMATION
ok := win32.CreateProcessW(
nil,
@@ -583,7 +614,7 @@ _process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path
}
_get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) {
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
token_handle: win32.HANDLE
if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) {
err = _get_platform_error()
@@ -598,7 +629,7 @@ _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Alloc
}
err = nil
}
- token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator()) or_return))
+ token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator) or_return))
if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) {
err = _get_platform_error()
return
@@ -614,8 +645,8 @@ _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Alloc
err = _get_platform_error()
return
}
- username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator()) or_return
- domain := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator()) or_return
+ username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator) or_return
+ domain := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator) or_return
return strings.concatenate({domain, "\\", username}, allocator)
}
diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin
index 7d76902eb..d6b524684 100644
--- a/core/os/os2/stat.odin
+++ b/core/os/os2/stat.odin
@@ -73,14 +73,14 @@ last_write_time_by_name :: modification_time_by_path
@(require_results)
modification_time :: proc(f: ^File) -> (time.Time, Error) {
- TEMP_ALLOCATOR_GUARD()
- fi, err := fstat(f, temp_allocator())
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ fi, err := fstat(f, temp_allocator)
return fi.modification_time, err
}
@(require_results)
modification_time_by_path :: proc(path: string) -> (time.Time, Error) {
- TEMP_ALLOCATOR_GUARD()
- fi, err := stat(path, temp_allocator())
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ fi, err := stat(path, temp_allocator)
return fi.modification_time, err
}
diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin
index 7bff08f29..373765be5 100644
--- a/core/os/os2/stat_linux.odin
+++ b/core/os/os2/stat_linux.odin
@@ -47,8 +47,8 @@ _fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fi: File
// NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath
_stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
- TEMP_ALLOCATOR_GUARD()
- name_cstr := temp_cstring(name) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+ name_cstr := clone_to_cstring(name, temp_allocator) or_return
fd, errno := linux.open(name_cstr, {})
if errno != .NONE {
@@ -59,8 +59,8 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err
}
_lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) {
- TEMP_ALLOCATOR_GUARD()
- name_cstr := temp_cstring(name) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+ name_cstr := clone_to_cstring(name, temp_allocator) or_return
fd, errno := linux.open(name_cstr, {.PATH, .NOFOLLOW})
if errno != .NONE {
diff --git a/core/os/os2/stat_posix.odin b/core/os/os2/stat_posix.odin
index 260dc7b52..6ffbdf1da 100644
--- a/core/os/os2/stat_posix.odin
+++ b/core/os/os2/stat_posix.odin
@@ -69,8 +69,8 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err
return
}
- TEMP_ALLOCATOR_GUARD()
- cname := temp_cstring(name) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+ cname := clone_to_cstring(name, temp_allocator) or_return
fd := posix.open(cname, {})
if fd == -1 {
@@ -96,33 +96,34 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er
return
}
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
// NOTE: can't use realpath or open (+ fcntl F_GETPATH) here because it tries to resolve symlinks.
// NOTE: This might not be correct when given "/symlink/foo.txt",
// you would want that to resolve "/symlink", but not resolve "foo.txt".
- fullpath := clean_path(name, temp_allocator()) or_return
+ fullpath := clean_path(name, temp_allocator) or_return
assert(len(fullpath) > 0)
switch {
case fullpath[0] == '/':
// nothing.
case fullpath == ".":
- fullpath = getwd(temp_allocator()) or_return
+ fullpath = getwd(temp_allocator) or_return
case len(fullpath) > 1 && fullpath[0] == '.' && fullpath[1] == '/':
fullpath = fullpath[2:]
fallthrough
case:
fullpath = concatenate({
- getwd(temp_allocator()) or_return,
+ getwd(temp_allocator) or_return,
"/",
fullpath,
- }, temp_allocator()) or_return
+ }, temp_allocator) or_return
}
stat: posix.stat_t
- if posix.lstat(temp_cstring(fullpath), &stat) != .OK {
+ c_fullpath := clone_to_cstring(fullpath, temp_allocator) or_return
+ if posix.lstat(c_fullpath, &stat) != .OK {
err = _get_platform_error()
return
}
diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin
index 7d8dd3843..3cdc80405 100644
--- a/core/os/os2/stat_windows.odin
+++ b/core/os/os2/stat_windows.odin
@@ -45,15 +45,15 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path
name = "."
}
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
- p := win32_utf8_to_utf16(name, temp_allocator()) or_return
+ p := win32_utf8_to_utf16(name, temp_allocator) or_return
n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil)
if n == 0 {
return "", _get_platform_error()
}
- buf := make([]u16, n+1, temp_allocator())
+ buf := make([]u16, n+1, temp_allocator)
n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil)
if n == 0 {
return "", _get_platform_error()
@@ -65,9 +65,9 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt
if len(name) == 0 {
return {}, .Not_Exist
}
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
- wname := _fix_long_path(name, temp_allocator()) or_return
+ wname := _fix_long_path(name, temp_allocator) or_return
fa: win32.WIN32_FILE_ATTRIBUTE_DATA
ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa)
if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
@@ -137,9 +137,9 @@ _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (strin
return "", _get_platform_error()
}
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
- buf := make([]u16, max(n, 260)+1, temp_allocator())
+ buf := make([]u16, max(n, 260)+1, temp_allocator)
n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
return _cleanpath_from_buf(buf[:n], allocator)
}
@@ -155,9 +155,9 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) {
return nil, _get_platform_error()
}
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
- buf := make([]u16, max(n, 260)+1, temp_allocator())
+ buf := make([]u16, max(n, 260)+1, temp_allocator)
n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0)
return _cleanpath_strip_prefix(buf[:n]), nil
}
@@ -236,14 +236,30 @@ _file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: wi
return
}
+// a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC)
+time_as_filetime :: #force_inline proc(t: time.Time) -> (ft: win32.LARGE_INTEGER) {
+ win := u64(t._nsec / 100) + 116444736000000000
+ return win32.LARGE_INTEGER(win)
+}
+
+filetime_as_time_li :: #force_inline proc(ft: win32.LARGE_INTEGER) -> (t: time.Time) {
+ return {_nsec=(i64(ft) - 116444736000000000) * 100}
+}
+
+filetime_as_time_ft :: #force_inline proc(ft: win32.FILETIME) -> (t: time.Time) {
+ return filetime_as_time_li(win32.LARGE_INTEGER(ft.dwLowDateTime) + win32.LARGE_INTEGER(ft.dwHighDateTime) << 32)
+}
+
+filetime_as_time :: proc{filetime_as_time_ft, filetime_as_time_li}
+
_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) {
fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow)
type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
fi.type = type
fi.mode |= mode
- fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
- fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime))
- fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
+ fi.creation_time = filetime_as_time(d.ftCreationTime)
+ fi.modification_time = filetime_as_time(d.ftLastWriteTime)
+ fi.access_time = filetime_as_time(d.ftLastAccessTime)
fi.fullpath, e = full_path_from_name(name, allocator)
fi.name = basename(fi.fullpath)
return
@@ -254,9 +270,9 @@ _file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string
type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0)
fi.type = type
fi.mode |= mode
- fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
- fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime))
- fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
+ fi.creation_time = filetime_as_time(d.ftCreationTime)
+ fi.modification_time = filetime_as_time(d.ftLastWriteTime)
+ fi.access_time = filetime_as_time(d.ftLastAccessTime)
fi.fullpath, e = full_path_from_name(name, allocator)
fi.name = basename(fi.fullpath)
return
@@ -286,9 +302,9 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA
type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, h, 0)
fi.type = type
fi.mode |= mode
- fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime))
- fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime))
- fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime))
+ fi.creation_time = filetime_as_time(d.ftCreationTime)
+ fi.modification_time = filetime_as_time(d.ftLastWriteTime)
+ fi.access_time = filetime_as_time(d.ftLastAccessTime)
return fi, nil
}
@@ -310,42 +326,68 @@ _is_reserved_name :: proc(path: string) -> bool {
return false
}
-_is_UNC :: proc(path: string) -> bool {
- return _volume_name_len(path) > 2
-}
-
-_volume_name_len :: proc(path: string) -> int {
+_volume_name_len :: proc(path: string) -> (length: int) {
if len(path) < 2 {
return 0
}
- c := path[0]
+
if path[1] == ':' {
- switch c {
+ switch path[0] {
case 'a'..='z', 'A'..='Z':
return 2
}
}
- // URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
- if l := len(path); l >= 5 && _is_path_separator(path[0]) && _is_path_separator(path[1]) &&
- !_is_path_separator(path[2]) && path[2] != '.' {
- for n := 3; n < l-1; n += 1 {
- if _is_path_separator(path[n]) {
- n += 1
- if !_is_path_separator(path[n]) {
- if path[n] == '.' {
- break
- }
- }
- for ; n < l; n += 1 {
- if _is_path_separator(path[n]) {
- break
- }
- }
- return n
+ /*
+ See: URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
+ Further allowed paths can be of the form of:
+ - \\server\share or \\server\share\more\path
+ - \\?\C:\...
+ - \\.\PhysicalDriveX
+ */
+ // Any remaining kind of path has to start with two slashes.
+ if !_is_path_separator(path[0]) || !_is_path_separator(path[1]) {
+ return 0
+ }
+
+ // Device path. The volume name is the whole string
+ if len(path) >= 5 && path[2] == '.' && _is_path_separator(path[3]) {
+ return len(path)
+ }
+
+ // We're a UNC share `\\host\share`, file namespace `\\?\C:` or UNC in file namespace `\\?\\host\share`
+ prefix := 2
+
+ // File namespace.
+ if len(path) >= 5 && path[2] == '?' && _is_path_separator(path[3]) {
+ if _is_path_separator(path[4]) {
+ // `\\?\\` UNC path in file namespace
+ prefix = 5
+ }
+
+ if len(path) >= 6 && path[5] == ':' {
+ switch path[4] {
+ case 'a'..='z', 'A'..='Z':
+ return 6
+ case:
+ return 0
}
- break
}
}
- return 0
-}
+
+ // UNC path, minimum version of the volume is `\\h\s` for host, share.
+ // Can also contain an IP address in the host position.
+ slash_count := 0
+ for i in prefix.. 0 {
+ slash_count += 1
+
+ if slash_count == 2 {
+ return i
+ }
+ }
+ }
+
+ return len(path)
+}
\ No newline at end of file
diff --git a/core/os/os2/temp_file.odin b/core/os/os2/temp_file.odin
index e93117f02..ad20b5706 100644
--- a/core/os/os2/temp_file.odin
+++ b/core/os/os2/temp_file.odin
@@ -15,13 +15,13 @@ MAX_ATTEMPTS :: 1<<13 // Should be enough for everyone, right?
// The caller must `close` the file once finished with.
@(require_results)
create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) {
- TEMP_ALLOCATOR_GUARD()
- dir := dir if dir != "" else temp_directory(temp_allocator()) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+ dir := dir if dir != "" else temp_directory(temp_allocator) or_return
prefix, suffix := _prefix_and_suffix(pattern) or_return
prefix = temp_join_path(dir, prefix) or_return
rand_buf: [10]byte
- name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator())
+ name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator)
attempts := 0
for {
@@ -47,13 +47,13 @@ mkdir_temp :: make_directory_temp
// If `dir` is an empty tring, `temp_directory()` will be used.
@(require_results)
make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (temp_path: string, err: Error) {
- TEMP_ALLOCATOR_GUARD()
- dir := dir if dir != "" else temp_directory(temp_allocator()) or_return
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+ dir := dir if dir != "" else temp_directory(temp_allocator) or_return
prefix, suffix := _prefix_and_suffix(pattern) or_return
prefix = temp_join_path(dir, prefix) or_return
rand_buf: [10]byte
- name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator())
+ name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator)
attempts := 0
for {
@@ -70,7 +70,7 @@ make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator)
return "", err
}
if err == .Not_Exist {
- if _, serr := stat(dir, temp_allocator()); serr == .Not_Exist {
+ if _, serr := stat(dir, temp_allocator); serr == .Not_Exist {
return "", serr
}
}
@@ -89,9 +89,11 @@ temp_directory :: proc(allocator: runtime.Allocator) -> (string, Error) {
@(private="file")
temp_join_path :: proc(dir, name: string) -> (string, runtime.Allocator_Error) {
+ temp_allocator := TEMP_ALLOCATOR_GUARD({})
+
if len(dir) > 0 && is_path_separator(dir[len(dir)-1]) {
- return concatenate({dir, name}, temp_allocator(),)
+ return concatenate({dir, name}, temp_allocator,)
}
- return concatenate({dir, Path_Separator_String, name}, temp_allocator())
+ return concatenate({dir, Path_Separator_String, name}, temp_allocator)
}
diff --git a/core/os/os2/temp_file_linux.odin b/core/os/os2/temp_file_linux.odin
index 4eacbc54a..310720cbe 100644
--- a/core/os/os2/temp_file_linux.odin
+++ b/core/os/os2/temp_file_linux.odin
@@ -4,8 +4,8 @@ package os2
import "base:runtime"
_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) {
- TEMP_ALLOCATOR_GUARD()
- tmpdir := get_env("TMPDIR", temp_allocator())
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
+ tmpdir := get_env("TMPDIR", temp_allocator)
if tmpdir == "" {
tmpdir = "/tmp"
}
diff --git a/core/os/os2/temp_file_windows.odin b/core/os/os2/temp_file_windows.odin
index 3e3e1285c..9d75ef99d 100644
--- a/core/os/os2/temp_file_windows.odin
+++ b/core/os/os2/temp_file_windows.odin
@@ -9,9 +9,9 @@ _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Er
if n == 0 {
return "", nil
}
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
- b := make([]u16, max(win32.MAX_PATH, n), temp_allocator())
+ b := make([]u16, max(win32.MAX_PATH, n), temp_allocator)
n = win32.GetTempPathW(u32(len(b)), raw_data(b))
if n == 3 && b[1] == ':' && b[2] == '\\' {
diff --git a/core/os/os2/user.odin b/core/os/os2/user.odin
index 7fcc87bf8..b2856a319 100644
--- a/core/os/os2/user.odin
+++ b/core/os/os2/user.odin
@@ -4,27 +4,27 @@ import "base:runtime"
@(require_results)
user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
#partial switch ODIN_OS {
case .Windows:
- dir = get_env("LocalAppData", temp_allocator())
+ dir = get_env("LocalAppData", temp_allocator)
if dir != "" {
- dir = clone_string(dir, allocator) or_return
+ dir = clone_string(dir, temp_allocator) or_return
}
case .Darwin:
- dir = get_env("HOME", temp_allocator())
+ dir = get_env("HOME", temp_allocator)
if dir != "" {
- dir = concatenate({dir, "/Library/Caches"}, allocator) or_return
+ dir = concatenate({dir, "/Library/Caches"}, temp_allocator) or_return
}
case: // All other UNIX systems
dir = get_env("XDG_CACHE_HOME", allocator)
if dir == "" {
- dir = get_env("HOME", temp_allocator())
+ dir = get_env("HOME", temp_allocator)
if dir == "" {
return
}
- dir = concatenate({dir, "/.cache"}, allocator) or_return
+ dir = concatenate({dir, "/.cache"}, temp_allocator) or_return
}
}
if dir == "" {
@@ -35,23 +35,23 @@ user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error
@(require_results)
user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) {
- TEMP_ALLOCATOR_GUARD()
+ temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator })
#partial switch ODIN_OS {
case .Windows:
- dir = get_env("AppData", temp_allocator())
+ dir = get_env("AppData", temp_allocator)
if dir != "" {
dir = clone_string(dir, allocator) or_return
}
case .Darwin:
- dir = get_env("HOME", temp_allocator())
+ dir = get_env("HOME", temp_allocator)
if dir != "" {
dir = concatenate({dir, "/.config"}, allocator) or_return
}
case: // All other UNIX systems
dir = get_env("XDG_CONFIG_HOME", allocator)
if dir == "" {
- dir = get_env("HOME", temp_allocator())
+ dir = get_env("HOME", temp_allocator)
if dir == "" {
return
}
diff --git a/core/reflect/reflect.odin b/core/reflect/reflect.odin
index 7f79acb77..b3315a0c3 100644
--- a/core/reflect/reflect.odin
+++ b/core/reflect/reflect.odin
@@ -24,7 +24,6 @@ Type_Info_Enumerated_Array :: runtime.Type_Info_Enumerated_Array
Type_Info_Dynamic_Array :: runtime.Type_Info_Dynamic_Array
Type_Info_Slice :: runtime.Type_Info_Slice
Type_Info_Parameters :: runtime.Type_Info_Parameters
-Type_Info_Tuple :: runtime.Type_Info_Parameters
Type_Info_Struct :: runtime.Type_Info_Struct
Type_Info_Union :: runtime.Type_Info_Union
Type_Info_Enum :: runtime.Type_Info_Enum
@@ -58,7 +57,7 @@ Type_Kind :: enum {
Enumerated_Array,
Dynamic_Array,
Slice,
- Tuple,
+ Parameters,
Struct,
Union,
Enum,
@@ -93,7 +92,7 @@ type_kind :: proc(T: typeid) -> Type_Kind {
case Type_Info_Enumerated_Array: return .Enumerated_Array
case Type_Info_Dynamic_Array: return .Dynamic_Array
case Type_Info_Slice: return .Slice
- case Type_Info_Parameters: return .Tuple
+ case Type_Info_Parameters: return .Parameters
case Type_Info_Struct: return .Struct
case Type_Info_Union: return .Union
case Type_Info_Enum: return .Enum
@@ -176,6 +175,7 @@ typeid_elem :: proc(id: typeid) -> typeid {
case Type_Info_Enumerated_Array: return v.elem.id
case Type_Info_Slice: return v.elem.id
case Type_Info_Dynamic_Array: return v.elem.id
+ case Type_Info_Simd_Vector: return v.elem.id
}
return id
}
@@ -260,7 +260,11 @@ length :: proc(val: any) -> int {
} else {
return (^runtime.Raw_String)(val.data).len
}
+
+ case Type_Info_Simd_Vector:
+ return a.count
}
+
return 0
}
@@ -286,7 +290,11 @@ capacity :: proc(val: any) -> int {
case Type_Info_Map:
return runtime.map_cap((^runtime.Raw_Map)(val.data)^)
+
+ case Type_Info_Simd_Vector:
+ return a.count
}
+
return 0
}
@@ -1431,6 +1439,11 @@ as_f64 :: proc(a: any) -> (value: f64, valid: bool) {
case Type_Info_Complex:
switch v in a {
+ case complex32:
+ if imag(v) == 0 {
+ value = f64(real(v))
+ valid = true
+ }
case complex64:
if imag(v) == 0 {
value = f64(real(v))
@@ -1445,6 +1458,11 @@ as_f64 :: proc(a: any) -> (value: f64, valid: bool) {
case Type_Info_Quaternion:
switch v in a {
+ case quaternion64:
+ if imag(v) == 0 && jmag(v) == 0 && kmag(v) == 0 {
+ value = f64(real(v))
+ valid = true
+ }
case quaternion128:
if imag(v) == 0 && jmag(v) == 0 && kmag(v) == 0 {
value = f64(real(v))
@@ -1638,13 +1656,40 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_
return equal(va, vb, including_indirect_array_recursion, recursion_level+1)
case Type_Info_Map:
return false
+ case Type_Info_Float:
+ x, _ := as_f64(a)
+ y, _ := as_f64(b)
+ return x == y
+ case Type_Info_Complex:
+ switch x in a {
+ case complex32:
+ #no_type_assert y := b.(complex32)
+ return x == y
+ case complex64:
+ #no_type_assert y := b.(complex64)
+ return x == y
+ case complex128:
+ #no_type_assert y := b.(complex128)
+ return x == y
+ }
+ return false
+ case Type_Info_Quaternion:
+ switch x in a {
+ case quaternion64:
+ #no_type_assert y := b.(quaternion64)
+ return x == y
+ case quaternion128:
+ #no_type_assert y := b.(quaternion128)
+ return x == y
+ case quaternion256:
+ #no_type_assert y := b.(quaternion256)
+ return x == y
+ }
+ return false
case
Type_Info_Boolean,
Type_Info_Integer,
Type_Info_Rune,
- Type_Info_Float,
- Type_Info_Complex,
- Type_Info_Quaternion,
Type_Info_Type_Id,
Type_Info_Pointer,
Type_Info_Multi_Pointer,
diff --git a/core/reflect/types.odin b/core/reflect/types.odin
index cb31a27e2..ba47fee4d 100644
--- a/core/reflect/types.odin
+++ b/core/reflect/types.odin
@@ -348,12 +348,6 @@ is_parameters :: proc(info: ^Type_Info) -> bool {
_, ok := type_info_base(info).variant.(Type_Info_Parameters)
return ok
}
-@(require_results, deprecated="prefer is_parameters")
-is_tuple :: proc(info: ^Type_Info) -> bool {
- if info == nil { return false }
- _, ok := type_info_base(info).variant.(Type_Info_Parameters)
- return ok
-}
@(require_results)
is_struct :: proc(info: ^Type_Info) -> bool {
if info == nil { return false }
diff --git a/core/simd/simd.odin b/core/simd/simd.odin
index 0bce4e16a..a97155f58 100644
--- a/core/simd/simd.odin
+++ b/core/simd/simd.odin
@@ -1328,13 +1328,18 @@ Example:
// to load valid positions of the `ptrs` array, and the array of defaults which
// will have `127` in each position as the default value.
- v1 := [4] f32 {1, 2, 3, 4};
- v2 := [4] f32 {9, 10,11,12};
- ptrs := #simd [4]rawptr { &v1[1], nil, &v2[1], nil }
- mask := #simd [4]bool { true, false, true, false }
- defaults := #simd [4]f32 { 0x7f, 0x7f, 0x7f, 0x7f }
- res := simd.gather(ptrs, defaults, mask)
- fmt.println(res)
+ import "core:fmt"
+ import "core:simd"
+
+ simd_gather_example :: proc() {
+ v1 := [4] f32 {1, 2, 3, 4};
+ v2 := [4] f32 {9, 10,11,12};
+ ptrs := #simd [4]rawptr { &v1[1], nil, &v2[1], nil }
+ mask := #simd [4]bool { true, false, true, false }
+ defaults := #simd [4]f32 { 0x7f, 0x7f, 0x7f, 0x7f }
+ res := simd.gather(ptrs, defaults, mask)
+ fmt.println(res)
+ }
Output:
@@ -1396,14 +1401,19 @@ Example:
// vectors. The addresses of store destinations are written to the first and the
// third argument of the `ptr` vector, and the `mask` is set accordingly.
- v1 := [4] f32 {1, 2, 3, 4};
- v2 := [4] f32 {5, 6, 7, 8};
- ptrs := #simd [4]rawptr { &v1[1], nil, &v2[1], nil }
- mask := #simd [4]bool { true, false, true, false }
- vals := #simd [4]f32 { 0x7f, 0x7f, 0x7f, 0x7f }
- simd.scatter(ptrs, vals, mask)
- fmt.println(v1)
- fmt.println(v2)
+ import "core:fmt"
+ import "core:simd"
+
+ simd_scatter_example :: proc() {
+ v1 := [4] f32 {1, 2, 3, 4};
+ v2 := [4] f32 {5, 6, 7, 8};
+ ptrs := #simd [4]rawptr { &v1[1], nil, &v2[1], nil }
+ mask := #simd [4]bool { true, false, true, false }
+ vals := #simd [4]f32 { 0x7f, 0x7f, 0x7f, 0x7f }
+ simd.scatter(ptrs, vals, mask)
+ fmt.println(v1)
+ fmt.println(v2)
+ }
Output:
@@ -1467,11 +1477,16 @@ Example:
// third value (selected by the mask). The masked-off values are given the value
// of 127 (`0x7f`).
- src := [4] f32 {1, 2, 3, 4};
- mask := #simd [4]bool { true, false, true, false }
- vals := #simd [4]f32 { 0x7f, 0x7f, 0x7f, 0x7f }
- res := simd.masked_load(&src, vals, mask)
- fmt.println(res)
+ import "core:fmt"
+ import "core:simd"
+
+ simd_masked_load_example :: proc() {
+ src := [4] f32 {1, 2, 3, 4};
+ mask := #simd [4]bool { true, false, true, false }
+ vals := #simd [4]f32 { 0x7f, 0x7f, 0x7f, 0x7f }
+ res := simd.masked_load(&src, vals, mask)
+ fmt.println(res)
+ }
Output:
@@ -1526,11 +1541,16 @@ Example:
// Example below stores the value 127 into the first and the third slot of the
// vector `v`.
- v := [4] f32 {1, 2, 3, 4};
- mask := #simd [4]bool { true, false, true, false }
- vals := #simd [4]f32 { 0x7f, 0x7f, 0x7f, 0x7f }
- simd.masked_store(&v, vals, mask)
- fmt.println(v)
+ import "core:fmt"
+ import "core:simd"
+
+ simd_masked_store_example :: proc() {
+ v := [4] f32 {1, 2, 3, 4};
+ mask := #simd [4]bool { true, false, true, false }
+ vals := #simd [4]f32 { 0x7f, 0x7f, 0x7f, 0x7f }
+ simd.masked_store(&v, vals, mask)
+ fmt.println(v)
+ }
Output:
@@ -1600,11 +1620,16 @@ Example:
// the third lane of the result vector. All the other lanes of the result vector
// will be initialized to the default value `127`.
- v := [2] f64 {1, 2};
- mask := #simd [4]bool { true, false, true, false }
- vals := #simd [4]f64 { 0x7f, 0x7f, 0x7f, 0x7f }
- res := simd.masked_expand_load(&v, vals, mask)
- fmt.println(res)
+ import "core:fmt"
+ import "core:simd"
+
+ simd_masked_expand_load_example :: proc() {
+ v := [2] f64 {1, 2};
+ mask := #simd [4]bool { true, false, true, false }
+ vals := #simd [4]f64 { 0x7f, 0x7f, 0x7f, 0x7f }
+ res := simd.masked_expand_load(&v, vals, mask)
+ fmt.println(res)
+ }
Output:
@@ -1661,11 +1686,16 @@ Example:
// vector, the first and the third value. The items in the mask are set to `true`
// in those lanes.
- v := [2] f64 { };
- mask := #simd [4]bool { true, false, true, false }
- vals := #simd [4]f64 { 1, 2, 3, 4 }
- simd.masked_compress_store(&v, vals, mask)
- fmt.println(v)
+ import "core:fmt"
+ import "core:simd"
+
+ simd_masked_compress_store_example :: proc() {
+ v := [2] f64 { };
+ mask := #simd [4]bool { true, false, true, false }
+ vals := #simd [4]f64 { 1, 2, 3, 4 }
+ simd.masked_compress_store(&v, vals, mask)
+ fmt.println(v)
+ }
Output:
@@ -1729,7 +1759,103 @@ Returns:
replace :: intrinsics.simd_replace
/*
-Reduce a vector to a scalar by adding up all the lanes.
+Reduce a vector to a scalar by adding up all the lanes in a bisecting fashion.
+
+This procedure returns a scalar that is the sum of all lanes, calculated by
+bisecting the vector into two parts, where the first contains lanes [0, N/2)
+and the second contains lanes [N/2, N), and adding the two halves element-wise
+to produce N/2 values. This is repeated until only a single element remains.
+This order may be faster to compute than the ordered sum for floats, as it can
+often be better parallelized.
+
+The order of the sum may be important for accounting for precision errors in
+floating-point computation, as floating-point addition is not associative, that
+is `(a+b)+c` may not be equal to `a+(b+c)`.
+
+Inputs:
+- `v`: The vector to reduce.
+
+Result:
+- Sum of all lanes, as a scalar.
+
+**Operation**:
+
+ for n > 1 {
+ n = n / 2
+ for i in 0 ..< n {
+ a[i] += a[i+n]
+ }
+ }
+ res := a[0]
+
+Graphical representation of the operation for N=4:
+
+ +-----------------------+
+ | v0 | v1 | v2 | v3 |
+ +-----------------------+
+ | | | |
+ [+]<-- | ---' |
+ | [+]<--------'
+ | |
+ `>[+]<'
+ |
+ v
+ +-----+
+ result: | y0 |
+ +-----+
+*/
+reduce_add_bisect :: intrinsics.simd_reduce_add_bisect
+
+/*
+Reduce a vector to a scalar by multiplying up all the lanes in a bisecting fashion.
+
+This procedure returns a scalar that is the product of all lanes, calculated by
+bisecting the vector into two parts, where the first contains indices [0, N/2)
+and the second contains indices [N/2, N), and multiplying the two halves
+together element-wise to produce N/2 values. This is repeated until only a
+single element remains. This order may be faster to compute than the ordered
+product for floats, as it can often be better parallelized.
+
+The order of the product may be important for accounting for precision errors
+in floating-point computation, as floating-point multiplication is not
+associative, that is `(a*b)*c` may not be equal to `a*(b*c)`.
+
+Inputs:
+- `v`: The vector to reduce.
+
+Result:
+- Product of all lanes, as a scalar.
+
+**Operation**:
+
+ for n > 1 {
+ n = n / 2
+ for i in 0 ..< n {
+ a[i] *= a[i+n]
+ }
+ }
+ res := a[0]
+
+Graphical representation of the operation for N=4:
+
+ +-----------------------+
+ | v0 | v1 | v2 | v3 |
+ +-----------------------+
+ | | | |
+ [x]<-- | ---' |
+ | [x]<--------'
+ | |
+ `>[x]<'
+ |
+ v
+ +-----+
+ result: | y0 |
+ +-----+
+*/
+reduce_mul_bisect :: intrinsics.simd_reduce_mul_bisect
+
+/*
+Reduce a vector to a scalar by adding up all the lanes in an ordered fashion.
This procedure returns a scalar that is the ordered sum of all lanes. The
ordered sum may be important for accounting for precision errors in
@@ -1752,7 +1878,7 @@ Result:
reduce_add_ordered :: intrinsics.simd_reduce_add_ordered
/*
-Reduce a vector to a scalar by multiplying all the lanes.
+Reduce a vector to a scalar by multiplying all the lanes in an ordered fashion.
This procedure returns a scalar that is the ordered product of all lanes.
The ordered product may be important for accounting for precision errors in
@@ -1774,6 +1900,100 @@ Result:
*/
reduce_mul_ordered :: intrinsics.simd_reduce_mul_ordered
+/*
+Reduce a vector to a scalar by adding up all the lanes in a pairwise fashion.
+
+This procedure returns a scalar that is the sum of all lanes, calculated by
+adding each even-indexed element with the following odd-indexed element to
+produce N/2 values. This is repeated until only a single element remains. This
+order is supported by hardware instructions for some types/architectures (e.g.
+i16/i32/f32/f64 on x86 SSE, i8/i16/i32/f32 on ARM NEON).
+
+The order of the sum may be important for accounting for precision errors in
+floating-point computation, as floating-point addition is not associative, that
+is `(a+b)+c` may not be equal to `a+(b+c)`.
+
+Inputs:
+- `v`: The vector to reduce.
+
+Result:
+- Sum of all lanes, as a scalar.
+
+**Operation**:
+
+ for n > 1 {
+ n = n / 2
+ for i in 0 ..< n {
+ a[i] = a[2*i+0] + a[2*i+1]
+ }
+ }
+ res := a[0]
+
+Graphical representation of the operation for N=4:
+
+ +-----------------------+
+ v: | v0 | v1 | v2 | v3 |
+ +-----------------------+
+ | | | |
+ `>[+]<' `>[+]<'
+ | |
+ `--->[+]<--'
+ |
+ v
+ +-----+
+ result: | y0 |
+ +-----+
+*/
+reduce_add_pairs :: intrinsics.simd_reduce_add_pairs
+
+/*
+Reduce a vector to a scalar by multiplying all the lanes in a pairwise fashion.
+
+This procedure returns a scalar that is the product of all lanes, calculated by
+bisecting the vector into two parts, where the first contains lanes [0, N/2)
+and the second contains lanes [N/2, N), and multiplying the two halves together
+multiplying each even-indexed element with the following odd-indexed element to
+produce N/2 values. This is repeated until only a single element remains. This
+order may be faster to compute than the ordered product for floats, as it can
+often be better parallelized.
+
+The order of the product may be important for accounting for precision errors
+in floating-point computation, as floating-point multiplication is not
+associative, that is `(a*b)*c` may not be equal to `a*(b*c)`.
+
+Inputs:
+- `v`: The vector to reduce.
+
+Result:
+- Product of all lanes, as a scalar.
+
+**Operation**:
+
+ for n > 1 {
+ n = n / 2
+ for i in 0 ..< n {
+ a[i] = a[2*i+0] * a[2*i+1]
+ }
+ }
+ res := a[0]
+
+Graphical representation of the operation for N=4:
+
+ +-----------------------+
+ v: | v0 | v1 | v2 | v3 |
+ +-----------------------+
+ | | | |
+ `>[x]<' `>[x]<'
+ | |
+ `--->[x]<--'
+ |
+ v
+ +-----+
+ result: | y0 |
+ +-----+
+*/
+reduce_mul_pairs :: intrinsics.simd_reduce_mul_pairs
+
/*
Reduce a vector to a scalar by finding the minimum value between all of the lanes.
@@ -1949,14 +2169,19 @@ Example:
// The example below shows how the indices are used to determine which lanes of the
// input vector get written into the result vector.
-
- x := #simd [4]f32 { 1.5, 2.5, 3.5, 4.5 }
- res := simd.swizzle(x, 0, 3, 1, 1)
- fmt.println("res")
+
+ import "core:fmt"
+ import "core:simd"
+
+ swizzle_example :: proc() {
+ x := #simd [4]f32 { 1.5, 2.5, 3.5, 4.5 }
+ res := simd.swizzle(x, 0, 3, 1, 1)
+ fmt.println(res)
+ }
Output:
- [ 1.5, 3.5, 2.5, 2.5 ]
+ <1.5, 4.5, 2.5, 2.5>
The graphical representation of the operation is as follows. The `idx` vector in
the picture represents the `indices` parameter:
@@ -2013,8 +2238,14 @@ Example:
// Since lanes 0, 1, 4, 7 contain negative numbers, the most significant
// bits for them will be set.
- v := #simd [8]i32 { -1, -2, +3, +4, -5, +6, +7, -8 }
- fmt.println(simd.extract_msbs(v))
+
+ import "core:fmt"
+ import "core:simd"
+
+ simd_extract_msbs_example :: proc() {
+ v := #simd [8]i32 { -1, -2, +3, +4, -5, +6, +7, -8 }
+ fmt.println(simd.extract_msbs(v))
+ }
Output:
@@ -2052,8 +2283,14 @@ Example:
// Since lanes 0, 2, 4, 6 contain odd integers, the least significant bits
// for these lanes are set.
- v := #simd [8]i32 { -1, -2, +3, +4, -5, +6, +7, -8 }
- fmt.println(simd.extract_lsbs(v))
+
+ import "core:fmt"
+ import "core:simd"
+
+ simd_extract_lsbs_example :: proc() {
+ v := #simd [8]i32 { -1, -2, +3, +4, -5, +6, +7, -8 }
+ fmt.println(simd.extract_lsbs(v))
+ }
Output:
@@ -2097,15 +2334,20 @@ Example:
// The example below shows how the indices are used to determine lanes of the
// input vector that are shuffled into the result vector.
-
- a := #simd [4]f32 { 1, 2, 3, 4 }
- b := #simd [4]f32 { 5, 6, 7, 8 }
- res := simd.shuffle(a, b, 0, 4, 2, 5)
- fmt.println("res")
+
+ import "core:fmt"
+ import "core:simd"
+
+ simd_shuffle_example :: proc() {
+ a := #simd [4]f32 { 1, 2, 3, 4 }
+ b := #simd [4]f32 { 5, 6, 7, 8 }
+ res := simd.shuffle(a, b, 0, 4, 2, 5)
+ fmt.println(res)
+ }
Output:
- [ 1, 5, 3, 6 ]
+ <1, 5, 3, 6>
The graphical representation of the operation is as follows. The `idx` vector in
the picture represents the `indices` parameter:
@@ -2163,14 +2405,20 @@ Example:
// The following example selects values from the two input vectors, `a` and `b`
// into a single vector.
- a := #simd [4] f64 { 1,2,3,4 }
- b := #simd [4] f64 { 5,6,7,8 }
- cond := #simd[4] int { 1, 0, 1, 0 }
- fmt.println(simd.select(cond,a,b))
+
+ import "core:fmt"
+ import "core:simd"
+
+ simd_select_example :: proc() {
+ a := #simd [4] f64 { 1,2,3,4 }
+ b := #simd [4] f64 { 5,6,7,8 }
+ cond := #simd[4] int { 1, 0, 1, 0 }
+ fmt.println(simd.select(cond,a,b))
+ }
Output:
- [ 1, 6, 3, 8 ]
+ <1, 6, 3, 8>
Graphically, the operation looks as follows. The `t` and `f` represent the
`true` and `false` vectors respectively:
@@ -2452,3 +2700,17 @@ Example:
recip :: #force_inline proc "contextless" (v: $T/#simd[$LANES]$E) -> T where intrinsics.type_is_float(E) {
return T(1) / v
}
+
+
+/*
+Create a vector where each lane contains the index of that lane.
+Inputs:
+- `V`: The type of the vector to create.
+Result:
+- A vector of the given type, where each lane contains the index of that lane.
+**Operation**:
+ for i in 0 ..< N {
+ res[i] = i
+ }
+*/
+indices :: intrinsics.simd_indices
\ No newline at end of file
diff --git a/core/simd/x86/bmi.odin b/core/simd/x86/bmi.odin
new file mode 100644
index 000000000..661272dbf
--- /dev/null
+++ b/core/simd/x86/bmi.odin
@@ -0,0 +1,79 @@
+#+build i386, amd64
+package simd_x86
+
+import "base:intrinsics"
+
+@(require_results, enable_target_feature="bmi")
+_andn_u32 :: #force_inline proc "c" (a, b: u32) -> u32 {
+ return a &~ b
+}
+@(require_results, enable_target_feature="bmi")
+_andn_u64 :: #force_inline proc "c" (a, b: u64) -> u64 {
+ return a &~ b
+}
+
+@(require_results, enable_target_feature="bmi")
+_bextr_u32 :: #force_inline proc "c" (a, start, len: u32) -> u32 {
+ return bextr_u32(a, (start & 0xff) | (len << 8))
+}
+@(require_results, enable_target_feature="bmi")
+_bextr_u64 :: #force_inline proc "c" (a: u64, start, len: u32) -> u64 {
+ return bextr_u64(a, cast(u64)((start & 0xff) | (len << 8)))
+}
+
+@(require_results, enable_target_feature="bmi")
+_bextr2_u32 :: #force_inline proc "c" (a, control: u32) -> u32 {
+ return bextr_u32(a, control)
+}
+@(require_results, enable_target_feature="bmi")
+_bextr2_u64 :: #force_inline proc "c" (a, control: u64) -> u64 {
+ return bextr_u64(a, control)
+}
+
+@(require_results, enable_target_feature="bmi")
+_blsi_u32 :: #force_inline proc "c" (a: u32) -> u32 {
+ return a & -a
+}
+@(require_results, enable_target_feature="bmi")
+_blsi_u64 :: #force_inline proc "c" (a: u64) -> u64 {
+ return a & -a
+}
+
+@(require_results, enable_target_feature="bmi")
+_blsmsk_u32 :: #force_inline proc "c" (a: u32) -> u32 {
+ return a ~ (a-1)
+}
+@(require_results, enable_target_feature="bmi")
+_blsmsk_u64 :: #force_inline proc "c" (a: u64) -> u64 {
+ return a ~ (a-1)
+}
+
+@(require_results, enable_target_feature="bmi")
+_blsr_u32 :: #force_inline proc "c" (a: u32) -> u32 {
+ return a & (a-1)
+}
+@(require_results, enable_target_feature="bmi")
+_blsr_u64 :: #force_inline proc "c" (a: u64) -> u64 {
+ return a & (a-1)
+}
+
+@(require_results, enable_target_feature = "bmi")
+_tzcnt_u16 :: #force_inline proc "c" (a: u16) -> u16 {
+ return intrinsics.count_trailing_zeros(a)
+}
+@(require_results, enable_target_feature = "bmi")
+_tzcnt_u32 :: #force_inline proc "c" (a: u32) -> u32 {
+ return intrinsics.count_trailing_zeros(a)
+}
+@(require_results, enable_target_feature = "bmi")
+_tzcnt_u64 :: #force_inline proc "c" (a: u64) -> u64 {
+ return intrinsics.count_trailing_zeros(a)
+}
+
+@(private, default_calling_convention = "none")
+foreign _ {
+ @(link_name = "llvm.x86.bmi.bextr.32")
+ bextr_u32 :: proc(a, control: u32) -> u32 ---
+ @(link_name = "llvm.x86.bmi.bextr.64")
+ bextr_u64 :: proc(a, control: u64) -> u64 ---
+}
diff --git a/core/simd/x86/bmi2.odin b/core/simd/x86/bmi2.odin
new file mode 100644
index 000000000..65ce7f77c
--- /dev/null
+++ b/core/simd/x86/bmi2.odin
@@ -0,0 +1,46 @@
+#+build i386, amd64
+package simd_x86
+
+@(require_results, enable_target_feature = "bmi2")
+_bzhi_u32 :: #force_inline proc "c" (a, index: u32) -> u32 {
+ return bzhi_u32(a, index)
+}
+@(require_results, enable_target_feature = "bmi2")
+_bzhi_u64 :: #force_inline proc "c" (a, index: u64) -> u64 {
+ return bzhi_u64(a, index)
+}
+
+@(require_results, enable_target_feature = "bmi2")
+_pdep_u32 :: #force_inline proc "c" (a, mask: u32) -> u32 {
+ return pdep_u32(a, mask)
+}
+@(require_results, enable_target_feature = "bmi2")
+_pdep_u64 :: #force_inline proc "c" (a, mask: u64) -> u64 {
+ return pdep_u64(a, mask)
+}
+
+@(require_results, enable_target_feature = "bmi2")
+_pext_u32 :: #force_inline proc "c" (a, mask: u32) -> u32 {
+ return pext_u32(a, mask)
+}
+@(require_results, enable_target_feature = "bmi2")
+_pext_u64 :: #force_inline proc "c" (a, mask: u64) -> u64 {
+ return pext_u64(a, mask)
+}
+
+
+@(private, default_calling_convention = "none")
+foreign _ {
+ @(link_name = "llvm.x86.bmi.bzhi.32")
+ bzhi_u32 :: proc(a, index: u32) -> u32 ---
+ @(link_name = "llvm.x86.bmi.bzhi.64")
+ bzhi_u64 :: proc(a, index: u64) -> u64 ---
+ @(link_name = "llvm.x86.bmi.pdep.32")
+ pdep_u32 :: proc(a, mask: u32) -> u32 ---
+ @(link_name = "llvm.x86.bmi.pdep.64")
+ pdep_u64 :: proc(a, mask: u64) -> u64 ---
+ @(link_name = "llvm.x86.bmi.pext.32")
+ pext_u32 :: proc(a, mask: u32) -> u32 ---
+ @(link_name = "llvm.x86.bmi.pext.64")
+ pext_u64 :: proc(a, mask: u64) -> u64 ---
+}
diff --git a/core/strconv/decimal/decimal.odin b/core/strconv/decimal/decimal.odin
index 06503d01a..cb9285083 100644
--- a/core/strconv/decimal/decimal.odin
+++ b/core/strconv/decimal/decimal.odin
@@ -399,7 +399,7 @@ shift_left :: proc(a: ^Decimal, k: uint) #no_bounds_check {
a.decimal_point += delta
- a.count = clamp(a.count, 0, len(a.digits))
+ a.count = clamp(a.count+delta, 0, len(a.digits))
trim(a)
}
/*
@@ -562,4 +562,3 @@ rounded_integer :: proc(a: ^Decimal) -> u64 {
}
return n
}
-
diff --git a/core/strconv/generic_float.odin b/core/strconv/generic_float.odin
index b049f0fe1..b126dc3b6 100644
--- a/core/strconv/generic_float.odin
+++ b/core/strconv/generic_float.odin
@@ -339,45 +339,37 @@ Converts a decimal number to its floating-point representation with the given fo
- b: The bits representing the floating-point number
- overflow: A boolean indicating whether an overflow occurred during conversion
*/
-@(private)
decimal_to_float_bits :: proc(d: ^decimal.Decimal, info: ^Float_Info) -> (b: u64, overflow: bool) {
- end :: proc "contextless" (d: ^decimal.Decimal, mant: u64, exp: int, info: ^Float_Info) -> (bits: u64) {
+ overflow_end :: proc "contextless" (d: ^decimal.Decimal, info: ^Float_Info) -> (u64, bool) {
+ mant: u64 = 0
+ exp: int = 1< (bits: u64, overflow: bool) {
bits = mant & (u64(1)< bool {
- mant^ = 0
- exp^ = 1< 310 {
- set_overflow(&mant, &exp, info)
- b = end(d, mant, exp, info)
- return
+ return overflow_end(d, info)
} else if d.decimal_point < -330 {
- mant = 0
- exp = info.bias
- b = end(d, mant, exp, info)
- return
+ return end(d, 0, info.bias, info, false)
}
@(static, rodata) power_table := [?]int{1, 3, 6, 9, 13, 16, 19, 23, 26}
- exp = 0
+
+ exp := 0
for d.decimal_point > 0 {
n := 27 if d.decimal_point >= len(power_table) else power_table[d.decimal_point]
decimal.shift(d, -n)
@@ -392,35 +384,34 @@ decimal_to_float_bits :: proc(d: ^decimal.Decimal, info: ^Float_Info) -> (b: u64
// go from [0.5, 1) to [1, 2)
exp -= 1
+ // Min rep exp is 1+bias
if exp < info.bias + 1 {
n := info.bias + 1 - exp
- decimal.shift(d, n)
+ decimal.shift(d, -n)
exp += n
}
if (exp-info.bias) >= (1<>= 1
exp += 1
if (exp-info.bias) >= (1< (value: f64, nr: int, ok: bool) {
return transmute(f64)bits, ok
}
+ if len(str) > 2 && str[0] == '0' && str[1] == 'h' {
+ nr = 2
+
+ as_int: u64
+ digits: int
+ for r in str[2:] {
+ if r == '_' {
+ nr += 1
+ continue
+ }
+ v := u64(_digit_value(r))
+ if v >= 16 {
+ break
+ }
+ as_int *= 16
+ as_int += v
+ digits += 1
+ }
+ nr += digits
+ ok = len(str) == nr
+
+ switch digits {
+ case 4:
+ value = cast(f64)transmute(f16)cast(u16)as_int
+ case 8:
+ value = cast(f64)transmute(f32)cast(u32)as_int
+ case 16:
+ value = transmute(f64)as_int
+ case:
+ ok = false
+ }
+ return
+ }
if value, nr, ok = check_special(str); ok {
return
diff --git a/core/strings/builder.odin b/core/strings/builder.odin
index e5a88527a..05382f04e 100644
--- a/core/strings/builder.odin
+++ b/core/strings/builder.odin
@@ -311,7 +311,7 @@ Returns:
- res: A cstring of the Builder's buffer upon success
- err: An optional allocator error if one occured, `nil` otherwise
*/
-to_cstring :: proc(b: ^Builder) -> (res: cstring, err: mem.Allocator_Error) {
+to_cstring :: proc(b: ^Builder) -> (res: cstring, err: mem.Allocator_Error) #optional_allocator_error {
n := append(&b.buf, 0) or_return
if n != 1 {
return nil, .Out_Of_Memory
diff --git a/core/strings/strings.odin b/core/strings/strings.odin
index 52230f572..e15754dff 100644
--- a/core/strings/strings.odin
+++ b/core/strings/strings.odin
@@ -787,7 +787,7 @@ Example:
import "core:fmt"
import "core:strings"
- cut_example :: proc() {
+ cut_clone_example :: proc() {
fmt.println(strings.cut_clone("some example text", 0, 4)) // -> "some"
fmt.println(strings.cut_clone("some example text", 2, 2)) // -> "me"
fmt.println(strings.cut_clone("some example text", 5, 7)) // -> "example"
@@ -3044,7 +3044,6 @@ left_justify :: proc(str: string, length: int, pad: string, allocator := context
pad_len := rune_count(pad)
b: Builder
- builder_init(&b, allocator)
builder_init(&b, 0, len(str) + (remains/pad_len + 1)*len(pad), allocator) or_return
w := to_writer(&b)
@@ -3079,7 +3078,6 @@ right_justify :: proc(str: string, length: int, pad: string, allocator := contex
pad_len := rune_count(pad)
b: Builder
- builder_init(&b, allocator)
builder_init(&b, 0, len(str) + (remains/pad_len + 1)*len(pad), allocator) or_return
w := to_writer(&b)
diff --git a/core/sync/chan/chan.odin b/core/sync/chan/chan.odin
index c470d15f3..eca4c28d7 100644
--- a/core/sync/chan/chan.odin
+++ b/core/sync/chan/chan.odin
@@ -7,16 +7,62 @@ import "core:mem"
import "core:sync"
import "core:math/rand"
+/*
+Determines what operations `Chan` supports.
+*/
Direction :: enum {
Send = -1,
Both = 0,
Recv = +1,
}
+/*
+A typed wrapper around `Raw_Chan` which should be used
+preferably.
+
+Note: all procedures accepting `Raw_Chan` also accept `Chan`.
+
+**Inputs**
+- `$T`: The type of the messages
+- `Direction`: what `Direction` the channel supports
+
+Example:
+
+ import "core:sync/chan"
+
+ chan_example :: proc() {
+ // Create an unbuffered channel with messages of type int,
+ // supporting both sending and receiving.
+ // Creating unidirectional channels, although possible, is useless.
+ c, _ := chan.create(chan.Chan(int), context.allocator)
+ defer chan.destroy(c)
+
+ // This channel can now only be used for receiving messages
+ recv_only_channel: chan.Chan(int, .Recv) = chan.as_recv(c)
+ // This channel can now only be used for sending messages
+ send_only_channel: chan.Chan(int, .Send) = chan.as_send(c)
+ }
+*/
Chan :: struct($T: typeid, $D: Direction = Direction.Both) {
#subtype impl: ^Raw_Chan `fmt:"-"`,
}
+/*
+`Raw_Chan` allows for thread-safe communication using fixed-size messages.
+This is the low-level implementation of `Chan`, which does not include
+the concept of Direction.
+
+Example:
+
+ import "core:sync/chan"
+
+ raw_chan_example :: proc() {
+ // Create an unbuffered channel with messages of type int,
+ c, _ := chan.create_raw(size_of(int), align_of(int), context.allocator)
+ defer chan.destroy(c)
+ }
+
+*/
Raw_Chan :: struct {
// Shared
allocator: runtime.Allocator,
@@ -36,12 +82,66 @@ Raw_Chan :: struct {
unbuffered_data: rawptr,
}
+/*
+Creates a buffered or unbuffered `Chan` instance.
+*Allocates Using Provided Allocator*
+
+**Inputs**
+- `$C`: Type of `Chan` to create
+- [`cap`: The capacity of the channel] omit for creating unbuffered channels
+- `allocator`: The allocator to use
+
+**Returns**:
+- The initialized `Chan`
+- An `Allocator_Error`
+
+Example:
+
+ import "core:sync/chan"
+
+ create_example :: proc() {
+ unbuffered: chan.Chan(int)
+ buffered: chan.Chan(int)
+ err: runtime.Allocator_Error
+
+ unbuffered, err = chan.create(chan.Chan(int), context.allocator)
+ assert(err == .None)
+ defer chan.destroy(unbuffered)
+
+ buffered, err = chan.create(chan.Chan(int), 10, context.allocator)
+ assert(err == .None)
+ defer chan.destroy(buffered)
+ }
+*/
create :: proc{
create_unbuffered,
create_buffered,
}
+/*
+Creates an unbuffered version of the specified `Chan` type.
+
+*Allocates Using Provided Allocator*
+
+**Inputs**
+- `$C`: Type of `Chan` to create
+- `allocator`: The allocator to use
+
+**Returns**:
+- The initialized `Chan`
+- An `Allocator_Error`
+
+Example:
+
+ import "core:sync/chan"
+
+ create_unbuffered_example :: proc() {
+ c, err := chan.create_unbuffered(chan.Chan(int), context.allocator)
+ assert(err == .None)
+ defer chan.destroy(c)
+ }
+*/
@(require_results)
create_unbuffered :: proc($C: typeid/Chan($T), allocator: runtime.Allocator) -> (c: C, err: runtime.Allocator_Error)
where size_of(T) <= int(max(u16)) {
@@ -49,6 +149,30 @@ create_unbuffered :: proc($C: typeid/Chan($T), allocator: runtime.Allocator) ->
return
}
+/*
+Creates a buffered version of the specified `Chan` type.
+
+*Allocates Using Provided Allocator*
+
+**Inputs**
+- `$C`: Type of `Chan` to create
+- `cap`: The capacity of the channel
+- `allocator`: The allocator to use
+
+**Returns**:
+- The initialized `Chan`
+- An `Allocator_Error`
+
+Example:
+
+ import "core:sync/chan"
+
+ create_buffered_example :: proc() {
+ c, err := chan.create_buffered(chan.Chan(int), 10, context.allocator)
+ assert(err == .None)
+ defer chan.destroy(c)
+ }
+*/
@(require_results)
create_buffered :: proc($C: typeid/Chan($T), #any_int cap: int, allocator: runtime.Allocator) -> (c: C, err: runtime.Allocator_Error)
where size_of(T) <= int(max(u16)) {
@@ -56,11 +180,70 @@ create_buffered :: proc($C: typeid/Chan($T), #any_int cap: int, allocator: runti
return
}
+/*
+Creates a buffered or unbuffered `Raw_Chan` for messages of the specified
+size and alignment.
+
+*Allocates Using Provided Allocator*
+
+**Inputs**
+- `msg_size`: The size of the messages the messages being sent
+- `msg_alignment`: The alignment of the messages being sent
+- [`cap`: The capacity of the channel] omit for creating unbuffered channels
+- `allocator`: The allocator to use
+
+**Returns**:
+- The initialized `Raw_Chan`
+- An `Allocator_Error`
+
+Example:
+
+ import "core:sync/chan"
+
+ create_raw_example :: proc() {
+ unbuffered: ^chan.Raw_Chan
+ buffered: ^chan.Raw_Chan
+ err: runtime.Allocator_Error
+
+ unbuffered, err = chan.create_raw(size_of(int), align_of(int), context.allocator)
+ assert(err == .None)
+ defer chan.destroy(unbuffered)
+
+ buffered, err = chan.create_raw(size_of(int), align_of(int), 10, context.allocator)
+ assert(err == .None)
+ defer chan.destroy(buffered)
+ }
+*/
create_raw :: proc{
create_raw_unbuffered,
create_raw_buffered,
}
+/*
+Creates an unbuffered `Raw_Chan` for messages of the specified
+size and alignment.
+
+*Allocates Using Provided Allocator*
+
+**Inputs**
+- `msg_size`: The size of the messages the messages being sent
+- `msg_alignment`: The alignment of the messages being sent
+- `allocator`: The allocator to use
+
+**Returns**:
+- The initialized `Raw_Chan`
+- An `Allocator_Error`
+
+Example:
+
+ import "core:sync/chan"
+
+ create_raw_unbuffered_example :: proc() {
+ unbuffered, err := chan.create_raw(size_of(int), align_of(int), context.allocator)
+ assert(err == .None)
+ defer chan.destroy(unbuffered)
+ }
+*/
@(require_results)
create_raw_unbuffered :: proc(#any_int msg_size, msg_alignment: int, allocator: runtime.Allocator) -> (c: ^Raw_Chan, err: runtime.Allocator_Error) {
assert(msg_size <= int(max(u16)))
@@ -80,6 +263,32 @@ create_raw_unbuffered :: proc(#any_int msg_size, msg_alignment: int, allocator:
return
}
+/*
+Creates a buffered `Raw_Chan` for messages of the specified
+size and alignment.
+
+*Allocates Using Provided Allocator*
+
+**Inputs**
+- `msg_size`: The size of the messages the messages being sent
+- `msg_alignment`: The alignment of the messages being sent
+- `cap`: The capacity of the channel
+- `allocator`: The allocator to use
+
+**Returns**:
+- The initialized `Raw_Chan`
+- An `Allocator_Error`
+
+Example:
+
+ import "core:sync/chan"
+
+ create_raw_unbuffered_example :: proc() {
+ c, err := chan.create_raw_buffered(size_of(int), align_of(int), 10, context.allocator)
+ assert(err == .None)
+ defer chan.destroy(c)
+ }
+*/
@(require_results)
create_raw_buffered :: proc(#any_int msg_size, msg_alignment: int, #any_int cap: int, allocator: runtime.Allocator) -> (c: ^Raw_Chan, err: runtime.Allocator_Error) {
assert(msg_size <= int(max(u16)))
@@ -110,6 +319,16 @@ create_raw_buffered :: proc(#any_int msg_size, msg_alignment: int, #any_int cap:
return
}
+
+/*
+Destroys the Channel.
+
+**Inputs**
+- `c`: The channel to destroy
+
+**Returns**:
+- An `Allocator_Error`
+*/
destroy :: proc(c: ^Raw_Chan) -> (err: runtime.Allocator_Error) {
if c != nil {
allocator := c.allocator
@@ -118,22 +337,142 @@ destroy :: proc(c: ^Raw_Chan) -> (err: runtime.Allocator_Error) {
return
}
+/*
+Creates a version of a channel that can only be used for sending
+not receiving.
+
+**Inputs**
+- `c`: The channel
+
+**Returns**:
+- An `Allocator_Error`
+
+Example:
+
+ import "core:sync/chan"
+
+ as_send_example :: proc() {
+ // this procedure takes a channel that can only
+ // be used for sending not receiving.
+ producer :: proc(c: chan.Chan(int, .Send)) {
+ chan.send(c, 112)
+
+ // compile-time error:
+ // value, ok := chan.recv(c)
+ }
+
+ c, err := chan.create(chan.Chan(int), 1, context.allocator)
+ assert(err == .None)
+ defer chan.destroy(c)
+
+ producer(chan.as_send(c))
+ }
+*/
@(require_results)
as_send :: #force_inline proc "contextless" (c: $C/Chan($T, $D)) -> (s: Chan(T, .Send)) where C.D <= .Both {
return transmute(type_of(s))c
}
+
+/*
+Creates a version of a channel that can only be used for receiving
+not sending.
+
+**Inputs**
+- `c`: The channel
+
+**Returns**:
+- An `Allocator_Error`
+
+Example:
+
+ import "core:sync/chan"
+
+ as_recv_example :: proc() {
+ consumer :: proc(c: chan.Chan(int, .Recv)) {
+ value, ok := chan.recv(c)
+
+ // compile-time error:
+ // chan.send(c, 22)
+ }
+
+ c, err := chan.create(chan.Chan(int), 1, context.allocator)
+ assert(err == .None)
+ defer chan.destroy(c)
+
+ chan.send(c, 112)
+ consumer(chan.as_recv(c))
+ }
+*/
@(require_results)
as_recv :: #force_inline proc "contextless" (c: $C/Chan($T, $D)) -> (r: Chan(T, .Recv)) where C.D >= .Both {
return transmute(type_of(r))c
}
+/*
+Sends the specified message, blocking the current thread if:
+- the channel is unbuffered
+- the channel's buffer is full
+until the channel is being read from. `send` will return
+`false` when attempting to send on an already closed channel.
+**Inputs**
+- `c`: The channel
+- `data`: The message to send
+
+**Returns**
+- `true` if the message was sent, `false` when the channel was already closed
+
+Example:
+
+ import "core:sync/chan"
+
+ send_example :: proc() {
+ c, err := chan.create(chan.Chan(int), 1, context.allocator)
+ assert(err == .None)
+ defer chan.destroy(c)
+
+ assert(chan.send(c, 2))
+
+ // this would block since the channel has a buffersize of 1
+ // assert(chan.send(c, 2))
+
+ // sending on a closed channel returns false
+ chan.close(c)
+ assert(! chan.send(c, 2))
+ }
+*/
send :: proc "contextless" (c: $C/Chan($T, $D), data: T) -> (ok: bool) where C.D <= .Both {
data := data
ok = send_raw(c, &data)
return
}
+/*
+Tries sending the specified message which is:
+- blocking: given the channel is unbuffered
+- non-blocking: given the channel is buffered
+
+**Inputs**
+- `c`: The channel
+- `data`: The message to send
+
+**Returns**
+- `true` if the message was sent, `false` when the channel was
+already closed or the channel's buffer was full
+
+Example:
+
+ import "core:sync/chan"
+
+ try_send_example :: proc() {
+ c, err := chan.create(chan.Chan(int), 1, context.allocator)
+ assert(err == .None)
+ defer chan.destroy(c)
+
+ assert(chan.try_send(c, 2), "there is enough space")
+ assert(!chan.try_send(c, 2), "the buffer is already full")
+ }
+*/
@(require_results)
try_send :: proc "contextless" (c: $C/Chan($T, $D), data: T) -> (ok: bool) where C.D <= .Both {
data := data
@@ -141,6 +480,43 @@ try_send :: proc "contextless" (c: $C/Chan($T, $D), data: T) -> (ok: bool) where
return
}
+/*
+Reads a message from the channel, blocking the current thread if:
+- the channel is unbuffered
+- the channel's buffer is empty
+until the channel is being written to. `recv` will return
+`false` when attempting to receive a message on an already closed channel.
+
+**Inputs**
+- `c`: The channel
+
+**Returns**
+- The message
+- `true` if a message was received, `false` when the channel was already closed
+
+Example:
+
+ import "core:sync/chan"
+
+ recv_example :: proc() {
+ c, err := chan.create(chan.Chan(int), 1, context.allocator)
+ assert(err == .None)
+ defer chan.destroy(c)
+
+ assert(chan.send(c, 2))
+
+ value, ok := chan.recv(c)
+ assert(ok, "the value was received")
+
+ // this would block since the channel is now empty
+ // value, ok = chan.recv(c)
+
+ // reading from a closed channel returns false
+ chan.close(c)
+ value, ok = chan.recv(c)
+ assert(!ok, "the channel is closed")
+ }
+*/
@(require_results)
recv :: proc "contextless" (c: $C/Chan($T)) -> (data: T, ok: bool) where C.D >= .Both {
ok = recv_raw(c, &data)
@@ -148,6 +524,29 @@ recv :: proc "contextless" (c: $C/Chan($T)) -> (data: T, ok: bool) where C.D >=
}
+/*
+Tries reading a message from the channel in a non-blocking fashion.
+
+**Inputs**
+- `c`: The channel
+
+**Returns**
+- The message
+- `true` if a message was received, `false` when the channel was already closed or no message was available
+
+Example:
+
+ import "core:sync/chan"
+
+ try_recv_example :: proc() {
+ c, err := chan.create(chan.Chan(int), context.allocator)
+ assert(err == .None)
+ defer chan.destroy(c)
+
+ _, ok := chan.try_recv(c)
+ assert(!ok, "there is not value to read")
+ }
+*/
@(require_results)
try_recv :: proc "contextless" (c: $C/Chan($T)) -> (data: T, ok: bool) where C.D >= .Both {
ok = try_recv_raw(c, &data)
@@ -155,6 +554,43 @@ try_recv :: proc "contextless" (c: $C/Chan($T)) -> (data: T, ok: bool) where C.D
}
+/*
+Sends the specified message, blocking the current thread if:
+- the channel is unbuffered
+- the channel's buffer is full
+until the channel is being read from. `send_raw` will return
+`false` when attempting to send on an already closed channel.
+
+Note: The message referenced by `msg_out` must match the size
+and alignment used when the `Raw_Chan` was created.
+
+**Inputs**
+- `c`: The channel
+- `msg_out`: Pointer to the data to send
+
+**Returns**
+- `true` if the message was sent, `false` when the channel was already closed
+
+Example:
+
+ import "core:sync/chan"
+
+ send_raw_example :: proc() {
+ c, err := chan.create_raw(size_of(int), align_of(int), 1, context.allocator)
+ assert(err == .None)
+ defer chan.destroy(c)
+
+ value := 2
+ assert(chan.send_raw(c, &value))
+
+ // this would block since the channel has a buffersize of 1
+ // assert(chan.send_raw(c, &value))
+
+ // sending on a closed channel returns false
+ chan.close(c)
+ assert(! chan.send_raw(c, &value))
+ }
+*/
@(require_results)
send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool) {
if c == nil {
@@ -194,6 +630,45 @@ send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool) {
return
}
+/*
+Reads a message from the channel, blocking the current thread if:
+- the channel is unbuffered
+- the channel's buffer is empty
+until the channel is being written to. `recv_raw` will return
+`false` when attempting to receive a message on an already closed channel.
+
+Note: The location pointed to by `msg_out` must match the size
+and alignment used when the `Raw_Chan` was created.
+
+**Inputs**
+- `c`: The channel
+- `msg_out`: Pointer to where the message should be stored
+
+**Returns**
+- `true` if a message was received, `false` when the channel was already closed
+
+Example:
+
+ import "core:sync/chan"
+
+ recv_raw_example :: proc() {
+ c, err := chan.create_raw(size_of(int), align_of(int), 1, context.allocator)
+ assert(err == .None)
+ defer chan.destroy(c)
+
+ value := 2
+ assert(chan.send_raw(c, &value))
+
+ assert(chan.recv_raw(c, &value))
+
+ // this would block since the channel is now empty
+ // assert(chan.recv_raw(c, &value))
+
+ // reading from a closed channel returns false
+ chan.close(c)
+ assert(! chan.recv_raw(c, &value))
+ }
+*/
@(require_results)
recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> (ok: bool) {
if c == nil {
@@ -244,6 +719,36 @@ recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> (ok: bool) {
}
+/*
+Tries sending the specified message which is:
+- blocking: given the channel is unbuffered
+- non-blocking: given the channel is buffered
+
+Note: The message referenced by `msg_out` must match the size
+and alignment used when the `Raw_Chan` was created.
+
+**Inputs**
+- `c`: the channel
+- `msg_out`: pointer to the data to send
+
+**Returns**
+- `true` if the message was sent, `false` when the channel was
+already closed or the channel's buffer was full
+
+Example:
+
+ import "core:sync/chan"
+
+ try_send_raw_example :: proc() {
+ c, err := chan.create_raw(size_of(int), align_of(int), 1, context.allocator)
+ assert(err == .None)
+ defer chan.destroy(c)
+
+ value := 2
+ assert(chan.try_send_raw(c, &value), "there is enough space")
+ assert(!chan.try_send_raw(c, &value), "the buffer is already full")
+ }
+*/
@(require_results)
try_send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool) {
if c == nil {
@@ -281,6 +786,32 @@ try_send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool)
return
}
+/*
+Reads a message from the channel if one is available.
+
+Note: The location pointed to by `msg_out` must match the size
+and alignment used when the `Raw_Chan` was created.
+
+**Inputs**
+- `c`: The channel
+- `msg_out`: Pointer to where the message should be stored
+
+**Returns**
+- `true` if a message was received, `false` when the channel was already closed or no message was available
+
+Example:
+
+ import "core:sync/chan"
+
+ try_recv_raw_example :: proc() {
+ c, err := chan.create_raw(size_of(int), align_of(int), context.allocator)
+ assert(err == .None)
+ defer chan.destroy(c)
+
+ value: int
+ assert(!chan.try_recv_raw(c, &value))
+ }
+*/
@(require_results)
try_recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> bool {
if c == nil {
@@ -319,16 +850,85 @@ try_recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> bool {
+/*
+Checks if the given channel is buffered.
+
+**Inputs**
+- `c`: The channel
+
+**Returns**:
+- `true` if the channel is buffered, `false` otherwise
+
+Example:
+
+ import "core:sync/chan"
+
+ is_buffered_example :: proc() {
+ c, _ := chan.create(chan.Chan(int), 1, context.allocator)
+ defer chan.destroy(c)
+ assert(chan.is_buffered(c))
+ }
+*/
@(require_results)
is_buffered :: proc "contextless" (c: ^Raw_Chan) -> bool {
return c != nil && c.queue != nil
}
+/*
+Checks if the given channel is unbuffered.
+
+**Inputs**
+- `c`: The channel
+
+**Returns**:
+- `true` if the channel is unbuffered, `false` otherwise
+
+Example:
+
+ import "core:sync/chan"
+
+ is_buffered_example :: proc() {
+ c, _ := chan.create(chan.Chan(int), context.allocator)
+ defer chan.destroy(c)
+ assert(chan.is_unbuffered(c))
+ }
+*/
@(require_results)
is_unbuffered :: proc "contextless" (c: ^Raw_Chan) -> bool {
return c != nil && c.unbuffered_data != nil
}
+/*
+Returns the number of elements currently in the channel.
+
+Note: Unbuffered channels will always return `0`
+because they cannot hold elements.
+
+**Inputs**
+- `c`: The channel
+
+**Returns**:
+- Number of elements
+
+Example:
+
+ import "core:sync/chan"
+ import "core:fmt"
+
+ len_example :: proc() {
+ c, _ := chan.create(chan.Chan(int), 2, context.allocator)
+ defer chan.destroy(c)
+
+ fmt.println(chan.len(c))
+ assert(chan.send(c, 1)) // add an element
+ fmt.println(chan.len(c))
+ }
+
+Output:
+
+ 0
+ 1
+*/
@(require_results)
len :: proc "contextless" (c: ^Raw_Chan) -> int {
if c != nil && c.queue != nil {
@@ -338,6 +938,34 @@ len :: proc "contextless" (c: ^Raw_Chan) -> int {
return 0
}
+/*
+Returns the number of elements the channel could hold.
+
+Note: Unbuffered channels will always return `0`
+because they cannot hold elements.
+
+**Inputs**
+- `c`: The channel
+
+**Returns**:
+- Number of elements
+
+Example:
+
+ import "core:sync/chan"
+ import "core:fmt"
+
+ cap_example :: proc() {
+ c, _ := chan.create(chan.Chan(int), 2, context.allocator)
+ defer chan.destroy(c)
+
+ fmt.println(chan.cap(c))
+ }
+
+Output:
+
+ 2
+*/
@(require_results)
cap :: proc "contextless" (c: ^Raw_Chan) -> int {
if c != nil && c.queue != nil {
@@ -347,6 +975,36 @@ cap :: proc "contextless" (c: ^Raw_Chan) -> int {
return 0
}
+/*
+Closes the channel, preventing new messages from being added.
+
+**Inputs**
+- `c`: The channel
+
+**Returns**:
+- `true` if the channel was closed by this operation, `false` if it was already closed
+
+Example:
+
+ import "core:sync/chan"
+
+ close_example :: proc() {
+ c, _ := chan.create(chan.Chan(int), 2, context.allocator)
+ defer chan.destroy(c)
+
+ // Sending a message to an open channel
+ assert(chan.send(c, 1), "allowed to send")
+
+ // Closing the channel successfully
+ assert(chan.close(c), "successfully closed")
+
+ // Trying to send a message after the channel is closed (should fail)
+ assert(!chan.send(c, 1), "not allowed to send after close")
+
+ // Trying to close the channel again (should fail since it's already closed)
+ assert(!chan.close(c), "was already closed")
+ }
+*/
close :: proc "contextless" (c: ^Raw_Chan) -> bool {
if c == nil {
return false
@@ -361,6 +1019,15 @@ close :: proc "contextless" (c: ^Raw_Chan) -> bool {
return true
}
+/*
+Returns if the channel is closed or not
+
+**Inputs**
+- `c`: The channel
+
+**Returns**:
+- `true` if the channel is closed, `false` otherwise
+*/
@(require_results)
is_closed :: proc "contextless" (c: ^Raw_Chan) -> bool {
if c == nil {
@@ -370,56 +1037,30 @@ is_closed :: proc "contextless" (c: ^Raw_Chan) -> bool {
return bool(c.closed)
}
+/*
+Returns whether a message is ready to be read, i.e.,
+if a call to `recv` or `recv_raw` would block
+**Inputs**
+- `c`: The channel
+**Returns**
+- `true` if a message can be read, `false` otherwise
-Raw_Queue :: struct {
- data: [^]byte,
- len: int,
- cap: int,
- next: int,
- size: int, // element size
-}
+Example:
-raw_queue_init :: proc "contextless" (q: ^Raw_Queue, data: rawptr, cap: int, size: int) {
- q.data = ([^]byte)(data)
- q.len = 0
- q.cap = cap
- q.next = 0
- q.size = size
-}
+ import "core:sync/chan"
+ can_recv_example :: proc() {
+ c, err := chan.create(chan.Chan(int), 1, context.allocator)
+ assert(err == .None)
+ defer chan.destroy(c)
-@(require_results)
-raw_queue_push :: proc "contextless" (q: ^Raw_Queue, data: rawptr) -> bool {
- if q.len == q.cap {
- return false
+ assert(!chan.can_recv(c), "the cannel is empty")
+ assert(chan.send(c, 2))
+ assert(chan.can_recv(c), "there is message to read")
}
- pos := q.next + q.len
- if pos >= q.cap {
- pos -= q.cap
- }
-
- val_ptr := q.data[pos*q.size:]
- mem.copy(val_ptr, data, q.size)
- q.len += 1
- return true
-}
-
-@(require_results)
-raw_queue_pop :: proc "contextless" (q: ^Raw_Queue) -> (data: rawptr) {
- if q.len > 0 {
- data = q.data[q.next*q.size:]
- q.next += 1
- q.len -= 1
- if q.next >= q.cap {
- q.next -= q.cap
- }
- }
- return
-}
-
-
+*/
@(require_results)
can_recv :: proc "contextless" (c: ^Raw_Chan) -> bool {
sync.guard(&c.mutex)
@@ -430,6 +1071,31 @@ can_recv :: proc "contextless" (c: ^Raw_Chan) -> bool {
}
+/*
+Returns whether a message can be sent without blocking the current
+thread. Specifically, it checks if the channel is buffered and not full,
+or if there is already a reader waiting for a message.
+
+**Inputs**
+- `c`: The channel
+
+**Returns**
+- `true` if a message can be send, `false` otherwise
+
+Example:
+
+ import "core:sync/chan"
+
+ can_send_example :: proc() {
+ c, err := chan.create(chan.Chan(int), 1, context.allocator)
+ assert(err == .None)
+ defer chan.destroy(c)
+
+ assert(chan.can_send(c), "the channel's buffer is not full")
+ assert(chan.send(c, 2))
+ assert(!chan.can_send(c), "the channel's buffer is full")
+ }
+*/
@(require_results)
can_send :: proc "contextless" (c: ^Raw_Chan) -> bool {
sync.guard(&c.mutex)
@@ -440,7 +1106,69 @@ can_send :: proc "contextless" (c: ^Raw_Chan) -> bool {
}
+/*
+Attempts to either send or receive messages on the specified channels.
+`select_raw` first identifies which channels have messages ready to be received
+and which are available for sending. It then randomly selects one operation
+(either a send or receive) to perform.
+
+Note: Each message in `send_msgs` corresponds to the send channel at the same index in `sends`.
+
+**Inputs**
+- `recv`: A slice of channels to read from
+- `sends`: A slice of channels to send messages on
+- `send_msgs`: A slice of messages to send
+- `recv_out`: A pointer to the location where, when receiving, the message should be stored
+
+**Returns**
+- Position of the available channel which was used for receiving or sending
+- `true` if sending/receiving was successfull, `false` if the channel was closed or no channel was available
+
+Example:
+
+ import "core:sync/chan"
+ import "core:fmt"
+
+ select_raw_example :: proc() {
+ c, err := chan.create(chan.Chan(int), 1, context.allocator)
+ assert(err == .None)
+ defer chan.destroy(c)
+
+ // sending value '1' on the channel
+ value1 := 1
+ msgs := [?]rawptr{&value1}
+ send_chans := [?]^chan.Raw_Chan{c}
+
+ // for simplicity the same channel used for sending is also used for receiving
+ receive_chans := [?]^chan.Raw_Chan{c}
+ // where the value from the read should be stored
+ received_value: int
+
+ idx, ok := chan.select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value)
+ fmt.println("SELECT: ", idx, ok)
+ fmt.println("RECEIVED VALUE ", received_value)
+
+ idx, ok = chan.select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value)
+ fmt.println("SELECT: ", idx, ok)
+ fmt.println("RECEIVED VALUE ", received_value)
+
+ // closing of a channel also affects the select operation
+ chan.close(c)
+
+ idx, ok = chan.select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value)
+ fmt.println("SELECT: ", idx, ok)
+ }
+
+Output:
+
+ SELECT: 0 true
+ RECEIVED VALUE 0
+ SELECT: 0 true
+ RECEIVED VALUE 1
+ SELECT: 0 false
+
+*/
@(require_results)
select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: []rawptr, recv_out: rawptr) -> (select_idx: int, ok: bool) #no_bounds_check {
Select_Op :: struct {
@@ -486,3 +1214,137 @@ select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: []
}
return
}
+
+
+/*
+`Raw_Queue` is a non-thread-safe queue implementation designed to store messages
+of fixed size and alignment.
+
+Note: For most use cases, it is recommended to use `core:container/queue` instead,
+as `Raw_Queue` is used internally by `Raw_Chan` and may not provide the desired
+level of convenience for typical applications.
+*/
+@(private)
+Raw_Queue :: struct {
+ data: [^]byte,
+ len: int,
+ cap: int,
+ next: int,
+ size: int, // element size
+}
+
+/*
+Initializes a `Raw_Queue`
+
+**Inputs**
+- `q`: A pointert to the `Raw_Queue` to initialize
+- `data`: The pointer to backing slice storing the messages
+- `cap`: The capacity of the queue
+- `size`: The size of a message
+
+Example:
+
+ import "core:sync/chan"
+
+ raw_queue_init_example :: proc() {
+ // use a stack allocated array as backing storage
+ storage: [100]int
+
+ rq: chan.Raw_Queue
+ chan.raw_queue_init(&rq, &storage, cap(storage), size_of(int))
+ }
+*/
+@(private)
+raw_queue_init :: proc "contextless" (q: ^Raw_Queue, data: rawptr, cap: int, size: int) {
+ q.data = ([^]byte)(data)
+ q.len = 0
+ q.cap = cap
+ q.next = 0
+ q.size = size
+}
+
+/*
+Add an element to the queue.
+
+Note: The message referenced by `data` must match the size
+and alignment used when the `Raw_Queue` was initialized.
+
+**Inputs**
+- `q`: A pointert to the `Raw_Queue`
+- `data`: The pointer to message to add
+
+**Returns**
+- `true` if the element was added, `false` when the queue is already full
+
+Example:
+
+ import "core:sync/chan"
+
+ raw_queue_push_example :: proc() {
+ storage: [100]int
+ rq: chan.Raw_Queue
+ chan.raw_queue_init(&rq, &storage, cap(storage), size_of(int))
+
+ value := 2
+ assert(chan.raw_queue_push(&rq, &value), "there was enough space")
+ }
+*/
+@(private, require_results)
+raw_queue_push :: proc "contextless" (q: ^Raw_Queue, data: rawptr) -> bool {
+ if q.len == q.cap {
+ return false
+ }
+ pos := q.next + q.len
+ if pos >= q.cap {
+ pos -= q.cap
+ }
+
+ val_ptr := q.data[pos*q.size:]
+ mem.copy(val_ptr, data, q.size)
+ q.len += 1
+ return true
+}
+
+/*
+Removes and returns the first element of the queue.
+
+Note: The returned element is only guaranteed to be valid until the next
+`raw_queue_push` operation. Accessing it after that point may result in
+undefined behavior.
+
+**Inputs**
+- `c`: A pointer to the `Raw_Queue`.
+
+**Returns**
+- A pointer to the first element in the queue, or `nil` if the queue is empty.
+
+Example:
+
+ import "core:sync/chan"
+
+ raw_queue_pop_example :: proc() {
+ storage: [100]int
+ rq: chan.Raw_Queue
+ chan.raw_queue_init(&rq, &storage, cap(storage), size_of(int))
+
+ assert(chan.raw_queue_pop(&rq) == nil, "queue was empty")
+
+ // add an element to the queue
+ value := 2
+ assert(chan.raw_queue_push(&rq, &value), "there was enough space")
+
+ assert((cast(^int)chan.raw_queue_pop(&rq))^ == 2, "retrieved the element")
+ }
+*/
+@(private, require_results)
+raw_queue_pop :: proc "contextless" (q: ^Raw_Queue) -> (data: rawptr) {
+ if q.len > 0 {
+ data = q.data[q.next*q.size:]
+ q.next += 1
+ q.len -= 1
+ if q.next >= q.cap {
+ q.next -= q.cap
+ }
+ }
+ return
+}
diff --git a/core/sync/chan/doc.odin b/core/sync/chan/doc.odin
new file mode 100644
index 000000000..5d65d7410
--- /dev/null
+++ b/core/sync/chan/doc.odin
@@ -0,0 +1,69 @@
+/*
+This package provides both high-level and low-level channel types
+for thread-safe communication.
+
+While channels are essentially thread-safe queues under the hood,
+their primary purpose is to facilitate safe communication between
+multiple readers and multiple writers.
+Although they can be used like queues, channels are designed with
+synchronization and concurrent messaging patterns in mind.
+
+Provided types:
+- `Chan` a high level channel
+- `Raw_Chan` a low level channel
+- `Raw_Queue` a low level non-threadsafe queue implementation used internally
+
+Example:
+
+ import "core:sync/chan"
+ import "core:fmt"
+ import "core:thread"
+
+ // The consumer reads from the channel until it's closed.
+ // Closing the channel acts as a signal to stop.
+ consumer :: proc(recv_chan: chan.Chan(int, .Recv)) {
+ for {
+ value, ok := chan.recv(recv_chan)
+ if !ok {
+ break // More idiomatic than return here
+ }
+ fmt.println("[CONSUMER] Received:", value)
+ }
+ fmt.println("[CONSUMER] Channel closed, stopping.")
+ }
+
+ // The producer sends `count` number of messages.
+ producer :: proc(send_chan: chan.Chan(int, .Send), count: int) {
+ for i in 0.. ^Menu {
+Application_mainMenu :: proc "c" (self: ^Application) -> ^Menu {
return msgSend(^Menu, self, "mainMenu")
}
diff --git a/core/sys/darwin/Foundation/NSArray.odin b/core/sys/darwin/Foundation/NSArray.odin
index b238f63f8..0977c6469 100644
--- a/core/sys/darwin/Foundation/NSArray.odin
+++ b/core/sys/darwin/Foundation/NSArray.odin
@@ -40,3 +40,49 @@ Array_objectAs :: proc "c" (self: ^Array, index: UInteger, $T: typeid) -> T wher
Array_count :: proc "c" (self: ^Array) -> UInteger {
return msgSend(UInteger, self, "count")
}
+
+
+@(objc_class="NSMutableArray")
+MutableArray :: struct {
+ using _: Copying(MutableArray),
+}
+
+@(objc_type=MutableArray, objc_name="alloc", objc_is_class_method=true)
+MutableArray_alloc :: proc "c" () -> ^MutableArray {
+ return msgSend(^MutableArray, MutableArray, "alloc")
+}
+
+@(objc_type=MutableArray, objc_name="init")
+MutableArray_init :: proc "c" (self: ^MutableArray) -> ^MutableArray {
+ return msgSend(^MutableArray, self, "init")
+}
+
+@(objc_type=MutableArray, objc_name="initWithObjects")
+MutableArray_initWithObjects :: proc "c" (self: ^MutableArray, objects: [^]^Object, count: UInteger) -> ^MutableArray {
+ return msgSend(^MutableArray, self, "initWithObjects:count:", objects, count)
+}
+
+@(objc_type=MutableArray, objc_name="initWithCoder")
+MutableArray_initWithCoder :: proc "c" (self: ^MutableArray, coder: ^Coder) -> ^MutableArray {
+ return msgSend(^MutableArray, self, "initWithCoder:", coder)
+}
+
+@(objc_type=MutableArray, objc_name="object")
+MutableArray_object :: proc "c" (self: ^MutableArray, index: UInteger) -> ^Object {
+ return msgSend(^Object, self, "objectAtIndex:", index)
+}
+@(objc_type=MutableArray, objc_name="objectAs")
+MutableArray_objectAs :: proc "c" (self: ^MutableArray, index: UInteger, $T: typeid) -> T where intrinsics.type_is_pointer(T), intrinsics.type_is_subtype_of(T, ^Object) {
+ return (T)(MutableArray_object(self, index))
+}
+
+@(objc_type=MutableArray, objc_name="count")
+MutableArray_count :: proc "c" (self: ^MutableArray) -> UInteger {
+ return msgSend(UInteger, self, "count")
+}
+
+
+@(objc_type=MutableArray, objc_name="exchangeObjectAtIndex")
+MutableArray_exchangeObjectAtIndex :: proc "c" (self: ^MutableArray, idx1, idx2: UInteger) {
+ msgSend(nil, self, "exchangeObjectAtIndex:withObjectAtIndex:", idx1, idx2)
+}
diff --git a/core/sys/darwin/Foundation/NSDictionary.odin b/core/sys/darwin/Foundation/NSDictionary.odin
index 8af58cf62..ed98f3168 100644
--- a/core/sys/darwin/Foundation/NSDictionary.odin
+++ b/core/sys/darwin/Foundation/NSDictionary.odin
@@ -15,7 +15,7 @@ Dictionary_dictionaryWithObject :: proc "c" (object: ^Object, forKey: ^Object) -
@(objc_type=Dictionary, objc_name="dictionaryWithObjects", objc_is_class_method=true)
Dictionary_dictionaryWithObjects :: proc "c" (objects: [^]^Object, forKeys: [^]^Object, count: UInteger) -> ^Dictionary {
- return msgSend(^Dictionary, Dictionary, "dictionaryWithObjects:forKeys:count", objects, forKeys, count)
+ return msgSend(^Dictionary, Dictionary, "dictionaryWithObjects:forKeys:count:", objects, forKeys, count)
}
@@ -31,7 +31,7 @@ Dictionary_init :: proc "c" (self: ^Dictionary) -> ^Dictionary {
@(objc_type=Dictionary, objc_name="initWithObjects")
Dictionary_initWithObjects :: proc "c" (self: ^Dictionary, objects: [^]^Object, forKeys: [^]^Object, count: UInteger) -> ^Dictionary {
- return msgSend(^Dictionary, self, "initWithObjects:forKeys:count", objects, forKeys, count)
+ return msgSend(^Dictionary, self, "initWithObjects:forKeys:count:", objects, forKeys, count)
}
@(objc_type=Dictionary, objc_name="objectForKey")
diff --git a/core/sys/darwin/Foundation/NSMenu.odin b/core/sys/darwin/Foundation/NSMenu.odin
index 9a74151b0..747920ab7 100644
--- a/core/sys/darwin/Foundation/NSMenu.odin
+++ b/core/sys/darwin/Foundation/NSMenu.odin
@@ -2,127 +2,562 @@ package objc_Foundation
import "base:builtin"
import "base:intrinsics"
-
-KeyEquivalentModifierFlag :: enum UInteger {
- CapsLock = 16, // Set if Caps Lock key is pressed.
- Shift = 17, // Set if Shift key is pressed.
- Control = 18, // Set if Control key is pressed.
- Option = 19, // Set if Option or Alternate key is pressed.
- Command = 20, // Set if Command key is pressed.
- NumericPad = 21, // Set if any key in the numeric keypad is pressed.
- Help = 22, // Set if the Help key is pressed.
- Function = 23, // Set if any function key is pressed.
-}
-KeyEquivalentModifierMask :: distinct bit_set[KeyEquivalentModifierFlag; UInteger]
-
-// Used to retrieve only the device-independent modifier flags, allowing applications to mask off the device-dependent modifier flags, including event coalescing information.
-KeyEventModifierFlagDeviceIndependentFlagsMask := transmute(KeyEquivalentModifierMask)_KeyEventModifierFlagDeviceIndependentFlagsMask
-@(private) _KeyEventModifierFlagDeviceIndependentFlagsMask := UInteger(0xffff0000)
+import "core:c"
-MenuItemCallback :: proc "c" (unused: rawptr, name: SEL, sender: ^Object)
-
-
-@(objc_class="NSMenuItem")
-MenuItem :: struct {using _: Object}
-
-@(objc_type=MenuItem, objc_name="alloc", objc_is_class_method=true)
-MenuItem_alloc :: proc "c" () -> ^MenuItem {
- return msgSend(^MenuItem, MenuItem, "alloc")
+MenuSelectionMode :: enum c.long {
+ Automatic = 0,
+ SelectOne = 1,
+ SelectAny = 2,
}
-@(objc_type=MenuItem, objc_name="registerActionCallback", objc_is_class_method=true)
-MenuItem_registerActionCallback :: proc "c" (name: cstring, callback: MenuItemCallback) -> SEL {
- s := string(name)
- n := len(s)
- sel: SEL
- if n > 0 && s[n-1] != ':' {
- col_name := intrinsics.alloca(n+2, 1)
- builtin.copy(col_name[:n], s)
- col_name[n] = ':'
- col_name[n+1] = 0
- sel = sel_registerName(cstring(col_name))
- } else {
- sel = sel_registerName(name)
- }
- if callback != nil {
- class_addMethod(intrinsics.objc_find_class("NSObject"), sel, auto_cast callback, "v@:@")
- }
- return sel
+MenuPresentationStyle :: enum c.long {
+ Regular = 0,
+ Palette = 1,
}
-@(objc_type=MenuItem, objc_name="separatorItem", objc_is_class_method=true)
-MenuItem_separatorItem :: proc "c" () -> ^MenuItem {
- return msgSend(^MenuItem, MenuItem, "separatorItem")
+UserInterfaceLayoutDirection :: enum c.long {
+ LeftToRight = 0,
+ RightToLeft = 1,
}
-@(objc_type=MenuItem, objc_name="init")
-MenuItem_init :: proc "c" (self: ^MenuItem) -> ^MenuItem {
- return msgSend(^MenuItem, self, "init")
+MenuPropertyItem :: enum c.ulong {
+ Title = 0,
+ AttributedTitle = 1,
+ KeyEquivalent = 2,
+ Image = 3,
+ Enabled = 4,
+ AccessibilityDescription = 5,
}
-
-@(objc_type=MenuItem, objc_name="initWithTitle")
-MenuItem_initWithTitle :: proc "c" (self: ^MenuItem, title: ^String, action: SEL, keyEquivalent: ^String) -> ^MenuItem {
- return msgSend(^MenuItem, self, "initWithTitle:action:keyEquivalent:", title, action, keyEquivalent)
-}
-
-@(objc_type=MenuItem, objc_name="setKeyEquivalentModifierMask")
-MenuItem_setKeyEquivalentModifierMask :: proc "c" (self: ^MenuItem, modifierMask: KeyEquivalentModifierMask) {
- msgSend(nil, self, "setKeyEquivalentModifierMask:", modifierMask)
-}
-
-@(objc_type=MenuItem, objc_name="keyEquivalentModifierMask")
-MenuItem_keyEquivalentModifierMask :: proc "c" (self: ^MenuItem) -> KeyEquivalentModifierMask {
- return msgSend(KeyEquivalentModifierMask, self, "keyEquivalentModifierMask")
-}
-
-@(objc_type=MenuItem, objc_name="setSubmenu")
-MenuItem_setSubmenu :: proc "c" (self: ^MenuItem, submenu: ^Menu) {
- msgSend(nil, self, "setSubmenu:", submenu)
-}
-
-@(objc_type=MenuItem, objc_name="title")
-MenuItem_title :: proc "c" (self: ^MenuItem) -> ^String {
- return msgSend(^String, self, "title")
-}
-
-@(objc_type=MenuItem, objc_name="setTitle")
-MenuItem_setTitle :: proc "c" (self: ^MenuItem, title: ^String) -> ^String {
- return msgSend(^String, self, "title:", title)
-}
-
+MenuProperties :: distinct bit_set[MenuPropertyItem; c.ulong]
@(objc_class="NSMenu")
Menu :: struct {using _: Object}
-@(objc_type=Menu, objc_name="alloc", objc_is_class_method=true)
-Menu_alloc :: proc "c" () -> ^Menu {
- return msgSend(^Menu, Menu, "alloc")
-}
-
@(objc_type=Menu, objc_name="init")
Menu_init :: proc "c" (self: ^Menu) -> ^Menu {
return msgSend(^Menu, self, "init")
}
+
@(objc_type=Menu, objc_name="initWithTitle")
-Menu_initWithTitle :: proc "c" (self: ^Menu, title: ^String) -> ^Menu {
+Menu_initWithTitle :: #force_inline proc "c" (self: ^Menu, title: ^String) -> ^Menu {
return msgSend(^Menu, self, "initWithTitle:", title)
}
-
-
+@(objc_type=Menu, objc_name="initWithCoder")
+Menu_initWithCoder :: #force_inline proc "c" (self: ^Menu, coder: ^Coder) -> ^Menu {
+ return msgSend(^Menu, self, "initWithCoder:", coder)
+}
+@(objc_type=Menu, objc_name="popUpContextMenu_withEvent_forView", objc_is_class_method=true)
+Menu_popUpContextMenu_withEvent_forView :: #force_inline proc "c" (menu: ^Menu, event: ^Event, view: ^View) {
+ msgSend(nil, Menu, "popUpContextMenu:withEvent:forView:", menu, event, view)
+}
+// @(objc_type=Menu, objc_name="popUpContextMenu_withEvent_forView_withFont", objc_is_class_method=true)
+// Menu_popUpContextMenu_withEvent_forView_withFont :: #force_inline proc "c" (menu: ^Menu, event: ^Event, view: ^View, font: ^Font) {
+// msgSend(nil, Menu, "popUpContextMenu:withEvent:forView:withFont:", menu, event, view, font)
+// }
+@(objc_type=Menu, objc_name="popUpMenuPositioningItem")
+Menu_popUpMenuPositioningItem :: #force_inline proc "c" (self: ^Menu, item: ^MenuItem, location: Point, view: ^View) -> bool {
+ return msgSend(bool, self, "popUpMenuPositioningItem:atLocation:inView:", item, location, view)
+}
+@(objc_type=Menu, objc_name="setMenuBarVisible", objc_is_class_method=true)
+Menu_setMenuBarVisible :: #force_inline proc "c" (visible: bool) {
+ msgSend(nil, Menu, "setMenuBarVisible:", visible)
+}
+@(objc_type=Menu, objc_name="menuBarVisible", objc_is_class_method=true)
+Menu_menuBarVisible :: #force_inline proc "c" () -> bool {
+ return msgSend(bool, Menu, "menuBarVisible")
+}
+@(objc_type=Menu, objc_name="insertItem")
+Menu_insertItem :: #force_inline proc "c" (self: ^Menu, newItem: ^MenuItem, index: Integer) {
+ msgSend(nil, self, "insertItem:atIndex:", newItem, index)
+}
@(objc_type=Menu, objc_name="addItem")
-Menu_addItem :: proc "c" (self: ^Menu, item: ^MenuItem) {
- msgSend(nil, self, "addItem:", item)
+Menu_addItem :: #force_inline proc "c" (self: ^Menu, newItem: ^MenuItem) {
+ msgSend(nil, self, "addItem:", newItem)
+}
+@(objc_type=Menu, objc_name="insertItemWithTitle")
+Menu_insertItemWithTitle :: #force_inline proc "c" (self: ^Menu, string: ^String, selector: SEL, charCode: ^String, index: Integer) -> ^MenuItem {
+ return msgSend(^MenuItem, self, "insertItemWithTitle:action:keyEquivalent:atIndex:", string, selector, charCode, index)
}
-
@(objc_type=Menu, objc_name="addItemWithTitle")
-Menu_addItemWithTitle :: proc "c" (self: ^Menu, title: ^String, selector: SEL, keyEquivalent: ^String) -> ^MenuItem {
- return msgSend(^MenuItem, self, "addItemWithTitle:action:keyEquivalent:", title, selector, keyEquivalent)
+Menu_addItemWithTitle :: #force_inline proc "c" (self: ^Menu, string: ^String, selector: SEL, charCode: ^String) -> ^MenuItem {
+ return msgSend(^MenuItem, self, "addItemWithTitle:action:keyEquivalent:", string, selector, charCode)
+}
+@(objc_type=Menu, objc_name="removeItemAtIndex")
+Menu_removeItemAtIndex :: #force_inline proc "c" (self: ^Menu, index: Integer) {
+ msgSend(nil, self, "removeItemAtIndex:", index)
+}
+@(objc_type=Menu, objc_name="removeItem")
+Menu_removeItem :: #force_inline proc "c" (self: ^Menu, item: ^MenuItem) {
+ msgSend(nil, self, "removeItem:", item)
+}
+@(objc_type=Menu, objc_name="setSubmenu")
+Menu_setSubmenu :: #force_inline proc "c" (self: ^Menu, menu: ^Menu, item: ^MenuItem) {
+ msgSend(nil, self, "setSubmenu:forItem:", menu, item)
+}
+@(objc_type=Menu, objc_name="removeAllItems")
+Menu_removeAllItems :: #force_inline proc "c" (self: ^Menu) {
+ msgSend(nil, self, "removeAllItems")
+}
+@(objc_type=Menu, objc_name="itemAtIndex")
+Menu_itemAtIndex :: #force_inline proc "c" (self: ^Menu, index: Integer) -> ^MenuItem {
+ return msgSend(^MenuItem, self, "itemAtIndex:", index)
+}
+@(objc_type=Menu, objc_name="indexOfItem")
+Menu_indexOfItem :: #force_inline proc "c" (self: ^Menu, item: ^MenuItem) -> Integer {
+ return msgSend(Integer, self, "indexOfItem:", item)
+}
+@(objc_type=Menu, objc_name="indexOfItemWithTitle")
+Menu_indexOfItemWithTitle :: #force_inline proc "c" (self: ^Menu, title: ^String) -> Integer {
+ return msgSend(Integer, self, "indexOfItemWithTitle:", title)
+}
+@(objc_type=Menu, objc_name="indexOfItemWithTag")
+Menu_indexOfItemWithTag :: #force_inline proc "c" (self: ^Menu, tag: Integer) -> Integer {
+ return msgSend(Integer, self, "indexOfItemWithTag:", tag)
+}
+@(objc_type=Menu, objc_name="indexOfItemWithRepresentedObject")
+Menu_indexOfItemWithRepresentedObject :: #force_inline proc "c" (self: ^Menu, object: id) -> Integer {
+ return msgSend(Integer, self, "indexOfItemWithRepresentedObject:", object)
+}
+@(objc_type=Menu, objc_name="indexOfItemWithSubmenu")
+Menu_indexOfItemWithSubmenu :: #force_inline proc "c" (self: ^Menu, submenu: ^Menu) -> Integer {
+ return msgSend(Integer, self, "indexOfItemWithSubmenu:", submenu)
+}
+@(objc_type=Menu, objc_name="indexOfItemWithTarget")
+Menu_indexOfItemWithTarget :: #force_inline proc "c" (self: ^Menu, target: id, actionSelector: SEL) -> Integer {
+ return msgSend(Integer, self, "indexOfItemWithTarget:andAction:", target, actionSelector)
+}
+@(objc_type=Menu, objc_name="itemWithTitle")
+Menu_itemWithTitle :: #force_inline proc "c" (self: ^Menu, title: ^String) -> ^MenuItem {
+ return msgSend(^MenuItem, self, "itemWithTitle:", title)
+}
+@(objc_type=Menu, objc_name="itemWithTag")
+Menu_itemWithTag :: #force_inline proc "c" (self: ^Menu, tag: Integer) -> ^MenuItem {
+ return msgSend(^MenuItem, self, "itemWithTag:", tag)
+}
+@(objc_type=Menu, objc_name="update")
+Menu_update :: #force_inline proc "c" (self: ^Menu) {
+ msgSend(nil, self, "update")
+}
+@(objc_type=Menu, objc_name="performKeyEquivalent")
+Menu_performKeyEquivalent :: #force_inline proc "c" (self: ^Menu, event: ^Event) -> bool {
+ return msgSend(bool, self, "performKeyEquivalent:", event)
+}
+@(objc_type=Menu, objc_name="itemChanged")
+Menu_itemChanged :: #force_inline proc "c" (self: ^Menu, item: ^MenuItem) {
+ msgSend(nil, self, "itemChanged:", item)
+}
+@(objc_type=Menu, objc_name="performActionForItemAtIndex")
+Menu_performActionForItemAtIndex :: #force_inline proc "c" (self: ^Menu, index: Integer) {
+ msgSend(nil, self, "performActionForItemAtIndex:", index)
+}
+@(objc_type=Menu, objc_name="cancelTracking")
+Menu_cancelTracking :: #force_inline proc "c" (self: ^Menu) {
+ msgSend(nil, self, "cancelTracking")
+}
+@(objc_type=Menu, objc_name="cancelTrackingWithoutAnimation")
+Menu_cancelTrackingWithoutAnimation :: #force_inline proc "c" (self: ^Menu) {
+ msgSend(nil, self, "cancelTrackingWithoutAnimation")
+}
+@(objc_type=Menu, objc_name="title")
+Menu_title :: #force_inline proc "c" (self: ^Menu) -> ^String {
+ return msgSend(^String, self, "title")
+}
+@(objc_type=Menu, objc_name="setTitle")
+Menu_setTitle :: #force_inline proc "c" (self: ^Menu, title: ^String) {
+ msgSend(nil, self, "setTitle:", title)
+}
+@(objc_type=Menu, objc_name="supermenu")
+Menu_supermenu :: #force_inline proc "c" (self: ^Menu) -> ^Menu {
+ return msgSend(^Menu, self, "supermenu")
+}
+@(objc_type=Menu, objc_name="setSupermenu")
+Menu_setSupermenu :: #force_inline proc "c" (self: ^Menu, supermenu: ^Menu) {
+ msgSend(nil, self, "setSupermenu:", supermenu)
+}
+@(objc_type=Menu, objc_name="itemArray")
+Menu_itemArray :: #force_inline proc "c" (self: ^Menu) -> ^Array {
+ return msgSend(^Array, self, "itemArray")
+}
+@(objc_type=Menu, objc_name="setItemArray")
+Menu_setItemArray :: #force_inline proc "c" (self: ^Menu, itemArray: ^Array) {
+ msgSend(nil, self, "setItemArray:", itemArray)
+}
+@(objc_type=Menu, objc_name="numberOfItems")
+Menu_numberOfItems :: #force_inline proc "c" (self: ^Menu) -> Integer {
+ return msgSend(Integer, self, "numberOfItems")
+}
+@(objc_type=Menu, objc_name="autoenablesItems")
+Menu_autoenablesItems :: #force_inline proc "c" (self: ^Menu) -> bool {
+ return msgSend(bool, self, "autoenablesItems")
+}
+@(objc_type=Menu, objc_name="setAutoenablesItems")
+Menu_setAutoenablesItems :: #force_inline proc "c" (self: ^Menu, autoenablesItems: bool) {
+ msgSend(nil, self, "setAutoenablesItems:", autoenablesItems)
+}
+@(objc_type=Menu, objc_name="delegate")
+Menu_delegate :: #force_inline proc "c" (self: ^Menu) -> ^MenuDelegate {
+ return msgSend(^MenuDelegate, self, "delegate")
+}
+@(objc_type=Menu, objc_name="setDelegate")
+Menu_setDelegate :: #force_inline proc "c" (self: ^Menu, delegate: ^MenuDelegate) {
+ msgSend(nil, self, "setDelegate:", delegate)
+}
+@(objc_type=Menu, objc_name="menuBarHeight")
+Menu_menuBarHeight :: #force_inline proc "c" (self: ^Menu) -> Float {
+ return msgSend(Float, self, "menuBarHeight")
+}
+@(objc_type=Menu, objc_name="highlightedItem")
+Menu_highlightedItem :: #force_inline proc "c" (self: ^Menu) -> ^MenuItem {
+ return msgSend(^MenuItem, self, "highlightedItem")
+}
+@(objc_type=Menu, objc_name="minimumWidth")
+Menu_minimumWidth :: #force_inline proc "c" (self: ^Menu) -> Float {
+ return msgSend(Float, self, "minimumWidth")
+}
+@(objc_type=Menu, objc_name="setMinimumWidth")
+Menu_setMinimumWidth :: #force_inline proc "c" (self: ^Menu, minimumWidth: Float) {
+ msgSend(nil, self, "setMinimumWidth:", minimumWidth)
+}
+@(objc_type=Menu, objc_name="size")
+Menu_size :: #force_inline proc "c" (self: ^Menu) -> Size {
+ return msgSend(Size, self, "size")
+}
+// @(objc_type=Menu, objc_name="font")
+// Menu_font :: #force_inline proc "c" (self: ^Menu) -> ^Font {
+// return msgSend(^Font, self, "font")
+// }
+// @(objc_type=Menu, objc_name="setFont")
+// Menu_setFont :: #force_inline proc "c" (self: ^Menu, font: ^Font) {
+// msgSend(nil, self, "setFont:", font)
+// }
+@(objc_type=Menu, objc_name="allowsContextMenuPlugIns")
+Menu_allowsContextMenuPlugIns :: #force_inline proc "c" (self: ^Menu) -> bool {
+ return msgSend(bool, self, "allowsContextMenuPlugIns")
+}
+@(objc_type=Menu, objc_name="setAllowsContextMenuPlugIns")
+Menu_setAllowsContextMenuPlugIns :: #force_inline proc "c" (self: ^Menu, allowsContextMenuPlugIns: bool) {
+ msgSend(nil, self, "setAllowsContextMenuPlugIns:", allowsContextMenuPlugIns)
+}
+@(objc_type=Menu, objc_name="showsStateColumn")
+Menu_showsStateColumn :: #force_inline proc "c" (self: ^Menu) -> bool {
+ return msgSend(bool, self, "showsStateColumn")
+}
+@(objc_type=Menu, objc_name="setShowsStateColumn")
+Menu_setShowsStateColumn :: #force_inline proc "c" (self: ^Menu, showsStateColumn: bool) {
+ msgSend(nil, self, "setShowsStateColumn:", showsStateColumn)
+}
+@(objc_type=Menu, objc_name="userInterfaceLayoutDirection")
+Menu_userInterfaceLayoutDirection :: #force_inline proc "c" (self: ^Menu) -> UserInterfaceLayoutDirection {
+ return msgSend(UserInterfaceLayoutDirection, self, "userInterfaceLayoutDirection")
+}
+@(objc_type=Menu, objc_name="setUserInterfaceLayoutDirection")
+Menu_setUserInterfaceLayoutDirection :: #force_inline proc "c" (self: ^Menu, userInterfaceLayoutDirection: UserInterfaceLayoutDirection) {
+ msgSend(nil, self, "setUserInterfaceLayoutDirection:", userInterfaceLayoutDirection)
+}
+@(objc_type=Menu, objc_name="paletteMenuWithColors_titles_selectionHandler", objc_is_class_method=true)
+Menu_paletteMenuWithColors_titles_selectionHandler :: #force_inline proc "c" (colors: ^Array, itemTitles: ^Array, onSelectionChange: proc "c" (_arg_0: ^Menu)) -> ^Menu {
+ return msgSend(^Menu, Menu, "paletteMenuWithColors:titles:selectionHandler:", colors, itemTitles, onSelectionChange)
+}
+// @(objc_type=Menu, objc_name="paletteMenuWithColors_titles_templateImage_selectionHandler", objc_is_class_method=true)
+// Menu_paletteMenuWithColors_titles_templateImage_selectionHandler :: #force_inline proc "c" (colors: ^Array, itemTitles: ^Array, image: ^Image, onSelectionChange: proc "c" (_arg_0: ^Menu)) -> ^Menu {
+// return msgSend(^Menu, Menu, "paletteMenuWithColors:titles:templateImage:selectionHandler:", colors, itemTitles, image, onSelectionChange)
+// }
+@(objc_type=Menu, objc_name="presentationStyle")
+Menu_presentationStyle :: #force_inline proc "c" (self: ^Menu) -> MenuPresentationStyle {
+ return msgSend(MenuPresentationStyle, self, "presentationStyle")
+}
+@(objc_type=Menu, objc_name="setPresentationStyle")
+Menu_setPresentationStyle :: #force_inline proc "c" (self: ^Menu, presentationStyle: MenuPresentationStyle) {
+ msgSend(nil, self, "setPresentationStyle:", presentationStyle)
+}
+@(objc_type=Menu, objc_name="selectionMode")
+Menu_selectionMode :: #force_inline proc "c" (self: ^Menu) -> MenuSelectionMode {
+ return msgSend(MenuSelectionMode, self, "selectionMode")
+}
+@(objc_type=Menu, objc_name="setSelectionMode")
+Menu_setSelectionMode :: #force_inline proc "c" (self: ^Menu, selectionMode: MenuSelectionMode) {
+ msgSend(nil, self, "setSelectionMode:", selectionMode)
+}
+@(objc_type=Menu, objc_name="selectedItems")
+Menu_selectedItems :: #force_inline proc "c" (self: ^Menu) -> ^Array {
+ return msgSend(^Array, self, "selectedItems")
+}
+@(objc_type=Menu, objc_name="setSelectedItems")
+Menu_setSelectedItems :: #force_inline proc "c" (self: ^Menu, selectedItems: ^Array) {
+ msgSend(nil, self, "setSelectedItems:", selectedItems)
+}
+@(objc_type=Menu, objc_name="submenuAction")
+Menu_submenuAction :: #force_inline proc "c" (self: ^Menu, sender: id) {
+ msgSend(nil, self, "submenuAction:", sender)
+}
+@(objc_type=Menu, objc_name="propertiesToUpdate")
+Menu_propertiesToUpdate :: #force_inline proc "c" (self: ^Menu) -> MenuProperties {
+ return msgSend(MenuProperties, self, "propertiesToUpdate")
+}
+@(objc_type=Menu, objc_name="setMenuRepresentation")
+Menu_setMenuRepresentation :: #force_inline proc "c" (self: ^Menu, menuRep: id) {
+ msgSend(nil, self, "setMenuRepresentation:", menuRep)
+}
+@(objc_type=Menu, objc_name="menuRepresentation")
+Menu_menuRepresentation :: #force_inline proc "c" (self: ^Menu) -> id {
+ return msgSend(id, self, "menuRepresentation")
+}
+@(objc_type=Menu, objc_name="setContextMenuRepresentation")
+Menu_setContextMenuRepresentation :: #force_inline proc "c" (self: ^Menu, menuRep: id) {
+ msgSend(nil, self, "setContextMenuRepresentation:", menuRep)
+}
+@(objc_type=Menu, objc_name="contextMenuRepresentation")
+Menu_contextMenuRepresentation :: #force_inline proc "c" (self: ^Menu) -> id {
+ return msgSend(id, self, "contextMenuRepresentation")
+}
+@(objc_type=Menu, objc_name="setTearOffMenuRepresentation")
+Menu_setTearOffMenuRepresentation :: #force_inline proc "c" (self: ^Menu, menuRep: id) {
+ msgSend(nil, self, "setTearOffMenuRepresentation:", menuRep)
+}
+@(objc_type=Menu, objc_name="tearOffMenuRepresentation")
+Menu_tearOffMenuRepresentation :: #force_inline proc "c" (self: ^Menu) -> id {
+ return msgSend(id, self, "tearOffMenuRepresentation")
+}
+@(objc_type=Menu, objc_name="menuZone", objc_is_class_method=true)
+Menu_menuZone :: #force_inline proc "c" () -> ^Zone {
+ return msgSend(^Zone, Menu, "menuZone")
+}
+@(objc_type=Menu, objc_name="setMenuZone", objc_is_class_method=true)
+Menu_setMenuZone :: #force_inline proc "c" (zone: ^Zone) {
+ msgSend(nil, Menu, "setMenuZone:", zone)
+}
+@(objc_type=Menu, objc_name="attachedMenu")
+Menu_attachedMenu :: #force_inline proc "c" (self: ^Menu) -> ^Menu {
+ return msgSend(^Menu, self, "attachedMenu")
+}
+@(objc_type=Menu, objc_name="isAttached")
+Menu_isAttached :: #force_inline proc "c" (self: ^Menu) -> bool {
+ return msgSend(bool, self, "isAttached")
+}
+@(objc_type=Menu, objc_name="sizeToFit")
+Menu_sizeToFit :: #force_inline proc "c" (self: ^Menu) {
+ msgSend(nil, self, "sizeToFit")
+}
+@(objc_type=Menu, objc_name="locationForSubmenu")
+Menu_locationForSubmenu :: #force_inline proc "c" (self: ^Menu, submenu: ^Menu) -> Point {
+ return msgSend(Point, self, "locationForSubmenu:", submenu)
+}
+@(objc_type=Menu, objc_name="helpRequested")
+Menu_helpRequested :: #force_inline proc "c" (self: ^Menu, eventPtr: ^Event) {
+ msgSend(nil, self, "helpRequested:", eventPtr)
+}
+@(objc_type=Menu, objc_name="menuChangedMessagesEnabled")
+Menu_menuChangedMessagesEnabled :: #force_inline proc "c" (self: ^Menu) -> bool {
+ return msgSend(bool, self, "menuChangedMessagesEnabled")
+}
+@(objc_type=Menu, objc_name="setMenuChangedMessagesEnabled")
+Menu_setMenuChangedMessagesEnabled :: #force_inline proc "c" (self: ^Menu, menuChangedMessagesEnabled: bool) {
+ msgSend(nil, self, "setMenuChangedMessagesEnabled:", menuChangedMessagesEnabled)
+}
+@(objc_type=Menu, objc_name="isTornOff")
+Menu_isTornOff :: #force_inline proc "c" (self: ^Menu) -> bool {
+ return msgSend(bool, self, "isTornOff")
+}
+@(objc_type=Menu, objc_name="load", objc_is_class_method=true)
+Menu_load :: #force_inline proc "c" () {
+ msgSend(nil, Menu, "load")
+}
+@(objc_type=Menu, objc_name="initialize", objc_is_class_method=true)
+Menu_initialize :: #force_inline proc "c" () {
+ msgSend(nil, Menu, "initialize")
+}
+@(objc_type=Menu, objc_name="new", objc_is_class_method=true)
+Menu_new :: #force_inline proc "c" () -> ^Menu {
+ return msgSend(^Menu, Menu, "new")
+}
+@(objc_type=Menu, objc_name="allocWithZone", objc_is_class_method=true)
+Menu_allocWithZone :: #force_inline proc "c" (zone: ^Zone) -> ^Menu {
+ return msgSend(^Menu, Menu, "allocWithZone:", zone)
+}
+@(objc_type=Menu, objc_name="alloc", objc_is_class_method=true)
+Menu_alloc :: #force_inline proc "c" () -> ^Menu {
+ return msgSend(^Menu, Menu, "alloc")
+}
+@(objc_type=Menu, objc_name="copyWithZone", objc_is_class_method=true)
+Menu_copyWithZone :: #force_inline proc "c" (zone: ^Zone) -> id {
+ return msgSend(id, Menu, "copyWithZone:", zone)
+}
+@(objc_type=Menu, objc_name="mutableCopyWithZone", objc_is_class_method=true)
+Menu_mutableCopyWithZone :: #force_inline proc "c" (zone: ^Zone) -> id {
+ return msgSend(id, Menu, "mutableCopyWithZone:", zone)
+}
+@(objc_type=Menu, objc_name="instancesRespondToSelector", objc_is_class_method=true)
+Menu_instancesRespondToSelector :: #force_inline proc "c" (aSelector: SEL) -> bool {
+ return msgSend(bool, Menu, "instancesRespondToSelector:", aSelector)
+}
+@(objc_type=Menu, objc_name="conformsToProtocol", objc_is_class_method=true)
+Menu_conformsToProtocol :: #force_inline proc "c" (protocol: ^Protocol) -> bool {
+ return msgSend(bool, Menu, "conformsToProtocol:", protocol)
+}
+@(objc_type=Menu, objc_name="instanceMethodForSelector", objc_is_class_method=true)
+Menu_instanceMethodForSelector :: #force_inline proc "c" (aSelector: SEL) -> IMP {
+ return msgSend(IMP, Menu, "instanceMethodForSelector:", aSelector)
+}
+// @(objc_type=Menu, objc_name="instanceMethodSignatureForSelector", objc_is_class_method=true)
+// Menu_instanceMethodSignatureForSelector :: #force_inline proc "c" (aSelector: SEL) -> ^MethodSignature {
+// return msgSend(^MethodSignature, Menu, "instanceMethodSignatureForSelector:", aSelector)
+// }
+@(objc_type=Menu, objc_name="isSubclassOfClass", objc_is_class_method=true)
+Menu_isSubclassOfClass :: #force_inline proc "c" (aClass: Class) -> bool {
+ return msgSend(bool, Menu, "isSubclassOfClass:", aClass)
+}
+@(objc_type=Menu, objc_name="resolveClassMethod", objc_is_class_method=true)
+Menu_resolveClassMethod :: #force_inline proc "c" (sel: SEL) -> bool {
+ return msgSend(bool, Menu, "resolveClassMethod:", sel)
+}
+@(objc_type=Menu, objc_name="resolveInstanceMethod", objc_is_class_method=true)
+Menu_resolveInstanceMethod :: #force_inline proc "c" (sel: SEL) -> bool {
+ return msgSend(bool, Menu, "resolveInstanceMethod:", sel)
+}
+@(objc_type=Menu, objc_name="hash", objc_is_class_method=true)
+Menu_hash :: #force_inline proc "c" () -> UInteger {
+ return msgSend(UInteger, Menu, "hash")
+}
+@(objc_type=Menu, objc_name="superclass", objc_is_class_method=true)
+Menu_superclass :: #force_inline proc "c" () -> Class {
+ return msgSend(Class, Menu, "superclass")
+}
+@(objc_type=Menu, objc_name="class", objc_is_class_method=true)
+Menu_class :: #force_inline proc "c" () -> Class {
+ return msgSend(Class, Menu, "class")
+}
+@(objc_type=Menu, objc_name="description", objc_is_class_method=true)
+Menu_description :: #force_inline proc "c" () -> ^String {
+ return msgSend(^String, Menu, "description")
+}
+@(objc_type=Menu, objc_name="debugDescription", objc_is_class_method=true)
+Menu_debugDescription :: #force_inline proc "c" () -> ^String {
+ return msgSend(^String, Menu, "debugDescription")
+}
+@(objc_type=Menu, objc_name="version", objc_is_class_method=true)
+Menu_version :: #force_inline proc "c" () -> Integer {
+ return msgSend(Integer, Menu, "version")
+}
+@(objc_type=Menu, objc_name="setVersion", objc_is_class_method=true)
+Menu_setVersion :: #force_inline proc "c" (aVersion: Integer) {
+ msgSend(nil, Menu, "setVersion:", aVersion)
+}
+@(objc_type=Menu, objc_name="poseAsClass", objc_is_class_method=true)
+Menu_poseAsClass :: #force_inline proc "c" (aClass: Class) {
+ msgSend(nil, Menu, "poseAsClass:", aClass)
+}
+@(objc_type=Menu, objc_name="cancelPreviousPerformRequestsWithTarget_selector_object", objc_is_class_method=true)
+Menu_cancelPreviousPerformRequestsWithTarget_selector_object :: #force_inline proc "c" (aTarget: id, aSelector: SEL, anArgument: id) {
+ msgSend(nil, Menu, "cancelPreviousPerformRequestsWithTarget:selector:object:", aTarget, aSelector, anArgument)
+}
+@(objc_type=Menu, objc_name="cancelPreviousPerformRequestsWithTarget_", objc_is_class_method=true)
+Menu_cancelPreviousPerformRequestsWithTarget_ :: #force_inline proc "c" (aTarget: id) {
+ msgSend(nil, Menu, "cancelPreviousPerformRequestsWithTarget:", aTarget)
+}
+@(objc_type=Menu, objc_name="accessInstanceVariablesDirectly", objc_is_class_method=true)
+Menu_accessInstanceVariablesDirectly :: #force_inline proc "c" () -> bool {
+ return msgSend(bool, Menu, "accessInstanceVariablesDirectly")
+}
+@(objc_type=Menu, objc_name="useStoredAccessor", objc_is_class_method=true)
+Menu_useStoredAccessor :: #force_inline proc "c" () -> bool {
+ return msgSend(bool, Menu, "useStoredAccessor")
+}
+@(objc_type=Menu, objc_name="keyPathsForValuesAffectingValueForKey", objc_is_class_method=true)
+Menu_keyPathsForValuesAffectingValueForKey :: #force_inline proc "c" (key: ^String) -> ^Set {
+ return msgSend(^Set, Menu, "keyPathsForValuesAffectingValueForKey:", key)
+}
+@(objc_type=Menu, objc_name="automaticallyNotifiesObserversForKey", objc_is_class_method=true)
+Menu_automaticallyNotifiesObserversForKey :: #force_inline proc "c" (key: ^String) -> bool {
+ return msgSend(bool, Menu, "automaticallyNotifiesObserversForKey:", key)
+}
+@(objc_type=Menu, objc_name="setKeys", objc_is_class_method=true)
+Menu_setKeys :: #force_inline proc "c" (keys: ^Array, dependentKey: ^String) {
+ msgSend(nil, Menu, "setKeys:triggerChangeNotificationsForDependentKey:", keys, dependentKey)
+}
+@(objc_type=Menu, objc_name="classFallbacksForKeyedArchiver", objc_is_class_method=true)
+Menu_classFallbacksForKeyedArchiver :: #force_inline proc "c" () -> ^Array {
+ return msgSend(^Array, Menu, "classFallbacksForKeyedArchiver")
+}
+@(objc_type=Menu, objc_name="classForKeyedUnarchiver", objc_is_class_method=true)
+Menu_classForKeyedUnarchiver :: #force_inline proc "c" () -> Class {
+ return msgSend(Class, Menu, "classForKeyedUnarchiver")
+}
+@(objc_type=Menu, objc_name="exposeBinding", objc_is_class_method=true)
+Menu_exposeBinding :: #force_inline proc "c" (binding: ^String) {
+ msgSend(nil, Menu, "exposeBinding:", binding)
+}
+@(objc_type=Menu, objc_name="setDefaultPlaceholder", objc_is_class_method=true)
+Menu_setDefaultPlaceholder :: #force_inline proc "c" (placeholder: id, marker: id, binding: ^String) {
+ msgSend(nil, Menu, "setDefaultPlaceholder:forMarker:withBinding:", placeholder, marker, binding)
+}
+@(objc_type=Menu, objc_name="defaultPlaceholderForMarker", objc_is_class_method=true)
+Menu_defaultPlaceholderForMarker :: #force_inline proc "c" (marker: id, binding: ^String) -> id {
+ return msgSend(id, Menu, "defaultPlaceholderForMarker:withBinding:", marker, binding)
+}
+@(objc_type=Menu, objc_name="popUpContextMenu")
+Menu_popUpContextMenu :: proc {
+ Menu_popUpContextMenu_withEvent_forView,
+ // Menu_popUpContextMenu_withEvent_forView_withFont,
}
-@(objc_type=Menu, objc_name="itemArray")
-Menu_itemArray :: proc "c" (self: ^Menu) -> ^Array {
- return msgSend(^Array, self, "itemArray")
-}
\ No newline at end of file
+@(objc_type=Menu, objc_name="paletteMenuWithColors")
+Menu_paletteMenuWithColors :: proc {
+ Menu_paletteMenuWithColors_titles_selectionHandler,
+ // Menu_paletteMenuWithColors_titles_templateImage_selectionHandler,
+}
+
+@(objc_type=Menu, objc_name="cancelPreviousPerformRequestsWithTarget")
+Menu_cancelPreviousPerformRequestsWithTarget :: proc {
+ Menu_cancelPreviousPerformRequestsWithTarget_selector_object,
+ Menu_cancelPreviousPerformRequestsWithTarget_,
+}
+
+
+
+
+
+
+
+@(objc_class="NSMenuDelegate")
+MenuDelegate :: struct {using _: Object, using _: ObjectProtocol}
+
+@(objc_type=MenuDelegate, objc_name="menuNeedsUpdate")
+MenuDelegate_menuNeedsUpdate :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu) {
+ msgSend(nil, self, "menuNeedsUpdate:", menu)
+}
+@(objc_type=MenuDelegate, objc_name="numberOfItemsInMenu")
+MenuDelegate_numberOfItemsInMenu :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu) -> Integer {
+ return msgSend(Integer, self, "numberOfItemsInMenu:", menu)
+}
+@(objc_type=MenuDelegate, objc_name="menu_updateItem_atIndex_shouldCancel")
+MenuDelegate_menu_updateItem_atIndex_shouldCancel :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu, item: ^MenuItem, index: Integer, shouldCancel: bool) -> bool {
+ return msgSend(bool, self, "menu:updateItem:atIndex:shouldCancel:", menu, item, index, shouldCancel)
+}
+@(objc_type=MenuDelegate, objc_name="menuHasKeyEquivalent")
+MenuDelegate_menuHasKeyEquivalent :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu, event: ^Event, target: ^id, action: ^SEL) -> bool {
+ return msgSend(bool, self, "menuHasKeyEquivalent:forEvent:target:action:", menu, event, target, action)
+}
+@(objc_type=MenuDelegate, objc_name="menuWillOpen")
+MenuDelegate_menuWillOpen :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu) {
+ msgSend(nil, self, "menuWillOpen:", menu)
+}
+@(objc_type=MenuDelegate, objc_name="menuDidClose")
+MenuDelegate_menuDidClose :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu) {
+ msgSend(nil, self, "menuDidClose:", menu)
+}
+@(objc_type=MenuDelegate, objc_name="menu_willHighlightItem")
+MenuDelegate_menu_willHighlightItem :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu, item: ^MenuItem) {
+ msgSend(nil, self, "menu:willHighlightItem:", menu, item)
+}
+@(objc_type=MenuDelegate, objc_name="confinementRectForMenu")
+MenuDelegate_confinementRectForMenu :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu, screen: ^Screen) -> Rect {
+ return msgSend(Rect, self, "confinementRectForMenu:onScreen:", menu, screen)
+}
+@(objc_type=MenuDelegate, objc_name="menu")
+MenuDelegate_menu :: proc {
+ MenuDelegate_menu_updateItem_atIndex_shouldCancel,
+ MenuDelegate_menu_willHighlightItem,
+}
diff --git a/core/sys/darwin/Foundation/NSMenuItem.odin b/core/sys/darwin/Foundation/NSMenuItem.odin
new file mode 100644
index 000000000..248a0cf4f
--- /dev/null
+++ b/core/sys/darwin/Foundation/NSMenuItem.odin
@@ -0,0 +1,460 @@
+package objc_Foundation
+
+import "base:builtin"
+import "base:intrinsics"
+
+KeyEquivalentModifierFlag :: EventModifierFlag
+KeyEquivalentModifierMask :: EventModifierFlags
+
+// Used to retrieve only the device-independent modifier flags, allowing applications to mask off the device-dependent modifier flags, including event coalescing information.
+KeyEventModifierFlagDeviceIndependentFlagsMask := transmute(KeyEquivalentModifierMask)_KeyEventModifierFlagDeviceIndependentFlagsMask
+@(private) _KeyEventModifierFlagDeviceIndependentFlagsMask := UInteger(0xffff0000)
+
+MenuItemCallback :: proc "c" (unused: rawptr, name: SEL, sender: ^Object)
+
+@(objc_class="NSMenuItem")
+MenuItem :: struct {using _: Object}
+
+@(objc_type=MenuItem, objc_name="registerActionCallback", objc_is_class_method=true)
+MenuItem_registerActionCallback :: proc "c" (name: cstring, callback: MenuItemCallback) -> SEL {
+ s := string(name)
+ n := len(s)
+ sel: SEL
+ if n > 0 && s[n-1] != ':' {
+ col_name := intrinsics.alloca(n+2, 1)
+ builtin.copy(col_name[:n], s)
+ col_name[n] = ':'
+ col_name[n+1] = 0
+ sel = sel_registerName(cstring(col_name))
+ } else {
+ sel = sel_registerName(name)
+ }
+ if callback != nil {
+ class_addMethod(intrinsics.objc_find_class("NSObject"), sel, auto_cast callback, "v@:@")
+ }
+ return sel
+}
+
+@(objc_type=MenuItem, objc_name="init")
+MenuItem_init :: proc "c" (self: ^MenuItem) -> ^MenuItem {
+ return msgSend(^MenuItem, self, "init")
+}
+
+
+@(objc_type=MenuItem, objc_name="separatorItem", objc_is_class_method=true)
+MenuItem_separatorItem :: #force_inline proc "c" () -> ^MenuItem {
+ return msgSend(^MenuItem, MenuItem, "separatorItem")
+}
+@(objc_type=MenuItem, objc_name="sectionHeaderWithTitle", objc_is_class_method=true)
+MenuItem_sectionHeaderWithTitle :: #force_inline proc "c" (title: ^String) -> ^MenuItem {
+ return msgSend(^MenuItem, MenuItem, "sectionHeaderWithTitle:", title)
+}
+@(objc_type=MenuItem, objc_name="initWithTitle")
+MenuItem_initWithTitle :: #force_inline proc "c" (self: ^MenuItem, string: ^String, selector: SEL, charCode: ^String) -> ^MenuItem {
+ return msgSend(^MenuItem, self, "initWithTitle:action:keyEquivalent:", string, selector, charCode)
+}
+@(objc_type=MenuItem, objc_name="initWithCoder")
+MenuItem_initWithCoder :: #force_inline proc "c" (self: ^MenuItem, coder: ^Coder) -> ^MenuItem {
+ return msgSend(^MenuItem, self, "initWithCoder:", coder)
+}
+@(objc_type=MenuItem, objc_name="usesUserKeyEquivalents", objc_is_class_method=true)
+MenuItem_usesUserKeyEquivalents :: #force_inline proc "c" () -> bool {
+ return msgSend(bool, MenuItem, "usesUserKeyEquivalents")
+}
+@(objc_type=MenuItem, objc_name="setUsesUserKeyEquivalents", objc_is_class_method=true)
+MenuItem_setUsesUserKeyEquivalents :: #force_inline proc "c" (usesUserKeyEquivalents: bool) {
+ msgSend(nil, MenuItem, "setUsesUserKeyEquivalents:", usesUserKeyEquivalents)
+}
+@(objc_type=MenuItem, objc_name="menu")
+MenuItem_menu :: #force_inline proc "c" (self: ^MenuItem) -> ^Menu {
+ return msgSend(^Menu, self, "menu")
+}
+@(objc_type=MenuItem, objc_name="setMenu")
+MenuItem_setMenu :: #force_inline proc "c" (self: ^MenuItem, menu: ^Menu) {
+ msgSend(nil, self, "setMenu:", menu)
+}
+@(objc_type=MenuItem, objc_name="hasSubmenu")
+MenuItem_hasSubmenu :: #force_inline proc "c" (self: ^MenuItem) -> bool {
+ return msgSend(bool, self, "hasSubmenu")
+}
+@(objc_type=MenuItem, objc_name="submenu")
+MenuItem_submenu :: #force_inline proc "c" (self: ^MenuItem) -> ^Menu {
+ return msgSend(^Menu, self, "submenu")
+}
+@(objc_type=MenuItem, objc_name="setSubmenu")
+MenuItem_setSubmenu :: #force_inline proc "c" (self: ^MenuItem, submenu: ^Menu) {
+ msgSend(nil, self, "setSubmenu:", submenu)
+}
+@(objc_type=MenuItem, objc_name="parentItem")
+MenuItem_parentItem :: #force_inline proc "c" (self: ^MenuItem) -> ^MenuItem {
+ return msgSend(^MenuItem, self, "parentItem")
+}
+@(objc_type=MenuItem, objc_name="title")
+MenuItem_title :: #force_inline proc "c" (self: ^MenuItem) -> ^String {
+ return msgSend(^String, self, "title")
+}
+@(objc_type=MenuItem, objc_name="setTitle")
+MenuItem_setTitle :: #force_inline proc "c" (self: ^MenuItem, title: ^String) {
+ msgSend(nil, self, "setTitle:", title)
+}
+// @(objc_type=MenuItem, objc_name="attributedTitle")
+// MenuItem_attributedTitle :: #force_inline proc "c" (self: ^MenuItem) -> ^AttributedString {
+// return msgSend(^AttributedString, self, "attributedTitle")
+// }
+// @(objc_type=MenuItem, objc_name="setAttributedTitle")
+// MenuItem_setAttributedTitle :: #force_inline proc "c" (self: ^MenuItem, attributedTitle: ^AttributedString) {
+// msgSend(nil, self, "setAttributedTitle:", attributedTitle)
+// }
+@(objc_type=MenuItem, objc_name="subtitle")
+MenuItem_subtitle :: #force_inline proc "c" (self: ^MenuItem) -> ^String {
+ return msgSend(^String, self, "subtitle")
+}
+@(objc_type=MenuItem, objc_name="setSubtitle")
+MenuItem_setSubtitle :: #force_inline proc "c" (self: ^MenuItem, subtitle: ^String) {
+ msgSend(nil, self, "setSubtitle:", subtitle)
+}
+@(objc_type=MenuItem, objc_name="isSeparatorItem")
+MenuItem_isSeparatorItem :: #force_inline proc "c" (self: ^MenuItem) -> bool {
+ return msgSend(bool, self, "isSeparatorItem")
+}
+@(objc_type=MenuItem, objc_name="isSectionHeader")
+MenuItem_isSectionHeader :: #force_inline proc "c" (self: ^MenuItem) -> bool {
+ return msgSend(bool, self, "isSectionHeader")
+}
+@(objc_type=MenuItem, objc_name="keyEquivalent")
+MenuItem_keyEquivalent :: #force_inline proc "c" (self: ^MenuItem) -> ^String {
+ return msgSend(^String, self, "keyEquivalent")
+}
+@(objc_type=MenuItem, objc_name="setKeyEquivalent")
+MenuItem_setKeyEquivalent :: #force_inline proc "c" (self: ^MenuItem, keyEquivalent: ^String) {
+ msgSend(nil, self, "setKeyEquivalent:", keyEquivalent)
+}
+@(objc_type=MenuItem, objc_name="keyEquivalentModifierMask")
+MenuItem_keyEquivalentModifierMask :: #force_inline proc "c" (self: ^MenuItem) -> EventModifierFlags {
+ return msgSend(EventModifierFlags, self, "keyEquivalentModifierMask")
+}
+@(objc_type=MenuItem, objc_name="setKeyEquivalentModifierMask")
+MenuItem_setKeyEquivalentModifierMask :: #force_inline proc "c" (self: ^MenuItem, keyEquivalentModifierMask: EventModifierFlags) {
+ msgSend(nil, self, "setKeyEquivalentModifierMask:", keyEquivalentModifierMask)
+}
+@(objc_type=MenuItem, objc_name="userKeyEquivalent")
+MenuItem_userKeyEquivalent :: #force_inline proc "c" (self: ^MenuItem) -> ^String {
+ return msgSend(^String, self, "userKeyEquivalent")
+}
+@(objc_type=MenuItem, objc_name="allowsKeyEquivalentWhenHidden")
+MenuItem_allowsKeyEquivalentWhenHidden :: #force_inline proc "c" (self: ^MenuItem) -> bool {
+ return msgSend(bool, self, "allowsKeyEquivalentWhenHidden")
+}
+@(objc_type=MenuItem, objc_name="setAllowsKeyEquivalentWhenHidden")
+MenuItem_setAllowsKeyEquivalentWhenHidden :: #force_inline proc "c" (self: ^MenuItem, allowsKeyEquivalentWhenHidden: bool) {
+ msgSend(nil, self, "setAllowsKeyEquivalentWhenHidden:", allowsKeyEquivalentWhenHidden)
+}
+@(objc_type=MenuItem, objc_name="allowsAutomaticKeyEquivalentLocalization")
+MenuItem_allowsAutomaticKeyEquivalentLocalization :: #force_inline proc "c" (self: ^MenuItem) -> bool {
+ return msgSend(bool, self, "allowsAutomaticKeyEquivalentLocalization")
+}
+@(objc_type=MenuItem, objc_name="setAllowsAutomaticKeyEquivalentLocalization")
+MenuItem_setAllowsAutomaticKeyEquivalentLocalization :: #force_inline proc "c" (self: ^MenuItem, allowsAutomaticKeyEquivalentLocalization: bool) {
+ msgSend(nil, self, "setAllowsAutomaticKeyEquivalentLocalization:", allowsAutomaticKeyEquivalentLocalization)
+}
+@(objc_type=MenuItem, objc_name="allowsAutomaticKeyEquivalentMirroring")
+MenuItem_allowsAutomaticKeyEquivalentMirroring :: #force_inline proc "c" (self: ^MenuItem) -> bool {
+ return msgSend(bool, self, "allowsAutomaticKeyEquivalentMirroring")
+}
+@(objc_type=MenuItem, objc_name="setAllowsAutomaticKeyEquivalentMirroring")
+MenuItem_setAllowsAutomaticKeyEquivalentMirroring :: #force_inline proc "c" (self: ^MenuItem, allowsAutomaticKeyEquivalentMirroring: bool) {
+ msgSend(nil, self, "setAllowsAutomaticKeyEquivalentMirroring:", allowsAutomaticKeyEquivalentMirroring)
+}
+// @(objc_type=MenuItem, objc_name="image")
+// MenuItem_image :: #force_inline proc "c" (self: ^MenuItem) -> ^Image {
+// return msgSend(^Image, self, "image")
+// }
+// @(objc_type=MenuItem, objc_name="setImage")
+// MenuItem_setImage :: #force_inline proc "c" (self: ^MenuItem, image: ^Image) {
+// msgSend(nil, self, "setImage:", image)
+// }
+// @(objc_type=MenuItem, objc_name="state")
+// MenuItem_state :: #force_inline proc "c" (self: ^MenuItem) -> ControlStateValue {
+// return msgSend(ControlStateValue, self, "state")
+// }
+// @(objc_type=MenuItem, objc_name="setState")
+// MenuItem_setState :: #force_inline proc "c" (self: ^MenuItem, state: ControlStateValue) {
+// msgSend(nil, self, "setState:", state)
+// }
+// @(objc_type=MenuItem, objc_name="onStateImage")
+// MenuItem_onStateImage :: #force_inline proc "c" (self: ^MenuItem) -> ^Image {
+// return msgSend(^Image, self, "onStateImage")
+// }
+// @(objc_type=MenuItem, objc_name="setOnStateImage")
+// MenuItem_setOnStateImage :: #force_inline proc "c" (self: ^MenuItem, onStateImage: ^Image) {
+// msgSend(nil, self, "setOnStateImage:", onStateImage)
+// }
+// @(objc_type=MenuItem, objc_name="offStateImage")
+// MenuItem_offStateImage :: #force_inline proc "c" (self: ^MenuItem) -> ^Image {
+// return msgSend(^Image, self, "offStateImage")
+// }
+// @(objc_type=MenuItem, objc_name="setOffStateImage")
+// MenuItem_setOffStateImage :: #force_inline proc "c" (self: ^MenuItem, offStateImage: ^Image) {
+// msgSend(nil, self, "setOffStateImage:", offStateImage)
+// }
+// @(objc_type=MenuItem, objc_name="mixedStateImage")
+// MenuItem_mixedStateImage :: #force_inline proc "c" (self: ^MenuItem) -> ^Image {
+// return msgSend(^Image, self, "mixedStateImage")
+// }
+// @(objc_type=MenuItem, objc_name="setMixedStateImage")
+// MenuItem_setMixedStateImage :: #force_inline proc "c" (self: ^MenuItem, mixedStateImage: ^Image) {
+// msgSend(nil, self, "setMixedStateImage:", mixedStateImage)
+// }
+@(objc_type=MenuItem, objc_name="isEnabled")
+MenuItem_isEnabled :: #force_inline proc "c" (self: ^MenuItem) -> bool {
+ return msgSend(bool, self, "isEnabled")
+}
+@(objc_type=MenuItem, objc_name="setEnabled")
+MenuItem_setEnabled :: #force_inline proc "c" (self: ^MenuItem, enabled: bool) {
+ msgSend(nil, self, "setEnabled:", enabled)
+}
+@(objc_type=MenuItem, objc_name="isAlternate")
+MenuItem_isAlternate :: #force_inline proc "c" (self: ^MenuItem) -> bool {
+ return msgSend(bool, self, "isAlternate")
+}
+@(objc_type=MenuItem, objc_name="setAlternate")
+MenuItem_setAlternate :: #force_inline proc "c" (self: ^MenuItem, alternate: bool) {
+ msgSend(nil, self, "setAlternate:", alternate)
+}
+@(objc_type=MenuItem, objc_name="indentationLevel")
+MenuItem_indentationLevel :: #force_inline proc "c" (self: ^MenuItem) -> Integer {
+ return msgSend(Integer, self, "indentationLevel")
+}
+@(objc_type=MenuItem, objc_name="setIndentationLevel")
+MenuItem_setIndentationLevel :: #force_inline proc "c" (self: ^MenuItem, indentationLevel: Integer) {
+ msgSend(nil, self, "setIndentationLevel:", indentationLevel)
+}
+@(objc_type=MenuItem, objc_name="target")
+MenuItem_target :: #force_inline proc "c" (self: ^MenuItem) -> id {
+ return msgSend(id, self, "target")
+}
+@(objc_type=MenuItem, objc_name="setTarget")
+MenuItem_setTarget :: #force_inline proc "c" (self: ^MenuItem, target: id) {
+ msgSend(nil, self, "setTarget:", target)
+}
+@(objc_type=MenuItem, objc_name="action")
+MenuItem_action :: #force_inline proc "c" (self: ^MenuItem) -> SEL {
+ return msgSend(SEL, self, "action")
+}
+@(objc_type=MenuItem, objc_name="setAction")
+MenuItem_setAction :: #force_inline proc "c" (self: ^MenuItem, action: SEL) {
+ msgSend(nil, self, "setAction:", action)
+}
+@(objc_type=MenuItem, objc_name="tag")
+MenuItem_tag :: #force_inline proc "c" (self: ^MenuItem) -> Integer {
+ return msgSend(Integer, self, "tag")
+}
+@(objc_type=MenuItem, objc_name="setTag")
+MenuItem_setTag :: #force_inline proc "c" (self: ^MenuItem, tag: Integer) {
+ msgSend(nil, self, "setTag:", tag)
+}
+@(objc_type=MenuItem, objc_name="representedObject")
+MenuItem_representedObject :: #force_inline proc "c" (self: ^MenuItem) -> id {
+ return msgSend(id, self, "representedObject")
+}
+@(objc_type=MenuItem, objc_name="setRepresentedObject")
+MenuItem_setRepresentedObject :: #force_inline proc "c" (self: ^MenuItem, representedObject: id) {
+ msgSend(nil, self, "setRepresentedObject:", representedObject)
+}
+@(objc_type=MenuItem, objc_name="view")
+MenuItem_view :: #force_inline proc "c" (self: ^MenuItem) -> ^View {
+ return msgSend(^View, self, "view")
+}
+@(objc_type=MenuItem, objc_name="setView")
+MenuItem_setView :: #force_inline proc "c" (self: ^MenuItem, view: ^View) {
+ msgSend(nil, self, "setView:", view)
+}
+@(objc_type=MenuItem, objc_name="isHighlighted")
+MenuItem_isHighlighted :: #force_inline proc "c" (self: ^MenuItem) -> bool {
+ return msgSend(bool, self, "isHighlighted")
+}
+@(objc_type=MenuItem, objc_name="isHidden")
+MenuItem_isHidden :: #force_inline proc "c" (self: ^MenuItem) -> bool {
+ return msgSend(bool, self, "isHidden")
+}
+@(objc_type=MenuItem, objc_name="setHidden")
+MenuItem_setHidden :: #force_inline proc "c" (self: ^MenuItem, hidden: bool) {
+ msgSend(nil, self, "setHidden:", hidden)
+}
+@(objc_type=MenuItem, objc_name="isHiddenOrHasHiddenAncestor")
+MenuItem_isHiddenOrHasHiddenAncestor :: #force_inline proc "c" (self: ^MenuItem) -> bool {
+ return msgSend(bool, self, "isHiddenOrHasHiddenAncestor")
+}
+@(objc_type=MenuItem, objc_name="toolTip")
+MenuItem_toolTip :: #force_inline proc "c" (self: ^MenuItem) -> ^String {
+ return msgSend(^String, self, "toolTip")
+}
+@(objc_type=MenuItem, objc_name="setToolTip")
+MenuItem_setToolTip :: #force_inline proc "c" (self: ^MenuItem, toolTip: ^String) {
+ msgSend(nil, self, "setToolTip:", toolTip)
+}
+// @(objc_type=MenuItem, objc_name="badge")
+// MenuItem_badge :: #force_inline proc "c" (self: ^MenuItem) -> ^MenuItemBadge {
+// return msgSend(^MenuItemBadge, self, "badge")
+// }
+// @(objc_type=MenuItem, objc_name="setBadge")
+// MenuItem_setBadge :: #force_inline proc "c" (self: ^MenuItem, badge: ^MenuItemBadge) {
+// msgSend(nil, self, "setBadge:", badge)
+// }
+@(objc_type=MenuItem, objc_name="setMnemonicLocation")
+MenuItem_setMnemonicLocation :: #force_inline proc "c" (self: ^MenuItem, location: UInteger) {
+ msgSend(nil, self, "setMnemonicLocation:", location)
+}
+@(objc_type=MenuItem, objc_name="mnemonicLocation")
+MenuItem_mnemonicLocation :: #force_inline proc "c" (self: ^MenuItem) -> UInteger {
+ return msgSend(UInteger, self, "mnemonicLocation")
+}
+@(objc_type=MenuItem, objc_name="mnemonic")
+MenuItem_mnemonic :: #force_inline proc "c" (self: ^MenuItem) -> ^String {
+ return msgSend(^String, self, "mnemonic")
+}
+@(objc_type=MenuItem, objc_name="setTitleWithMnemonic")
+MenuItem_setTitleWithMnemonic :: #force_inline proc "c" (self: ^MenuItem, stringWithAmpersand: ^String) {
+ msgSend(nil, self, "setTitleWithMnemonic:", stringWithAmpersand)
+}
+@(objc_type=MenuItem, objc_name="load", objc_is_class_method=true)
+MenuItem_load :: #force_inline proc "c" () {
+ msgSend(nil, MenuItem, "load")
+}
+@(objc_type=MenuItem, objc_name="initialize", objc_is_class_method=true)
+MenuItem_initialize :: #force_inline proc "c" () {
+ msgSend(nil, MenuItem, "initialize")
+}
+@(objc_type=MenuItem, objc_name="new", objc_is_class_method=true)
+MenuItem_new :: #force_inline proc "c" () -> ^MenuItem {
+ return msgSend(^MenuItem, MenuItem, "new")
+}
+@(objc_type=MenuItem, objc_name="allocWithZone", objc_is_class_method=true)
+MenuItem_allocWithZone :: #force_inline proc "c" (zone: ^Zone) -> ^MenuItem {
+ return msgSend(^MenuItem, MenuItem, "allocWithZone:", zone)
+}
+@(objc_type=MenuItem, objc_name="alloc", objc_is_class_method=true)
+MenuItem_alloc :: #force_inline proc "c" () -> ^MenuItem {
+ return msgSend(^MenuItem, MenuItem, "alloc")
+}
+@(objc_type=MenuItem, objc_name="copyWithZone", objc_is_class_method=true)
+MenuItem_copyWithZone :: #force_inline proc "c" (zone: ^Zone) -> id {
+ return msgSend(id, MenuItem, "copyWithZone:", zone)
+}
+@(objc_type=MenuItem, objc_name="mutableCopyWithZone", objc_is_class_method=true)
+MenuItem_mutableCopyWithZone :: #force_inline proc "c" (zone: ^Zone) -> id {
+ return msgSend(id, MenuItem, "mutableCopyWithZone:", zone)
+}
+@(objc_type=MenuItem, objc_name="instancesRespondToSelector", objc_is_class_method=true)
+MenuItem_instancesRespondToSelector :: #force_inline proc "c" (aSelector: SEL) -> bool {
+ return msgSend(bool, MenuItem, "instancesRespondToSelector:", aSelector)
+}
+@(objc_type=MenuItem, objc_name="conformsToProtocol", objc_is_class_method=true)
+MenuItem_conformsToProtocol :: #force_inline proc "c" (protocol: ^Protocol) -> bool {
+ return msgSend(bool, MenuItem, "conformsToProtocol:", protocol)
+}
+@(objc_type=MenuItem, objc_name="instanceMethodForSelector", objc_is_class_method=true)
+MenuItem_instanceMethodForSelector :: #force_inline proc "c" (aSelector: SEL) -> IMP {
+ return msgSend(IMP, MenuItem, "instanceMethodForSelector:", aSelector)
+}
+// @(objc_type=MenuItem, objc_name="instanceMethodSignatureForSelector", objc_is_class_method=true)
+// MenuItem_instanceMethodSignatureForSelector :: #force_inline proc "c" (aSelector: SEL) -> ^MethodSignature {
+// return msgSend(^MethodSignature, MenuItem, "instanceMethodSignatureForSelector:", aSelector)
+// }
+@(objc_type=MenuItem, objc_name="isSubclassOfClass", objc_is_class_method=true)
+MenuItem_isSubclassOfClass :: #force_inline proc "c" (aClass: Class) -> bool {
+ return msgSend(bool, MenuItem, "isSubclassOfClass:", aClass)
+}
+@(objc_type=MenuItem, objc_name="resolveClassMethod", objc_is_class_method=true)
+MenuItem_resolveClassMethod :: #force_inline proc "c" (sel: SEL) -> bool {
+ return msgSend(bool, MenuItem, "resolveClassMethod:", sel)
+}
+@(objc_type=MenuItem, objc_name="resolveInstanceMethod", objc_is_class_method=true)
+MenuItem_resolveInstanceMethod :: #force_inline proc "c" (sel: SEL) -> bool {
+ return msgSend(bool, MenuItem, "resolveInstanceMethod:", sel)
+}
+@(objc_type=MenuItem, objc_name="hash", objc_is_class_method=true)
+MenuItem_hash :: #force_inline proc "c" () -> UInteger {
+ return msgSend(UInteger, MenuItem, "hash")
+}
+@(objc_type=MenuItem, objc_name="superclass", objc_is_class_method=true)
+MenuItem_superclass :: #force_inline proc "c" () -> Class {
+ return msgSend(Class, MenuItem, "superclass")
+}
+@(objc_type=MenuItem, objc_name="class", objc_is_class_method=true)
+MenuItem_class :: #force_inline proc "c" () -> Class {
+ return msgSend(Class, MenuItem, "class")
+}
+@(objc_type=MenuItem, objc_name="description", objc_is_class_method=true)
+MenuItem_description :: #force_inline proc "c" () -> ^String {
+ return msgSend(^String, MenuItem, "description")
+}
+@(objc_type=MenuItem, objc_name="debugDescription", objc_is_class_method=true)
+MenuItem_debugDescription :: #force_inline proc "c" () -> ^String {
+ return msgSend(^String, MenuItem, "debugDescription")
+}
+@(objc_type=MenuItem, objc_name="version", objc_is_class_method=true)
+MenuItem_version :: #force_inline proc "c" () -> Integer {
+ return msgSend(Integer, MenuItem, "version")
+}
+@(objc_type=MenuItem, objc_name="setVersion", objc_is_class_method=true)
+MenuItem_setVersion :: #force_inline proc "c" (aVersion: Integer) {
+ msgSend(nil, MenuItem, "setVersion:", aVersion)
+}
+@(objc_type=MenuItem, objc_name="poseAsClass", objc_is_class_method=true)
+MenuItem_poseAsClass :: #force_inline proc "c" (aClass: Class) {
+ msgSend(nil, MenuItem, "poseAsClass:", aClass)
+}
+@(objc_type=MenuItem, objc_name="cancelPreviousPerformRequestsWithTarget_selector_object", objc_is_class_method=true)
+MenuItem_cancelPreviousPerformRequestsWithTarget_selector_object :: #force_inline proc "c" (aTarget: id, aSelector: SEL, anArgument: id) {
+ msgSend(nil, MenuItem, "cancelPreviousPerformRequestsWithTarget:selector:object:", aTarget, aSelector, anArgument)
+}
+@(objc_type=MenuItem, objc_name="cancelPreviousPerformRequestsWithTarget_", objc_is_class_method=true)
+MenuItem_cancelPreviousPerformRequestsWithTarget_ :: #force_inline proc "c" (aTarget: id) {
+ msgSend(nil, MenuItem, "cancelPreviousPerformRequestsWithTarget:", aTarget)
+}
+@(objc_type=MenuItem, objc_name="accessInstanceVariablesDirectly", objc_is_class_method=true)
+MenuItem_accessInstanceVariablesDirectly :: #force_inline proc "c" () -> bool {
+ return msgSend(bool, MenuItem, "accessInstanceVariablesDirectly")
+}
+@(objc_type=MenuItem, objc_name="useStoredAccessor", objc_is_class_method=true)
+MenuItem_useStoredAccessor :: #force_inline proc "c" () -> bool {
+ return msgSend(bool, MenuItem, "useStoredAccessor")
+}
+@(objc_type=MenuItem, objc_name="keyPathsForValuesAffectingValueForKey", objc_is_class_method=true)
+MenuItem_keyPathsForValuesAffectingValueForKey :: #force_inline proc "c" (key: ^String) -> ^Set {
+ return msgSend(^Set, MenuItem, "keyPathsForValuesAffectingValueForKey:", key)
+}
+@(objc_type=MenuItem, objc_name="automaticallyNotifiesObserversForKey", objc_is_class_method=true)
+MenuItem_automaticallyNotifiesObserversForKey :: #force_inline proc "c" (key: ^String) -> bool {
+ return msgSend(bool, MenuItem, "automaticallyNotifiesObserversForKey:", key)
+}
+@(objc_type=MenuItem, objc_name="setKeys", objc_is_class_method=true)
+MenuItem_setKeys :: #force_inline proc "c" (keys: ^Array, dependentKey: ^String) {
+ msgSend(nil, MenuItem, "setKeys:triggerChangeNotificationsForDependentKey:", keys, dependentKey)
+}
+@(objc_type=MenuItem, objc_name="classFallbacksForKeyedArchiver", objc_is_class_method=true)
+MenuItem_classFallbacksForKeyedArchiver :: #force_inline proc "c" () -> ^Array {
+ return msgSend(^Array, MenuItem, "classFallbacksForKeyedArchiver")
+}
+@(objc_type=MenuItem, objc_name="classForKeyedUnarchiver", objc_is_class_method=true)
+MenuItem_classForKeyedUnarchiver :: #force_inline proc "c" () -> Class {
+ return msgSend(Class, MenuItem, "classForKeyedUnarchiver")
+}
+@(objc_type=MenuItem, objc_name="exposeBinding", objc_is_class_method=true)
+MenuItem_exposeBinding :: #force_inline proc "c" (binding: ^String) {
+ msgSend(nil, MenuItem, "exposeBinding:", binding)
+}
+@(objc_type=MenuItem, objc_name="setDefaultPlaceholder", objc_is_class_method=true)
+MenuItem_setDefaultPlaceholder :: #force_inline proc "c" (placeholder: id, marker: id, binding: ^String) {
+ msgSend(nil, MenuItem, "setDefaultPlaceholder:forMarker:withBinding:", placeholder, marker, binding)
+}
+@(objc_type=MenuItem, objc_name="defaultPlaceholderForMarker", objc_is_class_method=true)
+MenuItem_defaultPlaceholderForMarker :: #force_inline proc "c" (marker: id, binding: ^String) -> id {
+ return msgSend(id, MenuItem, "defaultPlaceholderForMarker:withBinding:", marker, binding)
+}
+@(objc_type=MenuItem, objc_name="cancelPreviousPerformRequestsWithTarget")
+MenuItem_cancelPreviousPerformRequestsWithTarget :: proc {
+ MenuItem_cancelPreviousPerformRequestsWithTarget_selector_object,
+ MenuItem_cancelPreviousPerformRequestsWithTarget_,
+}
\ No newline at end of file
diff --git a/core/sys/darwin/Foundation/objc_helper.odin b/core/sys/darwin/Foundation/objc_helper.odin
new file mode 100644
index 000000000..0748d700b
--- /dev/null
+++ b/core/sys/darwin/Foundation/objc_helper.odin
@@ -0,0 +1,136 @@
+package objc_Foundation
+
+import "base:runtime"
+import "base:intrinsics"
+
+Subclasser_Proc :: proc(cls: Class, vtable: rawptr)
+
+Object_VTable_Info :: struct {
+ vtable: rawptr,
+ size: uint,
+ impl: Subclasser_Proc,
+}
+
+Class_VTable_Info :: struct {
+ _context: runtime.Context,
+ super_vtable: rawptr,
+ protocol_vtable: rawptr,
+}
+
+@(require_results)
+class_get_metaclass :: #force_inline proc "contextless" (cls: Class) -> Class {
+ return (^Class)(cls)^
+}
+
+@(require_results)
+object_get_vtable_info :: proc "contextless" (obj: id) -> ^Class_VTable_Info {
+ return (^Class_VTable_Info)(object_getIndexedIvars(obj))
+}
+
+@(require_results)
+make_subclasser :: #force_inline proc(vtable: ^$T, impl: proc(cls: Class, vt: ^T)) -> Object_VTable_Info {
+ return Object_VTable_Info{
+ vtable = vtable,
+ size = size_of(T),
+ impl = (Subclasser_Proc)(impl),
+ }
+}
+
+@(require_results)
+register_subclass :: proc(
+ class_name: cstring,
+ superclass: Class,
+ superclass_overrides: Maybe(Object_VTable_Info) = nil,
+ protocol: Maybe(Object_VTable_Info) = nil,
+ _context: Maybe(runtime.Context) = nil,
+) -> Class {
+ assert(superclass != nil)
+
+ super_size: uint
+ proto_size: uint
+
+ if superclass_overrides != nil {
+ // Align to 8-byte boundary
+ super_size = (superclass_overrides.?.size + 7)/8 * 8
+ }
+
+ if protocol != nil {
+ // Align to 8-byte boundary
+ proto_size = (protocol.?.size + 7)/8 * 8
+ }
+
+ cls := objc_lookUpClass(class_name)
+ if cls != nil {
+ return cls
+ }
+
+ extra_size := uint(size_of(Class_VTable_Info)) + 8 + super_size + proto_size
+
+ cls = objc_allocateClassPair(superclass, class_name, extra_size)
+ assert(cls != nil)
+
+ if s, ok := superclass_overrides.?; ok {
+ s.impl(cls, s.vtable)
+ }
+
+ if p, ok := protocol.?; ok {
+ p.impl(cls, p.vtable)
+ }
+
+ objc_registerClassPair(cls)
+ meta_cls := class_get_metaclass(cls)
+ meta_size := uint(class_getInstanceSize(meta_cls))
+
+ // Offsets are always aligned to 8-byte boundary
+ info_offset := (meta_size + 7) / 8 * 8
+ super_vtable_offset := (info_offset + size_of(Class_VTable_Info) + 7) / 8 * 8
+ ptoto_vtable_offset := super_vtable_offset + super_size
+
+
+ p_info := (^Class_VTable_Info)(([^]u8)(cls)[info_offset:])
+ p_super_vtable := ([^]u8)(cls)[super_vtable_offset:]
+ p_proto_vtable := ([^]u8)(cls)[ptoto_vtable_offset:]
+
+ intrinsics.mem_zero(p_info, size_of(Class_VTable_Info))
+
+ // Assign the context
+ p_info._context = _context.? or_else context
+
+ if s, ok := superclass_overrides.?; ok {
+ p_info.super_vtable = p_super_vtable
+ intrinsics.mem_copy(p_super_vtable, s.vtable, super_size)
+ }
+ if p, ok := protocol.?; ok {
+ p_info.protocol_vtable = p_proto_vtable
+ intrinsics.mem_copy(p_proto_vtable, p.vtable, p.size)
+ }
+
+ return cls
+}
+
+@(require_results)
+class_get_vtable_info :: proc "contextless" (cls: Class) -> ^Class_VTable_Info {
+ meta_cls := class_get_metaclass(cls)
+ meta_size := uint(class_getInstanceSize(meta_cls))
+
+ // Align to 8-byte boundary
+ info_offset := (meta_size+7) / 8 * 8
+
+ p_cls := ([^]u8)(cls)[info_offset:]
+ ctx := (^Class_VTable_Info)(p_cls)
+ return ctx
+}
+
+@(require_results)
+alloc_user_object :: proc "contextless" (cls: Class, _context: Maybe(runtime.Context) = nil) -> id {
+ info := class_get_vtable_info(cls)
+
+ obj := class_createInstance(cls, size_of(Class_VTable_Info))
+ obj_info := (^Class_VTable_Info)(object_getIndexedIvars(obj))
+ obj_info^ = info^
+
+ if _context != nil {
+ obj_info._context = _context.?
+ }
+ return obj
+}
\ No newline at end of file
diff --git a/core/sys/darwin/copyfile.odin b/core/sys/darwin/copyfile.odin
new file mode 100644
index 000000000..6c58b8067
--- /dev/null
+++ b/core/sys/darwin/copyfile.odin
@@ -0,0 +1,67 @@
+package darwin
+
+import "core:sys/posix"
+
+copyfile_state_t :: distinct rawptr
+
+copyfile_flags :: bit_set[enum {
+ ACL,
+ STAT,
+ XATTR,
+ DATA,
+
+ RECURSIVE = 15,
+
+ CHECK,
+ EXCL,
+ NOFOLLOW_SRC,
+ NOFOLLOW_DST,
+ MOVE,
+ UNLINK,
+ PACK,
+ UNPACK,
+
+ CLONE,
+ CLONE_FORCE,
+ RUN_IN_PLACE,
+ DATA_SPARSE,
+ PRESERVE_DST_TRACKED,
+ VERBOSE = 30,
+}; u32]
+
+COPYFILE_SECURITY :: copyfile_flags{.STAT, .ACL}
+COPYFILE_METADATA :: COPYFILE_SECURITY + copyfile_flags{.XATTR}
+COPYFILE_ALL :: COPYFILE_METADATA + copyfile_flags{.DATA}
+
+COPYFILE_NOFOLLOW :: copyfile_flags{.NOFOLLOW_SRC, .NOFOLLOW_DST}
+
+copyfile_state_flag :: enum u32 {
+ SRC_FD = 1,
+ SRC_FILENAME,
+ DST_FD,
+ DST_FILENAME,
+ QUARANTINE,
+ STATUS_CB,
+ STATUS_CTX,
+ COPIED,
+ XATTRNAME,
+ WAS_CLONED,
+ SRC_BSIZE,
+ DST_BSIZE,
+ BSIZE,
+ FORBID_CROSS_MOUNT,
+ NOCPROTECT,
+ PRESERVE_SUID,
+ RECURSIVE_SRC_FTSENT,
+ FORBID_DST_EXISTING_SYMLINKS,
+}
+
+foreign system {
+ copyfile :: proc(from, to: cstring, state: copyfile_state_t, flags: copyfile_flags) -> i32 ---
+ fcopyfile :: proc(from, to: posix.FD, state: copyfile_state_t, flags: copyfile_flags) -> i32 ---
+
+ copyfile_state_alloc :: proc() -> copyfile_state_t ---
+ copyfile_state_free :: proc(state: copyfile_state_t) -> posix.result ---
+ copyfile_state_get :: proc(state: copyfile_state_t, flag: copyfile_state_flag, dst: rawptr) -> posix.result ---
+ copyfile_state_set :: proc(state: copyfile_state_t, flag: copyfile_state_flag, src: rawptr) -> posix.result ---
+}
diff --git a/core/sys/darwin/darwin.odin b/core/sys/darwin/darwin.odin
index d109f5544..96cfc7be6 100644
--- a/core/sys/darwin/darwin.odin
+++ b/core/sys/darwin/darwin.odin
@@ -3,6 +3,7 @@ package darwin
import "core:c"
+@(export)
foreign import system "system:System.framework"
Bool :: b8
diff --git a/core/sys/darwin/sync.odin b/core/sys/darwin/sync.odin
index 58fc7c9e4..6d68dc8f8 100644
--- a/core/sys/darwin/sync.odin
+++ b/core/sys/darwin/sync.odin
@@ -1,7 +1,5 @@
package darwin
-foreign import system "system:System.framework"
-
// #define OS_WAIT_ON_ADDR_AVAILABILITY \
// __API_AVAILABLE(macos(14.4), ios(17.4), tvos(17.4), watchos(10.4))
when ODIN_OS == .Darwin {
diff --git a/core/sys/darwin/xnu_system_call_wrappers.odin b/core/sys/darwin/xnu_system_call_wrappers.odin
index 1188091a9..6376949f4 100644
--- a/core/sys/darwin/xnu_system_call_wrappers.odin
+++ b/core/sys/darwin/xnu_system_call_wrappers.odin
@@ -19,16 +19,6 @@ X_OK :: c.int((1 << 0)) /* test for execute or search permission */
W_OK :: c.int((1 << 1)) /* test for write permission */
R_OK :: c.int((1 << 2)) /* test for read permission */
-/* copyfile flags */
-COPYFILE_ACL :: (1 << 0)
-COPYFILE_STAT :: (1 << 1)
-COPYFILE_XATTR :: (1 << 2)
-COPYFILE_DATA :: (1 << 3)
-
-COPYFILE_SECURITY :: (COPYFILE_STAT | COPYFILE_ACL)
-COPYFILE_METADATA :: (COPYFILE_SECURITY | COPYFILE_XATTR)
-COPYFILE_ALL :: (COPYFILE_METADATA | COPYFILE_DATA)
-
/* syslimits.h */
PATH_MAX :: 1024 /* max bytes in pathname */
diff --git a/core/sys/info/platform_linux.odin b/core/sys/info/platform_linux.odin
index 45efc3329..9c342e567 100644
--- a/core/sys/info/platform_linux.odin
+++ b/core/sys/info/platform_linux.odin
@@ -16,9 +16,13 @@ init_os_version :: proc () {
b := strings.builder_from_bytes(version_string_buf[:])
// Try to parse `/etc/os-release` for `PRETTY_NAME="Ubuntu 20.04.3 LTS`
- {
+ pretty_parse: {
fd, errno := linux.open("/etc/os-release", {})
- assert(errno == .NONE, "Failed to read /etc/os-release")
+ if errno != .NONE {
+ strings.write_string(&b, "Unknown Linux Distro")
+ break pretty_parse
+ }
+
defer {
cerrno := linux.close(fd)
assert(cerrno == .NONE, "Failed to close the file descriptor")
@@ -26,7 +30,10 @@ init_os_version :: proc () {
os_release_buf: [2048]u8
n, read_errno := linux.read(fd, os_release_buf[:])
- assert(read_errno == .NONE, "Failed to read data from /etc/os-release")
+ if read_errno != .NONE {
+ strings.write_string(&b, "Unknown Linux Distro")
+ break pretty_parse
+ }
release := string(os_release_buf[:n])
// Search the line in the file until we find "PRETTY_NAME="
@@ -59,7 +66,7 @@ init_os_version :: proc () {
os_version.as_string = strings.to_string(b)
// Parse the Linux version out of the release string
- {
+ version_loop: {
version_num, _, version_suffix := strings.partition(release_str, "-")
os_version.version = version_suffix
@@ -72,11 +79,11 @@ init_os_version :: proc () {
case 0: dst = &os_version.major
case 1: dst = &os_version.minor
case 2: dst = &os_version.patch
- case: break
+ case: break version_loop
}
num, ok := strconv.parse_int(part)
- if !ok { break }
+ if !ok { break version_loop }
dst^ = num
}
diff --git a/core/sys/linux/bits.odin b/core/sys/linux/bits.odin
index 53660dc8f..d4edf354b 100644
--- a/core/sys/linux/bits.odin
+++ b/core/sys/linux/bits.odin
@@ -579,7 +579,7 @@ Inotify_Event_Bits :: enum u32 {
/*
Bits for Mem_Protection bitfield
*/
-Mem_Protection_Bits :: enum{
+Mem_Protection_Bits :: enum {
READ = 0,
WRITE = 1,
EXEC = 2,
@@ -594,11 +594,13 @@ Mem_Protection_Bits :: enum{
/*
Bits for Map_Flags
+
+ See `constants.odin` for `MAP_SHARED_VALIDATE` and `MAP_HUGE_16KB`, et al.
*/
Map_Flags_Bits :: enum {
SHARED = 0,
PRIVATE = 1,
- SHARED_VALIDATE = 2,
+ DROPPABLE = 3,
FIXED = 4,
ANONYMOUS = 5,
// platform-dependent section start
diff --git a/core/sys/linux/constants.odin b/core/sys/linux/constants.odin
index b3bbcafb3..1010c931a 100644
--- a/core/sys/linux/constants.odin
+++ b/core/sys/linux/constants.odin
@@ -373,3 +373,22 @@ PTRACE_SECCOMP_GET_FILTER :: PTrace_Seccomp_Get_Filter_Type(.SECCOMP_GET_FIL
PTRACE_SECCOMP_GET_METADATA :: PTrace_Seccomp_Get_Metadata_Type(.SECCOMP_GET_METADATA)
PTRACE_GET_SYSCALL_INFO :: PTrace_Get_Syscall_Info_Type(.GET_SYSCALL_INFO)
PTRACE_GET_RSEQ_CONFIGURATION :: PTrace_Get_RSeq_Configuration_Type(.GET_RSEQ_CONFIGURATION)
+
+MAP_SHARED_VALIDATE :: Map_Flags{.SHARED, .PRIVATE}
+
+MAP_HUGE_SHIFT :: 26
+MAP_HUGE_MASK :: 63
+
+MAP_HUGE_16KB :: transmute(Map_Flags)(u32(14) << MAP_HUGE_SHIFT)
+MAP_HUGE_64KB :: transmute(Map_Flags)(u32(16) << MAP_HUGE_SHIFT)
+MAP_HUGE_512KB :: transmute(Map_Flags)(u32(19) << MAP_HUGE_SHIFT)
+MAP_HUGE_1MB :: transmute(Map_Flags)(u32(20) << MAP_HUGE_SHIFT)
+MAP_HUGE_2MB :: transmute(Map_Flags)(u32(21) << MAP_HUGE_SHIFT)
+MAP_HUGE_8MB :: transmute(Map_Flags)(u32(23) << MAP_HUGE_SHIFT)
+MAP_HUGE_16MB :: transmute(Map_Flags)(u32(24) << MAP_HUGE_SHIFT)
+MAP_HUGE_32MB :: transmute(Map_Flags)(u32(25) << MAP_HUGE_SHIFT)
+MAP_HUGE_256MB :: transmute(Map_Flags)(u32(28) << MAP_HUGE_SHIFT)
+MAP_HUGE_512MB :: transmute(Map_Flags)(u32(29) << MAP_HUGE_SHIFT)
+MAP_HUGE_1GB :: transmute(Map_Flags)(u32(30) << MAP_HUGE_SHIFT)
+MAP_HUGE_2GB :: transmute(Map_Flags)(u32(31) << MAP_HUGE_SHIFT)
+MAP_HUGE_16GB :: transmute(Map_Flags)(u32(34) << MAP_HUGE_SHIFT)
\ No newline at end of file
diff --git a/core/sys/linux/types.odin b/core/sys/linux/types.odin
index 8f2284f56..08e0026d3 100644
--- a/core/sys/linux/types.odin
+++ b/core/sys/linux/types.odin
@@ -288,7 +288,7 @@ Rename_Flags :: bit_set[Rename_Flags_Bits; u32]
/*
Directory entry record.
- Recommended iterate these with `dirent_iterator()`,
+ Recommended to iterate these with `dirent_iterate_buf()`,
and obtain the name via `dirent_name()`.
*/
Dirent :: struct {
@@ -368,6 +368,8 @@ Mem_Protection :: bit_set[Mem_Protection_Bits; i32]
/*
Flags for mmap.
+
+ See `constants.odin` for `MAP_SHARED_VALIDATE` and `MAP_HUGE_16KB`, et al.
*/
Map_Flags :: bit_set[Map_Flags_Bits; i32]
diff --git a/core/sys/linux/wrappers.odin b/core/sys/linux/wrappers.odin
index ab1992a57..53eb80f86 100644
--- a/core/sys/linux/wrappers.odin
+++ b/core/sys/linux/wrappers.odin
@@ -54,22 +54,45 @@ WCOREDUMP :: #force_inline proc "contextless" (s: u32) -> bool {
// TODO: sigaddset etc
-/// Iterate the results of getdents
-/// Only iterates as much data as loaded in the buffer
-/// In case you need to iterate *all* files in a directory
-/// consider using dirent_get_iterate
-///
-/// Example of using dirent_iterate_buf
-/// // Get dirents into a buffer
-/// buf: [128]u8
-/// sys.getdents(dirfd, buf[:])
-/// // Print the names of the files
-/// for dir in sys.dirent_iterate_buf(buf[:], &offs) {
-/// name := sys.dirent_name(dir)
-/// fmt.println(name)
-/// }
-/// This function doesn't automatically make a request
-/// for the buffer to be refilled
+/*
+Iterate the results of `getdents()`.
+
+This procedure extracts a directory entry from `buf` at the offset `offs`.
+`offs` will be modified to store an offset to the possible next directory entry
+in `buf`. The procedure only iterates as much data as loaded in the buffer and
+does not automatically make a request for the buffer to be refilled.
+
+Inputs:
+- buf: A byte buffer with data from `getdents()`
+- offs: An offset to the next possible directory entry in `buf`
+
+Returns:
+- A pointer to a directory entry in `buf`, or `nil`
+- A bool value denoting if a valid directory entry is returned
+
+Example:
+
+ import "core:fmt"
+ import "core:sys/linux"
+
+ print_names :: proc(dirfd: linux.Fd) {
+ // Get dirents into a buffer.
+ buf: [128]u8
+ // Loop until there are no more entries.
+ for {
+ written, err := linux.getdents(dirfd, buf[:])
+ if err != .NONE || written == 0 {
+ break
+ }
+ // Print the names of the files.
+ offset : int
+ for dir in linux.dirent_iterate_buf(buf[:written], &offset) {
+ name := linux.dirent_name(dir)
+ fmt.println(name)
+ }
+ }
+ }
+*/
dirent_iterate_buf :: proc "contextless" (buf: []u8, offs: ^int) -> (d: ^Dirent, cont: bool) {
// Stopped iterating when there's no space left
if offs^ >= len(buf) {
@@ -82,8 +105,17 @@ dirent_iterate_buf :: proc "contextless" (buf: []u8, offs: ^int) -> (d: ^Dirent,
return dirent, true
}
-/// Obtain the name of dirent as a string
-/// The lifetime of the string is bound to the lifetime of the provided dirent structure
+/*
+Obtain the name of dirent as a string.
+
+The lifetime of the returned string is bound to the lifetime of the provided dirent structure.
+
+Inputs:
+- dirent: A directory entry
+
+Returns:
+- A name of the entry
+*/
dirent_name :: proc "contextless" (dirent: ^Dirent) -> string #no_bounds_check {
str := ([^]u8)(&dirent.name)
// Dirents are aligned to 8 bytes, so there is guaranteed to be a null
@@ -93,10 +125,10 @@ dirent_name :: proc "contextless" (dirent: ^Dirent) -> string #no_bounds_check {
trunc := min(str_size, 8)
str_size -= trunc
for _ in 0.. HBRUSH ---
CreateDIBitmap :: proc(hdc: HDC, pbmih: ^BITMAPINFOHEADER, flInit: DWORD, pjBits: VOID, pbmi: ^BITMAPINFO, iUsage: UINT) -> HBITMAP ---
- CreateDIBSection :: proc(hdc: HDC, pbmi: ^BITMAPINFO, usage: UINT, ppvBits: VOID, hSection: HANDLE, offset: DWORD) -> HBITMAP ---
+ CreateDIBSection :: proc(hdc: HDC, pbmi: ^BITMAPINFO, usage: UINT, ppvBits: ^^VOID, hSection: HANDLE, offset: DWORD) -> HBITMAP ---
StretchDIBits :: proc(hdc: HDC, xDest, yDest, DestWidth, DestHeight, xSrc, ySrc, SrcWidth, SrcHeight: INT, lpBits: VOID, lpbmi: ^BITMAPINFO, iUsage: UINT, rop: DWORD) -> INT ---
StretchBlt :: proc(hdcDest: HDC, xDest, yDest, wDest, hDest: INT, hdcSrc: HDC, xSrc, ySrc, wSrc, hSrc: INT, rop: DWORD) -> BOOL ---
diff --git a/core/sys/windows/scan_codes.odin b/core/sys/windows/scan_codes.odin
new file mode 100644
index 000000000..54949c2f6
--- /dev/null
+++ b/core/sys/windows/scan_codes.odin
@@ -0,0 +1,172 @@
+#+build windows
+package sys_windows
+
+// Win32 scan codes for QWERTY layout
+// https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#scan-codes
+
+KB_SYS_POWERDOWN :: 0xE05E
+KB_SYS_SLEEP :: 0xE05F
+KB_SYS_WAKEUP :: 0xE063
+KB_ERR_ROLLOVER :: 0x00FF
+
+KB_A :: 0x001E
+KB_B :: 0x0030
+KB_C :: 0x002E
+KB_D :: 0x0020
+KB_E :: 0x0012
+KB_F :: 0x0021
+KB_G :: 0x0022
+KB_H :: 0x0023
+KB_I :: 0x0017
+KB_J :: 0x0024
+KB_K :: 0x0025
+KB_L :: 0x0026
+KB_M :: 0x0032
+KB_N :: 0x0031
+KB_O :: 0x0018
+KB_P :: 0x0019
+KB_Q :: 0x0010
+KB_R :: 0x0013
+KB_S :: 0x001F
+KB_T :: 0x0014
+KB_U :: 0x0016
+KB_V :: 0x002F
+KB_W :: 0x0011
+KB_X :: 0x002D
+KB_Y :: 0x0015
+KB_Z :: 0x002C
+
+KB_1_BANG :: 0x0002
+KB_2_AT :: 0x0003
+KB_3_HASH :: 0x0004
+KB_4_DOLLAR :: 0x0005
+KB_5_PERCENT :: 0x0006
+KB_6_CARET :: 0x0007
+KB_7_AMPERSAND :: 0x0008
+KB_8_STAR :: 0x0009
+KB_9_LEFTBRACKET :: 0x000A
+KB_0_RIGHTBRACKET :: 0x000B
+
+KB_RETURN_ENTER :: 0x001C
+KB_ESCAPE :: 0x0001
+KB_DELETE :: 0x000E
+KB_TAB :: 0x000F
+KB_SPACEBAR :: 0x0039
+KB_DASH_UNDERSCORE :: 0x000C
+KB_EQUALS_PLUS :: 0x000D
+KB_LEFTBRACE :: 0x001A
+KB_RIGHTBRACE :: 0x001B
+KB_PIPE_SLASH :: 0x002B
+KB_NONUS :: 0x002B
+KB_SEMICOLON_COLON :: 0x0027
+KB_APOSTR_DOUBLEQUOT :: 0x0028
+KB_GRAVEACC_TILDE :: 0x0029
+KB_COMMA :: 0x0033
+KB_PERIOD :: 0x0034
+KB_QUESTIONMARK :: 0x0035
+KB_CAPSLOCK :: 0x003A
+
+KB_F1 :: 0x003B
+KB_F2 :: 0x003C
+KB_F3 :: 0x003D
+KB_F4 :: 0x003E
+KB_F5 :: 0x003F
+KB_F6 :: 0x0040
+KB_F7 :: 0x0041
+KB_F8 :: 0x0042
+KB_F9 :: 0x0043
+KB_F10 :: 0x0044
+KB_F11 :: 0x0057
+KB_F12 :: 0x0058
+
+KB_PRINTSCREEN :: 0xE037
+KB_SCROLLLOCK :: 0x0046
+KB_PAUSE :: 0xE11D45
+KB_INSERT :: 0xE052
+KB_HOME :: 0xE047
+KB_PAGEUP :: 0xE049
+KB_DELETEFORWARD :: 0xE053
+KB_END :: 0xE04F
+KB_PAGEDOWN :: 0xE051
+KB_RIGHTARROW :: 0xE04D
+KB_LEFTARROW :: 0xE04B
+KB_DOWNARROW :: 0xE050
+KB_UPARROW :: 0xE048
+
+KP_NUMLOCK_CLEAR :: 0x0045
+KP_FORWARDSLASH :: 0xE035
+KP_STAR :: 0x0037
+KP_DASH :: 0x004A
+KP_PLUS :: 0x004E
+KP_ENTER :: 0xE01C
+KP_1_END :: 0x004F
+KP_2_DOWNARROW :: 0x0050
+KP_3_PAGEDN :: 0x0051
+KP_4_LEFTARROW :: 0x004B
+KP_5 :: 0x004C
+KP_6_RIGHTARROW :: 0x004D
+KP_7_HOME :: 0x0047
+KP_8_UPARROW :: 0x0048
+KP_9_PAGEUP :: 0x0049
+KP_0_INSERT :: 0x0052
+KP_PERIOD :: 0x0053
+
+KB_NONUS_SLASHBAR :: 0x0056
+KB_APPLICATION :: 0xE05D
+KB_POWER :: 0xE05E
+KB_EQUALS :: 0x0059
+KB_F13 :: 0x0064
+KB_F14 :: 0x0065
+KB_F15 :: 0x0066
+KB_F16 :: 0x0067
+KB_F17 :: 0x0068
+KB_F18 :: 0x0069
+KB_F19 :: 0x006A
+KB_F20 :: 0x006B
+KB_F21 :: 0x006C
+KB_F22 :: 0x006D
+KB_F23 :: 0x006E
+KB_F24 :: 0x0076
+
+KP_COMMA :: 0x007E
+
+KB_INTERNATIONAL1 :: 0x0073
+KB_INTERNATIONAL2 :: 0x0070
+KB_INTERNATIONAL3 :: 0x007D
+KB_INTERNATIONAL4 :: 0x0079
+KB_INTERNATIONAL5 :: 0x007B
+KB_INTERNATIONAL6 :: 0x005C
+
+KB_LANG1 :: 0x0072
+KB_LANG2 :: 0x0071
+KB_LANG3 :: 0x0078
+KB_LANG4 :: 0x0077
+KB_LANG5 :: 0x0076
+
+KB_LEFTCONTROL :: 0x001D
+KB_LEFTSHIFT :: 0x002A
+KB_LEFTALT :: 0x0038
+KB_LEFTGUI :: 0xE05B
+KB_RIGHTCONTROL :: 0xE01D
+KB_RIGHTSHIFT :: 0x0036
+KB_RIGHTALT :: 0xE038
+KB_RIGHTGUI :: 0xE05C
+
+FN_SCANNEXTTRACK :: 0xE019
+FN_SCANPREVTRACK :: 0xE010
+FN_STOP :: 0xE024
+FN_PLAY_PAUSE :: 0xE022
+FN_MUTE :: 0xE020
+FN_VOLUMEINC :: 0xE030
+FN_VOLUMEDEC :: 0xE02E
+FN_AL_CONSUMERCTRLCONFIG :: 0xE06D
+FN_AL_EMAILREADER :: 0xE06C
+FN_AL_CALCULATOR :: 0xE021
+FN_AL_LOCALMACHINEBROWSER :: 0xE06B
+FN_AC_SEARCH :: 0xE065
+FN_AC_HOME :: 0xE032
+FN_AC_BACK :: 0xE06A
+FN_AC_FORWARD :: 0xE069
+FN_AC_STOP :: 0xE068
+FN_AC_REFRESH :: 0xE067
+FN_AC_BOOKMARKS :: 0xE066
diff --git a/core/sys/windows/user32.odin b/core/sys/windows/user32.odin
index 94cd57811..49ebb49cb 100644
--- a/core/sys/windows/user32.odin
+++ b/core/sys/windows/user32.odin
@@ -47,6 +47,8 @@ foreign user32 {
lpParam: LPVOID,
) -> HWND ---
+ GetWindowThreadProcessId :: proc(hwnd: HWND, lpdwProcessId: LPDWORD) -> DWORD ---
+
DestroyWindow :: proc(hWnd: HWND) -> BOOL ---
ShowWindow :: proc(hWnd: HWND, nCmdShow: INT) -> BOOL ---
diff --git a/core/sys/windows/ws2_32.odin b/core/sys/windows/ws2_32.odin
index 5b2952495..d808bcd09 100644
--- a/core/sys/windows/ws2_32.odin
+++ b/core/sys/windows/ws2_32.odin
@@ -91,6 +91,7 @@ foreign ws2_32 {
WSACleanup :: proc() -> c_int ---
// [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsagetlasterror)
WSAGetLastError :: proc() -> c_int ---
+ WSASetLastError :: proc(err: c_int) ---
// [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsapoll)
WSAPoll :: proc(fdArray: ^WSA_POLLFD, fds: c_ulong, timeout: c_int) -> c_int ---
// [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsaduplicatesocketw)
diff --git a/core/encoding/ansi/ansi.odin b/core/terminal/ansi/ansi.odin
similarity index 100%
rename from core/encoding/ansi/ansi.odin
rename to core/terminal/ansi/ansi.odin
diff --git a/core/encoding/ansi/doc.odin b/core/terminal/ansi/doc.odin
similarity index 100%
rename from core/encoding/ansi/doc.odin
rename to core/terminal/ansi/doc.odin
diff --git a/core/terminal/doc.odin b/core/terminal/doc.odin
new file mode 100644
index 000000000..490e9d398
--- /dev/null
+++ b/core/terminal/doc.odin
@@ -0,0 +1,4 @@
+/*
+This package is for interacting with the command line interface of the system.
+*/
+package terminal
diff --git a/core/terminal/internal.odin b/core/terminal/internal.odin
new file mode 100644
index 000000000..485f6868d
--- /dev/null
+++ b/core/terminal/internal.odin
@@ -0,0 +1,87 @@
+#+private
+package terminal
+
+import "core:os"
+import "core:strings"
+
+// Reference documentation:
+//
+// - [[ https://no-color.org/ ]]
+// - [[ https://github.com/termstandard/colors ]]
+// - [[ https://invisible-island.net/ncurses/terminfo.src.html ]]
+
+get_no_color :: proc() -> bool {
+ if no_color, ok := os.lookup_env("NO_COLOR"); ok {
+ defer delete(no_color)
+ return no_color != ""
+ }
+ return false
+}
+
+get_environment_color :: proc() -> Color_Depth {
+ // `COLORTERM` is non-standard but widespread and unambiguous.
+ if colorterm, ok := os.lookup_env("COLORTERM"); ok {
+ defer delete(colorterm)
+ // These are the only values that are typically advertised that have
+ // anything to do with color depth.
+ if colorterm == "truecolor" || colorterm == "24bit" {
+ return .True_Color
+ }
+ }
+
+ if term, ok := os.lookup_env("TERM"); ok {
+ defer delete(term)
+ if strings.contains(term, "-truecolor") {
+ return .True_Color
+ }
+ if strings.contains(term, "-256color") {
+ return .Eight_Bit
+ }
+ if strings.contains(term, "-16color") {
+ return .Four_Bit
+ }
+
+ // The `terminfo` database, which is stored in binary on *nix
+ // platforms, has an undocumented format that is not guaranteed to be
+ // portable, so beyond this point, we can only make safe assumptions.
+ //
+ // This section should only be necessary for terminals that do not
+ // define any of the previous environment values.
+ //
+ // Only a small sampling of some common values are checked here.
+ switch term {
+ case "ansi": fallthrough
+ case "konsole": fallthrough
+ case "putty": fallthrough
+ case "rxvt": fallthrough
+ case "rxvt-color": fallthrough
+ case "screen": fallthrough
+ case "st": fallthrough
+ case "tmux": fallthrough
+ case "vte": fallthrough
+ case "xterm": fallthrough
+ case "xterm-color":
+ return .Three_Bit
+ }
+ }
+
+ return .None
+}
+
+@(init)
+init_terminal :: proc() {
+ _init_terminal()
+
+ // We respect `NO_COLOR` specifically as a color-disabler but not as a
+ // blanket ban on any terminal manipulation codes, hence why this comes
+ // after `_init_terminal` which will allow Windows to enable Virtual
+ // Terminal Processing for non-color control sequences.
+ if !get_no_color() {
+ color_enabled = color_depth > .None
+ }
+}
+
+@(fini)
+fini_terminal :: proc() {
+ _fini_terminal()
+}
diff --git a/core/terminal/terminal.odin b/core/terminal/terminal.odin
new file mode 100644
index 000000000..1e5566295
--- /dev/null
+++ b/core/terminal/terminal.odin
@@ -0,0 +1,36 @@
+package terminal
+
+import "core:os"
+
+/*
+This describes the range of colors that a terminal is capable of supporting.
+*/
+Color_Depth :: enum {
+ None, // No color support
+ Three_Bit, // 8 colors
+ Four_Bit, // 16 colors
+ Eight_Bit, // 256 colors
+ True_Color, // 24-bit true color
+}
+
+/*
+Returns true if the file `handle` is attached to a terminal.
+
+This is normally true for `os.stdout` and `os.stderr` unless they are
+redirected to a file.
+*/
+@(require_results)
+is_terminal :: proc(handle: os.Handle) -> bool {
+ return _is_terminal(handle)
+}
+
+/*
+This is true if the terminal is accepting any form of colored text output.
+*/
+color_enabled: bool
+
+/*
+This value reports the color depth support as reported by the terminal at the
+start of the program.
+*/
+color_depth: Color_Depth
diff --git a/core/terminal/terminal_posix.odin b/core/terminal/terminal_posix.odin
new file mode 100644
index 000000000..f578e12c6
--- /dev/null
+++ b/core/terminal/terminal_posix.odin
@@ -0,0 +1,16 @@
+#+private
+#+build linux, darwin, netbsd, openbsd, freebsd, haiku
+package terminal
+
+import "core:os"
+import "core:sys/posix"
+
+_is_terminal :: proc(handle: os.Handle) -> bool {
+ return bool(posix.isatty(posix.FD(handle)))
+}
+
+_init_terminal :: proc() {
+ color_depth = get_environment_color()
+}
+
+_fini_terminal :: proc() { }
diff --git a/core/terminal/terminal_windows.odin b/core/terminal/terminal_windows.odin
new file mode 100644
index 000000000..18ec98332
--- /dev/null
+++ b/core/terminal/terminal_windows.odin
@@ -0,0 +1,60 @@
+#+private
+package terminal
+
+import "core:os"
+import "core:sys/windows"
+
+_is_terminal :: proc(handle: os.Handle) -> bool {
+ is_tty := windows.GetFileType(windows.HANDLE(handle)) == windows.FILE_TYPE_CHAR
+ return is_tty
+}
+
+old_modes: [2]struct{
+ handle: windows.DWORD,
+ mode: windows.DWORD,
+} = {
+ {windows.STD_OUTPUT_HANDLE, 0},
+ {windows.STD_ERROR_HANDLE, 0},
+}
+
+@(init)
+_init_terminal :: proc() {
+ vtp_enabled: bool
+
+ for &v in old_modes {
+ handle := windows.GetStdHandle(v.handle)
+ if handle == windows.INVALID_HANDLE || handle == nil {
+ return
+ }
+ if windows.GetConsoleMode(handle, &v.mode) {
+ windows.SetConsoleMode(handle, v.mode | windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
+
+ new_mode: windows.DWORD
+ windows.GetConsoleMode(handle, &new_mode)
+
+ if new_mode & (windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0 {
+ vtp_enabled = true
+ }
+ }
+ }
+
+ if vtp_enabled {
+ // This color depth is available on Windows 10 since build 10586.
+ color_depth = .Four_Bit
+ } else {
+ // The user may be on a non-default terminal emulator.
+ color_depth = get_environment_color()
+ }
+}
+
+@(fini)
+_fini_terminal :: proc() {
+ for v in old_modes {
+ handle := windows.GetStdHandle(v.handle)
+ if handle == windows.INVALID_HANDLE || handle == nil {
+ return
+ }
+
+ windows.SetConsoleMode(handle, v.mode)
+ }
+}
diff --git a/core/testing/reporting.odin b/core/testing/reporting.odin
index 6752cd79b..7c7eb7b2d 100644
--- a/core/testing/reporting.odin
+++ b/core/testing/reporting.odin
@@ -10,12 +10,12 @@ package testing
*/
import "base:runtime"
-import "core:encoding/ansi"
import "core:fmt"
import "core:io"
import "core:mem"
import "core:path/filepath"
import "core:strings"
+import "core:terminal/ansi"
// Definitions of colors for use in the test runner.
SGR_RESET :: ansi.CSI + ansi.RESET + ansi.SGR
diff --git a/core/testing/runner.odin b/core/testing/runner.odin
index 83a5ac4e7..56d561d3d 100644
--- a/core/testing/runner.odin
+++ b/core/testing/runner.odin
@@ -13,7 +13,6 @@ package testing
import "base:intrinsics"
import "base:runtime"
import "core:bytes"
-import "core:encoding/ansi"
@require import "core:encoding/base64"
@require import "core:encoding/json"
import "core:fmt"
@@ -25,6 +24,8 @@ import "core:os"
import "core:slice"
@require import "core:strings"
import "core:sync/chan"
+import "core:terminal"
+import "core:terminal/ansi"
import "core:thread"
import "core:time"
@@ -44,6 +45,7 @@ PER_THREAD_MEMORY : int : #config(ODIN_TEST_THREAD_MEMORY, mem.ROLLBACK_S
// The format is: `package.test_name,test_name_only,...`
TEST_NAMES : string : #config(ODIN_TEST_NAMES, "")
// Show the fancy animated progress report.
+// This requires terminal color support, as well as STDOUT to not be redirected to a file.
FANCY_OUTPUT : bool : #config(ODIN_TEST_FANCY, true)
// Copy failed tests to the clipboard when done.
USE_CLIPBOARD : bool : #config(ODIN_TEST_CLIPBOARD, false)
@@ -70,6 +72,9 @@ get_log_level :: #force_inline proc() -> runtime.Logger_Level {
}
}
+@(private) global_log_colors_disabled: bool
+@(private) global_ansi_disabled: bool
+
JSON :: struct {
total: int,
success: int,
@@ -129,11 +134,16 @@ run_test_task :: proc(task: thread.Task) {
context.assertion_failure_proc = test_assertion_failure_proc
+ logger_options := Default_Test_Logger_Opts
+ if global_log_colors_disabled {
+ logger_options -= {.Terminal_Color}
+ }
+
context.logger = {
procedure = test_logger_proc,
data = &data.t,
lowest_level = get_log_level(),
- options = Default_Test_Logger_Opts,
+ options = logger_options,
}
random_generator_state: runtime.Default_Random_State
@@ -204,13 +214,16 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
}
}
- when ODIN_OS == .Windows {
- console_ansi_init()
- }
-
stdout := io.to_writer(os.stream_from_handle(os.stdout))
stderr := io.to_writer(os.stream_from_handle(os.stderr))
+ // The animations are only ever shown through STDOUT;
+ // STDERR is used exclusively for logging regardless of error level.
+ global_log_colors_disabled = !terminal.color_enabled || !terminal.is_terminal(os.stderr)
+ global_ansi_disabled = !terminal.is_terminal(os.stdout)
+
+ should_show_animations := FANCY_OUTPUT && terminal.color_enabled && !global_ansi_disabled
+
// -- Prepare test data.
alloc_error: mem.Allocator_Error
@@ -268,12 +281,12 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
total_done_count := 0
total_test_count := len(internal_tests)
- when !FANCY_OUTPUT {
- // This is strictly for updating the window title when the progress
- // report is disabled. We're otherwise able to depend on the call to
- // `needs_to_redraw`.
- last_done_count := -1
- }
+
+ // This is strictly for updating the window title when the progress
+ // report is disabled. We're otherwise able to depend on the call to
+ // `needs_to_redraw`.
+ last_done_count := -1
+
if total_test_count == 0 {
// Exit early.
@@ -342,31 +355,31 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
fmt.assertf(alloc_error == nil, "Error allocating memory for test report: %v", alloc_error)
defer destroy_report(&report)
- when FANCY_OUTPUT {
- // We cannot make use of the ANSI save/restore cursor codes, because they
- // work by absolute screen coordinates. This will cause unnecessary
- // scrollback if we print at the bottom of someone's terminal.
- ansi_redraw_string := fmt.aprintf(
- // ANSI for "go up N lines then erase the screen from the cursor forward."
- ansi.CSI + "%i" + ansi.CPL + ansi.CSI + ansi.ED +
- // We'll combine this with the window title format string, since it
- // can be printed at the same time.
- "%s",
- // 1 extra line for the status bar.
- 1 + len(report.packages), OSC_WINDOW_TITLE)
- assert(len(ansi_redraw_string) > 0, "Error allocating ANSI redraw string.")
- defer delete(ansi_redraw_string)
- thread_count_status_string: string = ---
- {
- PADDING :: PROGRESS_COLUMN_SPACING + PROGRESS_WIDTH
+ // We cannot make use of the ANSI save/restore cursor codes, because they
+ // work by absolute screen coordinates. This will cause unnecessary
+ // scrollback if we print at the bottom of someone's terminal.
+ ansi_redraw_string := fmt.aprintf(
+ // ANSI for "go up N lines then erase the screen from the cursor forward."
+ ansi.CSI + "%i" + ansi.CPL + ansi.CSI + ansi.ED +
+ // We'll combine this with the window title format string, since it
+ // can be printed at the same time.
+ "%s",
+ // 1 extra line for the status bar.
+ 1 + len(report.packages), OSC_WINDOW_TITLE)
+ assert(len(ansi_redraw_string) > 0, "Error allocating ANSI redraw string.")
+ defer delete(ansi_redraw_string)
- unpadded := fmt.tprintf("%i thread%s", thread_count, "" if thread_count == 1 else "s")
- thread_count_status_string = fmt.aprintf("%- *[1]s", unpadded, report.pkg_column_len + PADDING)
- assert(len(thread_count_status_string) > 0, "Error allocating thread count status string.")
- }
- defer delete(thread_count_status_string)
+ thread_count_status_string: string = ---
+ {
+ PADDING :: PROGRESS_COLUMN_SPACING + PROGRESS_WIDTH
+
+ unpadded := fmt.tprintf("%i thread%s", thread_count, "" if thread_count == 1 else "s")
+ thread_count_status_string = fmt.aprintf("%- *[1]s", unpadded, report.pkg_column_len + PADDING)
+ assert(len(thread_count_status_string) > 0, "Error allocating thread count status string.")
}
+ defer delete(thread_count_status_string)
+
task_data_slots: []Task_Data = ---
task_data_slots, alloc_error = make([]Task_Data, thread_count)
@@ -442,11 +455,16 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
// digging through the source to divine everywhere it is used for that.
shared_log_allocator := context.allocator
+ logger_options := Default_Test_Logger_Opts - {.Short_File_Path, .Line, .Procedure}
+ if global_log_colors_disabled {
+ logger_options -= {.Terminal_Color}
+ }
+
context.logger = {
procedure = runner_logger_proc,
data = &log_messages,
lowest_level = get_log_level(),
- options = Default_Test_Logger_Opts - {.Short_File_Path, .Line, .Procedure},
+ options = logger_options,
}
run_index: int
@@ -481,11 +499,13 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
setup_signal_handler()
- fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_HIDE)
+ if !global_ansi_disabled {
+ fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_HIDE)
+ }
- when FANCY_OUTPUT {
- signals_were_raised := false
+ signals_were_raised := false
+ if should_show_animations {
redraw_report(stdout, report)
draw_status_bar(stdout, thread_count_status_string, total_done_count, total_test_count)
}
@@ -703,22 +723,22 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
break main_loop
}
- when FANCY_OUTPUT {
- // Because the bounds checking procs send directly to STDERR with
- // no way to redirect or handle them, we need to at least try to
- // let the user see those messages when using the animated progress
- // report. This flag may be set by the block of code below if a
- // signal is raised.
- //
- // It'll be purely by luck if the output is interleaved properly,
- // given the nature of non-thread-safe printing.
- //
- // At worst, if Odin did not print any error for this signal, we'll
- // just re-display the progress report. The fatal log error message
- // should be enough to clue the user in that something dire has
- // occurred.
- bypass_progress_overwrite := false
- }
+
+ // Because the bounds checking procs send directly to STDERR with
+ // no way to redirect or handle them, we need to at least try to
+ // let the user see those messages when using the animated progress
+ // report. This flag may be set by the block of code below if a
+ // signal is raised.
+ //
+ // It'll be purely by luck if the output is interleaved properly,
+ // given the nature of non-thread-safe printing.
+ //
+ // At worst, if Odin did not print any error for this signal, we'll
+ // just re-display the progress report. The fatal log error message
+ // should be enough to clue the user in that something dire has
+ // occurred.
+ bypass_progress_overwrite := false
+
if test_index, reason, ok := should_stop_test(); ok {
#no_bounds_check report.all_test_states[test_index] = .Failed
@@ -752,7 +772,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
log.fatalf("Caught signal to stop test #%i %s.%s for: %v.", test_index, it.pkg, it.name, reason)
}
- when FANCY_OUTPUT {
+ if should_show_animations {
bypass_progress_overwrite = true
signals_were_raised = true
}
@@ -766,7 +786,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
// -- Redraw.
- when FANCY_OUTPUT {
+ if should_show_animations {
if len(log_messages) == 0 && !needs_to_redraw(report) {
continue main_loop
}
@@ -776,7 +796,9 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
}
} else {
if total_done_count != last_done_count {
- fmt.wprintf(stdout, OSC_WINDOW_TITLE, total_done_count, total_test_count)
+ if !global_ansi_disabled {
+ fmt.wprintf(stdout, OSC_WINDOW_TITLE, total_done_count, total_test_count)
+ }
last_done_count = total_done_count
}
@@ -801,7 +823,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
clear(&log_messages)
bytes.buffer_reset(&batch_buffer)
- when FANCY_OUTPUT {
+ if should_show_animations {
redraw_report(batch_writer, report)
draw_status_bar(batch_writer, thread_count_status_string, total_done_count, total_test_count)
fmt.wprint(stdout, bytes.buffer_to_string(&batch_buffer))
@@ -822,7 +844,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
finished_in := time.since(start_time)
- when !FANCY_OUTPUT {
+ if !should_show_animations || !terminal.is_terminal(os.stderr) {
// One line to space out the results, since we don't have the status
// bar in plain mode.
fmt.wprintln(batch_writer)
@@ -836,24 +858,28 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
if total_done_count != total_test_count {
not_run_count := total_test_count - total_done_count
+ message := " %i %s left undone." if global_log_colors_disabled else " " + SGR_READY + "%i" + SGR_RESET + " %s left undone."
fmt.wprintf(batch_writer,
- " " + SGR_READY + "%i" + SGR_RESET + " %s left undone.",
+ message,
not_run_count,
"test was" if not_run_count == 1 else "tests were")
}
if total_success_count == total_test_count {
+ message := " %s successful." if global_log_colors_disabled else " %s " + SGR_SUCCESS + "successful." + SGR_RESET
fmt.wprintfln(batch_writer,
- " %s " + SGR_SUCCESS + "successful." + SGR_RESET,
+ message,
"The test was" if total_test_count == 1 else "All tests were")
} else if total_failure_count > 0 {
if total_failure_count == total_test_count {
+ message := " %s failed." if global_log_colors_disabled else " %s " + SGR_FAILED + "failed." + SGR_RESET
fmt.wprintfln(batch_writer,
- " %s " + SGR_FAILED + "failed." + SGR_RESET,
+ message,
"The test" if total_test_count == 1 else "All tests")
} else {
+ message := " %i test%s failed." if global_log_colors_disabled else " " + SGR_FAILED + "%i" + SGR_RESET + " test%s failed."
fmt.wprintfln(batch_writer,
- " " + SGR_FAILED + "%i" + SGR_RESET + " test%s failed.",
+ message,
total_failure_count,
"" if total_failure_count == 1 else "s")
}
@@ -907,9 +933,11 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
}
}
- fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_SHOW)
+ if !global_ansi_disabled {
+ fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_SHOW)
+ }
- when FANCY_OUTPUT {
+ if should_show_animations {
if signals_were_raised {
fmt.wprintln(batch_writer, `
Signals were raised during this test run. Log messages are likely to have collided with each other.
diff --git a/core/testing/runner_windows.odin b/core/testing/runner_windows.odin
deleted file mode 100644
index 401804c71..000000000
--- a/core/testing/runner_windows.odin
+++ /dev/null
@@ -1,22 +0,0 @@
-#+private
-package testing
-
-import win32 "core:sys/windows"
-
-console_ansi_init :: proc() {
- stdout := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE)
- if stdout != win32.INVALID_HANDLE && stdout != nil {
- old_console_mode: u32
- if win32.GetConsoleMode(stdout, &old_console_mode) {
- win32.SetConsoleMode(stdout, old_console_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
- }
- }
-
- stderr := win32.GetStdHandle(win32.STD_ERROR_HANDLE)
- if stderr != win32.INVALID_HANDLE && stderr != nil {
- old_console_mode: u32
- if win32.GetConsoleMode(stderr, &old_console_mode) {
- win32.SetConsoleMode(stderr, old_console_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
- }
- }
-}
diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin
index 281fbde40..f9527e22f 100644
--- a/core/testing/signal_handler_libc.odin
+++ b/core/testing/signal_handler_libc.odin
@@ -12,9 +12,9 @@ package testing
import "base:intrinsics"
import "core:c/libc"
-import "core:encoding/ansi"
-import "core:sync"
import "core:os"
+import "core:sync"
+import "core:terminal/ansi"
@(private="file") stop_runner_flag: libc.sig_atomic_t
@@ -63,9 +63,11 @@ stop_test_callback :: proc "c" (sig: libc.int) {
// NOTE(Feoramund): Using these write calls in a signal handler is
// undefined behavior in C99 but possibly tolerated in POSIX 2008.
// Either way, we may as well try to salvage what we can.
- show_cursor := ansi.CSI + ansi.DECTCEM_SHOW
- libc.fwrite(raw_data(show_cursor), size_of(byte), len(show_cursor), libc.stdout)
- libc.fflush(libc.stdout)
+ if !global_ansi_disabled {
+ show_cursor := ansi.CSI + ansi.DECTCEM_SHOW
+ libc.fwrite(raw_data(show_cursor), size_of(byte), len(show_cursor), libc.stdout)
+ libc.fflush(libc.stdout)
+ }
// This is an attempt at being compliant by avoiding printf.
sigbuf: [8]byte
diff --git a/core/text/regex/regex.odin b/core/text/regex/regex.odin
index 8f8efe252..c805740f7 100644
--- a/core/text/regex/regex.odin
+++ b/core/text/regex/regex.odin
@@ -8,6 +8,7 @@ package regex
Feoramund: Initial implementation.
*/
+import "base:runtime"
import "core:text/regex/common"
import "core:text/regex/compiler"
import "core:text/regex/optimizer"
@@ -27,6 +28,8 @@ Creation_Error :: enum {
Expected_Delimiter,
// An unknown letter was supplied to `create_by_user` after the last delimiter.
Unknown_Flag,
+ // An unsupported flag was supplied.
+ Unsupported_Flag,
}
Error :: union #shared_nil {
@@ -51,7 +54,7 @@ This struct corresponds to a set of string captures from a RegEx match.
such that `str[pos[0][0]:pos[0][1]] == groups[0]`.
*/
Capture :: struct {
- pos: [][2]int,
+ pos: [][2]int,
groups: []string,
}
@@ -59,11 +62,22 @@ Capture :: struct {
A compiled Regular Expression value, to be used with the `match_*` procedures.
*/
Regular_Expression :: struct {
- flags: Flags `fmt:"-"`,
+ flags: Flags `fmt:"-"`,
class_data: []virtual_machine.Rune_Class_Data `fmt:"-"`,
- program: []virtual_machine.Opcode `fmt:"-"`,
+ program: []virtual_machine.Opcode `fmt:"-"`,
}
+/*
+An iterator to repeatedly match a pattern against a string, to be used with `*_iterator` procedures.
+Note: Does not handle `.Multiline` properly.
+*/
+Match_Iterator :: struct {
+ regex: Regular_Expression,
+ capture: Capture,
+ vm: virtual_machine.Machine,
+ idx: int,
+ temp: runtime.Allocator,
+}
/*
Create a regular expression from a string pattern and a set of flags.
@@ -245,6 +259,45 @@ create_by_user :: proc(
return create(pattern[start:end], flags, permanent_allocator, temporary_allocator)
}
+/*
+Create a `Match_Iterator` using a string to search, a regular expression to match against it, and a set of flags.
+
+*Allocates Using Provided Allocators*
+
+Inputs:
+- str: The string to iterate over.
+- pattern: The pattern to match.
+- flags: A `bit_set` of RegEx flags.
+- permanent_allocator: The allocator to use for the compiled regular expression. (default: context.allocator)
+- temporary_allocator: The allocator to use for the intermediate compilation and iteration stages. (default: context.temp_allocator)
+
+Returns:
+- result: The `Match_Iterator`.
+- err: An error, if one occurred.
+*/
+create_iterator :: proc(
+ str: string,
+ pattern: string,
+ flags: Flags = {},
+ permanent_allocator := context.allocator,
+ temporary_allocator := context.temp_allocator,
+) -> (result: Match_Iterator, err: Error) {
+ flags := flags
+ flags += {.Global} // We're iterating over a string, so the next match could start anywhere
+
+ if .Multiline in flags {
+ return {}, .Unsupported_Flag
+ }
+
+ result.regex = create(pattern, flags, permanent_allocator, temporary_allocator) or_return
+ result.capture = preallocate_capture()
+ result.temp = temporary_allocator
+ result.vm = virtual_machine.create(result.regex.program, str)
+ result.vm.class_data = result.regex.class_data
+
+ return
+}
+
/*
Match a regular expression against a string and allocate the results into the
returned `capture` structure.
@@ -387,9 +440,72 @@ match_with_preallocated_capture :: proc(
return
}
+/*
+Iterate over a `Match_Iterator` and return successive captures.
+Note: Does not handle `.Multiline` properly.
+
+Inputs:
+- it: Pointer to the `Match_Iterator` to iterate over.
+
+Returns:
+- result: `Capture` for this iteration.
+- ok: A bool indicating if there was a match, stopping the iteration on `false`.
+*/
+match_iterator :: proc(it: ^Match_Iterator) -> (result: Capture, index: int, ok: bool) {
+ assert(len(it.capture.groups) >= common.MAX_CAPTURE_GROUPS,
+ "Pre-allocated RegEx capture `groups` must be at least 10 elements long.")
+ assert(len(it.capture.pos) >= common.MAX_CAPTURE_GROUPS,
+ "Pre-allocated RegEx capture `pos` must be at least 10 elements long.")
+
+ runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
+
+ saved: ^[2 * common.MAX_CAPTURE_GROUPS]int
+ {
+ context.allocator = it.temp
+ if .Unicode in it.regex.flags {
+ saved, ok = virtual_machine.run(&it.vm, true)
+ } else {
+ saved, ok = virtual_machine.run(&it.vm, false)
+ }
+ }
+
+ str := string(it.vm.memory)
+ num_groups: int
+
+ if saved != nil {
+ n := 0
+
+ #no_bounds_check for i := 0; i < len(saved); i += 2 {
+ a, b := saved[i], saved[i + 1]
+ if a == -1 || b == -1 {
+ continue
+ }
+
+ it.capture.groups[n] = str[a:b]
+ it.capture.pos[n] = {a, b}
+ n += 1
+ }
+ num_groups = n
+ }
+
+ defer if ok {
+ it.idx += 1
+ }
+
+ if num_groups > 0 {
+ result = {it.capture.pos[:num_groups], it.capture.groups[:num_groups]}
+ }
+ return result, it.idx, ok
+}
+
match :: proc {
match_and_allocate_capture,
match_with_preallocated_capture,
+ match_iterator,
+}
+
+reset :: proc(it: ^Match_Iterator) {
+ it.idx = 0
}
/*
@@ -406,7 +522,7 @@ Returns:
@require_results
preallocate_capture :: proc(allocator := context.allocator) -> (result: Capture) {
context.allocator = allocator
- result.pos = make([][2]int, common.MAX_CAPTURE_GROUPS)
+ result.pos = make([][2]int, common.MAX_CAPTURE_GROUPS)
result.groups = make([]string, common.MAX_CAPTURE_GROUPS)
return
}
@@ -436,7 +552,7 @@ Free all data allocated by the `match_and_allocate_capture` procedure.
*Frees Using Provided Allocator*
Inputs:
-- capture: A Capture.
+- capture: A `Capture`.
- allocator: (default: context.allocator)
*/
destroy_capture :: proc(capture: Capture, allocator := context.allocator) {
@@ -445,7 +561,24 @@ destroy_capture :: proc(capture: Capture, allocator := context.allocator) {
delete(capture.pos)
}
+/*
+Free all data allocated by the `create_iterator` procedure.
+
+*Frees Using Provided Allocator*
+
+Inputs:
+- it: A `Match_Iterator`
+- allocator: (default: context.allocator)
+*/
+destroy_iterator :: proc(it: Match_Iterator, allocator := context.allocator) {
+ context.allocator = allocator
+ destroy(it.regex)
+ destroy(it.capture)
+ virtual_machine.destroy(it.vm)
+}
+
destroy :: proc {
destroy_regex,
destroy_capture,
+ destroy_iterator,
}
diff --git a/core/text/regex/virtual_machine/virtual_machine.odin b/core/text/regex/virtual_machine/virtual_machine.odin
index a4fca6c4d..ab1dfbec1 100644
--- a/core/text/regex/virtual_machine/virtual_machine.odin
+++ b/core/text/regex/virtual_machine/virtual_machine.odin
@@ -627,8 +627,9 @@ opcode_count :: proc(code: Program) -> (opcodes: int) {
return
}
-create :: proc(code: Program, str: string) -> (vm: Machine) {
+create :: proc(code: Program, str: string, allocator := context.allocator) -> (vm: Machine) {
assert(len(code) > 0, "RegEx VM has no instructions.")
+ context.allocator = allocator
vm.memory = str
vm.code = code
@@ -644,3 +645,11 @@ create :: proc(code: Program, str: string) -> (vm: Machine) {
return
}
+
+destroy :: proc(vm: Machine, allocator := context.allocator) {
+ context.allocator = allocator
+
+ delete(vm.busy_map)
+ free(vm.threads)
+ free(vm.next_threads)
+}
\ No newline at end of file
diff --git a/core/thread/thread.odin b/core/thread/thread.odin
index c1cbceb42..c7fd082ac 100644
--- a/core/thread/thread.odin
+++ b/core/thread/thread.odin
@@ -264,12 +264,14 @@ flag is specified.
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
in order to free the resources associated with the temporary allocations.
*/
-create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread {
+create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread) {
thread_proc :: proc(t: ^Thread) {
fn := cast(proc())t.data
fn()
}
- t := create(thread_proc, priority)
+ if t = create(thread_proc, priority); t == nil {
+ return
+ }
t.data = rawptr(fn)
if self_cleanup {
intrinsics.atomic_or(&t.flags, {.Self_Cleanup})
@@ -295,14 +297,16 @@ flag is specified.
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
in order to free the resources associated with the temporary allocations.
*/
-create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread {
+create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread) {
thread_proc :: proc(t: ^Thread) {
fn := cast(proc(rawptr))t.data
assert(t.user_index >= 1)
data := t.user_args[0]
fn(data)
}
- t := create(thread_proc, priority)
+ if t = create(thread_proc, priority); t == nil {
+ return
+ }
t.data = rawptr(fn)
t.user_index = 1
t.user_args[0] = data
@@ -330,7 +334,7 @@ flag is specified.
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
in order to free the resources associated with the temporary allocations.
*/
-create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread
+create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread)
where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
thread_proc :: proc(t: ^Thread) {
fn := cast(proc(T))t.data
@@ -338,7 +342,9 @@ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_contex
data := (^T)(&t.user_args[0])^
fn(data)
}
- t := create(thread_proc, priority)
+ if t = create(thread_proc, priority); t == nil {
+ return
+ }
t.data = rawptr(fn)
t.user_index = 1
@@ -371,7 +377,7 @@ flag is specified.
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
in order to free the resources associated with the temporary allocations.
*/
-create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread
+create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread)
where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
thread_proc :: proc(t: ^Thread) {
fn := cast(proc(T1, T2))t.data
@@ -383,7 +389,9 @@ create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2),
fn(arg1, arg2)
}
- t := create(thread_proc, priority)
+ if t = create(thread_proc, priority); t == nil {
+ return
+ }
t.data = rawptr(fn)
t.user_index = 2
@@ -418,7 +426,7 @@ flag is specified.
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
in order to free the resources associated with the temporary allocations.
*/
-create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread
+create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread)
where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
thread_proc :: proc(t: ^Thread) {
fn := cast(proc(T1, T2, T3))t.data
@@ -431,7 +439,9 @@ create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: pr
fn(arg1, arg2, arg3)
}
- t := create(thread_proc, priority)
+ if t = create(thread_proc, priority); t == nil {
+ return
+ }
t.data = rawptr(fn)
t.user_index = 3
@@ -467,7 +477,7 @@ flag is specified.
is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()`
in order to free the resources associated with the temporary allocations.
*/
-create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread
+create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread)
where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS {
thread_proc :: proc(t: ^Thread) {
fn := cast(proc(T1, T2, T3, T4))t.data
@@ -481,7 +491,9 @@ create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4:
fn(arg1, arg2, arg3, arg4)
}
- t := create(thread_proc, priority)
+ if t = create(thread_proc, priority); t == nil {
+ return
+ }
t.data = rawptr(fn)
t.user_index = 4
@@ -513,8 +525,10 @@ _select_context_for_thread :: proc(init_context: Maybe(runtime.Context)) -> runt
Ensure that the temp allocator is thread-safe when the user provides a specific initial context to use.
Without this, the thread will use the same temp allocator state as the parent thread, and thus, bork it up.
*/
- if ctx.temp_allocator.procedure == runtime.default_temp_allocator_proc {
- ctx.temp_allocator.data = &runtime.global_default_temp_allocator_data
+ when !ODIN_DEFAULT_TO_NIL_ALLOCATOR {
+ if ctx.temp_allocator.procedure == runtime.default_temp_allocator_proc {
+ ctx.temp_allocator.data = &runtime.global_default_temp_allocator_data
+ }
}
return ctx
}
@@ -529,4 +543,4 @@ _maybe_destroy_default_temp_allocator :: proc(init_context: Maybe(runtime.Contex
if context.temp_allocator.procedure == runtime.default_temp_allocator_proc {
runtime.default_temp_allocator_destroy(auto_cast context.temp_allocator.data)
}
-}
+}
\ No newline at end of file
diff --git a/core/time/perf.odin b/core/time/perf.odin
index 784d7acd6..265a20edf 100644
--- a/core/time/perf.odin
+++ b/core/time/perf.odin
@@ -17,6 +17,13 @@ tick_now :: proc "contextless" () -> Tick {
return _tick_now()
}
+/*
+Add duration to a tick.
+*/
+tick_add :: proc "contextless" (t: Tick, d: Duration) -> Tick {
+ return Tick{t._nsec + i64(d)}
+}
+
/*
Obtain the difference between ticks.
*/
diff --git a/core/time/timezone/tzdate.odin b/core/time/timezone/tzdate.odin
index e62400889..8e900ec11 100644
--- a/core/time/timezone/tzdate.odin
+++ b/core/time/timezone/tzdate.odin
@@ -224,7 +224,7 @@ datetime_to_tz :: proc(dt: datetime.DateTime, tz: ^datetime.TZ_Region) -> (out:
record := region_get_nearest(tz, tm) or_return
secs := time.time_to_unix(tm)
- adj_time := time.unix(secs + record.utc_offset, 0)
+ adj_time := time.unix(secs + record.utc_offset, i64(dt.nano))
adj_dt := time.time_to_datetime(adj_time) or_return
adj_dt.tz = tz
diff --git a/examples/all/all_linux.odin b/examples/all/all_linux.odin
index ca51d6562..dde712b8d 100644
--- a/examples/all/all_linux.odin
+++ b/examples/all/all_linux.odin
@@ -3,4 +3,8 @@ package all
import linux "core:sys/linux"
-_ :: linux
\ No newline at end of file
+import xlib "vendor:x11/xlib"
+
+_ :: linux
+
+_ :: xlib
diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin
index 0e7648f96..de037f6cd 100644
--- a/examples/all/all_main.odin
+++ b/examples/all/all_main.odin
@@ -58,7 +58,6 @@ import trace "core:debug/trace"
import dynlib "core:dynlib"
import net "core:net"
-import ansi "core:encoding/ansi"
import base32 "core:encoding/base32"
import base64 "core:encoding/base64"
import cbor "core:encoding/cbor"
@@ -118,6 +117,7 @@ import relative "core:relative"
import reflect "core:reflect"
import runtime "base:runtime"
+import sanitizer "base:sanitizer"
import simd "core:simd"
import x86 "core:simd/x86"
import slice "core:slice"
@@ -128,6 +128,9 @@ import strings "core:strings"
import sync "core:sync"
import testing "core:testing"
+import terminal "core:terminal"
+import ansi "core:terminal/ansi"
+
import edit "core:text/edit"
import i18n "core:text/i18n"
import match "core:text/match"
@@ -200,7 +203,6 @@ _ :: pe
_ :: trace
_ :: dynlib
_ :: net
-_ :: ansi
_ :: base32
_ :: base64
_ :: csv
@@ -256,6 +258,8 @@ _ :: strconv
_ :: strings
_ :: sync
_ :: testing
+_ :: terminal
+_ :: ansi
_ :: scanner
_ :: i18n
_ :: match
@@ -275,3 +279,4 @@ _ :: uuid_legacy
_ :: utf8
_ :: utf8string
_ :: utf16
+_ :: sanitizer
diff --git a/examples/all/all_vendor.odin b/examples/all/all_vendor.odin
index b224a3bbe..ebbfe786b 100644
--- a/examples/all/all_vendor.odin
+++ b/examples/all/all_vendor.odin
@@ -28,8 +28,6 @@ import nvg "vendor:nanovg"
import nvg_gl "vendor:nanovg/gl"
import fontstash "vendor:fontstash"
-import xlib "vendor:x11/xlib"
-
_ :: cgltf
// _ :: commonmark
_ :: ENet
@@ -57,8 +55,6 @@ _ :: nvg
_ :: nvg_gl
_ :: fontstash
-_ :: xlib
-
// NOTE: needed for doc generator
diff --git a/examples/all/all_vendor_windows.odin b/examples/all/all_vendor_windows.odin
index 5087bac07..df6542cdd 100644
--- a/examples/all/all_vendor_windows.odin
+++ b/examples/all/all_vendor_windows.odin
@@ -3,8 +3,10 @@ package all
import wgpu "vendor:wgpu"
import b2 "vendor:box2d"
import game_input "vendor:windows/GameInput"
+import XAudio2 "vendor:windows/XAudio2"
_ :: wgpu
_ :: b2
_ :: game_input
+_ :: XAudio2
diff --git a/misc/get-date.c b/misc/get-date.c
index bf5b32738..b3eb1be78 100644
--- a/misc/get-date.c
+++ b/misc/get-date.c
@@ -9,5 +9,5 @@
int main(int arg_count, char const **arg_ptr) {
time_t t = time(NULL);
struct tm* now = localtime(&t);
- printf("%04d%02d%02d", now->tm_year + 1900, now->tm_mon + 1, now->tm_mday);
+ printf("%04d-%02d-%02d", now->tm_year + 1900, now->tm_mon + 1, now->tm_mday);
}
\ No newline at end of file
diff --git a/src/bug_report.cpp b/src/bug_report.cpp
index c44c4be33..32210c23e 100644
--- a/src/bug_report.cpp
+++ b/src/bug_report.cpp
@@ -527,7 +527,7 @@ gb_internal void report_os_info() {
#elif defined(GB_SYSTEM_OSX)
gbString sw_vers = gb_string_make(heap_allocator(), "");
- if (!system_exec_command_line_app_output("sw_vers --productVersion", &sw_vers)) {
+ if (!system_exec_command_line_app_output("sw_vers -productVersion", &sw_vers)) {
gb_printf("macOS Unknown\n");
return;
}
@@ -667,8 +667,14 @@ gb_internal void print_bug_report_help() {
gb_printf("-nightly");
#endif
+ String version = {};
+
#ifdef GIT_SHA
- gb_printf(":%s", GIT_SHA);
+ version.text = cast(u8 *)GIT_SHA;
+ version.len = gb_strlen(GIT_SHA);
+ if (version != "") {
+ gb_printf(":%.*s", LIT(version));
+ }
#endif
gb_printf("\n");
diff --git a/src/build_settings.cpp b/src/build_settings.cpp
index 1f5aba254..b3bbf726b 100644
--- a/src/build_settings.cpp
+++ b/src/build_settings.cpp
@@ -459,6 +459,7 @@ struct BuildContext {
bool ignore_unknown_attributes;
bool no_bounds_check;
bool no_type_assert;
+ bool dynamic_literals; // Opt-in to `#+feature dynamic-literals` project-wide.
bool no_output_files;
bool no_crt;
bool no_rpath;
@@ -551,10 +552,9 @@ struct BuildContext {
String ODIN_ANDROID_NDK_TOOLCHAIN_LIB_LEVEL;
String ODIN_ANDROID_NDK_TOOLCHAIN_SYSROOT;
- String ODIN_ANDROID_JAR_SIGNER;
String android_keystore;
String android_keystore_alias;
- String android_manifest;
+ String android_keystore_password;
};
gb_global BuildContext build_context = {0};
@@ -848,13 +848,39 @@ gb_global NamedTargetMetrics *selected_target_metrics;
gb_global Subtarget selected_subtarget;
-gb_internal TargetOsKind get_target_os_from_string(String str) {
+gb_internal TargetOsKind get_target_os_from_string(String str, Subtarget *subtarget_ = nullptr) {
+ String os_name = str;
+ String subtarget = {};
+ auto part = string_partition(str, str_lit(":"));
+ if (part.match.len == 1) {
+ os_name = part.head;
+ subtarget = part.tail;
+ }
+
+ TargetOsKind kind = TargetOs_Invalid;
+
for (isize i = 0; i < TargetOs_COUNT; i++) {
- if (str_eq_ignore_case(target_os_names[i], str)) {
- return cast(TargetOsKind)i;
+ if (str_eq_ignore_case(target_os_names[i], os_name)) {
+ kind = cast(TargetOsKind)i;
+ break;
}
}
- return TargetOs_Invalid;
+ if (subtarget_) *subtarget_ = Subtarget_Default;
+
+ if (subtarget.len != 0) {
+ if (str_eq_ignore_case(subtarget, "generic") || str_eq_ignore_case(subtarget, "default")) {
+ if (subtarget_) *subtarget_ = Subtarget_Default;
+ } else {
+ for (isize i = 1; i < Subtarget_COUNT; i++) {
+ if (str_eq_ignore_case(subtarget_strings[i], subtarget)) {
+ if (subtarget_) *subtarget_ = cast(Subtarget)i;
+ break;
+ }
+ }
+ }
+ }
+
+ return kind;
}
gb_internal TargetArchKind get_target_arch_from_string(String str) {
@@ -1573,24 +1599,15 @@ gb_internal void init_android_values(bool with_sdk) {
bc->ODIN_ANDROID_NDK_TOOLCHAIN_SYSROOT = concatenate_strings(permanent_allocator(), bc->ODIN_ANDROID_NDK_TOOLCHAIN, str_lit("sysroot/"));
- bc->ODIN_ANDROID_JAR_SIGNER = normalize_path(permanent_allocator(), make_string_c(gb_get_env("ODIN_ANDROID_JAR_SIGNER", permanent_allocator())), NIX_SEPARATOR_STRING);
if (with_sdk) {
if (bc->ODIN_ANDROID_SDK.len == 0) {
gb_printf_err("Error: ODIN_ANDROID_SDK not set, which is required for -build-mode:executable for -subtarget:android");
gb_exit(1);
}
- if (bc->ODIN_ANDROID_JAR_SIGNER.len == 0) {
- gb_printf_err("Error: ODIN_ANDROID_JAR_SIGNER not set, which is required for -build-mode:executable for -subtarget:android");
- gb_exit(1);
- }
if (bc->android_keystore.len == 0) {
gb_printf_err("Error: -android-keystore: has not been set\n");
gb_exit(1);
}
- if (bc->android_keystore_alias.len == 0) {
- gb_printf_err("Error: -android-keystore_alias: has not been set\n");
- gb_exit(1);
- }
}
}
@@ -1774,6 +1791,30 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
bc->ODIN_WINDOWS_SUBSYSTEM = windows_subsystem_names[Windows_Subsystem_CONSOLE];
}
+ if (subtarget == Subtarget_Android) {
+ switch (build_context.build_mode) {
+ case BuildMode_DynamicLibrary:
+ case BuildMode_Object:
+ case BuildMode_Assembly:
+ case BuildMode_LLVM_IR:
+ break;
+ default:
+ case BuildMode_Executable:
+ case BuildMode_StaticLibrary:
+ if ((build_context.command_kind & Command__does_build) != 0) {
+ gb_printf_err("Unsupported -build-mode for -subtarget:android\n");
+ gb_printf_err("\tCurrently only supporting: \n");
+ // gb_printf_err("\t\texe\n");
+ gb_printf_err("\t\tshared\n");
+ gb_printf_err("\t\tobject\n");
+ gb_printf_err("\t\tassembly\n");
+ gb_printf_err("\t\tllvm-ir\n");
+ gb_exit(1);
+ }
+ break;
+ }
+ }
+
if (metrics->os == TargetOs_darwin && subtarget == Subtarget_iOS) {
switch (metrics->arch) {
case TargetArch_arm64:
@@ -1849,7 +1890,7 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
bc->metrics.target_triplet = concatenate_strings(permanent_allocator(), bc->metrics.target_triplet, bc->minimum_os_version_string);
}
} else if (selected_subtarget == Subtarget_Android) {
- init_android_values(bc->build_mode == BuildMode_Executable);
+ init_android_values(bc->build_mode == BuildMode_Executable && (bc->command_kind & Command__does_build) != 0);
}
if (!bc->custom_optimization_level) {
@@ -1875,12 +1916,6 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
}
- // TODO: Static map calls are bugged on `amd64sysv` abi.
- if (bc->metrics.os != TargetOs_windows && bc->metrics.arch == TargetArch_amd64) {
- // ENFORCE DYNAMIC MAP CALLS
- bc->dynamic_map_calls = true;
- }
-
bc->ODIN_VALGRIND_SUPPORT = false;
if (build_context.metrics.os != TargetOs_windows) {
switch (bc->metrics.arch) {
@@ -1893,30 +1928,6 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta
if (bc->metrics.os == TargetOs_freestanding) {
bc->ODIN_DEFAULT_TO_NIL_ALLOCATOR = !bc->ODIN_DEFAULT_TO_PANIC_ALLOCATOR;
}
-
- if (subtarget == Subtarget_Android) {
- switch (build_context.build_mode) {
- case BuildMode_DynamicLibrary:
- case BuildMode_Object:
- case BuildMode_Assembly:
- case BuildMode_LLVM_IR:
- break;
- default:
- case BuildMode_Executable:
- case BuildMode_StaticLibrary:
- if ((build_context.command_kind & Command__does_build) != 0) {
- gb_printf_err("Unsupported -build-mode for -subtarget:android\n");
- gb_printf_err("\tCurrently only supporting: \n");
- // gb_printf_err("\t\texe\n");
- gb_printf_err("\t\tshared\n");
- gb_printf_err("\t\tobject\n");
- gb_printf_err("\t\tassembly\n");
- gb_printf_err("\t\tllvm-ir\n");
- gb_exit(1);
- }
- break;
- }
- }
}
#if defined(GB_SYSTEM_WINDOWS)
@@ -2198,11 +2209,34 @@ gb_internal bool init_build_paths(String init_filename) {
while (output_name.len > 0 && (output_name[output_name.len-1] == '/' || output_name[output_name.len-1] == '\\')) {
output_name.len -= 1;
}
+ // Only trim the extension if it's an Odin source file.
+ // This lets people build folders with extensions or files beginning with dots.
+ if (path_extension(output_name) == ".odin" && !path_is_directory(output_name)) {
+ output_name = remove_extension_from_path(output_name);
+ }
output_name = remove_directory_from_path(output_name);
- output_name = remove_extension_from_path(output_name);
output_name = copy_string(ha, string_trim_whitespace(output_name));
- output_path = path_from_string(ha, output_name);
-
+ // This is `path_from_string` without the extension trimming.
+ Path res = {};
+ if (output_name.len > 0) {
+ String fullpath = path_to_full_path(ha, output_name);
+ defer (gb_free(ha, fullpath.text));
+
+ res.basename = directory_from_path(fullpath);
+ res.basename = copy_string(ha, res.basename);
+
+ if (path_is_directory(fullpath)) {
+ if (res.basename.len > 0 && res.basename.text[res.basename.len - 1] == '/') {
+ res.basename.len--;
+ }
+ } else {
+ isize name_start = (res.basename.len > 0) ? res.basename.len + 1 : res.basename.len;
+ res.name = substring(fullpath, name_start, fullpath.len);
+ res.name = copy_string(ha, res.name);
+ }
+ }
+ output_path = res;
+
// Note(Dragos): This is a fix for empty filenames
// Turn the trailing folder into the file name
if (output_path.name.len == 0) {
diff --git a/src/bundle_command.cpp b/src/bundle_command.cpp
index b3bca2b51..cd0cd589f 100644
--- a/src/bundle_command.cpp
+++ b/src/bundle_command.cpp
@@ -126,16 +126,6 @@ i32 bundle_android(String original_init_directory) {
defer (gb_string_free(cmd));
- String current_directory = normalize_path(temporary_allocator(), get_working_directory(temporary_allocator()), NIX_SEPARATOR_STRING);
- defer (set_working_directory(current_directory));
-
- if (current_directory.len != 0) {
- bool ok = set_working_directory(init_directory);
- if (!ok) {
- gb_printf_err("Error: Unable to currectly set the current working directory to '%.*s'\n", LIT(init_directory));
- }
- }
-
String output_filename = str_lit("test");
String output_apk = path_remove_extension(output_filename);
@@ -144,51 +134,36 @@ i32 bundle_android(String original_init_directory) {
TEMPORARY_ALLOCATOR_GUARD();
gb_string_clear(cmd);
- String manifest = {};
- if (build_context.android_manifest.len != 0) {
- manifest = concatenate_strings(temporary_allocator(), current_directory, build_context.android_manifest);
- } else {
- manifest = concatenate_strings(temporary_allocator(), init_directory, str_lit("AndroidManifest.xml"));
- }
+ String manifest = concatenate_strings(temporary_allocator(), init_directory, str_lit("AndroidManifest.xml"));
cmd = gb_string_append_length(cmd, android_sdk_build_tools.text, android_sdk_build_tools.len);
cmd = gb_string_appendc(cmd, "aapt");
cmd = gb_string_appendc(cmd, " package -f");
- if (manifest.len != 0) {
- cmd = gb_string_append_fmt(cmd, " -M \"%.*s\"", LIT(manifest));
- }
+ cmd = gb_string_append_fmt(cmd, " -M \"%.*s\"", LIT(manifest));
cmd = gb_string_append_fmt(cmd, " -I \"%.*sandroid.jar\"", LIT(android_sdk_platforms));
cmd = gb_string_append_fmt(cmd, " -F \"%.*s.apk-build\"", LIT(output_apk));
+ String resources_dir = concatenate_strings(temporary_allocator(), init_directory, str_lit("res"));
+ if (gb_file_exists((const char *)resources_dir.text)) {
+ cmd = gb_string_append_fmt(cmd, " -S \"%.*s\"", LIT(resources_dir));
+ }
+
+ String assets_dir = concatenate_strings(temporary_allocator(), init_directory, str_lit("assets"));
+ if (gb_file_exists((const char *)assets_dir.text)) {
+ cmd = gb_string_append_fmt(cmd, " -A \"%.*s\"", LIT(assets_dir));
+ }
+
+ String lib_dir = concatenate_strings(temporary_allocator(), init_directory, str_lit("lib"));
+ if (gb_file_exists((const char *)lib_dir.text)) {
+ cmd = gb_string_append_fmt(cmd, " \"%.*s\"", LIT(lib_dir));
+ }
+
result = system_exec_command_line_app("android-aapt", cmd);
if (result) {
return result;
}
}
- TIME_SECTION("Android jarsigner");
- {
- TEMPORARY_ALLOCATOR_GUARD();
- gb_string_clear(cmd);
-
- cmd = gb_string_append_length(cmd, build_context.ODIN_ANDROID_JAR_SIGNER.text, build_context.ODIN_ANDROID_JAR_SIGNER.len);
- cmd = gb_string_append_fmt(cmd, " -storepass android");
- if (build_context.android_keystore.len != 0) {
- String keystore = concatenate_strings(temporary_allocator(), current_directory, build_context.android_keystore);
- cmd = gb_string_append_fmt(cmd, " -keystore \"%.*s\"", LIT(keystore));
- }
- cmd = gb_string_append_fmt(cmd, " \"%.*s.apk-build\"", LIT(output_apk));
- if (build_context.android_keystore_alias.len != 0) {
- String keystore_alias = build_context.android_keystore_alias;
- cmd = gb_string_append_fmt(cmd, " \"%.*s\"", LIT(keystore_alias));
- }
-
- result = system_exec_command_line_app("android-jarsigner", cmd);
- if (result) {
- return result;
- }
- }
-
TIME_SECTION("Android zipalign");
{
TEMPORARY_ALLOCATOR_GUARD();
@@ -205,5 +180,33 @@ i32 bundle_android(String original_init_directory) {
}
}
+ TIME_SECTION("Android apksigner");
+ {
+ TEMPORARY_ALLOCATOR_GUARD();
+ gb_string_clear(cmd);
+
+ cmd = gb_string_append_length(cmd, android_sdk_build_tools.text, android_sdk_build_tools.len);
+ cmd = gb_string_appendc(cmd, "apksigner");
+ cmd = gb_string_appendc(cmd, " sign");
+
+ String keystore = normalize_path(temporary_allocator(), build_context.android_keystore, NIX_SEPARATOR_STRING);
+ keystore = substring(keystore, 0, keystore.len - 1);
+ cmd = gb_string_append_fmt(cmd, " --ks \"%.*s\"", LIT(keystore));
+
+ if (build_context.android_keystore_alias.len != 0) {
+ cmd = gb_string_append_fmt(cmd, " --ks-key-alias \"%.*s\"", LIT(build_context.android_keystore_alias));
+ }
+ if (build_context.android_keystore_password.len != 0) {
+ cmd = gb_string_append_fmt(cmd, " --ks-pass pass:\"%.*s\"", LIT(build_context.android_keystore_password));
+ }
+
+ cmd = gb_string_append_fmt(cmd, " \"%.*s.apk\"", LIT(output_apk));
+
+ result = system_exec_command_line_app("android-apksigner", cmd);
+ if (result) {
+ return result;
+ }
+ }
+
return 0;
}
diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp
index 05f0ac7d7..71906d9c6 100644
--- a/src/check_builtin.cpp
+++ b/src/check_builtin.cpp
@@ -223,9 +223,9 @@ gb_internal void add_objc_proc_type(CheckerContext *c, Ast *call, Type *return_t
data.kind = kind;
data.proc_type = alloc_type_proc(scope, params, param_types.count, results, results->Tuple.variables.count, false, ProcCC_CDecl);
- mutex_lock(&c->info->objc_types_mutex);
+ mutex_lock(&c->info->objc_objc_msgSend_mutex);
map_set(&c->info->objc_msgSend_types, call, data);
- mutex_unlock(&c->info->objc_types_mutex);
+ mutex_unlock(&c->info->objc_objc_msgSend_mutex);
try_to_add_package_dependency(c, "runtime", "objc_msgSend");
try_to_add_package_dependency(c, "runtime", "objc_msgSend_fpret");
@@ -387,6 +387,59 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan
try_to_add_package_dependency(c, "runtime", "objc_allocateClassPair");
return true;
} break;
+
+ case BuiltinProc_objc_ivar_get:
+ {
+ Type *self_type = nullptr;
+
+ Operand self = {};
+ check_expr_or_type(c, &self, ce->args[0]);
+
+ if (!is_operand_value(self) || !check_is_assignable_to(c, &self, t_objc_id)) {
+ gbString e = expr_to_string(self.expr);
+ gbString t = type_to_string(self.type);
+ error(self.expr, "'%.*s' expected a type or value derived from intrinsics.objc_object, got '%s' of type %s", LIT(builtin_name), e, t);
+ gb_string_free(t);
+ gb_string_free(e);
+ return false;
+ } else if (!is_type_pointer(self.type)) {
+ gbString e = expr_to_string(self.expr);
+ gbString t = type_to_string(self.type);
+ error(self.expr, "'%.*s' expected a pointer of a value derived from intrinsics.objc_object, got '%s' of type %s", LIT(builtin_name), e, t);
+ gb_string_free(t);
+ gb_string_free(e);
+ return false;
+ }
+
+ self_type = type_deref(self.type);
+
+ if (!(self_type->kind == Type_Named &&
+ self_type->Named.type_name != nullptr &&
+ self_type->Named.type_name->TypeName.objc_class_name != "")) {
+ gbString t = type_to_string(self_type);
+ error(self.expr, "'%.*s' expected a named type with the attribute @(objc_class=) , got type %s", LIT(builtin_name), t);
+ gb_string_free(t);
+ return false;
+ }
+
+ Type *ivar_type = self_type->Named.type_name->TypeName.objc_ivar;
+ if (ivar_type == nullptr) {
+ gbString t = type_to_string(self_type);
+ error(self.expr, "'%.*s' requires that type %s have the attribute @(objc_ivar=).", LIT(builtin_name), t);
+ gb_string_free(t);
+ return false;
+ }
+
+ if (type_hint != nullptr && type_hint->kind == Type_Pointer && type_hint->Pointer.elem == ivar_type) {
+ operand->type = type_hint;
+ } else {
+ operand->type = alloc_type_pointer(ivar_type);
+ }
+
+ operand->mode = Addressing_Value;
+ return true;
+
+ } break;
}
}
@@ -760,6 +813,36 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan
return true;
}
+ case BuiltinProc_simd_indices:
+ {
+ Operand x = {};
+ check_expr_or_type(c, &x, ce->args[0], nullptr);
+ if (x.mode == Addressing_Invalid) return false;
+ if (x.mode != Addressing_Type) {
+ gbString s = expr_to_string(x.expr);
+ error(x.expr, "'%.*s' expected a simd vector type, got '%s'", LIT(builtin_name), s);
+ gb_string_free(s);
+ return false;
+ }
+ if (!is_type_simd_vector(x.type)) {
+ gbString s = type_to_string(x.type);
+ error(x.expr, "'%.*s' expected a simd vector type, got '%s'", LIT(builtin_name), s);
+ gb_string_free(s);
+ return false;
+ }
+
+ Type *elem = base_array_type(x.type);
+ if (!is_type_numeric(elem)) {
+ gbString s = type_to_string(x.type);
+ error(x.expr, "'%.*s' expected a simd vector type with a numeric element type, got '%s'", LIT(builtin_name), s);
+ gb_string_free(s);
+ }
+
+ operand->mode = Addressing_Value;
+ operand->type = x.type;
+ return true;
+ }
+
case BuiltinProc_simd_extract:
{
Operand x = {};
@@ -823,8 +906,12 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan
}
break;
+ case BuiltinProc_simd_reduce_add_bisect:
+ case BuiltinProc_simd_reduce_mul_bisect:
case BuiltinProc_simd_reduce_add_ordered:
case BuiltinProc_simd_reduce_mul_ordered:
+ case BuiltinProc_simd_reduce_add_pairs:
+ case BuiltinProc_simd_reduce_mul_pairs:
case BuiltinProc_simd_reduce_min:
case BuiltinProc_simd_reduce_max:
{
@@ -2073,6 +2160,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
case BuiltinProc_atomic_type_is_lock_free:
case BuiltinProc_has_target_feature:
case BuiltinProc_procedure_of:
+ case BuiltinProc_simd_indices:
// NOTE(bill): The first arg may be a Type, this will be checked case by case
break;
@@ -2146,7 +2234,8 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
case BuiltinProc_objc_find_selector:
case BuiltinProc_objc_find_class:
case BuiltinProc_objc_register_selector:
- case BuiltinProc_objc_register_class:
+ case BuiltinProc_objc_register_class:
+ case BuiltinProc_objc_ivar_get:
return check_builtin_objc_procedure(c, operand, call, id, type_hint);
case BuiltinProc___entry_point:
@@ -3168,6 +3257,194 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
break;
}
+ case BuiltinProc_compress_values: {
+ Operand *ops = gb_alloc_array(temporary_allocator(), Operand, ce->args.count);
+
+ isize value_count = 0;
+
+ for_array(i, ce->args) {
+ Ast *arg = ce->args[i];
+ Operand *op = &ops[i];
+ check_multi_expr(c, op, arg);
+ if (op->mode == Addressing_Invalid) {
+ return false;
+ }
+
+ if (op->type == nullptr || op->type == t_invalid) {
+ gbString s = expr_to_string(op->expr);
+ error(op->expr, "Invalid expression to '%.*s', got %s", LIT(builtin_name), s);
+ gb_string_free(s);
+ }
+ if (is_type_tuple(op->type)) {
+ value_count += op->type->Tuple.variables.count;
+ } else {
+ value_count += 1;
+ }
+ }
+
+ GB_ASSERT(value_count >= 1);
+
+ if (value_count == 1) {
+ *operand = ops[0];
+ break;
+ }
+
+ if (type_hint != nullptr) {
+ Type *th = base_type(type_hint);
+ if (th->kind == Type_Struct) {
+ if (value_count == th->Struct.fields.count) {
+ isize index = 0;
+ for_array(i, ce->args) {
+ Operand *op = &ops[i];
+ if (is_type_tuple(op->type)) {
+ for (Entity *v : op->type->Tuple.variables) {
+ Operand x = {};
+ x.mode = Addressing_Value;
+ x.type = v->type;
+ check_assignment(c, &x, th->Struct.fields[index++]->type, builtin_name);
+ if (x.mode == Addressing_Invalid) {
+ return false;
+ }
+ }
+ } else {
+ check_assignment(c, op, th->Struct.fields[index++]->type, builtin_name);
+ if (op->mode == Addressing_Invalid) {
+ return false;
+ }
+ }
+ }
+
+ operand->type = type_hint;
+ operand->mode = Addressing_Value;
+ break;
+ }
+ } else if (is_type_array_like(th)) {
+ if (cast(i64)value_count == get_array_type_count(th)) {
+ Type *elem = base_array_type(th);
+ for_array(i, ce->args) {
+ Operand *op = &ops[i];
+ if (is_type_tuple(op->type)) {
+ for (Entity *v : op->type->Tuple.variables) {
+ Operand x = {};
+ x.mode = Addressing_Value;
+ x.type = v->type;
+ check_assignment(c, &x, elem, builtin_name);
+ if (x.mode == Addressing_Invalid) {
+ return false;
+ }
+ }
+ } else {
+ check_assignment(c, op, elem, builtin_name);
+ if (op->mode == Addressing_Invalid) {
+ return false;
+ }
+ }
+ }
+
+ operand->type = type_hint;
+ operand->mode = Addressing_Value;
+ break;
+ }
+ }
+ }
+
+ bool all_types_the_same = true;
+ Type *last_type = nullptr;
+ for_array(i, ce->args) {
+ Operand *op = &ops[i];
+ if (is_type_tuple(op->type)) {
+ if (last_type == nullptr) {
+ op->type->Tuple.variables[0]->type;
+ }
+ for (Entity *v : op->type->Tuple.variables) {
+ if (!are_types_identical(last_type, v->type)) {
+ all_types_the_same = false;
+ break;
+ }
+ last_type = v->type;
+ }
+ } else {
+ if (last_type == nullptr) {
+ last_type = op->type;
+ } else {
+ if (!are_types_identical(last_type, op->type)) {
+ all_types_the_same = false;
+ break;
+ }
+ last_type = op->type;
+ }
+ }
+ }
+
+ if (all_types_the_same) {
+ Type *elem_type = default_type(last_type);
+ if (is_type_untyped(elem_type)) {
+ gbString s = expr_to_string(call);
+ error(call, "Invalid use of '%s' in '%.*s'", s, LIT(builtin_name));
+ gb_string_free(s);
+ return false;
+ }
+
+ operand->type = alloc_type_array(elem_type, value_count);
+ operand->mode = Addressing_Value;
+ } else {
+ Type *st = alloc_type_struct_complete();
+ st->Struct.fields = slice_make(permanent_allocator(), value_count);
+ st->Struct.tags = gb_alloc_array(permanent_allocator(), String, value_count);
+ st->Struct.offsets = gb_alloc_array(permanent_allocator(), i64, value_count);
+
+ Scope *scope = create_scope(c->info, nullptr);
+
+ Token token = {};
+ token.kind = Token_Ident;
+ token.pos = ast_token(call).pos;
+
+ isize index = 0;
+ for_array(i, ce->args) {
+ Operand *op = &ops[i];
+ if (is_type_tuple(op->type)) {
+ for (Entity *v : op->type->Tuple.variables) {
+ Type *t = default_type(v->type);
+ if (is_type_untyped(t)) {
+ gbString s = expr_to_string(op->expr);
+ error(op->expr, "Invalid use of '%s' in '%.*s'", s, LIT(builtin_name));
+ gb_string_free(s);
+ return false;
+ }
+
+ gbString s = gb_string_make_reserve(permanent_allocator(), 32);
+ s = gb_string_append_fmt(s, "v%lld", cast(long long)index);
+ token.string = make_string_c(s);
+ Entity *e = alloc_entity_field(scope, token, t, false, cast(i32)index, EntityState_Resolved);
+ st->Struct.fields[index++] = e;
+ }
+ } else {
+ Type *t = default_type(op->type);
+ if (is_type_untyped(t)) {
+ gbString s = expr_to_string(op->expr);
+ error(op->expr, "Invalid use of '%s' in '%.*s'", s, LIT(builtin_name));
+ gb_string_free(s);
+ return false;
+ }
+
+ gbString s = gb_string_make_reserve(permanent_allocator(), 32);
+ s = gb_string_append_fmt(s, "v%lld", cast(long long)index);
+ token.string = make_string_c(s);
+ Entity *e = alloc_entity_field(scope, token, t, false, cast(i32)index, EntityState_Resolved);
+ st->Struct.fields[index++] = e;
+ }
+ }
+
+
+ gb_unused(type_size_of(st));
+
+ operand->type = st;
+ operand->mode = Addressing_Value;
+ }
+ break;
+ }
+
+
case BuiltinProc_min: {
// min :: proc($T: typeid) -> ordered
// min :: proc(a: ..ordered) -> ordered
@@ -5564,6 +5841,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
case Type_EnumeratedArray: operand->type = bt->EnumeratedArray.elem; break;
case Type_Slice: operand->type = bt->Slice.elem; break;
case Type_DynamicArray: operand->type = bt->DynamicArray.elem; break;
+ case Type_SimdVector: operand->type = bt->SimdVector.elem; break;
}
}
operand->mode = Addressing_Type;
@@ -5613,6 +5891,87 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
}
operand->mode = Addressing_Type;
break;
+ case BuiltinProc_type_integer_to_unsigned:
+ if (operand->mode != Addressing_Type) {
+ error(operand->expr, "Expected a type for '%.*s'", LIT(builtin_name));
+ return false;
+ }
+
+ if (is_type_polymorphic(operand->type)) {
+ gbString t = type_to_string(operand->type);
+ error(operand->expr, "Expected a non-polymorphic type for '%.*s', got %s", LIT(builtin_name), t);
+ gb_string_free(t);
+ return false;
+ }
+
+ {
+ Type *bt = base_type(operand->type);
+
+ if (bt->kind != Type_Basic ||
+ (bt->Basic.flags & BasicFlag_Unsigned) != 0 ||
+ (bt->Basic.flags & BasicFlag_Integer) == 0) {
+ gbString t = type_to_string(operand->type);
+ error(operand->expr, "Expected a signed integer type for '%.*s', got %s", LIT(builtin_name), t);
+ gb_string_free(t);
+ return false;
+ }
+
+ if ((bt->Basic.flags & BasicFlag_Untyped) != 0) {
+ gbString t = type_to_string(operand->type);
+ error(operand->expr, "Expected a non-untyped integer type for '%.*s', got %s", LIT(builtin_name), t);
+ gb_string_free(t);
+ return false;
+ }
+
+ Type *u_type = &basic_types[bt->Basic.kind + 1];
+
+ operand->type = u_type;
+ }
+ break;
+ case BuiltinProc_type_integer_to_signed:
+ if (operand->mode != Addressing_Type) {
+ error(operand->expr, "Expected a type for '%.*s'", LIT(builtin_name));
+ return false;
+ }
+
+ if (is_type_polymorphic(operand->type)) {
+ gbString t = type_to_string(operand->type);
+ error(operand->expr, "Expected a non-polymorphic type for '%.*s', got %s", LIT(builtin_name), t);
+ gb_string_free(t);
+ return false;
+ }
+
+ {
+ Type *bt = base_type(operand->type);
+
+ if (bt->kind != Type_Basic ||
+ (bt->Basic.flags & BasicFlag_Unsigned) == 0 ||
+ (bt->Basic.flags & BasicFlag_Integer) == 0) {
+ gbString t = type_to_string(operand->type);
+ error(operand->expr, "Expected an unsigned integer type for '%.*s', got %s", LIT(builtin_name), t);
+ gb_string_free(t);
+ return false;
+ }
+
+ if ((bt->Basic.flags & BasicFlag_Untyped) != 0) {
+ gbString t = type_to_string(operand->type);
+ error(operand->expr, "Expected a non-untyped integer type for '%.*s', got %s", LIT(builtin_name), t);
+ gb_string_free(t);
+ return false;
+ }
+
+ if (bt->Basic.kind == Basic_uintptr) {
+ gbString t = type_to_string(operand->type);
+ error(operand->expr, "Type %s does not have a signed integer mapping for '%.*s'", t, LIT(builtin_name));
+ gb_string_free(t);
+ return false;
+ }
+
+ Type *u_type = &basic_types[bt->Basic.kind - 1];
+
+ operand->type = u_type;
+ }
+ break;
case BuiltinProc_type_merge:
{
operand->mode = Addressing_Type;
@@ -6014,12 +6373,13 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As
// NOTE(jakubtomsu): forces calculation of variant_block_size
type_size_of(u);
- i64 tag_offset = u->Union.variant_block_size;
- GB_ASSERT(tag_offset > 0);
+ // NOTE(Jeroen): A tag offset of zero is perfectly fine if all members of the union are empty structs.
+ // What matters is that the tag size is > 0.
+ GB_ASSERT(u->Union.tag_size > 0);
operand->mode = Addressing_Constant;
operand->type = t_untyped_integer;
- operand->value = exact_value_i64(tag_offset);
+ operand->value = exact_value_i64(u->Union.variant_block_size);
}
break;
diff --git a/src/check_decl.cpp b/src/check_decl.cpp
index 250e8b854..d53c3c6b7 100644
--- a/src/check_decl.cpp
+++ b/src/check_decl.cpp
@@ -468,6 +468,10 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr,
}
e->type = named;
+ if (!is_distinct) {
+ e->TypeName.is_type_alias = true;
+ }
+
check_type_path_push(ctx, e);
Type *bt = check_type_expr(ctx, te, named);
check_type_path_pop(ctx);
@@ -502,9 +506,9 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr,
if (!is_distinct) {
e->type = bt;
named->Named.base = bt;
- e->TypeName.is_type_alias = true;
}
+ e->TypeName.is_type_alias = !is_distinct;
if (decl->type_expr != nullptr) {
Type *t = check_type(ctx, decl->type_expr);
@@ -520,12 +524,90 @@ gb_internal void check_type_decl(CheckerContext *ctx, Entity *e, Ast *init_expr,
if (decl != nullptr) {
AttributeContext ac = {};
check_decl_attributes(ctx, decl->attributes, type_decl_attribute, &ac);
+
if (e->kind == Entity_TypeName && ac.objc_class != "") {
+
e->TypeName.objc_class_name = ac.objc_class;
+ if (ac.objc_is_implementation) {
+ e->TypeName.objc_is_implementation = ac.objc_is_implementation;
+ e->TypeName.objc_superclass = ac.objc_superclass;
+ e->TypeName.objc_ivar = ac.objc_ivar;
+ e->TypeName.objc_context_provider = ac.objc_context_provider;
+
+ mutex_lock(&ctx->info->objc_class_name_mutex);
+ bool class_exists = string_set_update(&ctx->info->obcj_class_name_set, ac.objc_class);
+ mutex_unlock(&ctx->info->objc_class_name_mutex);
+ if (class_exists) {
+ error(e->token, "@(objc_class) name '%.*s' has already been used elsewhere", LIT(ac.objc_class));
+ }
+
+ mpsc_enqueue(&ctx->info->objc_class_implementations, e);
+
+ GB_ASSERT(e->TypeName.objc_ivar == nullptr || e->TypeName.objc_ivar->kind == Type_Named);
+
+ // Enqueue the contex_provider proc to be checked after it is resolved
+ if (e->TypeName.objc_context_provider != nullptr) {
+ mpsc_enqueue(&ctx->checker->procs_with_objc_context_provider_to_check, e);
+ }
+
+ // TODO(harold): I think there's a Check elsewhere in the checker for checking cycles.
+ // See about moving this to the right location.
+ // Ensure superclass hierarchy are all Objective-C classes and does not cycle
+
+ // NOTE(harold): We check for superclass unconditionally (before checking if super is null)
+ // because this should be the case 99.99% of the time. Not subclassing something that
+ // is, or is the child of, NSObject means the objc runtime messaging will not properly work on this type.
+ TypeSet super_set{};
+ type_set_init(&super_set, 8);
+ defer (type_set_destroy(&super_set));
+
+ type_set_update(&super_set, e->type);
+
+ Type *super = ac.objc_superclass;
+ while (super != nullptr) {
+ if (type_set_update(&super_set, super)) {
+ error(e->token, "@(objc_superclass) Superclass hierarchy cycle encountered");
+ break;
+ }
+
+ check_single_global_entity(ctx->checker, super->Named.type_name, super->Named.type_name->decl_info);
+
+ if (super->kind != Type_Named) {
+ error(e->token, "@(objc_superclass) Referenced type must be a named struct");
+ break;
+ }
+
+ Type* named_type = base_named_type(super);
+ GB_ASSERT(named_type->kind == Type_Named);
+
+ if (!is_type_objc_object(named_type)) {
+ error(e->token, "@(objc_superclass) Superclass '%.*s' must be an Objective-C class", LIT(named_type->Named.name));
+ break;
+ }
+
+ if (named_type->Named.type_name->TypeName.objc_class_name == "") {
+ error(e->token, "@(objc_superclass) Superclass '%.*s' must have a valid @(objc_class) attribute", LIT(named_type->Named.name));
+ break;
+ }
+
+ super = named_type->Named.type_name->TypeName.objc_superclass;
+ }
+ } else {
+ if (ac.objc_superclass != nullptr) {
+ error(e->token, "@(objc_superclass) may only be applied when the @(obj_implement) attribute is also applied");
+ } else if (ac.objc_ivar != nullptr) {
+ error(e->token, "@(objc_ivar) may only be applied when the @(obj_implement) attribute is also applied");
+ } else if (ac.objc_context_provider != nullptr) {
+ error(e->token, "@(objc_context_provider) may only be applied when the @(obj_implement) attribute is also applied");
+ }
+ }
+
if (type_size_of(e->type) > 0) {
error(e->token, "@(objc_class) marked type must be of zero size");
}
+ } else if (ac.objc_is_implementation) {
+ error(e->token, "@(objc_implement) may only be applied when the @(objc_class) attribute is also applied");
}
}
@@ -918,7 +1000,7 @@ gb_internal String handle_link_name(CheckerContext *ctx, Token token, String lin
}
-gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeContext const &ac) {
+gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeContext &ac) {
if (!(ac.objc_name.len || ac.objc_is_class_method || ac.objc_type)) {
return;
}
@@ -930,48 +1012,107 @@ gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeCon
error(e->token, "@(objc_name) is required with @(objc_type)");
} else {
Type *t = ac.objc_type;
- if (t->kind == Type_Named) {
- Entity *tn = t->Named.type_name;
- GB_ASSERT(tn->kind == Entity_TypeName);
+ GB_ASSERT(t->kind == Type_Named); // NOTE(harold): This is already checked for at the attribute resolution stage.
+ Entity *tn = t->Named.type_name;
- if (tn->scope != e->scope) {
- error(e->token, "@(objc_name) attribute may only be applied to procedures and types within the same scope");
- } else {
- mutex_lock(&global_type_name_objc_metadata_mutex);
- defer (mutex_unlock(&global_type_name_objc_metadata_mutex));
+ GB_ASSERT(tn->kind == Entity_TypeName);
- if (!tn->TypeName.objc_metadata) {
- tn->TypeName.objc_metadata = create_type_name_obj_c_metadata();
- }
- auto *md = tn->TypeName.objc_metadata;
- mutex_lock(md->mutex);
- defer (mutex_unlock(md->mutex));
+ if (tn->scope != e->scope) {
+ error(e->token, "@(objc_name) attribute may only be applied to procedures and types within the same scope");
+ } else {
- if (!ac.objc_is_class_method) {
- bool ok = true;
- for (TypeNameObjCMetadataEntry const &entry : md->value_entries) {
- if (entry.name == ac.objc_name) {
- error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name));
- ok = false;
- break;
- }
- }
- if (ok) {
- array_add(&md->value_entries, TypeNameObjCMetadataEntry{ac.objc_name, e});
- }
+ // Enable implementation by default if the class is an implementer too and
+ // @objc_implement was not set to false explicitly in this proc.
+ bool implement = tn->TypeName.objc_is_implementation;
+ if (ac.objc_is_disabled_implement) {
+ implement = false;
+ }
+
+ if (implement) {
+ GB_ASSERT(e->kind == Entity_Procedure);
+
+ auto &proc = e->type->Proc;
+ Type *first_param = proc.param_count > 0 ? proc.params->Tuple.variables[0]->type : t_untyped_nil;
+
+ if (!tn->TypeName.objc_is_implementation) {
+ error(e->token, "@(objc_is_implement) attribute may only be applied to procedures whose class also have @(objc_is_implement) applied");
+ } else if (!ac.objc_is_class_method && !(first_param->kind == Type_Pointer && internal_check_is_assignable_to(t, first_param->Pointer.elem))) {
+ error(e->token, "Objective-C instance methods implementations require the first parameter to be a pointer to the class type set by @(objc_type)");
+ } else if (proc.calling_convention == ProcCC_Odin && !tn->TypeName.objc_context_provider) {
+ error(e->token, "Objective-C methods with Odin calling convention can only be used with classes that have @(objc_context_provider) set");
+ } else if (ac.objc_is_class_method && proc.calling_convention != ProcCC_CDecl) {
+ error(e->token, "Objective-C class methods (objc_is_class_method=true) that have @objc_is_implementation can only use \"c\" calling convention");
+ } else if (proc.result_count > 1) {
+ error(e->token, "Objective-C method implementations may return at most 1 value");
} else {
- bool ok = true;
- for (TypeNameObjCMetadataEntry const &entry : md->type_entries) {
- if (entry.name == ac.objc_name) {
- error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name));
- ok = false;
- break;
- }
+ // Always export unconditionally
+ // NOTE(harold): This means check_objc_methods() MUST be called before
+ // e->Procedure.is_export is set in check_proc_decl()!
+ if (ac.is_export) {
+ error(e->token, "Explicit export not allowed when @(objc_implement) is set. It set exported implicitly");
}
- if (ok) {
- array_add(&md->type_entries, TypeNameObjCMetadataEntry{ac.objc_name, e});
+ if (ac.link_name != "") {
+ error(e->token, "Explicit linkage not allowed when @(objc_implement) is set. It set to \"strong\" implicitly");
}
+
+ ac.is_export = true;
+ ac.linkage = STR_LIT("strong");
+
+ auto method = ObjcMethodData{ ac, e };
+ method.ac.objc_selector = ac.objc_selector != "" ? ac.objc_selector : ac.objc_name;
+
+ CheckerInfo *info = ctx->info;
+ mutex_lock(&info->objc_method_mutex);
+ defer (mutex_unlock(&info->objc_method_mutex));
+
+ Array* method_list = map_get(&info->objc_method_implementations, t);
+ if (method_list) {
+ array_add(method_list, method);
+ } else {
+ auto list = array_make(permanent_allocator(), 1, 8);
+ list[0] = method;
+
+ map_set(&info->objc_method_implementations, t, list);
+ }
+ }
+ } else if (ac.objc_selector != "") {
+ error(e->token, "@(objc_selector) may only be applied to procedures that are Objective-C implementations.");
+ }
+
+ mutex_lock(&global_type_name_objc_metadata_mutex);
+ defer (mutex_unlock(&global_type_name_objc_metadata_mutex));
+
+ if (!tn->TypeName.objc_metadata) {
+ tn->TypeName.objc_metadata = create_type_name_obj_c_metadata();
+ }
+ auto *md = tn->TypeName.objc_metadata;
+ mutex_lock(md->mutex);
+ defer (mutex_unlock(md->mutex));
+
+ if (!ac.objc_is_class_method) {
+ bool ok = true;
+ for (TypeNameObjCMetadataEntry const &entry : md->value_entries) {
+ if (entry.name == ac.objc_name) {
+ error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name));
+ ok = false;
+ break;
+ }
+ }
+ if (ok) {
+ array_add(&md->value_entries, TypeNameObjCMetadataEntry{ac.objc_name, e});
+ }
+ } else {
+ bool ok = true;
+ for (TypeNameObjCMetadataEntry const &entry : md->type_entries) {
+ if (entry.name == ac.objc_name) {
+ error(e->token, "Previous declaration of @(objc_name=\"%.*s\")", LIT(ac.objc_name));
+ ok = false;
+ break;
+ }
+ }
+ if (ok) {
+ array_add(&md->type_entries, TypeNameObjCMetadataEntry{ac.objc_name, e});
}
}
}
@@ -1141,6 +1282,9 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
break;
}
+ // NOTE(harold): For Objective-C method implementations, this must happen after
+ // check_objc_methods() is called as it re-sets ac.is_export to true unconditionally.
+ // The same is true for the linkage, set below.
e->Procedure.entry_point_only = ac.entry_point_only;
e->Procedure.is_export = ac.is_export;
@@ -1225,6 +1369,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
e->Procedure.has_instrumentation = has_instrumentation;
+ e->Procedure.no_sanitize_address = ac.no_sanitize_address;
e->deprecated_message = ac.deprecated_message;
e->warning_message = ac.warning_message;
@@ -1240,6 +1385,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) {
}
}
+ // NOTE(harold): See export/linkage note above(where is_export is assigned) regarding Objective-C method implementations
bool is_foreign = e->Procedure.is_foreign;
bool is_export = e->Procedure.is_export;
diff --git a/src/check_expr.cpp b/src/check_expr.cpp
index da193a4cc..72aa07e42 100644
--- a/src/check_expr.cpp
+++ b/src/check_expr.cpp
@@ -643,7 +643,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E
gb_internal bool check_polymorphic_procedure_assignment(CheckerContext *c, Operand *operand, Type *type, Ast *poly_def_node, PolyProcData *poly_proc_data) {
if (operand->expr == nullptr) return false;
- Entity *base_entity = entity_of_node(operand->expr);
+ Entity *base_entity = entity_from_expr(operand->expr);
if (base_entity == nullptr) return false;
return find_or_generate_polymorphic_procedure(c, base_entity, type, nullptr, poly_def_node, poly_proc_data);
}
@@ -995,14 +995,34 @@ gb_internal i64 assign_score_function(i64 distance, bool is_variadic=false) {
gb_internal bool check_is_assignable_to_with_score(CheckerContext *c, Operand *operand, Type *type, i64 *score_, bool is_variadic=false, bool allow_array_programming=true) {
- i64 score = 0;
- i64 distance = check_distance_between_types(c, operand, type, allow_array_programming);
- bool ok = distance >= 0;
- if (ok) {
- score = assign_score_function(distance, is_variadic);
+ if (c == nullptr) {
+ GB_ASSERT(operand->mode == Addressing_Value);
+ GB_ASSERT(is_type_typed(operand->type));
}
- if (score_) *score_ = score;
- return ok;
+ if (operand->mode == Addressing_Invalid || type == t_invalid) {
+ if (score_) *score_ = 0;
+ return false;
+ }
+
+ // Handle polymorphic procedure used as default parameter
+ if (operand->mode == Addressing_Value && is_type_proc(type) && is_type_proc(operand->type)) {
+ Entity *e = entity_from_expr(operand->expr);
+ if (e != nullptr && e->kind == Entity_Procedure && is_type_polymorphic(e->type) && !is_type_polymorphic(type)) {
+ // Special case: Allow a polymorphic procedure to be used as default value for concrete proc type
+ // during the initial check. It will be properly instantiated when actually used.
+ if (score_) *score_ = assign_score_function(1);
+ return true;
+ }
+ }
+
+ i64 score = check_distance_between_types(c, operand, type, allow_array_programming);
+ if (score >= 0) {
+ if (score_) *score_ = assign_score_function(score, is_variadic);
+ return true;
+ }
+
+ if (score_) *score_ = 0;
+ return false;
}
@@ -1854,7 +1874,10 @@ gb_internal Entity *check_ident(CheckerContext *c, Operand *o, Ast *n, Type *nam
o->type = t_invalid;
}
if (o->type != nullptr && o->type->kind == Type_Named && o->type->Named.type_name->TypeName.is_type_alias) {
- o->type = base_type(o->type);
+ Type *bt = base_type(o->type);
+ if (bt != nullptr) {
+ o->type = bt;
+ }
}
break;
@@ -2907,9 +2930,20 @@ gb_internal void check_comparison(CheckerContext *c, Ast *node, Operand *x, Oper
if (!defined) {
gbString xs = type_to_string(x->type, temporary_allocator());
gbString ys = type_to_string(y->type, temporary_allocator());
- err_str = gb_string_make(temporary_allocator(),
- gb_bprintf("operator '%.*s' not defined between the types '%s' and '%s'", LIT(token_strings[op]), xs, ys)
- );
+
+ if (!is_type_comparable(x->type)) {
+ err_str = gb_string_make(temporary_allocator(),
+ gb_bprintf("Type '%s' is not simply comparable, so operator '%.*s' is not defined for it", xs, LIT(token_strings[op]))
+ );
+ } else if (!is_type_comparable(y->type)) {
+ err_str = gb_string_make(temporary_allocator(),
+ gb_bprintf("Type '%s' is not simply comparable, so operator '%.*s' is not defined for it", ys, LIT(token_strings[op]))
+ );
+ } else {
+ err_str = gb_string_make(temporary_allocator(),
+ gb_bprintf("Operator '%.*s' not defined between the types '%s' and '%s'", LIT(token_strings[op]), xs, ys)
+ );
+ }
} else {
Type *comparison_type = x->type;
if (x->type == err_type && is_operand_nil(*x)) {
@@ -2930,11 +2964,11 @@ gb_internal void check_comparison(CheckerContext *c, Ast *node, Operand *x, Oper
} else {
yt = type_to_string(y->type);
}
- err_str = gb_string_make(temporary_allocator(), gb_bprintf("mismatched types '%s' and '%s'", xt, yt));
+ err_str = gb_string_make(temporary_allocator(), gb_bprintf("Mismatched types '%s' and '%s'", xt, yt));
}
if (err_str != nullptr) {
- error(node, "Cannot compare expression, %s", err_str);
+ error(node, "Cannot compare expression. %s.", err_str);
x->type = t_untyped_bool;
} else {
if (x->mode == Addressing_Constant &&
@@ -5427,8 +5461,18 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod
}
}
+ if (operand->type && is_type_simd_vector(type_deref(operand->type))) {
+ String field_name = selector->Ident.token.string;
+ if (field_name.len == 1) {
+ error(op_expr, "Extracting an element from a #simd array using .%.*s syntax is disallowed, prefer `simd.extract`", LIT(field_name));
+ } else {
+ error(op_expr, "Extracting elements from a #simd array using .%.*s syntax is disallowed, prefer `swizzle`", LIT(field_name));
+ }
+ return nullptr;
+ }
+
if (entity == nullptr && selector->kind == Ast_Ident && operand->type != nullptr &&
- (is_type_array(type_deref(operand->type)) || is_type_simd_vector(type_deref(operand->type)))) {
+ (is_type_array(type_deref(operand->type)))) {
String field_name = selector->Ident.token.string;
if (1 < field_name.len && field_name.len <= 4) {
u8 swizzles_xyzw[4] = {'x', 'y', 'z', 'w'};
@@ -5483,7 +5527,7 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod
Type *original_type = operand->type;
Type *array_type = base_type(type_deref(original_type));
- GB_ASSERT(array_type->kind == Type_Array || array_type->kind == Type_SimdVector);
+ GB_ASSERT(array_type->kind == Type_Array);
i64 array_count = get_array_type_count(array_type);
@@ -5524,10 +5568,6 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod
break;
}
- if (array_type->kind == Type_SimdVector) {
- operand->mode = Addressing_Value;
- }
-
Entity *swizzle_entity = alloc_entity_variable(nullptr, make_token_ident(field_name), operand->type, EntityState_Resolved);
add_type_and_value(c, operand->expr, operand->mode, operand->type, operand->value);
return swizzle_entity;
@@ -8142,7 +8182,7 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c
type = pt;
}
type = base_type(type);
- if (type->kind == Type_Proc && type->Proc.optional_ok) {
+ if (type->kind == Type_Proc && type->Proc.optional_ok && type->Proc.result_count > 0) {
operand->mode = Addressing_OptionalOk;
operand->type = type->Proc.results->Tuple.variables[0]->type;
if (operand->expr != nullptr && operand->expr->kind == Ast_CallExpr) {
@@ -9013,7 +9053,19 @@ gb_internal ExprKind check_or_else_expr(CheckerContext *c, Operand *o, Ast *node
if (left_type != nullptr) {
if (!y_is_diverging) {
- check_assignment(c, &y, left_type, name);
+ if (is_type_tuple(left_type)) {
+ if (!is_type_tuple(y.type)) {
+ error(y.expr, "Found a single value where a %td-valued expression was expected", left_type->Tuple.variables.count);
+ } else if (!are_types_identical(left_type, y.type)) {
+ gbString xt = type_to_string(left_type);
+ gbString yt = type_to_string(y.type);
+ error(y.expr, "Mismatched types, expected (%s), got (%s)", xt, yt);
+ gb_string_free(yt);
+ gb_string_free(xt);
+ }
+ } else {
+ check_assignment(c, &y, left_type, name);
+ }
}
} else {
check_or_else_expr_no_value_error(c, name, x, type_hint);
@@ -9389,7 +9441,7 @@ gb_internal bool is_expr_inferred_fixed_array(Ast *type_expr) {
}
gb_internal bool check_for_dynamic_literals(CheckerContext *c, Ast *node, AstCompoundLit *cl) {
- if (cl->elems.count > 0 && (check_feature_flags(c, node) & OptInFeatureFlag_DynamicLiterals) == 0) {
+ if (cl->elems.count > 0 && (check_feature_flags(c, node) & OptInFeatureFlag_DynamicLiterals) == 0 && !build_context.dynamic_literals) {
ERROR_BLOCK();
error(node, "Compound literals of dynamic types are disabled by default");
error_line("\tSuggestion: If you want to enable them for this specific file, add '#+feature dynamic-literals' at the top of the file\n");
@@ -10972,7 +11024,7 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast
return kind;
case_end;
- case_ast_node(i, Implicit, node)
+ case_ast_node(i, Implicit, node);
switch (i->kind) {
case Token_context:
{
diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp
index e81996566..0460f5bec 100644
--- a/src/check_stmt.cpp
+++ b/src/check_stmt.cpp
@@ -2108,10 +2108,12 @@ gb_internal void check_value_decl_stmt(CheckerContext *ctx, Ast *node, u32 mod_f
if (init_type == nullptr) {
init_type = t_invalid;
} else if (is_type_polymorphic(base_type(init_type))) {
+ /* DISABLED: This error seems too aggressive for instantiated generic types.
gbString str = type_to_string(init_type);
error(vd->type, "Invalid use of a polymorphic type '%s' in variable declaration", str);
gb_string_free(str);
init_type = t_invalid;
+ */
}
if (init_type == t_invalid && entity_count == 1 && (mod_flags & (Stmt_BreakAllowed|Stmt_FallthroughAllowed))) {
Entity *e = entities[0];
@@ -2755,6 +2757,47 @@ gb_internal void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags)
if (ctx->decl) {
ctx->decl->defer_used += 1;
}
+
+ // NOTE(bill): Handling errors/warnings
+
+ Ast *stmt = ds->stmt;
+ Ast *original_stmt = stmt;
+
+ bool is_singular = true;
+ while (is_singular && stmt->kind == Ast_BlockStmt) {
+ Ast *inner_stmt = nullptr;
+ for (Ast *s : stmt->BlockStmt.stmts) {
+ if (s->kind == Ast_EmptyStmt) {
+ continue;
+ }
+ if (inner_stmt != nullptr) {
+ is_singular = false;
+ break;
+ }
+ inner_stmt = s;
+ }
+
+ if (inner_stmt != nullptr) {
+ stmt = inner_stmt;
+ }
+ }
+ if (!is_singular) {
+ stmt = original_stmt;
+ }
+
+ switch (stmt->kind) {
+ case_ast_node(as, AssignStmt, stmt);
+ if (as->op.kind != Token_Eq) {
+ break;
+ }
+ for (Ast *lhs : as->lhs) {
+ Entity *e = entity_of_node(lhs);
+ if (e && e->flags & EntityFlag_Result) {
+ error(lhs, "Assignments to named return values within 'defer' will not affect the value that is returned");
+ }
+ }
+ case_end;
+ }
}
case_end;
diff --git a/src/check_type.cpp b/src/check_type.cpp
index 9d4defbb2..431698459 100644
--- a/src/check_type.cpp
+++ b/src/check_type.cpp
@@ -1155,8 +1155,7 @@ gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type,
}
}
-
-
+ #if 0 // Reconsider at a later date
if (bit_sizes.count > 0 && is_type_integer(backing_type)) {
bool all_booleans = is_type_boolean(fields[0]->type);
bool all_ones = bit_sizes[0] == 1;
@@ -1182,7 +1181,7 @@ gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type,
}
}
}
-
+ #endif
bit_field_type->BitField.fields = slice_from_array(fields);
bit_field_type->BitField.bit_sizes = slice_from_array(bit_sizes);
@@ -1911,9 +1910,18 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para
case ParameterValue_Location:
case ParameterValue_Expression:
case ParameterValue_Value:
+ // Special case for polymorphic procedures as default values
+ if (param_value.ast_value != nullptr) {
+ Entity *e = entity_from_expr(param_value.ast_value);
+ if (e != nullptr && e->kind == Entity_Procedure && is_type_polymorphic(e->type)) {
+ // Allow polymorphic procedures as default parameter values
+ // The type will be correctly determined at call site
+ break;
+ }
+ }
gbString str = type_to_string(type);
error(params[i], "A default value for a parameter must not be a polymorphic constant type, got %s", str);
- gb_string_free(str);
+ gb_string_free(str);
break;
}
}
@@ -2082,7 +2090,9 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para
if (type != t_invalid && !check_is_assignable_to(ctx, &op, type, allow_array_programming)) {
bool ok = true;
if (p->flags&FieldFlag_any_int) {
- if ((!is_type_integer(op.type) && !is_type_enum(op.type)) || (!is_type_integer(type) && !is_type_enum(type))) {
+ if (op.type == nullptr) {
+ ok = false;
+ } else if ((!is_type_integer(op.type) && !is_type_enum(op.type)) || (!is_type_integer(type) && !is_type_enum(type))) {
ok = false;
} else if (!check_is_castable_to(ctx, &op, type)) {
ok = false;
@@ -2773,6 +2783,21 @@ gb_internal void add_map_key_type_dependencies(CheckerContext *ctx, Type *key) {
return;
}
+ if (key->kind == Type_Basic) {
+ if (key->Basic.flags & BasicFlag_Quaternion) {
+ add_package_dependency(ctx, "runtime", "default_hasher_f64");
+ add_package_dependency(ctx, "runtime", "default_hasher_quaternion256");
+ return;
+ } else if (key->Basic.flags & BasicFlag_Complex) {
+ add_package_dependency(ctx, "runtime", "default_hasher_f64");
+ add_package_dependency(ctx, "runtime", "default_hasher_complex128");
+ return;
+ } else if (key->Basic.flags & BasicFlag_Float) {
+ add_package_dependency(ctx, "runtime", "default_hasher_f64");
+ return;
+ }
+ }
+
if (key->kind == Type_Struct) {
add_package_dependency(ctx, "runtime", "default_hasher");
for_array(i, key->Struct.fields) {
@@ -3281,8 +3306,11 @@ gb_internal void check_array_type_internal(CheckerContext *ctx, Ast *e, Type **t
if (generic_type != nullptr) {
// Ignore
} else if (count < 1 || !is_power_of_two(count)) {
- error(at->count, "Invalid length for #simd, expected a power of two length, got '%lld'", cast(long long)count);
*type = alloc_type_array(elem, count, generic_type);
+ if (ctx->disallow_polymorphic_return_types && count == 0) {
+ return;
+ }
+ error(at->count, "Invalid length for #simd, expected a power of two length, got '%lld'", cast(long long)count);
return;
}
@@ -3502,6 +3530,17 @@ gb_internal bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, T
elem = o.type;
}
+ if (!ctx->in_polymorphic_specialization && ctx->disallow_polymorphic_return_types) {
+ Type *t = base_type(elem);
+ if (t != nullptr &&
+ unparen_expr(pt->type)->kind == Ast_Ident &&
+ is_type_polymorphic_record_unspecialized(t)) {
+ gbString err_str = expr_to_string(e);
+ error(e, "Invalid use of a non-specialized polymorphic type '%s'", err_str);
+ gb_string_free(err_str);
+ }
+ }
+
if (pt->tag != nullptr) {
GB_ASSERT(pt->tag->kind == Ast_BasicDirective);
diff --git a/src/checker.cpp b/src/checker.cpp
index c44c6ce5b..aaa815365 100644
--- a/src/checker.cpp
+++ b/src/checker.cpp
@@ -728,12 +728,17 @@ gb_internal void check_scope_usage_internal(Checker *c, Scope *scope, u64 vet_fl
bool is_unused = false;
if (vet_unused && check_vet_unused(c, e, &ve_unused)) {
is_unused = true;
- } else if (vet_unused_procedures &&
- e->kind == Entity_Procedure) {
+ } else if (vet_unused_procedures && e->kind == Entity_Procedure) {
if (e->flags&EntityFlag_Used) {
is_unused = false;
} else if (e->flags & EntityFlag_Require) {
is_unused = false;
+ } else if (e->flags & EntityFlag_Init) {
+ is_unused = false;
+ } else if (e->flags & EntityFlag_Fini) {
+ is_unused = false;
+ } else if (e->Procedure.is_export) {
+ is_unused = false;
} else if (e->pkg && e->pkg->kind == Package_Init && e->token.string == "main") {
is_unused = false;
} else {
@@ -751,7 +756,7 @@ gb_internal void check_scope_usage_internal(Checker *c, Scope *scope, u64 vet_fl
array_add(&vetted_entities, ve_unused);
} else if (is_shadowed) {
array_add(&vetted_entities, ve_shadowed);
- } else if (e->kind == Entity_Variable && (e->flags & (EntityFlag_Param|EntityFlag_Using|EntityFlag_Static)) == 0 && !e->Variable.is_global) {
+ } else if (e->kind == Entity_Variable && (e->flags & (EntityFlag_Param|EntityFlag_Using|EntityFlag_Static|EntityFlag_Field)) == 0 && !e->Variable.is_global) {
i64 sz = type_size_of(e->type);
// TODO(bill): When is a good size warn?
// Is >256 KiB good enough?
@@ -1351,10 +1356,12 @@ gb_internal void init_universal(void) {
t_objc_object = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_object"), alloc_type_struct_complete());
t_objc_selector = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_selector"), alloc_type_struct_complete());
t_objc_class = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_class"), alloc_type_struct_complete());
+ t_objc_ivar = add_global_type_name(intrinsics_pkg->scope, str_lit("objc_ivar"), alloc_type_struct_complete());
t_objc_id = alloc_type_pointer(t_objc_object);
t_objc_SEL = alloc_type_pointer(t_objc_selector);
t_objc_Class = alloc_type_pointer(t_objc_class);
+ t_objc_Ivar = alloc_type_pointer(t_objc_ivar);
}
}
@@ -1387,6 +1394,10 @@ gb_internal void init_checker_info(CheckerInfo *i) {
array_init(&i->defineables, a);
map_init(&i->objc_msgSend_types);
+ mpsc_init(&i->objc_class_implementations, a);
+ string_set_init(&i->obcj_class_name_set, 0);
+ map_init(&i->objc_method_implementations);
+
string_map_init(&i->load_file_cache);
array_init(&i->all_procedures, heap_allocator());
@@ -1497,6 +1508,8 @@ gb_internal void init_checker(Checker *c) {
TIME_SECTION("init proc queues");
mpsc_init(&c->procs_with_deferred_to_check, a); //, 1<<10);
+ mpsc_init(&c->procs_with_objc_context_provider_to_check, a);
+
// NOTE(bill): 1 Mi elements should be enough on average
array_init(&c->procs_to_check, heap_allocator(), 0, 1<<20);
@@ -3662,6 +3675,33 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) {
}
}
return true;
+ } else if (name == "objc_implement") {
+ ExactValue ev = check_decl_attribute_value(c, value);
+ if (ev.kind == ExactValue_Bool) {
+ ac->objc_is_implementation = ev.value_bool;
+
+ if (!ac->objc_is_implementation) {
+ ac->objc_is_disabled_implement = true;
+ }
+ } else if (ev.kind == ExactValue_Invalid) {
+ ac->objc_is_implementation = true;
+ } else {
+ error(elem, "Expected a boolean value, or no value, for '%.*s'", LIT(name));
+ }
+
+ return true;
+ } else if (name == "objc_selector") {
+ ExactValue ev = check_decl_attribute_value(c, value);
+ if (ev.kind == ExactValue_String) {
+ if (string_is_valid_identifier(ev.value_string)) {
+ ac->objc_selector = ev.value_string;
+ } else {
+ error(elem, "Invalid identifier for '%.*s', got '%.*s'", LIT(name), LIT(ev.value_string));
+ }
+ } else {
+ error(elem, "Expected a string value for '%.*s'", LIT(name));
+ }
+ return true;
} else if (name == "require_target_feature") {
ExactValue ev = check_decl_attribute_value(c, value);
if (ev.kind == ExactValue_String) {
@@ -3711,6 +3751,12 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) {
}
ac->instrumentation_exit = true;
return true;
+ } else if (name == "no_sanitize_address") {
+ if (value != nullptr) {
+ error(value, "'%.*s' expects no parameter", LIT(name));
+ }
+ ac->no_sanitize_address = true;
+ return true;
}
return false;
}
@@ -3901,6 +3947,51 @@ gb_internal DECL_ATTRIBUTE_PROC(type_decl_attribute) {
ac->objc_class = ev.value_string;
}
return true;
+ } else if (name == "objc_implement") {
+ ExactValue ev = check_decl_attribute_value(c, value);
+ if (ev.kind == ExactValue_Bool) {
+ ac->objc_is_implementation = ev.value_bool;
+ } else if (ev.kind == ExactValue_Invalid) {
+ ac->objc_is_implementation = true;
+ } else {
+ error(elem, "Expected a boolean value, or no value, for '%.*s'", LIT(name));
+ }
+ return true;
+ } else if (name == "objc_superclass") {
+ Type *objc_superclass = check_type(c, value);
+
+ if (objc_superclass != nullptr) {
+ ac->objc_superclass = objc_superclass;
+ } else {
+ error(value, "'%.*s' expected a named type", LIT(name));
+ }
+ return true;
+ } else if (name == "objc_ivar") {
+ Type *objc_ivar = check_type(c, value);
+
+ if (objc_ivar != nullptr && objc_ivar->kind == Type_Named) {
+ ac->objc_ivar = objc_ivar;
+ } else {
+ error(value, "'%.*s' expected a named type", LIT(name));
+ }
+ return true;
+ } else if (name == "objc_context_provider") {
+ Operand o = {};
+ check_expr(c, &o, value);
+ Entity *e = entity_of_node(o.expr);
+
+ if (e != nullptr) {
+ if (ac->objc_context_provider != nullptr) {
+ error(elem, "Previous usage of a 'objc_context_provider' attribute");
+ }
+ if (e->kind != Entity_Procedure) {
+ error(elem, "'objc_context_provider' must refer to a procedure");
+ } else {
+ ac->objc_context_provider = e;
+ }
+
+ return true;
+ }
}
return false;
}
@@ -6234,6 +6325,12 @@ gb_internal void check_deferred_procedures(Checker *c) {
continue;
}
+ if (dst->flags & EntityFlag_Disabled) {
+ // Prevent procedures that have been disabled from acting as deferrals.
+ src->Procedure.deferred_procedure = {};
+ continue;
+ }
+
GB_ASSERT(is_type_proc(src->type));
GB_ASSERT(is_type_proc(dst->type));
Type *src_params = base_type(src->type)->Proc.params;
@@ -6389,6 +6486,44 @@ gb_internal void check_deferred_procedures(Checker *c) {
}
+gb_internal void check_objc_context_provider_procedures(Checker *c) {
+ for (Entity *e = nullptr; mpsc_dequeue(&c->procs_with_objc_context_provider_to_check, &e); /**/) {
+ GB_ASSERT(e->kind == Entity_TypeName);
+
+ Entity *proc_entity = e->TypeName.objc_context_provider;
+ GB_ASSERT(proc_entity->kind == Entity_Procedure);
+
+ auto &proc = proc_entity->type->Proc;
+
+ Type *return_type = proc.result_count != 1 ? t_untyped_nil : base_named_type(proc.results->Tuple.variables[0]->type);
+ if (return_type != t_context) {
+ error(proc_entity->token, "The @(objc_context_provider) procedure must only return a context.");
+ }
+
+ const char *self_param_err = "The @(objc_context_provider) procedure must take as a parameter a single pointer to the @(objc_type) value.";
+ if (proc.param_count != 1) {
+ error(proc_entity->token, self_param_err);
+ }
+
+ Type *self_param = base_type(proc.params->Tuple.variables[0]->type);
+ if (self_param->kind != Type_Pointer) {
+ error(proc_entity->token, self_param_err);
+ }
+
+ Type *self_type = base_named_type(self_param->Pointer.elem);
+ if (!internal_check_is_assignable_to(self_type, e->type) &&
+ !(e->TypeName.objc_ivar && internal_check_is_assignable_to(self_type, e->TypeName.objc_ivar))) {
+ error(proc_entity->token, self_param_err);
+ }
+ if (proc.calling_convention != ProcCC_CDecl && proc.calling_convention != ProcCC_Contextless) {
+ error(e->token, self_param_err);
+ }
+ if (proc.is_polymorphic) {
+ error(e->token, self_param_err);
+ }
+ }
+}
+
gb_internal void check_unique_package_names(Checker *c) {
ERROR_BLOCK();
@@ -6423,6 +6558,19 @@ gb_internal void check_unique_package_names(Checker *c) {
"\tThere is no relation between a package name and the directory that contains it, so they can be completely different\n"
"\tA package name is required for link name prefixing to have a consistent ABI\n");
error_line("%s found at previous location\n", token_pos_to_string(ast_token(prev).pos));
+
+ // NOTE(Jeroen): Check if the conflicting imports are the same case-folded directory
+ // See https://github.com/odin-lang/Odin/issues/5080
+ #if defined(GB_SYSTEM_WINDOWS)
+ String dir_a = pkg->files[0]->directory;
+ String dir_b = (*found)->files[0]->directory;
+
+ if (str_eq_ignore_case(dir_a, dir_b)) {
+ error_line("\tRemember that Windows case-folds paths, and so %.*s and %.*s are the same directory.\n", LIT(dir_a), LIT(dir_b));
+ // Could also perform a FS lookup to check which of the two is the actual directory and suggest it, but this should be enough.
+ }
+ #endif
+
end_error_block();
}
}
@@ -6536,6 +6684,7 @@ gb_internal void check_update_dependency_tree_for_procedures(Checker *c) {
}
}
+
gb_internal void check_parsed_files(Checker *c) {
TIME_SECTION("map full filepaths to scope");
add_type_info_type(&c->builtin_ctx, t_invalid);
@@ -6645,6 +6794,9 @@ gb_internal void check_parsed_files(Checker *c) {
TIME_SECTION("check deferred procedures");
check_deferred_procedures(c);
+ TIME_SECTION("check objc context provider procedures");
+ check_objc_context_provider_procedures(c);
+
TIME_SECTION("calculate global init order");
calculate_global_init_order(c);
diff --git a/src/checker.hpp b/src/checker.hpp
index d3b2d7d89..0cdfd69ab 100644
--- a/src/checker.hpp
+++ b/src/checker.hpp
@@ -139,6 +139,7 @@ struct AttributeContext {
bool entry_point_only : 1;
bool instrumentation_enter : 1;
bool instrumentation_exit : 1;
+ bool no_sanitize_address : 1;
bool rodata : 1;
bool ignore_duplicates : 1;
u32 optimization_mode; // ProcedureOptimizationMode
@@ -148,8 +149,14 @@ struct AttributeContext {
String objc_class;
String objc_name;
- bool objc_is_class_method;
+ String objc_selector;
Type * objc_type;
+ Type * objc_superclass;
+ Type * objc_ivar;
+ Entity *objc_context_provider;
+ bool objc_is_class_method;
+ bool objc_is_implementation; // This struct or proc provides a class/method implementation, not a binding to an existing type.
+ bool objc_is_disabled_implement; // This means the method explicitly set @objc_implement to false so it won't be inferred from the class' attribute.
String require_target_feature; // required by the target micro-architecture
String enable_target_feature; // will be enabled for the procedure only
@@ -365,6 +372,11 @@ struct ObjcMsgData {
Type *proc_type;
};
+struct ObjcMethodData {
+ AttributeContext ac;
+ Entity *proc_entity;
+};
+
enum LoadFileTier {
LoadFileTier_Invalid,
LoadFileTier_Exists,
@@ -476,9 +488,17 @@ struct CheckerInfo {
MPSCQueue intrinsics_entry_point_usage;
- BlockingMutex objc_types_mutex;
+ BlockingMutex objc_objc_msgSend_mutex;
PtrMap objc_msgSend_types;
+ BlockingMutex objc_class_name_mutex;
+ StringSet obcj_class_name_set;
+ MPSCQueue objc_class_implementations;
+
+ BlockingMutex objc_method_mutex;
+ PtrMap> objc_method_implementations;
+
+
BlockingMutex load_file_mutex;
StringMap load_file_cache;
@@ -555,6 +575,7 @@ struct Checker {
CheckerContext builtin_ctx;
MPSCQueue procs_with_deferred_to_check;
+ MPSCQueue procs_with_objc_context_provider_to_check;
Array procs_to_check;
BlockingMutex nested_proc_lits_mutex;
@@ -629,4 +650,4 @@ gb_internal void add_untyped_expressions(CheckerInfo *cinfo, UntypedExprInfoMap
gb_internal GenTypesData *ensure_polymorphic_record_entity_has_gen_types(CheckerContext *ctx, Type *original_type);
-gb_internal void init_map_internal_types(Type *type);
\ No newline at end of file
+gb_internal void init_map_internal_types(Type *type);
diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp
index 40dde8240..f3b55daa4 100644
--- a/src/checker_builtin_procs.hpp
+++ b/src/checker_builtin_procs.hpp
@@ -26,6 +26,7 @@ enum BuiltinProcId {
BuiltinProc_conj,
BuiltinProc_expand_values,
+ BuiltinProc_compress_values,
BuiltinProc_min,
BuiltinProc_max,
@@ -170,8 +171,12 @@ BuiltinProc__simd_begin,
BuiltinProc_simd_extract,
BuiltinProc_simd_replace,
+ BuiltinProc_simd_reduce_add_bisect,
+ BuiltinProc_simd_reduce_mul_bisect,
BuiltinProc_simd_reduce_add_ordered,
BuiltinProc_simd_reduce_mul_ordered,
+ BuiltinProc_simd_reduce_add_pairs,
+ BuiltinProc_simd_reduce_mul_pairs,
BuiltinProc_simd_reduce_min,
BuiltinProc_simd_reduce_max,
BuiltinProc_simd_reduce_and,
@@ -205,6 +210,9 @@ BuiltinProc__simd_begin,
BuiltinProc_simd_masked_expand_load,
BuiltinProc_simd_masked_compress_store,
+ BuiltinProc_simd_indices,
+
+
// Platform specific SIMD intrinsics
BuiltinProc_simd_x86__MM_SHUFFLE,
BuiltinProc__simd_end,
@@ -227,6 +235,9 @@ BuiltinProc__type_begin,
BuiltinProc_type_convert_variants_to_pointers,
BuiltinProc_type_merge,
+ BuiltinProc_type_integer_to_unsigned,
+ BuiltinProc_type_integer_to_signed,
+
BuiltinProc__type_simple_boolean_begin,
BuiltinProc_type_is_boolean,
BuiltinProc_type_is_integer,
@@ -331,6 +342,7 @@ BuiltinProc__type_end,
BuiltinProc_objc_find_class,
BuiltinProc_objc_register_selector,
BuiltinProc_objc_register_class,
+ BuiltinProc_objc_ivar_get,
BuiltinProc_constant_utf16_cstring,
@@ -368,6 +380,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
{STR_LIT("conj"), 1, false, Expr_Expr, BuiltinProcPkg_builtin},
{STR_LIT("expand_values"), 1, false, Expr_Expr, BuiltinProcPkg_builtin},
+ {STR_LIT("compress_values"), 1, true, Expr_Expr, BuiltinProcPkg_builtin},
{STR_LIT("min"), 1, true, Expr_Expr, BuiltinProcPkg_builtin},
{STR_LIT("max"), 1, true, Expr_Expr, BuiltinProcPkg_builtin},
@@ -515,8 +528,12 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
{STR_LIT("simd_extract"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("simd_replace"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+ {STR_LIT("simd_reduce_add_bisect"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+ {STR_LIT("simd_reduce_mul_bisect"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("simd_reduce_add_ordered"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("simd_reduce_mul_ordered"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+ {STR_LIT("simd_reduce_add_pairs"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+ {STR_LIT("simd_reduce_mul_pairs"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("simd_reduce_min"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("simd_reduce_max"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("simd_reduce_and"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
@@ -551,6 +568,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
{STR_LIT("simd_masked_expand_load"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("simd_masked_compress_store"), 3, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
+ {STR_LIT("simd_indices"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+
{STR_LIT("simd_x86__MM_SHUFFLE"), 4, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
@@ -569,6 +588,9 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
{STR_LIT("type_convert_variants_to_pointers"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("type_merge"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+ {STR_LIT("type_integer_to_unsigned"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+ {STR_LIT("type_integer_to_signed"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
+
{STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics},
{STR_LIT("type_is_boolean"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("type_is_integer"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
@@ -673,6 +695,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
{STR_LIT("objc_find_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
{STR_LIT("objc_register_selector"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true},
{STR_LIT("objc_register_class"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true},
+ {STR_LIT("objc_ivar_get"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics, false, true},
{STR_LIT("constant_utf16_cstring"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics},
diff --git a/src/entity.cpp b/src/entity.cpp
index b2148aa7b..a16779419 100644
--- a/src/entity.cpp
+++ b/src/entity.cpp
@@ -235,6 +235,10 @@ struct Entity {
Type * type_parameter_specialization;
String ir_mangled_name;
bool is_type_alias;
+ bool objc_is_implementation;
+ Type* objc_superclass;
+ Type* objc_ivar;
+ Entity*objc_context_provider;
String objc_class_name;
TypeNameObjCMetadata *objc_metadata;
} TypeName;
@@ -258,6 +262,7 @@ struct Entity {
bool is_memcpy_like : 1;
bool uses_branch_location : 1;
bool is_anonymous : 1;
+ bool no_sanitize_address : 1;
} Procedure;
struct {
Array entities;
diff --git a/src/exact_value.cpp b/src/exact_value.cpp
index ceaed84c1..37751c8f1 100644
--- a/src/exact_value.cpp
+++ b/src/exact_value.cpp
@@ -954,6 +954,10 @@ gb_internal bool compare_exact_values(TokenKind op, ExactValue x, ExactValue y)
case ExactValue_Float: {
f64 a = x.value_float;
f64 b = y.value_float;
+ if (isnan(a) || isnan(b)) {
+ return op == Token_NotEq;
+ }
+
switch (op) {
case Token_CmpEq: return cmp_f64(a, b) == 0;
case Token_NotEq: return cmp_f64(a, b) != 0;
diff --git a/src/gb/gb.h b/src/gb/gb.h
index 98c362e93..a1b659637 100644
--- a/src/gb/gb.h
+++ b/src/gb/gb.h
@@ -5838,18 +5838,25 @@ gb_inline isize gb_printf_err_va(char const *fmt, va_list va) {
gb_inline isize gb_fprintf_va(struct gbFile *f, char const *fmt, va_list va) {
char buf[4096];
- isize len = gb_snprintf_va(buf, gb_size_of(buf), fmt, va);
+ va_list va_save;
+ va_copy(va_save, va);
+ isize len = gb_snprintf_va(buf, gb_size_of(buf), fmt, va_save);
+ va_end(va_save);
char *new_buf = NULL;
isize n = gb_size_of(buf);
while (len < 0) {
+ va_copy(va_save, va);
+ defer (va_end(va_save));
n <<= 1;
gb_free(gb_heap_allocator(), new_buf);
new_buf = gb_alloc_array(gb_heap_allocator(), char, n);;
- len = gb_snprintf_va(new_buf, n, fmt, va);
+ len = gb_snprintf_va(new_buf, n, fmt, va_save);
}
- gb_file_write(f, buf, len-1); // NOTE(bill): prevent extra whitespace
if (new_buf != NULL) {
+ gb_file_write(f, new_buf, len-1); // NOTE(bill): prevent extra whitespace
gb_free(gb_heap_allocator(), new_buf);
+ } else {
+ gb_file_write(f, buf, len-1); // NOTE(bill): prevent extra whitespace
}
return len;
}
@@ -5912,7 +5919,7 @@ gb_internal isize gb__print_string(char *text, isize max_len, gbprivFmtInfo *inf
len = info->precision < len ? info->precision : len;
}
- res += gb_strlcpy(text, str, len);
+ res += gb_strlcpy(text, str, gb_min(len, remaining));
if (info->width > res) {
isize padding = info->width - len;
@@ -5930,7 +5937,7 @@ gb_internal isize gb__print_string(char *text, isize max_len, gbprivFmtInfo *inf
}
}
- res += gb_strlcpy(text, str, len);
+ res += gb_strlcpy(text, str, gb_min(len, remaining));
}
@@ -6066,15 +6073,16 @@ gb_internal isize gb__print_f64(char *text, isize max_len, gbprivFmtInfo *info,
gb_no_inline isize gb_snprintf_va(char *text, isize max_len, char const *fmt, va_list va) {
char const *text_begin = text;
- isize remaining = max_len, res;
+ isize remaining = max_len - 1, res;
- while (*fmt) {
+ while (*fmt && remaining > 0) {
gbprivFmtInfo info = {0};
isize len = 0;
info.precision = -1;
- while (*fmt && *fmt != '%' && remaining) {
+ while (remaining > 0 && *fmt && *fmt != '%') {
*text++ = *fmt++;
+ remaining--;
}
if (*fmt == '%') {
@@ -6240,7 +6248,7 @@ gb_no_inline isize gb_snprintf_va(char *text, isize max_len, char const *fmt, va
text += len;
if (len >= remaining) {
- remaining = gb_min(remaining, 1);
+ remaining = 0;
} else {
remaining -= len;
}
diff --git a/src/linker.cpp b/src/linker.cpp
index 5c0fe446f..41d4a13a1 100644
--- a/src/linker.cpp
+++ b/src/linker.cpp
@@ -143,7 +143,7 @@ gb_internal i32 linker_stage(LinkerData *gen) {
LIT(target_arch_names[build_context.metrics.arch])
);
#endif
- } else if (build_context.cross_compiling && build_context.different_os) {
+ } else if (build_context.cross_compiling && (build_context.different_os || selected_subtarget != Subtarget_Default)) {
switch (selected_subtarget) {
case Subtarget_Android:
is_cross_linking = true;
@@ -603,7 +603,7 @@ try_cross_linking:;
// object
// dynamic lib
// static libs, absolute full path relative to the file in which the lib was imported from
- lib_str = gb_string_append_fmt(lib_str, " %.*s ", LIT(lib));
+ lib_str = gb_string_append_fmt(lib_str, " \"%.*s\" ", LIT(lib));
} else {
// dynamic or static system lib, just link regularly searching system library paths
lib_str = gb_string_append_fmt(lib_str, " -l%.*s ", LIT(lib));
@@ -643,9 +643,10 @@ try_cross_linking:;
android_glue_object = concatenate4_strings(temporary_allocator(), temp_dir, str_lit("android_native_app_glue-"), hash, str_lit(".o"));
android_glue_static_lib = concatenate4_strings(permanent_allocator(), temp_dir, str_lit("libandroid_native_app_glue-"), hash, str_lit(".a"));
- gbString glue = gb_string_make(heap_allocator(), clang_path);
+ gbString glue = gb_string_make_length(heap_allocator(), ODIN_ANDROID_NDK_TOOLCHAIN.text, ODIN_ANDROID_NDK_TOOLCHAIN.len);
defer (gb_string_free(glue));
+ glue = gb_string_append_fmt(glue, "bin/clang");
glue = gb_string_append_fmt(glue, " --target=aarch64-linux-android%d ", ODIN_ANDROID_API_LEVEL);
glue = gb_string_appendc(glue, "-c \"");
glue = gb_string_append_length(glue, ODIN_ANDROID_NDK.text, ODIN_ANDROID_NDK.len);
@@ -655,6 +656,11 @@ try_cross_linking:;
glue = gb_string_append_length(glue, android_glue_object.text, android_glue_object.len);
glue = gb_string_appendc(glue, "\" ");
+ glue = gb_string_appendc(glue, "--sysroot \"");
+ glue = gb_string_append_length(glue, ODIN_ANDROID_NDK_TOOLCHAIN.text, ODIN_ANDROID_NDK_TOOLCHAIN.len);
+ glue = gb_string_appendc(glue, "sysroot");
+ glue = gb_string_appendc(glue, "\" ");
+
glue = gb_string_appendc(glue, "\"-I");
glue = gb_string_append_length(glue, ODIN_ANDROID_NDK_TOOLCHAIN.text, ODIN_ANDROID_NDK_TOOLCHAIN.len);
glue = gb_string_appendc(glue, "sysroot/usr/include/");
@@ -763,7 +769,17 @@ try_cross_linking:;
gbString platform_lib_str = gb_string_make(heap_allocator(), "");
defer (gb_string_free(platform_lib_str));
if (build_context.metrics.os == TargetOs_darwin) {
- platform_lib_str = gb_string_appendc(platform_lib_str, "-Wl,-syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -L/usr/local/lib ");
+ // Get the MacOSX SDK path.
+ gbString darwin_sdk_path = gb_string_make(temporary_allocator(), "");
+ if (!system_exec_command_line_app_output("xcrun --sdk macosx --show-sdk-path", &darwin_sdk_path)) {
+ darwin_sdk_path = gb_string_set(darwin_sdk_path, "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk");
+ } else {
+ // Trim the trailing newline.
+ darwin_sdk_path = gb_string_trim_space(darwin_sdk_path);
+ }
+ platform_lib_str = gb_string_append_fmt(platform_lib_str, "--sysroot %s ", darwin_sdk_path);
+
+ platform_lib_str = gb_string_appendc(platform_lib_str, "-L/usr/local/lib ");
// Homebrew's default library path, checking if it exists to avoid linking warnings.
if (gb_file_exists("/opt/homebrew/lib")) {
@@ -785,6 +801,9 @@ try_cross_linking:;
// This points the linker to where the entry point is
link_settings = gb_string_appendc(link_settings, "-e _main ");
}
+ } else if (build_context.metrics.os == TargetOs_freebsd) {
+ // FreeBSD pkg installs third-party shared libraries in /usr/local/lib.
+ platform_lib_str = gb_string_appendc(platform_lib_str, "-Wl,-L/usr/local/lib ");
} else if (build_context.metrics.os == TargetOs_openbsd) {
// OpenBSD ports install shared libraries in /usr/local/lib. Also, we must explicitly link libpthread.
platform_lib_str = gb_string_appendc(platform_lib_str, "-lpthread -Wl,-L/usr/local/lib ");
@@ -803,6 +822,9 @@ try_cross_linking:;
platform_lib_str = gb_string_append_length(platform_lib_str, ODIN_ANDROID_NDK_TOOLCHAIN_LIB_LEVEL.text, ODIN_ANDROID_NDK_TOOLCHAIN_LIB_LEVEL.len);
platform_lib_str = gb_string_appendc(platform_lib_str, "\" ");
+ platform_lib_str = gb_string_appendc(platform_lib_str, "-landroid ");
+ platform_lib_str = gb_string_appendc(platform_lib_str, "-llog ");
+
platform_lib_str = gb_string_appendc(platform_lib_str, "\"--sysroot=");
platform_lib_str = gb_string_append_length(platform_lib_str, ODIN_ANDROID_NDK_TOOLCHAIN_SYSROOT.text, ODIN_ANDROID_NDK_TOOLCHAIN_SYSROOT.len);
platform_lib_str = gb_string_appendc(platform_lib_str, "\" ");
@@ -835,11 +857,16 @@ try_cross_linking:;
}
}
- gbString link_command_line = gb_string_make(heap_allocator(), clang_path);
+ gbString link_command_line = gb_string_make(heap_allocator(), "");
defer (gb_string_free(link_command_line));
if (is_android) {
+ gbString ndk_bin_directory = gb_string_make_length(temporary_allocator(), ODIN_ANDROID_NDK_TOOLCHAIN.text, ODIN_ANDROID_NDK_TOOLCHAIN.len);
+ link_command_line = gb_string_appendc(link_command_line, ndk_bin_directory);
+ link_command_line = gb_string_appendc(link_command_line, "bin/clang");
link_command_line = gb_string_append_fmt(link_command_line, " --target=aarch64-linux-android%d ", ODIN_ANDROID_API_LEVEL);
+ } else {
+ link_command_line = gb_string_appendc(link_command_line, clang_path);
}
link_command_line = gb_string_appendc(link_command_line, " -Wno-unused-command-line-argument ");
link_command_line = gb_string_appendc(link_command_line, object_files);
diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp
index 6d9f6d958..af08722c3 100644
--- a/src/llvm_abi.cpp
+++ b/src/llvm_abi.cpp
@@ -573,7 +573,9 @@ namespace lbAbiAmd64SysV {
gb_internal void classify_with(LLVMTypeRef t, Array *cls, i64 ix, i64 off);
gb_internal void fixup(LLVMTypeRef t, Array *cls);
- gb_internal lbArgType amd64_type(LLVMContextRef c, LLVMTypeRef type, Amd64TypeAttributeKind attribute_kind, ProcCallingConvention calling_convention);
+ gb_internal lbArgType amd64_type(LLVMContextRef c, LLVMTypeRef type, Amd64TypeAttributeKind attribute_kind, ProcCallingConvention calling_convention,
+ bool is_arg,
+ i32 *int_regs, i32 *sse_regs);
gb_internal Array classify(LLVMTypeRef t);
gb_internal LLVMTypeRef llreg(LLVMContextRef c, Array const ®_classes, LLVMTypeRef type);
@@ -583,7 +585,9 @@ namespace lbAbiAmd64SysV {
}
LB_ABI_MODIFY_RETURN_IF_TUPLE_MACRO();
- return amd64_type(c, return_type, Amd64TypeAttribute_StructRect, ft->calling_convention);
+ return amd64_type(c, return_type, Amd64TypeAttribute_StructRect, ft->calling_convention,
+ false,
+ nullptr, nullptr);
}
gb_internal LB_ABI_INFO(abi_info) {
@@ -592,10 +596,16 @@ namespace lbAbiAmd64SysV {
ft->ctx = c;
ft->calling_convention = calling_convention;
+ i32 int_regs = 6; // rdi, rsi, rdx, rcx, r8, r9
+ i32 sse_regs = 8; // xmm0-xmm7
+
ft->args = array_make(lb_function_type_args_allocator(), arg_count);
for (unsigned i = 0; i < arg_count; i++) {
- ft->args[i] = amd64_type(c, arg_types[i], Amd64TypeAttribute_ByVal, calling_convention);
+ ft->args[i] = amd64_type(c, arg_types[i], Amd64TypeAttribute_ByVal, calling_convention,
+ true,
+ &int_regs, &sse_regs);
}
+
ft->ret = compute_return_type(ft, c, return_type, return_is_defined, return_is_tuple);
return ft;
@@ -654,17 +664,79 @@ namespace lbAbiAmd64SysV {
}
- gb_internal lbArgType amd64_type(LLVMContextRef c, LLVMTypeRef type, Amd64TypeAttributeKind attribute_kind, ProcCallingConvention calling_convention) {
+
+ gb_internal bool is_aggregate(LLVMTypeRef type) {
+ LLVMTypeKind kind = LLVMGetTypeKind(type);
+ switch (kind) {
+ case LLVMStructTypeKind:
+ if (LLVMCountStructElementTypes(type) == 1) {
+ return is_aggregate(LLVMStructGetTypeAtIndex(type, 0));
+ }
+ return true;
+ case LLVMArrayTypeKind:
+ if (LLVMGetArrayLength(type) == 1) {
+ return is_aggregate(LLVMGetElementType(type));
+ }
+ return true;
+ }
+ return false;
+ };
+
+ gb_internal lbArgType amd64_type(LLVMContextRef c, LLVMTypeRef type, Amd64TypeAttributeKind attribute_kind, ProcCallingConvention calling_convention,
+ bool is_arg,
+ i32 *int_regs, i32 *sse_regs) {
+ auto cls = classify(type);
+ i32 needed_int = 0;
+ i32 needed_sse = 0;
+ for (auto c : cls) {
+ switch (c) {
+ case RegClass_Int:
+ needed_int += 1;
+ break;
+ case RegClass_SSEFs:
+ case RegClass_SSEFv:
+ case RegClass_SSEDs:
+ case RegClass_SSEDv:
+ case RegClass_SSEInt8:
+ case RegClass_SSEInt16:
+ case RegClass_SSEInt32:
+ case RegClass_SSEInt64:
+ case RegClass_SSEInt128:
+ case RegClass_SSEUp:
+ needed_sse += 1;
+ break;
+ }
+ }
+
+ bool ran_out_of_regs = false;
+ if (int_regs && sse_regs) {
+ *int_regs -= needed_int;
+ *sse_regs -= needed_sse;
+ bool int_ok = *int_regs >= 0;
+ bool sse_ok = *sse_regs >= 0;
+
+ *int_regs = gb_max(*int_regs, 0);
+ *sse_regs = gb_max(*sse_regs, 0);
+
+ if ((!int_ok || !sse_ok) && is_aggregate(type)) {
+ ran_out_of_regs = true;
+ }
+ }
+
if (is_register(type)) {
LLVMAttributeRef attribute = nullptr;
if (type == LLVMInt1TypeInContext(c)) {
attribute = lb_create_enum_attribute(c, "zeroext");
}
return lb_arg_type_direct(type, nullptr, nullptr, attribute);
- }
-
- auto cls = classify(type);
- if (is_mem_cls(cls, attribute_kind)) {
+ } else if (ran_out_of_regs) {
+ if (is_arg) {
+ return lb_arg_type_indirect_byval(c, type);
+ } else {
+ LLVMAttributeRef attribute = lb_create_enum_attribute_with_type(c, "sret", type);
+ return lb_arg_type_indirect(type, attribute);
+ }
+ } else if (is_mem_cls(cls, attribute_kind)) {
LLVMAttributeRef attribute = nullptr;
if (attribute_kind == Amd64TypeAttribute_ByVal) {
if (is_calling_convention_odin(calling_convention)) {
@@ -905,7 +977,7 @@ namespace lbAbiAmd64SysV {
return types[0];
}
- return LLVMStructTypeInContext(c, types.data, cast(unsigned)types.count, sz == 0);
+ return LLVMStructTypeInContext(c, types.data, cast(unsigned)types.count, false);
}
gb_internal void classify_with(LLVMTypeRef t, Array *cls, i64 ix, i64 off) {
@@ -1159,38 +1231,24 @@ namespace lbAbiArm64 {
}
} else {
i64 size = lb_sizeof(return_type);
- if (size <= 16) {
- LLVMTypeRef cast_type = nullptr;
-
- if (size == 0) {
- cast_type = LLVMStructTypeInContext(c, nullptr, 0, false);
- } else if (size <= 8) {
- cast_type = LLVMIntTypeInContext(c, cast(unsigned)(size*8));
- } else {
- unsigned count = cast(unsigned)((size+7)/8);
-
- LLVMTypeRef llvm_i64 = LLVMIntTypeInContext(c, 64);
- LLVMTypeRef *types = gb_alloc_array(temporary_allocator(), LLVMTypeRef, count);
-
- i64 size_copy = size;
- for (unsigned i = 0; i < count; i++) {
- if (size_copy >= 8) {
- types[i] = llvm_i64;
- } else {
- types[i] = LLVMIntTypeInContext(c, 8*cast(unsigned)size_copy);
- }
- size_copy -= 8;
- }
- GB_ASSERT(size_copy <= 0);
- cast_type = LLVMStructTypeInContext(c, types, count, true);
- }
- return lb_arg_type_direct(return_type, cast_type, nullptr, nullptr);
- } else {
+ if (size > 16) {
LB_ABI_MODIFY_RETURN_IF_TUPLE_MACRO();
LLVMAttributeRef attr = lb_create_enum_attribute_with_type(c, "sret", return_type);
return lb_arg_type_indirect(return_type, attr);
}
+
+ GB_ASSERT(size <= 16);
+ LLVMTypeRef cast_type = nullptr;
+ if (size == 0) {
+ cast_type = LLVMStructTypeInContext(c, nullptr, 0, false);
+ } else if (size <= 8) {
+ cast_type = LLVMIntTypeInContext(c, cast(unsigned)(size*8));
+ } else {
+ LLVMTypeRef llvm_i64 = LLVMIntTypeInContext(c, 64);
+ cast_type = llvm_array_type(llvm_i64, 2);
+ }
+ return lb_arg_type_direct(return_type, cast_type, nullptr, nullptr);
}
}
@@ -1818,7 +1876,8 @@ gb_internal LB_ABI_INFO(lb_get_abi_info) {
return_type, return_is_defined,
ALLOW_SPLIT_MULTI_RETURNS && return_is_tuple && is_calling_convention_odin(calling_convention),
calling_convention,
- base_type(original_type));
+ base_type(original_type)
+ );
// NOTE(bill): this is handled here rather than when developing the type in `lb_type_internal_for_procedures_raw`
diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp
index 396b94f98..c73552d57 100644
--- a/src/llvm_backend.cpp
+++ b/src/llvm_backend.cpp
@@ -563,6 +563,53 @@ gb_internal lbValue lb_hasher_proc_for_type(lbModule *m, Type *type) {
lbValue res = lb_emit_runtime_call(p, "default_hasher_string", args);
lb_add_callsite_force_inline(p, res);
LLVMBuildRet(p->builder, res.value);
+ } else if (is_type_float(type)) {
+ lbValue ptr = lb_emit_conv(p, data, pt);
+ lbValue v = lb_emit_load(p, ptr);
+ v = lb_emit_conv(p, v, t_f64);
+
+ auto args = array_make(temporary_allocator(), 2);
+ args[0] = v;
+ args[1] = seed;
+ lbValue res = lb_emit_runtime_call(p, "default_hasher_f64", args);
+ lb_add_callsite_force_inline(p, res);
+ LLVMBuildRet(p->builder, res.value);
+ } else if (is_type_complex(type)) {
+ lbValue ptr = lb_emit_conv(p, data, pt);
+ lbValue xp = lb_emit_struct_ep(p, ptr, 0);
+ lbValue yp = lb_emit_struct_ep(p, ptr, 1);
+
+ lbValue x = lb_emit_conv(p, lb_emit_load(p, xp), t_f64);
+ lbValue y = lb_emit_conv(p, lb_emit_load(p, yp), t_f64);
+
+ auto args = array_make(temporary_allocator(), 3);
+ args[0] = x;
+ args[1] = y;
+ args[2] = seed;
+ lbValue res = lb_emit_runtime_call(p, "default_hasher_complex128", args);
+ lb_add_callsite_force_inline(p, res);
+ LLVMBuildRet(p->builder, res.value);
+ } else if (is_type_quaternion(type)) {
+ lbValue ptr = lb_emit_conv(p, data, pt);
+ lbValue xp = lb_emit_struct_ep(p, ptr, 0);
+ lbValue yp = lb_emit_struct_ep(p, ptr, 1);
+ lbValue zp = lb_emit_struct_ep(p, ptr, 2);
+ lbValue wp = lb_emit_struct_ep(p, ptr, 3);
+
+ lbValue x = lb_emit_conv(p, lb_emit_load(p, xp), t_f64);
+ lbValue y = lb_emit_conv(p, lb_emit_load(p, yp), t_f64);
+ lbValue z = lb_emit_conv(p, lb_emit_load(p, zp), t_f64);
+ lbValue w = lb_emit_conv(p, lb_emit_load(p, wp), t_f64);
+
+ auto args = array_make(temporary_allocator(), 5);
+ args[0] = x;
+ args[1] = y;
+ args[2] = z;
+ args[3] = w;
+ args[4] = seed;
+ lbValue res = lb_emit_runtime_call(p, "default_hasher_quaternion256", args);
+ lb_add_callsite_force_inline(p, res);
+ LLVMBuildRet(p->builder, res.value);
} else {
GB_PANIC("Unhandled type for hasher: %s", type_to_string(type));
}
@@ -1126,6 +1173,344 @@ gb_internal lbProcedure *lb_create_objc_names(lbModule *main_module) {
return p;
}
+String lb_get_objc_type_encoding(Type *t, isize pointer_depth = 0) {
+ // NOTE(harold): See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100
+
+ // NOTE(harold): Darwin targets are always 64-bit. Should we drop this and assume "q" always?
+ #define INT_SIZE_ENCODING (build_context.metrics.int_size == 4 ? "i" : "q")
+ switch (t->kind) {
+ case Type_Basic: {
+ switch (t->Basic.kind) {
+ case Basic_Invalid:
+ return str_lit("?");
+
+ case Basic_llvm_bool:
+ case Basic_bool:
+ case Basic_b8:
+ return str_lit("B");
+
+ case Basic_b16:
+ return str_lit("C");
+ case Basic_b32:
+ return str_lit("I");
+ case Basic_b64:
+ return str_lit("q");
+ case Basic_i8:
+ return str_lit("c");
+ case Basic_u8:
+ return str_lit("C");
+ case Basic_i16:
+ case Basic_i16le:
+ case Basic_i16be:
+ return str_lit("s");
+ case Basic_u16:
+ case Basic_u16le:
+ case Basic_u16be:
+ return str_lit("S");
+ case Basic_i32:
+ case Basic_i32le:
+ case Basic_i32be:
+ return str_lit("i");
+ case Basic_u32le:
+ case Basic_u32:
+ case Basic_u32be:
+ return str_lit("I");
+ case Basic_i64:
+ case Basic_i64le:
+ case Basic_i64be:
+ return str_lit("q");
+ case Basic_u64:
+ case Basic_u64le:
+ case Basic_u64be:
+ return str_lit("Q");
+ case Basic_i128:
+ case Basic_i128le:
+ case Basic_i128be:
+ return str_lit("t");
+ case Basic_u128:
+ case Basic_u128le:
+ case Basic_u128be:
+ return str_lit("T");
+ case Basic_rune:
+ return str_lit("I");
+ case Basic_f16:
+ case Basic_f16le:
+ case Basic_f16be:
+ return str_lit("s"); // @harold: Closest we've got?
+ case Basic_f32:
+ case Basic_f32le:
+ case Basic_f32be:
+ return str_lit("f");
+ case Basic_f64:
+ case Basic_f64le:
+ case Basic_f64be:
+ return str_lit("d");
+
+ case Basic_complex32: return str_lit("{complex32=ss}"); // No f16 encoding, so fallback to i16, as above in Basic_f16*
+ case Basic_complex64: return str_lit("{complex64=ff}");
+ case Basic_complex128: return str_lit("{complex128=dd}");
+ case Basic_quaternion64: return str_lit("{quaternion64=ssss}");
+ case Basic_quaternion128: return str_lit("{quaternion128=ffff}");
+ case Basic_quaternion256: return str_lit("{quaternion256=dddd}");
+
+ case Basic_int:
+ return str_lit(INT_SIZE_ENCODING);
+ case Basic_uint:
+ return build_context.metrics.int_size == 4 ? str_lit("I") : str_lit("Q");
+ case Basic_uintptr:
+ case Basic_rawptr:
+ return str_lit("^v");
+
+ case Basic_string:
+ return build_context.metrics.int_size == 4 ? str_lit("{string=*i}") : str_lit("{string=*q}");
+
+ case Basic_cstring: return str_lit("*");
+ case Basic_any: return str_lit("{any=^v^v}"); // rawptr + ^Type_Info
+
+ case Basic_typeid:
+ GB_ASSERT(t->Basic.size == 8);
+ return str_lit("q");
+
+ // Untyped types
+ case Basic_UntypedBool:
+ case Basic_UntypedInteger:
+ case Basic_UntypedFloat:
+ case Basic_UntypedComplex:
+ case Basic_UntypedQuaternion:
+ case Basic_UntypedString:
+ case Basic_UntypedRune:
+ case Basic_UntypedNil:
+ case Basic_UntypedUninit:
+ GB_PANIC("Untyped types cannot be @encoded()");
+ return str_lit("?");
+ }
+ break;
+ }
+
+ case Type_Named:
+ case Type_Struct:
+ case Type_Union: {
+ Type* base = t;
+ if (base->kind == Type_Named) {
+ base = base_type(base);
+ if(base->kind != Type_Struct && base->kind != Type_Union) {
+ return lb_get_objc_type_encoding(base, pointer_depth);
+ }
+ }
+
+ const bool is_union = base->kind == Type_Union;
+ if (!is_union) {
+ // Check for objc_SEL
+ if (internal_check_is_assignable_to(base, t_objc_SEL)) {
+ return str_lit(":");
+ }
+
+ // Check for objc_Class
+ if (internal_check_is_assignable_to(base, t_objc_SEL)) {
+ return str_lit("#");
+ }
+
+ // Treat struct as an Objective-C Class?
+ if (has_type_got_objc_class_attribute(base) && pointer_depth == 0) {
+ return str_lit("#");
+ }
+ }
+
+ if (is_type_objc_object(base)) {
+ return str_lit("@");
+ }
+
+
+ gbString s = gb_string_make_reserve(temporary_allocator(), 16);
+ s = gb_string_append_length(s, is_union ? "(" :"{", 1);
+ if (t->kind == Type_Named) {
+ s = gb_string_append_length(s, t->Named.name.text, t->Named.name.len);
+ }
+
+ // Write fields
+ if (pointer_depth < 2) {
+ s = gb_string_append_length(s, "=", 1);
+
+ if (!is_union) {
+ for( auto& f : base->Struct.fields ) {
+ String field_type = lb_get_objc_type_encoding(f->type, pointer_depth);
+ s = gb_string_append_length(s, field_type.text, field_type.len);
+ }
+ } else {
+ for( auto& v : base->Union.variants ) {
+ String variant_type = lb_get_objc_type_encoding(v, pointer_depth);
+ s = gb_string_append_length(s, variant_type.text, variant_type.len);
+ }
+ }
+ }
+
+ s = gb_string_append_length(s, is_union ? ")" :"}", 1);
+
+ return make_string_c(s);
+ }
+
+ case Type_Generic:
+ GB_PANIC("Generic types cannot be @encoded()");
+ return str_lit("?");
+
+ case Type_Pointer: {
+ String pointee = lb_get_objc_type_encoding(t->Pointer.elem, pointer_depth +1);
+ // Special case for Objective-C Objects
+ if (pointer_depth == 0 && pointee == "@") {
+ return pointee;
+ }
+
+ return concatenate_strings(temporary_allocator(), str_lit("^"), pointee);
+ }
+
+ case Type_MultiPointer:
+ return concatenate_strings(temporary_allocator(), str_lit("^"), lb_get_objc_type_encoding(t->Pointer.elem, pointer_depth +1));
+
+ case Type_Array: {
+ String type_str = lb_get_objc_type_encoding(t->Array.elem, pointer_depth);
+
+ gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 8);
+ s = gb_string_append_fmt(s, "[%lld%.*s]", t->Array.count, LIT(type_str));
+ return make_string_c(s);
+ }
+
+ case Type_EnumeratedArray: {
+ String type_str = lb_get_objc_type_encoding(t->EnumeratedArray.elem, pointer_depth);
+
+ gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 8);
+ s = gb_string_append_fmt(s, "[%lld%.*s]", t->EnumeratedArray.count, LIT(type_str));
+ return make_string_c(s);
+ }
+
+ case Type_Slice: {
+ String type_str = lb_get_objc_type_encoding(t->Slice.elem, pointer_depth);
+ gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 8);
+ s = gb_string_append_fmt(s, "{slice=^%.*s%s}", LIT(type_str), INT_SIZE_ENCODING);
+ return make_string_c(s);
+ }
+
+ case Type_DynamicArray: {
+ String type_str = lb_get_objc_type_encoding(t->DynamicArray.elem, pointer_depth);
+ gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 8);
+ s = gb_string_append_fmt(s, "{dynamic=^%.*s%s%sAllocator={?^v}}", LIT(type_str), INT_SIZE_ENCODING, INT_SIZE_ENCODING);
+ return make_string_c(s);
+ }
+
+ case Type_Map:
+ return str_lit("{^v^v{Allocator=?^v}}");
+ case Type_Enum:
+ return lb_get_objc_type_encoding(t->Enum.base_type, pointer_depth);
+ case Type_Tuple:
+ // NOTE(harold): Is this type allowed here?
+ return str_lit("?");
+ case Type_Proc:
+ return str_lit("?");
+ case Type_BitSet:
+ return lb_get_objc_type_encoding(t->BitSet.underlying, pointer_depth);
+
+ case Type_SimdVector: {
+ String type_str = lb_get_objc_type_encoding(t->SimdVector.elem, pointer_depth);
+ gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 5);
+ gb_string_append_fmt(s, "[%lld%.*s]", t->SimdVector.count, LIT(type_str));
+ return make_string_c(s);
+ }
+
+ case Type_Matrix: {
+ String type_str = lb_get_objc_type_encoding(t->Matrix.elem, pointer_depth);
+ gbString s = gb_string_make_reserve(temporary_allocator(), type_str.len + 5);
+ i64 element_count = t->Matrix.column_count * t->Matrix.row_count;
+ gb_string_append_fmt(s, "[%lld%.*s]", element_count, LIT(type_str));
+ return make_string_c(s);
+ }
+
+ case Type_BitField:
+ return lb_get_objc_type_encoding(t->BitField.backing_type, pointer_depth);
+ case Type_SoaPointer: {
+ gbString s = gb_string_make_reserve(temporary_allocator(), 8);
+ s = gb_string_append_fmt(s, "{=^v%s}", INT_SIZE_ENCODING);
+ return make_string_c(s);
+ }
+
+ } // End switch t->kind
+ #undef INT_SIZE_ENCODING
+
+ GB_PANIC("Unreachable");
+ return str_lit("");
+}
+
+struct lbObjCGlobalClass {
+ lbObjCGlobal g;
+ lbValue class_value; // Local registered class value
+};
+
+gb_internal void lb_register_objc_thing(
+ StringSet &handled,
+ lbModule *m,
+ Array &args,
+ Array &class_impls,
+ StringMap &class_map,
+ lbProcedure *p,
+ lbObjCGlobal const &g,
+ char const *call
+) {
+ if (string_set_update(&handled, g.name)) {
+ return;
+ }
+
+ lbAddr addr = {};
+ lbValue *found = string_map_get(&m->members, g.global_name);
+ if (found) {
+ addr = lb_addr(*found);
+ } else {
+ lbValue v = {};
+ LLVMTypeRef t = lb_type(m, g.type);
+ v.value = LLVMAddGlobal(m->mod, t, g.global_name);
+ v.type = alloc_type_pointer(g.type);
+ addr = lb_addr(v);
+ LLVMSetInitializer(v.value, LLVMConstNull(t));
+ }
+
+ lbValue class_ptr = {};
+ lbValue class_name = lb_const_value(m, t_cstring, exact_value_string(g.name));
+
+ // If this class requires an implementation, save it for registration below.
+ if (g.class_impl_type != nullptr) {
+
+ // Make sure the superclass has been initialized before us
+ lbValue superclass_value = lb_const_nil(m, t_objc_Class);
+
+ auto &tn = g.class_impl_type->Named.type_name->TypeName;
+ Type *superclass = tn.objc_superclass;
+ if (superclass != nullptr) {
+ auto& superclass_global = string_map_must_get(&class_map, superclass->Named.type_name->TypeName.objc_class_name);
+ lb_register_objc_thing(handled, m, args, class_impls, class_map, p, superclass_global.g, call);
+ GB_ASSERT(superclass_global.class_value.value);
+
+ superclass_value = superclass_global.class_value;
+ }
+
+ args.count = 3;
+ args[0] = superclass_value;
+ args[1] = class_name;
+ args[2] = lb_const_int(m, t_uint, 0);
+ class_ptr = lb_emit_runtime_call(p, "objc_allocateClassPair", args);
+
+ array_add(&class_impls, lbObjCGlobalClass{g, class_ptr});
+ }
+ else {
+ args.count = 1;
+ args[0] = class_name;
+ class_ptr = lb_emit_runtime_call(p, call, args);
+ }
+
+ lb_addr_store(p, addr, class_ptr);
+
+ lbObjCGlobalClass* class_global = string_map_get(&class_map, g.name);
+ if (class_global != nullptr) {
+ class_global->class_value = class_ptr;
+ }
+}
+
gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) {
if (p == nullptr) {
return;
@@ -1139,38 +1524,329 @@ gb_internal void lb_finalize_objc_names(lbGenerator *gen, lbProcedure *p) {
string_set_init(&handled);
defer (string_set_destroy(&handled));
- auto args = array_make(temporary_allocator(), 1);
+ auto args = array_make(temporary_allocator(), 3, 8);
+ auto class_impls = array_make(temporary_allocator(), 0, 16);
+
+ // Register all class implementations unconditionally, even if not statically referenced
+ for (Entity *e = {}; mpsc_dequeue(&gen->info->objc_class_implementations, &e); /**/) {
+ GB_ASSERT(e->kind == Entity_TypeName && e->TypeName.objc_is_implementation);
+ lb_handle_objc_find_or_register_class(p, e->TypeName.objc_class_name, e->type);
+ }
+
+ // Ensure classes that have been implicitly referenced through
+ // the objc_superclass attribute have a global variable available for them.
+ TypeSet class_set{};
+ type_set_init(&class_set, gen->objc_classes.count+16);
+ defer (type_set_destroy(&class_set));
+
+ auto referenced_classes = array_make(temporary_allocator());
+ for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_classes, &g); /**/) {
+ array_add(&referenced_classes, g);
+
+ Type *cls = g.class_impl_type;
+ while (cls) {
+ if (type_set_update(&class_set, cls)) {
+ break;
+ }
+ GB_ASSERT(cls->kind == Type_Named);
+
+ cls = cls->Named.type_name->TypeName.objc_superclass;
+ }
+ }
+
+ for (auto pair : class_set) {
+ auto& tn = pair.type->Named.type_name->TypeName;
+ Type *class_impl = !tn.objc_is_implementation ? nullptr : pair.type;
+ lb_handle_objc_find_or_register_class(p, tn.objc_class_name, class_impl);
+ }
+ for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_classes, &g); /**/) {
+ array_add( &referenced_classes, g );
+ }
+
+ // Add all class globals to a map so that we can look them up dynamically
+ // in order to resolve out-of-order because classes that are being implemented
+ // require their superclasses to be registered before them.
+ StringMap global_class_map{};
+ string_map_init(&global_class_map, (usize)gen->objc_classes.count);
+ defer (string_map_destroy(&global_class_map));
+
+ for (lbObjCGlobal g :referenced_classes) {
+ string_map_set(&global_class_map, g.name, lbObjCGlobalClass{g});
+ }
LLVMSetLinkage(p->value, LLVMInternalLinkage);
lb_begin_procedure_body(p);
- auto register_thing = [&handled, &m, &args](lbProcedure *p, lbObjCGlobal const &g, char const *call) {
- if (!string_set_update(&handled, g.name)) {
- lbAddr addr = {};
- lbValue *found = string_map_get(&m->members, g.global_name);
- if (found) {
- addr = lb_addr(*found);
- } else {
- lbValue v = {};
- LLVMTypeRef t = lb_type(m, g.type);
- v.value = LLVMAddGlobal(m->mod, t, g.global_name);
- v.type = alloc_type_pointer(g.type);
- addr = lb_addr(v);
- LLVMSetInitializer(v.value, LLVMConstNull(t));
- }
-
- args[0] = lb_const_value(m, t_cstring, exact_value_string(g.name));
- lbValue ptr = lb_emit_runtime_call(p, call, args);
- lb_addr_store(p, addr, ptr);
- }
- };
-
- for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_classes, &g); /**/) {
- register_thing(p, g, "objc_lookUpClass");
+ // Register class globals, gathering classes that must be implemented
+ for (auto& kv : global_class_map) {
+ lb_register_objc_thing(handled, m, args, class_impls, global_class_map, p, kv.value.g, "objc_lookUpClass");
}
+ // Prefetch selectors for implemented methods so that they can also be registered.
+ for (const auto& cd : class_impls) {
+ auto& g = cd.g;
+ Type *class_type = g.class_impl_type;
+
+ Array* methods = map_get(&m->info->objc_method_implementations, class_type);
+ if (!methods) {
+ continue;
+ }
+
+ for (const ObjcMethodData& md : *methods) {
+ lb_handle_objc_find_or_register_selector(p, md.ac.objc_selector);
+ }
+ }
+
+ // Now we can register all referenced selectors
for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_selectors, &g); /**/) {
- register_thing(p, g, "sel_registerName");
+ lb_register_objc_thing(handled, m, args, class_impls, global_class_map, p, g, "sel_registerName");
+ }
+
+
+ // Emit method wrapper implementations and registration
+ auto wrapper_args = array_make(temporary_allocator(), 2, 8);
+ auto get_context_args = array_make(temporary_allocator(), 1);
+
+
+ PtrMap ivar_map{};
+ map_init(&ivar_map, gen->objc_ivars.count);
+
+ for (lbObjCGlobal g = {}; mpsc_dequeue(&gen->objc_ivars, &g); /**/) {
+ map_set(&ivar_map, g.class_impl_type, g);
+ }
+
+ for (const auto &cd : class_impls) {
+ auto &g = cd.g;
+ Type *class_type = g.class_impl_type;
+ Type *class_ptr_type = alloc_type_pointer(class_type);
+ lbValue class_value = cd.class_value;
+
+ Type *ivar_type = class_type->Named.type_name->TypeName.objc_ivar;
+
+ Entity *context_provider = class_type->Named.type_name->TypeName.objc_context_provider;
+ Type *contex_provider_self_ptr_type = nullptr;
+ Type *contex_provider_self_named_type = nullptr;
+ bool is_context_provider_ivar = false;
+ lbValue context_provider_proc_value{};
+
+ if (context_provider) {
+ context_provider_proc_value = lb_find_procedure_value_from_entity(m, context_provider);
+
+ contex_provider_self_ptr_type = base_type(context_provider->type->Proc.params->Tuple.variables[0]->type);
+ GB_ASSERT(contex_provider_self_ptr_type->kind == Type_Pointer);
+ contex_provider_self_named_type = base_named_type(type_deref(contex_provider_self_ptr_type));
+
+ is_context_provider_ivar = ivar_type != nullptr && internal_check_is_assignable_to(contex_provider_self_named_type, ivar_type);
+ }
+
+
+ Array *methods = map_get(&m->info->objc_method_implementations, class_type);
+ if (!methods) {
+ continue;
+ }
+
+ for (const ObjcMethodData &md : *methods) {
+ GB_ASSERT( md.proc_entity->kind == Entity_Procedure);
+ Type *method_type = md.proc_entity->type;
+
+ String proc_name = make_string_c("__$objc_method::");
+ proc_name = concatenate_strings(temporary_allocator(), proc_name, g.name);
+ proc_name = concatenate_strings(temporary_allocator(), proc_name, str_lit("::"));
+ proc_name = concatenate_strings( permanent_allocator(), proc_name, md.ac.objc_name);
+
+ wrapper_args.count = 2;
+ wrapper_args[0] = md.ac.objc_is_class_method ? t_objc_Class : class_ptr_type;
+ wrapper_args[1] = t_objc_SEL;
+
+ isize method_param_count = method_type->Proc.param_count;
+ isize method_param_offset = 0;
+
+ if (!md.ac.objc_is_class_method) {
+ GB_ASSERT(method_param_count >= 1);
+ method_param_count -= 1;
+ method_param_offset = 1;
+ }
+
+ for (isize i = 0; i < method_param_count; i++) {
+ array_add(&wrapper_args, method_type->Proc.params->Tuple.variables[method_param_offset+i]->type);
+ }
+
+ Type *wrapper_args_tuple = alloc_type_tuple_from_field_types(wrapper_args.data, wrapper_args.count, false, true);
+ Type *wrapper_results_tuple = nullptr;
+
+ if (method_type->Proc.result_count > 0) {
+ GB_ASSERT(method_type->Proc.result_count == 1);
+ wrapper_results_tuple = alloc_type_tuple_from_field_types(&method_type->Proc.results->Tuple.variables[0]->type, 1, false, true);
+ }
+
+ Type *wrapper_proc_type = alloc_type_proc(nullptr, wrapper_args_tuple, wrapper_args_tuple->Tuple.variables.count,
+ wrapper_results_tuple, method_type->Proc.result_count, false, ProcCC_CDecl);
+
+ lbProcedure *wrapper_proc = lb_create_dummy_procedure(m, proc_name, wrapper_proc_type);
+ lb_add_attribute_to_proc(wrapper_proc->module, wrapper_proc->value, "nounwind");
+
+ // Emit the wrapper
+ LLVMSetLinkage(wrapper_proc->value, LLVMExternalLinkage);
+ lb_begin_procedure_body(wrapper_proc);
+ {
+ if (method_type->Proc.calling_convention == ProcCC_Odin) {
+ GB_ASSERT(context_provider);
+
+ // Emit the get odin context call
+
+ get_context_args[0] = lbValue {
+ wrapper_proc->raw_input_parameters[0],
+ contex_provider_self_ptr_type,
+ };
+
+ if (is_context_provider_ivar) {
+ // The context provider takes the ivar's type.
+ // Emit an objc_ivar_get call and use that pointer for 'self' instead.
+ lbValue real_self {
+ wrapper_proc->raw_input_parameters[0],
+ class_ptr_type
+ };
+ get_context_args[0] = lb_handle_objc_ivar_for_objc_object_pointer(wrapper_proc, real_self);
+ }
+
+ lbValue context = lb_emit_call(wrapper_proc, context_provider_proc_value, get_context_args);
+ lbAddr context_addr = lb_addr(lb_address_from_load_or_generate_local(wrapper_proc, context));
+ lb_push_context_onto_stack(wrapper_proc, context_addr);
+ }
+
+
+ auto method_call_args = array_make(temporary_allocator(), method_param_count + method_param_offset);
+
+ if (!md.ac.objc_is_class_method) {
+ method_call_args[0] = lbValue {
+ wrapper_proc->raw_input_parameters[0],
+ class_ptr_type,
+ };
+ }
+
+ for (isize i = 0; i < method_param_count; i++) {
+ method_call_args[i+method_param_offset] = lbValue {
+ wrapper_proc->raw_input_parameters[i+2],
+ method_type->Proc.params->Tuple.variables[i+method_param_offset]->type,
+ };
+ }
+ lbValue method_proc_value = lb_find_procedure_value_from_entity(m, md.proc_entity);
+
+ // Call real procedure for method from here, passing the parameters expected, if any.
+ lbValue return_value = lb_emit_call(wrapper_proc, method_proc_value, method_call_args);
+
+ if (wrapper_results_tuple != nullptr) {
+ auto &result_var = method_type->Proc.results->Tuple.variables[0];
+ return_value = lb_emit_conv(wrapper_proc, return_value, result_var->type);
+ lb_build_return_stmt_internal(wrapper_proc, return_value, result_var->token.pos);
+ }
+ }
+ lb_end_procedure_body(wrapper_proc);
+
+
+ // Add the method to the class
+ String method_encoding = str_lit("v");
+ // TODO (harold): Checker must ensure that objc_methods have a single return value or none!
+ GB_ASSERT(method_type->Proc.result_count <= 1);
+ if (method_type->Proc.result_count != 0) {
+ method_encoding = lb_get_objc_type_encoding(method_type->Proc.results->Tuple.variables[0]->type);
+ }
+
+ if (!md.ac.objc_is_class_method) {
+ method_encoding = concatenate_strings(temporary_allocator(), method_encoding, str_lit("@:"));
+ } else {
+ method_encoding = concatenate_strings(temporary_allocator(), method_encoding, str_lit("#:"));
+ }
+
+ for (isize i = method_param_offset; i < method_param_count; i++) {
+ Type *param_type = method_type->Proc.params->Tuple.variables[i]->type;
+ String param_encoding = lb_get_objc_type_encoding(param_type);
+
+ method_encoding = concatenate_strings(temporary_allocator(), method_encoding, param_encoding);
+ }
+
+ // Emit method registration
+ lbAddr* sel_address = string_map_get(&m->objc_selectors, md.ac.objc_selector);
+ GB_ASSERT(sel_address);
+ lbValue selector_value = lb_addr_load(p, *sel_address);
+
+ args.count = 4;
+ args[0] = class_value; // Class
+ args[1] = selector_value; // SEL
+ args[2] = lbValue { wrapper_proc->value, wrapper_proc->type };
+ args[3] = lb_const_value(m, t_cstring, exact_value_string(method_encoding));
+
+ // TODO(harold): Emit check BOOL result and panic if false.
+ lb_emit_runtime_call(p, "class_addMethod", args);
+
+ } // End methods
+
+ // Add ivar if we have one
+ if (ivar_type != nullptr) {
+ // Register a single ivar for this class
+ Type *ivar_base = ivar_type->Named.base;
+
+ // @note(harold): The alignment is supposed to be passed as log2(alignment): https://developer.apple.com/documentation/objectivec/class_addivar(_:_:_:_:_:)?language=objc
+ const i64 size = type_size_of(ivar_base);
+ const i64 alignment = (i64)floor_log2((u64)type_align_of(ivar_base));
+
+ // NOTE(harold): I've opted to not emit the type encoding for ivars in order to keep the data private.
+ // If there is desire in the future to emit the type encoding for introspection through the Obj-C runtime,
+ // then perhaps an option can be added for it then.
+ // Should we pass the actual type encoding? Might not be ideal for obfuscation.
+ String ivar_name = str_lit("__$ivar");
+ String ivar_types = str_lit("{= }"); //lb_get_objc_type_encoding(ivar_type);
+ args.count = 5;
+ args[0] = class_value;
+ args[1] = lb_const_value(m, t_cstring, exact_value_string(ivar_name));
+ args[2] = lb_const_value(m, t_uint, exact_value_u64((u64)size));
+ args[3] = lb_const_value(m, t_u8, exact_value_u64((u64)alignment));
+ args[4] = lb_const_value(m, t_cstring, exact_value_string(ivar_types));
+ lb_emit_runtime_call(p, "class_addIvar", args);
+ }
+
+ // Complete the class registration
+ args.count = 1;
+ args[0] = class_value;
+ lb_emit_runtime_call(p, "objc_registerClassPair", args);
+ }
+
+ // Register ivar offsets for any `objc_ivar_get` expressions emitted.
+ for (auto const& kv : ivar_map) {
+ lbObjCGlobal const& g = kv.value;
+ lbAddr ivar_addr = {};
+ lbValue *found = string_map_get(&m->members, g.global_name);
+
+ if (found) {
+ ivar_addr = lb_addr(*found);
+ GB_ASSERT(ivar_addr.addr.type == t_int_ptr);
+ } else {
+ // Defined in an external package, define it now in the main package
+ LLVMTypeRef t = lb_type(m, t_int);
+
+ lbValue global{};
+ global.value = LLVMAddGlobal(m->mod, t, g.global_name);
+ global.type = t_int_ptr;
+
+ LLVMSetInitializer(global.value, LLVMConstInt(t, 0, true));
+
+ ivar_addr = lb_addr(global);
+ }
+
+ String class_name = g.class_impl_type->Named.type_name->TypeName.objc_class_name;
+ lbValue class_value = string_map_must_get(&global_class_map, class_name).class_value;
+
+ args.count = 2;
+ args[0] = class_value;
+ args[1] = lb_const_value(m, t_cstring, exact_value_string(str_lit("__$ivar")));
+ lbValue ivar = lb_emit_runtime_call(p, "class_getInstanceVariable", args);
+
+ args.count = 1;
+ args[0] = ivar;
+ lbValue ivar_offset = lb_emit_runtime_call(p, "ivar_getOffset", args);
+ lbValue ivar_offset_int = lb_emit_conv(p, ivar_offset, t_int);
+
+ lb_addr_store(p, ivar_addr, ivar_offset_int);
}
lb_end_procedure_body(p);
@@ -1231,6 +1907,10 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc
lb_add_attribute_to_proc(p->module, p->value, "optnone");
lb_add_attribute_to_proc(p->module, p->value, "noinline");
+ // Make sure shared libraries call their own runtime startup on Linux.
+ LLVMSetVisibility(p->value, LLVMHiddenVisibility);
+ LLVMSetLinkage(p->value, LLVMWeakAnyLinkage);
+
lb_begin_procedure_body(p);
lb_setup_type_info_data(main_module);
@@ -1297,14 +1977,14 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc
gbString var_name = gb_string_make(permanent_allocator(), "__$global_any::");
gbString e_str = string_canonical_entity_name(temporary_allocator(), e);
var_name = gb_string_append_length(var_name, e_str, gb_strlen(e_str));
- lbAddr g = lb_add_global_generated_with_name(main_module, var_type, var.init, make_string_c(var_name));
+ lbAddr g = lb_add_global_generated_with_name(main_module, var_type, {}, make_string_c(var_name));
lb_addr_store(p, g, var.init);
lbValue gp = lb_addr_get_ptr(p, g);
lbValue data = lb_emit_struct_ep(p, var.var, 0);
lbValue ti = lb_emit_struct_ep(p, var.var, 1);
lb_emit_store(p, data, lb_emit_conv(p, gp, t_rawptr));
- lb_emit_store(p, ti, lb_type_info(p, var_type));
+ lb_emit_store(p, ti, lb_typeid(p->module, var_type));
} else {
LLVMTypeRef vt = llvm_addr_type(p->module, var.var);
lbValue src0 = lb_emit_conv(p, var.init, t);
@@ -1340,6 +2020,10 @@ gb_internal lbProcedure *lb_create_cleanup_runtime(lbModule *main_module) { // C
lb_add_attribute_to_proc(p->module, p->value, "optnone");
lb_add_attribute_to_proc(p->module, p->value, "noinline");
+ // Make sure shared libraries call their own runtime cleanup on Linux.
+ LLVMSetVisibility(p->value, LLVMHiddenVisibility);
+ LLVMSetLinkage(p->value, LLVMWeakAnyLinkage);
+
lb_begin_procedure_body(p);
CheckerInfo *info = main_module->gen->info;
@@ -1414,7 +2098,7 @@ gb_internal void lb_create_global_procedures_and_types(lbGenerator *gen, Checker
break;
case Entity_Constant:
if (build_context.ODIN_DEBUG) {
- add_debug_info_for_global_constant_from_entity(gen, e);
+ lb_add_debug_info_for_global_constant_from_entity(gen, e);
}
break;
}
@@ -2056,8 +2740,8 @@ gb_internal lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *star
if (testing_proc->pkg != nullptr) {
pkg_name = testing_proc->pkg->name;
}
- lbValue v_pkg = lb_find_or_add_entity_string(m, pkg_name);
- lbValue v_name = lb_find_or_add_entity_string(m, name);
+ lbValue v_pkg = lb_find_or_add_entity_string(m, pkg_name, false);
+ lbValue v_name = lb_find_or_add_entity_string(m, name, false);
lbValue v_proc = lb_find_procedure_value_from_entity(m, testing_proc);
indices[1] = LLVMConstInt(lb_type(m, t_int), testing_proc_index++, false);
@@ -2138,7 +2822,6 @@ gb_internal void lb_generate_procedure(lbModule *m, lbProcedure *p) {
p->is_done = true;
m->curr_procedure = nullptr;
}
- lb_end_procedure(p);
// Add Flags
if (p->entity && p->entity->kind == Entity_Procedure && p->entity->Procedure.is_memcpy_like) {
@@ -2447,6 +3130,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
LLVMSetInitializer(g, LLVMConstNull(lb_type(m, t)));
LLVMSetLinkage(g, LLVMInternalLinkage);
lb_make_global_private_const(g);
+ lb_set_odin_rtti_section(g);
return lb_addr({g, alloc_type_pointer(t)});
};
@@ -2518,24 +3202,9 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
lbValue g = {};
g.value = LLVMAddGlobal(m->mod, lb_type(m, e->type), alloc_cstring(permanent_allocator(), name));
g.type = alloc_type_pointer(e->type);
- if (e->Variable.thread_local_model != "") {
- LLVMSetThreadLocal(g.value, true);
- String m = e->Variable.thread_local_model;
- LLVMThreadLocalMode mode = LLVMGeneralDynamicTLSModel;
- if (m == "default") {
- mode = LLVMGeneralDynamicTLSModel;
- } else if (m == "localdynamic") {
- mode = LLVMLocalDynamicTLSModel;
- } else if (m == "initialexec") {
- mode = LLVMInitialExecTLSModel;
- } else if (m == "localexec") {
- mode = LLVMLocalExecTLSModel;
- } else {
- GB_PANIC("Unhandled thread local mode %.*s", LIT(m));
- }
- LLVMSetThreadLocalMode(g.value, mode);
- }
+ lb_apply_thread_local_model(g.value, e->Variable.thread_local_model);
+
if (is_foreign) {
LLVMSetLinkage(g.value, LLVMExternalLinkage);
LLVMSetDLLStorageClass(g.value, LLVMDLLImportStorageClass);
@@ -2551,6 +3220,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
LLVMSetLinkage(g.value, USE_SEPARATE_MODULES ? LLVMWeakAnyLinkage : LLVMInternalLinkage);
}
lb_set_linkage_from_entity_flags(m, g.value, e->flags);
+ LLVMSetAlignment(g.value, cast(u32)type_align_of(e->type));
if (e->Variable.link_section.len > 0) {
LLVMSetSection(g.value, alloc_cstring(permanent_allocator(), e->Variable.link_section));
@@ -2565,12 +3235,16 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
if (!is_type_any(e->type) && !is_type_union(e->type)) {
if (tav.mode != Addressing_Invalid) {
if (tav.value.kind != ExactValue_Invalid) {
- bool is_rodata = e->kind == Entity_Variable && e->Variable.is_rodata;
+ auto cc = LB_CONST_CONTEXT_DEFAULT;
+ cc.is_rodata = e->kind == Entity_Variable && e->Variable.is_rodata;
+ cc.allow_local = false;
+ cc.link_section = e->Variable.link_section;
+
ExactValue v = tav.value;
- lbValue init = lb_const_value(m, tav.type, v, false, is_rodata);
+ lbValue init = lb_const_value(m, tav.type, v, cc);
LLVMSetInitializer(g.value, init.value);
var.is_initialized = true;
- if (is_rodata) {
+ if (cc.is_rodata) {
LLVMSetGlobalConstant(g.value, true);
}
}
@@ -2585,6 +3259,11 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
} else if (e->kind == Entity_Variable && e->Variable.is_rodata) {
LLVMSetGlobalConstant(g.value, true);
}
+
+ if (e->flags & EntityFlag_Require) {
+ lb_append_to_compiler_used(m, g.value);
+ }
+
array_add(&global_variables, var);
lb_add_entity(m, e, g);
@@ -2621,6 +3300,28 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
}
}
+ if (build_context.ODIN_DEBUG) {
+ // Custom `.raddbg` section for its debugger
+ if (build_context.metrics.os == TargetOs_windows) {
+ lbModule *m = default_module;
+ LLVMModuleRef mod = m->mod;
+ LLVMContextRef ctx = m->ctx;
+
+ {
+ LLVMTypeRef type = LLVMArrayType(LLVMInt8TypeInContext(ctx), 1);
+ LLVMValueRef global = LLVMAddGlobal(mod, type, "raddbg_is_attached_byte_marker");
+ LLVMSetInitializer(global, LLVMConstNull(type));
+ LLVMSetSection(global, ".raddbg");
+ }
+
+ if (gen->info->entry_point) {
+ String mangled_name = lb_get_entity_name(m, gen->info->entry_point);
+ char const *str = alloc_cstring(temporary_allocator(), mangled_name);
+ lb_add_raddbg_string(m, "entry_point: \"", str, "\"");
+ }
+ }
+ }
+
TIME_SECTION("LLVM Runtime Objective-C Names Creation");
gen->objc_names = lb_create_objc_names(default_module);
@@ -2634,7 +3335,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
if (build_context.ODIN_DEBUG) {
for (auto const &entry : builtin_pkg->scope->elements) {
Entity *e = entry.value;
- add_debug_info_for_global_constant_from_entity(gen, e);
+ lb_add_debug_info_for_global_constant_from_entity(gen, e);
}
}
@@ -2664,6 +3365,72 @@ gb_internal bool lb_generate_code(lbGenerator *gen) {
if (build_context.ODIN_DEBUG) {
TIME_SECTION("LLVM Debug Info Complete Types and Finalize");
lb_debug_info_complete_types_and_finalize(gen);
+
+ // Custom `.raddbg` section for its debugger
+ if (build_context.metrics.os == TargetOs_windows) {
+ lbModule *m = default_module;
+ LLVMModuleRef mod = m->mod;
+ LLVMContextRef ctx = m->ctx;
+
+ lb_add_raddbg_string(m, "type_view: {type: \"[]?\", expr: \"array(data, len)\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"string\", expr: \"array(data, len)\"}");
+
+ // column major matrices
+ lb_add_raddbg_string(m, "type_view: {type: \"matrix[1, ?]?\", expr: \"table($.data, $[0])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"matrix[2, ?]?\", expr: \"table($.data, $[0], $[1])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"matrix[3, ?]?\", expr: \"table($.data, $[0], $[1], $[2])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"matrix[4, ?]?\", expr: \"table($.data, $[0], $[1], $[2], $[3])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"matrix[5, ?]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"matrix[6, ?]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4], $[5])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"matrix[7, ?]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4], $[5], $[6])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"matrix[8, ?]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4], $[5], $[6], $[7])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"matrix[9, ?]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4], $[5], $[6], $[7], $[8])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"matrix[10, ?]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4], $[5], $[6], $[7], $[8], $[9])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"matrix[11, ?]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4], $[5], $[6], $[7], $[8], $[9], $[10])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"matrix[12, ?]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4], $[5], $[6], $[7], $[8], $[9], $[10], $[11])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"matrix[13, ?]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4], $[5], $[6], $[7], $[8], $[9], $[10], $[11], $[12])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"matrix[14, ?]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4], $[5], $[6], $[7], $[8], $[9], $[10], $[11], $[12], $[13])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"matrix[15, ?]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4], $[5], $[6], $[7], $[8], $[9], $[10], $[11], $[12], $[13], $[14])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"matrix[16, ?]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4], $[5], $[6], $[7], $[8], $[9], $[10], $[11], $[12], $[13], $[14], $[15])\"}");
+
+ // row major matrices
+ lb_add_raddbg_string(m, "type_view: {type: \"#row_major matrix[?, 1]?\", expr: \"table($.data, $[0])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"#row_major matrix[?, 2]?\", expr: \"table($.data, $[0], $[1])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"#row_major matrix[?, 3]?\", expr: \"table($.data, $[0], $[1], $[2])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"#row_major matrix[?, 4]?\", expr: \"table($.data, $[0], $[1], $[2], $[3])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"#row_major matrix[?, 5]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"#row_major matrix[?, 6]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4], $[5])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"#row_major matrix[?, 7]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4], $[5], $[6])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"#row_major matrix[?, 8]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4], $[5], $[6], $[7])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"#row_major matrix[?, 9]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4], $[5], $[6], $[7], $[8])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"#row_major matrix[?, 10]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4], $[5], $[6], $[7], $[8], $[9])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"#row_major matrix[?, 11]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4], $[5], $[6], $[7], $[8], $[9], $[10])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"#row_major matrix[?, 12]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4], $[5], $[6], $[7], $[8], $[9], $[10], $[11])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"#row_major matrix[?, 13]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4], $[5], $[6], $[7], $[8], $[9], $[10], $[11], $[12])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"#row_major matrix[?, 14]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4], $[5], $[6], $[7], $[8], $[9], $[10], $[11], $[12], $[13])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"#row_major matrix[?, 15]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4], $[5], $[6], $[7], $[8], $[9], $[10], $[11], $[12], $[13], $[14])\"}");
+ lb_add_raddbg_string(m, "type_view: {type: \"#row_major matrix[?, 16]?\", expr: \"table($.data, $[0], $[1], $[2], $[3], $[4], $[5], $[6], $[7], $[8], $[9], $[10], $[11], $[12], $[13], $[14], $[15])\"}");
+
+
+ TEMPORARY_ALLOCATOR_GUARD();
+
+ u32 global_name_index = 0;
+ for (String str = {}; mpsc_dequeue(&gen->raddebug_section_strings, &str); /**/) {
+ LLVMValueRef data = LLVMConstStringInContext(ctx, cast(char const *)str.text, cast(unsigned)str.len, false);
+ LLVMTypeRef type = LLVMTypeOf(data);
+
+ gbString global_name = gb_string_make(temporary_allocator(), "raddbg_data__");
+ global_name = gb_string_append_fmt(global_name, "%u", global_name_index);
+ global_name_index += 1;
+
+ LLVMValueRef global = LLVMAddGlobal(mod, type, global_name);
+
+ LLVMSetInitializer(global, data);
+ LLVMSetAlignment(global, 1);
+
+ LLVMSetSection(global, ".raddbg");
+ }
+ }
}
if (do_threading) {
diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp
index 3e01ada5f..fd6f50dcd 100644
--- a/src/llvm_backend.hpp
+++ b/src/llvm_backend.hpp
@@ -196,6 +196,7 @@ struct lbModule {
StringMap objc_classes;
StringMap objc_selectors;
+ StringMap objc_ivars;
PtrMap map_cell_info_map; // address of runtime.Map_Info
PtrMap map_info_map; // address of runtime.Map_Cell_Info
@@ -219,6 +220,7 @@ struct lbObjCGlobal {
gbString global_name;
String name;
Type * type;
+ Type * class_impl_type; // This is set when the class has the objc_implement attribute set to true.
};
struct lbGenerator : LinkerData {
@@ -240,6 +242,8 @@ struct lbGenerator : LinkerData {
MPSCQueue entities_to_correct_linkage;
MPSCQueue objc_selectors;
MPSCQueue objc_classes;
+ MPSCQueue objc_ivars;
+ MPSCQueue raddebug_section_strings;
};
@@ -383,6 +387,8 @@ struct lbProcedure {
PtrMap selector_values;
PtrMap selector_addr;
PtrMap tuple_fix_map;
+
+ Array asan_stack_locals;
};
@@ -407,7 +413,6 @@ gb_internal LLVMAttributeRef lb_create_enum_attribute_with_type(LLVMContextRef c
gb_internal void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *name, u64 value);
gb_internal void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *name);
gb_internal lbProcedure *lb_create_procedure(lbModule *module, Entity *entity, bool ignore_body=false);
-gb_internal void lb_end_procedure(lbProcedure *p);
gb_internal LLVMTypeRef lb_type(lbModule *m, Type *type);
@@ -415,9 +420,19 @@ gb_internal LLVMTypeRef llvm_get_element_type(LLVMTypeRef type);
gb_internal lbBlock *lb_create_block(lbProcedure *p, char const *name, bool append=false);
+struct lbConstContext {
+ bool allow_local;
+ bool is_rodata;
+ String link_section;
+};
+
+static lbConstContext const LB_CONST_CONTEXT_DEFAULT = {true, false, {}};
+static lbConstContext const LB_CONST_CONTEXT_DEFAULT_ALLOW_LOCAL = {true, false, {}};
+static lbConstContext const LB_CONST_CONTEXT_DEFAULT_NO_LOCAL = {false, false, {}};
+
gb_internal lbValue lb_const_nil(lbModule *m, Type *type);
gb_internal lbValue lb_const_undef(lbModule *m, Type *type);
-gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bool allow_local=true, bool is_rodata=false);
+gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lbConstContext cc = LB_CONST_CONTEXT_DEFAULT);
gb_internal lbValue lb_const_bool(lbModule *m, Type *type, bool value);
gb_internal lbValue lb_const_int(lbModule *m, Type *type, u64 value);
@@ -514,7 +529,7 @@ gb_internal void lb_fill_slice(lbProcedure *p, lbAddr const &slice, lbValue base
gb_internal lbValue lb_type_info(lbProcedure *p, Type *type);
-gb_internal lbValue lb_find_or_add_entity_string(lbModule *m, String const &str);
+gb_internal lbValue lb_find_or_add_entity_string(lbModule *m, String const &str, bool custom_link_section);
gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &prefix_name, Ast *expr, lbProcedure *parent = nullptr);
gb_internal bool lb_is_const(lbValue value);
diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp
index 9401e4d55..51c8a4449 100644
--- a/src/llvm_backend_const.cpp
+++ b/src/llvm_backend_const.cpp
@@ -301,10 +301,10 @@ gb_internal lbValue lb_const_source_code_location_const(lbModule *m, String cons
}
LLVMValueRef fields[4] = {};
- fields[0]/*file*/ = lb_find_or_add_entity_string(m, file).value;
+ fields[0]/*file*/ = lb_find_or_add_entity_string(m, file, false).value;
fields[1]/*line*/ = lb_const_int(m, t_i32, line).value;
fields[2]/*column*/ = lb_const_int(m, t_i32, column).value;
- fields[3]/*procedure*/ = lb_find_or_add_entity_string(m, procedure).value;
+ fields[3]/*procedure*/ = lb_find_or_add_entity_string(m, procedure, false).value;
lbValue res = {};
res.value = llvm_const_named_struct(m, t_source_code_location, fields, gb_count_of(fields));
@@ -391,12 +391,12 @@ gb_internal lbValue lb_emit_source_code_location_as_global(lbProcedure *p, Ast *
-gb_internal LLVMValueRef lb_build_constant_array_values(lbModule *m, Type *type, Type *elem_type, isize count, LLVMValueRef *values, bool allow_local, bool is_rodata) {
- if (allow_local) {
- is_rodata = false;
+gb_internal LLVMValueRef lb_build_constant_array_values(lbModule *m, Type *type, Type *elem_type, isize count, LLVMValueRef *values, lbConstContext cc) {
+ if (cc.allow_local) {
+ cc.is_rodata = false;
}
- bool is_local = allow_local && m->curr_procedure != nullptr;
+ bool is_local = cc.allow_local && m->curr_procedure != nullptr;
bool is_const = true;
if (is_local) {
for (isize i = 0; i < count; i++) {
@@ -500,9 +500,9 @@ gb_internal bool lb_is_nested_possibly_constant(Type *ft, Selection const &sel,
return lb_is_elem_const(elem, ft);
}
-gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bool allow_local, bool is_rodata) {
- if (allow_local) {
- is_rodata = false;
+gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lbConstContext cc) {
+ if (cc.allow_local) {
+ cc.is_rodata = false;
}
LLVMContextRef ctx = m->ctx;
@@ -533,7 +533,10 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
Entity *e = entity_from_expr(expr);
res = lb_find_procedure_value_from_entity(m, e);
}
- GB_ASSERT(res.value != nullptr);
+ if (res.value == nullptr) {
+ // This is an unspecialized polymorphic procedure, return nil or dummy value
+ return lb_const_nil(m, original_type);
+ }
GB_ASSERT(LLVMGetValueKind(res.value) == LLVMFunctionValueKind);
if (LLVMGetIntrinsicID(res.value) == 0) {
@@ -543,7 +546,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
return res;
}
- bool is_local = allow_local && m->curr_procedure != nullptr;
+ bool is_local = cc.allow_local && m->curr_procedure != nullptr;
// GB_ASSERT_MSG(is_type_typed(type), "%s", type_to_string(type));
@@ -562,7 +565,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
count = gb_max(cast(isize)cl->max_count, count);
Type *elem = base_type(type)->Slice.elem;
Type *t = alloc_type_array(elem, count);
- lbValue backing_array = lb_const_value(m, t, value, allow_local, is_rodata);
+ lbValue backing_array = lb_const_value(m, t, value, cc);
LLVMValueRef array_data = nullptr;
@@ -599,7 +602,10 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
array_data = LLVMAddGlobal(m->mod, lb_type(m, t), str);
LLVMSetInitializer(array_data, backing_array.value);
- if (is_rodata) {
+ if (cc.link_section.len > 0) {
+ LLVMSetSection(array_data, alloc_cstring(permanent_allocator(), cc.link_section));
+ }
+ if (cc.is_rodata) {
LLVMSetGlobalConstant(array_data, true);
}
@@ -650,7 +656,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
}
// NOTE(bill, 2021-10-07): Allow for array programming value constants
Type *core_elem = core_array_type(type);
- return lb_const_value(m, core_elem, value, allow_local, is_rodata);
+ return lb_const_value(m, core_elem, value, cc);
} else if (is_type_u8_array(type) && value.kind == ExactValue_String) {
GB_ASSERT(type->Array.count == value.value_string.len);
LLVMValueRef data = LLVMConstStringInContext(ctx,
@@ -668,7 +674,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
Type *elem = type->Array.elem;
- lbValue single_elem = lb_const_value(m, elem, value, allow_local, is_rodata);
+ lbValue single_elem = lb_const_value(m, elem, value, cc);
LLVMValueRef *elems = gb_alloc_array(permanent_allocator(), LLVMValueRef, cast(isize)count);
for (i64 i = 0; i < count; i++) {
@@ -686,7 +692,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
Type *elem = type->Matrix.elem;
- lbValue single_elem = lb_const_value(m, elem, value, allow_local, is_rodata);
+ lbValue single_elem = lb_const_value(m, elem, value, cc);
single_elem.value = llvm_const_cast(single_elem.value, lb_type(m, elem));
i64 total_elem_count = matrix_type_total_internal_elems(type);
@@ -708,7 +714,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
i64 count = type->SimdVector.count;
Type *elem = type->SimdVector.elem;
- lbValue single_elem = lb_const_value(m, elem, value, allow_local, is_rodata);
+ lbValue single_elem = lb_const_value(m, elem, value, cc);
single_elem.value = llvm_const_cast(single_elem.value, lb_type(m, elem));
LLVMValueRef *elems = gb_alloc_array(permanent_allocator(), LLVMValueRef, count);
@@ -729,9 +735,16 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
return res;
case ExactValue_String:
{
- LLVMValueRef ptr = lb_find_or_add_entity_string_ptr(m, value.value_string);
+ bool custom_link_section = cc.link_section.len > 0;
+
+ LLVMValueRef ptr = lb_find_or_add_entity_string_ptr(m, value.value_string, custom_link_section);
lbValue res = {};
res.type = default_type(original_type);
+
+ if (custom_link_section) {
+ LLVMSetSection(ptr, alloc_cstring(permanent_allocator(), cc.link_section));
+ }
+
if (is_type_cstring(res.type)) {
res.value = ptr;
} else {
@@ -837,7 +850,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
case ExactValue_Compound:
if (is_type_slice(type)) {
- return lb_const_value(m, type, value, allow_local, is_rodata);
+ return lb_const_value(m, type, value, cc);
} else if (is_type_array(type)) {
ast_node(cl, CompoundLit, value.value_compound);
Type *elem_type = type->Array.elem;
@@ -871,7 +884,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
}
if (lo == i) {
TypeAndValue tav = fv->value->tav;
- LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value;
+ LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value;
for (i64 k = lo; k < hi; k++) {
values[value_index++] = val;
}
@@ -886,7 +899,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
i64 index = exact_value_to_i64(index_tav.value);
if (index == i) {
TypeAndValue tav = fv->value->tav;
- LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value;
+ LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value;
values[value_index++] = val;
found = true;
break;
@@ -899,7 +912,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
}
}
- res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->Array.count, values, allow_local, is_rodata);
+ res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->Array.count, values, cc);
return res;
} else {
GB_ASSERT_MSG(elem_count == type->Array.count, "%td != %td", elem_count, type->Array.count);
@@ -909,13 +922,13 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
for (isize i = 0; i < elem_count; i++) {
TypeAndValue tav = cl->elems[i]->tav;
GB_ASSERT(tav.mode != Addressing_Invalid);
- values[i] = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value;
+ values[i] = lb_const_value(m, elem_type, tav.value, cc).value;
}
for (isize i = elem_count; i < type->Array.count; i++) {
values[i] = LLVMConstNull(lb_type(m, elem_type));
}
- res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->Array.count, values, allow_local, is_rodata);
+ res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->Array.count, values, cc);
return res;
}
} else if (is_type_enumerated_array(type)) {
@@ -955,7 +968,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
}
if (lo == i) {
TypeAndValue tav = fv->value->tav;
- LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value;
+ LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value;
for (i64 k = lo; k < hi; k++) {
values[value_index++] = val;
}
@@ -970,7 +983,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
i64 index = exact_value_to_i64(index_tav.value);
if (index == i) {
TypeAndValue tav = fv->value->tav;
- LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value;
+ LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value;
values[value_index++] = val;
found = true;
break;
@@ -983,7 +996,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
}
}
- res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->EnumeratedArray.count, values, allow_local, is_rodata);
+ res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->EnumeratedArray.count, values, cc);
return res;
} else {
GB_ASSERT_MSG(elem_count == type->EnumeratedArray.count, "%td != %td", elem_count, type->EnumeratedArray.count);
@@ -993,13 +1006,13 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
for (isize i = 0; i < elem_count; i++) {
TypeAndValue tav = cl->elems[i]->tav;
GB_ASSERT(tav.mode != Addressing_Invalid);
- values[i] = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value;
+ values[i] = lb_const_value(m, elem_type, tav.value, cc).value;
}
for (isize i = elem_count; i < type->EnumeratedArray.count; i++) {
values[i] = LLVMConstNull(lb_type(m, elem_type));
}
- res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->EnumeratedArray.count, values, allow_local, is_rodata);
+ res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->EnumeratedArray.count, values, cc);
return res;
}
} else if (is_type_simd_vector(type)) {
@@ -1038,7 +1051,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
}
if (lo == i) {
TypeAndValue tav = fv->value->tav;
- LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value;
+ LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value;
for (i64 k = lo; k < hi; k++) {
values[value_index++] = val;
}
@@ -1053,7 +1066,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
i64 index = exact_value_to_i64(index_tav.value);
if (index == i) {
TypeAndValue tav = fv->value->tav;
- LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value;
+ LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value;
values[value_index++] = val;
found = true;
break;
@@ -1072,7 +1085,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
for (isize i = 0; i < elem_count; i++) {
TypeAndValue tav = cl->elems[i]->tav;
GB_ASSERT(tav.mode != Addressing_Invalid);
- values[i] = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value;
+ values[i] = lb_const_value(m, elem_type, tav.value, cc).value;
}
LLVMTypeRef et = lb_type(m, elem_type);
@@ -1121,11 +1134,13 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
i32 index = field_remapping[f->Variable.field_index];
if (elem_type_can_be_constant(f->type)) {
if (sel.index.count == 1) {
- values[index] = lb_const_value(m, f->type, tav.value, allow_local, is_rodata).value;
+ values[index] = lb_const_value(m, f->type, tav.value, cc).value;
visited[index] = true;
} else {
if (!visited[index]) {
- values[index] = lb_const_value(m, f->type, {}, /*allow_local*/false, is_rodata).value;
+ auto new_cc = cc;
+ new_cc.allow_local = false;
+ values[index] = lb_const_value(m, f->type, {}, new_cc).value;
visited[index] = true;
}
@@ -1165,7 +1180,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
}
}
if (is_constant) {
- LLVMValueRef elem_value = lb_const_value(m, tav.type, tav.value, allow_local, is_rodata).value;
+ LLVMValueRef elem_value = lb_const_value(m, tav.type, tav.value, cc).value;
if (LLVMIsConstant(elem_value) && LLVMIsConstant(values[index])) {
values[index] = llvm_const_insert_value(m, values[index], elem_value, idx_list, idx_list_len);
} else if (is_local) {
@@ -1219,7 +1234,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
i32 index = field_remapping[f->Variable.field_index];
if (elem_type_can_be_constant(f->type)) {
- values[index] = lb_const_value(m, f->type, val, allow_local, is_rodata).value;
+ values[index] = lb_const_value(m, f->type, val, cc).value;
visited[index] = true;
}
}
@@ -1353,7 +1368,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
TypeAndValue tav = fv->value->tav;
- LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value;
+ LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value;
for (i64 k = lo; k < hi; k++) {
i64 offset = matrix_row_major_index_to_offset(type, k);
GB_ASSERT(values[offset] == nullptr);
@@ -1365,7 +1380,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
i64 index = exact_value_to_i64(index_tav.value);
GB_ASSERT(index < max_count);
TypeAndValue tav = fv->value->tav;
- LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value;
+ LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value;
i64 offset = matrix_row_major_index_to_offset(type, index);
GB_ASSERT(values[offset] == nullptr);
values[offset] = val;
@@ -1378,7 +1393,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
}
}
- res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)total_count, values, allow_local, is_rodata);
+ res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)total_count, values, cc);
return res;
} else {
GB_ASSERT_MSG(elem_count == max_count, "%td != %td", elem_count, max_count);
@@ -1389,7 +1404,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
GB_ASSERT(tav.mode != Addressing_Invalid);
i64 offset = 0;
offset = matrix_row_major_index_to_offset(type, i);
- values[offset] = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value;
+ values[offset] = lb_const_value(m, elem_type, tav.value, cc).value;
}
for (isize i = 0; i < total_count; i++) {
if (values[i] == nullptr) {
@@ -1397,7 +1412,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo
}
}
- res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)total_count, values, allow_local, is_rodata);
+ res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)total_count, values, cc);
return res;
}
} else {
diff --git a/src/llvm_backend_debug.cpp b/src/llvm_backend_debug.cpp
index 53c007d8d..024c5564e 100644
--- a/src/llvm_backend_debug.cpp
+++ b/src/llvm_backend_debug.cpp
@@ -18,6 +18,25 @@ gb_internal void lb_set_llvm_metadata(lbModule *m, void *key, LLVMMetadataRef va
}
}
+gb_internal void lb_add_raddbg_string(lbModule *m, String const &str) {
+ mpsc_enqueue(&m->gen->raddebug_section_strings, copy_string(permanent_allocator(), str));
+}
+
+gb_internal void lb_add_raddbg_string(lbModule *m, char const *cstr) {
+ mpsc_enqueue(&m->gen->raddebug_section_strings, copy_string(permanent_allocator(), make_string_c(cstr)));
+}
+
+gb_internal void lb_add_raddbg_string(lbModule *m, char const *a, char const *b) {
+ String str = concatenate_strings(permanent_allocator(), make_string_c(a), make_string_c(b));
+ mpsc_enqueue(&m->gen->raddebug_section_strings, str);
+}
+
+gb_internal void lb_add_raddbg_string(lbModule *m, char const *a, char const *b, char const *c) {
+ String str = concatenate3_strings(permanent_allocator(), make_string_c(a), make_string_c(b), make_string_c(c));
+ mpsc_enqueue(&m->gen->raddebug_section_strings, str);
+}
+
+
gb_internal LLVMMetadataRef lb_get_current_debug_scope(lbProcedure *p) {
GB_ASSERT_MSG(p->debug_info != nullptr, "missing debug information for %.*s", LIT(p->name));
@@ -564,22 +583,21 @@ gb_internal LLVMMetadataRef lb_debug_bitfield(lbModule *m, Type *type, String na
u64 size_in_bits = 8*type_size_of(bt);
u32 align_in_bits = 8*cast(u32)type_align_of(bt);
- unsigned element_count = cast(unsigned)bt->BitField.fields.count;
- LLVMMetadataRef *elements = gb_alloc_array(permanent_allocator(), LLVMMetadataRef, element_count);
+ unsigned element_count = cast(unsigned)bt->BitField.fields.count;
+ LLVMMetadataRef *elements = gb_alloc_array(permanent_allocator(), LLVMMetadataRef, element_count);
- u64 offset_in_bits = 0;
- for (unsigned i = 0; i < element_count; i++) {
- Entity *f = bt->BitField.fields[i];
- u8 bit_size = bt->BitField.bit_sizes[i];
- GB_ASSERT(f->kind == Entity_Variable);
- String name = f->token.string;
- elements[i] = LLVMDIBuilderCreateBitFieldMemberType(m->debug_builder, scope, cast(char const *)name.text, name.len, file, line,
- bit_size, offset_in_bits, 0,
- LLVMDIFlagZero, lb_debug_type(m, f->type)
- );
-
- offset_in_bits += bit_size;
- }
+ u64 offset_in_bits = 0;
+ for (unsigned i = 0; i < element_count; i++) {
+ Entity *f = bt->BitField.fields[i];
+ u8 bit_size = bt->BitField.bit_sizes[i];
+ GB_ASSERT(f->kind == Entity_Variable);
+ String name = f->token.string;
+ elements[i] = LLVMDIBuilderCreateBitFieldMemberType(m->debug_builder, scope, cast(char const *)name.text, name.len, file, line,
+ bit_size, offset_in_bits, 0,
+ LLVMDIFlagZero, lb_debug_type(m, f->type)
+ );
+ offset_in_bits += bit_size;
+ }
LLVMMetadataRef final_decl = LLVMDIBuilderCreateStructType(
m->debug_builder, scope,
@@ -924,6 +942,7 @@ gb_internal LLVMMetadataRef lb_debug_type_internal(lbModule *m, Type *type) {
}
case Type_Matrix: {
+ #if 0
LLVMMetadataRef subscripts[1] = {};
subscripts[0] = LLVMDIBuilderGetOrCreateSubrange(m->debug_builder,
0ll,
@@ -935,6 +954,66 @@ gb_internal LLVMMetadataRef lb_debug_type_internal(lbModule *m, Type *type) {
8*cast(unsigned)type_align_of(type),
lb_debug_type(m, type->Matrix.elem),
subscripts, gb_count_of(subscripts));
+ #else
+ LLVMMetadataRef subscripts[2] = {};
+ subscripts[0] = LLVMDIBuilderGetOrCreateSubrange(m->debug_builder, 0ll, type->Matrix.row_count);
+ subscripts[1] = LLVMDIBuilderGetOrCreateSubrange(m->debug_builder, 0ll, type->Matrix.column_count);
+
+ LLVMMetadataRef scope = nullptr;
+ LLVMMetadataRef array_type = nullptr;
+
+ uint64_t size_in_bits = 8*cast(uint64_t)(type_size_of(type));
+ unsigned align_in_bits = 8*cast(unsigned)(type_align_of(type));
+
+ if (type->Matrix.is_row_major) {
+ LLVMMetadataRef base = LLVMDIBuilderCreateArrayType(m->debug_builder,
+ 8*cast(uint64_t)(type_size_of(type->Matrix.elem) * type->Matrix.column_count),
+ 8*cast(unsigned)type_align_of(type->Matrix.elem),
+ lb_debug_type(m, type->Matrix.elem),
+ subscripts+1, 1);
+ array_type = LLVMDIBuilderCreateArrayType(m->debug_builder,
+ size_in_bits,
+ align_in_bits,
+ base,
+ subscripts+0, 1);
+ } else {
+ LLVMMetadataRef base = LLVMDIBuilderCreateArrayType(m->debug_builder,
+ 8*cast(uint64_t)(type_size_of(type->Matrix.elem) * type->Matrix.row_count),
+ 8*cast(unsigned)type_align_of(type->Matrix.elem),
+ lb_debug_type(m, type->Matrix.elem),
+ subscripts+0, 1);
+ array_type = LLVMDIBuilderCreateArrayType(m->debug_builder,
+ size_in_bits,
+ align_in_bits,
+ base,
+ subscripts+1, 1);
+ }
+
+ LLVMMetadataRef elements[1] = {};
+ elements[0] = LLVMDIBuilderCreateMemberType(m->debug_builder, scope,
+ "data", 4,
+ nullptr, 0,
+ size_in_bits, align_in_bits, 0, LLVMDIFlagZero,
+ array_type
+ );
+
+ gbString name = temp_canonical_string(type);
+
+ LLVMMetadataRef final_decl = LLVMDIBuilderCreateStructType(
+ m->debug_builder, scope,
+ name, gb_string_length(name),
+ nullptr, 0,
+ size_in_bits, align_in_bits,
+ LLVMDIFlagZero,
+ nullptr,
+ elements, 1,
+ 0,
+ nullptr,
+ "", 0
+ );
+
+ return final_decl;
+ #endif
}
}
@@ -1186,7 +1265,7 @@ gb_internal void lb_add_debug_context_variable(lbProcedure *p, lbAddr const &ctx
}
-gb_internal String debug_info_mangle_constant_name(Entity *e, gbAllocator const &allocator, bool *did_allocate_) {
+gb_internal String lb_debug_info_mangle_constant_name(Entity *e, gbAllocator const &allocator, bool *did_allocate_) {
String name = e->token.string;
if (e->pkg && e->pkg->name.len > 0) {
gbString s = string_canonical_entity_name(allocator, e);
@@ -1196,7 +1275,7 @@ gb_internal String debug_info_mangle_constant_name(Entity *e, gbAllocator const
return name;
}
-gb_internal void add_debug_info_global_variable_expr(lbModule *m, String const &name, LLVMMetadataRef dtype, LLVMMetadataRef expr) {
+gb_internal void lb_add_debug_info_global_variable_expr(lbModule *m, String const &name, LLVMMetadataRef dtype, LLVMMetadataRef expr) {
LLVMMetadataRef scope = nullptr;
LLVMMetadataRef file = nullptr;
unsigned line = 0;
@@ -1212,20 +1291,20 @@ gb_internal void add_debug_info_global_variable_expr(lbModule *m, String const &
expr, decl, 8/*AlignInBits*/);
}
-gb_internal void add_debug_info_for_global_constant_internal_i64(lbModule *m, Entity *e, LLVMMetadataRef dtype, i64 v) {
+gb_internal void lb_add_debug_info_for_global_constant_internal_i64(lbModule *m, Entity *e, LLVMMetadataRef dtype, i64 v) {
LLVMMetadataRef expr = LLVMDIBuilderCreateConstantValueExpression(m->debug_builder, v);
TEMPORARY_ALLOCATOR_GUARD();
- String name = debug_info_mangle_constant_name(e, temporary_allocator(), nullptr);
+ String name = lb_debug_info_mangle_constant_name(e, temporary_allocator(), nullptr);
- add_debug_info_global_variable_expr(m, name, dtype, expr);
+ lb_add_debug_info_global_variable_expr(m, name, dtype, expr);
if ((e->pkg && e->pkg->kind == Package_Init) ||
(e->scope && (e->scope->flags & ScopeFlag_Global))) {
- add_debug_info_global_variable_expr(m, e->token.string, dtype, expr);
+ lb_add_debug_info_global_variable_expr(m, e->token.string, dtype, expr);
}
}
-gb_internal void add_debug_info_for_global_constant_from_entity(lbGenerator *gen, Entity *e) {
+gb_internal void lb_add_debug_info_for_global_constant_from_entity(lbGenerator *gen, Entity *e) {
if (e == nullptr || e->kind != Entity_Constant) {
return;
}
@@ -1256,14 +1335,14 @@ gb_internal void add_debug_info_for_global_constant_from_entity(lbGenerator *gen
dtype = lb_debug_type(m, e->type);
}
- add_debug_info_for_global_constant_internal_i64(m, e, dtype, v);
+ lb_add_debug_info_for_global_constant_internal_i64(m, e, dtype, v);
}
} else if (is_type_rune(e->type)) {
ExactValue const &value = e->Constant.value;
if (value.kind == ExactValue_Integer) {
LLVMMetadataRef dtype = lb_debug_type(m, t_rune);
i64 v = exact_value_to_i64(value);
- add_debug_info_for_global_constant_internal_i64(m, e, dtype, v);
+ lb_add_debug_info_for_global_constant_internal_i64(m, e, dtype, v);
}
} else if (is_type_boolean(e->type)) {
ExactValue const &value = e->Constant.value;
@@ -1271,7 +1350,7 @@ gb_internal void add_debug_info_for_global_constant_from_entity(lbGenerator *gen
LLVMMetadataRef dtype = lb_debug_type(m, default_type(e->type));
i64 v = cast(i64)value.value_bool;
- add_debug_info_for_global_constant_internal_i64(m, e, dtype, v);
+ lb_add_debug_info_for_global_constant_internal_i64(m, e, dtype, v);
}
} else if (is_type_enum(e->type)) {
ExactValue const &value = e->Constant.value;
@@ -1284,14 +1363,70 @@ gb_internal void add_debug_info_for_global_constant_from_entity(lbGenerator *gen
v = cast(i64)exact_value_to_u64(value);
}
- add_debug_info_for_global_constant_internal_i64(m, e, dtype, v);
+ lb_add_debug_info_for_global_constant_internal_i64(m, e, dtype, v);
}
} else if (is_type_pointer(e->type)) {
ExactValue const &value = e->Constant.value;
if (value.kind == ExactValue_Integer) {
LLVMMetadataRef dtype = lb_debug_type(m, default_type(e->type));
i64 v = cast(i64)exact_value_to_u64(value);
- add_debug_info_for_global_constant_internal_i64(m, e, dtype, v);
+ lb_add_debug_info_for_global_constant_internal_i64(m, e, dtype, v);
}
}
}
+
+gb_internal void lb_add_debug_label(lbProcedure *p, Ast *label, lbBlock *target) {
+// NOTE(tf2spi): LLVM-C DILabel API used only existed for major versions 20+
+#if LLVM_VERSION_MAJOR >= 20
+ if (p == nullptr || p->debug_info == nullptr) {
+ return;
+ }
+ if (target == nullptr || label == nullptr || label->kind != Ast_Label) {
+ return;
+ }
+ Token label_token = label->Label.token;
+ if (is_blank_ident(label_token.string)) {
+ return;
+ }
+ lbModule *m = p->module;
+ if (m == nullptr) {
+ return;
+ }
+
+ AstFile *file = label->file();
+ LLVMMetadataRef llvm_file = lb_get_llvm_metadata(m, file);
+ if (llvm_file == nullptr) {
+ debugf("llvm file not found for label\n");
+ return;
+ }
+ LLVMMetadataRef llvm_scope = p->debug_info;
+ if(llvm_scope == nullptr) {
+ debugf("llvm scope not found for label\n");
+ return;
+ }
+ LLVMMetadataRef llvm_debug_loc = lb_debug_location_from_token_pos(p, label_token.pos);
+ LLVMBasicBlockRef llvm_block = target->block;
+ if (llvm_block == nullptr || llvm_debug_loc == nullptr) {
+ return;
+ }
+ LLVMMetadataRef llvm_label = LLVMDIBuilderCreateLabel(
+ m->debug_builder,
+ llvm_scope,
+ (const char *)label_token.string.text,
+ (size_t)label_token.string.len,
+ llvm_file,
+ label_token.pos.line,
+
+ // NOTE(tf2spi): Defaults to false in LLVM API, but I'd rather not take chances
+ // Always preserve the label no matter what when debugging
+ true
+ );
+ GB_ASSERT(llvm_label != nullptr);
+ (void)LLVMDIBuilderInsertLabelAtEnd(
+ m->debug_builder,
+ llvm_label,
+ llvm_debug_loc,
+ llvm_block
+ );
+#endif
+}
diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp
index 0c82180ec..e17d958d7 100644
--- a/src/llvm_backend_expr.cpp
+++ b/src/llvm_backend_expr.cpp
@@ -222,6 +222,7 @@ gb_internal lbValue lb_emit_unary_arith(lbProcedure *p, TokenKind op, lbValue x,
return lb_emit_byte_swap(p, res, type);
}
+ Type* bt = base_type(type);
lbValue res = {};
switch (op) {
@@ -233,6 +234,8 @@ gb_internal lbValue lb_emit_unary_arith(lbProcedure *p, TokenKind op, lbValue x,
case Token_Sub: // Number negation
if (is_type_integer(x.type)) {
res.value = LLVMBuildNeg(p->builder, x.value, "");
+ } else if (bt->kind == Type_Enum && is_type_integer(bt->Enum.base_type)) {
+ res.value = LLVMBuildNeg(p->builder, x.value, "");
} else if (is_type_float(x.type)) {
res.value = LLVMBuildFNeg(p->builder, x.value, "");
} else if (is_type_complex(x.type)) {
@@ -2352,7 +2355,7 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) {
Type *elem = base_array_type(dst);
lbValue e = lb_emit_conv(p, value, elem);
lbAddr v = lb_add_local_generated(p, t, false);
- lbValue zero = lb_const_value(p->module, elem, exact_value_i64(0), true);
+ lbValue zero = lb_const_value(p->module, elem, exact_value_i64(0), LB_CONST_CONTEXT_DEFAULT_ALLOW_LOCAL);
for (i64 j = 0; j < dst->Matrix.column_count; j++) {
for (i64 i = 0; i < dst->Matrix.row_count; i++) {
lbValue ptr = lb_emit_matrix_epi(p, v.addr, i, j);
@@ -2389,7 +2392,7 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) {
lb_emit_store(p, d, s);
} else if (i == j) {
lbValue d = lb_emit_matrix_epi(p, v.addr, i, j);
- lbValue s = lb_const_value(p->module, dst->Matrix.elem, exact_value_i64(1), true);
+ lbValue s = lb_const_value(p->module, dst->Matrix.elem, exact_value_i64(1), LB_CONST_CONTEXT_DEFAULT_ALLOW_LOCAL);
lb_emit_store(p, d, s);
}
}
@@ -2514,7 +2517,7 @@ gb_internal lbValue lb_emit_c_vararg(lbProcedure *p, lbValue arg, Type *type) {
}
gb_internal lbValue lb_compare_records(lbProcedure *p, TokenKind op_kind, lbValue left, lbValue right, Type *type) {
- GB_ASSERT((is_type_struct(type) || is_type_union(type)) && is_type_comparable(type));
+ GB_ASSERT((is_type_struct(type) || is_type_soa_pointer(type) || is_type_union(type)) && is_type_comparable(type));
lbValue left_ptr = lb_address_from_load_or_generate_local(p, left);
lbValue right_ptr = lb_address_from_load_or_generate_local(p, right);
lbValue res = {};
@@ -2899,6 +2902,7 @@ gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left
is_type_proc(a) ||
is_type_enum(a)) {
LLVMIntPredicate pred = {};
+
if (is_type_unsigned(left.type)) {
switch (op_kind) {
case Token_Gt: pred = LLVMIntUGT; break;
@@ -2941,7 +2945,7 @@ gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left
case Token_GtEq: pred = LLVMRealOGE; break;
case Token_Lt: pred = LLVMRealOLT; break;
case Token_LtEq: pred = LLVMRealOLE; break;
- case Token_NotEq: pred = LLVMRealONE; break;
+ case Token_NotEq: pred = LLVMRealUNE; break;
}
if (is_type_different_to_arch_endianness(left.type)) {
@@ -2969,7 +2973,7 @@ gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left
LLVMRealPredicate pred = {};
switch (op_kind) {
case Token_CmpEq: pred = LLVMRealOEQ; break;
- case Token_NotEq: pred = LLVMRealONE; break;
+ case Token_NotEq: pred = LLVMRealUNE; break;
}
mask = LLVMBuildFCmp(p->builder, pred, left.value, right.value, "");
} else {
@@ -3022,6 +3026,9 @@ gb_internal lbValue lb_emit_comp(lbProcedure *p, TokenKind op_kind, lbValue left
return res;
+ } else if (is_type_soa_pointer(a)) {
+ // NOTE(Jeroen): Compare data pointer and index tag as if it were a simple struct.
+ return lb_compare_records(p, op_kind, left, right, a);
} else {
GB_PANIC("Unhandled comparison kind %s (%s) %.*s %s (%s)", type_to_string(left.type), type_to_string(base_type(left.type)), LIT(token_strings[op_kind]), type_to_string(right.type), type_to_string(base_type(right.type)));
}
@@ -3143,6 +3150,18 @@ gb_internal lbValue lb_emit_comp_against_nil(lbProcedure *p, TokenKind op_kind,
}
}
break;
+
+ case Type_SoaPointer:
+ {
+ // NOTE(bill): An SoaPointer is essentially just a pointer for nil comparison
+ lbValue ptr = lb_emit_struct_ev(p, x, 0); // Extract the base pointer component (field 0)
+ if (op_kind == Token_CmpEq) {
+ res.value = LLVMBuildIsNull(p->builder, ptr.value, "");
+ } else if (op_kind == Token_NotEq) {
+ res.value = LLVMBuildIsNotNull(p->builder, ptr.value, "");
+ }
+ return res;
+ }
case Type_Union:
{
@@ -3493,8 +3512,7 @@ gb_internal lbValue lb_build_expr_internal(lbProcedure *p, Ast *expr) {
if (tv.value.kind != ExactValue_Invalid) {
// NOTE(bill): Short on constant values
- bool allow_local = true;
- return lb_const_value(p->module, type, tv.value, allow_local);
+ return lb_const_value(p->module, type, tv.value, LB_CONST_CONTEXT_DEFAULT_ALLOW_LOCAL);
} else if (tv.mode == Addressing_Type) {
// NOTE(bill, 2023-01-16): is this correct? I hope so at least
return lb_typeid(m, tv.type);
@@ -4826,7 +4844,7 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) {
if (cl->elems.count == 0) {
break;
}
- GB_ASSERT(expr->file()->feature_flags & OptInFeatureFlag_DynamicLiterals);
+ GB_ASSERT(expr->file()->feature_flags & OptInFeatureFlag_DynamicLiterals || build_context.dynamic_literals);
lbValue err = lb_dynamic_map_reserve(p, v.addr, 2*cl->elems.count, pos);
gb_unused(err);
@@ -5136,8 +5154,6 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) {
return lb_build_addr(p, unparen_expr(se->selector));
}
-
- Type *type = base_type(tav.type);
if (tav.mode == Addressing_Type) { // Addressing_Type
Selection sel = lookup_field(tav.type, selector, true);
if (sel.pseudo_field) {
@@ -5172,18 +5188,29 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) {
return lb_addr_swizzle(a, type, swizzle_count, swizzle_indices);
}
- Selection sel = lookup_field(type, selector, false);
+ Selection sel = lookup_field(tav.type, selector, false);
GB_ASSERT(sel.entity != nullptr);
- if (sel.pseudo_field) {
- GB_ASSERT(sel.entity->kind == Entity_Procedure || sel.entity->kind == Entity_ProcGroup);
+ if (sel.pseudo_field && (sel.entity->kind == Entity_Procedure || sel.entity->kind == Entity_ProcGroup)) {
Entity *e = entity_of_node(sel_node);
GB_ASSERT(e->kind == Entity_Procedure);
return lb_addr(lb_find_value_from_entity(p->module, e));
}
- if (sel.is_bit_field) {
- lbAddr addr = lb_build_addr(p, se->expr);
+ lbAddr addr = lb_build_addr(p, se->expr);
+ // NOTE(harold): Only allow ivar pseudo field access on indirect selectors.
+ // It is incoherent otherwise as Objective-C objects are zero-sized.
+ Type *deref_type = type_deref(tav.type);
+ if (tav.type->kind == Type_Pointer && deref_type->kind == Type_Named && deref_type->Named.type_name->TypeName.objc_ivar) {
+ // NOTE(harold): We need to load the ivar from the current address and
+ // replace addr with the loaded ivar addr to apply the selector load properly.
+ addr = lb_addr(lb_emit_load(p, addr.addr));
+
+ lbValue ivar_ptr = lb_handle_objc_ivar_for_objc_object_pointer(p, addr.addr);
+ addr = lb_addr(ivar_ptr);
+ }
+
+ if (sel.is_bit_field) {
Selection sub_sel = sel;
sub_sel.index.count -= 1;
@@ -5209,7 +5236,6 @@ gb_internal lbAddr lb_build_addr_internal(lbProcedure *p, Ast *expr) {
}
{
- lbAddr addr = lb_build_addr(p, se->expr);
if (addr.kind == lbAddr_Map) {
lbValue v = lb_addr_load(p, addr);
lbValue a = lb_address_from_load_or_generate_local(p, v);
diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp
index ce2c70661..3a099ec55 100644
--- a/src/llvm_backend_general.cpp
+++ b/src/llvm_backend_general.cpp
@@ -101,6 +101,7 @@ gb_internal void lb_init_module(lbModule *m, Checker *c) {
string_map_init(&m->objc_classes);
string_map_init(&m->objc_selectors);
+ string_map_init(&m->objc_ivars);
map_init(&m->map_info_map, 0);
map_init(&m->map_cell_info_map, 0);
@@ -173,7 +174,9 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) {
mpsc_init(&gen->entities_to_correct_linkage, heap_allocator());
mpsc_init(&gen->objc_selectors, heap_allocator());
mpsc_init(&gen->objc_classes, heap_allocator());
-
+ mpsc_init(&gen->objc_ivars, heap_allocator());
+ mpsc_init(&gen->raddebug_section_strings, heap_allocator());
+
return true;
}
@@ -356,7 +359,7 @@ gb_internal LLVMValueRef llvm_const_insert_value(lbModule *m, LLVMValueRef agg,
gb_internal LLVMValueRef llvm_cstring(lbModule *m, String const &str) {
- lbValue v = lb_find_or_add_entity_string(m, str);
+ lbValue v = lb_find_or_add_entity_string(m, str, false);
unsigned indices[1] = {0};
return llvm_const_extract_value(m, v.value, indices, gb_count_of(indices));
}
@@ -568,7 +571,7 @@ gb_internal void lb_set_file_line_col(lbProcedure *p, Array arr, TokenP
col = obfuscate_i32(col);
}
- arr[0] = lb_find_or_add_entity_string(p->module, file);
+ arr[0] = lb_find_or_add_entity_string(p->module, file, false);
arr[1] = lb_const_int(p->module, t_i32, line);
arr[2] = lb_const_int(p->module, t_i32, col);
}
@@ -885,8 +888,8 @@ gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value) {
Type *t = base_type(type_deref(addr.addr.type));
GB_ASSERT(t->kind == Type_Struct && t->Struct.soa_kind != StructSoa_None);
lbValue len = lb_soa_struct_len(p, addr.addr);
- if (addr.soa.index_expr != nullptr) {
- lb_emit_bounds_check(p, ast_token(addr.soa.index_expr), index, len);
+ if (addr.soa.index_expr != nullptr && (!lb_is_const(addr.soa.index) || t->Struct.soa_kind != StructSoa_Fixed)) {
+ lb_emit_bounds_check(p, ast_token(addr.soa.index_expr), addr.soa.index, len);
}
}
@@ -2212,6 +2215,14 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) {
case Type_BitField:
return lb_type_internal(m, type->BitField.backing_type);
+
+ case Type_Generic:
+ if (type->Generic.specialized) {
+ return lb_type_internal(m, type->Generic.specialized);
+ } else {
+ // For unspecialized generics, use a pointer type as a placeholder
+ return LLVMPointerType(LLVMInt8TypeInContext(m->ctx), 0);
+ }
}
GB_PANIC("Invalid type %s", type_to_string(type));
@@ -2377,6 +2388,29 @@ gb_internal void lb_add_attribute_to_proc_with_string(lbModule *m, LLVMValueRef
}
+gb_internal bool lb_apply_thread_local_model(LLVMValueRef value, String model) {
+ if (model != "") {
+ LLVMSetThreadLocal(value, true);
+
+ LLVMThreadLocalMode mode = LLVMGeneralDynamicTLSModel;
+ if (model == "default") {
+ mode = LLVMGeneralDynamicTLSModel;
+ } else if (model == "localdynamic") {
+ mode = LLVMLocalDynamicTLSModel;
+ } else if (model == "initialexec") {
+ mode = LLVMInitialExecTLSModel;
+ } else if (model == "localexec") {
+ mode = LLVMLocalExecTLSModel;
+ } else {
+ GB_PANIC("Unhandled thread local mode %.*s", LIT(model));
+ }
+ LLVMSetThreadLocalMode(value, mode);
+ return true;
+ }
+
+ return false;
+}
+
gb_internal void lb_add_edge(lbBlock *from, lbBlock *to) {
LLVMValueRef instr = LLVMGetLastInstruction(from->block);
@@ -2515,10 +2549,13 @@ general_end:;
}
}
- src_size = align_formula(src_size, src_align);
- dst_size = align_formula(dst_size, dst_align);
+ // NOTE(laytan): even though this logic seems sound, the Address Sanitizer does not
+ // want you to load/store the space of a value that is there for alignment.
+#if 0
+ i64 aligned_src_size = align_formula(src_size, src_align);
+ i64 aligned_dst_size = align_formula(dst_size, dst_align);
- if (LLVMIsALoadInst(val) && (src_size >= dst_size && src_align >= dst_align)) {
+ if (LLVMIsALoadInst(val) && (aligned_src_size >= aligned_dst_size && src_align >= dst_align)) {
LLVMValueRef val_ptr = LLVMGetOperand(val, 0);
val_ptr = LLVMBuildPointerCast(p->builder, val_ptr, LLVMPointerType(dst_type, 0), "");
LLVMValueRef loaded_val = OdinLLVMBuildLoad(p, dst_type, val_ptr);
@@ -2526,8 +2563,57 @@ general_end:;
// LLVMSetAlignment(loaded_val, gb_min(src_align, dst_align));
return loaded_val;
+ }
+#endif
+
+ if (src_size > dst_size) {
+ GB_ASSERT(p->decl_block != p->curr_block);
+ // NOTE(laytan): src is bigger than dst, need to memcpy the part of src we want.
+
+ LLVMValueRef val_ptr;
+ if (LLVMIsALoadInst(val)) {
+ val_ptr = LLVMGetOperand(val, 0);
+ } else if (LLVMIsAAllocaInst(val)) {
+ val_ptr = LLVMBuildPointerCast(p->builder, val, LLVMPointerType(src_type, 0), "");
+ } else {
+ // NOTE(laytan): we need a pointer to memcpy from.
+ LLVMValueRef val_copy = llvm_alloca(p, src_type, src_align);
+ val_ptr = LLVMBuildPointerCast(p->builder, val_copy, LLVMPointerType(src_type, 0), "");
+ LLVMBuildStore(p->builder, val, val_ptr);
+ }
+
+ i64 max_align = gb_max(lb_alignof(src_type), lb_alignof(dst_type));
+ max_align = gb_max(max_align, 16);
+
+ LLVMValueRef ptr = llvm_alloca(p, dst_type, max_align);
+ LLVMValueRef nptr = LLVMBuildPointerCast(p->builder, ptr, LLVMPointerType(dst_type, 0), "");
+
+ LLVMTypeRef types[3] = {
+ lb_type(p->module, t_rawptr),
+ lb_type(p->module, t_rawptr),
+ lb_type(p->module, t_int)
+ };
+
+ LLVMValueRef args[4] = {
+ nptr,
+ val_ptr,
+ LLVMConstInt(LLVMIntTypeInContext(p->module->ctx, 8*cast(unsigned)build_context.int_size), dst_size, 0),
+ LLVMConstInt(LLVMInt1TypeInContext(p->module->ctx), 0, 0),
+ };
+
+ lb_call_intrinsic(
+ p,
+ "llvm.memcpy.inline",
+ args,
+ gb_count_of(args),
+ types,
+ gb_count_of(types)
+ );
+
+ return OdinLLVMBuildLoad(p, dst_type, ptr);
} else {
GB_ASSERT(p->decl_block != p->curr_block);
+ GB_ASSERT(dst_size >= src_size);
i64 max_align = gb_max(lb_alignof(src_type), lb_alignof(dst_type));
max_align = gb_max(max_align, 16);
@@ -2543,9 +2629,14 @@ general_end:;
-gb_internal LLVMValueRef lb_find_or_add_entity_string_ptr(lbModule *m, String const &str) {
- StringHashKey key = string_hash_string(str);
- LLVMValueRef *found = string_map_get(&m->const_strings, key);
+gb_internal LLVMValueRef lb_find_or_add_entity_string_ptr(lbModule *m, String const &str, bool custom_link_section) {
+ StringHashKey key = {};
+ LLVMValueRef *found = nullptr;
+
+ if (!custom_link_section) {
+ key = string_hash_string(str);
+ found = string_map_get(&m->const_strings, key);
+ }
if (found != nullptr) {
return *found;
} else {
@@ -2568,15 +2659,17 @@ gb_internal LLVMValueRef lb_find_or_add_entity_string_ptr(lbModule *m, String co
LLVMSetAlignment(global_data, 1);
LLVMValueRef ptr = LLVMConstInBoundsGEP2(type, global_data, indices, 2);
- string_map_set(&m->const_strings, key, ptr);
+ if (!custom_link_section) {
+ string_map_set(&m->const_strings, key, ptr);
+ }
return ptr;
}
}
-gb_internal lbValue lb_find_or_add_entity_string(lbModule *m, String const &str) {
+gb_internal lbValue lb_find_or_add_entity_string(lbModule *m, String const &str, bool custom_link_section) {
LLVMValueRef ptr = nullptr;
if (str.len != 0) {
- ptr = lb_find_or_add_entity_string_ptr(m, str);
+ ptr = lb_find_or_add_entity_string_ptr(m, str, custom_link_section);
} else {
ptr = LLVMConstNull(lb_type(m, t_u8_ptr));
}
@@ -2721,6 +2814,14 @@ gb_internal lbValue lb_find_procedure_value_from_entity(lbModule *m, Entity *e)
ignore_body = other_module != m;
lbProcedure *missing_proc = lb_create_procedure(m, e, ignore_body);
+ if (missing_proc == nullptr) {
+ // This is an unspecialized polymorphic procedure, which should not be codegen'd
+ lbValue dummy = {};
+ dummy.value = nullptr;
+ dummy.type = nullptr;
+ return dummy;
+ }
+
if (ignore_body) {
mutex_lock(&gen->anonymous_proc_lits_mutex);
defer (mutex_unlock(&gen->anonymous_proc_lits_mutex));
@@ -2913,25 +3014,7 @@ gb_internal lbValue lb_find_value_from_entity(lbModule *m, Entity *e) {
lb_set_entity_from_other_modules_linkage_correctly(other_module, e, name);
- if (e->Variable.thread_local_model != "") {
- LLVMSetThreadLocal(g.value, true);
-
- String m = e->Variable.thread_local_model;
- LLVMThreadLocalMode mode = LLVMGeneralDynamicTLSModel;
- if (m == "default") {
- mode = LLVMGeneralDynamicTLSModel;
- } else if (m == "localdynamic") {
- mode = LLVMLocalDynamicTLSModel;
- } else if (m == "initialexec") {
- mode = LLVMInitialExecTLSModel;
- } else if (m == "localexec") {
- mode = LLVMLocalExecTLSModel;
- } else {
- GB_PANIC("Unhandled thread local mode %.*s", LIT(m));
- }
- LLVMSetThreadLocalMode(g.value, mode);
- }
-
+ lb_apply_thread_local_model(g.value, e->Variable.thread_local_model);
return g;
}
@@ -3063,6 +3146,13 @@ gb_internal lbAddr lb_add_local(lbProcedure *p, Type *type, Entity *e, bool zero
if (e != nullptr) {
lb_add_entity(p->module, e, val);
lb_add_debug_local_variable(p, ptr, type, e->token);
+
+ // NOTE(lucas): In LLVM 20 and below we do not have the option to have asan cleanup poisoned stack
+ // locals ourselves. So we need to manually track and unpoison these locals on proc return.
+ // LLVM 21 adds the 'use-after-scope' asan option which does this for us.
+ if (build_context.sanitizer_flags & SanitizerFlag_Address && !p->entity->Procedure.no_sanitize_address) {
+ array_add(&p->asan_stack_locals, val);
+ }
}
if (zero_init) {
diff --git a/src/llvm_backend_opt.cpp b/src/llvm_backend_opt.cpp
index 7fe1359b4..8d5cfcb70 100644
--- a/src/llvm_backend_opt.cpp
+++ b/src/llvm_backend_opt.cpp
@@ -516,7 +516,7 @@ gb_internal void llvm_delete_function(LLVMValueRef func) {
LLVMDeleteFunction(func);
}
-gb_internal void lb_append_to_compiler_used(lbModule *m, LLVMValueRef func) {
+gb_internal void lb_append_to_compiler_used(lbModule *m, LLVMValueRef value) {
LLVMValueRef global = LLVMGetNamedGlobal(m->mod, "llvm.compiler.used");
LLVMValueRef *constants;
@@ -544,7 +544,7 @@ gb_internal void lb_append_to_compiler_used(lbModule *m, LLVMValueRef func) {
LLVMTypeRef Int8PtrTy = LLVMPointerType(LLVMInt8TypeInContext(m->ctx), 0);
LLVMTypeRef ATy = llvm_array_type(Int8PtrTy, operands);
- constants[operands - 1] = LLVMConstBitCast(func, Int8PtrTy);
+ constants[operands - 1] = LLVMConstBitCast(value, Int8PtrTy);
LLVMValueRef initializer = LLVMConstArray(Int8PtrTy, constants, operands);
global = LLVMAddGlobal(m->mod, ATy, "llvm.compiler.used");
diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp
index 304db75bc..f51ed2b4d 100644
--- a/src/llvm_backend_proc.cpp
+++ b/src/llvm_backend_proc.cpp
@@ -67,6 +67,14 @@ gb_internal void lb_mem_copy_non_overlapping(lbProcedure *p, lbValue dst, lbValu
gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool ignore_body) {
GB_ASSERT(entity != nullptr);
GB_ASSERT(entity->kind == Entity_Procedure);
+ // Skip codegen for unspecialized polymorphic procedures
+ if (is_type_polymorphic(entity->type) && !entity->Procedure.is_foreign) {
+ Type *bt = base_type(entity->type);
+ if (bt->kind == Type_Proc && bt->Proc.is_polymorphic && !bt->Proc.is_poly_specialized) {
+ // Do not generate code for unspecialized polymorphic procedures
+ return nullptr;
+ }
+ }
if (!entity->Procedure.is_foreign) {
if ((entity->flags & EntityFlag_ProcBodyChecked) == 0) {
GB_PANIC("%.*s :: %s (was parapoly: %d %d)", LIT(entity->token.string), type_to_string(entity->type), is_type_polymorphic(entity->type, true), is_type_polymorphic(entity->type, false));
@@ -115,12 +123,13 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i
p->is_entry_point = false;
gbAllocator a = heap_allocator();
- p->children.allocator = a;
- p->defer_stmts.allocator = a;
- p->blocks.allocator = a;
- p->branch_blocks.allocator = a;
- p->context_stack.allocator = a;
- p->scope_stack.allocator = a;
+ p->children.allocator = a;
+ p->defer_stmts.allocator = a;
+ p->blocks.allocator = a;
+ p->branch_blocks.allocator = a;
+ p->context_stack.allocator = a;
+ p->scope_stack.allocator = a;
+ p->asan_stack_locals.allocator = a;
// map_init(&p->selector_values, 0);
// map_init(&p->selector_addr, 0);
// map_init(&p->tuple_fix_map, 0);
@@ -333,7 +342,7 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i
}
if (p->body && entity->pkg && ((entity->pkg->kind == Package_Normal) || (entity->pkg->kind == Package_Init))) {
- if (build_context.sanitizer_flags & SanitizerFlag_Address) {
+ if (build_context.sanitizer_flags & SanitizerFlag_Address && !entity->Procedure.no_sanitize_address) {
lb_add_attribute_to_proc(m, p->value, "sanitize_address");
}
if (build_context.sanitizer_flags & SanitizerFlag_Memory) {
@@ -385,11 +394,12 @@ gb_internal lbProcedure *lb_create_dummy_procedure(lbModule *m, String link_name
p->is_entry_point = false;
gbAllocator a = permanent_allocator();
- p->children.allocator = a;
- p->defer_stmts.allocator = a;
- p->blocks.allocator = a;
- p->branch_blocks.allocator = a;
- p->context_stack.allocator = a;
+ p->children.allocator = a;
+ p->defer_stmts.allocator = a;
+ p->blocks.allocator = a;
+ p->branch_blocks.allocator = a;
+ p->context_stack.allocator = a;
+ p->asan_stack_locals.allocator = a;
map_init(&p->tuple_fix_map, 0);
@@ -781,8 +791,7 @@ gb_internal void lb_end_procedure_body(lbProcedure *p) {
p->curr_block = nullptr;
p->state_flags = 0;
-}
-gb_internal void lb_end_procedure(lbProcedure *p) {
+
LLVMDisposeBuilder(p->builder);
}
@@ -815,6 +824,10 @@ gb_internal void lb_build_nested_proc(lbProcedure *p, AstProcLit *pd, Entity *e)
e->Procedure.link_name = name;
lbProcedure *nested_proc = lb_create_procedure(p->module, e);
+ if (nested_proc == nullptr) {
+ // This is an unspecialized polymorphic procedure, skip codegen
+ return;
+ }
e->code_gen_procedure = nested_proc;
lbValue value = {};
@@ -1293,6 +1306,23 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn
lbValue res = {};
res.type = tv.type;
+ switch (builtin_id) {
+ case BuiltinProc_simd_indices: {
+ Type *type = base_type(res.type);
+ GB_ASSERT(type->kind == Type_SimdVector);
+ Type *elem = type->SimdVector.elem;
+
+ i64 count = type->SimdVector.count;
+ LLVMValueRef *scalars = gb_alloc_array(temporary_allocator(), LLVMValueRef, count);
+ for (i64 i = 0; i < count; i++) {
+ scalars[i] = lb_const_value(m, elem, exact_value_i64(i)).value;
+ }
+
+ res.value = LLVMConstVector(scalars, cast(unsigned)count);
+ return res;
+ }
+ }
+
lbValue arg0 = {}; if (ce->args.count > 0) arg0 = lb_build_expr(p, ce->args[0]);
lbValue arg1 = {}; if (ce->args.count > 1) arg1 = lb_build_expr(p, ce->args[1]);
lbValue arg2 = {}; if (ce->args.count > 2) arg2 = lb_build_expr(p, ce->args[2]);
@@ -1442,7 +1472,7 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn
LLVMRealPredicate pred = cast(LLVMRealPredicate)0;
switch (builtin_id) {
case BuiltinProc_simd_lanes_eq: pred = LLVMRealOEQ; break;
- case BuiltinProc_simd_lanes_ne: pred = LLVMRealONE; break;
+ case BuiltinProc_simd_lanes_ne: pred = LLVMRealUNE; break;
case BuiltinProc_simd_lanes_lt: pred = LLVMRealOLT; break;
case BuiltinProc_simd_lanes_le: pred = LLVMRealOLE; break;
case BuiltinProc_simd_lanes_gt: pred = LLVMRealOGT; break;
@@ -1478,6 +1508,38 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn
res.value = LLVMBuildInsertElement(p->builder, arg0.value, arg2.value, arg1.value, "");
return res;
+ case BuiltinProc_simd_reduce_add_bisect:
+ case BuiltinProc_simd_reduce_mul_bisect:
+ {
+ GB_ASSERT(arg0.type->kind == Type_SimdVector);
+ i64 num_elems = arg0.type->SimdVector.count;
+
+ LLVMValueRef *indices = gb_alloc_array(temporary_allocator(), LLVMValueRef, num_elems);
+ for (i64 i = 0; i < num_elems; i++) {
+ indices[i] = lb_const_int(m, t_uint, cast(u64)i).value;
+ }
+
+ switch (builtin_id) {
+ case BuiltinProc_simd_reduce_add_bisect: op_code = is_float ? LLVMFAdd : LLVMAdd; break;
+ case BuiltinProc_simd_reduce_mul_bisect: op_code = is_float ? LLVMFMul : LLVMMul; break;
+ }
+
+ LLVMValueRef remaining = arg0.value;
+ i64 num_remaining = num_elems;
+
+ while (num_remaining > 1) {
+ num_remaining /= 2;
+ LLVMValueRef left_indices = LLVMConstVector(&indices[0], cast(unsigned)num_remaining);
+ LLVMValueRef left_value = LLVMBuildShuffleVector(p->builder, remaining, remaining, left_indices, "");
+ LLVMValueRef right_indices = LLVMConstVector(&indices[num_remaining], cast(unsigned)num_remaining);
+ LLVMValueRef right_value = LLVMBuildShuffleVector(p->builder, remaining, remaining, right_indices, "");
+ remaining = LLVMBuildBinOp(p->builder, op_code, left_value, right_value, "");
+ }
+
+ res.value = LLVMBuildExtractElement(p->builder, remaining, indices[0], "");
+ return res;
+ }
+
case BuiltinProc_simd_reduce_add_ordered:
case BuiltinProc_simd_reduce_mul_ordered:
{
@@ -1510,6 +1572,40 @@ gb_internal lbValue lb_build_builtin_simd_proc(lbProcedure *p, Ast *expr, TypeAn
res.value = lb_call_intrinsic(p, name, args, cast(unsigned)args_count, types, gb_count_of(types));
return res;
}
+
+ case BuiltinProc_simd_reduce_add_pairs:
+ case BuiltinProc_simd_reduce_mul_pairs:
+ {
+ GB_ASSERT(arg0.type->kind == Type_SimdVector);
+ i64 num_elems = arg0.type->SimdVector.count;
+
+ LLVMValueRef *indices = gb_alloc_array(temporary_allocator(), LLVMValueRef, num_elems);
+ for (i64 i = 0; i < num_elems/2; i++) {
+ indices[i] = lb_const_int(m, t_uint, cast(u64)(2*i)).value;
+ indices[i+num_elems/2] = lb_const_int(m, t_uint, cast(u64)(2*i+1)).value;
+ }
+
+ switch (builtin_id) {
+ case BuiltinProc_simd_reduce_add_pairs: op_code = is_float ? LLVMFAdd : LLVMAdd; break;
+ case BuiltinProc_simd_reduce_mul_pairs: op_code = is_float ? LLVMFMul : LLVMMul; break;
+ }
+
+ LLVMValueRef remaining = arg0.value;
+ i64 num_remaining = num_elems;
+
+ while (num_remaining > 1) {
+ num_remaining /= 2;
+ LLVMValueRef left_indices = LLVMConstVector(&indices[0], cast(unsigned)num_remaining);
+ LLVMValueRef left_value = LLVMBuildShuffleVector(p->builder, remaining, remaining, left_indices, "");
+ LLVMValueRef right_indices = LLVMConstVector(&indices[num_elems/2], cast(unsigned)num_remaining);
+ LLVMValueRef right_value = LLVMBuildShuffleVector(p->builder, remaining, remaining, right_indices, "");
+ remaining = LLVMBuildBinOp(p->builder, op_code, left_value, right_value, "");
+ }
+
+ res.value = LLVMBuildExtractElement(p->builder, remaining, indices[0], "");
+ return res;
+ }
+
case BuiltinProc_simd_reduce_min:
case BuiltinProc_simd_reduce_max:
case BuiltinProc_simd_reduce_and:
@@ -2150,6 +2246,68 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
return lb_emit_load(p, tuple);
}
+ case BuiltinProc_compress_values: {
+ isize value_count = 0;
+ for (Ast *arg : ce->args) {
+ Type *t = arg->tav.type;
+ if (is_type_tuple(t)) {
+ value_count += t->Tuple.variables.count;
+ } else {
+ value_count += 1;
+ }
+ }
+
+ if (value_count == 1) {
+ lbValue x = lb_build_expr(p, ce->args[0]);
+ x = lb_emit_conv(p, x, tv.type);
+ return x;
+ }
+
+ Type *dt = base_type(tv.type);
+ lbAddr addr = lb_add_local_generated(p, tv.type, true);
+ if (is_type_struct(dt) || is_type_tuple(dt)) {
+ i32 index = 0;
+ for (Ast *arg : ce->args) {
+ lbValue x = lb_build_expr(p, arg);
+ if (is_type_tuple(x.type)) {
+ for (isize i = 0; i < x.type->Tuple.variables.count; i++) {
+ lbValue y = lb_emit_tuple_ev(p, x, cast(i32)i);
+ lbValue ptr = lb_emit_struct_ep(p, addr.addr, index++);
+ y = lb_emit_conv(p, y, type_deref(ptr.type));
+ lb_emit_store(p, ptr, y);
+ }
+ } else {
+ lbValue ptr = lb_emit_struct_ep(p, addr.addr, index++);
+ x = lb_emit_conv(p, x, type_deref(ptr.type));
+ lb_emit_store(p, ptr, x);
+ }
+ }
+ GB_ASSERT(index == value_count);
+ } else if (is_type_array_like(dt)) {
+ i32 index = 0;
+ for (Ast *arg : ce->args) {
+ lbValue x = lb_build_expr(p, arg);
+ if (is_type_tuple(x.type)) {
+ for (isize i = 0; i < x.type->Tuple.variables.count; i++) {
+ lbValue y = lb_emit_tuple_ev(p, x, cast(i32)i);
+ lbValue ptr = lb_emit_array_epi(p, addr.addr, index++);
+ y = lb_emit_conv(p, y, type_deref(ptr.type));
+ lb_emit_store(p, ptr, y);
+ }
+ } else {
+ lbValue ptr = lb_emit_array_epi(p, addr.addr, index++);
+ x = lb_emit_conv(p, x, type_deref(ptr.type));
+ lb_emit_store(p, ptr, x);
+ }
+ }
+ GB_ASSERT(index == value_count);
+ } else {
+ GB_PANIC("TODO(bill): compress_values -> %s", type_to_string(tv.type));
+ }
+
+ return lb_addr_load(p, addr);
+ }
+
case BuiltinProc_min: {
Type *t = type_of_expr(expr);
if (ce->args.count == 2) {
@@ -3226,11 +3384,28 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
{
GB_ASSERT(arg_count <= 7);
- char asm_string[] = "svc #0; cset x8, cc";
- gbString constraints = gb_string_make(heap_allocator(), "={x0},={x8}");
- for (unsigned i = 0; i < arg_count; i++) {
- constraints = gb_string_appendc(constraints, ",{");
- static char const *regs[] = {
+ char const *asm_string;
+ char const **regs;
+ gbString constraints;
+
+ if (build_context.metrics.os == TargetOs_netbsd) {
+ asm_string = "svc #0; cset x17, cc";
+ constraints = gb_string_make(heap_allocator(), "={x0},={x17}");
+ static char const *_regs[] = {
+ "x17",
+ "x0",
+ "x1",
+ "x2",
+ "x3",
+ "x4",
+ "x5",
+ };
+ regs = _regs;
+ } else {
+ // FreeBSD (tested), OpenBSD (untested).
+ asm_string = "svc #0; cset x8, cc";
+ constraints = gb_string_make(heap_allocator(), "={x0},={x8}");
+ static char const *_regs[] = {
"x8",
"x0",
"x1",
@@ -3239,13 +3414,19 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
"x4",
"x5",
};
+ regs = _regs;
+
+ // FreeBSD clobbered x1 on a call to sysctl.
+ constraints = gb_string_appendc(constraints, ",~{x1}");
+ }
+
+ for (unsigned i = 0; i < arg_count; i++) {
+ constraints = gb_string_appendc(constraints, ",{");
constraints = gb_string_appendc(constraints, regs[i]);
constraints = gb_string_appendc(constraints, "}");
}
- // FreeBSD clobbered x1 on a call to sysctl.
- constraints = gb_string_appendc(constraints, ",~{x1},~{cc},~{memory}");
-
+ constraints = gb_string_appendc(constraints, ",~{cc},~{memory}");
inline_asm = llvm_get_inline_asm(func_type, make_string_c(asm_string), make_string_c(constraints));
}
break;
@@ -3267,6 +3448,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu
case BuiltinProc_objc_find_class: return lb_handle_objc_find_class(p, expr);
case BuiltinProc_objc_register_selector: return lb_handle_objc_register_selector(p, expr);
case BuiltinProc_objc_register_class: return lb_handle_objc_register_class(p, expr);
+ case BuiltinProc_objc_ivar_get: return lb_handle_objc_ivar_get(p, expr);
case BuiltinProc_constant_utf16_cstring:
diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp
index 02ed8b092..efd2e4f87 100644
--- a/src/llvm_backend_stmt.cpp
+++ b/src/llvm_backend_stmt.cpp
@@ -136,7 +136,6 @@ gb_internal lbBranchBlocks lb_lookup_branch_blocks(lbProcedure *p, Ast *ident) {
return empty;
}
-
gb_internal lbTargetList *lb_push_target_list(lbProcedure *p, Ast *label, lbBlock *break_, lbBlock *continue_, lbBlock *fallthrough_) {
lbTargetList *tl = gb_alloc_item(permanent_allocator(), lbTargetList);
tl->prev = p->target_list;
@@ -688,6 +687,18 @@ gb_internal void lb_build_range_interval(lbProcedure *p, AstBinaryExpr *node,
lbBlock *body = lb_create_block(p, "for.interval.body");
lbBlock *done = lb_create_block(p, "for.interval.done");
+ // TODO(tf2spi): This is inlined in more than several places.
+ // Putting this in a function might be preferred.
+ // LLVMSetCurrentDebugLocation2 has side effects,
+ // so I didn't want to hide that before it got reviewed.
+ if (rs->label != nullptr && p->debug_info != nullptr) {
+ lbBlock *label = lb_create_block(p, "for.interval.label");
+ lb_emit_jump(p, label);
+ lb_start_block(p, label);
+
+ LLVMSetCurrentDebugLocation2(p->builder, lb_debug_location_from_ast(p, rs->label));
+ lb_add_debug_label(p, rs->label, label);
+ }
lb_emit_jump(p, loop);
lb_start_block(p, loop);
@@ -893,6 +904,14 @@ gb_internal void lb_build_range_stmt_struct_soa(lbProcedure *p, AstRangeStmt *rs
lbAddr index = lb_add_local_generated(p, t_int, false);
+ if (rs->label != nullptr && p->debug_info != nullptr) {
+ lbBlock *label = lb_create_block(p, "for.soa.label");
+ lb_emit_jump(p, label);
+ lb_start_block(p, label);
+
+ LLVMSetCurrentDebugLocation2(p->builder, lb_debug_location_from_ast(p, rs->label));
+ lb_add_debug_label(p, rs->label, label);
+ }
if (!is_reverse) {
/*
for x, i in array {
@@ -970,7 +989,6 @@ gb_internal void lb_build_range_stmt_struct_soa(lbProcedure *p, AstRangeStmt *rs
lb_store_range_stmt_val(p, val1, lb_addr_load(p, index));
}
-
lb_push_target_list(p, rs->label, done, loop, nullptr);
lb_build_stmt(p, rs->body);
@@ -1029,6 +1047,15 @@ gb_internal void lb_build_range_stmt(lbProcedure *p, AstRangeStmt *rs, Scope *sc
lbBlock *done = nullptr;
bool is_map = false;
+ if (rs->label != nullptr && p->debug_info != nullptr) {
+ lbBlock *label = lb_create_block(p, "for.range.label");
+ lb_emit_jump(p, label);
+ lb_start_block(p, label);
+
+ LLVMSetCurrentDebugLocation2(p->builder, lb_debug_location_from_ast(p, rs->label));
+ lb_add_debug_label(p, rs->label, label);
+ }
+
if (tav.mode == Addressing_Type) {
lb_build_range_enum(p, type_deref(tav.type), val0_type, &val, &key, &loop, &done);
} else {
@@ -1530,6 +1557,14 @@ gb_internal bool lb_switch_stmt_can_be_trivial_jump_table(AstSwitchStmt *ss, boo
gb_internal void lb_build_switch_stmt(lbProcedure *p, AstSwitchStmt *ss, Scope *scope) {
lb_open_scope(p, scope);
+ if (ss->label != nullptr && p->debug_info != nullptr) {
+ lbBlock *label = lb_create_block(p, "switch.label");
+ lb_emit_jump(p, label);
+ lb_start_block(p, label);
+
+ LLVMSetCurrentDebugLocation2(p->builder, lb_debug_location_from_ast(p, ss->label));
+ lb_add_debug_label(p, ss->label, label);
+ }
if (ss->init != nullptr) {
lb_build_stmt(p, ss->init);
}
@@ -1736,6 +1771,7 @@ gb_internal lbAddr lb_store_range_stmt_val(lbProcedure *p, Ast *stmt_val, lbValu
gb_internal void lb_type_case_body(lbProcedure *p, Ast *label, Ast *clause, lbBlock *body, lbBlock *done) {
ast_node(cc, CaseClause, clause);
+ // NOTE(tf2spi): Debug info for label not generated here on purpose
lb_push_target_list(p, label, done, nullptr, nullptr);
lb_build_stmt_list(p, cc->stmts);
lb_close_scope(p, lbDeferExit_Default, body, clause);
@@ -1963,8 +1999,7 @@ gb_internal void lb_build_static_variables(lbProcedure *p, AstValueDecl *vd) {
GB_ASSERT(ast_value->tav.mode == Addressing_Constant ||
ast_value->tav.mode == Addressing_Invalid);
- bool allow_local = false;
- value = lb_const_value(p->module, ast_value->tav.type, ast_value->tav.value, allow_local);
+ value = lb_const_value(p->module, ast_value->tav.type, ast_value->tav.value, LB_CONST_CONTEXT_DEFAULT_NO_LOCAL);
}
Ast *ident = vd->names[i];
@@ -1985,34 +2020,45 @@ gb_internal void lb_build_static_variables(lbProcedure *p, AstValueDecl *vd) {
char *c_name = alloc_cstring(permanent_allocator(), mangled_name);
LLVMValueRef global = LLVMAddGlobal(p->module->mod, lb_type(p->module, e->type), c_name);
+ LLVMSetAlignment(global, cast(u32)type_align_of(e->type));
LLVMSetInitializer(global, LLVMConstNull(lb_type(p->module, e->type)));
- if (value.value != nullptr) {
- LLVMSetInitializer(global, value.value);
- }
+
if (e->Variable.is_rodata) {
LLVMSetGlobalConstant(global, true);
}
- if (e->Variable.thread_local_model != "") {
- LLVMSetThreadLocal(global, true);
- String m = e->Variable.thread_local_model;
- LLVMThreadLocalMode mode = LLVMGeneralDynamicTLSModel;
- if (m == "default") {
- mode = LLVMGeneralDynamicTLSModel;
- } else if (m == "localdynamic") {
- mode = LLVMLocalDynamicTLSModel;
- } else if (m == "initialexec") {
- mode = LLVMInitialExecTLSModel;
- } else if (m == "localexec") {
- mode = LLVMLocalExecTLSModel;
- } else {
- GB_PANIC("Unhandled thread local mode %.*s", LIT(m));
- }
- LLVMSetThreadLocalMode(global, mode);
- } else {
+ if (!lb_apply_thread_local_model(global, e->Variable.thread_local_model)) {
LLVMSetLinkage(global, LLVMInternalLinkage);
}
+ if (value.value != nullptr) {
+ if (is_type_any(e->type)) {
+ Type *var_type = default_type(value.type);
+
+ gbString var_name = gb_string_make(temporary_allocator(), "__$static_any::");
+ var_name = gb_string_append_length(var_name, mangled_name.text, mangled_name.len);
+
+ lbAddr var_global = lb_add_global_generated_with_name(p->module, var_type, value, make_string_c(var_name), nullptr);
+ LLVMValueRef var_global_ref = var_global.addr.value;
+
+ if (e->Variable.is_rodata) {
+ LLVMSetGlobalConstant(var_global_ref, true);
+ }
+
+ if (!lb_apply_thread_local_model(var_global_ref, e->Variable.thread_local_model)) {
+ LLVMSetLinkage(var_global_ref, LLVMInternalLinkage);
+ }
+
+ LLVMValueRef vals[2] = {
+ lb_emit_conv(p, var_global.addr, t_rawptr).value,
+ lb_typeid(p->module, var_type).value,
+ };
+ LLVMValueRef init = llvm_const_named_struct(p->module, e->type, vals, gb_count_of(vals));
+ LLVMSetInitializer(global, init);
+ } else {
+ LLVMSetInitializer(global, value.value);
+ }
+ }
lbValue global_val = {global, alloc_type_pointer(e->type)};
lb_add_entity(p->module, e, global_val);
@@ -2307,6 +2353,14 @@ gb_internal void lb_build_if_stmt(lbProcedure *p, Ast *node) {
else_ = lb_create_block(p, "if.else");
}
if (is->label != nullptr) {
+ if (p->debug_info != nullptr) {
+ lbBlock *label = lb_create_block(p, "if.label");
+ lb_emit_jump(p, label);
+ lb_start_block(p, label);
+
+ LLVMSetCurrentDebugLocation2(p->builder, lb_debug_location_from_ast(p, is->label));
+ lb_add_debug_label(p, is->label, label);
+ }
lbTargetList *tl = lb_push_target_list(p, is->label, done, nullptr, nullptr);
tl->is_block = true;
}
@@ -2399,12 +2453,19 @@ gb_internal void lb_build_for_stmt(lbProcedure *p, Ast *node) {
lb_push_target_list(p, fs->label, done, post, nullptr);
+ if (fs->label != nullptr && p->debug_info != nullptr) {
+ lbBlock *label = lb_create_block(p, "for.label");
+ lb_emit_jump(p, label);
+ lb_start_block(p, label);
+
+ LLVMSetCurrentDebugLocation2(p->builder, lb_debug_location_from_ast(p, fs->label));
+ lb_add_debug_label(p, fs->label, label);
+ }
if (fs->init != nullptr) {
- #if 1
lbBlock *init = lb_create_block(p, "for.init");
lb_emit_jump(p, init);
lb_start_block(p, init);
- #endif
+
lb_build_stmt(p, fs->init);
}
@@ -2420,7 +2481,6 @@ gb_internal void lb_build_for_stmt(lbProcedure *p, Ast *node) {
lb_start_block(p, body);
}
-
lb_build_stmt(p, fs->body);
lb_pop_target_list(p);
@@ -2758,9 +2818,21 @@ gb_internal void lb_build_stmt(lbProcedure *p, Ast *node) {
case_ast_node(bs, BlockStmt, node);
+ lbBlock *body = nullptr;
lbBlock *done = nullptr;
if (bs->label != nullptr) {
+ if (p->debug_info != nullptr) {
+ lbBlock *label = lb_create_block(p, "block.label");
+ lb_emit_jump(p, label);
+ lb_start_block(p, label);
+
+ LLVMSetCurrentDebugLocation2(p->builder, lb_debug_location_from_ast(p, bs->label));
+ lb_add_debug_label(p, bs->label, label);
+ }
+ body = lb_create_block(p, "block.body");
done = lb_create_block(p, "block.done");
+ lb_emit_jump(p, body);
+ lb_start_block(p, body);
lbTargetList *tl = lb_push_target_list(p, bs->label, done, nullptr, nullptr);
tl->is_block = true;
}
@@ -2981,6 +3053,18 @@ gb_internal void lb_emit_defer_stmts(lbProcedure *p, lbDeferExitKind kind, lbBlo
}
defer (p->branch_location_pos = prev_token_pos);
+ // TODO(lucas): In LLVM 21 use the 'use-after-scope' asan option which does this for us.
+ if (kind == lbDeferExit_Return) {
+ for_array(i, p->asan_stack_locals) {
+ lbValue local = p->asan_stack_locals[i];
+
+ auto args = array_make(temporary_allocator(), 2);
+ args[0] = lb_emit_conv(p, local, t_rawptr);
+ args[1] = lb_const_int(p->module, t_int, type_size_of(local.type->Pointer.elem));
+ lb_emit_runtime_call(p, "__asan_unpoison_memory_region", args);
+ }
+ }
+
isize count = p->defer_stmts.count;
isize i = count;
while (i --> 0) {
diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp
index ad4250f3c..4e514c3d1 100644
--- a/src/llvm_backend_type.cpp
+++ b/src/llvm_backend_type.cpp
@@ -1,4 +1,10 @@
+gb_internal void lb_set_odin_rtti_section(LLVMValueRef value) {
+ if (build_context.metrics.os != TargetOs_darwin) {
+ LLVMSetSection(value, ".odinti");
+ }
+}
+
gb_internal isize lb_type_info_index(CheckerInfo *info, TypeInfoPair pair, bool err_on_not_found=true) {
isize index = type_info_index(info, pair, err_on_not_found);
if (index >= 0) {
@@ -221,6 +227,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ
gb_snprintf(name, 63, "__$ti-%lld", cast(long long)index);
LLVMValueRef g = LLVMAddGlobal(m->mod, type, name);
lb_make_global_private_const(g);
+ lb_set_odin_rtti_section(g);
return g;
};
@@ -716,6 +723,8 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ
LLVMSetInitializer(value_array.value, value_init);
LLVMSetGlobalConstant(name_array.value, true);
LLVMSetGlobalConstant(value_array.value, true);
+ lb_set_odin_rtti_section(name_array.value);
+ lb_set_odin_rtti_section(value_array.value);
lbValue v_count = lb_const_int(m, t_int, fields.count);
@@ -1056,6 +1065,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ
LLVMValueRef giant_array = lb_global_type_info_data_ptr(m).value;
LLVMSetInitializer(giant_array, giant_const);
lb_make_global_private_const(giant_array);
+ lb_set_odin_rtti_section(giant_array);
}
diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp
index bfeebfcbe..521553147 100644
--- a/src/llvm_backend_utility.cpp
+++ b/src/llvm_backend_utility.cpp
@@ -2125,7 +2125,7 @@ gb_internal lbAddr lb_handle_objc_find_or_register_selector(lbProcedure *p, Stri
return addr;
}
-gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String const &name) {
+gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String const &name, Type *class_impl_type) {
lbModule *m = p->module;
lbAddr *found = string_map_get(&m->objc_classes, name);
if (found) {
@@ -2148,13 +2148,75 @@ gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String
} else {
LLVMSetLinkage(g.value, LLVMExternalLinkage);
}
- mpsc_enqueue(&m->gen->objc_classes, lbObjCGlobal{m, global_name, name, t_objc_Class});
+ mpsc_enqueue(&m->gen->objc_classes, lbObjCGlobal{m, global_name, name, t_objc_Class, class_impl_type});
lbAddr addr = lb_addr(g);
string_map_set(&m->objc_classes, name, addr);
return addr;
}
+gb_internal lbAddr lb_handle_objc_find_or_register_ivar(lbModule *m, Type *self_type) {
+
+ String name = self_type->Named.type_name->TypeName.objc_class_name;
+ GB_ASSERT(name != "");
+
+ lbAddr *found = string_map_get(&m->objc_ivars, name);
+ if (found) {
+ return *found;
+ }
+
+ lbModule *default_module = &m->gen->default_module;
+
+ gbString global_name = gb_string_make(permanent_allocator(), "__$objc_ivar::");
+ global_name = gb_string_append_length(global_name, name.text, name.len);
+
+ // Create a global variable to store offset of the ivar in an instance of an object
+ LLVMTypeRef t = lb_type(m, t_int);
+
+ lbValue g = {};
+ g.value = LLVMAddGlobal(m->mod, t, global_name);
+ g.type = t_int_ptr;
+
+ if (default_module == m) {
+ LLVMSetInitializer(g.value, LLVMConstInt(t, 0, true));
+ lb_add_member(m, make_string_c(global_name), g);
+ } else {
+ LLVMSetLinkage(g.value, LLVMExternalLinkage);
+ }
+
+ mpsc_enqueue(&m->gen->objc_ivars, lbObjCGlobal{m, global_name, name, t_int, self_type});
+
+ lbAddr addr = lb_addr(g);
+ string_map_set(&m->objc_ivars, name, addr);
+ return addr;
+}
+
+gb_internal lbValue lb_handle_objc_ivar_for_objc_object_pointer(lbProcedure *p, lbValue self) {
+ GB_ASSERT(self.type->kind == Type_Pointer && self.type->Pointer.elem->kind == Type_Named);
+
+ Type *self_type = self.type->Pointer.elem;
+
+ lbValue self_uptr = lb_emit_conv(p, self, t_uintptr);
+
+ lbValue ivar_offset = lb_addr_load(p, lb_handle_objc_find_or_register_ivar(p->module, self_type));
+ lbValue ivar_offset_uptr = lb_emit_conv(p, ivar_offset, t_uintptr);
+
+
+ lbValue ivar_uptr = lb_emit_arith(p, Token_Add, self_uptr, ivar_offset_uptr, t_uintptr);
+
+ Type *ivar_type = self_type->Named.type_name->TypeName.objc_ivar;
+ return lb_emit_conv(p, ivar_uptr, alloc_type_pointer(ivar_type));
+}
+
+gb_internal lbValue lb_handle_objc_ivar_get(lbProcedure *p, Ast *expr) {
+ ast_node(ce, CallExpr, expr);
+
+ GB_ASSERT(ce->args[0]->tav.type->kind == Type_Pointer);
+ lbValue self = lb_build_expr(p, ce->args[0]);
+
+ return lb_handle_objc_ivar_for_objc_object_pointer(p, self);
+}
+
gb_internal lbValue lb_handle_objc_find_selector(lbProcedure *p, Ast *expr) {
ast_node(ce, CallExpr, expr);
@@ -2188,7 +2250,7 @@ gb_internal lbValue lb_handle_objc_find_class(lbProcedure *p, Ast *expr) {
auto tav = ce->args[0]->tav;
GB_ASSERT(tav.value.kind == ExactValue_String);
String name = tav.value.value_string;
- return lb_addr_load(p, lb_handle_objc_find_or_register_class(p, name));
+ return lb_addr_load(p, lb_handle_objc_find_or_register_class(p, name, nullptr));
}
gb_internal lbValue lb_handle_objc_register_class(lbProcedure *p, Ast *expr) {
@@ -2198,7 +2260,7 @@ gb_internal lbValue lb_handle_objc_register_class(lbProcedure *p, Ast *expr) {
auto tav = ce->args[0]->tav;
GB_ASSERT(tav.value.kind == ExactValue_String);
String name = tav.value.value_string;
- lbAddr dst = lb_handle_objc_find_or_register_class(p, name);
+ lbAddr dst = lb_handle_objc_find_or_register_class(p, name, nullptr);
auto args = array_make(permanent_allocator(), 3);
args[0] = lb_const_nil(m, t_objc_Class);
@@ -2220,7 +2282,9 @@ gb_internal lbValue lb_handle_objc_id(lbProcedure *p, Ast *expr) {
GB_ASSERT(e->kind == Entity_TypeName);
String name = e->TypeName.objc_class_name;
- return lb_addr_load(p, lb_handle_objc_find_or_register_class(p, name));
+ Type *class_impl_type = e->TypeName.objc_is_implementation ? type : nullptr;
+
+ return lb_addr_load(p, lb_handle_objc_find_or_register_class(p, name, class_impl_type));
}
return lb_build_expr(p, expr);
@@ -2266,9 +2330,6 @@ gb_internal lbValue lb_handle_objc_send(lbProcedure *p, Ast *expr) {
return lb_emit_call(p, the_proc, args);
}
-
-
-
gb_internal LLVMAtomicOrdering llvm_atomic_ordering_from_odin(ExactValue const &value) {
GB_ASSERT(value.kind == ExactValue_Integer);
i64 v = exact_value_to_i64(value);
diff --git a/src/main.cpp b/src/main.cpp
index c19bbde22..90f2aad7a 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -277,10 +277,11 @@ gb_internal void usage(String argv0, String argv1 = {}) {
print_usage_line(1, "build Compiles directory of .odin files, as an executable.");
print_usage_line(1, " One must contain the program's entry point, all must be in the same package.");
print_usage_line(1, "run Same as 'build', but also then runs the newly compiled executable.");
- print_usage_line(1, "check Parses, and type checks a directory of .odin files.");
+ print_usage_line(1, "bundle Bundles a directory in a specific layout for that platform.");
+ print_usage_line(1, "check Parses and type checks a directory of .odin files.");
print_usage_line(1, "strip-semicolon Parses, type checks, and removes unneeded semicolons from the entire program.");
print_usage_line(1, "test Builds and runs procedures with the attribute @(test) in the initial package.");
- print_usage_line(1, "doc Generates documentation on a directory of .odin files.");
+ print_usage_line(1, "doc Generates documentation from a directory of .odin files.");
print_usage_line(1, "version Prints version.");
print_usage_line(1, "report Prints information useful to reporting a bug.");
print_usage_line(1, "root Prints the root path where Odin looks for the builtin collections.");
@@ -318,6 +319,7 @@ enum BuildFlagKind {
BuildFlag_NoBoundsCheck,
BuildFlag_NoTypeAssert,
BuildFlag_NoDynamicLiterals,
+ BuildFlag_DynamicLiterals,
BuildFlag_NoCRT,
BuildFlag_NoRPath,
BuildFlag_NoEntryPoint,
@@ -411,7 +413,7 @@ enum BuildFlagKind {
BuildFlag_AndroidKeystore,
BuildFlag_AndroidKeystoreAlias,
- BuildFlag_AndroidManifest,
+ BuildFlag_AndroidKeystorePassword,
BuildFlag_COUNT,
};
@@ -537,6 +539,7 @@ gb_internal bool parse_build_flags(Array args) {
add_flag(&build_flags, BuildFlag_NoTypeAssert, str_lit("no-type-assert"), BuildFlagParam_None, Command__does_check);
add_flag(&build_flags, BuildFlag_NoThreadLocal, str_lit("no-thread-local"), BuildFlagParam_None, Command__does_check);
add_flag(&build_flags, BuildFlag_NoDynamicLiterals, str_lit("no-dynamic-literals"), BuildFlagParam_None, Command__does_check);
+ add_flag(&build_flags, BuildFlag_DynamicLiterals, str_lit("dynamic-literals"), BuildFlagParam_None, Command__does_check);
add_flag(&build_flags, BuildFlag_NoCRT, str_lit("no-crt"), BuildFlagParam_None, Command__does_build);
add_flag(&build_flags, BuildFlag_NoRPath, str_lit("no-rpath"), BuildFlagParam_None, Command__does_build);
add_flag(&build_flags, BuildFlag_NoEntryPoint, str_lit("no-entry-point"), BuildFlagParam_None, Command__does_check &~ Command_test);
@@ -631,7 +634,7 @@ gb_internal bool parse_build_flags(Array args) {
add_flag(&build_flags, BuildFlag_AndroidKeystore, str_lit("android-keystore"), BuildFlagParam_String, Command_bundle_android);
add_flag(&build_flags, BuildFlag_AndroidKeystoreAlias, str_lit("android-keystore-alias"), BuildFlagParam_String, Command_bundle_android);
- add_flag(&build_flags, BuildFlag_AndroidManifest, str_lit("android-manifest"), BuildFlagParam_String, Command_bundle_android);
+ add_flag(&build_flags, BuildFlag_AndroidKeystorePassword, str_lit("android-keystore-password"), BuildFlagParam_String, Command_bundle_android);
Array flag_args = {};
@@ -1206,6 +1209,9 @@ gb_internal bool parse_build_flags(Array args) {
case BuildFlag_NoDynamicLiterals:
gb_printf_err("Warning: Use of -no-dynamic-literals is now redundant\n");
break;
+ case BuildFlag_DynamicLiterals:
+ build_context.dynamic_literals = true;
+ break;
case BuildFlag_NoCRT:
build_context.no_crt = true;
break;
@@ -1664,9 +1670,9 @@ gb_internal bool parse_build_flags(Array args) {
build_context.android_keystore_alias = value.value_string;
break;
- case BuildFlag_AndroidManifest:
+ case BuildFlag_AndroidKeystorePassword:
GB_ASSERT(value.kind == ExactValue_String);
- build_context.android_manifest = value.value_string;
+ build_context.android_keystore_password = value.value_string;
break;
}
}
@@ -2200,7 +2206,7 @@ gb_internal void remove_temp_files(lbGenerator *gen) {
return;
}
- TIME_SECTION("remove keep temp files");
+ TIME_SECTION("remove temp files");
for (String const &path : gen->output_temp_paths) {
gb_file_remove(cast(char const *)path.text);
@@ -2220,20 +2226,30 @@ gb_internal void remove_temp_files(lbGenerator *gen) {
}
-gb_internal void print_show_help(String const arg0, String command, String optional_flag = {}) {
+gb_internal int print_show_help(String const arg0, String command, String optional_flag = {}) {
+ bool help_resolved = false;
+ bool printed_usage_header = false;
+ bool printed_flags_header = false;
+
if (command == "help" && optional_flag.len != 0 && optional_flag[0] != '-') {
command = optional_flag;
optional_flag = {};
}
- print_usage_line(0, "%.*s is a tool for managing Odin source code.", LIT(arg0));
- print_usage_line(0, "Usage:");
- print_usage_line(1, "%.*s %.*s [arguments]", LIT(arg0), LIT(command));
- print_usage_line(0, "");
- defer (print_usage_line(0, ""));
-
+ auto const print_usage_header_once = [&help_resolved, &printed_usage_header, arg0, command]() {
+ if (printed_usage_header) {
+ return;
+ }
+ print_usage_line(0, "%.*s is a tool for managing Odin source code.", LIT(arg0));
+ print_usage_line(0, "Usage:");
+ print_usage_line(1, "%.*s %.*s [arguments]", LIT(arg0), LIT(command));
+ print_usage_line(0, "");
+ help_resolved = true;
+ printed_usage_header = true;
+ };
if (command == "build") {
+ print_usage_header_once();
print_usage_line(1, "build Compiles directory of .odin files as an executable.");
print_usage_line(2, "One must contain the program's entry point, all must be in the same package.");
print_usage_line(2, "Use `-file` to build a single file instead.");
@@ -2242,6 +2258,7 @@ gb_internal void print_show_help(String const arg0, String command, String optio
print_usage_line(3, "odin build Builds package in .");
print_usage_line(3, "odin build filename.odin -file Builds single-file package, must contain entry point.");
} else if (command == "run") {
+ print_usage_header_once();
print_usage_line(1, "run Same as 'build', but also then runs the newly compiled executable.");
print_usage_line(2, "Append an empty flag and then the args, '-- ', to specify args for the output.");
print_usage_line(2, "Examples:");
@@ -2249,28 +2266,40 @@ gb_internal void print_show_help(String const arg0, String command, String optio
print_usage_line(3, "odin run Builds and runs package in .");
print_usage_line(3, "odin run filename.odin -file Builds and runs single-file package, must contain entry point.");
} else if (command == "check") {
+ print_usage_header_once();
print_usage_line(1, "check Parses and type checks directory of .odin files.");
print_usage_line(2, "Examples:");
print_usage_line(3, "odin check . Type checks package in current directory.");
print_usage_line(3, "odin check Type checks package in .");
print_usage_line(3, "odin check filename.odin -file Type checks single-file package, must contain entry point.");
} else if (command == "test") {
+ print_usage_header_once();
print_usage_line(1, "test Builds and runs procedures with the attribute @(test) in the initial package.");
} else if (command == "doc") {
+ print_usage_header_once();
print_usage_line(1, "doc Generates documentation from a directory of .odin files.");
print_usage_line(2, "Examples:");
print_usage_line(3, "odin doc . Generates documentation on package in current directory.");
print_usage_line(3, "odin doc Generates documentation on package in .");
print_usage_line(3, "odin doc filename.odin -file Generates documentation on single-file package.");
} else if (command == "version") {
+ print_usage_header_once();
print_usage_line(1, "version Prints version.");
} else if (command == "strip-semicolon") {
+ print_usage_header_once();
print_usage_line(1, "strip-semicolon");
print_usage_line(2, "Parses and type checks .odin file(s) and then removes unneeded semicolons from the entire project.");
} else if (command == "bundle") {
+ print_usage_header_once();
print_usage_line(1, "bundle Bundles a directory in a specific layout for that platform");
print_usage_line(2, "Supported platforms:");
print_usage_line(3, "android");
+ } else if (command == "report") {
+ print_usage_header_once();
+ print_usage_line(1, "report Prints information useful to reporting a bug.");
+ } else if (command == "root") {
+ print_usage_header_once();
+ print_usage_line(1, "root Prints the root path where Odin looks for the builtin collections.");
}
bool doc = command == "doc";
@@ -2292,13 +2321,10 @@ gb_internal void print_show_help(String const arg0, String command, String optio
check = true;
}
- print_usage_line(0, "");
- print_usage_line(1, "Flags");
- print_usage_line(0, "");
- auto const print_flag = [&optional_flag](char const *flag) -> bool {
+ auto const print_flag = [&optional_flag, &help_resolved, &printed_flags_header, print_usage_header_once](char const *flag) -> bool {
if (optional_flag.len != 0) {
String f = make_string_c(flag);
isize i = string_index_byte(f, ':');
@@ -2309,6 +2335,14 @@ gb_internal void print_show_help(String const arg0, String command, String optio
return false;
}
}
+ print_usage_header_once();
+ if (!printed_flags_header) {
+ print_usage_line(0, "");
+ print_usage_line(1, "Flags");
+ print_usage_line(0, "");
+ printed_flags_header = true;
+ }
+ help_resolved = true;
print_usage_line(0, "");
print_usage_line(1, flag);
return true;
@@ -2330,20 +2364,20 @@ gb_internal void print_show_help(String const arg0, String command, String optio
if (print_flag("-build-mode:")) {
print_usage_line(2, "Sets the build mode.");
print_usage_line(2, "Available options:");
- print_usage_line(3, "-build-mode:exe Builds as an executable.");
- print_usage_line(3, "-build-mode:test Builds as an executable that executes tests.");
- print_usage_line(3, "-build-mode:dll Builds as a dynamically linked library.");
- print_usage_line(3, "-build-mode:shared Builds as a dynamically linked library.");
- print_usage_line(3, "-build-mode:dynamic Builds as a dynamically linked library.");
- print_usage_line(3, "-build-mode:lib Builds as a statically linked library.");
- print_usage_line(3, "-build-mode:static Builds as a statically linked library.");
- print_usage_line(3, "-build-mode:obj Builds as an object file.");
- print_usage_line(3, "-build-mode:object Builds as an object file.");
- print_usage_line(3, "-build-mode:assembly Builds as an assembly file.");
- print_usage_line(3, "-build-mode:assembler Builds as an assembly file.");
- print_usage_line(3, "-build-mode:asm Builds as an assembly file.");
- print_usage_line(3, "-build-mode:llvm-ir Builds as an LLVM IR file.");
- print_usage_line(3, "-build-mode:llvm Builds as an LLVM IR file.");
+ print_usage_line(3, "-build-mode:exe Builds as an executable.");
+ print_usage_line(3, "-build-mode:test Builds as an executable that executes tests.");
+ print_usage_line(3, "-build-mode:dll Builds as a dynamically linked library.");
+ print_usage_line(3, "-build-mode:shared Builds as a dynamically linked library.");
+ print_usage_line(3, "-build-mode:dynamic Builds as a dynamically linked library.");
+ print_usage_line(3, "-build-mode:lib Builds as a statically linked library.");
+ print_usage_line(3, "-build-mode:static Builds as a statically linked library.");
+ print_usage_line(3, "-build-mode:obj Builds as an object file.");
+ print_usage_line(3, "-build-mode:object Builds as an object file.");
+ print_usage_line(3, "-build-mode:assembly Builds as an assembly file.");
+ print_usage_line(3, "-build-mode:assembler Builds as an assembly file.");
+ print_usage_line(3, "-build-mode:asm Builds as an assembly file.");
+ print_usage_line(3, "-build-mode:llvm-ir Builds as an LLVM IR file.");
+ print_usage_line(3, "-build-mode:llvm Builds as an LLVM IR file.");
}
}
@@ -2352,16 +2386,16 @@ gb_internal void print_show_help(String const arg0, String command, String optio
print_usage_line(2, "Defines a library collection used for imports.");
print_usage_line(2, "Example: -collection:shared=dir/to/shared");
print_usage_line(2, "Usage in Code:");
- print_usage_line(3, "import \"shared:foo\"");
+ print_usage_line(3, "import \"shared:foo\"");
}
if (print_flag("-custom-attribute:")) {
print_usage_line(2, "Add a custom attribute which will be ignored if it is unknown.");
print_usage_line(2, "This can be used with metaprogramming tools.");
print_usage_line(2, "Examples:");
- print_usage_line(3, "-custom-attribute:my_tag");
- print_usage_line(3, "-custom-attribute:my_tag,the_other_thing");
- print_usage_line(3, "-custom-attribute:my_tag -custom-attribute:the_other_thing");
+ print_usage_line(3, "-custom-attribute:my_tag");
+ print_usage_line(3, "-custom-attribute:my_tag,the_other_thing");
+ print_usage_line(3, "-custom-attribute:my_tag -custom-attribute:the_other_thing");
}
}
@@ -2384,7 +2418,7 @@ gb_internal void print_show_help(String const arg0, String command, String optio
print_usage_line(2, "Defines a scalar boolean, integer or string as global constant.");
print_usage_line(2, "Example: -define:SPAM=123");
print_usage_line(2, "Usage in code:");
- print_usage_line(3, "#config(SPAM, default_value)");
+ print_usage_line(3, "#config(SPAM, default_value)");
}
}
@@ -2419,9 +2453,9 @@ gb_internal void print_show_help(String const arg0, String command, String optio
if (check) {
if (print_flag("-error-pos-style:")) {
print_usage_line(2, "Available options:");
- print_usage_line(3, "-error-pos-style:unix file/path:45:3:");
- print_usage_line(3, "-error-pos-style:odin file/path(45:3)");
- print_usage_line(3, "-error-pos-style:default (Defaults to 'odin'.)");
+ print_usage_line(3, "-error-pos-style:unix file/path:45:3:");
+ print_usage_line(3, "-error-pos-style:odin file/path(45:3)");
+ print_usage_line(3, "-error-pos-style:default (Defaults to 'odin'.)");
}
if (print_flag("-export-defineables:")) {
@@ -2432,8 +2466,8 @@ gb_internal void print_show_help(String const arg0, String command, String optio
if (print_flag("-export-dependencies:")) {
print_usage_line(2, "Exports dependencies to one of a few formats. Requires `-export-dependencies-file`.");
print_usage_line(2, "Available options:");
- print_usage_line(3, "-export-dependencies:make Exports in Makefile format");
- print_usage_line(3, "-export-dependencies:json Exports in JSON format");
+ print_usage_line(3, "-export-dependencies:make Exports in Makefile format");
+ print_usage_line(3, "-export-dependencies:json Exports in JSON format");
}
if (print_flag("-export-dependencies-file:")) {
@@ -2444,8 +2478,8 @@ gb_internal void print_show_help(String const arg0, String command, String optio
if (print_flag("-export-timings:")) {
print_usage_line(2, "Exports timings to one of a few formats. Requires `-show-timings` or `-show-more-timings`.");
print_usage_line(2, "Available options:");
- print_usage_line(3, "-export-timings:json Exports compile time stats to JSON.");
- print_usage_line(3, "-export-timings:csv Exports compile time stats to CSV.");
+ print_usage_line(3, "-export-timings:json Exports compile time stats to JSON.");
+ print_usage_line(3, "-export-timings:csv Exports compile time stats to CSV.");
}
if (print_flag("-export-timings-file:")) {
@@ -2535,9 +2569,9 @@ gb_internal void print_show_help(String const arg0, String command, String optio
if (print_flag("-microarch:")) {
print_usage_line(2, "Specifies the specific micro-architecture for the build in a string.");
print_usage_line(2, "Examples:");
- print_usage_line(3, "-microarch:sandybridge");
- print_usage_line(3, "-microarch:native");
- print_usage_line(3, "-microarch:\"?\" for a list");
+ print_usage_line(3, "-microarch:sandybridge");
+ print_usage_line(3, "-microarch:native");
+ print_usage_line(3, "-microarch:\"?\" for a list");
}
}
@@ -2552,7 +2586,7 @@ gb_internal void print_show_help(String const arg0, String command, String optio
if (print_flag("-minimum-os-version:")) {
print_usage_line(2, "Sets the minimum OS version targeted by the application.");
print_usage_line(2, "Default: -minimum-os-version:11.0.0");
- print_usage_line(2, "Only used when target is Darwin, if given, linking mismatched versions will emit a warning.");
+ print_usage_line(2, "Only used when target is Darwin or subtarget is Android, if given, linking mismatched versions will emit a warning.");
}
}
@@ -2594,10 +2628,10 @@ gb_internal void print_show_help(String const arg0, String command, String optio
if (print_flag("-o:")) {
print_usage_line(2, "Sets the optimization mode for compilation.");
print_usage_line(2, "Available options:");
- print_usage_line(3, "-o:none");
- print_usage_line(3, "-o:minimal");
- print_usage_line(3, "-o:size");
- print_usage_line(3, "-o:speed");
+ print_usage_line(3, "-o:none");
+ print_usage_line(3, "-o:minimal");
+ print_usage_line(3, "-o:size");
+ print_usage_line(3, "-o:speed");
if (LB_USE_NEW_PASS_SYSTEM) {
print_usage_line(3, "-o:aggressive (use this with caution)");
}
@@ -2648,10 +2682,10 @@ gb_internal void print_show_help(String const arg0, String command, String optio
if (print_flag("-reloc-mode:")) {
print_usage_line(2, "Specifies the reloc mode.");
print_usage_line(2, "Available options:");
- print_usage_line(3, "-reloc-mode:default");
- print_usage_line(3, "-reloc-mode:static");
- print_usage_line(3, "-reloc-mode:pic");
- print_usage_line(3, "-reloc-mode:dynamic-no-pic");
+ print_usage_line(3, "-reloc-mode:default");
+ print_usage_line(3, "-reloc-mode:static");
+ print_usage_line(3, "-reloc-mode:pic");
+ print_usage_line(3, "-reloc-mode:dynamic-no-pic");
}
#if defined(GB_SYSTEM_WINDOWS)
@@ -2666,9 +2700,9 @@ gb_internal void print_show_help(String const arg0, String command, String optio
if (print_flag("-sanitize:")) {
print_usage_line(2, "Enables sanitization analysis.");
print_usage_line(2, "Available options:");
- print_usage_line(3, "-sanitize:address");
- print_usage_line(3, "-sanitize:memory");
- print_usage_line(3, "-sanitize:thread");
+ print_usage_line(3, "-sanitize:address");
+ print_usage_line(3, "-sanitize:memory");
+ print_usage_line(3, "-sanitize:thread");
print_usage_line(2, "NOTE: This flag can be used multiple times.");
}
}
@@ -2729,17 +2763,32 @@ gb_internal void print_show_help(String const arg0, String command, String optio
print_usage_line(2, "[Windows only]");
print_usage_line(2, "Defines the subsystem for the application.");
print_usage_line(2, "Available options:");
- print_usage_line(3, "-subsystem:console");
- print_usage_line(3, "-subsystem:windows");
+ print_usage_line(3, "-subsystem:console");
+ print_usage_line(3, "-subsystem:windows");
}
#endif
+ }
+ if (build) {
+ if (print_flag("-subtarget:")) {
+ print_usage_line(2, "[Darwin and Linux only]");
+ print_usage_line(2, "Available subtargets:");
+ String prefix = str_lit("-subtarget:");
+ for (u32 i = 1; i < Subtarget_COUNT; i++) {
+ String name = subtarget_strings[i];
+ String help_string = concatenate_strings(temporary_allocator(), prefix, name);
+ print_usage_line(3, (const char *)help_string.text);
+ }
+ }
+ }
+
+ if (run_or_build) {
if (print_flag("-target-features:")) {
print_usage_line(2, "Specifies CPU features to enable on top of the enabled features implied by -microarch.");
print_usage_line(2, "Examples:");
- print_usage_line(3, "-target-features:atomics");
- print_usage_line(3, "-target-features:\"sse2,aes\"");
- print_usage_line(3, "-target-features:\"?\" for a list");
+ print_usage_line(3, "-target-features:atomics");
+ print_usage_line(3, "-target-features:\"sse2,aes\"");
+ print_usage_line(3, "-target-features:\"?\" for a list");
}
}
@@ -2776,11 +2825,11 @@ gb_internal void print_show_help(String const arg0, String command, String optio
if (print_flag("-vet")) {
print_usage_line(2, "Does extra checks on the code.");
print_usage_line(2, "Extra checks include:");
- print_usage_line(3, "-vet-unused");
- print_usage_line(3, "-vet-unused-variables");
- print_usage_line(3, "-vet-unused-imports");
- print_usage_line(3, "-vet-shadowing");
- print_usage_line(3, "-vet-using-stmt");
+ print_usage_line(3, "-vet-unused");
+ print_usage_line(3, "-vet-unused-variables");
+ print_usage_line(3, "-vet-unused-imports");
+ print_usage_line(3, "-vet-shadowing");
+ print_usage_line(3, "-vet-using-stmt");
}
if (print_flag("-vet-cast")) {
@@ -2847,6 +2896,40 @@ gb_internal void print_show_help(String const arg0, String command, String optio
print_usage_line(2, "Treats warning messages as error messages.");
}
}
+
+ if (bundle) {
+ print_usage_line(0, "");
+ print_usage_line(1, "Android-specific flags");
+ print_usage_line(0, "");
+ if (print_flag("-android-keystore:")) {
+ print_usage_line(2, "Specifies the keystore file to use to sign the apk.");
+ }
+
+ if (print_flag("-android-keystore-alias:")) {
+ print_usage_line(2, "Specifies the key alias to use when signing the apk");
+ print_usage_line(2, "Can be omitted if the keystore only contains one key");
+ }
+
+ if (print_flag("-android-keystore-password:")) {
+ print_usage_line(2, "Sets the password to use to unlock the keystore");
+ print_usage_line(2, "If this is omitted, the terminal will prompt you to provide it.");
+ }
+ }
+
+ if (!help_resolved) {
+ usage(arg0);
+ print_usage_line(0, "");
+ if (command == "help") {
+ print_usage_line(0, "'%.*s' is not a recognized flag.", LIT(optional_flag));
+ } else {
+ print_usage_line(0, "'%.*s' is not a recognized command.", LIT(command));
+ }
+ return 1;
+ }
+
+ print_usage_line(0, "");
+
+ return 0;
}
gb_internal void print_show_unused(Checker *c) {
@@ -3219,6 +3302,16 @@ int main(int arg_count, char const **arg_ptr) {
String run_args_string = {};
isize last_non_run_arg = args.count;
+ for_array(i, args) {
+ if (args[i] == "--") {
+ break;
+ }
+ if (args[i] == "-help" || args[i] == "--help") {
+ build_context.show_help = true;
+ return print_show_help(args[0], command);
+ }
+ }
+
bool run_output = false;
if (command == "run" || command == "test") {
if (args.count < 3) {
@@ -3312,6 +3405,10 @@ int main(int arg_count, char const **arg_ptr) {
return 1;
#endif
} else if (command == "version") {
+ if (args.count != 2) {
+ usage(args[0]);
+ return 1;
+ }
build_context.command_kind = Command_version;
gb_printf("%.*s version %.*s", LIT(args[0]), LIT(ODIN_VERSION));
@@ -3326,6 +3423,10 @@ int main(int arg_count, char const **arg_ptr) {
gb_printf("\n");
return 0;
} else if (command == "report") {
+ if (args.count != 2) {
+ usage(args[0]);
+ return 1;
+ }
build_context.command_kind = Command_bug_report;
print_bug_report_help();
return 0;
@@ -3334,8 +3435,7 @@ int main(int arg_count, char const **arg_ptr) {
usage(args[0]);
return 1;
} else {
- print_show_help(args[0], args[1], args[2]);
- return 0;
+ return print_show_help(args[0], args[1], args[2]);
}
} else if (command == "bundle") {
if (args.count < 4) {
@@ -3351,6 +3451,10 @@ int main(int arg_count, char const **arg_ptr) {
}
init_filename = args[3];
} else if (command == "root") {
+ if (args.count != 2) {
+ usage(args[0]);
+ return 1;
+ }
gb_printf("%.*s", LIT(odin_root_dir()));
return 0;
} else if (command == "clear-cache") {
@@ -3366,11 +3470,6 @@ int main(int arg_count, char const **arg_ptr) {
init_filename = copy_string(permanent_allocator(), init_filename);
- if (init_filename == "-help" ||
- init_filename == "--help") {
- build_context.show_help = true;
- }
-
if (init_filename.len > 0 && !build_context.show_help) {
// The command must be build, run, test, check, or another that takes a directory or filename.
if (!path_is_directory(init_filename)) {
@@ -3421,8 +3520,7 @@ int main(int arg_count, char const **arg_ptr) {
}
if (build_context.show_help) {
- print_show_help(args[0], command);
- return 0;
+ return print_show_help(args[0], command);
}
if (command == "bundle") {
diff --git a/src/name_canonicalization.cpp b/src/name_canonicalization.cpp
index a80dc1996..0372f5039 100644
--- a/src/name_canonicalization.cpp
+++ b/src/name_canonicalization.cpp
@@ -1,3 +1,5 @@
+gb_internal bool is_in_doc_writer(void);
+
gb_internal GB_COMPARE_PROC(type_info_pair_cmp) {
TypeInfoPair *x = cast(TypeInfoPair *)a;
TypeInfoPair *y = cast(TypeInfoPair *)b;
@@ -284,6 +286,23 @@ gb_internal void write_canonical_params(TypeWriter *w, Type *params) {
} else {
write_type_to_canonical_string(w, v->type);
}
+ if (is_in_doc_writer()) {
+ // NOTE(bill): This just exists to make sure the entities default values exist when
+ // writing to the odin doc format
+ Ast *expr = v->Variable.init_expr;
+ if (expr == nullptr) {
+ expr = v->Variable.param_value.original_ast_expr;
+ }
+ if (expr != nullptr) {
+ type_writer_appendc(w, "=");
+ gbString s = write_expr_to_string( // Minor leak
+ gb_string_make(temporary_allocator(), ""),
+ expr,
+ build_context.cmd_doc_flags & CmdDocFlag_Short
+ );
+ type_writer_append(w, s, gb_string_length(s));
+ }
+ }
break;
case Entity_TypeName:
type_writer_appendc(w, CANONICAL_PARAM_TYPEID);
@@ -520,7 +539,6 @@ write_base_name:
return;
}
-gb_internal bool is_in_doc_writer(void);
// NOTE(bill): This exists so that we deterministically hash a type by serializing it to a canonical string
gb_internal void write_type_to_canonical_string(TypeWriter *w, Type *type) {
@@ -631,6 +649,10 @@ gb_internal void write_type_to_canonical_string(TypeWriter *w, Type *type) {
case Type_Union:
type_writer_appendc(w, "union");
+ if (is_in_doc_writer() && type->Union.polymorphic_params) {
+ write_canonical_params(w, type->Union.polymorphic_params);
+ }
+
switch (type->Union.kind) {
case UnionType_no_nil: type_writer_appendc(w, "#no_nil"); break;
case UnionType_shared_nil: type_writer_appendc(w, "#shared_nil"); break;
@@ -658,6 +680,11 @@ gb_internal void write_type_to_canonical_string(TypeWriter *w, Type *type) {
}
type_writer_appendc(w, "struct");
+
+ if (is_in_doc_writer() && type->Struct.polymorphic_params) {
+ write_canonical_params(w, type->Struct.polymorphic_params);
+ }
+
if (type->Struct.is_packed) type_writer_appendc(w, "#packed");
if (type->Struct.is_raw_union) type_writer_appendc(w, "#raw_union");
if (type->Struct.is_no_copy) type_writer_appendc(w, "#no_copy");
@@ -724,9 +751,17 @@ gb_internal void write_type_to_canonical_string(TypeWriter *w, Type *type) {
if (is_in_doc_writer()) {
type_writer_appendc(w, "$");
type_writer_append(w, type->Generic.name.text, type->Generic.name.len);
- type_writer_append_fmt(w, "%lld", cast(long long)type->Generic.id);
+ type_writer_append_fmt(w, "-%lld", cast(long long)type->Generic.id);
+ if (type->Generic.specialized) {
+ type_writer_appendc(w, "/");
+ write_type_to_canonical_string(w, type->Generic.specialized);
+ }
+ } else if (type->Generic.specialized) {
+ // If we have a specialized type, use that instead of panicking
+ write_type_to_canonical_string(w, type->Generic.specialized);
} else {
- GB_PANIC("Type_Generic should never be hit");
+ // For unspecialized generics, use a generic placeholder string
+ type_writer_appendc(w, "rawptr");
}
return;
diff --git a/src/parser.cpp b/src/parser.cpp
index df0ad87f8..7bb480cbd 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -6195,7 +6195,7 @@ gb_internal String build_tag_get_token(String s, String *out) {
isize width = utf8_decode(&s[n], s.len-n, &rune);
if (n == 0 && rune == '!') {
- } else if (!rune_is_letter(rune) && !rune_is_digit(rune)) {
+ } else if (!rune_is_letter(rune) && !rune_is_digit(rune) && rune != ':') {
isize k = gb_max(gb_max(n, width), 1);
*out = substring(s, k, s.len);
return substring(s, 0, k);
@@ -6247,7 +6247,9 @@ gb_internal bool parse_build_tag(Token token_for_pos, String s) {
continue;
}
- TargetOsKind os = get_target_os_from_string(p);
+ Subtarget subtarget = Subtarget_Default;
+
+ TargetOsKind os = get_target_os_from_string(p, &subtarget);
TargetArchKind arch = get_target_arch_from_string(p);
num_tokens += 1;
@@ -6261,11 +6263,13 @@ gb_internal bool parse_build_tag(Token token_for_pos, String s) {
if (os != TargetOs_Invalid) {
this_kind_os_seen = true;
+ bool same_subtarget = (subtarget == Subtarget_Default) || (subtarget == selected_subtarget);
+
GB_ASSERT(arch == TargetArch_Invalid);
if (is_notted) {
- this_kind_correct = this_kind_correct && (os != build_context.metrics.os);
+ this_kind_correct = this_kind_correct && (os != build_context.metrics.os || !same_subtarget);
} else {
- this_kind_correct = this_kind_correct && (os == build_context.metrics.os);
+ this_kind_correct = this_kind_correct && (os == build_context.metrics.os && same_subtarget);
}
} else if (arch != TargetArch_Invalid) {
this_kind_arch_seen = true;
diff --git a/src/path.cpp b/src/path.cpp
index 12f8d3d4e..d5e982088 100644
--- a/src/path.cpp
+++ b/src/path.cpp
@@ -81,7 +81,7 @@ String get_working_directory(gbAllocator allocator) {
auto buf = array_make(temporary_allocator());
size_t size = PATH_MAX;
- char const *cwd;
+ char const *cwd = nullptr;
for (; cwd == nullptr; size *= 2) {
array_resize(&buf, size);
diff --git a/src/ptr_map.cpp b/src/ptr_map.cpp
index 1c157c386..61f703cf1 100644
--- a/src/ptr_map.cpp
+++ b/src/ptr_map.cpp
@@ -15,7 +15,7 @@ static void *const MAP_TOMBSTONE = (void *)~(uintptr)0;
template
struct PtrMapEntry {
static_assert(sizeof(K) == sizeof(void *), "Key size must be pointer size");
-
+
K key;
V value;
};
@@ -374,7 +374,7 @@ struct PtrMapIterator {
}
bool operator==(PtrMapIterator const &other) const noexcept {
- return this->map == other->map && this->index == other->index;
+ return this->map == other.map && this->index == other.index;
}
operator PtrMapEntry *() const {
@@ -858,4 +858,4 @@ gb_internal OrderedInsertPtrMapEntry *end(OrderedInsertPtrMap &m) {
template
gb_internal OrderedInsertPtrMapEntry const *end(OrderedInsertPtrMap const &m) {
return m.entries + m.count;
-}
\ No newline at end of file
+}
diff --git a/src/string.cpp b/src/string.cpp
index 88b679540..ae8d066b1 100644
--- a/src/string.cpp
+++ b/src/string.cpp
@@ -336,6 +336,83 @@ gb_internal Array split_lines_from_array(Array const &array, gbAlloc
return lines;
}
+enum : u32 { PRIME_RABIN_KARP = 16777619u };
+
+gb_internal u32 hash_str_rabin_karp(String const &s, u32 *pow_) {
+ u32 hash = 0;
+ u32 pow = 1;
+ for (isize i = 0; i < s.len; i++) {
+ hash = hash*PRIME_RABIN_KARP + cast(u32)s.text[i];
+ }
+ u32 sq = PRIME_RABIN_KARP;
+ for (isize i = s.len; i > 0; i >>= 1) {
+ if ((i & 1) != 0) {
+ pow *= sq;
+ }
+ sq *= sq;
+ }
+ if (pow_) *pow_ = pow;
+ return hash;
+
+}
+
+
+gb_internal isize string_index(String const &s, String const &substr) {
+ isize n = substr.len;
+ if (n == 0) {
+ return 0;
+ } else if (n == 1) {
+ return string_index_byte(s, substr[0]);
+ } else if (n == s.len) {
+ if (s == substr) {
+ return 0;
+ }
+ return -1;
+ } else if (n > s.len) {
+ return -1;
+ }
+ u32 pow = 1;
+ u32 hash = hash_str_rabin_karp(s, &pow);
+ u32 h = 0;
+ for (isize i = 0; i < n; i++) {
+ h = h*PRIME_RABIN_KARP + cast(u32)s.text[i];
+ }
+ if (h == hash && substring(s, 0, n) == substr) {
+ return 0;
+ }
+ for (isize i = n; i < s.len; /**/) {
+ h *= PRIME_RABIN_KARP;
+ h += cast(u32)s.text[i];
+ h -= pow * u32(s.text[i-n]);
+ i += 1;
+ if (h == hash && substring(s, i-n, i) == substr) {
+ return i - n;
+ }
+ }
+ return -1;
+}
+
+
+struct StringPartition {
+ String head;
+ String match;
+ String tail;
+};
+
+gb_internal StringPartition string_partition(String const &str, String const &sep) {
+ StringPartition res = {};
+ isize i = string_index(str, sep);
+ if (i < 0) {
+ res.head = str;
+ return res;
+ }
+
+ res.head = substring(str, 0, i);
+ res.match = substring(str, i, i+sep.len);
+ res.tail = substring(str, i+sep.len, str.len);
+ return res;
+}
+
gb_internal bool string_contains_char(String const &s, u8 c) {
isize i;
for (i = 0; i < s.len; i++) {
diff --git a/src/types.cpp b/src/types.cpp
index 43fe625f2..ce921796d 100644
--- a/src/types.cpp
+++ b/src/types.cpp
@@ -111,7 +111,7 @@ enum BasicFlag {
BasicFlag_Ordered = BasicFlag_Integer | BasicFlag_Float | BasicFlag_String | BasicFlag_Pointer | BasicFlag_Rune,
BasicFlag_OrderedNumeric = BasicFlag_Integer | BasicFlag_Float | BasicFlag_Rune,
BasicFlag_ConstantType = BasicFlag_Boolean | BasicFlag_Numeric | BasicFlag_String | BasicFlag_Pointer | BasicFlag_Rune,
- BasicFlag_SimpleCompare = BasicFlag_Boolean | BasicFlag_Numeric | BasicFlag_Pointer | BasicFlag_Rune,
+ BasicFlag_SimpleCompare = BasicFlag_Boolean | BasicFlag_Integer | BasicFlag_Pointer | BasicFlag_Rune,
};
struct BasicType {
@@ -729,10 +729,12 @@ gb_global Type *t_map_set_proc = nullptr;
gb_global Type *t_objc_object = nullptr;
gb_global Type *t_objc_selector = nullptr;
gb_global Type *t_objc_class = nullptr;
+gb_global Type *t_objc_ivar = nullptr;
gb_global Type *t_objc_id = nullptr;
gb_global Type *t_objc_SEL = nullptr;
gb_global Type *t_objc_Class = nullptr;
+gb_global Type *t_objc_Ivar = nullptr;
enum OdinAtomicMemoryOrder : i32 {
OdinAtomicMemoryOrder_relaxed = 0, // unordered
@@ -872,6 +874,29 @@ gb_internal Type *base_type(Type *t) {
return t;
}
+gb_internal Type *base_named_type(Type *t) {
+ if (t->kind != Type_Named) {
+ return t_invalid;
+ }
+
+ Type *prev_named = t;
+ t = t->Named.base;
+ for (;;) {
+ if (t == nullptr) {
+ break;
+ }
+ if (t->kind != Type_Named) {
+ break;
+ }
+ if (t == t->Named.base) {
+ return t_invalid;
+ }
+ prev_named = t;
+ t = t->Named.base;
+ }
+ return prev_named;
+}
+
gb_internal Type *base_enum_type(Type *t) {
Type *bt = base_type(t);
if (bt != nullptr &&
@@ -2932,6 +2957,10 @@ gb_internal Type *default_type(Type *type) {
case Basic_UntypedString: return t_string;
case Basic_UntypedRune: return t_rune;
}
+ } else if (type->kind == Type_Generic) {
+ if (type->Generic.specialized) {
+ return default_type(type->Generic.specialized);
+ }
}
return type;
}
@@ -3327,6 +3356,15 @@ gb_internal Selection lookup_field_with_selection(Type *type_, String field_name
}
}
}
+
+ Type *objc_ivar_type = e->TypeName.objc_ivar;
+ if (objc_ivar_type != nullptr) {
+ sel = lookup_field_with_selection(objc_ivar_type, field_name, false, sel, allow_blank_ident);
+ if (sel.entity != nullptr) {
+ sel.pseudo_field = true;
+ return sel;
+ }
+ }
}
if (is_type_polymorphic(type)) {
@@ -4108,10 +4146,10 @@ gb_internal i64 type_size_of_internal(Type *t, TypePath *path) {
}
i64 max = 0;
- i64 field_size = 0;
for_array(i, t->Union.variants) {
Type *variant_type = t->Union.variants[i];
+
i64 size = type_size_of_internal(variant_type, path);
if (max < size) {
max = size;
@@ -4130,7 +4168,7 @@ gb_internal i64 type_size_of_internal(Type *t, TypePath *path) {
size = align_formula(max, tag_size);
// NOTE(bill): Calculate the padding between the common fields and the tag
t->Union.tag_size = cast(i16)tag_size;
- t->Union.variant_block_size = size - field_size;
+ t->Union.variant_block_size = size;
size += tag_size;
}
@@ -4542,7 +4580,7 @@ gb_internal Type *alloc_type_proc_from_types(Type **param_types, unsigned param_
// return type;
// }
-gb_internal gbString write_type_to_string(gbString str, Type *type, bool shorthand=false) {
+gb_internal gbString write_type_to_string(gbString str, Type *type, bool shorthand=false, bool allow_polymorphic=false) {
if (type == nullptr) {
return gb_string_appendc(str, "");
}
@@ -4567,24 +4605,24 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha
str = gb_string_append_length(str, name.text, name.len);
if (type->Generic.specialized != nullptr) {
str = gb_string_append_rune(str, '/');
- str = write_type_to_string(str, type->Generic.specialized);
+ str = write_type_to_string(str, type->Generic.specialized, shorthand, allow_polymorphic);
}
}
break;
case Type_Pointer:
str = gb_string_append_rune(str, '^');
- str = write_type_to_string(str, type->Pointer.elem);
+ str = write_type_to_string(str, type->Pointer.elem, shorthand, allow_polymorphic);
break;
case Type_SoaPointer:
str = gb_string_appendc(str, "#soa ^");
- str = write_type_to_string(str, type->SoaPointer.elem);
+ str = write_type_to_string(str, type->SoaPointer.elem, shorthand, allow_polymorphic);
break;
case Type_MultiPointer:
str = gb_string_appendc(str, "[^]");
- str = write_type_to_string(str, type->Pointer.elem);
+ str = write_type_to_string(str, type->Pointer.elem, shorthand, allow_polymorphic);
break;
case Type_EnumeratedArray:
@@ -4592,31 +4630,31 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha
str = gb_string_appendc(str, "#sparse");
}
str = gb_string_append_rune(str, '[');
- str = write_type_to_string(str, type->EnumeratedArray.index);
+ str = write_type_to_string(str, type->EnumeratedArray.index, shorthand, allow_polymorphic);
str = gb_string_append_rune(str, ']');
- str = write_type_to_string(str, type->EnumeratedArray.elem);
+ str = write_type_to_string(str, type->EnumeratedArray.elem, shorthand, allow_polymorphic);
break;
case Type_Array:
str = gb_string_appendc(str, gb_bprintf("[%lld]", cast(long long)type->Array.count));
- str = write_type_to_string(str, type->Array.elem);
+ str = write_type_to_string(str, type->Array.elem, shorthand, allow_polymorphic);
break;
case Type_Slice:
str = gb_string_appendc(str, "[]");
- str = write_type_to_string(str, type->Array.elem);
+ str = write_type_to_string(str, type->Array.elem, shorthand, allow_polymorphic);
break;
case Type_DynamicArray:
str = gb_string_appendc(str, "[dynamic]");
- str = write_type_to_string(str, type->DynamicArray.elem);
+ str = write_type_to_string(str, type->DynamicArray.elem, shorthand, allow_polymorphic);
break;
case Type_Enum:
str = gb_string_appendc(str, "enum");
if (type->Enum.base_type != nullptr) {
str = gb_string_appendc(str, " ");
- str = write_type_to_string(str, type->Enum.base_type);
+ str = write_type_to_string(str, type->Enum.base_type, shorthand, allow_polymorphic);
}
str = gb_string_appendc(str, " {");
for_array(i, type->Enum.fields) {
@@ -4633,6 +4671,13 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha
case Type_Union:
str = gb_string_appendc(str, "union");
+
+ if (allow_polymorphic && type->Struct.polymorphic_params) {
+ str = gb_string_appendc(str, "(");
+ str = write_type_to_string(str, type->Struct.polymorphic_params, shorthand, allow_polymorphic);
+ str = gb_string_appendc(str, ")");
+ }
+
switch (type->Union.kind) {
case UnionType_no_nil: str = gb_string_appendc(str, " #no_nil"); break;
case UnionType_shared_nil: str = gb_string_appendc(str, " #shared_nil"); break;
@@ -4642,7 +4687,7 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha
for_array(i, type->Union.variants) {
Type *t = type->Union.variants[i];
if (i > 0) str = gb_string_appendc(str, ", ");
- str = write_type_to_string(str, t);
+ str = write_type_to_string(str, t, shorthand, allow_polymorphic);
}
str = gb_string_append_rune(str, '}');
break;
@@ -4655,17 +4700,24 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha
case StructSoa_Dynamic: str = gb_string_appendc(str, "#soa[dynamic]"); break;
default: GB_PANIC("Unknown StructSoaKind"); break;
}
- str = write_type_to_string(str, type->Struct.soa_elem);
+ str = write_type_to_string(str, type->Struct.soa_elem, shorthand, allow_polymorphic);
break;
}
str = gb_string_appendc(str, "struct");
+
+ if (allow_polymorphic && type->Struct.polymorphic_params) {
+ str = gb_string_appendc(str, "(");
+ str = write_type_to_string(str, type->Struct.polymorphic_params, shorthand, allow_polymorphic);
+ str = gb_string_appendc(str, ")");
+ }
+
if (type->Struct.is_packed) str = gb_string_appendc(str, " #packed");
if (type->Struct.is_raw_union) str = gb_string_appendc(str, " #raw_union");
if (type->Struct.is_no_copy) str = gb_string_appendc(str, " #no_copy");
if (type->Struct.custom_align != 0) str = gb_string_append_fmt(str, " #align %d", cast(int)type->Struct.custom_align);
- str = gb_string_appendc(str, " {");
+ str = gb_string_appendc(str, " {");
if (shorthand && type->Struct.fields.count > 16) {
str = gb_string_append_fmt(str, "%lld fields...", cast(long long)type->Struct.fields.count);
@@ -4678,7 +4730,7 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha
}
str = gb_string_append_length(str, f->token.string.text, f->token.string.len);
str = gb_string_appendc(str, ": ");
- str = write_type_to_string(str, f->type);
+ str = write_type_to_string(str, f->type, shorthand, allow_polymorphic);
}
}
str = gb_string_append_rune(str, '}');
@@ -4686,9 +4738,9 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha
case Type_Map: {
str = gb_string_appendc(str, "map[");
- str = write_type_to_string(str, type->Map.key);
+ str = write_type_to_string(str, type->Map.key, shorthand, allow_polymorphic);
str = gb_string_append_rune(str, ']');
- str = write_type_to_string(str, type->Map.value);
+ str = write_type_to_string(str, type->Map.value, shorthand, allow_polymorphic);
} break;
case Type_Named:
@@ -4718,9 +4770,11 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha
str = gb_string_append_length(str, name.text, name.len);
if (!is_type_untyped(var->type)) {
str = gb_string_appendc(str, ": ");
- str = write_type_to_string(str, var->type);
- str = gb_string_appendc(str, " = ");
- str = write_exact_value_to_string(str, var->Constant.value);
+ str = write_type_to_string(str, var->type, shorthand, allow_polymorphic);
+ if (var->Constant.value.kind) {
+ str = gb_string_appendc(str, " = ");
+ str = write_exact_value_to_string(str, var->Constant.value);
+ }
} else {
str = gb_string_appendc(str, " := ");
str = write_exact_value_to_string(str, var->Constant.value);
@@ -4736,20 +4790,31 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha
Type *slice = base_type(var->type);
str = gb_string_appendc(str, "..");
GB_ASSERT(var->type->kind == Type_Slice);
- str = write_type_to_string(str, slice->Slice.elem);
+ str = write_type_to_string(str, slice->Slice.elem, shorthand, allow_polymorphic);
} else {
- str = write_type_to_string(str, var->type);
+ str = write_type_to_string(str, var->type, shorthand, allow_polymorphic);
}
} else {
GB_ASSERT(var->kind == Entity_TypeName);
if (var->type->kind == Type_Generic) {
- str = gb_string_appendc(str, "typeid/");
- str = write_type_to_string(str, var->type);
+ if (var->token.string.len != 0) {
+ String name = var->token.string;
+ str = gb_string_appendc(str, "$");
+ str = gb_string_append_length(str, name.text, name.len);
+ str = gb_string_appendc(str, ": typeid");
+ if (var->type->Generic.specialized) {
+ str = gb_string_appendc(str, "/");
+ str = write_type_to_string(str, var->type->Generic.specialized, shorthand, allow_polymorphic);
+ }
+ } else {
+ str = gb_string_appendc(str, "typeid/");
+ str = write_type_to_string(str, var->type, shorthand, allow_polymorphic);
+ }
} else {
str = gb_string_appendc(str, "$");
str = gb_string_append_length(str, name.text, name.len);
str = gb_string_appendc(str, "=");
- str = write_type_to_string(str, var->type);
+ str = write_type_to_string(str, var->type, shorthand, allow_polymorphic);
}
}
}
@@ -4795,7 +4860,7 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha
}
str = gb_string_appendc(str, "(");
if (type->Proc.params) {
- str = write_type_to_string(str, type->Proc.params);
+ str = write_type_to_string(str, type->Proc.params, shorthand, allow_polymorphic);
}
str = gb_string_appendc(str, ")");
if (type->Proc.results) {
@@ -4803,7 +4868,7 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha
if (type->Proc.results->Tuple.variables.count > 1) {
str = gb_string_appendc(str, "(");
}
- str = write_type_to_string(str, type->Proc.results);
+ str = write_type_to_string(str, type->Proc.results, shorthand, allow_polymorphic);
if (type->Proc.results->Tuple.variables.count > 1) {
str = gb_string_appendc(str, ")");
}
@@ -4815,7 +4880,7 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha
if (type->BitSet.elem == nullptr) {
str = gb_string_appendc(str, "");
} else if (is_type_enum(type->BitSet.elem)) {
- str = write_type_to_string(str, type->BitSet.elem);
+ str = write_type_to_string(str, type->BitSet.elem, shorthand, allow_polymorphic);
} else {
str = gb_string_append_fmt(str, "%lld", type->BitSet.lower);
str = gb_string_append_fmt(str, "..=");
@@ -4823,14 +4888,14 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha
}
if (type->BitSet.underlying != nullptr) {
str = gb_string_appendc(str, "; ");
- str = write_type_to_string(str, type->BitSet.underlying);
+ str = write_type_to_string(str, type->BitSet.underlying, shorthand, allow_polymorphic);
}
str = gb_string_appendc(str, "]");
break;
case Type_SimdVector:
str = gb_string_append_fmt(str, "#simd[%d]", cast(int)type->SimdVector.count);
- str = write_type_to_string(str, type->SimdVector.elem);
+ str = write_type_to_string(str, type->SimdVector.elem, shorthand, allow_polymorphic);
break;
case Type_Matrix:
@@ -4838,12 +4903,12 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha
str = gb_string_appendc(str, "#row_major ");
}
str = gb_string_appendc(str, gb_bprintf("matrix[%d, %d]", cast(int)type->Matrix.row_count, cast(int)type->Matrix.column_count));
- str = write_type_to_string(str, type->Matrix.elem);
+ str = write_type_to_string(str, type->Matrix.elem, shorthand, allow_polymorphic);
break;
case Type_BitField:
str = gb_string_appendc(str, "bit_field ");
- str = write_type_to_string(str, type->BitField.backing_type);
+ str = write_type_to_string(str, type->BitField.backing_type, shorthand, allow_polymorphic);
str = gb_string_appendc(str, " {");
for (isize i = 0; i < type->BitField.fields.count; i++) {
Entity *f = type->BitField.fields[i];
@@ -4852,7 +4917,7 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha
}
str = gb_string_append_length(str, f->token.string.text, f->token.string.len);
str = gb_string_appendc(str, ": ");
- str = write_type_to_string(str, f->type);
+ str = write_type_to_string(str, f->type, shorthand, allow_polymorphic);
str = gb_string_append_fmt(str, " | %u", type->BitField.bit_sizes[i]);
}
str = gb_string_appendc(str, " }");
@@ -4870,6 +4935,11 @@ gb_internal gbString type_to_string(Type *type, bool shorthand) {
return write_type_to_string(gb_string_make(heap_allocator(), ""), type, shorthand);
}
+gb_internal gbString type_to_string_polymorphic(Type *type) {
+ return write_type_to_string(gb_string_make(heap_allocator(), ""), type, false, true);
+}
+
+
gb_internal gbString type_to_string_shorthand(Type *type) {
return type_to_string(type, true);
}
diff --git a/tests/core/assets/XML/entities.html b/tests/core/assets/XML/entities.html
index 05a6b107e..a60f45070 100644
--- a/tests/core/assets/XML/entities.html
+++ b/tests/core/assets/XML/entities.html
@@ -25,5 +25,10 @@
| | | fj ` \ ® ϱ ∳ ⁏
+ HHellope!
+ HHellope!
+ HHellope!
+ HHellope!
+ HHellope!