diff --git a/README.md b/README.md index 704152777..7703c0a7d 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,10 @@ Answers to common questions about Odin. Documentation for all the official packages part of the [core](https://pkg.odin-lang.org/core/) and [vendor](https://pkg.odin-lang.org/vendor/) library collections. +#### [Examples](https://github.com/odin-lang/examples) + +Examples on how to write idiomatic Odin code. Shows how to accomplish specific tasks in Odin, as well as how to use packages from `core` and `vendor`. + #### [Odin Documentation](https://odin-lang.org/docs/) Documentation for the Odin language itself. diff --git a/base/runtime/default_temp_allocator_arena.odin b/base/runtime/default_temp_allocator_arena.odin index d86750553..e48d0f8fe 100644 --- a/base/runtime/default_temp_allocator_arena.odin +++ b/base/runtime/default_temp_allocator_arena.odin @@ -212,10 +212,24 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, case size == 0: err = .Mode_Not_Implemented return - case (uintptr(old_data) & uintptr(alignment-1) == 0) && size < old_size: - // shrink data in-place - data = old_data[:size] - return + case uintptr(old_data) & uintptr(alignment-1) == 0: + if size < old_size { + // shrink data in-place + data = old_data[:size] + return + } + + if block := arena.curr_block; block != nil { + start := uint(uintptr(old_memory)) - uint(uintptr(block.base)) + old_end := start + old_size + new_end := start + size + if start < old_end && old_end == block.used && new_end <= block.capacity { + // grow data in-place, adjusting next allocation + block.used = uint(new_end) + data = block.base[start:new_end] + return + } + } } new_memory := arena_alloc(arena, size, alignment, location) or_return @@ -284,9 +298,10 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { if block := arena.curr_block; block != nil { assert(block.used >= temp.used, "out of order use of arena_temp_end", loc) - amount_to_zero := min(block.used-temp.used, block.capacity-block.used) + amount_to_zero := block.used-temp.used intrinsics.mem_zero(block.base[temp.used:], amount_to_zero) 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 8b7c8a02a..ea3ecd986 100644 --- a/base/runtime/dynamic_map_internal.odin +++ b/base/runtime/dynamic_map_internal.odin @@ -400,7 +400,7 @@ map_alloc_dynamic :: proc "odin" (info: ^Map_Info, log2_capacity: uintptr, alloc // This procedure returns the address of the just inserted value, and will // return 'nil' if there was no room to insert the entry @(require_results) -map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, ik: uintptr, iv: uintptr) -> (result: uintptr) { +map_insert_hash_dynamic_with_key :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, ik: uintptr, iv: uintptr) -> (key: uintptr, result: uintptr) { h := h pos := map_desired_position(m^, h) distance := uintptr(0) @@ -436,7 +436,11 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^ intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v) hs[pos] = h - return result if result != 0 else v_dst + if result == 0 { + key = k_dst + result = v_dst + } + return } if map_hash_is_deleted(element_hash) { @@ -444,13 +448,14 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^ } if probe_distance := map_probe_distance(m^, element_hash, pos); distance > probe_distance { - if result == 0 { - result = map_cell_index_dynamic(vs, info.vs, pos) - } - kp := map_cell_index_dynamic(ks, info.ks, pos) vp := map_cell_index_dynamic(vs, info.vs, pos) + if result == 0 { + key = kp + result = vp + } + intrinsics.mem_copy_non_overlapping(rawptr(tk), rawptr(k), size_of_k) intrinsics.mem_copy_non_overlapping(rawptr(k), rawptr(kp), size_of_k) intrinsics.mem_copy_non_overlapping(rawptr(kp), rawptr(tk), size_of_k) @@ -491,7 +496,11 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^ intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v) hs[pos] = h - return result if result != 0 else v_dst + if result == 0 { + key = k_dst + result = v_dst + } + return } k_src := map_cell_index_dynamic(ks, info.ks, la_pos) @@ -501,6 +510,7 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^ if probe_distance < look_ahead { // probed can be made ideal while placing saved (ending condition) if result == 0 { + key = k_dst result = v_dst } intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k) @@ -550,6 +560,7 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^ } else { // place saved, save probed if result == 0 { + key = k_dst result = v_dst } intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k) @@ -568,6 +579,12 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^ } } +@(require_results) +map_insert_hash_dynamic :: #force_inline proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, ik: uintptr, iv: uintptr) -> (result: uintptr) { + _, result = map_insert_hash_dynamic_with_key(m, info, h, ik, iv) + return +} + @(require_results) map_grow_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> Allocator_Error { log2_capacity := map_log2_cap(m^) @@ -959,9 +976,9 @@ __dynamic_map_entry :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_ hash = info.key_hasher(key, map_seed(m^)) } - value_ptr = rawptr(map_insert_hash_dynamic(m, info, hash, uintptr(key), uintptr(zero))) - assert(value_ptr != nil) - key_ptr = rawptr(map_cell_index_dynamic(map_data(m^), info.ks, map_desired_position(m^, hash))) + kp, vp := map_insert_hash_dynamic_with_key(m, info, hash, uintptr(key), uintptr(zero)) + key_ptr = rawptr(kp) + value_ptr = rawptr(vp) m.len += 1 just_inserted = true diff --git a/base/runtime/procs_darwin.odin b/base/runtime/procs_darwin.odin index 4f4903d47..c3fc46af1 100644 --- a/base/runtime/procs_darwin.odin +++ b/base/runtime/procs_darwin.odin @@ -1,6 +1,7 @@ #+private package runtime +@(priority_index=-1e6) foreign import "system:Foundation.framework" import "base:intrinsics" diff --git a/build_odin.sh b/build_odin.sh index f4452e291..c7d5c9288 100755 --- a/build_odin.sh +++ b/build_odin.sh @@ -9,7 +9,7 @@ set -eu 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 -lstdc++" +LDFLAGS="$LDFLAGS -pthread -lm" OS_ARCH="$(uname -m)" OS_NAME="$(uname -s)" @@ -95,15 +95,15 @@ Darwin) ;; FreeBSD) CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" - LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)" + LDFLAGS="$LDFLAGS -lstdc++ $($LLVM_CONFIG --libs core native --system-libs)" ;; NetBSD) CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" - LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)" + LDFLAGS="$LDFLAGS -lstdc++ $($LLVM_CONFIG --libs core native --system-libs)" ;; Linux) CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" - LDFLAGS="$LDFLAGS -ldl $($LLVM_CONFIG --libs core native --system-libs --libfiles)" + LDFLAGS="$LDFLAGS -lstdc++ -ldl $($LLVM_CONFIG --libs core native --system-libs --libfiles)" # Copy libLLVM*.so into current directory for linking # NOTE: This is needed by the Linux release pipeline! # cp $(readlink -f $($LLVM_CONFIG --libfiles)) ./ @@ -111,12 +111,12 @@ Linux) ;; OpenBSD) CXXFLAGS="$CXXFLAGS -I/usr/local/include $($LLVM_CONFIG --cxxflags --ldflags)" - LDFLAGS="$LDFLAGS -L/usr/local/lib -liconv" + LDFLAGS="$LDFLAGS -lstdc++ -L/usr/local/lib -liconv" LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)" ;; Haiku) CXXFLAGS="$CXXFLAGS -D_GNU_SOURCE $($LLVM_CONFIG --cxxflags --ldflags) -I/system/develop/headers/private/shared -I/system/develop/headers/private/kernel" - LDFLAGS="$LDFLAGS -liconv" + LDFLAGS="$LDFLAGS -lstdc++ -liconv" LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)" ;; *) diff --git a/core/c/c.odin b/core/c/c.odin index 3dfc19ffc..73727d8d5 100644 --- a/core/c/c.odin +++ b/core/c/c.odin @@ -114,3 +114,5 @@ CHAR_BIT :: 8 va_list :: struct #align(16) { _: [4096]u8, } + +FILE :: struct {} diff --git a/core/c/libc/stdio.odin b/core/c/libc/stdio.odin index a94a53696..56e4e8f66 100644 --- a/core/c/libc/stdio.odin +++ b/core/c/libc/stdio.odin @@ -1,5 +1,6 @@ package libc +import "core:c" import "core:io" when ODIN_OS == .Windows { @@ -15,7 +16,7 @@ when ODIN_OS == .Windows { // 7.21 Input/output -FILE :: struct {} +FILE :: c.FILE Whence :: enum int { SET = SEEK_SET, diff --git a/core/container/queue/queue.odin b/core/container/queue/queue.odin index f83a5f2b7..d1040a7c9 100644 --- a/core/container/queue/queue.odin +++ b/core/container/queue/queue.odin @@ -46,8 +46,7 @@ init_with_contents :: proc(q: ^$Q/Queue($T), backing: []T) -> bool { cap = builtin.len(backing), allocator = {procedure=runtime.nil_allocator_proc, data=nil}, } - q.len = len(backing) - q.offset = len(backing) + q.len = builtin.len(backing) return true } diff --git a/core/crypto/rand_generic.odin b/core/crypto/rand_generic.odin index ef578f5c0..8266f8ffc 100644 --- a/core/crypto/rand_generic.odin +++ b/core/crypto/rand_generic.odin @@ -5,6 +5,7 @@ #+build !netbsd #+build !darwin #+build !js +#+build !wasi package crypto HAS_RAND_BYTES :: false diff --git a/core/crypto/rand_wasi.odin b/core/crypto/rand_wasi.odin new file mode 100644 index 000000000..9653fb985 --- /dev/null +++ b/core/crypto/rand_wasi.odin @@ -0,0 +1,13 @@ +package crypto + +import "core:fmt" +import "core:sys/wasm/wasi" + +HAS_RAND_BYTES :: true + +@(private) +_rand_bytes :: proc(dst: []byte) { + if err := wasi.random_get(dst); err != nil { + fmt.panicf("crypto: wasi.random_get failed: %v", err) + } +} diff --git a/core/encoding/base32/base32.odin b/core/encoding/base32/base32.odin index 8629491b1..2267a872b 100644 --- a/core/encoding/base32/base32.odin +++ b/core/encoding/base32/base32.odin @@ -118,10 +118,10 @@ _encode :: proc(out, data: []byte, ENC_TBL := ENC_TABLE, allocator := context.al @(optimization_mode="favor_size") decode :: proc( - data: string, - DEC_TBL := DEC_TABLE, - validate: Validate_Proc = _validate_default, - allocator := context.allocator) -> (out: []byte, err: Error) { + data: string, + DEC_TBL := DEC_TABLE, + validate: Validate_Proc = _validate_default, + allocator := context.allocator) -> (out: []byte, err: Error) { if len(data) == 0 { return nil, .None } diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index 49e9f2e6d..51e70f6b7 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -314,7 +314,29 @@ assertf :: proc(condition: bool, fmt: string, args: ..any, loc := #caller_locati p = runtime.default_assertion_failure_proc } message := tprintf(fmt, ..args) - p("Runtime assertion", message, loc) + p("runtime assertion", message, loc) + } + internal(loc, fmt, ..args) + } +} +// Runtime ensure with a formatted message +// +// Inputs: +// - condition: The boolean condition to be asserted +// - fmt: A format string with placeholders for the provided arguments +// - args: A variadic list of arguments to be formatted +// - loc: The location of the caller +// +ensuref :: proc(condition: bool, fmt: string, args: ..any, loc := #caller_location) { + if !condition { + @(cold) + internal :: proc(loc: runtime.Source_Code_Location, fmt: string, args: ..any) { + p := context.assertion_failure_proc + if p == nil { + p = runtime.default_assertion_failure_proc + } + message := tprintf(fmt, ..args) + p("unsatisfied ensure", message, loc) } internal(loc, fmt, ..args) } @@ -332,7 +354,7 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! { p = runtime.default_assertion_failure_proc } message := tprintf(fmt, ..args) - p("Panic", message, loc) + p("panic", message, loc) } // Creates a formatted C string @@ -1357,9 +1379,9 @@ _pad :: proc(fi: ^Info, s: string) { if fi.minus { // right pad io.write_string(fi.writer, s, &fi.n) fmt_write_padding(fi, width) - } else if !fi.space && s != "" && s[0] == '-' { + } else if !fi.space && s != "" && (s[0] == '-' || s[0] == '+') { // left pad accounting for zero pad of negative number - io.write_byte(fi.writer, '-', &fi.n) + io.write_byte(fi.writer, s[0], &fi.n) fmt_write_padding(fi, width) io.write_string(fi.writer, s[1:], &fi.n) } else { // left pad diff --git a/core/log/log.odin b/core/log/log.odin index cbb2e922b..2b6317060 100644 --- a/core/log/log.odin +++ b/core/log/log.odin @@ -115,7 +115,7 @@ panicf :: proc(fmt_str: string, args: ..any, location := #caller_location) -> ! } @(disabled=ODIN_DISABLE_ASSERT) -assert :: proc(condition: bool, message := "", loc := #caller_location) { +assert :: proc(condition: bool, message := #caller_expression(condition), loc := #caller_location) { if !condition { @(cold) internal :: proc(message: string, loc: runtime.Source_Code_Location) { @@ -145,7 +145,38 @@ assertf :: proc(condition: bool, fmt_str: string, args: ..any, loc := #caller_lo } message := fmt.tprintf(fmt_str, ..args) log(.Fatal, message, location=loc) - p("Runtime assertion", message, loc) + p("runtime assertion", message, loc) + } + internal(loc, fmt_str, ..args) + } +} + +ensure :: proc(condition: bool, message := #caller_expression(condition), loc := #caller_location) { + if !condition { + @(cold) + internal :: proc(message: string, loc: runtime.Source_Code_Location) { + p := context.assertion_failure_proc + if p == nil { + p = runtime.default_assertion_failure_proc + } + log(.Fatal, message, location=loc) + p("unsatisfied ensure", message, loc) + } + internal(message, loc) + } +} + +ensuref :: proc(condition: bool, fmt_str: string, args: ..any, loc := #caller_location) { + if !condition { + @(cold) + internal :: proc(loc: runtime.Source_Code_Location, fmt_str: string, args: ..any) { + p := context.assertion_failure_proc + if p == nil { + p = runtime.default_assertion_failure_proc + } + message := fmt.tprintf(fmt_str, ..args) + log(.Fatal, message, location=loc) + p("unsatisfied ensure", message, loc) } internal(loc, fmt_str, ..args) } diff --git a/core/math/linalg/specific.odin b/core/math/linalg/specific.odin index b841f0610..c23feddce 100644 --- a/core/math/linalg/specific.odin +++ b/core/math/linalg/specific.odin @@ -1207,8 +1207,8 @@ matrix2_inverse_f16 :: proc "contextless" (m: Matrix2f16) -> (c: Matrix2f16) #no d := m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] id := 1.0/d c[0, 0] = +m[1, 1] * id - c[0, 1] = -m[1, 0] * id - c[1, 0] = -m[0, 1] * id + c[0, 1] = -m[0, 1] * id + c[1, 0] = -m[1, 0] * id c[1, 1] = +m[0, 0] * id return c } @@ -1217,8 +1217,8 @@ matrix2_inverse_f32 :: proc "contextless" (m: Matrix2f32) -> (c: Matrix2f32) #no d := m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] id := 1.0/d c[0, 0] = +m[1, 1] * id - c[0, 1] = -m[1, 0] * id - c[1, 0] = -m[0, 1] * id + c[0, 1] = -m[0, 1] * id + c[1, 0] = -m[1, 0] * id c[1, 1] = +m[0, 0] * id return c } @@ -1227,8 +1227,8 @@ matrix2_inverse_f64 :: proc "contextless" (m: Matrix2f64) -> (c: Matrix2f64) #no d := m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] id := 1.0/d c[0, 0] = +m[1, 1] * id - c[0, 1] = -m[1, 0] * id - c[1, 0] = -m[0, 1] * id + c[0, 1] = -m[0, 1] * id + c[1, 0] = -m[1, 0] * id c[1, 1] = +m[0, 0] * id return c } diff --git a/core/math/rand/rand.odin b/core/math/rand/rand.odin index 72d9400d7..bbd59a419 100644 --- a/core/math/rand/rand.odin +++ b/core/math/rand/rand.odin @@ -16,6 +16,7 @@ Generator_Query_Info :: runtime.Random_Generator_Query_Info Default_Random_State :: runtime.Default_Random_State default_random_generator :: runtime.default_random_generator +@(require_results) create :: proc(seed: u64) -> (state: Default_Random_State) { seed := seed runtime.default_random_generator(&state) diff --git a/core/mem/alloc.odin b/core/mem/alloc.odin index fac58daaf..1094e7381 100644 --- a/core/mem/alloc.odin +++ b/core/mem/alloc.odin @@ -785,6 +785,27 @@ delete_map :: proc( return runtime.delete_map(m, loc) } +/* +Free an SoA slice. +*/ +delete_soa_slice :: proc( + array: $T/#soa[]$E, + allocator := context.allocator, + loc := #caller_location, +) -> Allocator_Error { + return runtime.delete_soa_slice(array, allocator, loc) +} + +/* +Free an SoA dynamic array. +*/ +delete_soa_dynamic_array :: proc( + array: $T/#soa[dynamic]$E, + loc := #caller_location, +) -> Allocator_Error { + return runtime.delete_soa_dynamic_array(array, loc) +} + /* Free. */ @@ -794,6 +815,8 @@ delete :: proc{ delete_dynamic_array, delete_slice, delete_map, + delete_soa_slice, + delete_soa_dynamic_array, } /* @@ -900,8 +923,7 @@ make_dynamic_array :: proc( Allocate a dynamic array with initial length. This procedure creates a dynamic array of type `T`, with `allocator` as its -backing allocator, and initial capacity of `0`, and initial length specified by -`len`. +backing allocator, and initial capacity and length specified by `len`. */ @(require_results) make_dynamic_array_len :: proc( @@ -910,7 +932,7 @@ make_dynamic_array_len :: proc( allocator := context.allocator, loc := #caller_location, ) -> (T, Allocator_Error) { - return runtime.make_dynamic_array_len_cap(T, len, len, allocator, loc) + return runtime.make_dynamic_array_len(T, len, allocator, loc) } /* @@ -964,6 +986,71 @@ make_multi_pointer :: proc( return runtime.make_multi_pointer(T, len, allocator, loc) } +/* +Allocate an SoA slice. + +This procedure allocates an SoA slice of type `T` with length `len`, from an +allocator specified by `allocator`, and returns the allocated SoA slice. +*/ +@(require_results) +make_soa_slice :: proc( + $T: typeid/#soa[]$E, + #any_int len: int, + allocator := context.allocator, + loc := #caller_location +) -> (array: T, err: Allocator_Error) { + return runtime.make_soa_slice(T, len, allocator, loc) +} + +/* +Allocate an SoA dynamic array. + +This procedure creates an SoA dynamic array of type `T`, with `allocator` as +its backing allocator, and initial length and capacity of `0`. +*/ +@(require_results) +make_soa_dynamic_array :: proc( + $T: typeid/#soa[dynamic]$E, + allocator := context.allocator, + loc := #caller_location +) -> (array: T, err: Allocator_Error) { + return runtime.make_soa_dynamic_array(T, allocator, loc) +} + +/* +Allocate an SoA dynamic array with initial length. + +This procedure creates an SoA dynamic array of type `T`, with `allocator` as its +backing allocator, and initial capacity and length specified by `len`. +*/ +@(require_results) +make_soa_dynamic_array_len :: proc( + $T: typeid/#soa[dynamic]$E, + #any_int len: int, + allocator := context.allocator, + loc := #caller_location +) -> (array: T, err: Allocator_Error) { + return runtime.make_soa_dynamic_array_len(T, len, allocator, loc) +} + +/* +Allocate an SoA dynamic array with initial length and capacity. + +This procedure creates an SoA dynamic array of type `T`, with `allocator` as its +backing allocator, and initial capacity specified by `cap`, and initial length +specified by `len`. +*/ +@(require_results) +make_soa_dynamic_array_len_cap :: proc( + $T: typeid/#soa[dynamic]$E, + #any_int len: int, + #any_int cap: int, + allocator := context.allocator, + loc := #caller_location +) -> (array: T, err: Allocator_Error) { + return runtime.make_soa_dynamic_array_len_cap(T, len, cap, allocator, loc) +} + /* Allocate. */ @@ -974,6 +1061,10 @@ make :: proc{ make_dynamic_array_len_cap, make_map, make_multi_pointer, + make_soa_slice, + make_soa_dynamic_array, + make_soa_dynamic_array_len, + make_soa_dynamic_array_len_cap, } /* diff --git a/core/mem/virtual/arena.odin b/core/mem/virtual/arena.odin index 4a0fff241..5191505cf 100644 --- a/core/mem/virtual/arena.odin +++ b/core/mem/virtual/arena.odin @@ -328,10 +328,24 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, case size == 0: err = .Mode_Not_Implemented return - case (uintptr(old_data) & uintptr(alignment-1) == 0) && size < old_size: - // shrink data in-place - data = old_data[:size] - return + case uintptr(old_data) & uintptr(alignment-1) == 0: + if size < old_size { + // shrink data in-place + data = old_data[:size] + return + } + + if block := arena.curr_block; block != nil { + start := uint(uintptr(old_memory)) - uint(uintptr(block.base)) + old_end := start + old_size + new_end := start + size + if start < old_end && old_end == block.used && new_end <= block.reserved { + // grow data in-place, adjusting next allocation + _ = alloc_from_memory_block(block, new_end - old_end, 1, default_commit_size=arena.default_commit_size) or_return + data = block.base[start:new_end] + return + } + } } new_memory := arena_alloc(arena, size, alignment, location) or_return @@ -402,9 +416,10 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { if block := arena.curr_block; block != nil { assert(block.used >= temp.used, "out of order use of arena_temp_end", loc) - amount_to_zero := min(block.used-temp.used, block.reserved-block.used) + amount_to_zero := block.used-temp.used mem.zero_slice(block.base[temp.used:][:amount_to_zero]) block.used = temp.used + arena.total_used -= amount_to_zero } } diff --git a/core/net/common.odin b/core/net/common.odin index 263fc770f..12add8225 100644 --- a/core/net/common.odin +++ b/core/net/common.odin @@ -95,6 +95,7 @@ Resolve_Error :: enum u32 { } DNS_Error :: enum u32 { + None = 0, Invalid_Hostname_Error = 1, Invalid_Hosts_Config_Error, Invalid_Resolv_Config_Error, @@ -147,6 +148,9 @@ IP6_Loopback :: IP6_Address{0, 0, 0, 0, 0, 0, 0, 1} IP4_Any := IP4_Address{} IP6_Any := IP6_Address{} +IP4_mDNS_Broadcast := Endpoint{address=IP4_Address{224, 0, 0, 251}, port=5353} +IP6_mDNS_Broadcast := Endpoint{address=IP6_Address{65282, 0, 0, 0, 0, 0, 0, 251}, port = 5353} + Endpoint :: struct { address: Address, port: int, diff --git a/core/net/dns.odin b/core/net/dns.odin index ffb97fc5b..6d5dfea23 100644 --- a/core/net/dns.odin +++ b/core/net/dns.odin @@ -132,7 +132,14 @@ resolve_ip4 :: proc(hostname_and_maybe_port: string) -> (ep4: Endpoint, err: Net return } case Host: - recs, _ := get_dns_records_from_os(t.hostname, .IP4, context.temp_allocator) + recs: []DNS_Record + + if ODIN_OS != .Windows && strings.has_suffix(t.hostname, ".local") { + recs, _ = get_dns_records_from_nameservers(t.hostname, .IP4, {IP4_mDNS_Broadcast}, nil, context.temp_allocator) + } else { + recs, _ = get_dns_records_from_os(t.hostname, .IP4, context.temp_allocator) + } + if len(recs) == 0 { err = .Unable_To_Resolve return @@ -159,7 +166,14 @@ resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Net return t, nil } case Host: - recs, _ := get_dns_records_from_os(t.hostname, .IP6, context.temp_allocator) + recs: []DNS_Record + + if ODIN_OS != .Windows && strings.has_suffix(t.hostname, ".local") { + recs, _ = get_dns_records_from_nameservers(t.hostname, .IP6, {IP6_mDNS_Broadcast}, nil, context.temp_allocator) + } else { + recs, _ = get_dns_records_from_os(t.hostname, .IP6, context.temp_allocator) + } + if len(recs) == 0 { err = .Unable_To_Resolve return @@ -255,12 +269,6 @@ get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type return nil, .Connection_Error } - // recv_sz, _, recv_err := recv_udp(conn, dns_response_buf[:]) - // if recv_err == UDP_Recv_Error.Timeout { - // continue - // } else if recv_err != nil { - // continue - // } recv_sz, _ := recv_udp(conn, dns_response_buf[:]) or_continue if recv_sz == 0 { continue diff --git a/core/net/dns_windows.odin b/core/net/dns_windows.odin index 2f3831767..7736851b8 100644 --- a/core/net/dns_windows.odin +++ b/core/net/dns_windows.odin @@ -29,9 +29,14 @@ import win "core:sys/windows" _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { context.allocator = allocator + options := win.DNS_QUERY_OPTIONS{} + if strings.has_suffix(hostname, ".local") { + options = {.MULTICAST_ONLY, .MULTICAST_WAIT} // 0x00020500 + } + host_cstr := strings.clone_to_cstring(hostname, context.temp_allocator) rec: ^win.DNS_RECORD - res := win.DnsQuery_UTF8(host_cstr, u16(type), 0, nil, &rec, nil) + res := win.DnsQuery_UTF8(host_cstr, u16(type), options, nil, &rec, nil) switch u32(res) { case 0: diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin index b7816b0b6..cafec747d 100644 --- a/core/net/socket_linux.odin +++ b/core/net/socket_linux.odin @@ -35,6 +35,7 @@ Socket_Option :: enum c.int { Send_Buffer_Size = c.int(linux.Socket_Option.SNDBUF), Receive_Timeout = c.int(linux.Socket_Option.RCVTIMEO), Send_Timeout = c.int(linux.Socket_Option.SNDTIMEO), + Broadcast = c.int(linux.Socket_Option.BROADCAST), } // Wrappers and unwrappers for system-native types @@ -337,7 +338,8 @@ _set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc := .Reuse_Address, .Keep_Alive, .Out_Of_Bounds_Data_Inline, - .TCP_Nodelay: + .TCP_Nodelay, + .Broadcast: // TODO: verify whether these are options or not on Linux // .Broadcast, <-- yes // .Conditional_Accept, diff --git a/core/os/os2/dir_linux.odin b/core/os/os2/dir_linux.odin index f26b4fc79..f7723936b 100644 --- a/core/os/os2/dir_linux.odin +++ b/core/os/os2/dir_linux.odin @@ -1,20 +1,101 @@ #+private package os2 +import "core:sys/linux" + Read_Directory_Iterator_Impl :: struct { - + prev_fi: File_Info, + dirent_backing: []u8, + dirent_buflen: int, + dirent_off: int, + index: int, } - @(require_results) _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { + scan_entries :: proc(dfd: linux.Fd, entries: []u8, offset: ^int) -> (fd: linux.Fd, file_name: string) { + for d in linux.dirent_iterate_buf(entries, offset) { + file_name = linux.dirent_name(d) + if file_name == "." || file_name == ".." { + continue + } + + file_name_cstr := cstring(raw_data(file_name)) + entry_fd, errno := linux.openat(dfd, file_name_cstr, {.NOFOLLOW, .PATH}) + if errno == .NONE { + return entry_fd, file_name + } + } + return -1, "" + } + + index = it.impl.index + it.impl.index += 1 + + dfd := linux.Fd(_fd(it.f)) + + entries := it.impl.dirent_backing[:it.impl.dirent_buflen] + entry_fd, file_name := scan_entries(dfd, entries, &it.impl.dirent_off) + + for entry_fd == -1 { + if len(it.impl.dirent_backing) == 0 { + it.impl.dirent_backing = make([]u8, 512, file_allocator()) + } + + loop: for { + buflen, errno := linux.getdents(linux.Fd(dfd), it.impl.dirent_backing[:]) + #partial switch errno { + case .EINVAL: + delete(it.impl.dirent_backing, file_allocator()) + n := len(it.impl.dirent_backing) * 2 + it.impl.dirent_backing = make([]u8, n, file_allocator()) + continue + case .NONE: + if buflen == 0 { + return + } + it.impl.dirent_off = 0 + it.impl.dirent_buflen = buflen + entries = it.impl.dirent_backing[:buflen] + break loop + case: // error + return + } + } + + entry_fd, file_name = scan_entries(dfd, entries, &it.impl.dirent_off) + } + defer linux.close(entry_fd) + + file_info_delete(it.impl.prev_fi, file_allocator()) + fi, _ = _fstat_internal(entry_fd, file_allocator()) + it.impl.prev_fi = fi + + ok = true return } @(require_results) _read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) { - return {}, .Unsupported + if f == nil || f.impl == nil { + return {}, .Invalid_File + } + + stat: linux.Stat + errno := linux.fstat(linux.Fd(fd(f)), &stat) + if errno != .NONE { + return {}, _get_platform_error(errno) + } + if (stat.mode & linux.S_IFMT) != linux.S_IFDIR { + return {}, .Invalid_Dir + } + return {f = f}, nil } _read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { + if it == nil { + return + } + delete(it.impl.dirent_backing, file_allocator()) + file_info_delete(it.impl.prev_fi, file_allocator()) } diff --git a/core/os/os2/dir_posix.odin b/core/os/os2/dir_posix.odin index 14fddde50..36cac2597 100644 --- a/core/os/os2/dir_posix.odin +++ b/core/os/os2/dir_posix.odin @@ -39,8 +39,11 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info } n := len(fimpl.name)+1 - non_zero_resize(&it.impl.fullpath, n+len(sname)) - n += copy(it.impl.fullpath[n:], sname) + if err := non_zero_resize(&it.impl.fullpath, n+len(sname)); err != nil { + // Can't really tell caller we had an error, sad. + return + } + copy(it.impl.fullpath[n:], sname) fi = internal_stat(stat, string(it.impl.fullpath[:])) ok = true @@ -60,7 +63,7 @@ _read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Itera iter.f = f iter.impl.idx = 0 - iter.impl.fullpath.allocator = file_allocator() + iter.impl.fullpath = make([dynamic]byte, 0, len(impl.name)+128, file_allocator()) or_return append(&iter.impl.fullpath, impl.name) append(&iter.impl.fullpath, "/") defer if err != nil { delete(iter.impl.fullpath) } diff --git a/core/os/os2/dir_wasi.odin b/core/os/os2/dir_wasi.odin new file mode 100644 index 000000000..e4349069a --- /dev/null +++ b/core/os/os2/dir_wasi.odin @@ -0,0 +1,110 @@ +#+private +package os2 + +import "base:intrinsics" +import "core:sys/wasm/wasi" + +Read_Directory_Iterator_Impl :: struct { + fullpath: [dynamic]byte, + buf: []byte, + off: int, + idx: int, +} + +@(require_results) +_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { + fimpl := (^File_Impl)(it.f.impl) + + buf := it.impl.buf[it.impl.off:] + + index = it.impl.idx + it.impl.idx += 1 + + for { + if len(buf) < size_of(wasi.dirent_t) { + return + } + + entry := intrinsics.unaligned_load((^wasi.dirent_t)(raw_data(buf))) + buf = buf[size_of(wasi.dirent_t):] + + if len(buf) < int(entry.d_namlen) { + // shouldn't be possible. + return + } + + name := string(buf[:entry.d_namlen]) + buf = buf[entry.d_namlen:] + it.impl.off += size_of(wasi.dirent_t) + int(entry.d_namlen) + + if name == "." || name == ".." { + continue + } + + n := len(fimpl.name)+1 + if alloc_err := non_zero_resize(&it.impl.fullpath, n+len(name)); alloc_err != nil { + // Can't really tell caller we had an error, sad. + return + } + copy(it.impl.fullpath[n:], name) + + stat, err := wasi.path_filestat_get(__fd(it.f), {}, name) + if err != nil { + // Can't stat, fill what we have from dirent. + stat = { + ino = entry.d_ino, + filetype = entry.d_type, + } + } + + fi = internal_stat(stat, string(it.impl.fullpath[:])) + ok = true + return + } +} + +@(require_results) +_read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Iterator, err: Error) { + if f == nil || f.impl == nil { + err = .Invalid_File + return + } + + impl := (^File_Impl)(f.impl) + iter.f = f + + buf: [dynamic]byte + buf.allocator = file_allocator() + defer if err != nil { delete(buf) } + + // NOTE: this is very grug. + for { + non_zero_resize(&buf, 512 if len(buf) == 0 else len(buf)*2) or_return + + n, _err := wasi.fd_readdir(__fd(f), buf[:], 0) + if _err != nil { + err = _get_platform_error(_err) + return + } + + if n < len(buf) { + non_zero_resize(&buf, n) + break + } + + assert(n == len(buf)) + } + iter.impl.buf = buf[:] + + iter.impl.fullpath = make([dynamic]byte, 0, len(impl.name)+128, file_allocator()) or_return + append(&iter.impl.fullpath, impl.name) + append(&iter.impl.fullpath, "/") + + return +} + +_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { + delete(it.impl.buf, file_allocator()) + delete(it.impl.fullpath) + it^ = {} +} diff --git a/core/os/os2/env_linux.odin b/core/os/os2/env_linux.odin index c248a323c..e0ef06010 100644 --- a/core/os/os2/env_linux.odin +++ b/core/os/os2/env_linux.odin @@ -76,7 +76,7 @@ _set_env :: proc(key, v_new: string) -> bool { // wasn't in the environment in the first place. k_addr, v_addr := _kv_addr_from_val(v_curr, key) if len(v_new) > len(v_curr) { - k_addr = ([^]u8)(heap_resize(k_addr, kv_size)) + k_addr = ([^]u8)(runtime.heap_resize(k_addr, kv_size)) if k_addr == nil { return false } @@ -90,7 +90,7 @@ _set_env :: proc(key, v_new: string) -> bool { } } - k_addr := ([^]u8)(heap_alloc(kv_size)) + k_addr := ([^]u8)(runtime.heap_alloc(kv_size)) if k_addr == nil { return false } @@ -129,7 +129,7 @@ _unset_env :: proc(key: string) -> bool { // if we got this far, the envrionment variable // existed AND was allocated by us. k_addr, _ := _kv_addr_from_val(v, key) - heap_free(k_addr) + runtime.heap_free(k_addr) return true } @@ -139,7 +139,7 @@ _clear_env :: proc() { for kv in _env { if !_is_in_org_env(kv) { - heap_free(raw_data(kv)) + runtime.heap_free(raw_data(kv)) } } clear(&_env) @@ -193,7 +193,7 @@ _build_env :: proc() { return } - _env = make(type_of(_env), heap_allocator()) + _env = make(type_of(_env), runtime.heap_allocator()) cstring_env := _get_original_env() _org_env_begin = uintptr(rawptr(cstring_env[0])) for i := 0; cstring_env[i] != nil; i += 1 { diff --git a/core/os/os2/env_wasi.odin b/core/os/os2/env_wasi.odin new file mode 100644 index 000000000..8bf4eff38 --- /dev/null +++ b/core/os/os2/env_wasi.odin @@ -0,0 +1,186 @@ +#+private +package os2 + +import "base:runtime" + +import "core:strings" +import "core:sync" +import "core:sys/wasm/wasi" + +g_env: map[string]string +g_env_buf: []byte +g_env_mutex: sync.RW_Mutex +g_env_error: Error +g_env_built: bool + +build_env :: proc() -> (err: Error) { + if g_env_built || g_env_error != nil { + return g_env_error + } + + sync.guard(&g_env_mutex) + + if g_env_built || g_env_error != nil { + return g_env_error + } + + defer if err != nil { + g_env_error = err + } + + num_envs, size_of_envs, _err := wasi.environ_sizes_get() + if _err != nil { + return _get_platform_error(_err) + } + + g_env = make(map[string]string, num_envs, file_allocator()) or_return + defer if err != nil { delete(g_env) } + + 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() + + envs := make([]cstring, num_envs, temp_allocator()) or_return + + _err = wasi.environ_get(raw_data(envs), raw_data(g_env_buf)) + if _err != nil { + return _get_platform_error(_err) + } + + for env in envs { + key, _, value := strings.partition(string(env), "=") + g_env[key] = value + } + + g_env_built = true + return +} + +delete_string_if_not_original :: proc(str: string) { + start := uintptr(raw_data(g_env_buf)) + end := start + uintptr(len(g_env_buf)) + ptr := uintptr(raw_data(str)) + if ptr < start || ptr > end { + delete(str, file_allocator()) + } +} + +@(require_results) +_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { + if err := build_env(); err != nil { + return + } + + sync.shared_guard(&g_env_mutex) + + value = g_env[key] or_return + value, _ = clone_string(value, allocator) + return +} + +@(require_results) +_set_env :: proc(key, value: string) -> bool { + if err := build_env(); err != nil { + return false + } + + sync.guard(&g_env_mutex) + + key_ptr, value_ptr, just_inserted, err := map_entry(&g_env, key) + if err != nil { + return false + } + + alloc_err: runtime.Allocator_Error + + if just_inserted { + key_ptr^, alloc_err = clone_string(key, file_allocator()) + if alloc_err != nil { + delete_key(&g_env, key) + return false + } + + value_ptr^, alloc_err = clone_string(value, file_allocator()) + if alloc_err != nil { + delete_key(&g_env, key) + delete(key_ptr^, file_allocator()) + return false + } + + return true + } + + delete_string_if_not_original(value_ptr^) + + value_ptr^, alloc_err = clone_string(value, file_allocator()) + if alloc_err != nil { + delete_key(&g_env, key) + return false + } + + return true +} + +@(require_results) +_unset_env :: proc(key: string) -> bool { + if err := build_env(); err != nil { + return false + } + + sync.guard(&g_env_mutex) + + dkey, dval := delete_key(&g_env, key) + delete_string_if_not_original(dkey) + delete_string_if_not_original(dval) + return true +} + +_clear_env :: proc() { + sync.guard(&g_env_mutex) + + for k, v in g_env { + delete_string_if_not_original(k) + delete_string_if_not_original(v) + } + + delete(g_env_buf, file_allocator()) + g_env_buf = {} + + clear(&g_env) + + g_env_built = true +} + +@(require_results) +_environ :: proc(allocator: runtime.Allocator) -> []string { + if err := build_env(); err != nil { + return nil + } + + sync.shared_guard(&g_env_mutex) + + envs, alloc_err := make([]string, len(g_env), allocator) + if alloc_err != nil { + return nil + } + + defer if alloc_err != nil { + for env in envs { + delete(env, allocator) + } + delete(envs, allocator) + } + + i: int + for k, v in g_env { + defer i += 1 + + envs[i], alloc_err = concatenate({k, "=", v}, allocator) + if alloc_err != nil { + return nil + } + } + + return envs +} diff --git a/core/os/os2/errors_posix.odin b/core/os/os2/errors_posix.odin index 0b5876c0b..8a9ca07df 100644 --- a/core/os/os2/errors_posix.odin +++ b/core/os/os2/errors_posix.odin @@ -10,8 +10,12 @@ _error_string :: proc(errno: i32) -> string { return string(posix.strerror(posix.Errno(errno))) } -_get_platform_error :: proc() -> Error { - #partial switch errno := posix.errno(); errno { +_get_platform_error_from_errno :: proc() -> Error { + return _get_platform_error_existing(posix.errno()) +} + +_get_platform_error_existing :: proc(errno: posix.Errno) -> Error { + #partial switch errno { case .EPERM: return .Permission_Denied case .EEXIST: @@ -32,3 +36,8 @@ _get_platform_error :: proc() -> Error { return Platform_Error(errno) } } + +_get_platform_error :: proc{ + _get_platform_error_existing, + _get_platform_error_from_errno, +} diff --git a/core/os/os2/errors_wasi.odin b/core/os/os2/errors_wasi.odin new file mode 100644 index 000000000..b88e5b81e --- /dev/null +++ b/core/os/os2/errors_wasi.odin @@ -0,0 +1,47 @@ +#+private +package os2 + +import "base:runtime" + +import "core:slice" +import "core:sys/wasm/wasi" + +_Platform_Error :: wasi.errno_t + +_error_string :: proc(errno: i32) -> string { + e := wasi.errno_t(errno) + if e == .NONE { + return "" + } + + err := runtime.Type_Info_Enum_Value(e) + + ti := &runtime.type_info_base(type_info_of(wasi.errno_t)).variant.(runtime.Type_Info_Enum) + if idx, ok := slice.binary_search(ti.values, err); ok { + return ti.names[idx] + } + return "" +} + +_get_platform_error :: proc(errno: wasi.errno_t) -> Error { + #partial switch errno { + case .PERM: + return .Permission_Denied + case .EXIST: + return .Exist + case .NOENT: + return .Not_Exist + case .TIMEDOUT: + return .Timeout + case .PIPE: + return .Broken_Pipe + case .BADF: + return .Invalid_File + case .NOMEM: + return .Out_Of_Memory + case .NOSYS: + return .Unsupported + case: + return Platform_Error(errno) + } +} diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index f8e4026da..9f6625091 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -7,6 +7,13 @@ import "core:time" import "core:sync" import "core:sys/linux" +// Most implementations will EINVAL at some point when doing big writes. +// In practice a read/write call would probably never read/write these big buffers all at once, +// which is why the number of bytes is returned and why there are procs that will call this in a +// loop for you. +// We set a max of 1GB to keep alignment and to be safe. +MAX_RW :: 1 << 30 + File_Impl :: struct { file: File, name: string, @@ -179,10 +186,11 @@ _seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, er } _read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { - if len(p) == 0 { + if len(p) <= 0 { return 0, nil } - n, errno := linux.read(f.fd, p[:]) + + n, errno := linux.read(f.fd, p[:min(len(p), MAX_RW)]) if errno != .NONE { return -1, _get_platform_error(errno) } @@ -190,13 +198,13 @@ _read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { } _read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { - if len(p) == 0 { + if len(p) <= 0 { return 0, nil } if offset < 0 { return 0, .Invalid_Offset } - n, errno := linux.pread(f.fd, p[:], offset) + n, errno := linux.pread(f.fd, p[:min(len(p), MAX_RW)], offset) if errno != .NONE { return -1, _get_platform_error(errno) } @@ -206,29 +214,42 @@ _read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { return i64(n), nil } -_write :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { - if len(p) == 0 { - return 0, nil +_write :: proc(f: ^File_Impl, p: []byte) -> (nt: i64, err: Error) { + p := p + for len(p) > 0 { + n, errno := linux.write(f.fd, p[:min(len(p), MAX_RW)]) + if errno != .NONE { + err = _get_platform_error(errno) + return + } + + p = p[n:] + nt += i64(n) } - n, errno := linux.write(f.fd, p[:]) - if errno != .NONE { - return -1, _get_platform_error(errno) - } - return i64(n), nil + + return } -_write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { - if len(p) == 0 { - return 0, nil - } +_write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (nt: i64, err: Error) { if offset < 0 { return 0, .Invalid_Offset } - n, errno := linux.pwrite(f.fd, p[:], offset) - if errno != .NONE { - return -1, _get_platform_error(errno) + + p := p + offset := offset + for len(p) > 0 { + n, errno := linux.pwrite(f.fd, p[:min(len(p), MAX_RW)], offset) + if errno != .NONE { + err = _get_platform_error(errno) + return + } + + p = p[n:] + nt += i64(n) + offset += i64(n) } - return i64(n), nil + + return } _file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) { diff --git a/core/os/os2/file_wasi.odin b/core/os/os2/file_wasi.odin new file mode 100644 index 000000000..2b722e5dd --- /dev/null +++ b/core/os/os2/file_wasi.odin @@ -0,0 +1,534 @@ +#+private +package os2 + +import "base:runtime" + +import "core:io" +import "core:sys/wasm/wasi" +import "core:time" + +// NOTE: Don't know if there is a max in wasi. +MAX_RW :: 1 << 30 + +File_Impl :: struct { + file: File, + name: string, + fd: wasi.fd_t, + allocator: runtime.Allocator, +} + +// WASI works with "preopened" directories, the environment retrieves directories +// (for example with `wasmtime --dir=. module.wasm`) and those given directories +// are the only ones accessible by the application. +// +// So in order to facilitate the `os` API (absolute paths etc.) we keep a list +// of the given directories and match them when needed (notably `os.open`). +Preopen :: struct { + fd: wasi.fd_t, + prefix: string, +} +preopens: []Preopen + +@(init) +init_std_files :: proc() { + new_std :: proc(impl: ^File_Impl, fd: wasi.fd_t, name: string) -> ^File { + impl.file.impl = impl + impl.allocator = runtime.nil_allocator() + impl.fd = fd + impl.name = string(name) + impl.file.stream = { + data = impl, + procedure = _file_stream_proc, + } + impl.file.fstat = _fstat + return &impl.file + } + + @(static) files: [3]File_Impl + stdin = new_std(&files[0], 0, "/dev/stdin") + stdout = new_std(&files[1], 1, "/dev/stdout") + stderr = new_std(&files[2], 2, "/dev/stderr") +} + +@(init) +init_preopens :: proc() { + strip_prefixes :: proc(path: string) -> string { + path := path + loop: for len(path) > 0 { + switch { + case path[0] == '/': + path = path[1:] + case len(path) > 2 && path[0] == '.' && path[1] == '/': + path = path[2:] + case len(path) == 1 && path[0] == '.': + path = path[1:] + case: + break loop + } + } + return path + } + + n: int + n_loop: for fd := wasi.fd_t(3); ; fd += 1 { + _, err := wasi.fd_prestat_get(fd) + #partial switch err { + case .BADF: break n_loop + case .SUCCESS: n += 1 + case: + print_error(stderr, _get_platform_error(err), "unexpected error from wasi_prestat_get") + break n_loop + } + } + + alloc_err: runtime.Allocator_Error + preopens, alloc_err = make([]Preopen, n, file_allocator()) + if alloc_err != nil { + print_error(stderr, alloc_err, "could not allocate memory for wasi preopens") + return + } + + loop: for &preopen, i in preopens { + fd := wasi.fd_t(3 + i) + + desc, err := wasi.fd_prestat_get(fd) + assert(err == .SUCCESS) + + switch desc.tag { + case .DIR: + buf: []byte + buf, alloc_err = make([]byte, desc.dir.pr_name_len, file_allocator()) + if alloc_err != nil { + print_error(stderr, alloc_err, "could not allocate memory for wasi preopen dir name") + continue loop + } + + if err = wasi.fd_prestat_dir_name(fd, buf); err != .SUCCESS { + print_error(stderr, _get_platform_error(err), "could not get filesystem preopen dir name") + continue loop + } + + preopen.fd = fd + preopen.prefix = strip_prefixes(string(buf)) + } + } +} + +@(require_results) +match_preopen :: proc(path: string) -> (wasi.fd_t, string, bool) { + @(require_results) + prefix_matches :: proc(prefix, path: string) -> bool { + // Empty is valid for any relative path. + if len(prefix) == 0 && len(path) > 0 && path[0] != '/' { + return true + } + + if len(path) < len(prefix) { + return false + } + + if path[:len(prefix)] != prefix { + return false + } + + // Only match on full components. + i := len(prefix) + for i > 0 && prefix[i-1] == '/' { + i -= 1 + } + return path[i] == '/' + } + + path := path + if path == "" { + return 0, "", false + } + + for len(path) > 0 && path[0] == '/' { + path = path[1:] + } + + match: Preopen + #reverse for preopen in preopens { + if (match.fd == 0 || len(preopen.prefix) > len(match.prefix)) && prefix_matches(preopen.prefix, path) { + match = preopen + } + } + + if match.fd == 0 { + return 0, "", false + } + + relative := path[len(match.prefix):] + for len(relative) > 0 && relative[0] == '/' { + relative = relative[1:] + } + + if len(relative) == 0 { + relative = "." + } + + return match.fd, relative, true +} + +_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) { + dir_fd, relative, ok := match_preopen(name) + if !ok { + return nil, .Invalid_Path + } + + oflags: wasi.oflags_t + if .Create in flags { oflags += {.CREATE} } + if .Excl in flags { oflags += {.EXCL} } + if .Trunc in flags { oflags += {.TRUNC} } + + fdflags: wasi.fdflags_t + if .Append in flags { fdflags += {.APPEND} } + if .Sync in flags { fdflags += {.SYNC} } + + // NOTE: rights are adjusted to what this package's functions might want to call. + rights: wasi.rights_t + if .Read in flags { rights += {.FD_READ, .FD_FILESTAT_GET, .PATH_FILESTAT_GET} } + if .Write in flags { rights += {.FD_WRITE, .FD_SYNC, .FD_FILESTAT_SET_SIZE, .FD_FILESTAT_SET_TIMES, .FD_SEEK} } + + fd, fderr := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, oflags, rights, {}, fdflags) + if fderr != nil { + err = _get_platform_error(fderr) + return + } + + return _new_file(uintptr(fd), name, file_allocator()) +} + +_new_file :: proc(handle: uintptr, name: string, allocator: runtime.Allocator) -> (f: ^File, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + impl := new(File_Impl, allocator) or_return + defer if err != nil { free(impl, allocator) } + + impl.allocator = allocator + // NOTE: wasi doesn't really do full paths afact. + impl.name = clone_string(name, allocator) or_return + impl.fd = wasi.fd_t(handle) + impl.file.impl = impl + impl.file.stream = { + data = impl, + procedure = _file_stream_proc, + } + impl.file.fstat = _fstat + + return &impl.file, nil +} + +_close :: proc(f: ^File_Impl) -> (err: Error) { + if errno := wasi.fd_close(f.fd); errno != nil { + err = _get_platform_error(errno) + } + + delete(f.name, f.allocator) + free(f, f.allocator) + return +} + +_fd :: proc(f: ^File) -> uintptr { + return uintptr(__fd(f)) +} + +__fd :: proc(f: ^File) -> wasi.fd_t { + if f != nil && f.impl != nil { + return (^File_Impl)(f.impl).fd + } + return -1 +} + +_name :: proc(f: ^File) -> string { + if f != nil && f.impl != nil { + return (^File_Impl)(f.impl).name + } + return "" +} + +_sync :: proc(f: ^File) -> Error { + return _get_platform_error(wasi.fd_sync(__fd(f))) +} + +_truncate :: proc(f: ^File, size: i64) -> Error { + return _get_platform_error(wasi.fd_filestat_set_size(__fd(f), wasi.filesize_t(size))) +} + +_remove :: proc(name: string) -> Error { + dir_fd, relative, ok := match_preopen(name) + if !ok { + return .Invalid_Path + } + + err := wasi.path_remove_directory(dir_fd, relative) + if err == .NOTDIR { + err = wasi.path_unlink_file(dir_fd, relative) + } + + return _get_platform_error(err) +} + +_rename :: proc(old_path, new_path: string) -> Error { + src_dir_fd, src_relative, src_ok := match_preopen(old_path) + if !src_ok { + return .Invalid_Path + } + + new_dir_fd, new_relative, new_ok := match_preopen(new_path) + if !new_ok { + return .Invalid_Path + } + + return _get_platform_error(wasi.path_rename(src_dir_fd, src_relative, new_dir_fd, new_relative)) +} + +_link :: proc(old_name, new_name: string) -> Error { + src_dir_fd, src_relative, src_ok := match_preopen(old_name) + if !src_ok { + return .Invalid_Path + } + + new_dir_fd, new_relative, new_ok := match_preopen(new_name) + if !new_ok { + return .Invalid_Path + } + + return _get_platform_error(wasi.path_link(src_dir_fd, {.SYMLINK_FOLLOW}, src_relative, new_dir_fd, new_relative)) +} + +_symlink :: proc(old_name, new_name: string) -> Error { + src_dir_fd, src_relative, src_ok := match_preopen(old_name) + if !src_ok { + return .Invalid_Path + } + + new_dir_fd, new_relative, new_ok := match_preopen(new_name) + if !new_ok { + return .Invalid_Path + } + + if src_dir_fd != new_dir_fd { + return .Invalid_Path + } + + return _get_platform_error(wasi.path_symlink(src_relative, src_dir_fd, new_relative)) +} + +_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) { + dir_fd, relative, ok := match_preopen(name) + if !ok { + return "", .Invalid_Path + } + + n, _err := wasi.path_readlink(dir_fd, relative, nil) + if _err != nil { + err = _get_platform_error(_err) + return + } + + buf := make([]byte, n, allocator) or_return + + _, _err = wasi.path_readlink(dir_fd, relative, buf) + s = string(buf) + err = _get_platform_error(_err) + return +} + +_chdir :: proc(name: string) -> Error { + return .Unsupported +} + +_fchdir :: proc(f: ^File) -> Error { + return .Unsupported +} + +_fchmod :: proc(f: ^File, mode: int) -> Error { + return .Unsupported +} + +_chmod :: proc(name: string, mode: int) -> Error { + return .Unsupported +} + +_fchown :: proc(f: ^File, uid, gid: int) -> Error { + return .Unsupported +} + +_chown :: proc(name: string, uid, gid: int) -> Error { + return .Unsupported +} + +_lchown :: proc(name: string, uid, gid: int) -> Error { + return .Unsupported +} + +_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { + dir_fd, relative, ok := match_preopen(name) + if !ok { + return .Invalid_Path + } + + _atime := wasi.timestamp_t(atime._nsec) + _mtime := wasi.timestamp_t(mtime._nsec) + + return _get_platform_error(wasi.path_filestat_set_times(dir_fd, {.SYMLINK_FOLLOW}, relative, _atime, _mtime, {.MTIM, .ATIM})) +} + +_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { + _atime := wasi.timestamp_t(atime._nsec) + _mtime := wasi.timestamp_t(mtime._nsec) + + return _get_platform_error(wasi.fd_filestat_set_times(__fd(f), _atime, _mtime, {.ATIM, .MTIM})) +} + +_exists :: proc(path: string) -> bool { + dir_fd, relative, ok := match_preopen(path) + if !ok { + return false + } + + _, err := wasi.path_filestat_get(dir_fd, {.SYMLINK_FOLLOW}, relative) + if err != nil { + return false + } + + return true +} + +_file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { + f := (^File_Impl)(stream_data) + fd := f.fd + + switch mode { + case .Read: + if len(p) <= 0 { + return + } + + to_read := min(len(p), MAX_RW) + _n, _err := wasi.fd_read(fd, {p[:to_read]}) + n = i64(_n) + + if _err != nil { + err = .Unknown + } else if n == 0 { + err = .EOF + } + + return + + case .Read_At: + if len(p) <= 0 { + return + } + + if offset < 0 { + err = .Invalid_Offset + return + } + + to_read := min(len(p), MAX_RW) + _n, _err := wasi.fd_pread(fd, {p[:to_read]}, wasi.filesize_t(offset)) + n = i64(_n) + + if _err != nil { + err = .Unknown + } else if n == 0 { + err = .EOF + } + + return + + case .Write: + p := p + for len(p) > 0 { + to_write := min(len(p), MAX_RW) + _n, _err := wasi.fd_write(fd, {p[:to_write]}) + if _err != nil { + err = .Unknown + return + } + p = p[_n:] + n += i64(_n) + } + return + + case .Write_At: + p := p + offset := offset + + if offset < 0 { + err = .Invalid_Offset + return + } + + for len(p) > 0 { + to_write := min(len(p), MAX_RW) + _n, _err := wasi.fd_pwrite(fd, {p[:to_write]}, wasi.filesize_t(offset)) + if _err != nil { + err = .Unknown + return + } + + p = p[_n:] + n += i64(_n) + offset += i64(_n) + } + return + + case .Seek: + #assert(int(wasi.whence_t.SET) == int(io.Seek_From.Start)) + #assert(int(wasi.whence_t.CUR) == int(io.Seek_From.Current)) + #assert(int(wasi.whence_t.END) == int(io.Seek_From.End)) + + switch whence { + case .Start, .Current, .End: + break + case: + err = .Invalid_Whence + return + } + + _n, _err := wasi.fd_seek(fd, wasi.filedelta_t(offset), wasi.whence_t(whence)) + #partial switch _err { + case .INVAL: + err = .Invalid_Offset + case: + err = .Unknown + case .SUCCESS: + n = i64(_n) + } + return + + case .Size: + stat, _err := wasi.fd_filestat_get(fd) + if _err != nil { + err = .Unknown + return + } + + n = i64(stat.size) + return + + case .Flush: + ferr := _sync(&f.file) + err = error_to_io_error(ferr) + return + + case .Close, .Destroy: + ferr := _close(f) + err = error_to_io_error(ferr) + return + + case .Query: + return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Destroy, .Query}) + + case: + return 0, .Empty + } +} diff --git a/core/os/os2/heap_linux.odin b/core/os/os2/heap_linux.odin index 8819dfac7..1d1f12726 100644 --- a/core/os/os2/heap_linux.odin +++ b/core/os/os2/heap_linux.odin @@ -1,726 +1,6 @@ #+private package os2 -import "core:sys/linux" -import "core:sync" -import "core:mem" - -// NOTEs -// -// All allocations below DIRECT_MMAP_THRESHOLD exist inside of memory "Regions." A region -// consists of a Region_Header and the memory that will be divided into allocations to -// send to the user. The memory is an array of "Allocation_Headers" which are 8 bytes. -// Allocation_Headers are used to navigate the memory in the region. The "next" member of -// the Allocation_Header points to the next header, and the space between the headers -// can be used to send to the user. This space between is referred to as "blocks" in the -// code. The indexes in the header refer to these blocks instead of bytes. This allows us -// to index all the memory in the region with a u16. -// -// When an allocation request is made, it will use the first free block that can contain -// the entire block. If there is an excess number of blocks (as specified by the constant -// BLOCK_SEGMENT_THRESHOLD), this extra space will be segmented and left in the free_list. -// -// To keep the implementation simple, there can never exist 2 free blocks adjacent to each -// other. Any freeing will result in attempting to merge the blocks before and after the -// newly free'd blocks. -// -// Any request for size above the DIRECT_MMAP_THRESHOLD will result in the allocation -// getting its own individual mmap. Individual mmaps will still get an Allocation_Header -// that contains the size with the last bit set to 1 to indicate it is indeed a direct -// mmap allocation. - -// Why not brk? -// glibc's malloc utilizes a mix of the brk and mmap system calls. This implementation -// does *not* utilize the brk system call to avoid possible conflicts with foreign C -// code. Just because we aren't directly using libc, there is nothing stopping the user -// from doing it. - -// What's with all the #no_bounds_check? -// When memory is returned from mmap, it technically doesn't get written ... well ... anywhere -// until that region is written to by *you*. So, when a new region is created, we call mmap -// to get a pointer to some memory, and we claim that memory is a ^Region. Therefor, the -// region itself is never formally initialized by the compiler as this would result in writing -// zeros to memory that we can already assume are 0. This would also have the effect of -// actually commiting this data to memory whether it gets used or not. - - -// -// Some variables to play with -// - -// Minimum blocks used for any one allocation -MINIMUM_BLOCK_COUNT :: 2 - -// Number of extra blocks beyond the requested amount where we would segment. -// E.g. (blocks) |H0123456| 7 available -// |H01H0123| Ask for 2, now 4 available -BLOCK_SEGMENT_THRESHOLD :: 4 - -// Anything above this threshold will get its own memory map. Since regions -// are indexed by 16 bit integers, this value should not surpass max(u16) * 6 -DIRECT_MMAP_THRESHOLD_USER :: int(max(u16)) - -// The point at which we convert direct mmap to region. This should be a decent -// amount less than DIRECT_MMAP_THRESHOLD to avoid jumping in and out of regions. -MMAP_TO_REGION_SHRINK_THRESHOLD :: DIRECT_MMAP_THRESHOLD - PAGE_SIZE * 4 - -// free_list is dynamic and is initialized in the begining of the region memory -// when the region is initialized. Once resized, it can be moved anywhere. -FREE_LIST_DEFAULT_CAP :: 32 - - -// -// Other constants that should not be touched -// - -// This universally seems to be 4096 outside of uncommon archs. -PAGE_SIZE :: 4096 - -// just rounding up to nearest PAGE_SIZE -DIRECT_MMAP_THRESHOLD :: (DIRECT_MMAP_THRESHOLD_USER-1) + PAGE_SIZE - (DIRECT_MMAP_THRESHOLD_USER-1) % PAGE_SIZE - -// Regions must be big enough to hold DIRECT_MMAP_THRESHOLD - 1 as well -// as end right on a page boundary as to not waste space. -SIZE_OF_REGION :: DIRECT_MMAP_THRESHOLD + 4 * int(PAGE_SIZE) - -// size of user memory blocks -BLOCK_SIZE :: size_of(Allocation_Header) - -// number of allocation sections (call them blocks) of the region used for allocations -BLOCKS_PER_REGION :: u16((SIZE_OF_REGION - size_of(Region_Header)) / BLOCK_SIZE) - -// minimum amount of space that can used by any individual allocation (includes header) -MINIMUM_ALLOCATION :: (MINIMUM_BLOCK_COUNT * BLOCK_SIZE) + BLOCK_SIZE - -// This is used as a boolean value for Region_Header.local_addr. -CURRENTLY_ACTIVE :: (^^Region)(~uintptr(0)) - -FREE_LIST_ENTRIES_PER_BLOCK :: BLOCK_SIZE / size_of(u16) - -MMAP_FLAGS : linux.Map_Flags : {.ANONYMOUS, .PRIVATE} -MMAP_PROT : linux.Mem_Protection : {.READ, .WRITE} - -@thread_local _local_region: ^Region -global_regions: ^Region - - -// There is no way of correctly setting the last bit of free_idx or -// the last bit of requested, so we can safely use it as a flag to -// determine if we are interacting with a direct mmap. -REQUESTED_MASK :: 0x7FFFFFFFFFFFFFFF -IS_DIRECT_MMAP :: 0x8000000000000000 - -// Special free_idx value that does not index the free_list. -NOT_FREE :: 0x7FFF -Allocation_Header :: struct #raw_union { - using _: struct { - // Block indicies - idx: u16, - prev: u16, - next: u16, - free_idx: u16, - }, - requested: u64, -} - -Region_Header :: struct #align(16) { - next_region: ^Region, // points to next region in global_heap (linked list) - local_addr: ^^Region, // tracks region ownership via address of _local_region - reset_addr: ^^Region, // tracks old local addr for reset - free_list: []u16, - free_list_len: u16, - free_blocks: u16, // number of free blocks in region (includes headers) - last_used: u16, // farthest back block that has been used (need zeroing?) - _reserved: u16, -} - -Region :: struct { - hdr: Region_Header, - memory: [BLOCKS_PER_REGION]Allocation_Header, -} - -_heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, mem.Allocator_Error) { - // - // NOTE(tetra, 2020-01-14): The heap doesn't respect alignment. - // Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert - // padding. We also store the original pointer returned by heap_alloc right before - // the pointer we return to the user. - // - - aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr = nil) -> ([]byte, mem.Allocator_Error) { - a := max(alignment, align_of(rawptr)) - space := size + a - 1 - - allocated_mem: rawptr - if old_ptr != nil { - original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^ - allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr)) - } else { - allocated_mem = heap_alloc(space+size_of(rawptr)) - } - aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr))) - - ptr := uintptr(aligned_mem) - aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a) - diff := int(aligned_ptr - ptr) - if (size + diff) > space || allocated_mem == nil { - return nil, .Out_Of_Memory - } - - aligned_mem = rawptr(aligned_ptr) - mem.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem - - return mem.byte_slice(aligned_mem, size), nil - } - - aligned_free :: proc(p: rawptr) { - if p != nil { - heap_free(mem.ptr_offset((^rawptr)(p), -1)^) - } - } - - aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int) -> (new_memory: []byte, err: mem.Allocator_Error) { - if p == nil { - return nil, nil - } - - return aligned_alloc(new_size, new_alignment, p) - } - - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - return aligned_alloc(size, alignment) - - case .Free: - aligned_free(old_memory) - - case .Free_All: - return nil, .Mode_Not_Implemented - - case .Resize, .Resize_Non_Zeroed: - if old_memory == nil { - return aligned_alloc(size, alignment) - } - return aligned_resize(old_memory, old_size, size, alignment) - - case .Query_Features: - set := (^mem.Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Alloc, .Free, .Resize, .Query_Features} - } - return nil, nil - - case .Query_Info: - return nil, .Mode_Not_Implemented - } - - return nil, nil -} - -heap_alloc :: proc(size: int) -> rawptr { - if size >= DIRECT_MMAP_THRESHOLD { - return _direct_mmap_alloc(size) - } - - // atomically check if the local region has been stolen - if _local_region != nil { - res := sync.atomic_compare_exchange_strong_explicit( - &_local_region.hdr.local_addr, - &_local_region, - CURRENTLY_ACTIVE, - .Acquire, - .Relaxed, - ) - if res != &_local_region { - // At this point, the region has been stolen and res contains the unexpected value - expected := res - if res != CURRENTLY_ACTIVE { - expected = res - res = sync.atomic_compare_exchange_strong_explicit( - &_local_region.hdr.local_addr, - expected, - CURRENTLY_ACTIVE, - .Acquire, - .Relaxed, - ) - } - if res != expected { - _local_region = nil - } - } - } - - size := size - size = _round_up_to_nearest(size, BLOCK_SIZE) - blocks_needed := u16(max(MINIMUM_BLOCK_COUNT, size / BLOCK_SIZE)) - - // retrieve a region if new thread or stolen - if _local_region == nil { - _local_region, _ = _region_retrieve_with_space(blocks_needed) - if _local_region == nil { - return nil - } - } - defer sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) - - // At this point we have a usable region. Let's find the user some memory - idx: u16 - local_region_idx := _region_get_local_idx() - back_idx := -1 - infinite: for { - for i := 0; i < int(_local_region.hdr.free_list_len); i += 1 { - idx = _local_region.hdr.free_list[i] - #no_bounds_check if _get_block_count(_local_region.memory[idx]) >= blocks_needed { - break infinite - } - } - sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) - _local_region, back_idx = _region_retrieve_with_space(blocks_needed, local_region_idx, back_idx) - } - user_ptr, used := _region_get_block(_local_region, idx, blocks_needed) - - sync.atomic_sub_explicit(&_local_region.hdr.free_blocks, used + 1, .Release) - - // If this memory was ever used before, it now needs to be zero'd. - if idx < _local_region.hdr.last_used { - mem.zero(user_ptr, int(used) * BLOCK_SIZE) - } else { - _local_region.hdr.last_used = idx + used - } - - return user_ptr -} - -heap_resize :: proc(old_memory: rawptr, new_size: int) -> rawptr #no_bounds_check { - alloc := _get_allocation_header(old_memory) - if alloc.requested & IS_DIRECT_MMAP > 0 { - return _direct_mmap_resize(alloc, new_size) - } - - if new_size > DIRECT_MMAP_THRESHOLD { - return _direct_mmap_from_region(alloc, new_size) - } - - return _region_resize(alloc, new_size) -} - -heap_free :: proc(memory: rawptr) { - alloc := _get_allocation_header(memory) - if sync.atomic_load(&alloc.requested) & IS_DIRECT_MMAP == IS_DIRECT_MMAP { - _direct_mmap_free(alloc) - return - } - - assert(alloc.free_idx == NOT_FREE) - - _region_find_and_assign_local(alloc) - _region_local_free(alloc) - sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) -} - -// -// Regions -// -_new_region :: proc() -> ^Region #no_bounds_check { - ptr, errno := linux.mmap(0, uint(SIZE_OF_REGION), MMAP_PROT, MMAP_FLAGS, -1, 0) - if errno != .NONE { - return nil - } - new_region := (^Region)(ptr) - - new_region.hdr.local_addr = CURRENTLY_ACTIVE - new_region.hdr.reset_addr = &_local_region - - free_list_blocks := _round_up_to_nearest(FREE_LIST_DEFAULT_CAP, FREE_LIST_ENTRIES_PER_BLOCK) - _region_assign_free_list(new_region, &new_region.memory[1], u16(free_list_blocks) * FREE_LIST_ENTRIES_PER_BLOCK) - - // + 2 to account for free_list's allocation header - first_user_block := len(new_region.hdr.free_list) / FREE_LIST_ENTRIES_PER_BLOCK + 2 - - // first allocation header (this is a free list) - new_region.memory[0].next = u16(first_user_block) - new_region.memory[0].free_idx = NOT_FREE - new_region.memory[first_user_block].idx = u16(first_user_block) - new_region.memory[first_user_block].next = BLOCKS_PER_REGION - 1 - - // add the first user block to the free list - new_region.hdr.free_list[0] = u16(first_user_block) - new_region.hdr.free_list_len = 1 - new_region.hdr.free_blocks = _get_block_count(new_region.memory[first_user_block]) + 1 - - for r := sync.atomic_compare_exchange_strong(&global_regions, nil, new_region); - r != nil; - r = sync.atomic_compare_exchange_strong(&r.hdr.next_region, nil, new_region) {} - - return new_region -} - -_region_resize :: proc(alloc: ^Allocation_Header, new_size: int, alloc_is_free_list: bool = false) -> rawptr #no_bounds_check { - assert(alloc.free_idx == NOT_FREE) - - old_memory := mem.ptr_offset(alloc, 1) - - old_block_count := _get_block_count(alloc^) - new_block_count := u16( - max(MINIMUM_BLOCK_COUNT, _round_up_to_nearest(new_size, BLOCK_SIZE) / BLOCK_SIZE), - ) - if new_block_count < old_block_count { - if new_block_count - old_block_count >= MINIMUM_BLOCK_COUNT { - _region_find_and_assign_local(alloc) - _region_segment(_local_region, alloc, new_block_count, alloc.free_idx) - new_block_count = _get_block_count(alloc^) - sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) - } - // need to zero anything within the new block that that lies beyond new_size - extra_bytes := int(new_block_count * BLOCK_SIZE) - new_size - extra_bytes_ptr := mem.ptr_offset((^u8)(alloc), new_size + BLOCK_SIZE) - mem.zero(extra_bytes_ptr, extra_bytes) - return old_memory - } - - if !alloc_is_free_list { - _region_find_and_assign_local(alloc) - } - defer if !alloc_is_free_list { - sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) - } - - // First, let's see if we can grow in place. - if alloc.next != BLOCKS_PER_REGION - 1 && _local_region.memory[alloc.next].free_idx != NOT_FREE { - next_alloc := _local_region.memory[alloc.next] - total_available := old_block_count + _get_block_count(next_alloc) + 1 - if total_available >= new_block_count { - alloc.next = next_alloc.next - _local_region.memory[alloc.next].prev = alloc.idx - if total_available - new_block_count > BLOCK_SEGMENT_THRESHOLD { - _region_segment(_local_region, alloc, new_block_count, next_alloc.free_idx) - } else { - _region_free_list_remove(_local_region, next_alloc.free_idx) - } - mem.zero(&_local_region.memory[next_alloc.idx], int(alloc.next - next_alloc.idx) * BLOCK_SIZE) - _local_region.hdr.last_used = max(alloc.next, _local_region.hdr.last_used) - _local_region.hdr.free_blocks -= (_get_block_count(alloc^) - old_block_count) - if alloc_is_free_list { - _region_assign_free_list(_local_region, old_memory, _get_block_count(alloc^)) - } - return old_memory - } - } - - // If we made it this far, we need to resize, copy, zero and free. - region_iter := _local_region - local_region_idx := _region_get_local_idx() - back_idx := -1 - idx: u16 - infinite: for { - for i := 0; i < int(region_iter.hdr.free_list_len); i += 1 { - idx = region_iter.hdr.free_list[i] - if _get_block_count(region_iter.memory[idx]) >= new_block_count { - break infinite - } - } - if region_iter != _local_region { - sync.atomic_store_explicit( - ®ion_iter.hdr.local_addr, - region_iter.hdr.reset_addr, - .Release, - ) - } - region_iter, back_idx = _region_retrieve_with_space(new_block_count, local_region_idx, back_idx) - } - if region_iter != _local_region { - sync.atomic_store_explicit( - ®ion_iter.hdr.local_addr, - region_iter.hdr.reset_addr, - .Release, - ) - } - - // copy from old memory - new_memory, used_blocks := _region_get_block(region_iter, idx, new_block_count) - mem.copy(new_memory, old_memory, int(old_block_count * BLOCK_SIZE)) - - // zero any new memory - addon_section := mem.ptr_offset((^Allocation_Header)(new_memory), old_block_count) - new_blocks := used_blocks - old_block_count - mem.zero(addon_section, int(new_blocks) * BLOCK_SIZE) - - region_iter.hdr.free_blocks -= (used_blocks + 1) - - // Set free_list before freeing. - if alloc_is_free_list { - _region_assign_free_list(_local_region, new_memory, used_blocks) - } - - // free old memory - _region_local_free(alloc) - return new_memory -} - -_region_local_free :: proc(alloc: ^Allocation_Header) #no_bounds_check { - alloc := alloc - add_to_free_list := true - - idx := sync.atomic_load(&alloc.idx) - prev := sync.atomic_load(&alloc.prev) - next := sync.atomic_load(&alloc.next) - block_count := next - idx - 1 - free_blocks := sync.atomic_load(&_local_region.hdr.free_blocks) + block_count + 1 - sync.atomic_store_explicit(&_local_region.hdr.free_blocks, free_blocks, .Release) - - // try to merge with prev - if idx > 0 && sync.atomic_load(&_local_region.memory[prev].free_idx) != NOT_FREE { - sync.atomic_store_explicit(&_local_region.memory[prev].next, next, .Release) - _local_region.memory[next].prev = prev - alloc = &_local_region.memory[prev] - add_to_free_list = false - } - - // try to merge with next - if next < BLOCKS_PER_REGION - 1 && sync.atomic_load(&_local_region.memory[next].free_idx) != NOT_FREE { - old_next := next - sync.atomic_store_explicit(&alloc.next, sync.atomic_load(&_local_region.memory[old_next].next), .Release) - - sync.atomic_store_explicit(&_local_region.memory[next].prev, idx, .Release) - - if add_to_free_list { - sync.atomic_store_explicit(&_local_region.hdr.free_list[_local_region.memory[old_next].free_idx], idx, .Release) - sync.atomic_store_explicit(&alloc.free_idx, _local_region.memory[old_next].free_idx, .Release) - } else { - // NOTE: We have aleady merged with prev, and now merged with next. - // Now, we are actually going to remove from the free_list. - _region_free_list_remove(_local_region, _local_region.memory[old_next].free_idx) - } - add_to_free_list = false - } - - // This is the only place where anything is appended to the free list. - if add_to_free_list { - fl := _local_region.hdr.free_list - fl_len := sync.atomic_load(&_local_region.hdr.free_list_len) - sync.atomic_store_explicit(&alloc.free_idx, fl_len, .Release) - fl[alloc.free_idx] = idx - sync.atomic_store_explicit(&_local_region.hdr.free_list_len, fl_len + 1, .Release) - if int(fl_len + 1) == len(fl) { - free_alloc := _get_allocation_header(mem.raw_data(_local_region.hdr.free_list)) - _region_resize(free_alloc, len(fl) * 2 * size_of(fl[0]), true) - } - } -} - -_region_assign_free_list :: proc(region: ^Region, memory: rawptr, blocks: u16) { - raw_free_list := transmute(mem.Raw_Slice)region.hdr.free_list - raw_free_list.len = int(blocks) * FREE_LIST_ENTRIES_PER_BLOCK - raw_free_list.data = memory - region.hdr.free_list = transmute([]u16)(raw_free_list) -} - -_region_retrieve_with_space :: proc(blocks: u16, local_idx: int = -1, back_idx: int = -1) -> (^Region, int) { - r: ^Region - idx: int - for r = sync.atomic_load(&global_regions); r != nil; r = r.hdr.next_region { - if idx == local_idx || idx < back_idx || sync.atomic_load(&r.hdr.free_blocks) < blocks { - idx += 1 - continue - } - idx += 1 - local_addr: ^^Region = sync.atomic_load(&r.hdr.local_addr) - if local_addr != CURRENTLY_ACTIVE { - res := sync.atomic_compare_exchange_strong_explicit( - &r.hdr.local_addr, - local_addr, - CURRENTLY_ACTIVE, - .Acquire, - .Relaxed, - ) - if res == local_addr { - r.hdr.reset_addr = local_addr - return r, idx - } - } - } - - return _new_region(), idx -} - -_region_retrieve_from_addr :: proc(addr: rawptr) -> ^Region { - r: ^Region - for r = global_regions; r != nil; r = r.hdr.next_region { - if _region_contains_mem(r, addr) { - return r - } - } - unreachable() -} - -_region_get_block :: proc(region: ^Region, idx, blocks_needed: u16) -> (rawptr, u16) #no_bounds_check { - alloc := ®ion.memory[idx] - - assert(alloc.free_idx != NOT_FREE) - assert(alloc.next > 0) - - block_count := _get_block_count(alloc^) - if block_count - blocks_needed > BLOCK_SEGMENT_THRESHOLD { - _region_segment(region, alloc, blocks_needed, alloc.free_idx) - } else { - _region_free_list_remove(region, alloc.free_idx) - } - - alloc.free_idx = NOT_FREE - return mem.ptr_offset(alloc, 1), _get_block_count(alloc^) -} - -_region_segment :: proc(region: ^Region, alloc: ^Allocation_Header, blocks, new_free_idx: u16) #no_bounds_check { - old_next := alloc.next - alloc.next = alloc.idx + blocks + 1 - region.memory[old_next].prev = alloc.next - - // Initialize alloc.next allocation header here. - region.memory[alloc.next].prev = alloc.idx - region.memory[alloc.next].next = old_next - region.memory[alloc.next].idx = alloc.next - region.memory[alloc.next].free_idx = new_free_idx - - // Replace our original spot in the free_list with new segment. - region.hdr.free_list[new_free_idx] = alloc.next -} - -_region_get_local_idx :: proc() -> int { - idx: int - for r := sync.atomic_load(&global_regions); r != nil; r = r.hdr.next_region { - if r == _local_region { - return idx - } - idx += 1 - } - - return -1 -} - -_region_find_and_assign_local :: proc(alloc: ^Allocation_Header) { - // Find the region that contains this memory - if !_region_contains_mem(_local_region, alloc) { - _local_region = _region_retrieve_from_addr(alloc) - } - - // At this point, _local_region is set correctly. Spin until acquire - res := CURRENTLY_ACTIVE - - for res == CURRENTLY_ACTIVE { - res = sync.atomic_compare_exchange_strong_explicit( - &_local_region.hdr.local_addr, - &_local_region, - CURRENTLY_ACTIVE, - .Acquire, - .Relaxed, - ) - } -} - -_region_contains_mem :: proc(r: ^Region, memory: rawptr) -> bool #no_bounds_check { - if r == nil { - return false - } - mem_int := uintptr(memory) - return mem_int >= uintptr(&r.memory[0]) && mem_int <= uintptr(&r.memory[BLOCKS_PER_REGION - 1]) -} - -_region_free_list_remove :: proc(region: ^Region, free_idx: u16) #no_bounds_check { - // pop, swap and update allocation hdr - if n := region.hdr.free_list_len - 1; free_idx != n { - region.hdr.free_list[free_idx] = sync.atomic_load(®ion.hdr.free_list[n]) - alloc_idx := region.hdr.free_list[free_idx] - sync.atomic_store_explicit(®ion.memory[alloc_idx].free_idx, free_idx, .Release) - } - region.hdr.free_list_len -= 1 -} - -// -// Direct mmap -// -_direct_mmap_alloc :: proc(size: int) -> rawptr { - mmap_size := _round_up_to_nearest(size + BLOCK_SIZE, PAGE_SIZE) - new_allocation, errno := linux.mmap(0, uint(mmap_size), MMAP_PROT, MMAP_FLAGS, -1, 0) - if errno != .NONE { - return nil - } - - alloc := (^Allocation_Header)(uintptr(new_allocation)) - alloc.requested = u64(size) // NOTE: requested = requested size - alloc.requested += IS_DIRECT_MMAP - return rawptr(mem.ptr_offset(alloc, 1)) -} - -_direct_mmap_resize :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr { - old_requested := int(alloc.requested & REQUESTED_MASK) - old_mmap_size := _round_up_to_nearest(old_requested + BLOCK_SIZE, PAGE_SIZE) - new_mmap_size := _round_up_to_nearest(new_size + BLOCK_SIZE, PAGE_SIZE) - if int(new_mmap_size) < MMAP_TO_REGION_SHRINK_THRESHOLD { - return _direct_mmap_to_region(alloc, new_size) - } else if old_requested == new_size { - return mem.ptr_offset(alloc, 1) - } - - new_allocation, errno := linux.mremap(alloc, uint(old_mmap_size), uint(new_mmap_size), {.MAYMOVE}) - if errno != .NONE { - return nil - } - - new_header := (^Allocation_Header)(uintptr(new_allocation)) - new_header.requested = u64(new_size) - new_header.requested += IS_DIRECT_MMAP - - if new_mmap_size > old_mmap_size { - // new section may not be pointer aligned, so cast to ^u8 - new_section := mem.ptr_offset((^u8)(new_header), old_requested + BLOCK_SIZE) - mem.zero(new_section, new_mmap_size - old_mmap_size) - } - return mem.ptr_offset(new_header, 1) - -} - -_direct_mmap_from_region :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr { - new_memory := _direct_mmap_alloc(new_size) - if new_memory != nil { - old_memory := mem.ptr_offset(alloc, 1) - mem.copy(new_memory, old_memory, int(_get_block_count(alloc^)) * BLOCK_SIZE) - } - _region_find_and_assign_local(alloc) - _region_local_free(alloc) - sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) - return new_memory -} - -_direct_mmap_to_region :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr { - new_memory := heap_alloc(new_size) - if new_memory != nil { - mem.copy(new_memory, mem.ptr_offset(alloc, -1), new_size) - _direct_mmap_free(alloc) - } - return new_memory -} - -_direct_mmap_free :: proc(alloc: ^Allocation_Header) { - requested := int(alloc.requested & REQUESTED_MASK) - mmap_size := _round_up_to_nearest(requested + BLOCK_SIZE, PAGE_SIZE) - linux.munmap(alloc, uint(mmap_size)) -} - -// -// Util -// - -_get_block_count :: #force_inline proc(alloc: Allocation_Header) -> u16 { - return alloc.next - alloc.idx - 1 -} - -_get_allocation_header :: #force_inline proc(raw_mem: rawptr) -> ^Allocation_Header { - return mem.ptr_offset((^Allocation_Header)(raw_mem), -1) -} - -_round_up_to_nearest :: #force_inline proc(size, round: int) -> int { - return (size-1) + round - (size-1) % round -} +import "base:runtime" +_heap_allocator_proc :: runtime.heap_allocator_proc diff --git a/core/os/os2/heap_wasi.odin b/core/os/os2/heap_wasi.odin new file mode 100644 index 000000000..7da3c4845 --- /dev/null +++ b/core/os/os2/heap_wasi.odin @@ -0,0 +1,6 @@ +#+private +package os2 + +import "base:runtime" + +_heap_allocator_proc :: runtime.wasm_allocator_proc diff --git a/core/os/os2/path.odin b/core/os/os2/path.odin index 254950d68..9231307f5 100644 --- a/core/os/os2/path.odin +++ b/core/os/os2/path.odin @@ -2,6 +2,8 @@ package os2 import "base:runtime" +import "core:path/filepath" + Path_Separator :: _Path_Separator // OS-Specific Path_Separator_String :: _Path_Separator_String // OS-Specific Path_List_Separator :: _Path_List_Separator // OS-Specific @@ -39,3 +41,13 @@ setwd :: set_working_directory set_working_directory :: proc(dir: string) -> (err: Error) { return _set_working_directory(dir) } + +get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + return _get_executable_path(allocator) +} + +get_executable_directory :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + path = _get_executable_path(allocator) or_return + path, _ = filepath.split(path) + return +} diff --git a/core/os/os2/path_darwin.odin b/core/os/os2/path_darwin.odin new file mode 100644 index 000000000..65aaf1e95 --- /dev/null +++ b/core/os/os2/path_darwin.odin @@ -0,0 +1,17 @@ +package os2 + +import "base:runtime" + +import "core:sys/darwin" +import "core:sys/posix" + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + buffer: [darwin.PIDPATHINFO_MAXSIZE]byte = --- + ret := darwin.proc_pidpath(posix.getpid(), raw_data(buffer[:]), len(buffer)) + if ret > 0 { + return clone_string(string(buffer[:ret]), allocator) + } + + err = _get_platform_error() + return +} diff --git a/core/os/os2/path_freebsd.odin b/core/os/os2/path_freebsd.odin new file mode 100644 index 000000000..577108eca --- /dev/null +++ b/core/os/os2/path_freebsd.odin @@ -0,0 +1,29 @@ +package os2 + +import "base:runtime" + +import "core:sys/freebsd" +import "core:sys/posix" + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + req := []freebsd.MIB_Identifier{.CTL_KERN, .KERN_PROC, .KERN_PROC_PATHNAME, freebsd.MIB_Identifier(-1)} + + size: uint + if ret := freebsd.sysctl(req, nil, &size, nil, 0); ret != .NONE { + err = _get_platform_error(posix.Errno(ret)) + return + } + assert(size > 0) + + buf := make([]byte, size, allocator) or_return + defer if err != nil { delete(buf, allocator) } + + assert(uint(len(buf)) == size) + + if ret := freebsd.sysctl(req, raw_data(buf), &size, nil, 0); ret != .NONE { + err = _get_platform_error(posix.Errno(ret)) + return + } + + return string(buf[:size]), nil +} diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index bfdb645ef..e3e7f8a7c 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -1,9 +1,10 @@ #+private package os2 +import "base:runtime" + import "core:strings" import "core:strconv" -import "base:runtime" import "core:sys/linux" _Path_Separator :: '/' @@ -171,6 +172,25 @@ _set_working_directory :: proc(dir: string) -> Error { return _get_platform_error(linux.chdir(dir_cstr)) } +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + + buf := make([dynamic]byte, 1024, temp_allocator()) or_return + for { + n, errno := linux.readlink("/proc/self/exe", buf[:]) + if errno != .NONE { + err = _get_platform_error(errno) + return + } + + if n < len(buf) { + return clone_string(string(buf[:n]), allocator) + } + + resize(&buf, len(buf)*2) or_return + } +} + _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath: string, err: Error) { PROC_FD_PATH :: "/proc/self/fd/" diff --git a/core/os/os2/path_netbsd.odin b/core/os/os2/path_netbsd.odin new file mode 100644 index 000000000..f56a91fd6 --- /dev/null +++ b/core/os/os2/path_netbsd.odin @@ -0,0 +1,24 @@ +package os2 + +import "base:runtime" + +import "core:sys/posix" + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + + buf := make([dynamic]byte, 1024, temp_allocator()) or_return + for { + n := posix.readlink("/proc/curproc/exe", raw_data(buf), len(buf)) + if n < 0 { + err = _get_platform_error() + return + } + + if n < len(buf) { + return clone_string(string(buf[:n]), allocator) + } + + resize(&buf, len(buf)*2) or_return + } +} diff --git a/core/os/os2/path_openbsd.odin b/core/os/os2/path_openbsd.odin new file mode 100644 index 000000000..f56c1a61b --- /dev/null +++ b/core/os/os2/path_openbsd.odin @@ -0,0 +1,57 @@ +package os2 + +import "base:runtime" + +import "core:strings" +import "core:sys/posix" + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + // OpenBSD does not have an API for this, we do our best below. + + if len(runtime.args__) <= 0 { + err = .Invalid_Path + return + } + + real :: proc(path: cstring, allocator: runtime.Allocator) -> (out: string, err: Error) { + real := posix.realpath(path) + if real == nil { + err = _get_platform_error() + return + } + defer posix.free(real) + return clone_string(string(real), allocator) + } + + arg := runtime.args__[0] + sarg := string(arg) + + if len(sarg) == 0 { + err = .Invalid_Path + return + } + + if sarg[0] == '.' || sarg[0] == '/' { + return real(arg, allocator) + } + + TEMP_ALLOCATOR_GUARD() + + buf := strings.builder_make(temp_allocator()) + + paths := get_env("PATH", temp_allocator()) + for dir in strings.split_iterator(&paths, ":") { + strings.builder_reset(&buf) + strings.write_string(&buf, dir) + strings.write_string(&buf, "/") + strings.write_string(&buf, sarg) + + cpath := strings.to_cstring(&buf) + if posix.access(cpath, {.X_OK}) == .OK { + return real(cpath, allocator) + } + } + + err = .Invalid_Path + return +} diff --git a/core/os/os2/path_posix.odin b/core/os/os2/path_posix.odin index 5ffdac28e..e6b95c0d4 100644 --- a/core/os/os2/path_posix.odin +++ b/core/os/os2/path_posix.odin @@ -81,7 +81,7 @@ _remove_all :: proc(path: string) -> Error { fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator()) if entry.d_type == .DIR { - _remove_all(fullpath[:len(fullpath)-1]) + _remove_all(fullpath[:len(fullpath)-1]) or_return } else { if posix.unlink(cstring(raw_data(fullpath))) != .OK { return _get_platform_error() diff --git a/core/os/os2/path_wasi.odin b/core/os/os2/path_wasi.odin new file mode 100644 index 000000000..2f8a3c8c6 --- /dev/null +++ b/core/os/os2/path_wasi.odin @@ -0,0 +1,113 @@ +#+private +package os2 + +import "base:runtime" + +import "core:path/filepath" +import "core:sync" +import "core:sys/wasm/wasi" + +_Path_Separator :: '/' +_Path_Separator_String :: "/" +_Path_List_Separator :: ':' + +_is_path_separator :: proc(c: byte) -> bool { + return c == _Path_Separator +} + +_mkdir :: proc(name: string, perm: int) -> Error { + dir_fd, relative, ok := match_preopen(name) + if !ok { + return .Invalid_Path + } + + return _get_platform_error(wasi.path_create_directory(dir_fd, relative)) +} + +_mkdir_all :: proc(path: string, perm: int) -> Error { + if path == "" { + return .Invalid_Path + } + + TEMP_ALLOCATOR_GUARD() + + if exists(path) { + return .Exist + } + + clean_path := filepath.clean(path, temp_allocator()) + return internal_mkdir_all(clean_path) + + internal_mkdir_all :: proc(path: string) -> Error { + dir, file := filepath.split(path) + if file != path && dir != "/" { + if len(dir) > 1 && dir[len(dir) - 1] == '/' { + dir = dir[:len(dir) - 1] + } + internal_mkdir_all(dir) or_return + } + + err := _mkdir(path, 0) + if err == .Exist { err = nil } + return err + } +} + +_remove_all :: proc(path: string) -> (err: Error) { + // PERF: this works, but wastes a bunch of memory using the read_directory_iterator API + // and using open instead of wasi fds directly. + { + dir := open(path) or_return + defer close(dir) + + iter := read_directory_iterator_create(dir) or_return + defer read_directory_iterator_destroy(&iter) + + for fi in read_directory_iterator(&iter) { + if fi.type == .Directory { + _remove_all(fi.fullpath) or_return + } else { + remove(fi.fullpath) or_return + } + } + } + + return remove(path) +} + +g_wd: string +g_wd_mutex: sync.Mutex + +_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + sync.guard(&g_wd_mutex) + + return clone_string(g_wd if g_wd != "" else "/", allocator) +} + +_set_working_directory :: proc(dir: string) -> (err: Error) { + sync.guard(&g_wd_mutex) + + if dir == g_wd { + return + } + + if g_wd != "" { + delete(g_wd, file_allocator()) + } + + g_wd = clone_string(dir, file_allocator()) or_return + return +} + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + if len(args) <= 0 { + return clone_string("/", allocator) + } + + arg := args[0] + if len(arg) > 0 && (arg[0] == '.' || arg[0] == '/') { + return clone_string(arg, allocator) + } + + return concatenate({"/", arg}, allocator) +} diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index 3e92cb6f3..041a4d1e3 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -136,6 +136,26 @@ _set_working_directory :: proc(dir: string) -> (err: Error) { return } +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + + buf := make([dynamic]u16, 512, temp_allocator()) or_return + for { + ret := win32.GetModuleFileNameW(nil, raw_data(buf), win32.DWORD(len(buf))) + if ret == 0 { + err = _get_platform_error() + return + } + + if ret == win32.DWORD(len(buf)) && win32.GetLastError() == win32.ERROR_INSUFFICIENT_BUFFER { + resize(&buf, len(buf)*2) or_return + continue + } + + return win32_utf16_to_utf8(buf[:ret], allocator) + } +} + can_use_long_paths: bool @(init) diff --git a/core/os/os2/pipe_wasi.odin b/core/os/os2/pipe_wasi.odin new file mode 100644 index 000000000..19c11b51d --- /dev/null +++ b/core/os/os2/pipe_wasi.odin @@ -0,0 +1,13 @@ +#+private +package os2 + +_pipe :: proc() -> (r, w: ^File, err: Error) { + err = .Unsupported + return +} + +@(require_results) +_pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) { + err = .Unsupported + return +} diff --git a/core/os/os2/process_linux.odin b/core/os/os2/process_linux.odin index 936fbfc40..09fd8c255 100644 --- a/core/os/os2/process_linux.odin +++ b/core/os/os2/process_linux.odin @@ -547,6 +547,11 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { if _, errno = linux.dup2(stderr_fd, STDERR); errno != .NONE { write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) } + if dir_fd != linux.AT_FDCWD { + if errno = linux.fchdir(dir_fd); errno != .NONE { + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } + } errno = linux.execveat(dir_fd, exe_path, &cargs[0], env) assert(errno != nil) diff --git a/core/os/os2/process_wasi.odin b/core/os/os2/process_wasi.odin new file mode 100644 index 000000000..6ebfe3788 --- /dev/null +++ b/core/os/os2/process_wasi.odin @@ -0,0 +1,89 @@ +#+private +package os2 + +import "base:runtime" + +import "core:time" +import "core:sys/wasm/wasi" + +_exit :: proc "contextless" (code: int) -> ! { + wasi.proc_exit(wasi.exitcode_t(code)) +} + +_get_uid :: proc() -> int { + return 0 +} + +_get_euid :: proc() -> int { + return 0 +} + +_get_gid :: proc() -> int { + return 0 +} + +_get_egid :: proc() -> int { + return 0 +} + +_get_pid :: proc() -> int { + return 0 +} + +_get_ppid :: proc() -> int { + return 0 +} + +_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + err = .Unsupported + return +} + +_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + err = .Unsupported + return +} + +_Sys_Process_Attributes :: struct {} + +_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { + err = .Unsupported + return +} + +_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { + err = .Unsupported + return +} + +_process_close :: proc(process: Process) -> Error { + return .Unsupported +} + +_process_kill :: proc(process: Process) -> (err: Error) { + return .Unsupported +} + +_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + err = .Unsupported + return +} + +_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { + err = .Unsupported + return +} + +_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { + process.pid = pid + err = .Unsupported + return +} + +_process_handle_still_valid :: proc(p: Process) -> Error { + return nil +} + +_process_state_update_times :: proc(p: Process, state: ^Process_State) { + return +} diff --git a/core/os/os2/stat_wasi.odin b/core/os/os2/stat_wasi.odin new file mode 100644 index 000000000..2992c6267 --- /dev/null +++ b/core/os/os2/stat_wasi.odin @@ -0,0 +1,101 @@ +#+private +package os2 + +import "base:runtime" + +import "core:path/filepath" +import "core:sys/wasm/wasi" +import "core:time" + +internal_stat :: proc(stat: wasi.filestat_t, fullpath: string) -> (fi: File_Info) { + fi.fullpath = fullpath + fi.name = filepath.base(fi.fullpath) + + fi.inode = u128(stat.ino) + fi.size = i64(stat.size) + + switch stat.filetype { + case .BLOCK_DEVICE: fi.type = .Block_Device + case .CHARACTER_DEVICE: fi.type = .Character_Device + case .DIRECTORY: fi.type = .Directory + case .REGULAR_FILE: fi.type = .Regular + case .SOCKET_DGRAM, .SOCKET_STREAM: fi.type = .Socket + case .SYMBOLIC_LINK: fi.type = .Symlink + case .UNKNOWN: fi.type = .Undetermined + case: fi.type = .Undetermined + } + + fi.creation_time = time.Time{_nsec=i64(stat.ctim)} + fi.modification_time = time.Time{_nsec=i64(stat.mtim)} + fi.access_time = time.Time{_nsec=i64(stat.atim)} + + return +} + +_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if f == nil || f.impl == nil { + err = .Invalid_File + return + } + + impl := (^File_Impl)(f.impl) + + stat, _err := wasi.fd_filestat_get(__fd(f)) + if _err != nil { + err = _get_platform_error(_err) + return + } + + fullpath := clone_string(impl.name, allocator) or_return + return internal_stat(stat, fullpath), nil +} + +_stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + dir_fd, relative, ok := match_preopen(name) + if !ok { + err = .Invalid_Path + return + } + + stat, _err := wasi.path_filestat_get(dir_fd, {.SYMLINK_FOLLOW}, relative) + if _err != nil { + err = _get_platform_error(_err) + return + } + + // NOTE: wasi doesn't really do full paths afact. + fullpath := clone_string(name, allocator) or_return + return internal_stat(stat, fullpath), nil +} + +_lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + dir_fd, relative, ok := match_preopen(name) + if !ok { + err = .Invalid_Path + return + } + + stat, _err := wasi.path_filestat_get(dir_fd, {}, relative) + if _err != nil { + err = _get_platform_error(_err) + return + } + + // NOTE: wasi doesn't really do full paths afact. + fullpath := clone_string(name, allocator) or_return + return internal_stat(stat, fullpath), nil +} + +_same_file :: proc(fi1, fi2: File_Info) -> bool { + return fi1.fullpath == fi2.fullpath +} diff --git a/core/os/os2/temp_file_wasi.odin b/core/os/os2/temp_file_wasi.odin new file mode 100644 index 000000000..d5628d300 --- /dev/null +++ b/core/os/os2/temp_file_wasi.odin @@ -0,0 +1,9 @@ +#+private +package os2 + +import "base:runtime" + +_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { + // NOTE: requires user to add /tmp to their preopen dirs, no standard way exists. + return clone_string("/tmp", allocator) +} diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index d4435ec63..bbffc46d7 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -1287,7 +1287,7 @@ sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: soc } send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { - result := _unix_send(c.int(sd), raw_data(data), len(data), 0) + result := _unix_send(c.int(sd), raw_data(data), len(data), i32(flags)) if result < 0 { return 0, get_last_error() } diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index e023ce7cb..2281e6a82 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -1155,7 +1155,7 @@ sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: soc } send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { - result := unix.sys_sendto(int(sd), raw_data(data), len(data), 0, nil, 0) + result := unix.sys_sendto(int(sd), raw_data(data), len(data), flags, nil, 0) if result < 0 { return 0, _get_errno(int(result)) } diff --git a/core/path/filepath/match.odin b/core/path/filepath/match.odin index 003f8046d..1f0ac9287 100644 --- a/core/path/filepath/match.odin +++ b/core/path/filepath/match.odin @@ -1,3 +1,4 @@ +#+build !wasi package filepath import "core:os" diff --git a/core/path/filepath/path_wasi.odin b/core/path/filepath/path_wasi.odin new file mode 100644 index 000000000..74cc6ca1e --- /dev/null +++ b/core/path/filepath/path_wasi.odin @@ -0,0 +1,36 @@ +package filepath + +import "base:runtime" + +import "core:strings" + +SEPARATOR :: '/' +SEPARATOR_STRING :: `/` +LIST_SEPARATOR :: ':' + +is_reserved_name :: proc(path: string) -> bool { + return false +} + +is_abs :: proc(path: string) -> bool { + return strings.has_prefix(path, "/") +} + +abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { + if is_abs(path) { + return strings.clone(string(path), allocator), true + } + + return path, false +} + +join :: proc(elems: []string, allocator := context.allocator) -> (joined: string, err: runtime.Allocator_Error) #optional_allocator_error { + for e, i in elems { + if e != "" { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) + p := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) or_return + return clean(p, allocator) + } + } + return "", nil +} diff --git a/core/path/filepath/walk.odin b/core/path/filepath/walk.odin index 51dfa71d2..53b10eed7 100644 --- a/core/path/filepath/walk.odin +++ b/core/path/filepath/walk.odin @@ -1,3 +1,4 @@ +#+build !wasi package filepath import "core:os" diff --git a/core/sync/futex_linux.odin b/core/sync/futex_linux.odin index 52143880b..2f4b5af72 100644 --- a/core/sync/futex_linux.odin +++ b/core/sync/futex_linux.odin @@ -49,9 +49,6 @@ _futex_signal :: proc "contextless" (futex: ^Futex) { } _futex_broadcast :: proc "contextless" (futex: ^Futex) { - // NOTE(flysand): This code was kinda funny and I don't want to remove it, but here I will - // record history of what has been in here before - // FUTEX_WAKE_PRIVATE | FUTEX_WAKE _, errno := linux.futex(cast(^linux.Futex) futex, linux.FUTEX_WAKE, {.PRIVATE}, max(i32)) #partial switch errno { case .NONE: diff --git a/core/sync/futex_windows.odin b/core/sync/futex_windows.odin index bb9686a1a..927e6781e 100644 --- a/core/sync/futex_windows.odin +++ b/core/sync/futex_windows.odin @@ -26,7 +26,7 @@ foreign Ntdll { BUT requires taking the return value of it and if it is non-zero converting that status to a DOS error and then SetLastError If this is not done, then things don't work as expected when - and error occurs + an error occurs GODDAMN MICROSOFT! */ @@ -46,7 +46,7 @@ _futex_wait :: proc "contextless" (f: ^Futex, expect: u32) -> bool { _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expect: u32, duration: time.Duration) -> bool { expect := expect - // NOTE(bill): for some bizarre reason, this has be a negative number + // NOTE(bill): for some bizarre reason, this has to be a negative number timeout := -i64(duration / 100) return CustomWaitOnAddress(f, &expect, size_of(expect), &timeout) } diff --git a/core/sys/darwin/Foundation/NSBlock.odin b/core/sys/darwin/Foundation/NSBlock.odin index b9d94bfee..1ef5e8a9b 100644 --- a/core/sys/darwin/Foundation/NSBlock.odin +++ b/core/sys/darwin/Foundation/NSBlock.odin @@ -25,6 +25,10 @@ Block_createLocalWithParam :: proc (user_data: rawptr, user_proc: proc "c" (user b, _ := Block_createInternalWithParam(false, user_data, user_proc, {}) return b } +@(objc_type=Block, objc_name="invoke") +Block_invoke :: proc "c" (self: ^Block, args: ..any) -> ^Object { + return msgSend(^Object, self, "invoke:", ..args) +} @(private) Internal_Block_Literal_Base :: struct { diff --git a/core/sys/darwin/Foundation/NSData.odin b/core/sys/darwin/Foundation/NSData.odin index 04c1ce25d..8baaf3486 100644 --- a/core/sys/darwin/Foundation/NSData.odin +++ b/core/sys/darwin/Foundation/NSData.odin @@ -13,6 +13,23 @@ Data_init :: proc "c" (self: ^Data) -> ^Data { return msgSend(^Data, self, "init") } +@(objc_type=Data, objc_name="initWithBytes") +Data_initWithBytes :: proc "c" (self: ^Data, bytes: []byte) -> ^Data { + return msgSend(^Data, self, "initWithBytes:length:", raw_data(bytes), len(bytes)) +} + +@(objc_type=Data, objc_name="initWithBytesNoCopy") +Data_initWithBytesNoCopy :: proc "c" (self: ^Data, bytes: []byte, freeWhenDone: BOOL) -> ^Data { + return msgSend( + ^Data, + self, + "initWithBytesNoCopy:length:freeWhenDone:", + raw_data(bytes), + len(bytes), + freeWhenDone, + ) +} + @(objc_type=Data, objc_name="mutableBytes") Data_mutableBytes :: proc "c" (self: ^Data) -> rawptr { return msgSend(rawptr, self, "mutableBytes") diff --git a/core/sys/darwin/Foundation/NSDate.odin b/core/sys/darwin/Foundation/NSDate.odin index 41efb0cf5..4ba539aa4 100644 --- a/core/sys/darwin/Foundation/NSDate.odin +++ b/core/sys/darwin/Foundation/NSDate.odin @@ -18,6 +18,11 @@ Date_dateWithTimeIntervalSinceNow :: proc "c" (secs: TimeInterval) -> ^Date { return msgSend(^Date, Date, "dateWithTimeIntervalSinceNow:", secs) } +@(objc_type=Date, objc_name="timeIntervalSince1970") +Date_timeIntervalSince1970 :: proc "c" (self: ^Date) -> f64 { + return msgSend(f64, self, "timeIntervalSince1970") +} + @(objc_type=Date, objc_name="distantFuture", objc_is_class_method=true) Date_distantFuture :: proc "c" () -> ^Date { return msgSend(^Date, Date, "distantFuture") diff --git a/core/sys/darwin/Foundation/NSMenu.odin b/core/sys/darwin/Foundation/NSMenu.odin index e49162a7f..9a74151b0 100644 --- a/core/sys/darwin/Foundation/NSMenu.odin +++ b/core/sys/darwin/Foundation/NSMenu.odin @@ -30,6 +30,7 @@ MenuItem :: struct {using _: Object} MenuItem_alloc :: proc "c" () -> ^MenuItem { return msgSend(^MenuItem, MenuItem, "alloc") } + @(objc_type=MenuItem, objc_name="registerActionCallback", objc_is_class_method=true) MenuItem_registerActionCallback :: proc "c" (name: cstring, callback: MenuItemCallback) -> SEL { s := string(name) @@ -50,11 +51,21 @@ MenuItem_registerActionCallback :: proc "c" (name: cstring, callback: MenuItemCa return sel } +@(objc_type=MenuItem, objc_name="separatorItem", objc_is_class_method=true) +MenuItem_separatorItem :: proc "c" () -> ^MenuItem { + return msgSend(^MenuItem, MenuItem, "separatorItem") +} + @(objc_type=MenuItem, objc_name="init") MenuItem_init :: proc "c" (self: ^MenuItem) -> ^MenuItem { return msgSend(^MenuItem, self, "init") } +@(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) @@ -75,6 +86,11 @@ 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) +} + @(objc_class="NSMenu") diff --git a/core/sys/darwin/Foundation/NSSavePanel.odin b/core/sys/darwin/Foundation/NSSavePanel.odin index 8e4d7a07b..2f89696ee 100644 --- a/core/sys/darwin/Foundation/NSSavePanel.odin +++ b/core/sys/darwin/Foundation/NSSavePanel.odin @@ -7,3 +7,13 @@ SavePanel :: struct{ using _: Panel } SavePanel_runModal :: proc "c" (self: ^SavePanel) -> ModalResponse { return msgSend(ModalResponse, self, "runModal") } + +@(objc_type=SavePanel, objc_name="savePanel", objc_is_class_method=true) +SavePanel_savePanel :: proc "c" () -> ^SavePanel { + return msgSend(^SavePanel, SavePanel, "savePanel") +} + +@(objc_type=SavePanel, objc_name="URL") +SavePanel_URL :: proc "c" (self: ^SavePanel) -> ^Array { + return msgSend(^Array, self, "URL") +} \ No newline at end of file diff --git a/core/sys/darwin/Foundation/NSToolbar.odin b/core/sys/darwin/Foundation/NSToolbar.odin new file mode 100644 index 000000000..be6613df4 --- /dev/null +++ b/core/sys/darwin/Foundation/NSToolbar.odin @@ -0,0 +1,14 @@ +package objc_Foundation +@(objc_class = "NSToolbar") + +Toolbar :: struct { using _: Object } + +@(objc_type = Toolbar, objc_name = "alloc", objc_is_class_method = true) +Toolbar_alloc :: proc "c" () -> ^Toolbar { + return msgSend(^Toolbar, Toolbar, "alloc") +} + +@(objc_type = Toolbar, objc_name = "init") +Toolbar_init :: proc "c" (self: ^Toolbar) -> ^Toolbar { + return msgSend(^Toolbar, self, "init") +} diff --git a/core/sys/darwin/Foundation/NSURL.odin b/core/sys/darwin/Foundation/NSURL.odin index 9e9081219..fb9ebca9e 100644 --- a/core/sys/darwin/Foundation/NSURL.odin +++ b/core/sys/darwin/Foundation/NSURL.odin @@ -28,3 +28,8 @@ URL_initFileURLWithPath :: proc "c" (self: ^URL, path: ^String) -> ^URL { URL_fileSystemRepresentation :: proc "c" (self: ^URL) -> cstring { return msgSend(cstring, self, "fileSystemRepresentation") } + +@(objc_type=URL, objc_name="relativePath") +URL_relativePath :: proc "c" (self: ^URL) -> ^String { + return msgSend(^String, self, "relativePath") +} diff --git a/core/sys/darwin/Foundation/NSURLRequest.odin b/core/sys/darwin/Foundation/NSURLRequest.odin new file mode 100644 index 000000000..6b2819c67 --- /dev/null +++ b/core/sys/darwin/Foundation/NSURLRequest.odin @@ -0,0 +1,24 @@ +package objc_Foundation + +@(objc_class = "URLRequest") +URLRequest :: struct { using _: Object } + +@(objc_type = URLRequest, objc_name = "alloc", objc_is_class_method = true) +URLRequest_alloc :: proc "c" () -> ^URLRequest { + return msgSend(^URLRequest, URLRequest, "alloc") +} + +@(objc_type = URLRequest, objc_name = "requestWithURL", objc_is_class_method = true) +URLRequest_requestWithURL :: proc "c" (url: ^URL) -> ^URLRequest { + return msgSend(^URLRequest, URLRequest, "requestWithURL:", url) +} + +@(objc_type = URLRequest, objc_name = "init") +URLRequest_init :: proc "c" (self: ^URLRequest) -> ^URLRequest { + return msgSend(^URLRequest, URLRequest, "init") +} + +@(objc_type = URLRequest, objc_name = "url") +URLRequest_url :: proc "c" (self: ^URLRequest) -> ^URL { + return msgSend(^URL, self, "URL") +} \ No newline at end of file diff --git a/core/sys/darwin/Foundation/NSURLResponse.odin b/core/sys/darwin/Foundation/NSURLResponse.odin new file mode 100644 index 000000000..6295817e8 --- /dev/null +++ b/core/sys/darwin/Foundation/NSURLResponse.odin @@ -0,0 +1,19 @@ +package objc_Foundation + +@(objc_class = "NSURLResponse") +URLResponse :: struct { using _: Object } + +@(objc_type = URLResponse, objc_name = "alloc", objc_is_class_method = true) +URLResponse_alloc :: proc "c" () -> ^URLResponse { + return msgSend(^URLResponse, URLResponse, "alloc") +} + +@(objc_type = URLResponse, objc_name = "init") +URLResponse_init :: proc "c" (self: ^URLResponse) -> ^URLResponse { + return msgSend(^URLResponse, URLResponse, "init") +} + +@(objc_type = URLResponse, objc_name = "initWithURL") +URLResponse_initWithURL :: proc "c" (self: ^URLResponse, url: ^URL, mime_type: ^String, length: int, encoding: ^String ) -> ^URLResponse { + return msgSend(^URLResponse, self, "initWithURL:MIMEType:expectedContentLength:textEncodingName:", url, mime_type, Integer(length), encoding) +} \ No newline at end of file diff --git a/core/sys/darwin/Foundation/NSWindow.odin b/core/sys/darwin/Foundation/NSWindow.odin index 0fe334207..57ac2b6f6 100644 --- a/core/sys/darwin/Foundation/NSWindow.odin +++ b/core/sys/darwin/Foundation/NSWindow.odin @@ -129,6 +129,10 @@ WindowDelegateTemplate :: struct { windowDidExitVersionBrowser: proc(notification: ^Notification), } +Window_Title_Visibility :: enum UInteger { + Visible, + Hidden, +} WindowDelegate :: struct { using _: Object } // This is not the same as NSWindowDelegate _WindowDelegateInternal :: struct { @@ -616,6 +620,10 @@ View_setWantsLayer :: proc "c" (self: ^View, wantsLayer: BOOL) { View_convertPointFromView :: proc "c" (self: ^View, point: Point, view: ^View) -> Point { return msgSend(Point, self, "convertPoint:fromView:", point, view) } +@(objc_type=View, objc_name="addSubview") +View_addSubview :: proc "c" (self: ^View, view: ^View) { + msgSend(nil, self, "addSubview:", view) +} @(objc_class="NSWindow") Window :: struct {using _: Responder} @@ -748,4 +756,28 @@ Window_hasTitleBar :: proc "c" (self: ^Window) -> BOOL { @(objc_type=Window, objc_name="orderedIndex") Window_orderedIndex :: proc "c" (self: ^Window) -> Integer { return msgSend(Integer, self, "orderedIndex") +} +@(objc_type=Window, objc_name="setMinSize") +Window_setMinSize :: proc "c" (self: ^Window, size: Size) { + msgSend(nil, self, "setMinSize:", size) +} +@(objc_type=Window, objc_name="setTitleVisibility") +Window_setTitleVisibility :: proc "c" (self: ^Window, visibility: Window_Title_Visibility) { + msgSend(nil, self, "setTitleVisibility:", visibility) +} +@(objc_type=Window, objc_name="performZoom") +Window_performZoom :: proc "c" (self: ^Window) { + msgSend(nil, self, "performZoom:", self) +} +@(objc_type=Window, objc_name="setFrameAutosaveName") +NSWindow_setFrameAutosaveName :: proc "c" (self: ^Window, name: ^String) { + msgSend(nil, self, "setFrameAutosaveName:", name) +} +@(objc_type=Window, objc_name="performWindowDragWithEvent") +Window_performWindowDragWithEvent :: proc "c" (self: ^Window, event: ^Event) { + msgSend(nil, self, "performWindowDragWithEvent:", event) +} +@(objc_type=Window, objc_name="setToolbar") +Window_setToolbar :: proc "c" (self: ^Window, toolbar: ^Toolbar) { + msgSend(nil, self, "setToolbar:", toolbar) } \ No newline at end of file diff --git a/core/sys/linux/wrappers.odin b/core/sys/linux/wrappers.odin index 4f6118c80..ab1992a57 100644 --- a/core/sys/linux/wrappers.odin +++ b/core/sys/linux/wrappers.odin @@ -86,22 +86,18 @@ dirent_iterate_buf :: proc "contextless" (buf: []u8, offs: ^int) -> (d: ^Dirent, /// The lifetime of the string is bound to the lifetime of the provided dirent structure dirent_name :: proc "contextless" (dirent: ^Dirent) -> string #no_bounds_check { str := ([^]u8)(&dirent.name) - // Note(flysand): The string size calculated above applies only to the ideal case - // we subtract 1 byte from the string size, because a null terminator is guaranteed - // to be present. But! That said, the dirents are aligned to 8 bytes and the padding - // between the null terminator and the start of the next struct may be not initialized - // which means we also have to scan these garbage bytes. - str_size := int(dirent.reclen) - 1 - cast(int)offset_of(Dirent, name) - // This skips *only* over the garbage, since if we're not garbage we're at nul terminator, - // which skips this loop - for str[str_size] != 0 { - str_size -= 1 + // Dirents are aligned to 8 bytes, so there is guaranteed to be a null + // terminator in the last 8 bytes. + str_size := int(dirent.reclen) - cast(int)offset_of(Dirent, name) + + trunc := min(str_size, 8) + str_size -= trunc + for _ in 0.. u64 { return u64(id) | (u64(op) << 8) | (u64(res) << 16) -} \ No newline at end of file +} diff --git a/core/sys/windows/dnsapi.odin b/core/sys/windows/dnsapi.odin index 4fd9f7a19..728813696 100644 --- a/core/sys/windows/dnsapi.odin +++ b/core/sys/windows/dnsapi.odin @@ -5,6 +5,6 @@ foreign import "system:Dnsapi.lib" @(default_calling_convention="system") foreign Dnsapi { - DnsQuery_UTF8 :: proc(name: cstring, type: u16, options: DWORD, extra: PVOID, results: ^^DNS_RECORD, reserved: PVOID) -> DNS_STATUS --- + DnsQuery_UTF8 :: proc(name: cstring, type: u16, options: DNS_QUERY_OPTIONS, extra: PVOID, results: ^^DNS_RECORD, reserved: PVOID) -> DNS_STATUS --- DnsRecordListFree :: proc(list: ^DNS_RECORD, options: DWORD) --- } diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index f1d7202da..266dcdbf4 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -123,6 +123,7 @@ foreign kernel32 { WaitCommEvent :: proc(handle: HANDLE, lpEvtMask: LPDWORD, lpOverlapped: LPOVERLAPPED) -> BOOL --- GetCommandLineW :: proc() -> LPCWSTR --- GetTempPathW :: proc(nBufferLength: DWORD, lpBuffer: LPCWSTR) -> DWORD --- + GetTempFileNameW :: proc(lpPathName: LPCWSTR, lpPrefixString: LPCWSTR, uUnique: c_int, lpTempFileName: LPWSTR) -> c_uint --- GetCurrentProcess :: proc() -> HANDLE --- GetCurrentProcessId :: proc() -> DWORD --- GetCurrentThread :: proc() -> HANDLE --- @@ -1240,3 +1241,31 @@ GHND :: (GMEM_MOVEABLE | GMEM_ZEROINIT) GPTR :: (GMEM_FIXED | GMEM_ZEROINIT) LPTOP_LEVEL_EXCEPTION_FILTER :: PVECTORED_EXCEPTION_HANDLER + +ACTCTXW :: struct { + Size: ULONG, + Flags: DWORD, + Source: LPCWSTR, + ProcessorArchitecture: USHORT, + LangId: LANGID, + AssemblyDirectory: LPCWSTR, + ResourceName: LPCWSTR, + ApplicationName: LPCWSTR, + Module: HMODULE, +} +PACTCTXW :: ^ACTCTXW +PCACTCTXW :: ^ACTCTXW + +ACTCTX_FLAG_PROCESSOR_ARCHITECTURE_VALID :: 0x001 +ACTCTX_FLAG_LANGID_VALID :: 0x002 +ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID :: 0x004 +ACTCTX_FLAG_RESOURCE_NAME_VALID :: 0x008 +ACTCTX_FLAG_SET_PROCESS_DEFAULT :: 0x010 +ACTCTX_FLAG_APPLICATION_NAME_VALID :: 0x020 +ACTCTX_FLAG_HMODULE_VALID :: 0x080 + +@(default_calling_convention="system") +foreign kernel32 { + CreateActCtxW :: proc(pActCtx: ^ACTCTXW) -> HANDLE --- + ActivateActCtx :: proc(hActCtx: HANDLE, lpCookie: ^ULONG_PTR) -> BOOL --- +} diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index ab79c682a..8069659c9 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -4576,6 +4576,31 @@ DNS_SRV_DATAA :: struct { _: WORD, // padding } +// See https://learn.microsoft.com/en-us/windows/win32/dns/dns-constants +DNS_QUERY_OPTION :: enum DWORD { + ACCEPT_TRUNCATED_RESPONSE = 0, + DNS_QUERY_USE_TCP_ONLY = 1, + NO_RECURSION = 2, + BYPASS_CACHE = 3, + NO_WIRE_QUERY = 4, + NO_LOCAL_NAME = 5, + NO_HOSTS_FILE = 6, + NO_NETBT = 7, + WIRE_ONLY = 8, + RETURN_MESSAGE = 9, + MULTICAST_ONLY = 10, + NO_MULTICAST = 11, + TREAT_AS_FQDN = 12, + ADDRCONFIG = 13, + DUAL_ADDR = 14, + MULTICAST_WAIT = 17, + MULTICAST_VERIFY = 18, + DONT_RESET_TTL_VALUES = 20, + DISABLE_IDN_ENCODING = 21, + APPEND_MULTILABEL = 23, +} +DNS_QUERY_OPTIONS :: bit_set[DNS_QUERY_OPTION; DWORD] + SOCKADDR :: struct { sa_family: ADDRESS_FAMILY, sa_data: [14]CHAR, diff --git a/core/text/match/strlib.odin b/core/text/match/strlib.odin index bfa696dcd..819f464c5 100644 --- a/core/text/match/strlib.odin +++ b/core/text/match/strlib.odin @@ -682,11 +682,14 @@ find_aux :: proc( // iterative matching which returns the 0th/1st match // rest has to be used from captures +// assumes captures is zeroed on first iteration +// resets captures to zero on last iteration gmatch :: proc( haystack: ^string, pattern: string, captures: ^[MAX_CAPTURES]Match, ) -> (res: string, ok: bool) { + haystack^ = haystack[captures[0].byte_end:] if len(haystack) > 0 { length, err := find_aux(haystack^, pattern, 0, false, captures) @@ -695,10 +698,11 @@ gmatch :: proc( first := length > 1 ? 1 : 0 cap := captures[first] res = haystack[cap.byte_start:cap.byte_end] - haystack^ = haystack[cap.byte_end:] } } - + if !ok { + captures^ = {} + } return } @@ -794,11 +798,14 @@ gsub_with :: proc( gsub :: proc { gsub_builder, gsub_allocator } // iterative find with zeroth capture only +// assumes captures is zeroed on first iteration +// resets captures to zero on last iteration gfind :: proc( haystack: ^string, pattern: string, captures: ^[MAX_CAPTURES]Match, ) -> (res: string, ok: bool) { + haystack^ = haystack[captures[0].byte_end:] if len(haystack) > 0 { length, err := find_aux(haystack^, pattern, 0, true, captures) @@ -806,10 +813,11 @@ gfind :: proc( ok = true cap := captures[0] res = haystack[cap.byte_start:cap.byte_end] - haystack^ = haystack[cap.byte_end:] } } - + if !ok { + captures^ = {} + } return } diff --git a/core/time/timezone/tzdate.odin b/core/time/timezone/tzdate.odin index 96df44299..e62400889 100644 --- a/core/time/timezone/tzdate.odin +++ b/core/time/timezone/tzdate.odin @@ -168,7 +168,7 @@ process_rrule :: proc(rrule: datetime.TZ_RRule, tm: time.Time) -> (out: datetime }, } record_sort_proc :: proc(i, j: datetime.TZ_Record) -> bool { - return i.time > j.time + return i.time < j.time } slice.sort_by(records, record_sort_proc) @@ -179,7 +179,7 @@ process_rrule :: proc(rrule: datetime.TZ_RRule, tm: time.Time) -> (out: datetime } } - return records[len(records)-1], true + return records[0], true } datetime_to_utc :: proc(dt: datetime.DateTime) -> (out: datetime.DateTime, success: bool) #optional_ok { diff --git a/src/big_int.cpp b/src/big_int.cpp index 8e476f090..0b0a9a400 100644 --- a/src/big_int.cpp +++ b/src/big_int.cpp @@ -251,7 +251,10 @@ gb_internal void big_int_from_string(BigInt *dst, String const &s, bool *success exp *= 10; exp += v; } - big_int_exp_u64(dst, &b, exp, success); + BigInt tmp = {}; + mp_init(&tmp); + big_int_exp_u64(&tmp, &b, exp, success); + big_int_mul_eq(dst, &tmp); } if (is_negative) { diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 93168cf77..08df34c57 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -472,6 +472,7 @@ struct BuildContext { bool ignore_microsoft_magic; bool linker_map_file; + bool use_single_module; bool use_separate_modules; bool module_per_file; bool cached; @@ -1719,13 +1720,15 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta bc->optimization_level = gb_clamp(bc->optimization_level, -1, 3); -#if defined(GB_SYSTEM_WINDOWS) if (bc->optimization_level <= 0) { if (!is_arch_wasm()) { bc->use_separate_modules = true; } } -#endif + + if (build_context.use_single_module) { + bc->use_separate_modules = false; + } // TODO: Static map calls are bugged on `amd64sysv` abi. @@ -2124,6 +2127,7 @@ gb_internal bool init_build_paths(String init_filename) { } } + bool no_crt_checks_failed = false; if (build_context.no_crt && !build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR && !build_context.ODIN_DEFAULT_TO_PANIC_ALLOCATOR) { switch (build_context.metrics.os) { case TargetOs_linux: @@ -2133,11 +2137,29 @@ gb_internal bool init_build_paths(String init_filename) { case TargetOs_openbsd: case TargetOs_netbsd: case TargetOs_haiku: - gb_printf_err("-no-crt on unix systems requires either -default-to-nil-allocator or -default-to-panic-allocator to also be present because the default allocator requires crt\n"); - return false; + gb_printf_err("-no-crt on Unix systems requires either -default-to-nil-allocator or -default-to-panic-allocator to also be present, because the default allocator requires CRT\n"); + no_crt_checks_failed = true; } } + if (build_context.no_crt && !build_context.no_thread_local) { + switch (build_context.metrics.os) { + case TargetOs_linux: + case TargetOs_darwin: + case TargetOs_essence: + case TargetOs_freebsd: + case TargetOs_openbsd: + case TargetOs_netbsd: + case TargetOs_haiku: + gb_printf_err("-no-crt on Unix systems requires the -no-thread-local flag to also be present, because the TLS is inaccessible without CRT\n"); + no_crt_checks_failed = true; + } + } + + if (no_crt_checks_failed) { + return false; + } + return true; } diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 1d792dad8..bf6e39bd2 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -60,7 +60,7 @@ gb_internal Type *check_init_variable(CheckerContext *ctx, Entity *e, Operand *o error(operand->expr, "Cannot assign a type '%s' to variable '%.*s'", t, LIT(e->token.string)); } if (e->type == nullptr) { - error_line("\tThe type of the variable '%.*s' cannot be inferred as a type does not have a default type\n", LIT(e->token.string)); + error_line("\tThe type of the variable '%.*s' cannot be inferred as a type and does not have a default type\n", LIT(e->token.string)); } e->type = operand->type; return nullptr; diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 9ce2a6ce5..6a99fe171 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -1044,7 +1044,7 @@ gb_internal AstPackage *get_package_of_type(Type *type) { } -// NOTE(bill): 'content_name' is for debugging and error messages +// NOTE(bill): 'context_name' is for debugging and error messages gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *type, String context_name) { check_not_tuple(c, operand); if (operand->mode == Addressing_Invalid) { @@ -1973,10 +1973,10 @@ gb_internal bool check_binary_op(CheckerContext *c, Operand *o, Token op) { case Token_Quo: case Token_QuoEq: if (is_type_matrix(main_type)) { - error(op, "Operator '%.*s' is only allowed with matrix types", LIT(op.string)); + error(op, "Operator '%.*s' is not allowed with matrix types", LIT(op.string)); return false; } else if (is_type_simd_vector(main_type) && is_type_integer(type)) { - error(op, "Operator '%.*s' is only allowed with #simd types with integer elements", LIT(op.string)); + error(op, "Operator '%.*s' is not allowed with #simd types with integer elements", LIT(op.string)); return false; } /*fallthrough*/ @@ -2023,14 +2023,14 @@ gb_internal bool check_binary_op(CheckerContext *c, Operand *o, Token op) { case Token_ModEq: case Token_ModModEq: if (is_type_matrix(main_type)) { - error(op, "Operator '%.*s' is only allowed with matrix types", LIT(op.string)); + error(op, "Operator '%.*s' is not allowed with matrix types", LIT(op.string)); return false; } if (!is_type_integer(type)) { error(op, "Operator '%.*s' is only allowed with integers", LIT(op.string)); return false; } else if (is_type_simd_vector(main_type)) { - error(op, "Operator '%.*s' is only allowed with #simd types with integer elements", LIT(op.string)); + error(op, "Operator '%.*s' is not allowed with #simd types with integer elements", LIT(op.string)); return false; } break; diff --git a/src/check_type.cpp b/src/check_type.cpp index 44108ccbe..4d9101c6c 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -685,7 +685,8 @@ gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast * ST_ALIGN(min_field_align); ST_ALIGN(max_field_align); ST_ALIGN(align); - if (struct_type->Struct.custom_align < struct_type->Struct.custom_min_field_align) { + if (struct_type->Struct.custom_align != 0 && + struct_type->Struct.custom_align < struct_type->Struct.custom_min_field_align) { error(st->align, "#align(%lld) is defined to be less than #min_field_align(%lld)", cast(long long)struct_type->Struct.custom_align, cast(long long)struct_type->Struct.custom_min_field_align); diff --git a/src/checker.cpp b/src/checker.cpp index 5d3263789..baa1e0d2b 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -749,9 +749,15 @@ gb_internal void check_scope_usage_internal(Checker *c, Scope *scope, u64 vet_fl // TODO(bill): When is a good size warn? // Is >256 KiB good enough? if (sz > 1ll<<18) { - gbString type_str = type_to_string(e->type); - warning(e->token, "Declaration of '%.*s' may cause a stack overflow due to its type '%s' having a size of %lld bytes", LIT(e->token.string), type_str, cast(long long)sz); - gb_string_free(type_str); + bool is_ref = false; + if((e->flags & EntityFlag_ForValue) != 0) { + is_ref = type_deref(e->Variable.for_loop_parent_type) != NULL; + } + if(!is_ref) { + gbString type_str = type_to_string(e->type); + warning(e->token, "Declaration of '%.*s' may cause a stack overflow due to its type '%s' having a size of %lld bytes", LIT(e->token.string), type_str, cast(long long)sz); + gb_string_free(type_str); + } } } } @@ -5034,6 +5040,12 @@ gb_internal DECL_ATTRIBUTE_PROC(foreign_import_decl_attribute) { ac->extra_linker_flags = ev.value_string; } return true; + } else if (name == "ignore_duplicates") { + if (value != nullptr) { + error(elem, "Expected no parameter for '%.*s'", LIT(name)); + } + ac->ignore_duplicates = true; + return true; } return false; } @@ -5184,6 +5196,9 @@ gb_internal void check_add_foreign_import_decl(CheckerContext *ctx, Ast *decl) { if (ac.foreign_import_priority_index != 0) { e->LibraryName.priority_index = ac.foreign_import_priority_index; } + if (ac.ignore_duplicates) { + e->LibraryName.ignore_duplicates = true; + } String extra_linker_flags = string_trim_whitespace(ac.extra_linker_flags); if (extra_linker_flags.len != 0) { e->LibraryName.extra_linker_flags = extra_linker_flags; diff --git a/src/checker.hpp b/src/checker.hpp index 3951fcefe..4634047c0 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -140,6 +140,7 @@ struct AttributeContext { bool instrumentation_enter : 1; bool instrumentation_exit : 1; bool rodata : 1; + bool ignore_duplicates : 1; u32 optimization_mode; // ProcedureOptimizationMode i64 foreign_import_priority_index; String extra_linker_flags; diff --git a/src/entity.cpp b/src/entity.cpp index 802b381f9..d137a8674 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -274,6 +274,7 @@ struct Entity { Slice paths; String name; i64 priority_index; + bool ignore_duplicates; String extra_linker_flags; } LibraryName; i32 Nil; diff --git a/src/linker.cpp b/src/linker.cpp index 261d6e7a4..cf2ef638d 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -449,6 +449,26 @@ gb_internal i32 linker_stage(LinkerData *gen) { if (extra_linker_flags.len != 0) { lib_str = gb_string_append_fmt(lib_str, " %.*s", LIT(extra_linker_flags)); } + + if (build_context.metrics.os == TargetOs_darwin) { + // Print frameworks first + for (String lib : e->LibraryName.paths) { + lib = string_trim_whitespace(lib); + if (lib.len == 0) { + continue; + } + if (string_ends_with(lib, str_lit(".framework"))) { + if (string_set_update(&min_libs_set, lib)) { + continue; + } + + String lib_name = lib; + lib_name = remove_extension_from_path(lib_name); + lib_str = gb_string_append_fmt(lib_str, " -framework %.*s ", LIT(lib_name)); + } + } + } + for (String lib : e->LibraryName.paths) { lib = string_trim_whitespace(lib); if (lib.len == 0) { @@ -536,7 +556,18 @@ gb_internal i32 linker_stage(LinkerData *gen) { } array_add(&gen->output_object_paths, obj_file); } else { - if (string_set_update(&min_libs_set, lib) && build_context.min_link_libs) { + bool short_circuit = false; + if (string_ends_with(lib, str_lit(".framework"))) { + short_circuit = true; + } else if (string_ends_with(lib, str_lit(".dylib"))) { + short_circuit = true; + } else if (string_ends_with(lib, str_lit(".so"))) { + short_circuit = true; + } else if (e->LibraryName.ignore_duplicates) { + short_circuit = true; + } + + if (string_set_update(&min_libs_set, lib) && (build_context.min_link_libs || short_circuit)) { continue; } diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 696ced0df..29fa67f3f 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1147,14 +1147,14 @@ gb_internal void lb_finalize_objc_names(lbProcedure *p) { String name = entry.key; args[0] = lb_const_value(m, t_cstring, exact_value_string(name)); lbValue ptr = lb_emit_runtime_call(p, "objc_lookUpClass", args); - lb_addr_store(p, entry.value, ptr); + lb_addr_store(p, entry.value.local_module_addr, ptr); } for (auto const &entry : m->objc_selectors) { String name = entry.key; args[0] = lb_const_value(m, t_cstring, exact_value_string(name)); lbValue ptr = lb_emit_runtime_call(p, "sel_registerName", args); - lb_addr_store(p, entry.value, ptr); + lb_addr_store(p, entry.value.local_module_addr, ptr); } lb_end_procedure_body(p); diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 42d283a1e..a0775ac3b 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -143,6 +143,11 @@ struct lbPadType { LLVMTypeRef type; }; +struct lbObjcRef { + Entity * entity; + lbAddr local_module_addr; +}; + struct lbModule { LLVMModuleRef mod; LLVMContextRef ctx; @@ -196,8 +201,8 @@ struct lbModule { RecursiveMutex debug_values_mutex; PtrMap debug_values; - StringMap objc_classes; - StringMap objc_selectors; + StringMap objc_classes; + StringMap objc_selectors; PtrMap map_cell_info_map; // address of runtime.Map_Info PtrMap map_info_map; // address of runtime.Map_Cell_Info diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index df9dca801..871536927 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -4294,6 +4294,17 @@ gb_internal lbAddr lb_build_addr_index_expr(lbProcedure *p, Ast *expr) { gb_internal lbAddr lb_build_addr_slice_expr(lbProcedure *p, Ast *expr) { ast_node(se, SliceExpr, expr); + lbAddr addr = lb_build_addr(p, se->expr); + lbValue base = lb_addr_load(p, addr); + Type *type = base_type(base.type); + + if (is_type_pointer(type)) { + type = base_type(type_deref(type)); + addr = lb_addr(base); + base = lb_addr_load(p, addr); + } + + lbValue low = lb_const_int(p->module, t_int, 0); lbValue high = {}; @@ -4306,16 +4317,6 @@ gb_internal lbAddr lb_build_addr_slice_expr(lbProcedure *p, Ast *expr) { bool no_indices = se->low == nullptr && se->high == nullptr; - lbAddr addr = lb_build_addr(p, se->expr); - lbValue base = lb_addr_load(p, addr); - Type *type = base_type(base.type); - - if (is_type_pointer(type)) { - type = base_type(type_deref(type)); - addr = lb_addr(base); - base = lb_addr_load(p, addr); - } - switch (type->kind) { case Type_Slice: { Type *slice_type = type; diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 7b7c9d6e9..8910bd67a 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2093,23 +2093,34 @@ gb_internal void lb_set_wasm_export_attributes(LLVMValueRef value, String export gb_internal lbAddr lb_handle_objc_find_or_register_selector(lbProcedure *p, String const &name) { - lbAddr *found = string_map_get(&p->module->objc_selectors, name); + lbObjcRef *found = string_map_get(&p->module->objc_selectors, name); if (found) { - return *found; - } else { - lbModule *default_module = &p->module->gen->default_module; - Entity *e = nullptr; - lbAddr default_addr = lb_add_global_generated(default_module, t_objc_SEL, {}, &e); - - lbValue ptr = lb_find_value_from_entity(p->module, e); - lbAddr local_addr = lb_addr(ptr); - - string_map_set(&default_module->objc_selectors, name, default_addr); - if (default_module != p->module) { - string_map_set(&p->module->objc_selectors, name, local_addr); - } - return local_addr; + return found->local_module_addr; } + + lbModule *default_module = &p->module->gen->default_module; + Entity *entity = {}; + + if (default_module != p->module) { + found = string_map_get(&default_module->objc_selectors, name); + if (found) { + entity = found->entity; + } + } + + if (!entity) { + lbAddr default_addr = lb_add_global_generated(default_module, t_objc_SEL, {}, &entity); + string_map_set(&default_module->objc_selectors, name, lbObjcRef{entity, default_addr}); + } + + lbValue ptr = lb_find_value_from_entity(p->module, entity); + lbAddr local_addr = lb_addr(ptr); + + if (default_module != p->module) { + string_map_set(&p->module->objc_selectors, name, lbObjcRef{entity, local_addr}); + } + + return local_addr; } gb_internal lbValue lb_handle_objc_find_selector(lbProcedure *p, Ast *expr) { @@ -2139,23 +2150,34 @@ gb_internal lbValue lb_handle_objc_register_selector(lbProcedure *p, Ast *expr) } gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String const &name) { - lbAddr *found = string_map_get(&p->module->objc_classes, name); + lbObjcRef *found = string_map_get(&p->module->objc_classes, name); if (found) { - return *found; - } else { - lbModule *default_module = &p->module->gen->default_module; - Entity *e = nullptr; - lbAddr default_addr = lb_add_global_generated(default_module, t_objc_SEL, {}, &e); - - lbValue ptr = lb_find_value_from_entity(p->module, e); - lbAddr local_addr = lb_addr(ptr); - - string_map_set(&default_module->objc_classes, name, default_addr); - if (default_module != p->module) { - string_map_set(&p->module->objc_classes, name, local_addr); - } - return local_addr; + return found->local_module_addr; } + + lbModule *default_module = &p->module->gen->default_module; + Entity *entity = {}; + + if (default_module != p->module) { + found = string_map_get(&default_module->objc_classes, name); + if (found) { + entity = found->entity; + } + } + + if (!entity) { + lbAddr default_addr = lb_add_global_generated(default_module, t_objc_Class, {}, &entity); + string_map_set(&default_module->objc_classes, name, lbObjcRef{entity, default_addr}); + } + + lbValue ptr = lb_find_value_from_entity(p->module, entity); + lbAddr local_addr = lb_addr(ptr); + + if (default_module != p->module) { + string_map_set(&p->module->objc_classes, name, lbObjcRef{entity, local_addr}); + } + + return local_addr; } gb_internal lbValue lb_handle_objc_find_class(lbProcedure *p, Ast *expr) { @@ -2196,23 +2218,7 @@ gb_internal lbValue lb_handle_objc_id(lbProcedure *p, Ast *expr) { GB_ASSERT(e->kind == Entity_TypeName); String name = e->TypeName.objc_class_name; - lbAddr *found = string_map_get(&p->module->objc_classes, name); - if (found) { - return lb_addr_load(p, *found); - } else { - lbModule *default_module = &p->module->gen->default_module; - Entity *e = nullptr; - lbAddr default_addr = lb_add_global_generated(default_module, t_objc_Class, {}, &e); - - lbValue ptr = lb_find_value_from_entity(p->module, e); - lbAddr local_addr = lb_addr(ptr); - - string_map_set(&default_module->objc_classes, name, default_addr); - if (default_module != p->module) { - string_map_set(&p->module->objc_classes, name, local_addr); - } - return lb_addr_load(p, local_addr); - } + return lb_addr_load(p, lb_handle_objc_find_or_register_class(p, name)); } return lb_build_expr(p, expr); diff --git a/src/main.cpp b/src/main.cpp index 41c7170f6..e8336b292 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -331,6 +331,7 @@ enum BuildFlagKind { BuildFlag_UseRADLink, BuildFlag_Linker, BuildFlag_UseSeparateModules, + BuildFlag_UseSingleModule, BuildFlag_NoThreadedChecker, BuildFlag_ShowDebugMessages, @@ -545,6 +546,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_UseRADLink, str_lit("radlink"), BuildFlagParam_None, Command__does_build); add_flag(&build_flags, BuildFlag_Linker, str_lit("linker"), BuildFlagParam_String, Command__does_build); add_flag(&build_flags, BuildFlag_UseSeparateModules, str_lit("use-separate-modules"), BuildFlagParam_None, Command__does_build); + add_flag(&build_flags, BuildFlag_UseSingleModule, str_lit("use-single-module"), BuildFlagParam_None, Command__does_build); add_flag(&build_flags, BuildFlag_NoThreadedChecker, str_lit("no-threaded-checker"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_ShowDebugMessages, str_lit("show-debug-messages"), BuildFlagParam_None, Command_all); @@ -1240,8 +1242,19 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_UseSeparateModules: + if (build_context.use_single_module) { + gb_printf_err("-use-separate-modules cannot be used with -use-single-module\n"); + bad_flags = true; + } build_context.use_separate_modules = true; break; + case BuildFlag_UseSingleModule: + if (build_context.use_separate_modules) { + gb_printf_err("-use-single-module cannot be used with -use-separate-modules\n"); + bad_flags = true; + } + build_context.use_single_module = true; + break; case BuildFlag_NoThreadedChecker: build_context.no_threaded_checker = true; break; @@ -1801,7 +1814,10 @@ gb_internal void check_defines(BuildContext *bc, Checker *c) { if (!found) { ERROR_BLOCK(); warning(nullptr, "given -define:%.*s is unused in the project", LIT(name)); - error_line("\tSuggestion: use the -show-defineables flag for an overview of the possible defines\n"); + + if (!global_ignore_warnings()) { + error_line("\tSuggestion: use the -show-defineables flag for an overview of the possible defines\n"); + } } } } @@ -2331,6 +2347,10 @@ gb_internal void print_show_help(String const arg0, String command, String optio print_usage_line(2, "Sets the default allocator to be the nil_allocator, an allocator which does nothing."); } + if (print_flag("-default-to-panic-allocator")) { + print_usage_line(2, "Sets the default allocator to be the panic_allocator, an allocator which calls panic() on any allocation attempt."); + } + if (print_flag("-define:=")) { print_usage_line(2, "Defines a scalar boolean, integer or string as global constant."); print_usage_line(2, "Example: -define:SPAM=123"); @@ -2710,8 +2730,12 @@ gb_internal void print_show_help(String const arg0, String command, String optio if (run_or_build) { if (print_flag("-use-separate-modules")) { print_usage_line(2, "The backend generates multiple build units which are then linked together."); - print_usage_line(2, "Normally, a single build unit is generated for a standard project."); - print_usage_line(2, "This is the default behaviour on Windows for '-o:none' and '-o:minimal' builds."); + print_usage_line(2, "This is the default behaviour for '-o:none' and '-o:minimal' builds."); + print_usage_line(2, "Normally, a single build unit is generated for a standard project for '-o:speed' or '-o:size'."); + } + if (print_flag("-use-single-module")) { + print_usage_line(2, "The backend generates only a single build unit."); + print_usage_line(2, "This is the default behaviour for '-o:speed' or '-o:size'."); } } diff --git a/src/types.cpp b/src/types.cpp index 233f903a3..0b6e6d334 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -4773,7 +4773,9 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha case Type_BitSet: str = gb_string_appendc(str, "bit_set["); - if (is_type_enum(type->BitSet.elem)) { + 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); } else { str = gb_string_append_fmt(str, "%lld", type->BitSet.lower); diff --git a/tests/core/os/os2/path.odin b/tests/core/os/os2/path.odin new file mode 100644 index 000000000..b91f43368 --- /dev/null +++ b/tests/core/os/os2/path.odin @@ -0,0 +1,22 @@ +package tests_core_os_os2 + +import os "core:os/os2" +import "core:log" +import "core:path/filepath" +import "core:testing" +import "core:strings" + +@(test) +test_executable :: proc(t: ^testing.T) { + path, err := os.get_executable_path(context.allocator) + defer delete(path) + + log.infof("executable path: %q", path) + + // NOTE: some sanity checks that should always be the case, at least in the CI. + + testing.expect_value(t, err, nil) + testing.expect(t, len(path) > 0) + testing.expect(t, filepath.is_abs(path)) + testing.expectf(t, strings.contains(path, filepath.base(os.args[0])), "expected the executable path to contain the base of os.args[0] which is %q", filepath.base(os.args[0])) +} diff --git a/vendor/commonmark/cmark.odin b/vendor/commonmark/cmark.odin index 2fdf1387c..6b07f157f 100644 --- a/vendor/commonmark/cmark.odin +++ b/vendor/commonmark/cmark.odin @@ -7,7 +7,6 @@ package vendor_commonmark import "core:c" -import "core:c/libc" import "base:runtime" COMMONMARK_SHARED :: #config(COMMONMARK_SHARED, false) @@ -450,7 +449,7 @@ foreign lib { // Called `parse_from_libc_file` so as not to confuse with Odin's file handling. @(link_name = "parse_from_file") - parse_from_libc_file :: proc(file: ^libc.FILE, options: Options) -> (root: ^Node) --- + parse_from_libc_file :: proc(file: ^c.FILE, options: Options) -> (root: ^Node) --- } parser_feed_from_string :: proc "c" (parser: ^Parser, s: string) { diff --git a/vendor/directx/d3d12/d3d12.odin b/vendor/directx/d3d12/d3d12.odin index 3e078a5ed..1110289e4 100644 --- a/vendor/directx/d3d12/d3d12.odin +++ b/vendor/directx/d3d12/d3d12.odin @@ -1580,14 +1580,14 @@ SHADER_COMPONENT_MAPPING :: enum u32 { FORCE_VALUE_1 = 5, } ENCODE_SHADER_4_COMPONENT_MAPPING :: #force_inline proc "contextless" (Src0, Src1, Src2, Src3: u32) -> u32 { - return (Src0 & SHADER_COMPONENT_MAPPING_MASK) | - ((Src1 & SHADER_COMPONENT_MAPPING_MASK) << SHADER_COMPONENT_MAPPING_SHIFT) | - ((Src2 & SHADER_COMPONENT_MAPPING_MASK) << (SHADER_COMPONENT_MAPPING_SHIFT * 2)) | - ((Src3 & SHADER_COMPONENT_MAPPING_MASK) << (SHADER_COMPONENT_MAPPING_SHIFT * 3)) | - SHADER_COMPONENT_MAPPING_ALWAYS_SET_BIT_AVOIDING_ZEROMEM_MISTAKES + return (Src0 & SHADER_COMPONENT_MAPPING_MASK) | + ((Src1 & SHADER_COMPONENT_MAPPING_MASK) << SHADER_COMPONENT_MAPPING_SHIFT) | + ((Src2 & SHADER_COMPONENT_MAPPING_MASK) << (SHADER_COMPONENT_MAPPING_SHIFT * 2)) | + ((Src3 & SHADER_COMPONENT_MAPPING_MASK) << (SHADER_COMPONENT_MAPPING_SHIFT * 3)) | + SHADER_COMPONENT_MAPPING_ALWAYS_SET_BIT_AVOIDING_ZEROMEM_MISTAKES } DECODE_SHADER_4_COMPONENT_MAPPING :: #force_inline proc "contextless" (ComponentToExtract, Mapping: u32) -> u32 { - return Mapping >> (SHADER_COMPONENT_MAPPING_SHIFT * ComponentToExtract) & SHADER_COMPONENT_MAPPING_MASK + return Mapping >> (SHADER_COMPONENT_MAPPING_SHIFT * ComponentToExtract) & SHADER_COMPONENT_MAPPING_MASK } BUFFER_SRV_FLAGS :: distinct bit_set[BUFFER_SRV_FLAG; u32] diff --git a/vendor/glfw/bindings/bindings.odin b/vendor/glfw/bindings/bindings.odin index a4be006b0..e59239483 100644 --- a/vendor/glfw/bindings/bindings.odin +++ b/vendor/glfw/bindings/bindings.odin @@ -71,6 +71,7 @@ foreign glfw { GetPrimaryMonitor :: proc() -> MonitorHandle --- GetMonitors :: proc(count: ^c.int) -> [^]MonitorHandle --- GetMonitorPos :: proc(monitor: MonitorHandle, xpos, ypos: ^c.int) --- + GetMonitorWorkarea :: proc(monitor: MonitorHandle, xpos, ypos, width, height: ^c.int) --- GetMonitorPhysicalSize :: proc(monitor: MonitorHandle, widthMM, heightMM: ^c.int) --- GetMonitorContentScale :: proc(monitor: MonitorHandle, xscale, yscale: ^f32) --- diff --git a/vendor/glfw/wrapper.odin b/vendor/glfw/wrapper.odin index fa9329aa7..854dcdf9a 100644 --- a/vendor/glfw/wrapper.odin +++ b/vendor/glfw/wrapper.odin @@ -33,6 +33,10 @@ GetMonitorPos :: proc "c" (monitor: MonitorHandle) -> (xpos, ypos: c.int) { glfw.GetMonitorPos(monitor, &xpos, &ypos) return } +GetMonitorWorkarea :: proc "c" (monitor: MonitorHandle) -> (xpos, ypos, width, height: c.int) { + glfw.GetMonitorWorkarea(monitor, &xpos, &ypos, &width, &height) + return +} GetMonitorPhysicalSize :: proc "c" (monitor: MonitorHandle) -> (widthMM, heightMM: c.int) { glfw.GetMonitorPhysicalSize(monitor, &widthMM, &heightMM) return diff --git a/vendor/miniaudio/device_io_types.odin b/vendor/miniaudio/device_io_types.odin index eae804720..b52a3f423 100644 --- a/vendor/miniaudio/device_io_types.odin +++ b/vendor/miniaudio/device_io_types.odin @@ -351,8 +351,11 @@ device_id :: struct #raw_union { nullbackend: c.int, /* The null backend uses an integer for device IDs. */ } +data_format_flag :: enum c.int { + EXCLUSIVE_MODE = 1, /* If set, this is supported in exclusive mode. Otherwise not natively supported by exclusive mode. */ +} -DATA_FORMAT_FLAG_EXCLUSIVE_MODE :: 1 << 1 /* If set, this is supported in exclusive mode. Otherwise not natively supported by exclusive mode. */ +data_format_flags :: bit_set[data_format_flag; u32] MAX_DEVICE_NAME_LENGTH :: 255 @@ -364,10 +367,10 @@ device_info :: struct { nativeDataFormatCount: u32, nativeDataFormats: [/*len(format_count) * standard_sample_rate.rate_count * MAX_CHANNELS*/ 64]struct { /* Not sure how big to make this. There can be *many* permutations for virtual devices which can support anything. */ - format: format, /* Sample format. If set to ma_format_unknown, all sample formats are supported. */ - channels: u32, /* If set to 0, all channels are supported. */ - sampleRate: u32, /* If set to 0, all sample rates are supported. */ - flags: u32, /* A combination of MA_DATA_FORMAT_FLAG_* flags. */ + format: format, /* Sample format. If set to ma_format_unknown, all sample formats are supported. */ + channels: u32, /* If set to 0, all channels are supported. */ + sampleRate: u32, /* If set to 0, all sample rates are supported. */ + flags: data_format_flags, /* A combination of MA_DATA_FORMAT_FLAG_* flags. */ }, } diff --git a/vendor/miniaudio/engine.odin b/vendor/miniaudio/engine.odin index ecd3fb39d..a06e6c62c 100644 --- a/vendor/miniaudio/engine.odin +++ b/vendor/miniaudio/engine.odin @@ -11,20 +11,22 @@ Engine ************************************************************************************************************************************************************/ /* Sound flags. */ -sound_flags :: enum c.int { +sound_flag :: enum c.int { /* Resource manager flags. */ - STREAM = 0x00000001, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM */ - DECODE = 0x00000002, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE */ - ASYNC = 0x00000004, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC */ - WAIT_INIT = 0x00000008, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT */ - UNKNOWN_LENGTH = 0x00000010, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH */ + STREAM = 0, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM */ + DECODE = 1, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE */ + ASYNC = 2, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC */ + WAIT_INIT = 3, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT */ + UNKNOWN_LENGTH = 4, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH */ /* ma_sound specific flags. */ - NO_DEFAULT_ATTACHMENT = 0x00001000, /* Do not attach to the endpoint by default. Useful for when setting up nodes in a complex graph system. */ - NO_PITCH = 0x00002000, /* Disable pitch shifting with ma_sound_set_pitch() and ma_sound_group_set_pitch(). This is an optimization. */ - NO_SPATIALIZATION = 0x00004000, /* Disable spatialization. */ + NO_DEFAULT_ATTACHMENT = 12, /* Do not attach to the endpoint by default. Useful for when setting up nodes in a complex graph system. */ + NO_PITCH = 13, /* Disable pitch shifting with ma_sound_set_pitch() and ma_sound_group_set_pitch(). This is an optimization. */ + NO_SPATIALIZATION = 14, /* Disable spatialization. */ } +sound_flags :: bit_set[sound_flag; u32] + ENGINE_MAX_LISTENERS :: 4 LISTENER_INDEX_CLOSEST :: 255 @@ -81,7 +83,7 @@ engine_node :: struct { @(default_calling_convention="c", link_prefix="ma_") foreign lib { - engine_node_config_init :: proc(pEngine: ^engine, type: engine_node_type, flags: u32) -> engine_node_config --- + engine_node_config_init :: proc(pEngine: ^engine, type: engine_node_type, flags: sound_flags) -> engine_node_config --- engine_node_get_heap_size :: proc(pConfig: ^engine_node_config, pHeapSizeInBytes: ^c.size_t) -> result --- engine_node_init_preallocated :: proc(pConfig: ^engine_node_config, pHeap: rawptr, pEngineNode: ^engine_node) -> result --- @@ -96,17 +98,17 @@ SOUND_SOURCE_CHANNEL_COUNT :: 0xFFFFFFFF sound_end_proc :: #type proc "c" (pUserData: rawptr, pSound: ^sound) sound_config :: struct { - pFilePath: cstring, /* Set this to load from the resource manager. */ - pFilePathW: [^]c.wchar_t, /* Set this to load from the resource manager. */ - pDataSource: ^data_source, /* Set this to load from an existing data source. */ - pInitialAttachment: ^node, /* If set, the sound will be attached to an input of this node. This can be set to a ma_sound. If set to NULL, the sound will be attached directly to the endpoint unless MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT is set in `flags`. */ - initialAttachmentInputBusIndex: u32, /* The index of the input bus of pInitialAttachment to attach the sound to. */ - channelsIn: u32, /* Ignored if using a data source as input (the data source's channel count will be used always). Otherwise, setting to 0 will cause the engine's channel count to be used. */ - channelsOut: u32, /* Set this to 0 (default) to use the engine's channel count. Set to MA_SOUND_SOURCE_CHANNEL_COUNT to use the data source's channel count (only used if using a data source as input). */ + pFilePath: cstring, /* Set this to load from the resource manager. */ + pFilePathW: [^]c.wchar_t, /* Set this to load from the resource manager. */ + pDataSource: ^data_source, /* Set this to load from an existing data source. */ + pInitialAttachment: ^node, /* If set, the sound will be attached to an input of this node. This can be set to a ma_sound. If set to NULL, the sound will be attached directly to the endpoint unless MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT is set in `flags`. */ + initialAttachmentInputBusIndex: u32, /* The index of the input bus of pInitialAttachment to attach the sound to. */ + channelsIn: u32, /* Ignored if using a data source as input (the data source's channel count will be used always). Otherwise, setting to 0 will cause the engine's channel count to be used. */ + channelsOut: u32, /* Set this to 0 (default) to use the engine's channel count. Set to MA_SOUND_SOURCE_CHANNEL_COUNT to use the data source's channel count (only used if using a data source as input). */ monoExpansionMode: mono_expansion_mode, /* Controls how the mono channel should be expanded to other channels when spatialization is disabled on a sound. */ - flags: u32, /* A combination of MA_SOUND_FLAG_* flags. */ - volumeSmoothTimeInPCMFrames: u32, /* The number of frames to smooth over volume changes. Defaults to 0 in which case no smoothing is used. */ - initialSeekPointInPCMFrames: u64, /* Initializes the sound such that it's seeked to this location by default. */ + flags: sound_flags, /* A combination of MA_SOUND_FLAG_* flags. */ + volumeSmoothTimeInPCMFrames: u32, /* The number of frames to smooth over volume changes. Defaults to 0 in which case no smoothing is used. */ + initialSeekPointInPCMFrames: u64, /* Initializes the sound such that it's seeked to this location by default. */ rangeBegInPCMFrames: u64, rangeEndInPCMFrames: u64, loopPointBegInPCMFrames: u64, @@ -148,14 +150,14 @@ sound_inlined :: struct { @(default_calling_convention="c", link_prefix="ma_") foreign lib { - @(deprecated="Will be removed in 0.12. Use sound_config_init2() instead.") + @(deprecated="Will be removed in 0.12. Use sound_config_init_2() instead.") sound_config_init :: proc() -> sound_config --- - sound_config_init2 :: proc(pEngine: ^engine) -> sound_config --- /* Will be renamed to sound_config_init() in version 0.12. */ + sound_config_init_2 :: proc(pEngine: ^engine) -> sound_config --- /* Will be renamed to sound_config_init() in version 0.12. */ - sound_init_from_file :: proc(pEngine: ^engine, pFilePath: cstring, flags: u32, pGroup: ^sound_group, pDoneFence: ^fence, pSound: ^sound) -> result --- - sound_init_from_file_w :: proc(pEngine: ^engine, pFilePath: [^]c.wchar_t, flags: u32, pGroup: ^sound_group, pDoneFence: ^fence, pSound: ^sound) -> result --- - sound_init_copy :: proc(pEngine: ^engine, pExistingSound: ^sound, flags: u32, pGroup: ^sound_group, pSound: ^sound) -> result --- - sound_init_from_data_source :: proc(pEngine: ^engine, pDataSource: ^data_source, flags: u32, pGroup: ^sound_group, pSound: ^sound) -> result --- + sound_init_from_file :: proc(pEngine: ^engine, pFilePath: cstring, flags: sound_flags, pGroup: ^sound_group, pDoneFence: ^fence, pSound: ^sound) -> result --- + sound_init_from_file_w :: proc(pEngine: ^engine, pFilePath: [^]c.wchar_t, flags: sound_flags, pGroup: ^sound_group, pDoneFence: ^fence, pSound: ^sound) -> result --- + sound_init_copy :: proc(pEngine: ^engine, pExistingSound: ^sound, flags: sound_flags, pGroup: ^sound_group, pSound: ^sound) -> result --- + sound_init_from_data_source :: proc(pEngine: ^engine, pDataSource: ^data_source, flags: sound_flags, pGroup: ^sound_group, pSound: ^sound) -> result --- sound_init_ex :: proc(pEngine: ^engine, pConfig: ^sound_config, pSound: ^sound) -> result --- sound_uninit :: proc(pSound: ^sound) --- sound_get_engine :: proc(pSound: ^sound) -> ^engine --- @@ -239,11 +241,11 @@ sound_group :: distinct sound @(default_calling_convention="c", link_prefix="ma_") foreign lib { - @(deprecated="Will be removed in 0.12. Use sound_config_init2() instead.") + @(deprecated="Will be removed in 0.12. Use sound_config_init_2() instead.") sound_group_config_init :: proc() -> sound_group_config --- - sound_group_config_init2 :: proc(pEngine: ^engine) -> sound_group_config --- + sound_group_config_init_2 :: proc(pEngine: ^engine) -> sound_group_config --- - sound_group_init :: proc(pEngine: ^engine, flags: u32, pParentGroup, pGroup: ^sound_group) -> result --- + sound_group_init :: proc(pEngine: ^engine, flags: sound_flags, pParentGroup, pGroup: ^sound_group) -> result --- sound_group_init_ex :: proc(pEngine: ^engine, pConfig: ^sound_group_config, pGroup: ^sound_group) -> result --- sound_group_uninit :: proc(pGroup: ^sound_group) --- sound_group_get_engine :: proc(pGroup: ^sound_group) -> ^engine --- diff --git a/vendor/miniaudio/job_queue.odin b/vendor/miniaudio/job_queue.odin index 01ee31216..b5816a95a 100644 --- a/vendor/miniaudio/job_queue.odin +++ b/vendor/miniaudio/job_queue.odin @@ -108,7 +108,7 @@ job :: struct { pDataBufferNode: rawptr /*ma_resource_manager_data_buffer_node**/, pFilePath: cstring, pFilePathW: [^]c.wchar_t, - flags: u32, /* Resource manager data source flags that were used when initializing the data buffer. */ + flags: resource_manager_data_source_flags, /* Resource manager data source flags that were used when initializing the data buffer. */ pInitNotification: ^async_notification, /* Signalled when the data buffer has been initialized and the format/channels/rate can be retrieved. */ pDoneNotification: ^async_notification, /* Signalled when the data buffer has been fully decoded. Will be passed through to MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE when decoding. */ pInitFence: ^fence, /* Released when initialization of the decoder is complete. */ @@ -194,19 +194,21 @@ ma_job_queue_post(). ma_job_queue_next() will return MA_NO_DATA_AVAILABLE if not This flag should always be used for platforms that do not support multithreading. */ -job_queue_flags :: enum c.int { - NON_BLOCKING = 0x00000001, +job_queue_flag :: enum c.int { + NON_BLOCKING = 0, } +job_queue_flags :: bit_set[job_queue_flag; u32] + job_queue_config :: struct { - flags: u32, + flags: job_queue_flags, capacity: u32, /* The maximum number of jobs that can fit in the queue at a time. */ } USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE :: false job_queue :: struct { - flags: u32, /* Flags passed in at initialization time. */ + flags: job_queue_flags, /* Flags passed in at initialization time. */ capacity: u32, /* The maximum number of jobs that can fit in the queue at a time. Set by the config. */ head: u64, /*atomic*/ /* The first item in the list. Required for removing from the top of the list. */ tail: u64, /*atomic*/ /* The last item in the list. Required for appending to the end of the list. */ @@ -222,7 +224,7 @@ job_queue :: struct { @(default_calling_convention="c", link_prefix="ma_") foreign lib { - job_queue_config_init :: proc(flags, capacity: u32) -> job_queue_config --- + job_queue_config_init :: proc(flags: job_queue_flags, capacity: u32) -> job_queue_config --- job_queue_get_heap_size :: proc(pConfig: ^job_queue_config, pHeapSizeInBytes: ^c.size_t) -> result --- job_queue_init_preallocated :: proc(pConfig: ^job_queue_config, pHeap: rawptr, pQueue: ^job_queue) -> result --- diff --git a/vendor/miniaudio/node_graph.odin b/vendor/miniaudio/node_graph.odin index 63482413b..610ada7a8 100644 --- a/vendor/miniaudio/node_graph.odin +++ b/vendor/miniaudio/node_graph.odin @@ -22,14 +22,16 @@ NODE_BUS_COUNT_UNKNOWN :: 255 node :: struct {} /* Node flags. */ -node_flags :: enum c.int { - PASSTHROUGH = 0x00000001, - CONTINUOUS_PROCESSING = 0x00000002, - ALLOW_NULL_INPUT = 0x00000004, - DIFFERENT_PROCESSING_RATES = 0x00000008, - SILENT_OUTPUT = 0x00000010, +node_flag :: enum c.int { + PASSTHROUGH = 0, + CONTINUOUS_PROCESSING = 1, + ALLOW_NULL_INPUT = 2, + DIFFERENT_PROCESSING_RATES = 3, + SILENT_OUTPUT = 4, } +node_flags :: bit_set[node_flag; u32] + /* The playback state of a node. Either started or stopped. */ node_state :: enum c.int { started = 0, @@ -75,7 +77,7 @@ node_vtable :: struct { Flags describing characteristics of the node. This is currently just a placeholder for some ideas for later on. */ - flags: u32, + flags: node_flags, } node_config :: struct { @@ -87,6 +89,12 @@ node_config :: struct { pOutputChannels: ^u32, /* The number of elements are determined by the output bus count as determined by the vtable, or `outputBusCount` if the vtable specifies `MA_NODE_BUS_COUNT_UNKNOWN`. */ } +node_output_bus_flag :: enum c.int { + HAS_READ = 0, /* 0x01 */ +} + +node_output_bus_flags :: bit_set[node_output_bus_flag; u32] + /* A node has multiple output buses. An output bus is attached to an input bus as an item in a linked list. Think of the input bus as a linked list, with the output bus being an item in that list. @@ -99,7 +107,7 @@ node_output_bus :: struct { /* Mutable via multiple threads. Must be used atomically. The weird ordering here is for packing reasons. */ inputNodeInputBusIndex: u8, /* The index of the input bus on the input. Required for detaching. Will only be used in the spinlock so does not need to be atomic. */ - flags: u32, /*atomic*/ /* Some state flags for tracking the read state of the output buffer. A combination of MA_NODE_OUTPUT_BUS_FLAG_*. */ + flags: node_output_bus_flags, /*atomic*/ /* Some state flags for tracking the read state of the output buffer. A combination of MA_NODE_OUTPUT_BUS_FLAG_*. */ refCount: u32, /*atomic*/ /* Reference count for some thread-safety when detaching. */ isAttached: b32, /*atomic*/ /* This is used to prevent iteration of nodes that are in the middle of being detached. Used for thread safety. */ lock: spinlock, /*atomic*/ /* Unfortunate lock, but significantly simplifies the implementation. Required for thread-safe attaching and detaching. */ diff --git a/vendor/miniaudio/resource_manager.odin b/vendor/miniaudio/resource_manager.odin index 0284db86b..495a02c5d 100644 --- a/vendor/miniaudio/resource_manager.odin +++ b/vendor/miniaudio/resource_manager.odin @@ -10,14 +10,16 @@ Resource Manager ************************************************************************************************************************************************************/ -resource_manager_data_source_flags :: enum c.int { - STREAM = 0x00000001, /* When set, does not load the entire data source in memory. Disk I/O will happen on job threads. */ - DECODE = 0x00000002, /* Decode data before storing in memory. When set, decoding is done at the resource manager level rather than the mixing thread. Results in faster mixing, but higher memory usage. */ - ASYNC = 0x00000004, /* When set, the resource manager will load the data source asynchronously. */ - WAIT_INIT = 0x00000008, /* When set, waits for initialization of the underlying data source before returning from ma_resource_manager_data_source_init(). */ - UNKNOWN_LENGTH = 0x00000010, /* Gives the resource manager a hint that the length of the data source is unknown and calling `ma_data_source_get_length_in_pcm_frames()` should be avoided. */ +resource_manager_data_source_flag :: enum c.int { + STREAM = 0, /* When set, does not load the entire data source in memory. Disk I/O will happen on job threads. */ + DECODE = 1, /* Decode data before storing in memory. When set, decoding is done at the resource manager level rather than the mixing thread. Results in faster mixing, but higher memory usage. */ + ASYNC = 2, /* When set, the resource manager will load the data source asynchronously. */ + WAIT_INIT = 3, /* When set, waits for initialization of the underlying data source before returning from ma_resource_manager_data_source_init(). */ + UNKNOWN_LENGTH = 4, /* Gives the resource manager a hint that the length of the data source is unknown and calling `ma_data_source_get_length_in_pcm_frames()` should be avoided. */ } +resource_manager_data_source_flags :: bit_set[resource_manager_data_source_flag; u32] + /* Pipeline notifications used by the resource manager. Made up of both an async notification and a fence, both of which are optional. */ @@ -58,14 +60,16 @@ resource_manager_job_queue_next :: job_queue_next /* Maximum job thread count will be restricted to this, but this may be removed later and replaced with a heap allocation thereby removing any limitation. */ RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT :: 64 -resource_manager_flags :: enum c.int { +resource_manager_flag :: enum c.int { /* Indicates ma_resource_manager_next_job() should not block. Only valid when the job thread count is 0. */ - NON_BLOCKING = 0x00000001, + NON_BLOCKING = 0, /* Disables any kind of multithreading. Implicitly enables MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING. */ - NO_THREADING = 0x00000002, + NO_THREADING = 1, } +resource_manager_flags :: bit_set[resource_manager_flag; u32] + resource_manager_data_source_config :: struct { pFilePath: cstring, pFilePathW: [^]c.wchar_t, @@ -126,7 +130,7 @@ resource_manager_data_buffer :: struct { ds: data_source_base, /* Base data source. A data buffer is a data source. */ pResourceManager: ^resource_manager, /* A pointer to the resource manager that owns this buffer. */ pNode: ^resource_manager_data_buffer_node, /* The data node. This is reference counted and is what supplies the data. */ - flags: u32, /* The flags that were passed used to initialize the buffer. */ + flags: resource_manager_flags, /* The flags that were passed used to initialize the buffer. */ executionCounter: u32, /*atomic*/ /* For allocating execution orders for jobs. */ executionPointer: u32, /*atomic*/ /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ seekTargetInPCMFrames: u64, /* Only updated by the public API. Never written nor read from the job thread. */ diff --git a/vendor/miniaudio/utilities.odin b/vendor/miniaudio/utilities.odin index 8728f40dc..9285874b6 100644 --- a/vendor/miniaudio/utilities.odin +++ b/vendor/miniaudio/utilities.odin @@ -119,7 +119,12 @@ offset_pcm_frames_const_ptr_f32 :: #force_inline proc "c" (p: [^]f32, offsetInFr data_source :: struct {} -DATA_SOURCE_SELF_MANAGED_RANGE_AND_LOOP_POINT :: 0x00000001 +data_source_flag :: enum c.int { + SELF_MANAGED_RANGE_AND_LOOP_POINT = 0, +} + +data_source_flags :: bit_set[data_source_flag; u32] + data_source_vtable :: struct { onRead: proc "c" (pDataSource: ^data_source, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result, @@ -128,7 +133,7 @@ data_source_vtable :: struct { onGetCursor: proc "c" (pDataSource: ^data_source, pCursor: ^u64) -> result, onGetLength: proc "c" (pDataSource: ^data_source, pLength: ^u64) -> result, onSetLooping: proc "c" (pDataSource: ^data_source, isLooping: b32) -> result, - flags: u32, + flags: data_source_flags, } data_source_get_next_proc :: proc "c" (pDataSource: ^data_source) -> ^data_source diff --git a/vendor/miniaudio/vfs.odin b/vendor/miniaudio/vfs.odin index b045a1501..2a538c6e3 100644 --- a/vendor/miniaudio/vfs.odin +++ b/vendor/miniaudio/vfs.odin @@ -16,11 +16,13 @@ appropriate for a given situation. vfs :: struct {} vfs_file :: distinct handle -open_mode_flags :: enum c.int { - READ = 0x00000001, - WRITE = 0x00000002, +open_mode_flag :: enum c.int { + READ = 0, + WRITE = 1, } +open_mode_flags :: bit_set[open_mode_flag; u32] + seek_origin :: enum c.int { start, current, @@ -32,8 +34,8 @@ file_info :: struct { } vfs_callbacks :: struct { - onOpen: proc "c" (pVFS: ^vfs, pFilePath: cstring, openMode: u32, pFile: ^vfs_file) -> result, - onOpenW: proc "c" (pVFS: ^vfs, pFilePath: [^]c.wchar_t, openMode: u32, pFile: ^vfs_file) -> result, + onOpen: proc "c" (pVFS: ^vfs, pFilePath: cstring, openMode: open_mode_flags, pFile: ^vfs_file) -> result, + onOpenW: proc "c" (pVFS: ^vfs, pFilePath: [^]c.wchar_t, openMode: open_mode_flags, pFile: ^vfs_file) -> result, onClose: proc "c" (pVFS: ^vfs, file: vfs_file) -> result, onRead: proc "c" (pVFS: ^vfs, file: vfs_file, pDst: rawptr, sizeInBytes: c.size_t, pBytesRead: ^c.size_t) -> result, onWrite: proc "c" (pVFS: ^vfs, file: vfs_file, pSrc: rawptr, sizeInBytes: c.size_t, pBytesWritten: ^c.size_t) -> result, @@ -54,8 +56,8 @@ ma_tell_proc :: proc "c" (pUserData: rawptr, pCursor: ^i64) -> result @(default_calling_convention="c", link_prefix="ma_") foreign lib { - vfs_open :: proc(pVFS: ^vfs, pFilePath: cstring, openMode: u32, pFile: ^vfs_file) -> result --- - vfs_open_w :: proc(pVFS: ^vfs, pFilePath: [^]c.wchar_t, openMode: u32, pFile: ^vfs_file) -> result --- + vfs_open :: proc(pVFS: ^vfs, pFilePath: cstring, openMode: open_mode_flags, pFile: ^vfs_file) -> result --- + vfs_open_w :: proc(pVFS: ^vfs, pFilePath: [^]c.wchar_t, openMode: open_mode_flags, pFile: ^vfs_file) -> result --- vfs_close :: proc(pVFS: ^vfs, file: vfs_file) -> result --- vfs_read :: proc(pVFS: ^vfs, file: vfs_file, pDst: rawptr, sizeInBytes: c.size_t, pBytesRead: ^c.size_t) -> result --- vfs_write :: proc(pVFS: ^vfs, file: vfs_file, pSrc: rawptr, sizeInBytes: c.size_t, pBytesWritten: ^c.size_t) -> result --- diff --git a/vendor/raylib/raylib.odin b/vendor/raylib/raylib.odin index bb51f105f..02bb6deea 100644 --- a/vendor/raylib/raylib.odin +++ b/vendor/raylib/raylib.odin @@ -1205,7 +1205,7 @@ foreign lib { CameraMoveForward :: proc(camera: ^Camera, distance: f32, moveInWorldPlane: bool) --- // move the camera in its forward direction CameraMoveUp :: proc(camera: ^Camera, distance: f32) --- // move camera in its up direction - CameraMoveRight :: proc(camera: ^Camera, distance: f32, delta: f32) --- // move camera in it's current right direction + CameraMoveRight :: proc(camera: ^Camera, distance: f32, moveInWorldPlane: bool) --- // move camera in it's current right direction CameraMoveToTarget :: proc(camera: ^Camera, delta: f32) --- // moves the camera position closer/farther to/from the camera target CameraYaw :: proc(camera: ^Camera, angle: f32, rotateAroundTarget: bool) --- // rotates the camera around its up vector (left and right) CameraPitch :: proc(camera: ^Camera, angle: f32, lockView: bool, rotateAroundTarget: bool, rotateUp: bool) --- // rotates the camera around its right vector (up and down) @@ -1256,7 +1256,7 @@ foreign lib { DrawRectangleLines :: proc(posX, posY: c.int, width, height: c.int, color: Color) --- // Draw rectangle outline DrawRectangleLinesEx :: proc(rec: Rectangle, lineThick: f32, color: Color) --- // Draw rectangle outline with extended parameters DrawRectangleRounded :: proc(rec: Rectangle, roundness: f32, segments: c.int, color: Color) --- // Draw rectangle with rounded edges - DrawRectangleRoundedLines :: proc(rec: Rectangle, roundness: f32, segments: c.int, lineThick: f32, color: Color) --- // Draw rectangle lines with rounded edges + DrawRectangleRoundedLines :: proc(rec: Rectangle, roundness: f32, segments: c.int, color: Color) --- // Draw rectangle lines with rounded edges DrawRectangleRoundedLinesEx :: proc(rec: Rectangle, roundness: f32, segments: c.int, lineThick: f32, color: Color) --- // Draw rectangle with rounded edges outline DrawTriangle :: proc(v1, v2, v3: Vector2, color: Color) --- // Draw a color-filled triangle (vertex in counter-clockwise order!) DrawTriangleLines :: proc(v1, v2, v3: Vector2, color: Color) --- // Draw triangle outline (vertex in counter-clockwise order!) diff --git a/vendor/sdl2/sdl2.odin b/vendor/sdl2/sdl2.odin index b23389a64..5bc52b70e 100644 --- a/vendor/sdl2/sdl2.odin +++ b/vendor/sdl2/sdl2.odin @@ -26,8 +26,10 @@ import "core:c" import "base:intrinsics" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_audio.odin b/vendor/sdl2/sdl_audio.odin index 28a59d947..6ff9e93f4 100644 --- a/vendor/sdl2/sdl_audio.odin +++ b/vendor/sdl2/sdl_audio.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_blendmode.odin b/vendor/sdl2/sdl_blendmode.odin index 4fde5111b..3105ad72b 100644 --- a/vendor/sdl2/sdl_blendmode.odin +++ b/vendor/sdl2/sdl_blendmode.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_cpuinfo.odin b/vendor/sdl2/sdl_cpuinfo.odin index c5175e4d5..a98b6f8d3 100644 --- a/vendor/sdl2/sdl_cpuinfo.odin +++ b/vendor/sdl2/sdl_cpuinfo.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_events.odin b/vendor/sdl2/sdl_events.odin index b4c92683c..061eb964d 100644 --- a/vendor/sdl2/sdl_events.odin +++ b/vendor/sdl2/sdl_events.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_gamecontroller.odin b/vendor/sdl2/sdl_gamecontroller.odin index beb7d5ce7..be45d6520 100644 --- a/vendor/sdl2/sdl_gamecontroller.odin +++ b/vendor/sdl2/sdl_gamecontroller.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_gesture_haptic.odin b/vendor/sdl2/sdl_gesture_haptic.odin index a21e0df06..01d7a6da3 100644 --- a/vendor/sdl2/sdl_gesture_haptic.odin +++ b/vendor/sdl2/sdl_gesture_haptic.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_hints.odin b/vendor/sdl2/sdl_hints.odin index 913d4ea12..080dc6036 100644 --- a/vendor/sdl2/sdl_hints.odin +++ b/vendor/sdl2/sdl_hints.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_joystick.odin b/vendor/sdl2/sdl_joystick.odin index 35ca5cdcc..0725a3554 100644 --- a/vendor/sdl2/sdl_joystick.odin +++ b/vendor/sdl2/sdl_joystick.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_keyboard.odin b/vendor/sdl2/sdl_keyboard.odin index f880286aa..0d0557de9 100644 --- a/vendor/sdl2/sdl_keyboard.odin +++ b/vendor/sdl2/sdl_keyboard.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_log.odin b/vendor/sdl2/sdl_log.odin index 09b7eaef0..b7668ee1d 100644 --- a/vendor/sdl2/sdl_log.odin +++ b/vendor/sdl2/sdl_log.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_messagebox.odin b/vendor/sdl2/sdl_messagebox.odin index 6228704ac..edd8422e0 100644 --- a/vendor/sdl2/sdl_messagebox.odin +++ b/vendor/sdl2/sdl_messagebox.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_metal.odin b/vendor/sdl2/sdl_metal.odin index 1eccf7f5a..e8e650212 100644 --- a/vendor/sdl2/sdl_metal.odin +++ b/vendor/sdl2/sdl_metal.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_mouse.odin b/vendor/sdl2/sdl_mouse.odin index 0243b6623..8e782a5e3 100644 --- a/vendor/sdl2/sdl_mouse.odin +++ b/vendor/sdl2/sdl_mouse.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_mutex.odin b/vendor/sdl2/sdl_mutex.odin index 6ff7e5d2b..6eb096c81 100644 --- a/vendor/sdl2/sdl_mutex.odin +++ b/vendor/sdl2/sdl_mutex.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_pixels.odin b/vendor/sdl2/sdl_pixels.odin index 195f2920f..6a3d89f4e 100644 --- a/vendor/sdl2/sdl_pixels.odin +++ b/vendor/sdl2/sdl_pixels.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_rect.odin b/vendor/sdl2/sdl_rect.odin index 852309cd2..96cf7180e 100644 --- a/vendor/sdl2/sdl_rect.odin +++ b/vendor/sdl2/sdl_rect.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_render.odin b/vendor/sdl2/sdl_render.odin index cceebf3ac..5e913e5a3 100644 --- a/vendor/sdl2/sdl_render.odin +++ b/vendor/sdl2/sdl_render.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_rwops.odin b/vendor/sdl2/sdl_rwops.odin index 28d09511b..ca7fa0bea 100644 --- a/vendor/sdl2/sdl_rwops.odin +++ b/vendor/sdl2/sdl_rwops.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_stdinc.odin b/vendor/sdl2/sdl_stdinc.odin index 9136ae026..bf04a3f1f 100644 --- a/vendor/sdl2/sdl_stdinc.odin +++ b/vendor/sdl2/sdl_stdinc.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_surface.odin b/vendor/sdl2/sdl_surface.odin index f50de35f7..1502efbc7 100644 --- a/vendor/sdl2/sdl_surface.odin +++ b/vendor/sdl2/sdl_surface.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_system.odin b/vendor/sdl2/sdl_system.odin index d9b6b98df..1c34e557e 100644 --- a/vendor/sdl2/sdl_system.odin +++ b/vendor/sdl2/sdl_system.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_syswm.odin b/vendor/sdl2/sdl_syswm.odin index 62ca9d628..15501c222 100644 --- a/vendor/sdl2/sdl_syswm.odin +++ b/vendor/sdl2/sdl_syswm.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_thread.odin b/vendor/sdl2/sdl_thread.odin index 5d1c0bd37..84516e26b 100644 --- a/vendor/sdl2/sdl_thread.odin +++ b/vendor/sdl2/sdl_thread.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_timer.odin b/vendor/sdl2/sdl_timer.odin index d71ed2da5..50b5eb981 100644 --- a/vendor/sdl2/sdl_timer.odin +++ b/vendor/sdl2/sdl_timer.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_touch.odin b/vendor/sdl2/sdl_touch.odin index f0ca69333..44633aeb6 100644 --- a/vendor/sdl2/sdl_touch.odin +++ b/vendor/sdl2/sdl_touch.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_video.odin b/vendor/sdl2/sdl_video.odin index 86b564541..809735414 100644 --- a/vendor/sdl2/sdl_video.odin +++ b/vendor/sdl2/sdl_video.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_vulkan.odin b/vendor/sdl2/sdl_vulkan.odin index 33bb8e51c..4e0db0ffe 100644 --- a/vendor/sdl2/sdl_vulkan.odin +++ b/vendor/sdl2/sdl_vulkan.odin @@ -4,8 +4,10 @@ import "core:c" import vk "vendor:vulkan" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/stb/image/stb_image.odin b/vendor/stb/image/stb_image.odin index e74c825b8..1ba63dc47 100644 --- a/vendor/stb/image/stb_image.odin +++ b/vendor/stb/image/stb_image.odin @@ -1,6 +1,6 @@ package stb_image -import c "core:c/libc" +import "core:c" @(private) LIB :: (