diff options
| author | Courtney Strachan <courtney.strachan@gmail.com> | 2025-10-06 02:41:44 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-10-06 02:41:44 +0100 |
| commit | 6de2d6e8ca687c989bbb7806e5cbe8d791e425bf (patch) | |
| tree | 03a2e0a84c7c1530215f8e3f59a7f643b39b3677 /core | |
| parent | dbbe96ae5c343f0e803de6ee508207a62571534f (diff) | |
| parent | 0f97382fa3e46da80705c00dfe02f3deb9562e4f (diff) | |
Merge branch 'odin-lang:master' into master
Diffstat (limited to 'core')
427 files changed, 20317 insertions, 6071 deletions
diff --git a/core/bufio/reader.odin b/core/bufio/reader.odin index a875c732d..b78cac6e1 100644 --- a/core/bufio/reader.odin +++ b/core/bufio/reader.odin @@ -257,7 +257,7 @@ reader_read_rune :: proc(b: ^Reader) -> (r: rune, size: int, err: io.Error) { for b.r+utf8.UTF_MAX > b.w && !utf8.full_rune(b.buf[b.r:b.w]) && b.err == nil && - b.w-b.w < len(b.buf) { + b.w-b.r < len(b.buf) { _reader_read_new_chunk(b) or_return } diff --git a/core/bytes/bytes.odin b/core/bytes/bytes.odin index c0d25bcce..71b6ef70c 100644 --- a/core/bytes/bytes.odin +++ b/core/bytes/bytes.odin @@ -350,7 +350,7 @@ index_byte :: proc "contextless" (s: []byte, c: byte) -> (index: int) #no_bounds } c_vec: simd.u8x16 = c - when !simd.IS_EMULATED { + when simd.HAS_HARDWARE_SIMD { // Note: While this is something that could also logically take // advantage of AVX512, the various downclocking and power // consumption related woes make premature to have a dedicated @@ -485,7 +485,7 @@ last_index_byte :: proc "contextless" (s: []byte, c: byte) -> int #no_bounds_che } c_vec: simd.u8x16 = c - when !simd.IS_EMULATED { + when simd.HAS_HARDWARE_SIMD { // Note: While this is something that could also logically take // advantage of AVX512, the various downclocking and power // consumption related woes make premature to have a dedicated diff --git a/core/c/libc/complex.odin b/core/c/libc/complex.odin index 98fd7b1bb..7b6ac6417 100644 --- a/core/c/libc/complex.odin +++ b/core/c/libc/complex.odin @@ -5,7 +5,7 @@ package libc when ODIN_OS == .Windows { foreign import libc "system:libucrt.lib" } else when ODIN_OS == .Darwin { - foreign import libc "system:System.framework" + foreign import libc "system:System" } else { foreign import libc "system:c" } diff --git a/core/c/libc/ctype.odin b/core/c/libc/ctype.odin index 185385a5e..d156f1ed7 100644 --- a/core/c/libc/ctype.odin +++ b/core/c/libc/ctype.odin @@ -3,7 +3,7 @@ package libc when ODIN_OS == .Windows { foreign import libc "system:libucrt.lib" } else when ODIN_OS == .Darwin { - foreign import libc "system:System.framework" + foreign import libc "system:System" } else { foreign import libc "system:c" } diff --git a/core/c/libc/errno.odin b/core/c/libc/errno.odin index 5d1ca8248..138d70a80 100644 --- a/core/c/libc/errno.odin +++ b/core/c/libc/errno.odin @@ -5,7 +5,7 @@ package libc when ODIN_OS == .Windows { foreign import libc "system:libucrt.lib" } else when ODIN_OS == .Darwin { - foreign import libc "system:System.framework" + foreign import libc "system:System" } else { foreign import libc "system:c" } diff --git a/core/c/libc/locale.odin b/core/c/libc/locale.odin index d95f5c164..3216e0f90 100644 --- a/core/c/libc/locale.odin +++ b/core/c/libc/locale.odin @@ -5,7 +5,7 @@ import "core:c" when ODIN_OS == .Windows { foreign import libc "system:libucrt.lib" } else when ODIN_OS == .Darwin { - foreign import libc "system:System.framework" + foreign import libc "system:System" } else { foreign import libc "system:c" } @@ -72,14 +72,14 @@ when ODIN_OS == .Windows { n_sep_by_space: c.char, p_sign_posn: c.char, n_sign_posn: c.char, - _W_decimal_point: [^]u16 `fmt:"s,0"`, - _W_thousands_sep: [^]u16 `fmt:"s,0"`, - _W_int_curr_symbol: [^]u16 `fmt:"s,0"`, - _W_currency_symbol: [^]u16 `fmt:"s,0"`, - _W_mon_decimal_point: [^]u16 `fmt:"s,0"`, - _W_mon_thousands_sep: [^]u16 `fmt:"s,0"`, - _W_positive_sign: [^]u16 `fmt:"s,0"`, - _W_negative_sign: [^]u16 `fmt:"s,0"`, + _W_decimal_point: cstring16, + _W_thousands_sep: cstring16, + _W_int_curr_symbol: cstring16, + _W_currency_symbol: cstring16, + _W_mon_decimal_point: cstring16, + _W_mon_thousands_sep: cstring16, + _W_positive_sign: cstring16, + _W_negative_sign: cstring16, } } else { lconv :: struct { diff --git a/core/c/libc/math.odin b/core/c/libc/math.odin index 81d51728d..d0a015c70 100644 --- a/core/c/libc/math.odin +++ b/core/c/libc/math.odin @@ -7,7 +7,7 @@ import "base:intrinsics" when ODIN_OS == .Windows { foreign import libc "system:libucrt.lib" } else when ODIN_OS == .Darwin { - foreign import libc "system:System.framework" + foreign import libc "system:System" } else { foreign import libc "system:c" } diff --git a/core/c/libc/setjmp.odin b/core/c/libc/setjmp.odin index 101b614b3..4f5319316 100644 --- a/core/c/libc/setjmp.odin +++ b/core/c/libc/setjmp.odin @@ -5,7 +5,7 @@ package libc when ODIN_OS == .Windows { foreign import libc "system:libucrt.lib" } else when ODIN_OS == .Darwin { - foreign import libc "system:System.framework" + foreign import libc "system:System" } else { foreign import libc "system:c" } diff --git a/core/c/libc/signal.odin b/core/c/libc/signal.odin index c447e3cc3..cddf06916 100644 --- a/core/c/libc/signal.odin +++ b/core/c/libc/signal.odin @@ -5,7 +5,7 @@ package libc when ODIN_OS == .Windows { foreign import libc "system:libucrt.lib" } else when ODIN_OS == .Darwin { - foreign import libc "system:System.framework" + foreign import libc "system:System" } else { foreign import libc "system:c" } diff --git a/core/c/libc/stdio.odin b/core/c/libc/stdio.odin index 56e4e8f66..aa92e4a6b 100644 --- a/core/c/libc/stdio.odin +++ b/core/c/libc/stdio.odin @@ -9,7 +9,7 @@ when ODIN_OS == .Windows { "system:legacy_stdio_definitions.lib", } } else when ODIN_OS == .Darwin { - foreign import libc "system:System.framework" + foreign import libc "system:System" } else { foreign import libc "system:c" } @@ -275,7 +275,7 @@ foreign libc { // 7.21.7 Character input/output functions fgetc :: proc(stream: ^FILE) -> int --- fgets :: proc(s: [^]char, n: int, stream: ^FILE) -> [^]char --- - fputc :: proc(s: cstring, stream: ^FILE) -> int --- + fputc :: proc(s: c.int, stream: ^FILE) -> int --- getc :: proc(stream: ^FILE) -> int --- getchar :: proc() -> int --- putc :: proc(c: int, stream: ^FILE) -> int --- diff --git a/core/c/libc/stdlib.odin b/core/c/libc/stdlib.odin index c0e273872..ca906a5f0 100644 --- a/core/c/libc/stdlib.odin +++ b/core/c/libc/stdlib.odin @@ -5,7 +5,7 @@ package libc when ODIN_OS == .Windows { foreign import libc "system:libucrt.lib" } else when ODIN_OS == .Darwin { - foreign import libc "system:System.framework" + foreign import libc "system:System" } else { foreign import libc "system:c" } diff --git a/core/c/libc/string.odin b/core/c/libc/string.odin index 4ec4f3a7a..2ea54579c 100644 --- a/core/c/libc/string.odin +++ b/core/c/libc/string.odin @@ -7,7 +7,7 @@ import "base:runtime" when ODIN_OS == .Windows { foreign import libc "system:libucrt.lib" } else when ODIN_OS == .Darwin { - foreign import libc "system:System.framework" + foreign import libc "system:System" } else { foreign import libc "system:c" } diff --git a/core/c/libc/time.odin b/core/c/libc/time.odin index 33f8dc3af..6106923f5 100644 --- a/core/c/libc/time.odin +++ b/core/c/libc/time.odin @@ -5,7 +5,7 @@ package libc when ODIN_OS == .Windows { foreign import libc "system:libucrt.lib" } else when ODIN_OS == .Darwin { - foreign import libc "system:System.framework" + foreign import libc "system:System" } else { foreign import libc "system:c" } diff --git a/core/c/libc/uchar.odin b/core/c/libc/uchar.odin index a10969ceb..997b99b46 100644 --- a/core/c/libc/uchar.odin +++ b/core/c/libc/uchar.odin @@ -5,7 +5,7 @@ package libc when ODIN_OS == .Windows { foreign import libc "system:libucrt.lib" } else when ODIN_OS == .Darwin { - foreign import libc "system:System.framework" + foreign import libc "system:System" } else { foreign import libc "system:c" } diff --git a/core/c/libc/wchar.odin b/core/c/libc/wchar.odin index f0dae720e..248611409 100644 --- a/core/c/libc/wchar.odin +++ b/core/c/libc/wchar.odin @@ -5,7 +5,7 @@ package libc when ODIN_OS == .Windows { foreign import libc "system:libucrt.lib" } else when ODIN_OS == .Darwin { - foreign import libc "system:System.framework" + foreign import libc "system:System" } else { foreign import libc "system:c" } diff --git a/core/c/libc/wctype.odin b/core/c/libc/wctype.odin index b96410b4c..6526a14e2 100644 --- a/core/c/libc/wctype.odin +++ b/core/c/libc/wctype.odin @@ -5,7 +5,7 @@ package libc when ODIN_OS == .Windows { foreign import libc "system:libucrt.lib" } else when ODIN_OS == .Darwin { - foreign import libc "system:System.framework" + foreign import libc "system:System" } else { foreign import libc "system:c" } diff --git a/core/compress/common.odin b/core/compress/common.odin index 242538e78..f4429b667 100644 --- a/core/compress/common.odin +++ b/core/compress/common.odin @@ -139,9 +139,6 @@ Context_Memory_Input :: struct #packed { } when size_of(rawptr) == 8 { #assert(size_of(Context_Memory_Input) == 64) -} else { - // e.g. `-target:windows_i386` - #assert(size_of(Context_Memory_Input) == 52) } Context_Stream_Input :: struct #packed { diff --git a/core/container/intrusive/list/intrusive_list.odin b/core/container/intrusive/list/intrusive_list.odin index 5b29efb22..1e116ef18 100644 --- a/core/container/intrusive/list/intrusive_list.odin +++ b/core/container/intrusive/list/intrusive_list.odin @@ -278,19 +278,19 @@ Example: iterate_next_example :: proc() { l: list.List - one := My_Struct{value=1} - two := My_Struct{value=2} + one := My_Next_Struct{value=1} + two := My_Next_Struct{value=2} list.push_back(&l, &one.node) list.push_back(&l, &two.node) - it := list.iterator_head(l, My_Struct, "node") + it := list.iterator_head(l, My_Next_Struct, "node") for num in list.iterate_next(&it) { fmt.println(num.value) } } - My_Struct :: struct { + My_Next_Struct :: struct { node : list.Node, value: int, } @@ -325,22 +325,22 @@ Example: import "core:fmt" import "core:container/intrusive/list" - iterate_next_example :: proc() { + iterate_prev_example :: proc() { l: list.List - one := My_Struct{value=1} - two := My_Struct{value=2} + one := My_Prev_Struct{value=1} + two := My_Prev_Struct{value=2} list.push_back(&l, &one.node) list.push_back(&l, &two.node) - it := list.iterator_tail(l, My_Struct, "node") + it := list.iterator_tail(l, My_Prev_Struct, "node") for num in list.iterate_prev(&it) { fmt.println(num.value) } } - My_Struct :: struct { + My_Prev_Struct :: struct { node : list.Node, value: int, } diff --git a/core/container/lru/lru_cache.odin b/core/container/lru/lru_cache.odin index f8aa55dc2..d95e8e1d2 100644 --- a/core/container/lru/lru_cache.odin +++ b/core/container/lru/lru_cache.odin @@ -129,7 +129,7 @@ remove :: proc(c: ^$C/Cache($Key, $Value), key: Key) -> bool { return false } _remove_node(c, e) - free(node, c.node_allocator) + free(e, c.node_allocator) c.count -= 1 return true } diff --git a/core/container/priority_queue/priority_queue.odin b/core/container/priority_queue/priority_queue.odin index 8a6d77288..c62a821f4 100644 --- a/core/container/priority_queue/priority_queue.odin +++ b/core/container/priority_queue/priority_queue.odin @@ -1,6 +1,7 @@ package container_priority_queue import "base:builtin" +import "base:runtime" Priority_Queue :: struct($T: typeid) { queue: [dynamic]T, @@ -17,13 +18,14 @@ default_swap_proc :: proc($T: typeid) -> proc(q: []T, i, j: int) { } } -init :: proc(pq: ^$Q/Priority_Queue($T), less: proc(a, b: T) -> bool, swap: proc(q: []T, i, j: int), capacity := DEFAULT_CAPACITY, allocator := context.allocator) { +init :: proc(pq: ^$Q/Priority_Queue($T), less: proc(a, b: T) -> bool, swap: proc(q: []T, i, j: int), capacity := DEFAULT_CAPACITY, allocator := context.allocator) -> (err: runtime.Allocator_Error) { if pq.queue.allocator.procedure == nil { pq.queue.allocator = allocator } - reserve(pq, capacity) + reserve(pq, capacity) or_return pq.less = less pq.swap = swap + return .None } init_from_dynamic_array :: proc(pq: ^$Q/Priority_Queue($T), queue: [dynamic]T, less: proc(a, b: T) -> bool, swap: proc(q: []T, i, j: int)) { @@ -41,8 +43,8 @@ destroy :: proc(pq: ^$Q/Priority_Queue($T)) { delete(pq.queue) } -reserve :: proc(pq: ^$Q/Priority_Queue($T), capacity: int) { - builtin.reserve(&pq.queue, capacity) +reserve :: proc(pq: ^$Q/Priority_Queue($T), capacity: int) -> (err: runtime.Allocator_Error) { + return builtin.reserve(&pq.queue, capacity) } clear :: proc(pq: ^$Q/Priority_Queue($T)) { builtin.clear(&pq.queue) @@ -103,9 +105,10 @@ fix :: proc(pq: ^$Q/Priority_Queue($T), i: int) { } } -push :: proc(pq: ^$Q/Priority_Queue($T), value: T) { - append(&pq.queue, value) +push :: proc(pq: ^$Q/Priority_Queue($T), value: T) -> (err: runtime.Allocator_Error) { + append(&pq.queue, value) or_return _shift_up(pq, builtin.len(pq.queue)-1) + return .None } pop :: proc(pq: ^$Q/Priority_Queue($T), loc := #caller_location) -> (value: T) { @@ -130,12 +133,10 @@ pop_safe :: proc(pq: ^$Q/Priority_Queue($T), loc := #caller_location) -> (value: remove :: proc(pq: ^$Q/Priority_Queue($T), i: int) -> (value: T, ok: bool) { n := builtin.len(pq.queue) if 0 <= i && i < n { - if n != i { - pq.swap(pq.queue[:], i, n) - _shift_down(pq, i, n) - _shift_up(pq, i) - } - value, ok = builtin.pop_safe(&pq.queue) + pq.swap(pq.queue[:], i, n-1) + _shift_down(pq, i, n-1) + _shift_up(pq, i) + value, ok = builtin.pop(&pq.queue), true } return } diff --git a/core/container/queue/queue.odin b/core/container/queue/queue.odin index d1040a7c9..8de8d55c0 100644 --- a/core/container/queue/queue.odin +++ b/core/container/queue/queue.odin @@ -4,7 +4,13 @@ import "base:builtin" import "base:runtime" _ :: runtime -// Dynamically resizable double-ended queue/ring-buffer +/* +`Queue` is a dynamically resizable double-ended queue/ring-buffer. + +Being double-ended means that either end may be pushed onto or popped from +across the same block of memory, in any order, thus providing both stack and +queue-like behaviors in the same data structure. +*/ Queue :: struct($T: typeid) { data: [dynamic]T, len: uint, @@ -13,18 +19,31 @@ Queue :: struct($T: typeid) { DEFAULT_CAPACITY :: 16 -// Procedure to initialize a queue -init :: proc(q: ^$Q/Queue($T), capacity := DEFAULT_CAPACITY, allocator := context.allocator) -> runtime.Allocator_Error { - if q.data.allocator.procedure == nil { - q.data.allocator = allocator - } +/* +Initialize a `Queue` with a starting `capacity` and an `allocator`. +*/ +init :: proc(q: ^$Q/Queue($T), capacity := DEFAULT_CAPACITY, allocator := context.allocator, loc := #caller_location) -> runtime.Allocator_Error { clear(q) - return reserve(q, capacity) + q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{ + data = nil, + len = 0, + cap = 0, + allocator = allocator, + } + return reserve(q, capacity, loc) } -// Procedure to initialize a queue from a fixed backing slice. -// The contents of the `backing` will be overwritten as items are pushed onto the `Queue`. -// Any previous contents are not available. +/* +Initialize a `Queue` from a fixed `backing` slice into which modifications are +made directly. + +The contents of the `backing` will be overwritten as items are pushed onto the +`Queue`. Any previous contents will not be available through the API but are +not explicitly zeroed either. + +Note that procedures which need space to work (`push_back`, ...) will fail if +the backing slice runs out of space. +*/ init_from_slice :: proc(q: ^$Q/Queue($T), backing: []T) -> bool { clear(q) q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{ @@ -36,8 +55,14 @@ init_from_slice :: proc(q: ^$Q/Queue($T), backing: []T) -> bool { return true } -// Procedure to initialize a queue from a fixed backing slice. -// Existing contents are preserved and available on the queue. +/* +Initialize a `Queue` from a fixed `backing` slice into which modifications are +made directly. + +The contents of the queue will start out with all of the elements in `backing`, +effectively creating a full queue from the slice. As such, no procedures will +be able to add more elements to the queue until some are taken off. +*/ init_with_contents :: proc(q: ^$Q/Queue($T), backing: []T) -> bool { clear(q) q.data = transmute([dynamic]T)runtime.Raw_Dynamic_Array{ @@ -50,87 +75,203 @@ init_with_contents :: proc(q: ^$Q/Queue($T), backing: []T) -> bool { return true } -// Procedure to destroy a queue +/* +Delete memory that has been dynamically allocated from a `Queue` that was setup with `init`. + +Note that this procedure should not be used on queues setup with +`init_from_slice` or `init_with_contents`, as neither of those procedures keep +track of the allocator state of the underlying `backing` slice. +*/ destroy :: proc(q: ^$Q/Queue($T)) { delete(q.data) } -// The length of the queue +/* +Return the length of the queue. +*/ len :: proc(q: $Q/Queue($T)) -> int { return int(q.len) } -// The current capacity of the queue +/* +Return the capacity of the queue. +*/ cap :: proc(q: $Q/Queue($T)) -> int { return builtin.len(q.data) } -// Remaining space in the queue (cap-len) +/* +Return the remaining space in the queue. + +This will be `cap() - len()`. +*/ space :: proc(q: $Q/Queue($T)) -> int { return builtin.len(q.data) - int(q.len) } -// Reserve enough space for at least the specified capacity -reserve :: proc(q: ^$Q/Queue($T), capacity: int) -> runtime.Allocator_Error { +/* +Reserve enough space in the queue for at least the specified capacity. + +This may return an error if allocation failed. +*/ +reserve :: proc(q: ^$Q/Queue($T), capacity: int, loc := #caller_location) -> runtime.Allocator_Error { if capacity > space(q^) { - return _grow(q, uint(capacity)) + return _grow(q, uint(capacity), loc) } return nil } +/* +Shrink a queue's dynamically allocated array. + +This has no effect if the queue was initialized with a backing slice. +*/ +shrink :: proc(q: ^$Q/Queue($T), temp_allocator := context.temp_allocator, loc := #caller_location) { + if q.data.allocator.procedure == runtime.nil_allocator_proc { + return + } + + if q.len > 0 && q.offset > 0 { + // Make the array contiguous again. + buffer := make([]T, q.len, temp_allocator) + defer delete(buffer, temp_allocator) + + right := uint(builtin.len(q.data)) - q.offset + copy(buffer[:], q.data[q.offset:]) + copy(buffer[right:], q.data[:q.offset]) + + copy(q.data[:], buffer[:]) + + q.offset = 0 + } + + builtin.shrink(&q.data, q.len, loc) +} + +/* +Get the element at index `i`. +This will raise a bounds checking error if `i` is an invalid index. +*/ get :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> T { - runtime.bounds_check_error_loc(loc, i, builtin.len(q.data)) + runtime.bounds_check_error_loc(loc, i, int(q.len)) idx := (uint(i)+q.offset)%builtin.len(q.data) return q.data[idx] } -front :: proc(q: ^$Q/Queue($T)) -> T { +/* +Get a pointer to the element at index `i`. + +This will raise a bounds checking error if `i` is an invalid index. +*/ +get_ptr :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> ^T { + runtime.bounds_check_error_loc(loc, i, int(q.len)) + + idx := (uint(i)+q.offset)%builtin.len(q.data) + return &q.data[idx] +} + +/* +Set the element at index `i` to `val`. + +This will raise a bounds checking error if `i` is an invalid index. +*/ +set :: proc(q: ^$Q/Queue($T), #any_int i: int, val: T, loc := #caller_location) { + runtime.bounds_check_error_loc(loc, i, int(q.len)) + + idx := (uint(i)+q.offset)%builtin.len(q.data) + q.data[idx] = val +} + +/* +Get the element at the front of the queue. + +This will raise a bounds checking error if the queue is empty. +*/ +front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> T { + when !ODIN_NO_BOUNDS_CHECK { + ensure(q.len > 0, "Queue is empty.", loc) + } return q.data[q.offset] } -front_ptr :: proc(q: ^$Q/Queue($T)) -> ^T { + +/* +Get a pointer to the element at the front of the queue. + +This will raise a bounds checking error if the queue is empty. +*/ +front_ptr :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T { + when !ODIN_NO_BOUNDS_CHECK { + ensure(q.len > 0, "Queue is empty.", loc) + } return &q.data[q.offset] } -back :: proc(q: ^$Q/Queue($T)) -> T { +/* +Get the element at the back of the queue. + +This will raise a bounds checking error if the queue is empty. +*/ +back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> T { + when !ODIN_NO_BOUNDS_CHECK { + ensure(q.len > 0, "Queue is empty.", loc) + } idx := (q.offset+uint(q.len - 1))%builtin.len(q.data) return q.data[idx] } -back_ptr :: proc(q: ^$Q/Queue($T)) -> ^T { + +/* +Get a pointer to the element at the back of the queue. + +This will raise a bounds checking error if the queue is empty. +*/ +back_ptr :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T { + when !ODIN_NO_BOUNDS_CHECK { + ensure(q.len > 0, "Queue is empty.", loc) + } idx := (q.offset+uint(q.len - 1))%builtin.len(q.data) return &q.data[idx] } -set :: proc(q: ^$Q/Queue($T), #any_int i: int, val: T, loc := #caller_location) { - runtime.bounds_check_error_loc(loc, i, builtin.len(q.data)) - - idx := (uint(i)+q.offset)%builtin.len(q.data) - q.data[idx] = val -} -get_ptr :: proc(q: ^$Q/Queue($T), #any_int i: int, loc := #caller_location) -> ^T { - runtime.bounds_check_error_loc(loc, i, builtin.len(q.data)) - - idx := (uint(i)+q.offset)%builtin.len(q.data) - return &q.data[idx] -} +@(deprecated="Use `front_ptr` instead") peek_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T { - runtime.bounds_check_error_loc(loc, 0, builtin.len(q.data)) - idx := q.offset%builtin.len(q.data) - return &q.data[idx] + return front_ptr(q, loc) } +@(deprecated="Use `back_ptr` instead") peek_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> ^T { - runtime.bounds_check_error_loc(loc, int(q.len - 1), builtin.len(q.data)) - idx := (uint(q.len - 1)+q.offset)%builtin.len(q.data) - return &q.data[idx] + return back_ptr(q, loc) } -// Push an element to the back of the queue -push_back :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error) { +/* +Push an element to the back of the queue. + +If there is no more space left and allocation fails to get more, this will +return false with an `Allocator_Error`. + +Example: + + import "base:runtime" + import "core:container/queue" + + // This demonstrates typical queue behavior (First-In First-Out). + main :: proc() { + q: queue.Queue(int) + queue.init(&q) + queue.push_back(&q, 1) + queue.push_back(&q, 2) + queue.push_back(&q, 3) + // q.data is now [1, 2, 3, ...] + assert(queue.pop_front(&q) == 1) + assert(queue.pop_front(&q) == 2) + assert(queue.pop_front(&q) == 3) + } +*/ +push_back :: proc(q: ^$Q/Queue($T), elem: T, loc := #caller_location) -> (ok: bool, err: runtime.Allocator_Error) { if space(q^) == 0 { - _grow(q) or_return + _grow(q, loc = loc) or_return } idx := (q.offset+uint(q.len))%builtin.len(q.data) q.data[idx] = elem @@ -138,27 +279,78 @@ push_back :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocato return true, nil } -// Push an element to the front of the queue -push_front :: proc(q: ^$Q/Queue($T), elem: T) -> (ok: bool, err: runtime.Allocator_Error) { +/* +Push an element to the front of the queue. + +If there is no more space left and allocation fails to get more, this will +return false with an `Allocator_Error`. + +Example: + + import "base:runtime" + import "core:container/queue" + + // This demonstrates stack behavior (First-In Last-Out). + main :: proc() { + q: queue.Queue(int) + queue.init(&q) + queue.push_back(&q, 1) + queue.push_back(&q, 2) + queue.push_back(&q, 3) + // q.data is now [1, 2, 3, ...] + assert(queue.pop_back(&q) == 3) + assert(queue.pop_back(&q) == 2) + assert(queue.pop_back(&q) == 1) + } +*/ +push_front :: proc(q: ^$Q/Queue($T), elem: T, loc := #caller_location) -> (ok: bool, err: runtime.Allocator_Error) { if space(q^) == 0 { - _grow(q) or_return - } + _grow(q, loc = loc) or_return + } q.offset = uint(q.offset - 1 + builtin.len(q.data)) % builtin.len(q.data) q.len += 1 q.data[q.offset] = elem return true, nil } +/* +Pop an element from the back of the queue. -// Pop an element from the back of the queue +This will raise a bounds checking error if the queue is empty. + +Example: + + import "base:runtime" + import "core:container/queue" + + // This demonstrates stack behavior (First-In Last-Out) at the far end of the data array. + main :: proc() { + q: queue.Queue(int) + queue.init(&q) + queue.push_front(&q, 1) + queue.push_front(&q, 2) + queue.push_front(&q, 3) + // q.data is now [..., 3, 2, 1] + log.infof("%#v", q) + assert(queue.pop_front(&q) == 3) + assert(queue.pop_front(&q) == 2) + assert(queue.pop_front(&q) == 1) + } +*/ pop_back :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) { - assert(condition=q.len > 0, loc=loc) + when !ODIN_NO_BOUNDS_CHECK { + ensure(q.len > 0, "Queue is empty.", loc) + } q.len -= 1 idx := (q.offset+uint(q.len))%builtin.len(q.data) elem = q.data[idx] return } -// Safely pop an element from the back of the queue + +/* +Pop an element from the back of the queue if one exists and return true. +Otherwise, return a nil element and false. +*/ pop_back_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) { if q.len > 0 { q.len -= 1 @@ -169,15 +361,25 @@ pop_back_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) { return } -// Pop an element from the front of the queue +/* +Pop an element from the front of the queue + +This will raise a bounds checking error if the queue is empty. +*/ pop_front :: proc(q: ^$Q/Queue($T), loc := #caller_location) -> (elem: T) { - assert(condition=q.len > 0, loc=loc) + when !ODIN_NO_BOUNDS_CHECK { + ensure(q.len > 0, "Queue is empty.", loc) + } elem = q.data[q.offset] q.offset = (q.offset+1)%builtin.len(q.data) q.len -= 1 return } -// Safely pop an element from the front of the queue + +/* +Pop an element from the front of the queue if one exists and return true. +Otherwise, return a nil element and false. +*/ pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) { if q.len > 0 { elem = q.data[q.offset] @@ -188,13 +390,18 @@ pop_front_safe :: proc(q: ^$Q/Queue($T)) -> (elem: T, ok: bool) { return } -// Push multiple elements to the back of the queue -push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime.Allocator_Error) { +/* +Push many elements at once to the back of the queue. + +If there is not enough space left and allocation fails to get more, this will +return false with an `Allocator_Error`. +*/ +push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T, loc := #caller_location) -> (ok: bool, err: runtime.Allocator_Error) { n := uint(builtin.len(elems)) if space(q^) < int(n) { - _grow(q, q.len + n) or_return + _grow(q, q.len + n, loc) or_return } - + sz := uint(builtin.len(q.data)) insert_from := (q.offset + q.len) % sz insert_to := n @@ -207,19 +414,31 @@ push_back_elems :: proc(q: ^$Q/Queue($T), elems: ..T) -> (ok: bool, err: runtime return true, nil } -// Consume `n` elements from the front of the queue +/* +Consume `n` elements from the back of the queue. + +This will raise a bounds checking error if the queue does not have enough elements. +*/ consume_front :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) { - assert(condition=int(q.len) >= n, loc=loc) + when !ODIN_NO_BOUNDS_CHECK { + ensure(q.len >= uint(n), "Queue does not have enough elements to consume.", loc) + } if n > 0 { nu := uint(n) q.offset = (q.offset + nu) % builtin.len(q.data) - q.len -= nu + q.len -= nu } } -// Consume `n` elements from the back of the queue +/* +Consume `n` elements from the back of the queue. + +This will raise a bounds checking error if the queue does not have enough elements. +*/ consume_back :: proc(q: ^$Q/Queue($T), n: int, loc := #caller_location) { - assert(condition=int(q.len) >= n, loc=loc) + when !ODIN_NO_BOUNDS_CHECK { + ensure(q.len >= uint(n), "Queue does not have enough elements to consume.", loc) + } if n > 0 { q.len -= uint(n) } @@ -231,9 +450,14 @@ append_elem :: push_back append_elems :: push_back_elems push :: proc{push_back, push_back_elems} append :: proc{push_back, push_back_elems} +enqueue :: push_back +dequeue :: pop_front -// Clear the contents of the queue +/* +Reset the queue's length and offset to zero, letting it write new elements over +old memory, in effect clearing the accessible contents. +*/ clear :: proc(q: ^$Q/Queue($T)) { q.len = 0 q.offset = 0 @@ -241,10 +465,10 @@ clear :: proc(q: ^$Q/Queue($T)) { // Internal growing procedure -_grow :: proc(q: ^$Q/Queue($T), min_capacity: uint = 0) -> runtime.Allocator_Error { +_grow :: proc(q: ^$Q/Queue($T), min_capacity: uint = 0, loc := #caller_location) -> runtime.Allocator_Error { new_capacity := max(min_capacity, uint(8), uint(builtin.len(q.data))*2) n := uint(builtin.len(q.data)) - builtin.resize(&q.data, int(new_capacity)) or_return + builtin.resize(&q.data, int(new_capacity), loc) or_return if q.offset + q.len > n { diff := n - q.offset copy(q.data[new_capacity-diff:], q.data[q.offset:][:diff]) diff --git a/core/container/small_array/doc.odin b/core/container/small_array/doc.odin new file mode 100644 index 000000000..f3e9acd57 --- /dev/null +++ b/core/container/small_array/doc.odin @@ -0,0 +1,55 @@ +/* +Package small_array implements a dynamic array like +interface on a stack-allocated, fixed-size array. + +The Small_Array type is optimal for scenarios where you need +a container for a fixed number of elements of a specific type, +with the total number known at compile time but the exact +number to be used determined at runtime. + +Example: + import "core:fmt" + import "core:container/small_array" + + create :: proc() -> (result: small_array.Small_Array(10, rune)) { + // appending single elements + small_array.push(&result, 'e') + // pushing a bunch of elements at once + small_array.push(&result, 'l', 'i', 'x', '-', 'e') + // pre-pending + small_array.push_front(&result, 'H') + // removing elements + small_array.ordered_remove(&result, 4) + // resizing to the desired length (the capacity will stay unchanged) + small_array.resize(&result, 7) + // inserting elements + small_array.inject_at(&result, 'p', 5) + // updating elements + small_array.set(&result, 3, 'l') + // getting pointers to elements + o := small_array.get_ptr(&result, 4) + o^ = 'o' + // and much more .... + return + } + + // the Small_Array can be an ordinary parameter 'generic' over + // the actual length to be usable with different sizes + print_elements :: proc(arr: ^small_array.Small_Array($N, rune)) { + for r in small_array.slice(arr) { + fmt.print(r) + } + } + + main :: proc() { + arr := create() + // ... + print_elements(&arr) + } + +Output: + + Hellope + +*/ +package container_small_array diff --git a/core/container/small_array/small_array.odin b/core/container/small_array/small_array.odin index 77bb21cbc..1d9795db8 100644 --- a/core/container/small_array/small_array.odin +++ b/core/container/small_array/small_array.odin @@ -1,62 +1,367 @@ package container_small_array import "base:builtin" -import "base:runtime" -_ :: runtime +@require import "base:intrinsics" +@require import "base:runtime" +/* +A fixed-size stack-allocated array operated on in a dynamic fashion. + +Fields: +- `data`: The underlying array +- `len`: Amount of items that the `Small_Array` currently holds + +Example: + + import "core:container/small_array" + + example :: proc() { + a: small_array.Small_Array(100, int) + small_array.push_back(&a, 10) + } +*/ Small_Array :: struct($N: int, $T: typeid) where N >= 0 { data: [N]T, len: int, } +/* +Returns the amount of items in the small-array. + +**Inputs** +- `a`: The small-array +**Returns** +- the amount of items in the array +*/ len :: proc "contextless" (a: $A/Small_Array) -> int { return a.len } +/* +Returns the capacity of the small-array. + +**Inputs** +- `a`: The small-array + +**Returns** the capacity +*/ cap :: proc "contextless" (a: $A/Small_Array) -> int { return builtin.len(a.data) } +/* +Returns how many more items the small-array could fit. + +**Inputs** +- `a`: The small-array + +**Returns** +- the number of unused slots +*/ space :: proc "contextless" (a: $A/Small_Array) -> int { return builtin.len(a.data) - a.len } +/* +Returns a slice of the data. + +**Inputs** +- `a`: The pointer to the small-array + +**Returns** +- the slice + +Example: + + import "core:container/small_array" + import "core:fmt" + + slice_example :: proc() { + print :: proc(a: ^small_array.Small_Array($N, int)) { + for item in small_array.slice(a) { + fmt.println(item) + } + } + + a: small_array.Small_Array(5, int) + small_array.push_back(&a, 1) + small_array.push_back(&a, 2) + print(&a) + } + +Output: + + 1 + 2 +*/ slice :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> []T { return a.data[:a.len] } +/* +Get a copy of the item at the specified position. +This operation assumes that the small-array is large enough. + +This will result in: + - the value if 0 <= index < len + - the zero value of the type if len < index < capacity + - 'crash' if capacity < index or index < 0 +**Inputs** +- `a`: The small-array +- `index`: The position of the item to get + +**Returns** +- the element at the specified position +*/ get :: proc "contextless" (a: $A/Small_Array($N, $T), index: int) -> T { return a.data[index] } + +/* +Get a pointer to the item at the specified position. +This operation assumes that the small-array is large enough. + +This will result in: + - the pointer if 0 <= index < len + - the pointer to the zero value if len < index < capacity + - 'crash' if capacity < index or index < 0 + +**Inputs** +- `a`: A pointer to the small-array +- `index`: The position of the item to get + +**Returns** +- the pointer to the element at the specified position +*/ get_ptr :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int) -> ^T { return &a.data[index] } -get_safe :: proc(a: $A/Small_Array($N, $T), index: int) -> (T, bool) #no_bounds_check { +/* +Attempt to get a copy of the item at the specified position. + +**Inputs** +- `a`: The small-array +- `index`: The position of the item to get + +**Returns** +- the element at the specified position +- true if element exists, false otherwise + +Example: + + import "core:container/small_array" + import "core:fmt" + + get_safe_example :: proc() { + a: small_array.Small_Array(5, rune) + small_array.push_back(&a, 'A') + + fmt.println(small_array.get_safe(a, 0) or_else 'x') + fmt.println(small_array.get_safe(a, 1) or_else 'x') + } + +Output: + + A + x + +*/ +get_safe :: proc "contextless" (a: $A/Small_Array($N, $T), index: int) -> (T, bool) #no_bounds_check { if index < 0 || index >= a.len { return {}, false } return a.data[index], true } -get_ptr_safe :: proc(a: ^$A/Small_Array($N, $T), index: int) -> (^T, bool) #no_bounds_check { +/* +Get a pointer to the item at the specified position. + +**Inputs** +- `a`: A pointer to the small-array +- `index`: The position of the item to get + +**Returns** +- the pointer to the element at the specified position +- true if element exists, false otherwise +*/ +get_ptr_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int) -> (^T, bool) #no_bounds_check { if index < 0 || index >= a.len { return {}, false } return &a.data[index], true } +/* +Set the element at the specified position to the given value. +This operation assumes that the small-array is large enough. + +This will result in: + - the value being set if 0 <= index < capacity + - 'crash' otherwise + +**Inputs** +- `a`: A pointer to the small-array +- `index`: The position of the item to set +- `value`: The value to set the element to + +Example: + + import "core:container/small_array" + import "core:fmt" + + set_example :: proc() { + a: small_array.Small_Array(5, rune) + small_array.push_back(&a, 'A') + small_array.push_back(&a, 'B') + fmt.println(small_array.slice(&a)) + + // updates index 0 + small_array.set(&a, 0, 'Z') + fmt.println(small_array.slice(&a)) + + // updates to a position x, where + // len <= x < cap are not visible since + // the length of the small-array remains unchanged + small_array.set(&a, 2, 'X') + small_array.set(&a, 3, 'Y') + small_array.set(&a, 4, 'Z') + fmt.println(small_array.slice(&a)) + + // resizing makes the change visible + small_array.non_zero_resize(&a, 100) + fmt.println(small_array.slice(&a)) + } + +Output: + + [A, B] + [Z, B] + [Z, B] + [Z, B, X, Y, Z] + +*/ set :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, item: T) { a.data[index] = item } -resize :: proc "contextless" (a: ^$A/Small_Array, length: int) { +/* +Tries to resize the small-array to the specified length. + +The memory of added elements will be zeroed out. + +The new length will be: + - `length` if `length` <= capacity + - capacity if length > capacity + +**Inputs** +- `a`: A pointer to the small-array +- `length`: The new desired length + +Example: + + import "core:container/small_array" + import "core:fmt" + + resize_example :: proc() { + a: small_array.Small_Array(5, int) + + small_array.push_back(&a, 1) + small_array.push_back(&a, 2) + fmt.println(small_array.slice(&a)) + + small_array.resize(&a, 1) + fmt.println(small_array.slice(&a)) + + small_array.resize(&a, 100) + fmt.println(small_array.slice(&a)) + } + +Output: + + [1, 2] + [1] + [1, 0, 0, 0, 0] +*/ +resize :: proc "contextless" (a: ^$A/Small_Array($N, $T), length: int) { + prev_len := a.len a.len = min(length, builtin.len(a.data)) + if prev_len < a.len { + intrinsics.mem_zero(&a.data[prev_len], size_of(T)*(a.len-prev_len)) + } } +/* +Tries to resize the small-array to the specified length. + +The new length will be: + - `length` if `length` <= capacity + - capacity if length > capacity + +**Inputs** +- `a`: A pointer to the small-array +- `length`: The new desired length + +Example: + + import "core:container/small_array" + import "core:fmt" + + non_zero_resize :: proc() { + a: small_array.Small_Array(5, int) + + small_array.push_back(&a, 1) + small_array.push_back(&a, 2) + fmt.println(small_array.slice(&a)) + + small_array.non_zero_resize(&a, 1) + fmt.println(small_array.slice(&a)) + + small_array.non_zero_resize(&a, 100) + fmt.println(small_array.slice(&a)) + } + +Output: + [1, 2] + [1] + [1, 2, 0, 0, 0] +*/ +non_zero_resize :: proc "contextless" (a: ^$A/Small_Array, length: int) { + a.len = min(length, builtin.len(a.data)) +} + +/* +Attempts to add the given element to the end. + +**Inputs** +- `a`: A pointer to the small-array +- `item`: The item to append + +**Returns** +- true if there was enough space to fit the element, false otherwise + +Example: + + import "core:container/small_array" + import "core:fmt" + + push_back_example :: proc() { + a: small_array.Small_Array(2, int) + + assert(small_array.push_back(&a, 1), "this should fit") + assert(small_array.push_back(&a, 2), "this should fit") + assert(!small_array.push_back(&a, 3), "this should not fit") + + fmt.println(small_array.slice(&a)) + } + +Output: + + [1, 2] +*/ push_back :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool { if a.len < cap(a^) { a.data[a.len] = item @@ -66,6 +371,39 @@ push_back :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool { return false } +/* +Attempts to add the given element at the beginning. +This operation assumes that the small-array is not empty. + +Note: Performing this operation will cause pointers obtained +through get_ptr(_save) to reference incorrect elements. + +**Inputs** +- `a`: A pointer to the small-array +- `item`: The item to append + +**Returns** +- true if there was enough space to fit the element, false otherwise + +Example: + + import "core:container/small_array" + import "core:fmt" + + push_front_example :: proc() { + a: small_array.Small_Array(2, int) + + assert(small_array.push_front(&a, 2), "this should fit") + assert(small_array.push_front(&a, 1), "this should fit") + assert(!small_array.push_back(&a, 0), "this should not fit") + + fmt.println(small_array.slice(&a)) + } + +Output: + + [1, 2] +*/ push_front :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool { if a.len < cap(a^) { a.len += 1 @@ -77,6 +415,35 @@ push_front :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool { return false } +/* +Removes and returns the last element of the small-array. +This operation assumes that the small-array is not empty. + +**Inputs** +- `a`: A pointer to the small-array + +**Returns** +- a copy of the element removed from the end of the small-array + +Example: + + import "core:container/small_array" + import "core:fmt" + + pop_back_example :: proc() { + a: small_array.Small_Array(5, int) + small_array.push(&a, 0, 1, 2) + + fmt.println("BEFORE:", small_array.slice(&a)) + small_array.pop_back(&a) + fmt.println("AFTER: ", small_array.slice(&a)) + } + +Output: + + BEFORE: [0, 1, 2] + AFTER: [0, 1] +*/ pop_back :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) -> T { assert(condition=(N > 0 && a.len > 0), loc=loc) item := a.data[a.len-1] @@ -84,6 +451,38 @@ pop_back :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) -> return item } +/* +Removes and returns the first element of the small-array. +This operation assumes that the small-array is not empty. + +Note: Performing this operation will cause pointers obtained +through get_ptr(_save) to reference incorrect elements. + +**Inputs** +- `a`: A pointer to the small-array + +**Returns** +- a copy of the element removed from the beginning of the small-array + +Example: + + import "core:container/small_array" + import "core:fmt" + + pop_front_example :: proc() { + a: small_array.Small_Array(5, int) + small_array.push(&a, 0, 1, 2) + + fmt.println("BEFORE:", small_array.slice(&a)) + small_array.pop_front(&a) + fmt.println("AFTER: ", small_array.slice(&a)) + } + +Output: + + BEFORE: [0, 1, 2] + AFTER: [1, 2] +*/ pop_front :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) -> T { assert(condition=(N > 0 && a.len > 0), loc=loc) item := a.data[0] @@ -93,6 +492,32 @@ pop_front :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) - return item } +/* +Attempts to remove and return the last element of the small array. +Unlike `pop_back`, it does not assume that the array is non-empty. + +**Inputs** +- `a`: A pointer to the small-array + +**Returns** +- a copy of the element removed from the end of the small-array +- true if the small-array was not empty, false otherwise + +Example: + + import "core:container/small_array" + + pop_back_safe_example :: proc() { + a: small_array.Small_Array(3, int) + small_array.push(&a, 1) + + el, ok := small_array.pop_back_safe(&a) + assert(ok, "there was an element in the array") + + el, ok = small_array.pop_back_safe(&a) + assert(!ok, "there was NO element in the array") + } +*/ pop_back_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) { if N > 0 && a.len > 0 { item = a.data[a.len-1] @@ -102,6 +527,35 @@ pop_back_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, ok return } +/* +Attempts to remove and return the first element of the small array. +Unlike `pop_front`, it does not assume that the array is non-empty. + +Note: Performing this operation will cause pointers obtained +through get_ptr(_save) to reference incorrect elements. + +**Inputs** +- `a`: A pointer to the small-array + +**Returns** +- a copy of the element removed from the beginning of the small-array +- true if the small-array was not empty, false otherwise + +Example: + + import "core:container/small_array" + + pop_front_safe_example :: proc() { + a: small_array.Small_Array(3, int) + small_array.push(&a, 1) + + el, ok := small_array.pop_front_safe(&a) + assert(ok, "there was an element in the array") + + el, ok = small_array.pop_front_(&a) + assert(!ok, "there was NO element in the array") + } +*/ pop_front_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) { if N > 0 && a.len > 0 { item = a.data[0] @@ -113,11 +567,70 @@ pop_front_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, o return } +/* +Decreases the length of the small-array by the given amount. +The elements are therefore not really removed and can be +recovered by calling `resize`. + +Note: This procedure assumes that the array has a sufficient length. + +**Inputs** +- `a`: A pointer to the small-array +- `count`: The amount the length should be reduced by + +Example: + + import "core:container/small_array" + import "core:fmt" + + consume_example :: proc() { + a: small_array.Small_Array(3, int) + small_array.push(&a, 0, 1, 2) + + fmt.println("BEFORE:", small_array.slice(&a)) + small_array.consume(&a, 2) + fmt.println("AFTER :", small_array.slice(&a)) + } + +Output: + + BEFORE: [0, 1, 2] + AFTER : [0] +*/ consume :: proc "odin" (a: ^$A/Small_Array($N, $T), count: int, loc := #caller_location) { assert(condition=a.len >= count, loc=loc) a.len -= count } +/* +Removes the element at the specified index while retaining order. + +Note: Performing this operation will cause pointers obtained +through get_ptr(_save) to reference incorrect elements. + +**Inputs** +- `a`: A pointer to the small-array +- `index`: The position of the element to remove + +Example: + + import "core:container/small_array" + import "core:fmt" + + ordered_remove_example :: proc() { + a: small_array.Small_Array(4, int) + small_array.push(&a, 0, 1, 2, 3) + + fmt.println("BEFORE:", small_array.slice(&a)) + small_array.ordered_remove(&a, 1) + fmt.println("AFTER :", small_array.slice(&a)) + } + +Output: + + BEFORE: [0, 1, 2, 3] + AFTER : [0, 2, 3] +*/ ordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check { runtime.bounds_check_error_loc(loc, index, a.len) if index+1 < a.len { @@ -126,6 +639,32 @@ ordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, lo a.len -= 1 } +/* +Removes the element at the specified index without retaining order. + +**Inputs** +- `a`: A pointer to the small-array +- `index`: The position of the element to remove + +Example: + + import "core:container/small_array" + import "core:fmt" + + unordered_remove_example :: proc() { + a: small_array.Small_Array(4, int) + small_array.push(&a, 0, 1, 2, 3) + + fmt.println("BEFORE:", small_array.slice(&a)) + small_array.unordered_remove(&a, 1) + fmt.println("AFTER :", small_array.slice(&a)) + } + +Output: + + BEFORE: [0, 1, 2, 3] + AFTER : [0, 3, 2] +*/ unordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check { runtime.bounds_check_error_loc(loc, index, a.len) n := a.len-1 @@ -135,10 +674,63 @@ unordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, a.len -= 1 } +/* +Sets the length of the small-array to 0. + +**Inputs** +- `a`: A pointer to the small-array + +Example: + + import "core:container/small_array" + import "core:fmt" + + clear_example :: proc() { + a: small_array.Small_Array(4, int) + small_array.push(&a, 0, 1, 2, 3) + + fmt.println("BEFORE:", small_array.slice(&a)) + small_array.clear(&a) + fmt.println("AFTER :", small_array.slice(&a)) + } + +Output: + + BEFORE: [0, 1, 2, 3] + AFTER : [] + +*/ clear :: proc "contextless" (a: ^$A/Small_Array($N, $T)) { resize(a, 0) } +/* +Attempts to append all elements to the small-array returning +false if there is not enough space to fit all of them. + +**Inputs** +- `a`: A pointer to the small-array +- `item`: The item to append +- ..: + +**Returns** +- true if there was enough space to fit the element, false otherwise + +Example: + + import "core:container/small_array" + import "core:fmt" + + push_back_elems_example :: proc() { + a: small_array.Small_Array(100, int) + small_array.push_back_elems(&a, 0, 1, 2, 3, 4) + fmt.println(small_array.slice(&a)) + } + +Output: + + [0, 1, 2, 3, 4] +*/ push_back_elems :: proc "contextless" (a: ^$A/Small_Array($N, $T), items: ..T) -> bool { if a.len + builtin.len(items) <= cap(a^) { n := copy(a.data[a.len:], items[:]) @@ -148,6 +740,36 @@ push_back_elems :: proc "contextless" (a: ^$A/Small_Array($N, $T), items: ..T) - return false } +/* +Tries to insert an element at the specified position. + +Note: Performing this operation will cause pointers obtained +through get_ptr(_save) to reference incorrect elements. + +**Inputs** +- `a`: A pointer to the small-array +- `item`: The item to insert +- `index`: The index to insert the item at + +**Returns** +- true if there was enough space to fit the element, false otherwise + +Example: + + import "core:container/small_array" + import "core:fmt" + + inject_at_example :: proc() { + arr: small_array.Small_Array(100, rune) + small_array.push(&arr, 'A', 'C', 'D') + small_array.inject_at(&arr, 'B', 1) + fmt.println(small_array.slice(&arr)) + } + +Output: + + [A, B, C, D] +*/ inject_at :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T, index: int) -> bool #no_bounds_check { if a.len < cap(a^) && index >= 0 && index <= len(a^) { a.len += 1 @@ -160,7 +782,38 @@ inject_at :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T, index: int return false } +// Alias for `push_back` append_elem :: push_back +// Alias for `push_back_elems` append_elems :: push_back_elems + +/* +Tries to append the element(s) to the small-array. + +**Inputs** +- `a`: A pointer to the small-array +- `item`: The item to append +- ..: + +**Returns** +- true if there was enough space to fit the element, false otherwise + +Example: + + import "core:container/small_array" + import "core:fmt" + + push_example :: proc() { + a: small_array.Small_Array(100, int) + small_array.push(&a, 0) + small_array.push(&a, 1, 2, 3, 4) + fmt.println(small_array.slice(&a)) + } + +Output: + + [0, 1, 2, 3, 4] +*/ push :: proc{push_back, push_back_elems} +// Alias for `push` append :: proc{push_back, push_back_elems} diff --git a/core/crypto/_aes/aes.odin b/core/crypto/_aes/aes.odin index 4f52485d2..f458a12fb 100644 --- a/core/crypto/_aes/aes.odin +++ b/core/crypto/_aes/aes.odin @@ -25,4 +25,5 @@ GHASH_BLOCK_SIZE :: 16 GHASH_TAG_SIZE :: 16 // RCON is the AES keyschedule round constants. +@(rodata) RCON := [10]byte{0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36} diff --git a/core/crypto/_aes/ct64/ct64.odin b/core/crypto/_aes/ct64/ct64.odin index f198cab81..af2b42c1e 100644 --- a/core/crypto/_aes/ct64/ct64.odin +++ b/core/crypto/_aes/ct64/ct64.odin @@ -22,8 +22,6 @@ package aes_ct64 -import "base:intrinsics" - // Bitsliced AES for 64-bit general purpose (integer) registers. Each // invocation will process up to 4 blocks at a time. This implementation // is derived from the BearSSL ct64 code, and distributed under a 1-clause @@ -212,11 +210,8 @@ orthogonalize :: proc "contextless" (q: ^[8]u64) { } @(require_results) -interleave_in :: proc "contextless" (w: []u32) -> (q0, q1: u64) #no_bounds_check { - if len(w) < 4 { - intrinsics.trap() - } - x0, x1, x2, x3 := u64(w[0]), u64(w[1]), u64(w[2]), u64(w[3]) +interleave_in :: proc "contextless" (w0, w1, w2, w3: u32) -> (q0, q1: u64) #no_bounds_check { + x0, x1, x2, x3 := u64(w0), u64(w1), u64(w2), u64(w3) x0 |= (x0 << 16) x1 |= (x1 << 16) x2 |= (x2 << 16) diff --git a/core/crypto/_aes/ct64/ct64_enc.odin b/core/crypto/_aes/ct64/ct64_enc.odin index 36d4aebc8..bee6de722 100644 --- a/core/crypto/_aes/ct64/ct64_enc.odin +++ b/core/crypto/_aes/ct64/ct64_enc.odin @@ -22,12 +22,8 @@ package aes_ct64 -import "base:intrinsics" - add_round_key :: proc "contextless" (q: ^[8]u64, sk: []u64) #no_bounds_check { - if len(sk) < 8 { - intrinsics.trap() - } + ensure_contextless(len(sk) >= 8, "aes/ct64: invalid round key size") q[0] ~= sk[0] q[1] ~= sk[1] diff --git a/core/crypto/_aes/ct64/ct64_keysched.odin b/core/crypto/_aes/ct64/ct64_keysched.odin index 060a2c03e..0f00bba57 100644 --- a/core/crypto/_aes/ct64/ct64_keysched.odin +++ b/core/crypto/_aes/ct64/ct64_keysched.odin @@ -22,7 +22,6 @@ package aes_ct64 -import "base:intrinsics" import "core:crypto/_aes" import "core:encoding/endian" import "core:mem" @@ -42,7 +41,7 @@ sub_word :: proc "contextless" (x: u32) -> u32 { } @(private, require_results) -keysched :: proc(comp_skey: []u64, key: []byte) -> int { +keysched :: proc "contextless" (comp_skey: []u64, key: []byte) -> int { num_rounds, key_len := 0, len(key) switch key_len { case _aes.KEY_SIZE_128: @@ -52,7 +51,7 @@ keysched :: proc(comp_skey: []u64, key: []byte) -> int { case _aes.KEY_SIZE_256: num_rounds = _aes.ROUNDS_256 case: - panic("crypto/aes: invalid AES key size") + panic_contextless("crypto/aes: invalid AES key size") } skey: [60]u32 = --- @@ -78,7 +77,7 @@ keysched :: proc(comp_skey: []u64, key: []byte) -> int { q: [8]u64 = --- for i, j := 0, 0; i < nkf; i, j = i + 4, j + 2 { - q[0], q[4] = interleave_in(skey[i:]) + q[0], q[4] = interleave_in(skey[i], skey[i+1], skey[i+2], skey[i+3]) q[1] = q[0] q[2] = q[0] q[3] = q[0] @@ -123,57 +122,3 @@ skey_expand :: proc "contextless" (skey, comp_skey: []u64, num_rounds: int) { skey[v + 3] = (x3 << 4) - x3 } } - -orthogonalize_roundkey :: proc "contextless" (qq: []u64, key: []byte) { - if len(qq) < 8 || len(key) != 16 { - intrinsics.trap() - } - - skey: [4]u32 = --- - skey[0] = endian.unchecked_get_u32le(key[0:]) - skey[1] = endian.unchecked_get_u32le(key[4:]) - skey[2] = endian.unchecked_get_u32le(key[8:]) - skey[3] = endian.unchecked_get_u32le(key[12:]) - - q: [8]u64 = --- - q[0], q[4] = interleave_in(skey[:]) - q[1] = q[0] - q[2] = q[0] - q[3] = q[0] - q[5] = q[4] - q[6] = q[4] - q[7] = q[4] - orthogonalize(&q) - - comp_skey: [2]u64 = --- - comp_skey[0] = - (q[0] & 0x1111111111111111) | - (q[1] & 0x2222222222222222) | - (q[2] & 0x4444444444444444) | - (q[3] & 0x8888888888888888) - comp_skey[1] = - (q[4] & 0x1111111111111111) | - (q[5] & 0x2222222222222222) | - (q[6] & 0x4444444444444444) | - (q[7] & 0x8888888888888888) - - for x, u in comp_skey { - x0 := x - x1, x2, x3 := x0, x0, x0 - x0 &= 0x1111111111111111 - x1 &= 0x2222222222222222 - x2 &= 0x4444444444444444 - x3 &= 0x8888888888888888 - x1 >>= 1 - x2 >>= 2 - x3 >>= 3 - qq[u * 4 + 0] = (x0 << 4) - x0 - qq[u * 4 + 1] = (x1 << 4) - x1 - qq[u * 4 + 2] = (x2 << 4) - x2 - qq[u * 4 + 3] = (x3 << 4) - x3 - } - - mem.zero_explicit(&skey, size_of(skey)) - mem.zero_explicit(&q, size_of(q)) - mem.zero_explicit(&comp_skey, size_of(comp_skey)) -} diff --git a/core/crypto/_aes/ct64/ghash.odin b/core/crypto/_aes/ct64/ghash.odin index a522a481a..0c885d8ba 100644 --- a/core/crypto/_aes/ct64/ghash.odin +++ b/core/crypto/_aes/ct64/ghash.odin @@ -22,7 +22,6 @@ package aes_ct64 -import "base:intrinsics" import "core:crypto/_aes" import "core:encoding/endian" @@ -64,9 +63,8 @@ rev64 :: proc "contextless" (x: u64) -> u64 { // Note: `dst` is both an input and an output, to support easy implementation // of GCM. ghash :: proc "contextless" (dst, key, data: []byte) { - if len(dst) != _aes.GHASH_BLOCK_SIZE || len(key) != _aes.GHASH_BLOCK_SIZE { - intrinsics.trap() - } + ensure_contextless(len(dst) == _aes.GHASH_BLOCK_SIZE) + ensure_contextless(len(key) == _aes.GHASH_BLOCK_SIZE) buf := data l := len(buf) diff --git a/core/crypto/_aes/ct64/helpers.odin b/core/crypto/_aes/ct64/helpers.odin index 169271f6d..7eec5bdc4 100644 --- a/core/crypto/_aes/ct64/helpers.odin +++ b/core/crypto/_aes/ct64/helpers.odin @@ -1,60 +1,61 @@ package aes_ct64 -import "base:intrinsics" import "core:crypto/_aes" import "core:encoding/endian" +@(require_results) +load_interleaved :: proc "contextless" (src: []byte) -> (u64, u64) #no_bounds_check { + w0 := endian.unchecked_get_u32le(src[0:]) + w1 := endian.unchecked_get_u32le(src[4:]) + w2 := endian.unchecked_get_u32le(src[8:]) + w3 := endian.unchecked_get_u32le(src[12:]) + return interleave_in(w0, w1, w2, w3) +} + +store_interleaved :: proc "contextless" (dst: []byte, a0, a1: u64) #no_bounds_check { + w0, w1, w2, w3 := interleave_out(a0, a1) + endian.unchecked_put_u32le(dst[0:], w0) + endian.unchecked_put_u32le(dst[4:], w1) + endian.unchecked_put_u32le(dst[8:], w2) + endian.unchecked_put_u32le(dst[12:], w3) +} + +@(require_results) +xor_interleaved :: #force_inline proc "contextless" (a0, a1, b0, b1: u64) -> (u64, u64) { + return a0 ~ b0, a1 ~ b1 +} + +@(require_results) +and_interleaved :: #force_inline proc "contextless" (a0, a1, b0, b1: u64) -> (u64, u64) { + return a0 & b0, a1 & b1 +} + load_blockx1 :: proc "contextless" (q: ^[8]u64, src: []byte) { - if len(src) != _aes.BLOCK_SIZE { - intrinsics.trap() - } + ensure_contextless(len(src) == _aes.BLOCK_SIZE, "aes/ct64: invalid block size") - w: [4]u32 = --- - w[0] = endian.unchecked_get_u32le(src[0:]) - w[1] = endian.unchecked_get_u32le(src[4:]) - w[2] = endian.unchecked_get_u32le(src[8:]) - w[3] = endian.unchecked_get_u32le(src[12:]) - q[0], q[4] = interleave_in(w[:]) + q[0], q[4] = #force_inline load_interleaved(src) orthogonalize(q) } store_blockx1 :: proc "contextless" (dst: []byte, q: ^[8]u64) { - if len(dst) != _aes.BLOCK_SIZE { - intrinsics.trap() - } + ensure_contextless(len(dst) == _aes.BLOCK_SIZE, "aes/ct64: invalid block size") orthogonalize(q) - w0, w1, w2, w3 := interleave_out(q[0], q[4]) - endian.unchecked_put_u32le(dst[0:], w0) - endian.unchecked_put_u32le(dst[4:], w1) - endian.unchecked_put_u32le(dst[8:], w2) - endian.unchecked_put_u32le(dst[12:], w3) + #force_inline store_interleaved(dst, q[0], q[4]) } load_blocks :: proc "contextless" (q: ^[8]u64, src: [][]byte) { - if n := len(src); n > STRIDE || n == 0 { - intrinsics.trap() - } + ensure_contextless(len(src) == 0 || len(src) <= STRIDE, "aes/ct64: invalid block(s) size") - w: [4]u32 = --- for s, i in src { - if len(s) != _aes.BLOCK_SIZE { - intrinsics.trap() - } - - w[0] = endian.unchecked_get_u32le(s[0:]) - w[1] = endian.unchecked_get_u32le(s[4:]) - w[2] = endian.unchecked_get_u32le(s[8:]) - w[3] = endian.unchecked_get_u32le(s[12:]) - q[i], q[i + 4] = interleave_in(w[:]) + ensure_contextless(len(s) == _aes.BLOCK_SIZE, "aes/ct64: invalid block size") + q[i], q[i + 4] = #force_inline load_interleaved(s) } orthogonalize(q) } store_blocks :: proc "contextless" (dst: [][]byte, q: ^[8]u64) { - if n := len(dst); n > STRIDE || n == 0 { - intrinsics.trap() - } + ensure_contextless(len(dst) == 0 || len(dst) <= STRIDE, "aes/ct64: invalid block(s) size") orthogonalize(q) for d, i in dst { @@ -62,14 +63,7 @@ store_blocks :: proc "contextless" (dst: [][]byte, q: ^[8]u64) { if d == nil { break } - if len(d) != _aes.BLOCK_SIZE { - intrinsics.trap() - } - - w0, w1, w2, w3 := interleave_out(q[i], q[i + 4]) - endian.unchecked_put_u32le(d[0:], w0) - endian.unchecked_put_u32le(d[4:], w1) - endian.unchecked_put_u32le(d[8:], w2) - endian.unchecked_put_u32le(d[12:], w3) + ensure_contextless(len(d) == _aes.BLOCK_SIZE, "aes/ct64: invalid block size") + #force_inline store_interleaved(d, q[i], q[i + 4]) } } diff --git a/core/crypto/_aes/hw_intel/api.odin b/core/crypto/_aes/hw_intel/api.odin index 52669cb35..502d56213 100644 --- a/core/crypto/_aes/hw_intel/api.odin +++ b/core/crypto/_aes/hw_intel/api.odin @@ -6,7 +6,7 @@ import "core:sys/info" // is_supported returns true iff hardware accelerated AES // is supported. is_supported :: proc "contextless" () -> bool { - features, ok := info.cpu_features.? + features, ok := info.cpu.features.? if !ok { return false } diff --git a/core/crypto/_aes/hw_intel/ghash.odin b/core/crypto/_aes/hw_intel/ghash.odin index 4320dd59b..5f51b614b 100644 --- a/core/crypto/_aes/hw_intel/ghash.odin +++ b/core/crypto/_aes/hw_intel/ghash.odin @@ -52,7 +52,7 @@ GHASH_STRIDE_BYTES_HW :: GHASH_STRIDE_HW * _aes.GHASH_BLOCK_SIZE // that it is right-shifted by 1 bit. The left-shift is relatively // inexpensive, and it can be mutualised. // -// Since SSE2 opcodes do not have facilities for shitfting full 128-bit +// Since SSE2 opcodes do not have facilities for shifting full 128-bit // values with bit precision, we have to break down values into 64-bit // chunks. We number chunks from 0 to 3 in left to right order. @@ -155,7 +155,7 @@ square_f128 :: #force_inline proc "contextless" (kw: x86.__m128i) -> (x86.__m128 @(enable_target_feature = "sse2,ssse3,pclmul") ghash :: proc "contextless" (dst, key, data: []byte) #no_bounds_check { if len(dst) != _aes.GHASH_BLOCK_SIZE || len(key) != _aes.GHASH_BLOCK_SIZE { - intrinsics.trap() + panic_contextless("aes/ghash: invalid dst or key size") } // Note: BearSSL opts to copy the remainder into a zero-filled diff --git a/core/crypto/_blake2/blake2.odin b/core/crypto/_blake2/blake2.odin index 2ad74843b..89fbe3a7a 100644 --- a/core/crypto/_blake2/blake2.odin +++ b/core/crypto/_blake2/blake2.odin @@ -18,6 +18,8 @@ BLAKE2S_SIZE :: 32 BLAKE2B_BLOCK_SIZE :: 128 BLAKE2B_SIZE :: 64 +MAX_SIZE :: 255 + Blake2s_Context :: struct { h: [8]u32, t: [2]u32, @@ -68,13 +70,13 @@ Blake2_Tree :: struct { is_last_node: bool, } -@(private) +@(private, rodata) BLAKE2S_IV := [8]u32 { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, } -@(private) +@(private, rodata) BLAKE2B_IV := [8]u64 { 0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1, @@ -82,16 +84,13 @@ BLAKE2B_IV := [8]u64 { 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179, } -init :: proc(ctx: ^$T, cfg: ^Blake2_Config) { +init :: proc "contextless" (ctx: ^$T, cfg: ^Blake2_Config) { when T == Blake2s_Context { max_size :: BLAKE2S_SIZE } else when T == Blake2b_Context { max_size :: BLAKE2B_SIZE } - - if cfg.size > max_size { - panic("blake2: requested output size exceeeds algorithm max") - } + ensure_contextless(cfg.size <= max_size, "blake2: requested output size exceeeds algorithm max") // To save having to allocate a scratch buffer, use the internal // data buffer (`ctx.x`), as it is exactly the correct size. @@ -167,8 +166,8 @@ init :: proc(ctx: ^$T, cfg: ^Blake2_Config) { ctx.is_initialized = true } -update :: proc(ctx: ^$T, p: []byte) { - assert(ctx.is_initialized) +update :: proc "contextless" (ctx: ^$T, p: []byte) { + ensure_contextless(ctx.is_initialized) p := p when T == Blake2s_Context { @@ -195,8 +194,8 @@ update :: proc(ctx: ^$T, p: []byte) { ctx.nx += copy(ctx.x[ctx.nx:], p) } -final :: proc(ctx: ^$T, hash: []byte, finalize_clone: bool = false) { - assert(ctx.is_initialized) +final :: proc "contextless" (ctx: ^$T, hash: []byte, finalize_clone: bool = false) { + ensure_contextless(ctx.is_initialized) ctx := ctx if finalize_clone { @@ -206,24 +205,19 @@ final :: proc(ctx: ^$T, hash: []byte, finalize_clone: bool = false) { } defer(reset(ctx)) + ensure_contextless(len(hash) >= int(ctx.size), "crypto/blake2: invalid destination digest size") when T == Blake2s_Context { - if len(hash) < int(ctx.size) { - panic("crypto/blake2s: invalid destination digest size") - } blake2s_final(ctx, hash) } else when T == Blake2b_Context { - if len(hash) < int(ctx.size) { - panic("crypto/blake2b: invalid destination digest size") - } blake2b_final(ctx, hash) } } -clone :: proc(ctx, other: ^$T) { +clone :: proc "contextless" (ctx, other: ^$T) { ctx^ = other^ } -reset :: proc(ctx: ^$T) { +reset :: proc "contextless" (ctx: ^$T) { if !ctx.is_initialized { return } diff --git a/core/crypto/_chacha20/chacha20.odin b/core/crypto/_chacha20/chacha20.odin index a907209de..1a4b5a507 100644 --- a/core/crypto/_chacha20/chacha20.odin +++ b/core/crypto/_chacha20/chacha20.odin @@ -1,6 +1,5 @@ package _chacha20 -import "base:intrinsics" import "core:encoding/endian" import "core:math/bits" import "core:mem" @@ -46,9 +45,8 @@ Context :: struct { // derivation is expected to be handled by the caller, so that the // HChaCha call can be suitably accelerated. init :: proc "contextless" (ctx: ^Context, key, iv: []byte, is_xchacha: bool) { - if len(key) != KEY_SIZE || len(iv) != IV_SIZE { - intrinsics.trap() - } + ensure_contextless(len(key) == KEY_SIZE, "chacha20: invalid key size") + ensure_contextless(len(iv) == IV_SIZE, "chacha20: invalid key size") k, n := key, iv @@ -76,12 +74,10 @@ init :: proc "contextless" (ctx: ^Context, key, iv: []byte, is_xchacha: bool) { // seek seeks the (X)ChaCha20 stream counter to the specified block. seek :: proc(ctx: ^Context, block_nr: u64) { - assert(ctx._is_initialized) + ensure(ctx._is_initialized) if ctx._is_ietf_flavor { - if block_nr > MAX_CTR_IETF { - panic("crypto/chacha20: attempted to seek past maximum counter") - } + ensure(block_nr <= MAX_CTR_IETF, "crypto/chacha20: attempted to seek past maximum counter") } else { ctx._s[13] = u32(block_nr >> 32) } @@ -102,7 +98,7 @@ check_counter_limit :: proc(ctx: ^Context, nr_blocks: int) { // Enforce the maximum consumed keystream per IV. // // While all modern "standard" definitions of ChaCha20 use - // the IETF 32-bit counter, for XChaCha20 most common + // the IETF 32-bit counter, for XChaCha20 historical // implementations allow for a 64-bit counter. // // Honestly, the answer here is "use a MRAE primitive", but @@ -110,14 +106,14 @@ check_counter_limit :: proc(ctx: ^Context, nr_blocks: int) { ERR_CTR_EXHAUSTED :: "crypto/chacha20: maximum (X)ChaCha20 keystream per IV reached" + ctr_ok: bool if ctx._is_ietf_flavor { - if u64(ctx._s[12]) + u64(nr_blocks) > MAX_CTR_IETF { - panic(ERR_CTR_EXHAUSTED) - } + ctr_ok = u64(ctx._s[12]) + u64(nr_blocks) <= MAX_CTR_IETF } else { ctr := (u64(ctx._s[13]) << 32) | u64(ctx._s[12]) - if _, carry := bits.add_u64(ctr, u64(nr_blocks), 0); carry != 0 { - panic(ERR_CTR_EXHAUSTED) - } + _, carry := bits.add_u64(ctr, u64(nr_blocks), 0) + ctr_ok = carry == 0 } + + ensure(ctr_ok, "crypto/chacha20: maximum (X)ChaCha20 keystream per IV reached") } diff --git a/core/crypto/_chacha20/simd128/chacha20_simd128.odin b/core/crypto/_chacha20/simd128/chacha20_simd128.odin index fe0d0d518..4bf40e240 100644 --- a/core/crypto/_chacha20/simd128/chacha20_simd128.odin +++ b/core/crypto/_chacha20/simd128/chacha20_simd128.odin @@ -29,11 +29,24 @@ when ODIN_ARCH == .arm64 || ODIN_ARCH == .arm32 { // explicitly using simd.u8x16 shuffles. @(private = "file") TARGET_SIMD_FEATURES :: "sse2,ssse3" +} else when ODIN_ARCH == .riscv64 { + @(private = "file") + TARGET_SIMD_FEATURES :: "v" } else { @(private = "file") TARGET_SIMD_FEATURES :: "" } +// Some targets lack runtime feature detection, and will flat out refuse +// to load binaries that have unknown instructions. This is distinct from +// `simd.HAS_HARDWARE_SIMD` as actually good designs support runtime feature +// detection and that constant establishes a baseline. +// +// See: +// - https://github.com/WebAssembly/design/issues/1161 +@(private = "file") +TARGET_IS_DESIGNED_BY_IDIOTS :: (ODIN_ARCH == .wasm64p32 || ODIN_ARCH == .wasm32) && !intrinsics.has_target_feature("simd128") + @(private = "file") _ROT_7L: simd.u32x4 : {7, 7, 7, 7} @(private = "file") @@ -205,14 +218,16 @@ _store_simd128 :: #force_inline proc "contextless" ( // is_performant returns true iff the target and current host both support // "enough" 128-bit SIMD to make this implementation performant. is_performant :: proc "contextless" () -> bool { - when ODIN_ARCH == .arm64 || ODIN_ARCH == .arm32 || ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .arm32 || ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 || ODIN_ARCH == .riscv64 { when ODIN_ARCH == .arm64 || ODIN_ARCH == .arm32 { req_features :: info.CPU_Features{.asimd} } else when ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 { req_features :: info.CPU_Features{.sse2, .ssse3} + } else when ODIN_ARCH == .riscv64 { + req_features :: info.CPU_Features{.V} } - features, ok := info.cpu_features.? + features, ok := info.cpu.features.? if !ok { return false } @@ -245,8 +260,17 @@ stream_blocks :: proc(ctx: ^_chacha20.Context, dst, src: []byte, nr_blocks: int) // 8 blocks at a time. // - // Note: This is only worth it on Aarch64. - when ODIN_ARCH == .arm64 { + // Note: + // This uses a ton of registers so it is only worth it on targets + // that have something like 32 128-bit registers. This is currently + // all ARMv8 targets, and RISC-V Zvl128b (`V` application profile) + // targets. + // + // While our current definition of `.arm32` is 32-bit ARMv8, this + // may change in the future (ARMv7 is still relevant), and things + // like Cortex-A8/A9 does "pretend" 128-bit SIMD 64-bits at a time + // thus needs bemchmarking. + when ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 { for ; n >= 8; n = n - 8 { v0, v1, v2, v3 := s0, s1, s2, s3 @@ -354,9 +378,11 @@ stream_blocks :: proc(ctx: ^_chacha20.Context, dst, src: []byte, nr_blocks: int) // 4 blocks at a time. // - // Note: The i386 target lacks the required number of registers - // for this to be performant, so it is skipped. - when ODIN_ARCH != .i386 { + // Note: This is skipped on several targets for various reasons. + // - i386 lacks the required number of registers + // - Generating code when runtime "hardware" SIMD support is impossible + // to detect is pointless, since this will be emulated using GP regs. + when ODIN_ARCH != .i386 && !TARGET_IS_DESIGNED_BY_IDIOTS { for ; n >= 4; n = n - 4 { v0, v1, v2, v3 := s0, s1, s2, s3 diff --git a/core/crypto/_chacha20/simd256/chacha20_simd256.odin b/core/crypto/_chacha20/simd256/chacha20_simd256.odin index ccb02a947..12fffeb2f 100644 --- a/core/crypto/_chacha20/simd256/chacha20_simd256.odin +++ b/core/crypto/_chacha20/simd256/chacha20_simd256.odin @@ -41,7 +41,7 @@ _VEC_TWO: simd.u64x4 : {2, 0, 2, 0} is_performant :: proc "contextless" () -> bool { req_features :: info.CPU_Features{.avx, .avx2} - features, ok := info.cpu_features.? + features, ok := info.cpu.features.? if !ok { return false } diff --git a/core/crypto/_chacha20/simd256/chacha20_simd256_stub.odin b/core/crypto/_chacha20/simd256/chacha20_simd256_stub.odin index ce673b42b..287ddd885 100644 --- a/core/crypto/_chacha20/simd256/chacha20_simd256_stub.odin +++ b/core/crypto/_chacha20/simd256/chacha20_simd256_stub.odin @@ -13,5 +13,5 @@ stream_blocks :: proc(ctx: ^_chacha20.Context, dst, src: []byte, nr_blocks: int) } hchacha20 :: proc "contextless" (dst, key, iv: []byte) { - intrinsics.trap() + panic_contextless("crypto/chacha20: simd256 implementation unsupported") }
\ No newline at end of file diff --git a/core/crypto/_edwards25519/edwards25519.odin b/core/crypto/_edwards25519/edwards25519.odin index 6495f7a3a..d6f01d497 100644 --- a/core/crypto/_edwards25519/edwards25519.odin +++ b/core/crypto/_edwards25519/edwards25519.odin @@ -11,7 +11,6 @@ See: - https://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html */ -import "base:intrinsics" import "core:crypto" import field "core:crypto/_fiat/field_curve25519" import "core:mem" @@ -32,6 +31,7 @@ import "core:mem" // - The group element decoding routine takes the opinionated stance of // rejecting non-canonical encodings. +@(rodata) FE_D := field.Tight_Field_Element { 929955233495203, 466365720129213, @@ -39,7 +39,7 @@ FE_D := field.Tight_Field_Element { 2033849074728123, 1442794654840575, } -@(private) +@(private, rodata) FE_A := field.Tight_Field_Element { 2251799813685228, 2251799813685247, @@ -47,7 +47,7 @@ FE_A := field.Tight_Field_Element { 2251799813685247, 2251799813685247, } -@(private) +@(private, rodata) FE_D2 := field.Tight_Field_Element { 1859910466990425, 932731440258426, @@ -55,7 +55,7 @@ FE_D2 := field.Tight_Field_Element { 1815898335770999, 633789495995903, } -@(private) +@(private, rodata) GE_BASEPOINT := Group_Element { field.Tight_Field_Element { 1738742601995546, @@ -80,6 +80,7 @@ GE_BASEPOINT := Group_Element { 1821297809914039, }, } +@(rodata) GE_IDENTITY := Group_Element { field.Tight_Field_Element{0, 0, 0, 0, 0}, field.Tight_Field_Element{1, 0, 0, 0, 0}, @@ -107,9 +108,7 @@ ge_set :: proc "contextless" (ge, a: ^Group_Element) { @(require_results) ge_set_bytes :: proc "contextless" (ge: ^Group_Element, b: []byte) -> bool { - if len(b) != 32 { - intrinsics.trap() - } + ensure_contextless(len(b) == 32, "edwards25519: invalid group element size") b_ := (^[32]byte)(raw_data(b)) // Do the work in a scratch element, so that ge is unchanged on @@ -166,9 +165,7 @@ ge_set_bytes :: proc "contextless" (ge: ^Group_Element, b: []byte) -> bool { } ge_bytes :: proc "contextless" (ge: ^Group_Element, dst: []byte) { - if len(dst) != 32 { - intrinsics.trap() - } + ensure_contextless(len(dst) == 32, "edwards25519: invalid group element size") dst_ := (^[32]byte)(raw_data(dst)) // Convert the element to affine (x, y) representation. diff --git a/core/crypto/_edwards25519/edwards25519_scalar.odin b/core/crypto/_edwards25519/edwards25519_scalar.odin index e21fa3755..68c79a6e8 100644 --- a/core/crypto/_edwards25519/edwards25519_scalar.odin +++ b/core/crypto/_edwards25519/edwards25519_scalar.odin @@ -1,6 +1,5 @@ package _edwards25519 -import "base:intrinsics" import field "core:crypto/_fiat/field_scalar25519" import "core:mem" @@ -8,7 +7,7 @@ Scalar :: field.Montgomery_Domain_Field_Element // WARNING: This is non-canonical and only to be used when checking if // a group element is on the prime-order subgroup. -@(private) +@(private, rodata) SC_ELL := field.Non_Montgomery_Domain_Field_Element { field.ELL[0], field.ELL[1], @@ -25,17 +24,13 @@ sc_set_u64 :: proc "contextless" (sc: ^Scalar, i: u64) { @(require_results) sc_set_bytes :: proc "contextless" (sc: ^Scalar, b: []byte) -> bool { - if len(b) != 32 { - intrinsics.trap() - } + ensure_contextless(len(b) == 32, "edwards25519: invalid scalar size") b_ := (^[32]byte)(raw_data(b)) return field.fe_from_bytes(sc, b_) } sc_set_bytes_rfc8032 :: proc "contextless" (sc: ^Scalar, b: []byte) { - if len(b) != 32 { - intrinsics.trap() - } + ensure_contextless(len(b) == 32, "edwards25519: invalid scalar size") b_ := (^[32]byte)(raw_data(b)) field.fe_from_bytes_rfc8032(sc, b_) } diff --git a/core/crypto/_fiat/field_curve25519/field51.odin b/core/crypto/_fiat/field_curve25519/field51.odin index d039bd411..6716fa158 100644 --- a/core/crypto/_fiat/field_curve25519/field51.odin +++ b/core/crypto/_fiat/field_curve25519/field51.odin @@ -42,9 +42,12 @@ import "core:math/bits" Loose_Field_Element :: distinct [5]u64 Tight_Field_Element :: distinct [5]u64 +@(rodata) FE_ZERO := Tight_Field_Element{0, 0, 0, 0, 0} +@(rodata) FE_ONE := Tight_Field_Element{1, 0, 0, 0, 0} +@(rodata) FE_SQRT_M1 := Tight_Field_Element { 1718705420411056, 234908883556509, diff --git a/core/crypto/_fiat/field_curve448/field.odin b/core/crypto/_fiat/field_curve448/field.odin new file mode 100644 index 000000000..540d88f28 --- /dev/null +++ b/core/crypto/_fiat/field_curve448/field.odin @@ -0,0 +1,235 @@ +package field_curve448 + +import "core:mem" + +fe_relax_cast :: #force_inline proc "contextless" ( + arg1: ^Tight_Field_Element, +) -> ^Loose_Field_Element { + return (^Loose_Field_Element)(arg1) +} + +fe_tighten_cast :: #force_inline proc "contextless" ( + arg1: ^Loose_Field_Element, +) -> ^Tight_Field_Element { + return (^Tight_Field_Element)(arg1) +} + +fe_clear :: proc "contextless" ( + arg1: $T, +) where T == ^Tight_Field_Element || T == ^Loose_Field_Element { + mem.zero_explicit(arg1, size_of(arg1^)) +} + +fe_clear_vec :: proc "contextless" ( + arg1: $T, +) where T == []^Tight_Field_Element || T == []^Loose_Field_Element { + for fe in arg1 { + fe_clear(fe) + } +} + +fe_carry_mul_small :: proc "contextless" ( + out1: ^Tight_Field_Element, + arg1: ^Loose_Field_Element, + arg2: u64, +) { + arg2_ := Loose_Field_Element{arg2, 0, 0, 0, 0, 0, 0, 0} + fe_carry_mul(out1, arg1, &arg2_) +} + +fe_carry_pow2k :: proc "contextless" ( + out1: ^Tight_Field_Element, + arg1: ^Loose_Field_Element, + arg2: uint, +) { + // Special case: `arg1^(2 * 0) = 1`, though this should never happen. + if arg2 == 0 { + fe_one(out1) + return + } + + fe_carry_square(out1, arg1) + for _ in 1 ..< arg2 { + fe_carry_square(out1, fe_relax_cast(out1)) + } +} + +fe_carry_inv :: proc "contextless" ( + out1: ^Tight_Field_Element, + arg1: ^Loose_Field_Element, +) { + // Inversion computation is derived from the addition chain: + // + // _10 = 2*1 + // _11 = 1 + _10 + // _110 = 2*_11 + // _111 = 1 + _110 + // _111000 = _111 << 3 + // _111111 = _111 + _111000 + // x12 = _111111 << 6 + _111111 + // x24 = x12 << 12 + x12 + // i34 = x24 << 6 + // x30 = _111111 + i34 + // x48 = i34 << 18 + x24 + // x96 = x48 << 48 + x48 + // x192 = x96 << 96 + x96 + // x222 = x192 << 30 + x30 + // x223 = 2*x222 + 1 + // return (x223 << 223 + x222) << 2 + 1 + // + // Operations: 447 squares 13 multiplies + // + // Generated by github.com/mmcloughlin/addchain v0.4.0. + + t0, t1, t2: Tight_Field_Element = ---, ---, --- + + // Step 1: t0 = x^0x2 + fe_carry_square(&t0, arg1) + + // Step 2: t0 = x^0x3 + fe_carry_mul(&t0, arg1, fe_relax_cast(&t0)) + + // t0.Sqr(t0) + fe_carry_square(&t0, fe_relax_cast(&t0)) + + // Step 4: t0 = x^0x7 + fe_carry_mul(&t0, arg1, fe_relax_cast(&t0)) + + // Step 7: t1 = x^0x38 + fe_carry_pow2k(&t1, fe_relax_cast(&t0), 3) + + // Step 8: t0 = x^0x3f + fe_carry_mul(&t0, fe_relax_cast(&t0), fe_relax_cast(&t1)) + + // Step 14: t1 = x^0xfc0 + fe_carry_pow2k(&t1, fe_relax_cast(&t0), 6) + + // Step 15: t1 = x^0xfff + fe_carry_mul(&t1, fe_relax_cast(&t0), fe_relax_cast(&t1)) + + // Step 27: t2 = x^0xfff000 + fe_carry_pow2k(&t2, fe_relax_cast(&t1), 12) + + // Step 28: t1 = x^0xffffff + fe_carry_mul(&t1, fe_relax_cast(&t1), fe_relax_cast(&t2)) + + // Step 34: t2 = x^0x3fffffc0 + fe_carry_pow2k(&t2, fe_relax_cast(&t1), 6) + + // Step 35: t0 = x^0x3fffffff + fe_carry_mul(&t0, fe_relax_cast(&t0), fe_relax_cast(&t2)) + + // Step 53: t2 = x^0xffffff000000 + fe_carry_pow2k(&t2, fe_relax_cast(&t2), 18) + + // Step 54: t1 = x^0xffffffffffff + fe_carry_mul(&t1, fe_relax_cast(&t1), fe_relax_cast(&t2)) + + // Step 102: t2 = x^0xffffffffffff000000000000 + fe_carry_pow2k(&t2, fe_relax_cast(&t1), 48) + + // Step 103: t1 = x^0xffffffffffffffffffffffff + fe_carry_mul(&t1, fe_relax_cast(&t1), fe_relax_cast(&t2)) + + // Step 199: t2 = x^0xffffffffffffffffffffffff000000000000000000000000 + fe_carry_pow2k(&t2, fe_relax_cast(&t1), 96) + + // Step 200: t1 = x^0xffffffffffffffffffffffffffffffffffffffffffffffff + fe_carry_mul(&t1, fe_relax_cast(&t1), fe_relax_cast(&t2)) + + // Step 230: t1 = x^0x3fffffffffffffffffffffffffffffffffffffffffffffffc0000000 + fe_carry_pow2k(&t1, fe_relax_cast(&t1), 30) + + // Step 231: t0 = x^0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffff + fe_carry_mul(&t0, fe_relax_cast(&t0), fe_relax_cast(&t1)) + + // Step 232: t1 = x^0x7ffffffffffffffffffffffffffffffffffffffffffffffffffffffe + fe_carry_square(&t1, fe_relax_cast(&t0)) + + // Step 233: t1 = x^0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffff + fe_carry_mul(&t1, arg1, fe_relax_cast(&t1)) + + // Step 456: t1 = x^0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffff80000000000000000000000000000000000000000000000000000000 + fe_carry_pow2k(&t1, fe_relax_cast(&t1), 223) + + // Step 457: t0 = x^0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffbfffffffffffffffffffffffffffffffffffffffffffffffffffffff + fe_carry_mul(&t0, fe_relax_cast(&t0), fe_relax_cast(&t1)) + + // Step 459: t0 = x^0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffffffffffffffffffffffffffffffffffffffffffc + fe_carry_pow2k(&t0, fe_relax_cast(&t0), 2) + + // Step 460: z = x^0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffffffffffffffffffffffffffffffffffffffffffd + fe_carry_mul(out1, arg1, fe_relax_cast(&t0)) + + fe_clear_vec([]^Tight_Field_Element{&t0, &t1, &t2}) +} + +fe_zero :: proc "contextless" (out1: ^Tight_Field_Element) { + out1[0] = 0 + out1[1] = 0 + out1[2] = 0 + out1[3] = 0 + out1[4] = 0 + out1[5] = 0 + out1[6] = 0 + out1[7] = 0 +} + +fe_one :: proc "contextless" (out1: ^Tight_Field_Element) { + out1[0] = 1 + out1[1] = 0 + out1[2] = 0 + out1[3] = 0 + out1[4] = 0 + out1[5] = 0 + out1[6] = 0 + out1[7] = 0 +} + +fe_set :: proc "contextless" (out1, arg1: ^Tight_Field_Element) { + x1 := arg1[0] + x2 := arg1[1] + x3 := arg1[2] + x4 := arg1[3] + x5 := arg1[4] + x6 := arg1[5] + x7 := arg1[6] + x8 := arg1[7] + out1[0] = x1 + out1[1] = x2 + out1[2] = x3 + out1[3] = x4 + out1[4] = x5 + out1[5] = x6 + out1[6] = x7 + out1[7] = x8 +} + +@(optimization_mode = "none") +fe_cond_swap :: #force_no_inline proc "contextless" (out1, out2: ^Tight_Field_Element, arg1: int) { + mask := (u64(arg1) * 0xffffffffffffffff) + x := (out1[0] ~ out2[0]) & mask + x1, y1 := out1[0] ~ x, out2[0] ~ x + x = (out1[1] ~ out2[1]) & mask + x2, y2 := out1[1] ~ x, out2[1] ~ x + x = (out1[2] ~ out2[2]) & mask + x3, y3 := out1[2] ~ x, out2[2] ~ x + x = (out1[3] ~ out2[3]) & mask + x4, y4 := out1[3] ~ x, out2[3] ~ x + x = (out1[4] ~ out2[4]) & mask + x5, y5 := out1[4] ~ x, out2[4] ~ x + x = (out1[5] ~ out2[5]) & mask + x6, y6 := out1[5] ~ x, out2[5] ~ x + x = (out1[6] ~ out2[6]) & mask + x7, y7 := out1[6] ~ x, out2[6] ~ x + x = (out1[7] ~ out2[7]) & mask + x8, y8 := out1[7] ~ x, out2[7] ~ x + out1[0], out2[0] = x1, y1 + out1[1], out2[1] = x2, y2 + out1[2], out2[2] = x3, y3 + out1[3], out2[3] = x4, y4 + out1[4], out2[4] = x5, y5 + out1[5], out2[5] = x6, y6 + out1[6], out2[6] = x7, y7 + out1[7], out2[7] = x8, y8 +}
\ No newline at end of file diff --git a/core/crypto/_fiat/field_curve448/field51.odin b/core/crypto/_fiat/field_curve448/field51.odin new file mode 100644 index 000000000..d8e49e04d --- /dev/null +++ b/core/crypto/_fiat/field_curve448/field51.odin @@ -0,0 +1,1060 @@ +// The BSD 1-Clause License (BSD-1-Clause) +// +// Copyright (c) 2015-2020 the fiat-crypto authors (see the AUTHORS file) +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY the fiat-crypto authors "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Berkeley Software Design, +// Inc. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package field_curve448 + +// The file provides arithmetic on the field Z/(2^448 - 2^224 - 1) using +// unsaturated 64-bit integer arithmetic. It is derived primarily +// from the machine generated Golang output from the fiat-crypto project. +// +// While the base implementation is provably correct, this implementation +// makes no such claims as the port and optimizations were done by hand. +// +// TODO: +// * When fiat-crypto supports it, using a saturated 64-bit limbs +// instead of 56-bit limbs will be faster, though the gains are +// minimal unless adcx/adox/mulx are used. + +import fiat "core:crypto/_fiat" +import "core:math/bits" + +Loose_Field_Element :: distinct [8]u64 +Tight_Field_Element :: distinct [8]u64 + +@(rodata) +FE_ZERO := Tight_Field_Element{0, 0, 0, 0, 0, 0, 0, 0} +@(rodata) +FE_ONE := Tight_Field_Element{1, 0, 0, 0, 0, 0, 0, 0} + +_addcarryx_u56 :: #force_inline proc "contextless" ( + arg1: fiat.u1, + arg2, arg3: u64, +) -> ( + out1: u64, + out2: fiat.u1, +) { + x1 := ((u64(arg1) + arg2) + arg3) + x2 := (x1 & 0xffffffffffffff) + x3 := fiat.u1((x1 >> 56)) + out1 = x2 + out2 = x3 + return +} + +_subborrowx_u56 :: #force_inline proc "contextless" ( + arg1: fiat.u1, + arg2, arg3: u64, +) -> ( + out1: u64, + out2: fiat.u1, +) { + x1 := ((i64(arg2) - i64(arg1)) - i64(arg3)) + x2 := fiat.u1((x1 >> 56)) + x3 := (u64(x1) & 0xffffffffffffff) + out1 = x3 + out2 = (0x0 - fiat.u1(x2)) + return +} + +fe_carry_mul :: proc "contextless" (out1: ^Tight_Field_Element, arg1, arg2: ^Loose_Field_Element) { + x2, x1 := bits.mul_u64(arg1[7], arg2[7]) + x4, x3 := bits.mul_u64(arg1[7], arg2[6]) + x6, x5 := bits.mul_u64(arg1[7], arg2[5]) + x8, x7 := bits.mul_u64(arg1[6], arg2[7]) + x10, x9 := bits.mul_u64(arg1[6], arg2[6]) + x12, x11 := bits.mul_u64(arg1[5], arg2[7]) + x14, x13 := bits.mul_u64(arg1[7], arg2[7]) + x16, x15 := bits.mul_u64(arg1[7], arg2[6]) + x18, x17 := bits.mul_u64(arg1[7], arg2[5]) + x20, x19 := bits.mul_u64(arg1[6], arg2[7]) + x22, x21 := bits.mul_u64(arg1[6], arg2[6]) + x24, x23 := bits.mul_u64(arg1[5], arg2[7]) + x26, x25 := bits.mul_u64(arg1[7], arg2[7]) + x28, x27 := bits.mul_u64(arg1[7], arg2[6]) + x30, x29 := bits.mul_u64(arg1[7], arg2[5]) + x32, x31 := bits.mul_u64(arg1[7], arg2[4]) + x34, x33 := bits.mul_u64(arg1[7], arg2[3]) + x36, x35 := bits.mul_u64(arg1[7], arg2[2]) + x38, x37 := bits.mul_u64(arg1[7], arg2[1]) + x40, x39 := bits.mul_u64(arg1[6], arg2[7]) + x42, x41 := bits.mul_u64(arg1[6], arg2[6]) + x44, x43 := bits.mul_u64(arg1[6], arg2[5]) + x46, x45 := bits.mul_u64(arg1[6], arg2[4]) + x48, x47 := bits.mul_u64(arg1[6], arg2[3]) + x50, x49 := bits.mul_u64(arg1[6], arg2[2]) + x52, x51 := bits.mul_u64(arg1[5], arg2[7]) + x54, x53 := bits.mul_u64(arg1[5], arg2[6]) + x56, x55 := bits.mul_u64(arg1[5], arg2[5]) + x58, x57 := bits.mul_u64(arg1[5], arg2[4]) + x60, x59 := bits.mul_u64(arg1[5], arg2[3]) + x62, x61 := bits.mul_u64(arg1[4], arg2[7]) + x64, x63 := bits.mul_u64(arg1[4], arg2[6]) + x66, x65 := bits.mul_u64(arg1[4], arg2[5]) + x68, x67 := bits.mul_u64(arg1[4], arg2[4]) + x70, x69 := bits.mul_u64(arg1[3], arg2[7]) + x72, x71 := bits.mul_u64(arg1[3], arg2[6]) + x74, x73 := bits.mul_u64(arg1[3], arg2[5]) + x76, x75 := bits.mul_u64(arg1[2], arg2[7]) + x78, x77 := bits.mul_u64(arg1[2], arg2[6]) + x80, x79 := bits.mul_u64(arg1[1], arg2[7]) + x82, x81 := bits.mul_u64(arg1[7], arg2[4]) + x84, x83 := bits.mul_u64(arg1[7], arg2[3]) + x86, x85 := bits.mul_u64(arg1[7], arg2[2]) + x88, x87 := bits.mul_u64(arg1[7], arg2[1]) + x90, x89 := bits.mul_u64(arg1[6], arg2[5]) + x92, x91 := bits.mul_u64(arg1[6], arg2[4]) + x94, x93 := bits.mul_u64(arg1[6], arg2[3]) + x96, x95 := bits.mul_u64(arg1[6], arg2[2]) + x98, x97 := bits.mul_u64(arg1[5], arg2[6]) + x100, x99 := bits.mul_u64(arg1[5], arg2[5]) + x102, x101 := bits.mul_u64(arg1[5], arg2[4]) + x104, x103 := bits.mul_u64(arg1[5], arg2[3]) + x106, x105 := bits.mul_u64(arg1[4], arg2[7]) + x108, x107 := bits.mul_u64(arg1[4], arg2[6]) + x110, x109 := bits.mul_u64(arg1[4], arg2[5]) + x112, x111 := bits.mul_u64(arg1[4], arg2[4]) + x114, x113 := bits.mul_u64(arg1[3], arg2[7]) + x116, x115 := bits.mul_u64(arg1[3], arg2[6]) + x118, x117 := bits.mul_u64(arg1[3], arg2[5]) + x120, x119 := bits.mul_u64(arg1[2], arg2[7]) + x122, x121 := bits.mul_u64(arg1[2], arg2[6]) + x124, x123 := bits.mul_u64(arg1[1], arg2[7]) + x126, x125 := bits.mul_u64(arg1[7], arg2[0]) + x128, x127 := bits.mul_u64(arg1[6], arg2[1]) + x130, x129 := bits.mul_u64(arg1[6], arg2[0]) + x132, x131 := bits.mul_u64(arg1[5], arg2[2]) + x134, x133 := bits.mul_u64(arg1[5], arg2[1]) + x136, x135 := bits.mul_u64(arg1[5], arg2[0]) + x138, x137 := bits.mul_u64(arg1[4], arg2[3]) + x140, x139 := bits.mul_u64(arg1[4], arg2[2]) + x142, x141 := bits.mul_u64(arg1[4], arg2[1]) + x144, x143 := bits.mul_u64(arg1[4], arg2[0]) + x146, x145 := bits.mul_u64(arg1[3], arg2[4]) + x148, x147 := bits.mul_u64(arg1[3], arg2[3]) + x150, x149 := bits.mul_u64(arg1[3], arg2[2]) + x152, x151 := bits.mul_u64(arg1[3], arg2[1]) + x154, x153 := bits.mul_u64(arg1[3], arg2[0]) + x156, x155 := bits.mul_u64(arg1[2], arg2[5]) + x158, x157 := bits.mul_u64(arg1[2], arg2[4]) + x160, x159 := bits.mul_u64(arg1[2], arg2[3]) + x162, x161 := bits.mul_u64(arg1[2], arg2[2]) + x164, x163 := bits.mul_u64(arg1[2], arg2[1]) + x166, x165 := bits.mul_u64(arg1[2], arg2[0]) + x168, x167 := bits.mul_u64(arg1[1], arg2[6]) + x170, x169 := bits.mul_u64(arg1[1], arg2[5]) + x172, x171 := bits.mul_u64(arg1[1], arg2[4]) + x174, x173 := bits.mul_u64(arg1[1], arg2[3]) + x176, x175 := bits.mul_u64(arg1[1], arg2[2]) + x178, x177 := bits.mul_u64(arg1[1], arg2[1]) + x180, x179 := bits.mul_u64(arg1[1], arg2[0]) + x182, x181 := bits.mul_u64(arg1[0], arg2[7]) + x184, x183 := bits.mul_u64(arg1[0], arg2[6]) + x186, x185 := bits.mul_u64(arg1[0], arg2[5]) + x188, x187 := bits.mul_u64(arg1[0], arg2[4]) + x190, x189 := bits.mul_u64(arg1[0], arg2[3]) + x192, x191 := bits.mul_u64(arg1[0], arg2[2]) + x194, x193 := bits.mul_u64(arg1[0], arg2[1]) + x196, x195 := bits.mul_u64(arg1[0], arg2[0]) + x197, x198 := bits.add_u64(x43, x31, u64(0x0)) + x199, _ := bits.add_u64(x44, x32, u64(fiat.u1(x198))) + x201, x202 := bits.add_u64(x53, x197, u64(0x0)) + x203, _ := bits.add_u64(x54, x199, u64(fiat.u1(x202))) + x205, x206 := bits.add_u64(x61, x201, u64(0x0)) + x207, _ := bits.add_u64(x62, x203, u64(fiat.u1(x206))) + x209, x210 := bits.add_u64(x153, x205, u64(0x0)) + x211, _ := bits.add_u64(x154, x207, u64(fiat.u1(x210))) + x213, x214 := bits.add_u64(x163, x209, u64(0x0)) + x215, _ := bits.add_u64(x164, x211, u64(fiat.u1(x214))) + x217, x218 := bits.add_u64(x175, x213, u64(0x0)) + x219, _ := bits.add_u64(x176, x215, u64(fiat.u1(x218))) + x221, x222 := bits.add_u64(x189, x217, u64(0x0)) + x223, _ := bits.add_u64(x190, x219, u64(fiat.u1(x222))) + x225 := ((x221 >> 56) | ((x223 << 8) & 0xffffffffffffffff)) + x226 := (x221 & 0xffffffffffffff) + x227, x228 := bits.add_u64(x89, x81, u64(0x0)) + x229, _ := bits.add_u64(x90, x82, u64(fiat.u1(x228))) + x231, x232 := bits.add_u64(x97, x227, u64(0x0)) + x233, _ := bits.add_u64(x98, x229, u64(fiat.u1(x232))) + x235, x236 := bits.add_u64(x105, x231, u64(0x0)) + x237, _ := bits.add_u64(x106, x233, u64(fiat.u1(x236))) + x239, x240 := bits.add_u64(x125, x235, u64(0x0)) + x241, _ := bits.add_u64(x126, x237, u64(fiat.u1(x240))) + x243, x244 := bits.add_u64(x127, x239, u64(0x0)) + x245, _ := bits.add_u64(x128, x241, u64(fiat.u1(x244))) + x247, x248 := bits.add_u64(x131, x243, u64(0x0)) + x249, _ := bits.add_u64(x132, x245, u64(fiat.u1(x248))) + x251, x252 := bits.add_u64(x137, x247, u64(0x0)) + x253, _ := bits.add_u64(x138, x249, u64(fiat.u1(x252))) + x255, x256 := bits.add_u64(x145, x251, u64(0x0)) + x257, _ := bits.add_u64(x146, x253, u64(fiat.u1(x256))) + x259, x260 := bits.add_u64(x155, x255, u64(0x0)) + x261, _ := bits.add_u64(x156, x257, u64(fiat.u1(x260))) + x263, x264 := bits.add_u64(x167, x259, u64(0x0)) + x265, _ := bits.add_u64(x168, x261, u64(fiat.u1(x264))) + x267, x268 := bits.add_u64(x181, x263, u64(0x0)) + x269, _ := bits.add_u64(x182, x265, u64(fiat.u1(x268))) + x271, x272 := bits.add_u64(x25, x13, u64(0x0)) + x273, _ := bits.add_u64(x26, x14, u64(fiat.u1(x272))) + x275, x276 := bits.add_u64(x83, x271, u64(0x0)) + x277, _ := bits.add_u64(x84, x273, u64(fiat.u1(x276))) + x279, x280 := bits.add_u64(x91, x275, u64(0x0)) + x281, _ := bits.add_u64(x92, x277, u64(fiat.u1(x280))) + x283, x284 := bits.add_u64(x99, x279, u64(0x0)) + x285, _ := bits.add_u64(x100, x281, u64(fiat.u1(x284))) + x287, x288 := bits.add_u64(x107, x283, u64(0x0)) + x289, _ := bits.add_u64(x108, x285, u64(fiat.u1(x288))) + x291, x292 := bits.add_u64(x113, x287, u64(0x0)) + x293, _ := bits.add_u64(x114, x289, u64(fiat.u1(x292))) + x295, x296 := bits.add_u64(x129, x291, u64(0x0)) + x297, _ := bits.add_u64(x130, x293, u64(fiat.u1(x296))) + x299, x300 := bits.add_u64(x133, x295, u64(0x0)) + x301, _ := bits.add_u64(x134, x297, u64(fiat.u1(x300))) + x303, x304 := bits.add_u64(x139, x299, u64(0x0)) + x305, _ := bits.add_u64(x140, x301, u64(fiat.u1(x304))) + x307, x308 := bits.add_u64(x147, x303, u64(0x0)) + x309, _ := bits.add_u64(x148, x305, u64(fiat.u1(x308))) + x311, x312 := bits.add_u64(x157, x307, u64(0x0)) + x313, _ := bits.add_u64(x158, x309, u64(fiat.u1(x312))) + x315, x316 := bits.add_u64(x169, x311, u64(0x0)) + x317, _ := bits.add_u64(x170, x313, u64(fiat.u1(x316))) + x319, x320 := bits.add_u64(x183, x315, u64(0x0)) + x321, _ := bits.add_u64(x184, x317, u64(fiat.u1(x320))) + x323, x324 := bits.add_u64(x19, x15, u64(0x0)) + x325, _ := bits.add_u64(x20, x16, u64(fiat.u1(x324))) + x327, x328 := bits.add_u64(x27, x323, u64(0x0)) + x329, _ := bits.add_u64(x28, x325, u64(fiat.u1(x328))) + x331, x332 := bits.add_u64(x39, x327, u64(0x0)) + x333, _ := bits.add_u64(x40, x329, u64(fiat.u1(x332))) + x335, x336 := bits.add_u64(x85, x331, u64(0x0)) + x337, _ := bits.add_u64(x86, x333, u64(fiat.u1(x336))) + x339, x340 := bits.add_u64(x93, x335, u64(0x0)) + x341, _ := bits.add_u64(x94, x337, u64(fiat.u1(x340))) + x343, x344 := bits.add_u64(x101, x339, u64(0x0)) + x345, _ := bits.add_u64(x102, x341, u64(fiat.u1(x344))) + x347, x348 := bits.add_u64(x109, x343, u64(0x0)) + x349, _ := bits.add_u64(x110, x345, u64(fiat.u1(x348))) + x351, x352 := bits.add_u64(x115, x347, u64(0x0)) + x353, _ := bits.add_u64(x116, x349, u64(fiat.u1(x352))) + x355, x356 := bits.add_u64(x119, x351, u64(0x0)) + x357, _ := bits.add_u64(x120, x353, u64(fiat.u1(x356))) + x359, x360 := bits.add_u64(x135, x355, u64(0x0)) + x361, _ := bits.add_u64(x136, x357, u64(fiat.u1(x360))) + x363, x364 := bits.add_u64(x141, x359, u64(0x0)) + x365, _ := bits.add_u64(x142, x361, u64(fiat.u1(x364))) + x367, x368 := bits.add_u64(x149, x363, u64(0x0)) + x369, _ := bits.add_u64(x150, x365, u64(fiat.u1(x368))) + x371, x372 := bits.add_u64(x159, x367, u64(0x0)) + x373, _ := bits.add_u64(x160, x369, u64(fiat.u1(x372))) + x375, x376 := bits.add_u64(x171, x371, u64(0x0)) + x377, _ := bits.add_u64(x172, x373, u64(fiat.u1(x376))) + x379, x380 := bits.add_u64(x185, x375, u64(0x0)) + x381, _ := bits.add_u64(x186, x377, u64(fiat.u1(x380))) + x383, x384 := bits.add_u64(x21, x17, u64(0x0)) + x385, _ := bits.add_u64(x22, x18, u64(fiat.u1(x384))) + x387, x388 := bits.add_u64(x23, x383, u64(0x0)) + x389, _ := bits.add_u64(x24, x385, u64(fiat.u1(x388))) + x391, x392 := bits.add_u64(x29, x387, u64(0x0)) + x393, _ := bits.add_u64(x30, x389, u64(fiat.u1(x392))) + x395, x396 := bits.add_u64(x41, x391, u64(0x0)) + x397, _ := bits.add_u64(x42, x393, u64(fiat.u1(x396))) + x399, x400 := bits.add_u64(x51, x395, u64(0x0)) + x401, _ := bits.add_u64(x52, x397, u64(fiat.u1(x400))) + x403, x404 := bits.add_u64(x87, x399, u64(0x0)) + x405, _ := bits.add_u64(x88, x401, u64(fiat.u1(x404))) + x407, x408 := bits.add_u64(x95, x403, u64(0x0)) + x409, _ := bits.add_u64(x96, x405, u64(fiat.u1(x408))) + x411, x412 := bits.add_u64(x103, x407, u64(0x0)) + x413, _ := bits.add_u64(x104, x409, u64(fiat.u1(x412))) + x415, x416 := bits.add_u64(x111, x411, u64(0x0)) + x417, _ := bits.add_u64(x112, x413, u64(fiat.u1(x416))) + x419, x420 := bits.add_u64(x117, x415, u64(0x0)) + x421, _ := bits.add_u64(x118, x417, u64(fiat.u1(x420))) + x423, x424 := bits.add_u64(x121, x419, u64(0x0)) + x425, _ := bits.add_u64(x122, x421, u64(fiat.u1(x424))) + x427, x428 := bits.add_u64(x123, x423, u64(0x0)) + x429, _ := bits.add_u64(x124, x425, u64(fiat.u1(x428))) + x431, x432 := bits.add_u64(x143, x427, u64(0x0)) + x433, _ := bits.add_u64(x144, x429, u64(fiat.u1(x432))) + x435, x436 := bits.add_u64(x151, x431, u64(0x0)) + x437, _ := bits.add_u64(x152, x433, u64(fiat.u1(x436))) + x439, x440 := bits.add_u64(x161, x435, u64(0x0)) + x441, _ := bits.add_u64(x162, x437, u64(fiat.u1(x440))) + x443, x444 := bits.add_u64(x173, x439, u64(0x0)) + x445, _ := bits.add_u64(x174, x441, u64(fiat.u1(x444))) + x447, x448 := bits.add_u64(x187, x443, u64(0x0)) + x449, _ := bits.add_u64(x188, x445, u64(fiat.u1(x448))) + x451, x452 := bits.add_u64(x33, x1, u64(0x0)) + x453, _ := bits.add_u64(x34, x2, u64(fiat.u1(x452))) + x455, x456 := bits.add_u64(x45, x451, u64(0x0)) + x457, _ := bits.add_u64(x46, x453, u64(fiat.u1(x456))) + x459, x460 := bits.add_u64(x55, x455, u64(0x0)) + x461, _ := bits.add_u64(x56, x457, u64(fiat.u1(x460))) + x463, x464 := bits.add_u64(x63, x459, u64(0x0)) + x465, _ := bits.add_u64(x64, x461, u64(fiat.u1(x464))) + x467, x468 := bits.add_u64(x69, x463, u64(0x0)) + x469, _ := bits.add_u64(x70, x465, u64(fiat.u1(x468))) + x471, x472 := bits.add_u64(x165, x467, u64(0x0)) + x473, _ := bits.add_u64(x166, x469, u64(fiat.u1(x472))) + x475, x476 := bits.add_u64(x177, x471, u64(0x0)) + x477, _ := bits.add_u64(x178, x473, u64(fiat.u1(x476))) + x479, x480 := bits.add_u64(x191, x475, u64(0x0)) + x481, _ := bits.add_u64(x192, x477, u64(fiat.u1(x480))) + x483, x484 := bits.add_u64(x7, x3, u64(0x0)) + x485, _ := bits.add_u64(x8, x4, u64(fiat.u1(x484))) + x487, x488 := bits.add_u64(x35, x483, u64(0x0)) + x489, _ := bits.add_u64(x36, x485, u64(fiat.u1(x488))) + x491, x492 := bits.add_u64(x47, x487, u64(0x0)) + x493, _ := bits.add_u64(x48, x489, u64(fiat.u1(x492))) + x495, x496 := bits.add_u64(x57, x491, u64(0x0)) + x497, _ := bits.add_u64(x58, x493, u64(fiat.u1(x496))) + x499, x500 := bits.add_u64(x65, x495, u64(0x0)) + x501, _ := bits.add_u64(x66, x497, u64(fiat.u1(x500))) + x503, x504 := bits.add_u64(x71, x499, u64(0x0)) + x505, _ := bits.add_u64(x72, x501, u64(fiat.u1(x504))) + x507, x508 := bits.add_u64(x75, x503, u64(0x0)) + x509, _ := bits.add_u64(x76, x505, u64(fiat.u1(x508))) + x511, x512 := bits.add_u64(x179, x507, u64(0x0)) + x513, _ := bits.add_u64(x180, x509, u64(fiat.u1(x512))) + x515, x516 := bits.add_u64(x193, x511, u64(0x0)) + x517, _ := bits.add_u64(x194, x513, u64(fiat.u1(x516))) + x519, x520 := bits.add_u64(x9, x5, u64(0x0)) + x521, _ := bits.add_u64(x10, x6, u64(fiat.u1(x520))) + x523, x524 := bits.add_u64(x11, x519, u64(0x0)) + x525, _ := bits.add_u64(x12, x521, u64(fiat.u1(x524))) + x527, x528 := bits.add_u64(x37, x523, u64(0x0)) + x529, _ := bits.add_u64(x38, x525, u64(fiat.u1(x528))) + x531, x532 := bits.add_u64(x49, x527, u64(0x0)) + x533, _ := bits.add_u64(x50, x529, u64(fiat.u1(x532))) + x535, x536 := bits.add_u64(x59, x531, u64(0x0)) + x537, _ := bits.add_u64(x60, x533, u64(fiat.u1(x536))) + x539, x540 := bits.add_u64(x67, x535, u64(0x0)) + x541, _ := bits.add_u64(x68, x537, u64(fiat.u1(x540))) + x543, x544 := bits.add_u64(x73, x539, u64(0x0)) + x545, _ := bits.add_u64(x74, x541, u64(fiat.u1(x544))) + x547, x548 := bits.add_u64(x77, x543, u64(0x0)) + x549, _ := bits.add_u64(x78, x545, u64(fiat.u1(x548))) + x551, x552 := bits.add_u64(x79, x547, u64(0x0)) + x553, _ := bits.add_u64(x80, x549, u64(fiat.u1(x552))) + x555, x556 := bits.add_u64(x195, x551, u64(0x0)) + x557, _ := bits.add_u64(x196, x553, u64(fiat.u1(x556))) + x559, x560 := bits.add_u64(x225, x447, u64(0x0)) + x561 := (u64(fiat.u1(x560)) + x449) + x562 := ((x267 >> 56) | ((x269 << 8) & 0xffffffffffffffff)) + x563 := (x267 & 0xffffffffffffff) + x564, x565 := bits.add_u64(x559, x562, u64(0x0)) + x566 := (u64(fiat.u1(x565)) + x561) + x567 := ((x564 >> 56) | ((x566 << 8) & 0xffffffffffffffff)) + x568 := (x564 & 0xffffffffffffff) + x569, x570 := bits.add_u64(x555, x562, u64(0x0)) + x571 := (u64(fiat.u1(x570)) + x557) + x572, x573 := bits.add_u64(x567, x379, u64(0x0)) + x574 := (u64(fiat.u1(x573)) + x381) + x575 := ((x569 >> 56) | ((x571 << 8) & 0xffffffffffffffff)) + x576 := (x569 & 0xffffffffffffff) + x577, x578 := bits.add_u64(x575, x515, u64(0x0)) + x579 := (u64(fiat.u1(x578)) + x517) + x580 := ((x572 >> 56) | ((x574 << 8) & 0xffffffffffffffff)) + x581 := (x572 & 0xffffffffffffff) + x582, x583 := bits.add_u64(x580, x319, u64(0x0)) + x584 := (u64(fiat.u1(x583)) + x321) + x585 := ((x577 >> 56) | ((x579 << 8) & 0xffffffffffffffff)) + x586 := (x577 & 0xffffffffffffff) + x587, x588 := bits.add_u64(x585, x479, u64(0x0)) + x589 := (u64(fiat.u1(x588)) + x481) + x590 := ((x582 >> 56) | ((x584 << 8) & 0xffffffffffffffff)) + x591 := (x582 & 0xffffffffffffff) + x592 := (x590 + x563) + x593 := ((x587 >> 56) | ((x589 << 8) & 0xffffffffffffffff)) + x594 := (x587 & 0xffffffffffffff) + x595 := (x593 + x226) + x596 := (x592 >> 56) + x597 := (x592 & 0xffffffffffffff) + x598 := (x595 >> 56) + x599 := (x595 & 0xffffffffffffff) + x600 := (x568 + x596) + x601 := (x576 + x596) + x602 := (x598 + x600) + x603 := fiat.u1((x602 >> 56)) + x604 := (x602 & 0xffffffffffffff) + x605 := (u64(x603) + x581) + x606 := fiat.u1((x601 >> 56)) + x607 := (x601 & 0xffffffffffffff) + x608 := (u64(x606) + x586) + out1[0] = x607 + out1[1] = x608 + out1[2] = x594 + out1[3] = x599 + out1[4] = x604 + out1[5] = x605 + out1[6] = x591 + out1[7] = x597 +} + +fe_carry_square :: proc "contextless" (out1: ^Tight_Field_Element, arg1: ^Loose_Field_Element) { + x1 := arg1[7] + x2 := arg1[7] + x3 := (x1 * 0x2) + x4 := (x2 * 0x2) + x5 := (arg1[7] * 0x2) + x6 := arg1[6] + x7 := arg1[6] + x8 := (x6 * 0x2) + x9 := (x7 * 0x2) + x10 := (arg1[6] * 0x2) + x11 := arg1[5] + x12 := arg1[5] + x13 := (x11 * 0x2) + x14 := (x12 * 0x2) + x15 := (arg1[5] * 0x2) + x16 := arg1[4] + x17 := arg1[4] + x18 := (arg1[4] * 0x2) + x19 := (arg1[3] * 0x2) + x20 := (arg1[2] * 0x2) + x21 := (arg1[1] * 0x2) + x23, x22 := bits.mul_u64(arg1[7], x1) + x25, x24 := bits.mul_u64(arg1[6], x3) + x27, x26 := bits.mul_u64(arg1[6], x6) + x29, x28 := bits.mul_u64(arg1[5], x3) + x31, x30 := bits.mul_u64(arg1[7], x1) + x33, x32 := bits.mul_u64(arg1[6], x3) + x35, x34 := bits.mul_u64(arg1[6], x6) + x37, x36 := bits.mul_u64(arg1[5], x3) + x39, x38 := bits.mul_u64(arg1[7], x2) + x41, x40 := bits.mul_u64(arg1[6], x4) + x43, x42 := bits.mul_u64(arg1[6], x7) + x45, x44 := bits.mul_u64(arg1[5], x4) + x47, x46 := bits.mul_u64(arg1[5], x9) + x49, x48 := bits.mul_u64(arg1[5], x8) + x51, x50 := bits.mul_u64(arg1[5], x12) + x53, x52 := bits.mul_u64(arg1[5], x11) + x55, x54 := bits.mul_u64(arg1[4], x4) + x57, x56 := bits.mul_u64(arg1[4], x3) + x59, x58 := bits.mul_u64(arg1[4], x9) + x61, x60 := bits.mul_u64(arg1[4], x8) + x63, x62 := bits.mul_u64(arg1[4], x14) + x65, x64 := bits.mul_u64(arg1[4], x13) + x67, x66 := bits.mul_u64(arg1[4], x17) + x69, x68 := bits.mul_u64(arg1[4], x16) + x71, x70 := bits.mul_u64(arg1[3], x4) + x73, x72 := bits.mul_u64(arg1[3], x3) + x75, x74 := bits.mul_u64(arg1[3], x9) + x77, x76 := bits.mul_u64(arg1[3], x8) + x79, x78 := bits.mul_u64(arg1[3], x14) + x81, x80 := bits.mul_u64(arg1[3], x13) + x83, x82 := bits.mul_u64(arg1[3], x18) + x85, x84 := bits.mul_u64(arg1[3], arg1[3]) + x87, x86 := bits.mul_u64(arg1[2], x4) + x89, x88 := bits.mul_u64(arg1[2], x3) + x91, x90 := bits.mul_u64(arg1[2], x9) + x93, x92 := bits.mul_u64(arg1[2], x8) + x95, x94 := bits.mul_u64(arg1[2], x15) + x97, x96 := bits.mul_u64(arg1[2], x18) + x99, x98 := bits.mul_u64(arg1[2], x19) + x101, x100 := bits.mul_u64(arg1[2], arg1[2]) + x103, x102 := bits.mul_u64(arg1[1], x4) + x105, x104 := bits.mul_u64(arg1[1], x3) + x107, x106 := bits.mul_u64(arg1[1], x10) + x109, x108 := bits.mul_u64(arg1[1], x15) + x111, x110 := bits.mul_u64(arg1[1], x18) + x113, x112 := bits.mul_u64(arg1[1], x19) + x115, x114 := bits.mul_u64(arg1[1], x20) + x117, x116 := bits.mul_u64(arg1[1], arg1[1]) + x119, x118 := bits.mul_u64(arg1[0], x5) + x121, x120 := bits.mul_u64(arg1[0], x10) + x123, x122 := bits.mul_u64(arg1[0], x15) + x125, x124 := bits.mul_u64(arg1[0], x18) + x127, x126 := bits.mul_u64(arg1[0], x19) + x129, x128 := bits.mul_u64(arg1[0], x20) + x131, x130 := bits.mul_u64(arg1[0], x21) + x133, x132 := bits.mul_u64(arg1[0], arg1[0]) + x134, x135 := bits.add_u64(x54, x46, u64(0x0)) + x136, _ := bits.add_u64(x55, x47, u64(fiat.u1(x135))) + x138, x139 := bits.add_u64(x114, x134, u64(0x0)) + x140, _ := bits.add_u64(x115, x136, u64(fiat.u1(x139))) + x142, x143 := bits.add_u64(x126, x138, u64(0x0)) + x144, _ := bits.add_u64(x127, x140, u64(fiat.u1(x143))) + x146 := ((x142 >> 56) | ((x144 << 8) & 0xffffffffffffffff)) + x147 := (x142 & 0xffffffffffffff) + x148, x149 := bits.add_u64(x56, x48, u64(0x0)) + x150, _ := bits.add_u64(x57, x49, u64(fiat.u1(x149))) + x152, x153 := bits.add_u64(x82, x148, u64(0x0)) + x154, _ := bits.add_u64(x83, x150, u64(fiat.u1(x153))) + x156, x157 := bits.add_u64(x94, x152, u64(0x0)) + x158, _ := bits.add_u64(x95, x154, u64(fiat.u1(x157))) + x160, x161 := bits.add_u64(x106, x156, u64(0x0)) + x162, _ := bits.add_u64(x107, x158, u64(fiat.u1(x161))) + x164, x165 := bits.add_u64(x118, x160, u64(0x0)) + x166, _ := bits.add_u64(x119, x162, u64(fiat.u1(x165))) + x168, x169 := bits.add_u64(x38, x30, u64(0x0)) + x170, _ := bits.add_u64(x39, x31, u64(fiat.u1(x169))) + x172, x173 := bits.add_u64(x52, x168, u64(0x0)) + x174, _ := bits.add_u64(x53, x170, u64(fiat.u1(x173))) + x176, x177 := bits.add_u64(x60, x172, u64(0x0)) + x178, _ := bits.add_u64(x61, x174, u64(fiat.u1(x177))) + x180, x181 := bits.add_u64(x72, x176, u64(0x0)) + x182, _ := bits.add_u64(x73, x178, u64(fiat.u1(x181))) + x184, x185 := bits.add_u64(x84, x180, u64(0x0)) + x186, _ := bits.add_u64(x85, x182, u64(fiat.u1(x185))) + x188, x189 := bits.add_u64(x96, x184, u64(0x0)) + x190, _ := bits.add_u64(x97, x186, u64(fiat.u1(x189))) + x192, x193 := bits.add_u64(x108, x188, u64(0x0)) + x194, _ := bits.add_u64(x109, x190, u64(fiat.u1(x193))) + x196, x197 := bits.add_u64(x120, x192, u64(0x0)) + x198, _ := bits.add_u64(x121, x194, u64(fiat.u1(x197))) + x200, x201 := bits.add_u64(x40, x32, u64(0x0)) + x202, _ := bits.add_u64(x41, x33, u64(fiat.u1(x201))) + x204, x205 := bits.add_u64(x64, x200, u64(0x0)) + x206, _ := bits.add_u64(x65, x202, u64(fiat.u1(x205))) + x208, x209 := bits.add_u64(x76, x204, u64(0x0)) + x210, _ := bits.add_u64(x77, x206, u64(fiat.u1(x209))) + x212, x213 := bits.add_u64(x88, x208, u64(0x0)) + x214, _ := bits.add_u64(x89, x210, u64(fiat.u1(x213))) + x216, x217 := bits.add_u64(x98, x212, u64(0x0)) + x218, _ := bits.add_u64(x99, x214, u64(fiat.u1(x217))) + x220, x221 := bits.add_u64(x110, x216, u64(0x0)) + x222, _ := bits.add_u64(x111, x218, u64(fiat.u1(x221))) + x224, x225 := bits.add_u64(x122, x220, u64(0x0)) + x226, _ := bits.add_u64(x123, x222, u64(fiat.u1(x225))) + x228, x229 := bits.add_u64(x36, x34, u64(0x0)) + x230, _ := bits.add_u64(x37, x35, u64(fiat.u1(x229))) + x232, x233 := bits.add_u64(x42, x228, u64(0x0)) + x234, _ := bits.add_u64(x43, x230, u64(fiat.u1(x233))) + x236, x237 := bits.add_u64(x44, x232, u64(0x0)) + x238, _ := bits.add_u64(x45, x234, u64(fiat.u1(x237))) + x240, x241 := bits.add_u64(x68, x236, u64(0x0)) + x242, _ := bits.add_u64(x69, x238, u64(fiat.u1(x241))) + x244, x245 := bits.add_u64(x80, x240, u64(0x0)) + x246, _ := bits.add_u64(x81, x242, u64(fiat.u1(x245))) + x248, x249 := bits.add_u64(x92, x244, u64(0x0)) + x250, _ := bits.add_u64(x93, x246, u64(fiat.u1(x249))) + x252, x253 := bits.add_u64(x100, x248, u64(0x0)) + x254, _ := bits.add_u64(x101, x250, u64(fiat.u1(x253))) + x256, x257 := bits.add_u64(x104, x252, u64(0x0)) + x258, _ := bits.add_u64(x105, x254, u64(fiat.u1(x257))) + x260, x261 := bits.add_u64(x112, x256, u64(0x0)) + x262, _ := bits.add_u64(x113, x258, u64(fiat.u1(x261))) + x264, x265 := bits.add_u64(x124, x260, u64(0x0)) + x266, _ := bits.add_u64(x125, x262, u64(fiat.u1(x265))) + x268, x269 := bits.add_u64(x50, x22, u64(0x0)) + x270, _ := bits.add_u64(x51, x23, u64(fiat.u1(x269))) + x272, x273 := bits.add_u64(x58, x268, u64(0x0)) + x274, _ := bits.add_u64(x59, x270, u64(fiat.u1(x273))) + x276, x277 := bits.add_u64(x70, x272, u64(0x0)) + x278, _ := bits.add_u64(x71, x274, u64(fiat.u1(x277))) + x280, x281 := bits.add_u64(x116, x276, u64(0x0)) + x282, _ := bits.add_u64(x117, x278, u64(fiat.u1(x281))) + x284, x285 := bits.add_u64(x128, x280, u64(0x0)) + x286, _ := bits.add_u64(x129, x282, u64(fiat.u1(x285))) + x288, x289 := bits.add_u64(x62, x24, u64(0x0)) + x290, _ := bits.add_u64(x63, x25, u64(fiat.u1(x289))) + x292, x293 := bits.add_u64(x74, x288, u64(0x0)) + x294, _ := bits.add_u64(x75, x290, u64(fiat.u1(x293))) + x296, x297 := bits.add_u64(x86, x292, u64(0x0)) + x298, _ := bits.add_u64(x87, x294, u64(fiat.u1(x297))) + x300, x301 := bits.add_u64(x130, x296, u64(0x0)) + x302, _ := bits.add_u64(x131, x298, u64(fiat.u1(x301))) + x304, x305 := bits.add_u64(x28, x26, u64(0x0)) + x306, _ := bits.add_u64(x29, x27, u64(fiat.u1(x305))) + x308, x309 := bits.add_u64(x66, x304, u64(0x0)) + x310, _ := bits.add_u64(x67, x306, u64(fiat.u1(x309))) + x312, x313 := bits.add_u64(x78, x308, u64(0x0)) + x314, _ := bits.add_u64(x79, x310, u64(fiat.u1(x313))) + x316, x317 := bits.add_u64(x90, x312, u64(0x0)) + x318, _ := bits.add_u64(x91, x314, u64(fiat.u1(x317))) + x320, x321 := bits.add_u64(x102, x316, u64(0x0)) + x322, _ := bits.add_u64(x103, x318, u64(fiat.u1(x321))) + x324, x325 := bits.add_u64(x132, x320, u64(0x0)) + x326, _ := bits.add_u64(x133, x322, u64(fiat.u1(x325))) + x328, x329 := bits.add_u64(x146, x264, u64(0x0)) + x330 := (u64(fiat.u1(x329)) + x266) + x331 := ((x164 >> 56) | ((x166 << 8) & 0xffffffffffffffff)) + x332 := (x164 & 0xffffffffffffff) + x333, x334 := bits.add_u64(x328, x331, u64(0x0)) + x335 := (u64(fiat.u1(x334)) + x330) + x336 := ((x333 >> 56) | ((x335 << 8) & 0xffffffffffffffff)) + x337 := (x333 & 0xffffffffffffff) + x338, x339 := bits.add_u64(x324, x331, u64(0x0)) + x340 := (u64(fiat.u1(x339)) + x326) + x341, x342 := bits.add_u64(x336, x224, u64(0x0)) + x343 := (u64(fiat.u1(x342)) + x226) + x344 := ((x338 >> 56) | ((x340 << 8) & 0xffffffffffffffff)) + x345 := (x338 & 0xffffffffffffff) + x346, x347 := bits.add_u64(x344, x300, u64(0x0)) + x348 := (u64(fiat.u1(x347)) + x302) + x349 := ((x341 >> 56) | ((x343 << 8) & 0xffffffffffffffff)) + x350 := (x341 & 0xffffffffffffff) + x351, x352 := bits.add_u64(x349, x196, u64(0x0)) + x353 := (u64(fiat.u1(x352)) + x198) + x354 := ((x346 >> 56) | ((x348 << 8) & 0xffffffffffffffff)) + x355 := (x346 & 0xffffffffffffff) + x356, x357 := bits.add_u64(x354, x284, u64(0x0)) + x358 := (u64(fiat.u1(x357)) + x286) + x359 := ((x351 >> 56) | ((x353 << 8) & 0xffffffffffffffff)) + x360 := (x351 & 0xffffffffffffff) + x361 := (x359 + x332) + x362 := ((x356 >> 56) | ((x358 << 8) & 0xffffffffffffffff)) + x363 := (x356 & 0xffffffffffffff) + x364 := (x362 + x147) + x365 := (x361 >> 56) + x366 := (x361 & 0xffffffffffffff) + x367 := (x364 >> 56) + x368 := (x364 & 0xffffffffffffff) + x369 := (x337 + x365) + x370 := (x345 + x365) + x371 := (x367 + x369) + x372 := fiat.u1((x371 >> 56)) + x373 := (x371 & 0xffffffffffffff) + x374 := (u64(x372) + x350) + x375 := fiat.u1((x370 >> 56)) + x376 := (x370 & 0xffffffffffffff) + x377 := (u64(x375) + x355) + out1[0] = x376 + out1[1] = x377 + out1[2] = x363 + out1[3] = x368 + out1[4] = x373 + out1[5] = x374 + out1[6] = x360 + out1[7] = x366 +} + +fe_carry :: proc "contextless" (out1: ^Tight_Field_Element, arg1: ^Loose_Field_Element) { + x1 := arg1[3] + x2 := arg1[7] + x3 := (x2 >> 56) + x4 := (((x1 >> 56) + arg1[4]) + x3) + x5 := (arg1[0] + x3) + x6 := ((x4 >> 56) + arg1[5]) + x7 := ((x5 >> 56) + arg1[1]) + x8 := ((x6 >> 56) + arg1[6]) + x9 := ((x7 >> 56) + arg1[2]) + x10 := ((x8 >> 56) + (x2 & 0xffffffffffffff)) + x11 := ((x9 >> 56) + (x1 & 0xffffffffffffff)) + x12 := fiat.u1((x10 >> 56)) + x13 := ((x5 & 0xffffffffffffff) + u64(x12)) + x14 := (u64(fiat.u1((x11 >> 56))) + ((x4 & 0xffffffffffffff) + u64(x12))) + x15 := (x13 & 0xffffffffffffff) + x16 := (u64(fiat.u1((x13 >> 56))) + (x7 & 0xffffffffffffff)) + x17 := (x9 & 0xffffffffffffff) + x18 := (x11 & 0xffffffffffffff) + x19 := (x14 & 0xffffffffffffff) + x20 := (u64(fiat.u1((x14 >> 56))) + (x6 & 0xffffffffffffff)) + x21 := (x8 & 0xffffffffffffff) + x22 := (x10 & 0xffffffffffffff) + out1[0] = x15 + out1[1] = x16 + out1[2] = x17 + out1[3] = x18 + out1[4] = x19 + out1[5] = x20 + out1[6] = x21 + out1[7] = x22 +} + +fe_add :: proc "contextless" (out1: ^Loose_Field_Element, arg1, arg2: ^Tight_Field_Element) { + x1 := (arg1[0] + arg2[0]) + x2 := (arg1[1] + arg2[1]) + x3 := (arg1[2] + arg2[2]) + x4 := (arg1[3] + arg2[3]) + x5 := (arg1[4] + arg2[4]) + x6 := (arg1[5] + arg2[5]) + x7 := (arg1[6] + arg2[6]) + x8 := (arg1[7] + arg2[7]) + out1[0] = x1 + out1[1] = x2 + out1[2] = x3 + out1[3] = x4 + out1[4] = x5 + out1[5] = x6 + out1[6] = x7 + out1[7] = x8 +} + +fe_sub :: proc "contextless" (out1: ^Loose_Field_Element, arg1, arg2: ^Tight_Field_Element) { + x1 := ((0x1fffffffffffffe + arg1[0]) - arg2[0]) + x2 := ((0x1fffffffffffffe + arg1[1]) - arg2[1]) + x3 := ((0x1fffffffffffffe + arg1[2]) - arg2[2]) + x4 := ((0x1fffffffffffffe + arg1[3]) - arg2[3]) + x5 := ((0x1fffffffffffffc + arg1[4]) - arg2[4]) + x6 := ((0x1fffffffffffffe + arg1[5]) - arg2[5]) + x7 := ((0x1fffffffffffffe + arg1[6]) - arg2[6]) + x8 := ((0x1fffffffffffffe + arg1[7]) - arg2[7]) + out1[0] = x1 + out1[1] = x2 + out1[2] = x3 + out1[3] = x4 + out1[4] = x5 + out1[5] = x6 + out1[6] = x7 + out1[7] = x8 +} + +fe_opp :: proc "contextless" (out1: ^Loose_Field_Element, arg1: ^Tight_Field_Element) { + x1 := (0x1fffffffffffffe - arg1[0]) + x2 := (0x1fffffffffffffe - arg1[1]) + x3 := (0x1fffffffffffffe - arg1[2]) + x4 := (0x1fffffffffffffe - arg1[3]) + x5 := (0x1fffffffffffffc - arg1[4]) + x6 := (0x1fffffffffffffe - arg1[5]) + x7 := (0x1fffffffffffffe - arg1[6]) + x8 := (0x1fffffffffffffe - arg1[7]) + out1[0] = x1 + out1[1] = x2 + out1[2] = x3 + out1[3] = x4 + out1[4] = x5 + out1[5] = x6 + out1[6] = x7 + out1[7] = x8 +} + +@(optimization_mode = "none") +fe_cond_assign :: #force_no_inline proc "contextless" ( + out1, arg1: ^Tight_Field_Element, + arg2: int, +) { + x1 := fiat.cmovznz_u64(fiat.u1(arg2), out1[0], arg1[0]) + x2 := fiat.cmovznz_u64(fiat.u1(arg2), out1[1], arg1[1]) + x3 := fiat.cmovznz_u64(fiat.u1(arg2), out1[2], arg1[2]) + x4 := fiat.cmovznz_u64(fiat.u1(arg2), out1[3], arg1[3]) + x5 := fiat.cmovznz_u64(fiat.u1(arg2), out1[4], arg1[4]) + x6 := fiat.cmovznz_u64(fiat.u1(arg2), out1[5], arg1[5]) + x7 := fiat.cmovznz_u64(fiat.u1(arg2), out1[6], arg1[6]) + x8 := fiat.cmovznz_u64(fiat.u1(arg2), out1[7], arg1[7]) + out1[0] = x1 + out1[1] = x2 + out1[2] = x3 + out1[3] = x4 + out1[4] = x5 + out1[5] = x6 + out1[6] = x7 + out1[7] = x8 +} + +fe_to_bytes :: proc "contextless" (out1: ^[56]byte, arg1: ^Tight_Field_Element) { + x1, x2 := _subborrowx_u56(0x0, arg1[0], 0xffffffffffffff) + x3, x4 := _subborrowx_u56(x2, arg1[1], 0xffffffffffffff) + x5, x6 := _subborrowx_u56(x4, arg1[2], 0xffffffffffffff) + x7, x8 := _subborrowx_u56(x6, arg1[3], 0xffffffffffffff) + x9, x10 := _subborrowx_u56(x8, arg1[4], 0xfffffffffffffe) + x11, x12 := _subborrowx_u56(x10, arg1[5], 0xffffffffffffff) + x13, x14 := _subborrowx_u56(x12, arg1[6], 0xffffffffffffff) + x15, x16 := _subborrowx_u56(x14, arg1[7], 0xffffffffffffff) + x17 := fiat.cmovznz_u64(x16, u64(0x0), 0xffffffffffffffff) + x18, x19 := _addcarryx_u56(0x0, x1, (x17 & 0xffffffffffffff)) + x20, x21 := _addcarryx_u56(x19, x3, (x17 & 0xffffffffffffff)) + x22, x23 := _addcarryx_u56(x21, x5, (x17 & 0xffffffffffffff)) + x24, x25 := _addcarryx_u56(x23, x7, (x17 & 0xffffffffffffff)) + x26, x27 := _addcarryx_u56(x25, x9, (x17 & 0xfffffffffffffe)) + x28, x29 := _addcarryx_u56(x27, x11, (x17 & 0xffffffffffffff)) + x30, x31 := _addcarryx_u56(x29, x13, (x17 & 0xffffffffffffff)) + x32, _ := _addcarryx_u56(x31, x15, (x17 & 0xffffffffffffff)) + x34 := (u8(x18) & 0xff) + x35 := (x18 >> 8) + x36 := (u8(x35) & 0xff) + x37 := (x35 >> 8) + x38 := (u8(x37) & 0xff) + x39 := (x37 >> 8) + x40 := (u8(x39) & 0xff) + x41 := (x39 >> 8) + x42 := (u8(x41) & 0xff) + x43 := (x41 >> 8) + x44 := (u8(x43) & 0xff) + x45 := u8((x43 >> 8)) + x46 := (u8(x20) & 0xff) + x47 := (x20 >> 8) + x48 := (u8(x47) & 0xff) + x49 := (x47 >> 8) + x50 := (u8(x49) & 0xff) + x51 := (x49 >> 8) + x52 := (u8(x51) & 0xff) + x53 := (x51 >> 8) + x54 := (u8(x53) & 0xff) + x55 := (x53 >> 8) + x56 := (u8(x55) & 0xff) + x57 := u8((x55 >> 8)) + x58 := (u8(x22) & 0xff) + x59 := (x22 >> 8) + x60 := (u8(x59) & 0xff) + x61 := (x59 >> 8) + x62 := (u8(x61) & 0xff) + x63 := (x61 >> 8) + x64 := (u8(x63) & 0xff) + x65 := (x63 >> 8) + x66 := (u8(x65) & 0xff) + x67 := (x65 >> 8) + x68 := (u8(x67) & 0xff) + x69 := u8((x67 >> 8)) + x70 := (u8(x24) & 0xff) + x71 := (x24 >> 8) + x72 := (u8(x71) & 0xff) + x73 := (x71 >> 8) + x74 := (u8(x73) & 0xff) + x75 := (x73 >> 8) + x76 := (u8(x75) & 0xff) + x77 := (x75 >> 8) + x78 := (u8(x77) & 0xff) + x79 := (x77 >> 8) + x80 := (u8(x79) & 0xff) + x81 := u8((x79 >> 8)) + x82 := (u8(x26) & 0xff) + x83 := (x26 >> 8) + x84 := (u8(x83) & 0xff) + x85 := (x83 >> 8) + x86 := (u8(x85) & 0xff) + x87 := (x85 >> 8) + x88 := (u8(x87) & 0xff) + x89 := (x87 >> 8) + x90 := (u8(x89) & 0xff) + x91 := (x89 >> 8) + x92 := (u8(x91) & 0xff) + x93 := u8((x91 >> 8)) + x94 := (u8(x28) & 0xff) + x95 := (x28 >> 8) + x96 := (u8(x95) & 0xff) + x97 := (x95 >> 8) + x98 := (u8(x97) & 0xff) + x99 := (x97 >> 8) + x100 := (u8(x99) & 0xff) + x101 := (x99 >> 8) + x102 := (u8(x101) & 0xff) + x103 := (x101 >> 8) + x104 := (u8(x103) & 0xff) + x105 := u8((x103 >> 8)) + x106 := (u8(x30) & 0xff) + x107 := (x30 >> 8) + x108 := (u8(x107) & 0xff) + x109 := (x107 >> 8) + x110 := (u8(x109) & 0xff) + x111 := (x109 >> 8) + x112 := (u8(x111) & 0xff) + x113 := (x111 >> 8) + x114 := (u8(x113) & 0xff) + x115 := (x113 >> 8) + x116 := (u8(x115) & 0xff) + x117 := u8((x115 >> 8)) + x118 := (u8(x32) & 0xff) + x119 := (x32 >> 8) + x120 := (u8(x119) & 0xff) + x121 := (x119 >> 8) + x122 := (u8(x121) & 0xff) + x123 := (x121 >> 8) + x124 := (u8(x123) & 0xff) + x125 := (x123 >> 8) + x126 := (u8(x125) & 0xff) + x127 := (x125 >> 8) + x128 := (u8(x127) & 0xff) + x129 := u8((x127 >> 8)) + out1[0] = x34 + out1[1] = x36 + out1[2] = x38 + out1[3] = x40 + out1[4] = x42 + out1[5] = x44 + out1[6] = x45 + out1[7] = x46 + out1[8] = x48 + out1[9] = x50 + out1[10] = x52 + out1[11] = x54 + out1[12] = x56 + out1[13] = x57 + out1[14] = x58 + out1[15] = x60 + out1[16] = x62 + out1[17] = x64 + out1[18] = x66 + out1[19] = x68 + out1[20] = x69 + out1[21] = x70 + out1[22] = x72 + out1[23] = x74 + out1[24] = x76 + out1[25] = x78 + out1[26] = x80 + out1[27] = x81 + out1[28] = x82 + out1[29] = x84 + out1[30] = x86 + out1[31] = x88 + out1[32] = x90 + out1[33] = x92 + out1[34] = x93 + out1[35] = x94 + out1[36] = x96 + out1[37] = x98 + out1[38] = x100 + out1[39] = x102 + out1[40] = x104 + out1[41] = x105 + out1[42] = x106 + out1[43] = x108 + out1[44] = x110 + out1[45] = x112 + out1[46] = x114 + out1[47] = x116 + out1[48] = x117 + out1[49] = x118 + out1[50] = x120 + out1[51] = x122 + out1[52] = x124 + out1[53] = x126 + out1[54] = x128 + out1[55] = x129 +} + +fe_from_bytes :: proc "contextless" (out1: ^Tight_Field_Element, arg1: ^[56]byte) { + x1 := (u64(arg1[55]) << 48) + x2 := (u64(arg1[54]) << 40) + x3 := (u64(arg1[53]) << 32) + x4 := (u64(arg1[52]) << 24) + x5 := (u64(arg1[51]) << 16) + x6 := (u64(arg1[50]) << 8) + x7 := arg1[49] + x8 := (u64(arg1[48]) << 48) + x9 := (u64(arg1[47]) << 40) + x10 := (u64(arg1[46]) << 32) + x11 := (u64(arg1[45]) << 24) + x12 := (u64(arg1[44]) << 16) + x13 := (u64(arg1[43]) << 8) + x14 := arg1[42] + x15 := (u64(arg1[41]) << 48) + x16 := (u64(arg1[40]) << 40) + x17 := (u64(arg1[39]) << 32) + x18 := (u64(arg1[38]) << 24) + x19 := (u64(arg1[37]) << 16) + x20 := (u64(arg1[36]) << 8) + x21 := arg1[35] + x22 := (u64(arg1[34]) << 48) + x23 := (u64(arg1[33]) << 40) + x24 := (u64(arg1[32]) << 32) + x25 := (u64(arg1[31]) << 24) + x26 := (u64(arg1[30]) << 16) + x27 := (u64(arg1[29]) << 8) + x28 := arg1[28] + x29 := (u64(arg1[27]) << 48) + x30 := (u64(arg1[26]) << 40) + x31 := (u64(arg1[25]) << 32) + x32 := (u64(arg1[24]) << 24) + x33 := (u64(arg1[23]) << 16) + x34 := (u64(arg1[22]) << 8) + x35 := arg1[21] + x36 := (u64(arg1[20]) << 48) + x37 := (u64(arg1[19]) << 40) + x38 := (u64(arg1[18]) << 32) + x39 := (u64(arg1[17]) << 24) + x40 := (u64(arg1[16]) << 16) + x41 := (u64(arg1[15]) << 8) + x42 := arg1[14] + x43 := (u64(arg1[13]) << 48) + x44 := (u64(arg1[12]) << 40) + x45 := (u64(arg1[11]) << 32) + x46 := (u64(arg1[10]) << 24) + x47 := (u64(arg1[9]) << 16) + x48 := (u64(arg1[8]) << 8) + x49 := arg1[7] + x50 := (u64(arg1[6]) << 48) + x51 := (u64(arg1[5]) << 40) + x52 := (u64(arg1[4]) << 32) + x53 := (u64(arg1[3]) << 24) + x54 := (u64(arg1[2]) << 16) + x55 := (u64(arg1[1]) << 8) + x56 := arg1[0] + x57 := (x55 + u64(x56)) + x58 := (x54 + x57) + x59 := (x53 + x58) + x60 := (x52 + x59) + x61 := (x51 + x60) + x62 := (x50 + x61) + x63 := (x48 + u64(x49)) + x64 := (x47 + x63) + x65 := (x46 + x64) + x66 := (x45 + x65) + x67 := (x44 + x66) + x68 := (x43 + x67) + x69 := (x41 + u64(x42)) + x70 := (x40 + x69) + x71 := (x39 + x70) + x72 := (x38 + x71) + x73 := (x37 + x72) + x74 := (x36 + x73) + x75 := (x34 + u64(x35)) + x76 := (x33 + x75) + x77 := (x32 + x76) + x78 := (x31 + x77) + x79 := (x30 + x78) + x80 := (x29 + x79) + x81 := (x27 + u64(x28)) + x82 := (x26 + x81) + x83 := (x25 + x82) + x84 := (x24 + x83) + x85 := (x23 + x84) + x86 := (x22 + x85) + x87 := (x20 + u64(x21)) + x88 := (x19 + x87) + x89 := (x18 + x88) + x90 := (x17 + x89) + x91 := (x16 + x90) + x92 := (x15 + x91) + x93 := (x13 + u64(x14)) + x94 := (x12 + x93) + x95 := (x11 + x94) + x96 := (x10 + x95) + x97 := (x9 + x96) + x98 := (x8 + x97) + x99 := (x6 + u64(x7)) + x100 := (x5 + x99) + x101 := (x4 + x100) + x102 := (x3 + x101) + x103 := (x2 + x102) + x104 := (x1 + x103) + out1[0] = x62 + out1[1] = x68 + out1[2] = x74 + out1[3] = x80 + out1[4] = x86 + out1[5] = x92 + out1[6] = x98 + out1[7] = x104 +} + +fe_relax :: proc "contextless" (out1: ^Loose_Field_Element, arg1: ^Tight_Field_Element) { + x1 := arg1[0] + x2 := arg1[1] + x3 := arg1[2] + x4 := arg1[3] + x5 := arg1[4] + x6 := arg1[5] + x7 := arg1[6] + x8 := arg1[7] + out1[0] = x1 + out1[1] = x2 + out1[2] = x3 + out1[3] = x4 + out1[4] = x5 + out1[5] = x6 + out1[6] = x7 + out1[7] = x8 +} diff --git a/core/crypto/_fiat/field_poly1305/field.odin b/core/crypto/_fiat/field_poly1305/field.odin index b12046858..caaece98e 100644 --- a/core/crypto/_fiat/field_poly1305/field.odin +++ b/core/crypto/_fiat/field_poly1305/field.odin @@ -1,6 +1,5 @@ package field_poly1305 -import "base:intrinsics" import "core:encoding/endian" import "core:mem" @@ -29,9 +28,7 @@ fe_from_bytes :: #force_inline proc "contextless" ( // makes implementing the actual MAC block processing considerably // neater. - if len(arg1) != 16 { - intrinsics.trap() - } + ensure_contextless(len(arg1) == 16, "poly1305: invalid field element size") // While it may be unwise to do deserialization here on our // own when fiat-crypto provides equivalent functionality, diff --git a/core/crypto/_fiat/field_scalar25519/field.odin b/core/crypto/_fiat/field_scalar25519/field.odin index 9b40661b7..933637c54 100644 --- a/core/crypto/_fiat/field_scalar25519/field.odin +++ b/core/crypto/_fiat/field_scalar25519/field.odin @@ -1,18 +1,17 @@ package field_scalar25519 -import "base:intrinsics" import "core:encoding/endian" import "core:math/bits" import "core:mem" -@(private) +@(private, rodata) _TWO_168 := Montgomery_Domain_Field_Element { 0x5b8ab432eac74798, 0x38afddd6de59d5d7, 0xa2c131b399411b7c, 0x6329a7ed9ce5a30, } -@(private) +@(private, rodata) _TWO_336 := Montgomery_Domain_Field_Element { 0xbd3d108e2b35ecc5, 0x5c3a3718bdf9c90b, @@ -95,9 +94,8 @@ fe_from_bytes_wide :: proc "contextless" ( @(private) _fe_from_bytes_short :: proc "contextless" (out1: ^Montgomery_Domain_Field_Element, arg1: []byte) { // INVARIANT: len(arg1) < 32. - if len(arg1) >= 32 { - intrinsics.trap() - } + ensure_contextless(len(arg1) < 32, "edwards25519: oversized short scalar") + tmp: [32]byte copy(tmp[:], arg1) @@ -106,9 +104,7 @@ _fe_from_bytes_short :: proc "contextless" (out1: ^Montgomery_Domain_Field_Eleme } fe_to_bytes :: proc "contextless" (out1: []byte, arg1: ^Montgomery_Domain_Field_Element) { - if len(out1) != 32 { - intrinsics.trap() - } + ensure_contextless(len(out1) == 32, "edwards25519: oversized scalar output buffer") tmp: Non_Montgomery_Domain_Field_Element fe_from_montgomery(&tmp, arg1) diff --git a/core/crypto/_sha3/sha3.odin b/core/crypto/_sha3/sha3.odin index 2db76fce0..52b3fbda9 100644 --- a/core/crypto/_sha3/sha3.odin +++ b/core/crypto/_sha3/sha3.odin @@ -44,7 +44,7 @@ Context :: struct { is_finalized: bool, // For SHAKE (unlimited squeeze is allowed) } -@(private) +@(private, rodata) keccakf_rndc := [?]u64 { 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, 0x8000000080008000, 0x000000000000808b, 0x0000000080000001, @@ -56,13 +56,13 @@ keccakf_rndc := [?]u64 { 0x8000000000008080, 0x0000000080000001, 0x8000000080008008, } -@(private) +@(private, rodata) keccakf_rotc := [?]int { 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44, } -@(private) +@(private, rodata) keccakf_piln := [?]i32 { 10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1, @@ -122,7 +122,7 @@ keccakf :: proc "contextless" (st: ^[25]u64) { } } -init :: proc(ctx: ^Context) { +init :: proc "contextless" (ctx: ^Context) { for i := 0; i < 25; i += 1 { ctx.st.q[i] = 0 } @@ -133,9 +133,9 @@ init :: proc(ctx: ^Context) { ctx.is_finalized = false } -update :: proc(ctx: ^Context, data: []byte) { - assert(ctx.is_initialized) - assert(!ctx.is_finalized) +update :: proc "contextless" (ctx: ^Context, data: []byte) { + ensure_contextless(ctx.is_initialized) + ensure_contextless(!ctx.is_finalized) j := ctx.pt for i := 0; i < len(data); i += 1 { @@ -149,12 +149,9 @@ update :: proc(ctx: ^Context, data: []byte) { ctx.pt = j } -final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) { - assert(ctx.is_initialized) - - if len(hash) < ctx.mdlen { - panic("crypto/sha3: invalid destination digest size") - } +final :: proc "contextless" (ctx: ^Context, hash: []byte, finalize_clone: bool = false) { + ensure_contextless(ctx.is_initialized) + ensure_contextless(len(hash) >= ctx.mdlen, "crypto/sha3: invalid destination digest size") ctx := ctx if finalize_clone { @@ -173,11 +170,11 @@ final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) { } } -clone :: proc(ctx, other: ^Context) { +clone :: proc "contextless" (ctx, other: ^Context) { ctx^ = other^ } -reset :: proc(ctx: ^Context) { +reset :: proc "contextless" (ctx: ^Context) { if !ctx.is_initialized { return } @@ -185,9 +182,9 @@ reset :: proc(ctx: ^Context) { mem.zero_explicit(ctx, size_of(ctx^)) } -shake_xof :: proc(ctx: ^Context) { - assert(ctx.is_initialized) - assert(!ctx.is_finalized) +shake_xof :: proc "contextless" (ctx: ^Context) { + ensure_contextless(ctx.is_initialized) + ensure_contextless(!ctx.is_finalized) ctx.st.b[ctx.pt] ~= ctx.dsbyte ctx.st.b[ctx.rsiz - 1] ~= 0x80 @@ -197,9 +194,9 @@ shake_xof :: proc(ctx: ^Context) { ctx.is_finalized = true // No more absorb, unlimited squeeze. } -shake_out :: proc(ctx: ^Context, hash: []byte) { - assert(ctx.is_initialized) - assert(ctx.is_finalized) +shake_out :: proc "contextless" (ctx: ^Context, hash: []byte) { + ensure_contextless(ctx.is_initialized) + ensure_contextless(ctx.is_finalized) j := ctx.pt for i := 0; i < len(hash); i += 1 { diff --git a/core/crypto/_sha3/sp800_185.odin b/core/crypto/_sha3/sp800_185.odin index a96f78cc1..8390d8490 100644 --- a/core/crypto/_sha3/sp800_185.odin +++ b/core/crypto/_sha3/sp800_185.odin @@ -3,7 +3,7 @@ package _sha3 import "core:encoding/endian" import "core:math/bits" -init_cshake :: proc(ctx: ^Context, n, s: []byte, sec_strength: int) { +init_cshake :: proc "contextless" (ctx: ^Context, n, s: []byte, sec_strength: int) { ctx.mdlen = sec_strength / 8 // No domain separator is equivalent to vanilla SHAKE. @@ -18,7 +18,7 @@ init_cshake :: proc(ctx: ^Context, n, s: []byte, sec_strength: int) { bytepad(ctx, [][]byte{n, s}, rate_cshake(sec_strength)) } -final_cshake :: proc(ctx: ^Context, dst: []byte, finalize_clone: bool = false) { +final_cshake :: proc "contextless" (ctx: ^Context, dst: []byte, finalize_clone: bool = false) { ctx := ctx if finalize_clone { tmp_ctx: Context @@ -32,7 +32,7 @@ final_cshake :: proc(ctx: ^Context, dst: []byte, finalize_clone: bool = false) { shake_out(ctx, dst) } -rate_cshake :: #force_inline proc(sec_strength: int) -> int { +rate_cshake :: #force_inline proc "contextless" (sec_strength: int) -> int { switch sec_strength { case 128: return RATE_128 @@ -40,7 +40,7 @@ rate_cshake :: #force_inline proc(sec_strength: int) -> int { return RATE_256 } - panic("crypto/sha3: invalid security strength") + panic_contextless("crypto/sha3: invalid security strength") } // right_encode and left_encode are defined to support 0 <= x < 2^2040 @@ -52,10 +52,10 @@ rate_cshake :: #force_inline proc(sec_strength: int) -> int { // // Thus we support 0 <= x < 2^128. -@(private) +@(private, rodata) _PAD: [RATE_128]byte // Biggest possible value of w per spec. -bytepad :: proc(ctx: ^Context, x_strings: [][]byte, w: int) { +bytepad :: proc "contextless" (ctx: ^Context, x_strings: [][]byte, w: int) { // 1. z = left_encode(w) || X. z_hi: u64 z_lo := left_right_encode(ctx, 0, u64(w), true) @@ -70,9 +70,7 @@ bytepad :: proc(ctx: ^Context, x_strings: [][]byte, w: int) { // This isn't actually possible, at least with the currently // defined SP 800-185 routines. - if carry != 0 { - panic("crypto/sha3: bytepad input length overflow") - } + ensure_contextless(carry == 0, "crypto/sha3: bytepad input length overflow") } // We skip this step as we are doing a byte-oriented implementation @@ -95,7 +93,7 @@ bytepad :: proc(ctx: ^Context, x_strings: [][]byte, w: int) { } } -encode_string :: #force_inline proc(ctx: ^Context, s: []byte) -> (u64, u64) { +encode_string :: #force_inline proc "contextless" (ctx: ^Context, s: []byte) -> (u64, u64) { l := encode_byte_len(ctx, len(s), true) // left_encode update(ctx, s) @@ -104,13 +102,13 @@ encode_string :: #force_inline proc(ctx: ^Context, s: []byte) -> (u64, u64) { return hi, lo } -encode_byte_len :: #force_inline proc(ctx: ^Context, l: int, is_left: bool) -> u64 { +encode_byte_len :: #force_inline proc "contextless" (ctx: ^Context, l: int, is_left: bool) -> u64 { hi, lo := bits.mul_u64(u64(l), 8) return left_right_encode(ctx, hi, lo, is_left) } @(private) -left_right_encode :: proc(ctx: ^Context, hi, lo: u64, is_left: bool) -> u64 { +left_right_encode :: proc "contextless" (ctx: ^Context, hi, lo: u64, is_left: bool) -> u64 { HI_OFFSET :: 1 LO_OFFSET :: HI_OFFSET + 8 RIGHT_OFFSET :: LO_OFFSET + 8 diff --git a/core/crypto/aead/aead.odin b/core/crypto/aead/aead.odin index 9b7d810e4..c8f324929 100644 --- a/core/crypto/aead/aead.odin +++ b/core/crypto/aead/aead.odin @@ -16,7 +16,7 @@ seal_oneshot :: proc(algo: Algorithm, dst, tag, key, iv, aad, plaintext: []byte, // returning true iff the authentication was successful. If authentication // fails, the destination buffer will be zeroed. // -// dst and plaintext MUST alias exactly or not at all. +// dst and ciphertext MUST alias exactly or not at all. @(require_results) open_oneshot :: proc(algo: Algorithm, dst, key, iv, aad, ciphertext, tag: []byte, impl: Implementation = nil) -> bool { ctx: Context diff --git a/core/crypto/aead/low_level.odin b/core/crypto/aead/low_level.odin index 38a0c84ba..c80574a0d 100644 --- a/core/crypto/aead/low_level.odin +++ b/core/crypto/aead/low_level.odin @@ -1,8 +1,10 @@ package aead +import "core:crypto/aegis" import "core:crypto/aes" import "core:crypto/chacha20" import "core:crypto/chacha20poly1305" +import "core:crypto/deoxysii" import "core:reflect" // Implementation is an AEAD implementation. Most callers will not need @@ -15,7 +17,7 @@ Implementation :: union { // MAX_TAG_SIZE is the maximum size tag that can be returned by any of the // Algorithms supported via this package. -MAX_TAG_SIZE :: 16 +MAX_TAG_SIZE :: 32 // Algorithm is the algorithm identifier associated with a given Context. Algorithm :: enum { @@ -25,9 +27,14 @@ Algorithm :: enum { AES_GCM_256, CHACHA20POLY1305, XCHACHA20POLY1305, + AEGIS_128L, + AEGIS_128L_256, // AEGIS-128L (256-bit tag) + AEGIS_256, + AEGIS_256_256, // AEGIS-256 (256-bit tag) + DEOXYS_II_256, } -// ALGORITM_NAMES is the Agorithm to algorithm name string. +// ALGORITM_NAMES is the Algorithm to algorithm name string. ALGORITHM_NAMES := [Algorithm]string { .Invalid = "Invalid", .AES_GCM_128 = "AES-GCM-128", @@ -35,6 +42,11 @@ ALGORITHM_NAMES := [Algorithm]string { .AES_GCM_256 = "AES-GCM-256", .CHACHA20POLY1305 = "chacha20poly1305", .XCHACHA20POLY1305 = "xchacha20poly1305", + .AEGIS_128L = "AEGIS-128L", + .AEGIS_128L_256 = "AEGIS-128L-256", + .AEGIS_256 = "AEGIS-256", + .AEGIS_256_256 = "AEGIS-256-256", + .DEOXYS_II_256 = "Deoxys-II-256", } // TAG_SIZES is the Algorithm to tag size in bytes. @@ -45,6 +57,11 @@ TAG_SIZES := [Algorithm]int { .AES_GCM_256 = aes.GCM_TAG_SIZE, .CHACHA20POLY1305 = chacha20poly1305.TAG_SIZE, .XCHACHA20POLY1305 = chacha20poly1305.TAG_SIZE, + .AEGIS_128L = aegis.TAG_SIZE_128, + .AEGIS_128L_256 = aegis.TAG_SIZE_256, + .AEGIS_256 = aegis.TAG_SIZE_128, + .AEGIS_256_256 = aegis.TAG_SIZE_256, + .DEOXYS_II_256 = deoxysii.TAG_SIZE, } // KEY_SIZES is the Algorithm to key size in bytes. @@ -55,6 +72,11 @@ KEY_SIZES := [Algorithm]int { .AES_GCM_256 = aes.KEY_SIZE_256, .CHACHA20POLY1305 = chacha20poly1305.KEY_SIZE, .XCHACHA20POLY1305 = chacha20poly1305.KEY_SIZE, + .AEGIS_128L = aegis.KEY_SIZE_128L, + .AEGIS_128L_256 = aegis.KEY_SIZE_128L, + .AEGIS_256 = aegis.KEY_SIZE_256, + .AEGIS_256_256 = aegis.KEY_SIZE_256, + .DEOXYS_II_256 = deoxysii.KEY_SIZE, } // IV_SIZES is the Algorithm to initialization vector size in bytes. @@ -67,6 +89,11 @@ IV_SIZES := [Algorithm]int { .AES_GCM_256 = aes.GCM_IV_SIZE, .CHACHA20POLY1305 = chacha20poly1305.IV_SIZE, .XCHACHA20POLY1305 = chacha20poly1305.XIV_SIZE, + .AEGIS_128L = aegis.IV_SIZE_128L, + .AEGIS_128L_256 = aegis.IV_SIZE_128L, + .AEGIS_256 = aegis.IV_SIZE_256, + .AEGIS_256_256 = aegis.IV_SIZE_256, + .DEOXYS_II_256 = deoxysii.IV_SIZE, } // Context is a concrete instantiation of a specific AEAD algorithm. @@ -75,6 +102,8 @@ Context :: struct { _impl: union { aes.Context_GCM, chacha20poly1305.Context, + aegis.Context, + deoxysii.Context, }, } @@ -86,6 +115,11 @@ _IMPL_IDS := [Algorithm]typeid { .AES_GCM_256 = typeid_of(aes.Context_GCM), .CHACHA20POLY1305 = typeid_of(chacha20poly1305.Context), .XCHACHA20POLY1305 = typeid_of(chacha20poly1305.Context), + .AEGIS_128L = typeid_of(aegis.Context), + .AEGIS_128L_256 = typeid_of(aegis.Context), + .AEGIS_256 = typeid_of(aegis.Context), + .AEGIS_256_256 = typeid_of(aegis.Context), + .DEOXYS_II_256 = typeid_of(deoxysii.Context), } // init initializes a Context with a specific AEAD Algorithm. @@ -94,9 +128,7 @@ init :: proc(ctx: ^Context, algorithm: Algorithm, key: []byte, impl: Implementat reset(ctx) } - if len(key) != KEY_SIZES[algorithm] { - panic("crypto/aead: invalid key size") - } + ensure(len(key) == KEY_SIZES[algorithm], "crypto/aead: invalid key size") // Directly specialize the union by setting the type ID (save a copy). reflect.set_union_variant_typeid( @@ -113,6 +145,12 @@ init :: proc(ctx: ^Context, algorithm: Algorithm, key: []byte, impl: Implementat case .XCHACHA20POLY1305: impl_ := impl != nil ? impl.(chacha20.Implementation) : chacha20.DEFAULT_IMPLEMENTATION chacha20poly1305.init_xchacha(&ctx._impl.(chacha20poly1305.Context), key, impl_) + case .AEGIS_128L, .AEGIS_128L_256, .AEGIS_256, .AEGIS_256_256: + impl_ := impl != nil ? impl.(aes.Implementation) : aes.DEFAULT_IMPLEMENTATION + aegis.init(&ctx._impl.(aegis.Context), key, impl_) + case .DEOXYS_II_256: + impl_ := impl != nil ? impl.(aes.Implementation) : aes.DEFAULT_IMPLEMENTATION + deoxysii.init(&ctx._impl.(deoxysii.Context), key, impl_) case .Invalid: panic("crypto/aead: uninitialized algorithm") case: @@ -127,11 +165,17 @@ init :: proc(ctx: ^Context, algorithm: Algorithm, key: []byte, impl: Implementat // // dst and plaintext MUST alias exactly or not at all. seal_ctx :: proc(ctx: ^Context, dst, tag, iv, aad, plaintext: []byte) { + ensure(len(tag) == TAG_SIZES[ctx._algo], "crypto/aead: invalid tag size") + switch &impl in ctx._impl { case aes.Context_GCM: aes.seal_gcm(&impl, dst, tag, iv, aad, plaintext) case chacha20poly1305.Context: chacha20poly1305.seal(&impl, dst, tag, iv, aad, plaintext) + case aegis.Context: + aegis.seal(&impl, dst, tag, iv, aad, plaintext) + case deoxysii.Context: + deoxysii.seal(&impl, dst, tag, iv, aad, plaintext) case: panic("crypto/aead: uninitialized algorithm") } @@ -145,11 +189,17 @@ seal_ctx :: proc(ctx: ^Context, dst, tag, iv, aad, plaintext: []byte) { // dst and plaintext MUST alias exactly or not at all. @(require_results) open_ctx :: proc(ctx: ^Context, dst, iv, aad, ciphertext, tag: []byte) -> bool { + ensure(len(tag) == TAG_SIZES[ctx._algo], "crypto/aead: invalid tag size") + switch &impl in ctx._impl { case aes.Context_GCM: return aes.open_gcm(&impl, dst, iv, aad, ciphertext, tag) case chacha20poly1305.Context: return chacha20poly1305.open(&impl, dst, iv, aad, ciphertext, tag) + case aegis.Context: + return aegis.open(&impl, dst, iv, aad, ciphertext, tag) + case deoxysii.Context: + return deoxysii.open(&impl, dst, iv, aad, ciphertext, tag) case: panic("crypto/aead: uninitialized algorithm") } @@ -163,6 +213,10 @@ reset :: proc(ctx: ^Context) { aes.reset_gcm(&impl) case chacha20poly1305.Context: chacha20poly1305.reset(&impl) + case aegis.Context: + aegis.reset(&impl) + case deoxysii.Context: + deoxysii.reset(&impl) case: // Calling reset repeatedly is fine. } diff --git a/core/crypto/aegis/aegis.odin b/core/crypto/aegis/aegis.odin new file mode 100644 index 000000000..adecce91f --- /dev/null +++ b/core/crypto/aegis/aegis.odin @@ -0,0 +1,213 @@ +/* +package aegis implements the AEGIS-128L and AEGIS-256 Authenticated +Encryption with Additional Data algorithms. + +See: +- [[ https://www.ietf.org/archive/id/draft-irtf-cfrg-aegis-aead-12.txt ]] +*/ +package aegis + +import "core:bytes" +import "core:crypto" +import "core:crypto/aes" +import "core:mem" + +// KEY_SIZE_128L is the AEGIS-128L key size in bytes. +KEY_SIZE_128L :: 16 +// KEY_SIZE_256 is the AEGIS-256 key size in bytes. +KEY_SIZE_256 :: 32 +// IV_SIZE_128L is the AEGIS-128L IV size in bytes. +IV_SIZE_128L :: 16 +// IV_SIZE_256 is the AEGIS-256 IV size in bytes. +IV_SIZE_256 :: 32 +// TAG_SIZE_128 is the AEGIS-128L or AEGIS-256 128-bit tag size in bytes. +TAG_SIZE_128 :: 16 +// TAG_SIZE_256 is the AEGIS-128L or AEGIS-256 256-bit tag size in bytes. +TAG_SIZE_256 :: 32 + +@(private) +_RATE_128L :: 32 +@(private) +_RATE_256 :: 16 +@(private) +_RATE_MAX :: _RATE_128L + +@(private, rodata) +_C0 := [16]byte{ + 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, 0x08, 0x0d, + 0x15, 0x22, 0x37, 0x59, 0x90, 0xe9, 0x79, 0x62, +} + +@(private, rodata) +_C1 := [16]byte { + 0xdb, 0x3d, 0x18, 0x55, 0x6d, 0xc2, 0x2f, 0xf1, + 0x20, 0x11, 0x31, 0x42, 0x73, 0xb5, 0x28, 0xdd, +} + +// Context is a keyed AEGIS-128L or AEGIS-256 instance. +Context :: struct { + _key: [KEY_SIZE_256]byte, + _key_len: int, + _impl: aes.Implementation, + _is_initialized: bool, +} + +@(private) +_validate_common_slice_sizes :: proc (ctx: ^Context, tag, iv, aad, text: []byte) { + switch len(tag) { + case TAG_SIZE_128, TAG_SIZE_256: + case: + panic("crypto/aegis: invalid tag size") + } + + iv_ok: bool + switch ctx._key_len { + case KEY_SIZE_128L: + iv_ok = len(iv) == IV_SIZE_128L + case KEY_SIZE_256: + iv_ok = len(iv) == IV_SIZE_256 + } + ensure(iv_ok,"crypto/aegis: invalid IV size") + + #assert(size_of(int) == 8 || size_of(int) <= 4) + // As A_MAX and P_MAX are both defined to be 2^61 - 1 bytes, and + // the maximum length of a slice is bound by `size_of(int)`, where + // `int` is register sized, there is no need to check AAD/text + // lengths. +} + +// init initializes a Context with the provided key, for AEGIS-128L or AEGIS-256. +init :: proc(ctx: ^Context, key: []byte, impl := aes.DEFAULT_IMPLEMENTATION) { + switch len(key) { + case KEY_SIZE_128L, KEY_SIZE_256: + case: + panic("crypto/aegis: invalid key size") + } + + copy(ctx._key[:], key) + ctx._key_len = len(key) + ctx._impl = impl + if ctx._impl == .Hardware && !is_hardware_accelerated() { + ctx._impl = .Portable + } + ctx._is_initialized = true +} + +// seal encrypts the plaintext and authenticates the aad and ciphertext, +// with the provided Context and iv, stores the output in dst and tag. +// +// dst and plaintext MUST alias exactly or not at all. +seal :: proc(ctx: ^Context, dst, tag, iv, aad, plaintext: []byte) { + ensure(ctx._is_initialized) + + _validate_common_slice_sizes(ctx, tag, iv, aad, plaintext) + ensure(len(dst) == len(plaintext), "crypto/aegis: invalid destination ciphertext size") + ensure(!bytes.alias_inexactly(dst, plaintext), "crypto/aegis: dst and plaintext alias inexactly") + + switch ctx._impl { + case .Hardware: + st: State_HW + defer reset_state_hw(&st) + + init_hw(ctx, &st, iv) + + aad_len, pt_len := len(aad), len(plaintext) + if aad_len > 0 { + absorb_hw(&st, aad) + } + + if pt_len > 0 { + enc_hw(&st, dst, plaintext) + } + + finalize_hw(&st, tag, aad_len, pt_len) + case .Portable: + st: State_SW + defer reset_state_sw(&st) + + init_sw(ctx, &st, iv) + + aad_len, pt_len := len(aad), len(plaintext) + if aad_len > 0 { + absorb_sw(&st, aad) + } + + if pt_len > 0 { + enc_sw(&st, dst, plaintext) + } + + finalize_sw(&st, tag, aad_len, pt_len) + case: + panic("core/crypto/aegis: not implemented") + } +} + +// open authenticates the aad and ciphertext, and decrypts the ciphertext, +// with the provided Context, iv, and tag, and stores the output in dst, +// returning true iff the authentication was successful. If authentication +// fails, the destination buffer will be zeroed. +// +// dst and plaintext MUST alias exactly or not at all. +@(require_results) +open :: proc(ctx: ^Context, dst, iv, aad, ciphertext, tag: []byte) -> bool { + ensure(ctx._is_initialized) + + _validate_common_slice_sizes(ctx, tag, iv, aad, ciphertext) + ensure(len(dst) == len(ciphertext), "crypto/aegis: invalid destination plaintext size") + ensure(!bytes.alias_inexactly(dst, ciphertext), "crypto/aegis: dst and ciphertext alias inexactly") + + tmp: [TAG_SIZE_256]byte + derived_tag := tmp[:len(tag)] + aad_len, ct_len := len(aad), len(ciphertext) + + switch ctx._impl { + case .Hardware: + st: State_HW + defer reset_state_hw(&st) + + init_hw(ctx, &st, iv) + + if aad_len > 0 { + absorb_hw(&st, aad) + } + + if ct_len > 0 { + dec_hw(&st, dst, ciphertext) + } + + finalize_hw(&st, derived_tag, aad_len, ct_len) + case .Portable: + st: State_SW + defer reset_state_sw(&st) + + init_sw(ctx, &st, iv) + + if aad_len > 0 { + absorb_sw(&st, aad) + } + + if ct_len > 0 { + dec_sw(&st, dst, ciphertext) + } + + finalize_sw(&st, derived_tag, aad_len, ct_len) + case: + panic("core/crypto/aegis: not implemented") + } + + if crypto.compare_constant_time(tag, derived_tag) != 1 { + mem.zero_explicit(raw_data(derived_tag), len(derived_tag)) + mem.zero_explicit(raw_data(dst), ct_len) + return false + } + + return true +} + +// reset sanitizes the Context. The Context must be +// re-initialized to be used again. +reset :: proc "contextless" (ctx: ^Context) { + mem.zero_explicit(&ctx._key, len(ctx._key)) + ctx._key_len = 0 + ctx._is_initialized = false +} diff --git a/core/crypto/aegis/aegis_impl_ct64.odin b/core/crypto/aegis/aegis_impl_ct64.odin new file mode 100644 index 000000000..4813b37ec --- /dev/null +++ b/core/crypto/aegis/aegis_impl_ct64.odin @@ -0,0 +1,452 @@ +package aegis + +import aes "core:crypto/_aes/ct64" +import "core:encoding/endian" +import "core:mem" + +// This uses the bitlsiced 64-bit general purpose register SWAR AES +// round function. The intermediate state is stored in interleaved +// but NOT orthogonalized form, as leaving things in the orthgonalized +// format would overly complicate the update implementation. +// +// Note/perf: Per Frank Denis and a review of the specification, it is +// possible to gain slightly more performance by leaving the state in +// orthogonalized form while doing initialization, finalization, and +// absorbing AAD. This implementation opts out of those optimizations +// for the sake of simplicity. +// +// The update function leverages the paralleism (4xblocks) at once. + +@(private) +State_SW :: struct { + s0_0, s0_1: u64, + s1_0, s1_1: u64, + s2_0, s2_1: u64, + s3_0, s3_1: u64, + s4_0, s4_1: u64, + s5_0, s5_1: u64, + s6_0, s6_1: u64, + s7_0, s7_1: u64, + q_k, q_b: [8]u64, + rate: int, +} + +@(private) +init_sw :: proc "contextless" (ctx: ^Context, st: ^State_SW, iv: []byte) { + switch ctx._key_len { + case KEY_SIZE_128L: + key_0, key_1 := aes.load_interleaved(ctx._key[:16]) + iv_0, iv_1 := aes.load_interleaved(iv) + + st.s0_0, st.s0_1 = aes.xor_interleaved(key_0, key_1, iv_0, iv_1) + st.s1_0, st.s1_1 = aes.load_interleaved(_C1[:]) + st.s2_0, st.s2_1 = aes.load_interleaved(_C0[:]) + st.s3_0, st.s3_1 = st.s1_0, st.s1_1 + st.s4_0, st.s4_1 = st.s0_0, st.s0_1 + st.s5_0, st.s5_1 = aes.xor_interleaved(key_0, key_1, st.s2_0, st.s2_1) + st.s6_0, st.s6_1 = aes.xor_interleaved(key_0, key_1, st.s1_0, st.s1_1) + st.s7_0, st.s7_1 = st.s5_0, st.s5_1 + st.rate = _RATE_128L + + for _ in 0 ..< 10 { + update_sw_128l(st, iv_0, iv_1, key_0, key_1) + } + case KEY_SIZE_256: + k0_0, k0_1 := aes.load_interleaved(ctx._key[:16]) + k1_0, k1_1 := aes.load_interleaved(ctx._key[16:]) + n0_0, n0_1 := aes.load_interleaved(iv[:16]) + n1_0, n1_1 := aes.load_interleaved(iv[16:]) + + st.s0_0, st.s0_1 = aes.xor_interleaved(k0_0, k0_1, n0_0, n0_1) + st.s1_0, st.s1_1 = aes.xor_interleaved(k1_0, k1_1, n1_0, n1_1) + st.s2_0, st.s2_1 = aes.load_interleaved(_C1[:]) + st.s3_0, st.s3_1 = aes.load_interleaved(_C0[:]) + st.s4_0, st.s4_1 = aes.xor_interleaved(k0_0, k0_1, st.s3_0, st.s3_1) + st.s5_0, st.s5_1 = aes.xor_interleaved(k1_0, k1_1, st.s2_0, st.s2_1) + st.rate = _RATE_256 + + u0_0, u0_1, u1_0, u1_1 := st.s0_0, st.s0_1, st.s1_0, st.s1_1 + for _ in 0 ..< 4 { + update_sw_256(st, k0_0, k0_1) + update_sw_256(st, k1_0, k1_1) + update_sw_256(st, u0_0, u0_1) + update_sw_256(st, u1_0, u1_1) + } + } +} + +@(private = "file") +update_sw_128l :: proc "contextless" (st: ^State_SW, m0_0, m0_1, m1_0, m1_1: u64) { + st.q_k[0], st.q_k[4] = aes.xor_interleaved(st.s0_0, st.s0_1, m0_0, m0_1) + st.q_k[1], st.q_k[5] = st.s1_0, st.s1_1 + st.q_k[2], st.q_k[6] = st.s2_0, st.s2_1 + st.q_k[3], st.q_k[7] = st.s3_0, st.s3_1 + aes.orthogonalize(&st.q_k) + + st.q_b[0], st.q_b[4] = st.s7_0, st.s7_1 + st.q_b[1], st.q_b[5] = st.s0_0, st.s0_1 + st.q_b[2], st.q_b[6] = st.s1_0, st.s1_1 + st.q_b[3], st.q_b[7] = st.s2_0, st.s2_1 + aes.orthogonalize(&st.q_b) + + aes.sub_bytes(&st.q_b) + aes.shift_rows(&st.q_b) + aes.mix_columns(&st.q_b) + aes.add_round_key(&st.q_b, st.q_k[:]) + aes.orthogonalize(&st.q_b) + + st.s0_0, st.s0_1 = st.q_b[0], st.q_b[4] + st.s1_0, st.s1_1 = st.q_b[1], st.q_b[5] + st.s2_0, st.s2_1 = st.q_b[2], st.q_b[6] + s3_0, s3_1 := st.q_b[3], st.q_b[7] + + st.q_k[0], st.q_k[4] = aes.xor_interleaved(st.s4_0, st.s4_1, m1_0, m1_1) + st.q_k[1], st.q_k[5] = st.s5_0, st.s5_1 + st.q_k[2], st.q_k[6] = st.s6_0, st.s6_1 + st.q_k[3], st.q_k[7] = st.s7_0, st.s7_1 + aes.orthogonalize(&st.q_k) + + st.q_b[0], st.q_b[4] = st.s3_0, st.s3_1 + st.q_b[1], st.q_b[5] = st.s4_0, st.s4_1 + st.q_b[2], st.q_b[6] = st.s5_0, st.s5_1 + st.q_b[3], st.q_b[7] = st.s6_0, st.s6_1 + aes.orthogonalize(&st.q_b) + + aes.sub_bytes(&st.q_b) + aes.shift_rows(&st.q_b) + aes.mix_columns(&st.q_b) + aes.add_round_key(&st.q_b, st.q_k[:]) + aes.orthogonalize(&st.q_b) + + st.s3_0, st.s3_1 = s3_0, s3_1 + st.s4_0, st.s4_1 = st.q_b[0], st.q_b[4] + st.s5_0, st.s5_1 = st.q_b[1], st.q_b[5] + st.s6_0, st.s6_1 = st.q_b[2], st.q_b[6] + st.s7_0, st.s7_1 = st.q_b[3], st.q_b[7] +} + +@(private = "file") +update_sw_256 :: proc "contextless" (st: ^State_SW, m_0, m_1: u64) { + st.q_k[0], st.q_k[4] = aes.xor_interleaved(st.s0_0, st.s0_1, m_0, m_1) + st.q_k[1], st.q_k[5] = st.s1_0, st.s1_1 + st.q_k[2], st.q_k[6] = st.s2_0, st.s2_1 + st.q_k[3], st.q_k[7] = st.s3_0, st.s3_1 + aes.orthogonalize(&st.q_k) + + st.q_b[0], st.q_b[4] = st.s5_0, st.s5_1 + st.q_b[1], st.q_b[5] = st.s0_0, st.s0_1 + st.q_b[2], st.q_b[6] = st.s1_0, st.s1_1 + st.q_b[3], st.q_b[7] = st.s2_0, st.s2_1 + aes.orthogonalize(&st.q_b) + + aes.sub_bytes(&st.q_b) + aes.shift_rows(&st.q_b) + aes.mix_columns(&st.q_b) + aes.add_round_key(&st.q_b, st.q_k[:]) + aes.orthogonalize(&st.q_b) + + st.s0_0, st.s0_1 = st.q_b[0], st.q_b[4] + st.s1_0, st.s1_1 = st.q_b[1], st.q_b[5] + st.s2_0, st.s2_1 = st.q_b[2], st.q_b[6] + s3_0, s3_1 := st.q_b[3], st.q_b[7] + + st.q_k[0], st.q_k[4] = st.s4_0, st.s4_1 + st.q_k[1], st.q_k[5] = st.s5_0, st.s5_1 + aes.orthogonalize(&st.q_k) + + st.q_b[0], st.q_b[4] = st.s3_0, st.s3_1 + st.q_b[1], st.q_b[5] = st.s4_0, st.s4_1 + aes.orthogonalize(&st.q_b) + + aes.sub_bytes(&st.q_b) + aes.shift_rows(&st.q_b) + aes.mix_columns(&st.q_b) + aes.add_round_key(&st.q_b, st.q_k[:]) + aes.orthogonalize(&st.q_b) + + st.s3_0, st.s3_1 = s3_0, s3_1 + st.s4_0, st.s4_1 = st.q_b[0], st.q_b[4] + st.s5_0, st.s5_1 = st.q_b[1], st.q_b[5] +} + +@(private = "file") +absorb_sw_128l :: #force_inline proc "contextless" (st: ^State_SW, ai: []byte) #no_bounds_check { + t0_0, t0_1 := aes.load_interleaved(ai[:16]) + t1_0, t1_1 := aes.load_interleaved(ai[16:]) + update_sw_128l(st, t0_0, t0_1, t1_0, t1_1) +} + +@(private = "file") +absorb_sw_256 :: #force_inline proc "contextless" (st: ^State_SW, ai: []byte) { + m_0, m_1 := aes.load_interleaved(ai) + update_sw_256(st, m_0, m_1) +} + +@(private) +absorb_sw :: proc "contextless" (st: ^State_SW, aad: []byte) #no_bounds_check { + ai, l := aad, len(aad) + + switch st.rate { + case _RATE_128L: + for l >= _RATE_128L { + absorb_sw_128l(st, ai) + ai = ai[_RATE_128L:] + l -= _RATE_128L + } + case _RATE_256: + for l >= _RATE_256 { + absorb_sw_256(st, ai) + + ai = ai[_RATE_256:] + l -= _RATE_256 + } + } + + // Pad out the remainder with `0`s till it is rate sized. + if l > 0 { + tmp: [_RATE_MAX]byte // AAD is not confidential. + copy(tmp[:], ai) + switch st.rate { + case _RATE_128L: + absorb_sw_128l(st, tmp[:]) + case _RATE_256: + absorb_sw_256(st, tmp[:]) + } + } +} + +@(private = "file", require_results) +z_sw_128l :: proc "contextless" (st: ^State_SW) -> (u64, u64, u64, u64) { + z0_0, z0_1 := aes.and_interleaved(st.s2_0, st.s2_1, st.s3_0, st.s3_1) + z0_0, z0_1 = aes.xor_interleaved(st.s1_0, st.s1_1, z0_0, z0_1) + z0_0, z0_1 = aes.xor_interleaved(st.s6_0, st.s6_1, z0_0, z0_1) + + z1_0, z1_1 := aes.and_interleaved(st.s6_0, st.s6_1, st.s7_0, st.s7_1) + z1_0, z1_1 = aes.xor_interleaved(st.s5_0, st.s5_1, z1_0, z1_1) + z1_0, z1_1 = aes.xor_interleaved(st.s2_0, st.s2_1, z1_0, z1_1) + + return z0_0, z0_1, z1_0, z1_1 +} + +@(private = "file", require_results) +z_sw_256 :: proc "contextless" (st: ^State_SW) -> (u64, u64) { + z_0, z_1 := aes.and_interleaved(st.s2_0, st.s2_1, st.s3_0, st.s3_1) + z_0, z_1 = aes.xor_interleaved(st.s5_0, st.s5_1, z_0, z_1) + z_0, z_1 = aes.xor_interleaved(st.s4_0, st.s4_1, z_0, z_1) + return aes.xor_interleaved(st.s1_0, st.s1_1, z_0, z_1) +} + +@(private = "file") +enc_sw_128l :: #force_inline proc "contextless" (st: ^State_SW, ci, xi: []byte) #no_bounds_check { + z0_0, z0_1, z1_0, z1_1 := z_sw_128l(st) + + t0_0, t0_1 := aes.load_interleaved(xi[:16]) + t1_0, t1_1 := aes.load_interleaved(xi[16:]) + update_sw_128l(st, t0_0, t0_1, t1_0, t1_1) + + out0_0, out0_1 := aes.xor_interleaved(t0_0, t0_1, z0_0, z0_1) + out1_0, out1_1 := aes.xor_interleaved(t1_0, t1_1, z1_0, z1_1) + aes.store_interleaved(ci[:16], out0_0, out0_1) + aes.store_interleaved(ci[16:], out1_0, out1_1) +} + +@(private = "file") +enc_sw_256 :: #force_inline proc "contextless" (st: ^State_SW, ci, xi: []byte) #no_bounds_check { + z_0, z_1 := z_sw_256(st) + + xi_0, xi_1 := aes.load_interleaved(xi) + update_sw_256(st, xi_0, xi_1) + + ci_0, ci_1 := aes.xor_interleaved(xi_0, xi_1, z_0, z_1) + aes.store_interleaved(ci, ci_0, ci_1) +} + +@(private) +enc_sw :: proc "contextless" (st: ^State_SW, dst, src: []byte) #no_bounds_check { + ci, xi, l := dst, src, len(src) + + switch st.rate { + case _RATE_128L: + for l >= _RATE_128L { + enc_sw_128l(st, ci, xi) + ci = ci[_RATE_128L:] + xi = xi[_RATE_128L:] + l -= _RATE_128L + } + case _RATE_256: + for l >= _RATE_256 { + enc_sw_256(st, ci, xi) + ci = ci[_RATE_256:] + xi = xi[_RATE_256:] + l -= _RATE_256 + } + } + + // Pad out the remainder with `0`s till it is rate sized. + if l > 0 { + tmp: [_RATE_MAX]byte // Ciphertext is not confidential. + copy(tmp[:], xi) + switch st.rate { + case _RATE_128L: + enc_sw_128l(st, tmp[:], tmp[:]) + case _RATE_256: + enc_sw_256(st, tmp[:], tmp[:]) + } + copy(ci, tmp[:l]) + } +} + +@(private = "file") +dec_sw_128l :: #force_inline proc "contextless" (st: ^State_SW, xi, ci: []byte) #no_bounds_check { + z0_0, z0_1, z1_0, z1_1 := z_sw_128l(st) + + t0_0, t0_1 := aes.load_interleaved(ci[:16]) + t1_0, t1_1 := aes.load_interleaved(ci[16:]) + out0_0, out0_1 := aes.xor_interleaved(t0_0, t0_1, z0_0, z0_1) + out1_0, out1_1 := aes.xor_interleaved(t1_0, t1_1, z1_0, z1_1) + + update_sw_128l(st, out0_0, out0_1, out1_0, out1_1) + aes.store_interleaved(xi[:16], out0_0, out0_1) + aes.store_interleaved(xi[16:], out1_0, out1_1) +} + +@(private = "file") +dec_sw_256 :: #force_inline proc "contextless" (st: ^State_SW, xi, ci: []byte) #no_bounds_check { + z_0, z_1 := z_sw_256(st) + + ci_0, ci_1 := aes.load_interleaved(ci) + xi_0, xi_1 := aes.xor_interleaved(ci_0, ci_1, z_0, z_1) + + update_sw_256(st, xi_0, xi_1) + aes.store_interleaved(xi, xi_0, xi_1) +} + +@(private = "file") +dec_partial_sw_128l :: proc "contextless" (st: ^State_SW, xn, cn: []byte) #no_bounds_check { + tmp: [_RATE_128L]byte + defer mem.zero_explicit(&tmp, size_of(tmp)) + + z0_0, z0_1, z1_0, z1_1 := z_sw_128l(st) + copy(tmp[:], cn) + + t0_0, t0_1 := aes.load_interleaved(tmp[:16]) + t1_0, t1_1 := aes.load_interleaved(tmp[16:]) + out0_0, out0_1 := aes.xor_interleaved(t0_0, t0_1, z0_0, z0_1) + out1_0, out1_1 := aes.xor_interleaved(t1_0, t1_1, z1_0, z1_1) + + aes.store_interleaved(tmp[:16], out0_0, out0_1) + aes.store_interleaved(tmp[16:], out1_0, out1_1) + copy(xn, tmp[:]) + + for off := len(xn); off < _RATE_128L; off += 1 { + tmp[off] = 0 + } + out0_0, out0_1 = aes.load_interleaved(tmp[:16]) + out1_0, out1_1 = aes.load_interleaved(tmp[16:]) + update_sw_128l(st, out0_0, out0_1, out1_0, out1_1) +} + +@(private = "file") +dec_partial_sw_256 :: proc "contextless" (st: ^State_SW, xn, cn: []byte) #no_bounds_check { + tmp: [_RATE_256]byte + defer mem.zero_explicit(&tmp, size_of(tmp)) + + z_0, z_1 := z_sw_256(st) + copy(tmp[:], cn) + + cn_0, cn_1 := aes.load_interleaved(tmp[:]) + xn_0, xn_1 := aes.xor_interleaved(cn_0, cn_1, z_0, z_1) + + aes.store_interleaved(tmp[:], xn_0, xn_1) + copy(xn, tmp[:]) + + for off := len(xn); off < _RATE_256; off += 1 { + tmp[off] = 0 + } + xn_0, xn_1 = aes.load_interleaved(tmp[:]) + update_sw_256(st, xn_0, xn_1) +} + +@(private) +dec_sw :: proc "contextless" (st: ^State_SW, dst, src: []byte) #no_bounds_check { + xi, ci, l := dst, src, len(src) + + switch st.rate { + case _RATE_128L: + for l >= _RATE_128L { + dec_sw_128l(st, xi, ci) + xi = xi[_RATE_128L:] + ci = ci[_RATE_128L:] + l -= _RATE_128L + } + case _RATE_256: + for l >= _RATE_256 { + dec_sw_256(st, xi, ci) + xi = xi[_RATE_256:] + ci = ci[_RATE_256:] + l -= _RATE_256 + } + } + + // Process the remainder. + if l > 0 { + switch st.rate { + case _RATE_128L: + dec_partial_sw_128l(st, xi, ci) + case _RATE_256: + dec_partial_sw_256(st, xi, ci) + } + } +} + +@(private) +finalize_sw :: proc "contextless" (st: ^State_SW, tag: []byte, ad_len, msg_len: int) { + tmp: [16]byte + endian.unchecked_put_u64le(tmp[0:], u64(ad_len) * 8) + endian.unchecked_put_u64le(tmp[8:], u64(msg_len) * 8) + + t_0, t_1 := aes.load_interleaved(tmp[:]) + + t0_0, t0_1, t1_0, t1_1: u64 = ---, ---, ---, --- + switch st.rate { + case _RATE_128L: + t_0, t_1 = aes.xor_interleaved(st.s2_0, st.s2_1, t_0, t_1) + for _ in 0 ..< 7 { + update_sw_128l(st, t_0, t_1, t_0, t_1) + } + + t0_0, t0_1 = aes.xor_interleaved(st.s0_0, st.s0_1, st.s1_0, st.s1_1) + t0_0, t0_1 = aes.xor_interleaved(t0_0, t0_1, st.s2_0, st.s2_1) + t0_0, t0_1 = aes.xor_interleaved(t0_0, t0_1, st.s3_0, st.s3_1) + + t1_0, t1_1 = aes.xor_interleaved(st.s4_0, st.s4_1, st.s5_0, st.s5_1) + t1_0, t1_1 = aes.xor_interleaved(t1_0, t1_1, st.s6_0, st.s6_1) + if len(tag) == TAG_SIZE_256 { + t1_0, t1_1 = aes.xor_interleaved(t1_0, t1_1, st.s7_0, st.s7_1) + } + case _RATE_256: + t_0, t_1 = aes.xor_interleaved(st.s3_0, st.s3_1, t_0, t_1) + for _ in 0 ..< 7 { + update_sw_256(st, t_0, t_1) + } + + t0_0, t0_1 = aes.xor_interleaved(st.s0_0, st.s0_1, st.s1_0, st.s1_1) + t0_0, t0_1 = aes.xor_interleaved(t0_0, t0_1, st.s2_0, st.s2_1) + + t1_0, t1_1 = aes.xor_interleaved(st.s3_0, st.s3_1, st.s4_0, st.s4_1) + t1_0, t1_1 = aes.xor_interleaved(t1_0, t1_1, st.s5_0, st.s5_1) + } + switch len(tag) { + case TAG_SIZE_128: + t0_0, t0_1 = aes.xor_interleaved(t0_0, t0_1, t1_0, t1_1) + aes.store_interleaved(tag, t0_0, t0_1) + case TAG_SIZE_256: + aes.store_interleaved(tag[:16], t0_0, t0_1) + aes.store_interleaved(tag[16:], t1_0, t1_1) + } +} + +@(private) +reset_state_sw :: proc "contextless" (st: ^State_SW) { + mem.zero_explicit(st, size_of(st^)) +} diff --git a/core/crypto/aegis/aegis_impl_hw_gen.odin b/core/crypto/aegis/aegis_impl_hw_gen.odin new file mode 100644 index 000000000..5ec2f3d6e --- /dev/null +++ b/core/crypto/aegis/aegis_impl_hw_gen.odin @@ -0,0 +1,44 @@ +#+build !amd64 +package aegis + +@(private = "file") +ERR_HW_NOT_SUPPORTED :: "crypto/aegis: hardware implementation unsupported" + +@(private) +State_HW :: struct {} + +// is_hardware_accelerated returns true iff hardware accelerated AEGIS +// is supported. +is_hardware_accelerated :: proc "contextless" () -> bool { + return false +} + +@(private) +init_hw :: proc "contextless" (ctx: ^Context, st: ^State_HW, iv: []byte) { + panic_contextless(ERR_HW_NOT_SUPPORTED) +} + +@(private) +absorb_hw :: proc "contextless" (st: ^State_HW, aad: []byte) { + panic_contextless(ERR_HW_NOT_SUPPORTED) +} + +@(private) +enc_hw :: proc "contextless" (st: ^State_HW, dst, src: []byte) { + panic_contextless(ERR_HW_NOT_SUPPORTED) +} + +@(private) +dec_hw :: proc "contextless" (st: ^State_HW, dst, src: []byte) { + panic_contextless(ERR_HW_NOT_SUPPORTED) +} + +@(private) +finalize_hw :: proc "contextless" (st: ^State_HW, tag: []byte, ad_len, msg_len: int) { + panic_contextless(ERR_HW_NOT_SUPPORTED) +} + +@(private) +reset_state_hw :: proc "contextless" (st: ^State_HW) { + panic_contextless(ERR_HW_NOT_SUPPORTED) +} diff --git a/core/crypto/aegis/aegis_impl_hw_intel.odin b/core/crypto/aegis/aegis_impl_hw_intel.odin new file mode 100644 index 000000000..5334f3258 --- /dev/null +++ b/core/crypto/aegis/aegis_impl_hw_intel.odin @@ -0,0 +1,389 @@ +#+build amd64 +package aegis + +import "base:intrinsics" +import "core:crypto/aes" +import "core:encoding/endian" +import "core:mem" +import "core:simd/x86" + +@(private) +State_HW :: struct { + s0: x86.__m128i, + s1: x86.__m128i, + s2: x86.__m128i, + s3: x86.__m128i, + s4: x86.__m128i, + s5: x86.__m128i, + s6: x86.__m128i, + s7: x86.__m128i, + rate: int, +} + +// is_hardware_accelerated returns true iff hardware accelerated AEGIS +// is supported. +is_hardware_accelerated :: proc "contextless" () -> bool { + return aes.is_hardware_accelerated() +} + +@(private, enable_target_feature = "sse2,aes") +init_hw :: proc "contextless" (ctx: ^Context, st: ^State_HW, iv: []byte) { + switch ctx._key_len { + case KEY_SIZE_128L: + key := intrinsics.unaligned_load((^x86.__m128i)(&ctx._key[0])) + iv := intrinsics.unaligned_load((^x86.__m128i)(raw_data(iv))) + + st.s0 = x86._mm_xor_si128(key, iv) + st.s1 = intrinsics.unaligned_load((^x86.__m128i)(&_C1[0])) + st.s2 = intrinsics.unaligned_load((^x86.__m128i)(&_C0[0])) + st.s3 = st.s1 + st.s4 = st.s0 + st.s5 = x86._mm_xor_si128(key, st.s2) // key ^ C0 + st.s6 = x86._mm_xor_si128(key, st.s1) // key ^ C1 + st.s7 = st.s5 + st.rate = _RATE_128L + + for _ in 0 ..< 10 { + update_hw_128l(st, iv, key) + } + case KEY_SIZE_256: + k0 := intrinsics.unaligned_load((^x86.__m128i)(&ctx._key[0])) + k1 := intrinsics.unaligned_load((^x86.__m128i)(&ctx._key[16])) + n0 := intrinsics.unaligned_load((^x86.__m128i)(&iv[0])) + n1 := intrinsics.unaligned_load((^x86.__m128i)(&iv[16])) + + st.s0 = x86._mm_xor_si128(k0, n0) + st.s1 = x86._mm_xor_si128(k1, n1) + st.s2 = intrinsics.unaligned_load((^x86.__m128i)(&_C1[0])) + st.s3 = intrinsics.unaligned_load((^x86.__m128i)(&_C0[0])) + st.s4 = x86._mm_xor_si128(k0, st.s3) // k0 ^ C0 + st.s5 = x86._mm_xor_si128(k1, st.s2) // k1 ^ C1 + st.rate = _RATE_256 + + u0, u1 := st.s0, st.s1 + for _ in 0 ..< 4 { + update_hw_256(st, k0) + update_hw_256(st, k1) + update_hw_256(st, u0) + update_hw_256(st, u1) + } + } +} + +@(private = "file", enable_target_feature = "sse2,aes") +update_hw_128l :: #force_inline proc "contextless" (st: ^State_HW, m0, m1: x86.__m128i) { + s0_ := x86._mm_aesenc_si128(st.s7, x86._mm_xor_si128(st.s0, m0)) + s1_ := x86._mm_aesenc_si128(st.s0, st.s1) + s2_ := x86._mm_aesenc_si128(st.s1, st.s2) + s3_ := x86._mm_aesenc_si128(st.s2, st.s3) + s4_ := x86._mm_aesenc_si128(st.s3, x86._mm_xor_si128(st.s4, m1)) + s5_ := x86._mm_aesenc_si128(st.s4, st.s5) + s6_ := x86._mm_aesenc_si128(st.s5, st.s6) + s7_ := x86._mm_aesenc_si128(st.s6, st.s7) + st.s0, st.s1, st.s2, st.s3, st.s4, st.s5, st.s6, st.s7 = s0_, s1_, s2_, s3_, s4_, s5_, s6_, s7_ +} + +@(private = "file", enable_target_feature = "sse2,aes") +update_hw_256 :: #force_inline proc "contextless" (st: ^State_HW, m: x86.__m128i) { + s0_ := x86._mm_aesenc_si128(st.s5, x86._mm_xor_si128(st.s0, m)) + s1_ := x86._mm_aesenc_si128(st.s0, st.s1) + s2_ := x86._mm_aesenc_si128(st.s1, st.s2) + s3_ := x86._mm_aesenc_si128(st.s2, st.s3) + s4_ := x86._mm_aesenc_si128(st.s3, st.s4) + s5_ := x86._mm_aesenc_si128(st.s4, st.s5) + st.s0, st.s1, st.s2, st.s3, st.s4, st.s5 = s0_, s1_, s2_, s3_, s4_, s5_ +} + +@(private = "file", enable_target_feature = "sse2,aes") +absorb_hw_128l :: #force_inline proc "contextless" (st: ^State_HW, ai: []byte) { + t0 := intrinsics.unaligned_load((^x86.__m128i)(&ai[0])) + t1 := intrinsics.unaligned_load((^x86.__m128i)(&ai[16])) + update_hw_128l(st, t0, t1) +} + +@(private = "file", enable_target_feature = "sse2,aes") +absorb_hw_256 :: #force_inline proc "contextless" (st: ^State_HW, ai: []byte) { + m := intrinsics.unaligned_load((^x86.__m128i)(&ai[0])) + update_hw_256(st, m) +} + +@(private, enable_target_feature = "sse2,aes") +absorb_hw :: proc "contextless" (st: ^State_HW, aad: []byte) #no_bounds_check { + ai, l := aad, len(aad) + + switch st.rate { + case _RATE_128L: + for l >= _RATE_128L { + absorb_hw_128l(st, ai) + ai = ai[_RATE_128L:] + l -= _RATE_128L + } + case _RATE_256: + for l >= _RATE_256 { + absorb_hw_256(st, ai) + + ai = ai[_RATE_256:] + l -= _RATE_256 + } + } + + // Pad out the remainder with `0`s till it is rate sized. + if l > 0 { + tmp: [_RATE_MAX]byte // AAD is not confidential. + copy(tmp[:], ai) + switch st.rate { + case _RATE_128L: + absorb_hw_128l(st, tmp[:]) + case _RATE_256: + absorb_hw_256(st, tmp[:]) + } + } +} + +@(private = "file", enable_target_feature = "sse2", require_results) +z_hw_128l :: #force_inline proc "contextless" (st: ^State_HW) -> (x86.__m128i, x86.__m128i) { + z0 := x86._mm_xor_si128( + st.s6, + x86._mm_xor_si128( + st.s1, + x86._mm_and_si128(st.s2, st.s3), + ), + ) + z1 := x86._mm_xor_si128( + st.s2, + x86._mm_xor_si128( + st.s5, + x86._mm_and_si128(st.s6, st.s7), + ), + ) + return z0, z1 +} + +@(private = "file", enable_target_feature = "sse2", require_results) +z_hw_256 :: #force_inline proc "contextless" (st: ^State_HW) -> x86.__m128i { + return x86._mm_xor_si128( + st.s1, + x86._mm_xor_si128( + st.s4, + x86._mm_xor_si128( + st.s5, + x86._mm_and_si128(st.s2, st.s3), + ), + ), + ) +} + +@(private = "file", enable_target_feature = "sse2,aes") +enc_hw_128l :: #force_inline proc "contextless" (st: ^State_HW, ci, xi: []byte) #no_bounds_check { + z0, z1 := z_hw_128l(st) + + t0 := intrinsics.unaligned_load((^x86.__m128i)(&xi[0])) + t1 := intrinsics.unaligned_load((^x86.__m128i)(&xi[16])) + update_hw_128l(st, t0, t1) + + out0 := x86._mm_xor_si128(t0, z0) + out1 := x86._mm_xor_si128(t1, z1) + intrinsics.unaligned_store((^x86.__m128i)(&ci[0]), out0) + intrinsics.unaligned_store((^x86.__m128i)(&ci[16]), out1) +} + +@(private = "file", enable_target_feature = "sse2,aes") +enc_hw_256 :: #force_inline proc "contextless" (st: ^State_HW, ci, xi: []byte) #no_bounds_check { + z := z_hw_256(st) + + xi_ := intrinsics.unaligned_load((^x86.__m128i)(raw_data(xi))) + update_hw_256(st, xi_) + + ci_ := x86._mm_xor_si128(xi_, z) + intrinsics.unaligned_store((^x86.__m128i)(raw_data(ci)), ci_) +} + +@(private, enable_target_feature = "sse2,aes") +enc_hw :: proc "contextless" (st: ^State_HW, dst, src: []byte) #no_bounds_check { + ci, xi, l := dst, src, len(src) + + switch st.rate { + case _RATE_128L: + for l >= _RATE_128L { + enc_hw_128l(st, ci, xi) + ci = ci[_RATE_128L:] + xi = xi[_RATE_128L:] + l -= _RATE_128L + } + case _RATE_256: + for l >= _RATE_256 { + enc_hw_256(st, ci, xi) + ci = ci[_RATE_256:] + xi = xi[_RATE_256:] + l -= _RATE_256 + } + } + + // Pad out the remainder with `0`s till it is rate sized. + if l > 0 { + tmp: [_RATE_MAX]byte // Ciphertext is not confidential. + copy(tmp[:], xi) + switch st.rate { + case _RATE_128L: + enc_hw_128l(st, tmp[:], tmp[:]) + case _RATE_256: + enc_hw_256(st, tmp[:], tmp[:]) + } + copy(ci, tmp[:l]) + } +} + +@(private = "file", enable_target_feature = "sse2,aes") +dec_hw_128l :: #force_inline proc "contextless" (st: ^State_HW, xi, ci: []byte) #no_bounds_check { + z0, z1 := z_hw_128l(st) + + t0 := intrinsics.unaligned_load((^x86.__m128i)(&ci[0])) + t1 := intrinsics.unaligned_load((^x86.__m128i)(&ci[16])) + out0 := x86._mm_xor_si128(t0, z0) + out1 := x86._mm_xor_si128(t1, z1) + + update_hw_128l(st, out0, out1) + intrinsics.unaligned_store((^x86.__m128i)(&xi[0]), out0) + intrinsics.unaligned_store((^x86.__m128i)(&xi[16]), out1) +} + +@(private = "file", enable_target_feature = "sse2,aes") +dec_hw_256 :: #force_inline proc "contextless" (st: ^State_HW, xi, ci: []byte) #no_bounds_check { + z := z_hw_256(st) + + ci_ := intrinsics.unaligned_load((^x86.__m128i)(raw_data(ci))) + xi_ := x86._mm_xor_si128(ci_, z) + + update_hw_256(st, xi_) + intrinsics.unaligned_store((^x86.__m128i)(raw_data(xi)), xi_) +} + +@(private = "file", enable_target_feature = "sse2,aes") +dec_partial_hw_128l :: #force_inline proc "contextless" (st: ^State_HW, xn, cn: []byte) #no_bounds_check { + tmp: [_RATE_128L]byte + defer mem.zero_explicit(&tmp, size_of(tmp)) + + z0, z1 := z_hw_128l(st) + copy(tmp[:], cn) + + t0 := intrinsics.unaligned_load((^x86.__m128i)(&tmp[0])) + t1 := intrinsics.unaligned_load((^x86.__m128i)(&tmp[16])) + out0 := x86._mm_xor_si128(t0, z0) + out1 := x86._mm_xor_si128(t1, z1) + + intrinsics.unaligned_store((^x86.__m128i)(&tmp[0]), out0) + intrinsics.unaligned_store((^x86.__m128i)(&tmp[16]), out1) + copy(xn, tmp[:]) + + for off := len(xn); off < _RATE_128L; off += 1 { + tmp[off] = 0 + } + out0 = intrinsics.unaligned_load((^x86.__m128i)(&tmp[0])) // v0 + out1 = intrinsics.unaligned_load((^x86.__m128i)(&tmp[16])) // v1 + update_hw_128l(st, out0, out1) +} + +@(private = "file", enable_target_feature = "sse2,aes") +dec_partial_hw_256 :: #force_inline proc "contextless" (st: ^State_HW, xn, cn: []byte) #no_bounds_check { + tmp: [_RATE_256]byte + defer mem.zero_explicit(&tmp, size_of(tmp)) + + z := z_hw_256(st) + copy(tmp[:], cn) + + cn_ := intrinsics.unaligned_load((^x86.__m128i)(&tmp[0])) + xn_ := x86._mm_xor_si128(cn_, z) + + intrinsics.unaligned_store((^x86.__m128i)(&tmp[0]), xn_) + copy(xn, tmp[:]) + + for off := len(xn); off < _RATE_256; off += 1 { + tmp[off] = 0 + } + xn_ = intrinsics.unaligned_load((^x86.__m128i)(&tmp[0])) + update_hw_256(st, xn_) +} + +@(private, enable_target_feature = "sse2,aes") +dec_hw :: proc "contextless" (st: ^State_HW, dst, src: []byte) #no_bounds_check { + xi, ci, l := dst, src, len(src) + + switch st.rate { + case _RATE_128L: + for l >= _RATE_128L { + dec_hw_128l(st, xi, ci) + xi = xi[_RATE_128L:] + ci = ci[_RATE_128L:] + l -= _RATE_128L + } + case _RATE_256: + for l >= _RATE_256 { + dec_hw_256(st, xi, ci) + xi = xi[_RATE_256:] + ci = ci[_RATE_256:] + l -= _RATE_256 + } + } + + // Process the remainder. + if l > 0 { + switch st.rate { + case _RATE_128L: + dec_partial_hw_128l(st, xi, ci) + case _RATE_256: + dec_partial_hw_256(st, xi, ci) + } + } +} + +@(private, enable_target_feature = "sse2,aes") +finalize_hw :: proc "contextless" (st: ^State_HW, tag: []byte, ad_len, msg_len: int) { + tmp: [16]byte + endian.unchecked_put_u64le(tmp[0:], u64(ad_len) * 8) + endian.unchecked_put_u64le(tmp[8:], u64(msg_len) * 8) + + t := intrinsics.unaligned_load((^x86.__m128i)(&tmp[0])) + + t0, t1: x86.__m128i = ---, --- + switch st.rate { + case _RATE_128L: + t = x86._mm_xor_si128(st.s2, t) + for _ in 0 ..< 7 { + update_hw_128l(st, t, t) + } + + t0 = x86._mm_xor_si128(st.s0, st.s1) + t0 = x86._mm_xor_si128(t0, st.s2) + t0 = x86._mm_xor_si128(t0, st.s3) + + t1 = x86._mm_xor_si128(st.s4, st.s5) + t1 = x86._mm_xor_si128(t1, st.s6) + if len(tag) == TAG_SIZE_256 { + t1 = x86._mm_xor_si128(t1, st.s7) + } + case _RATE_256: + t = x86._mm_xor_si128(st.s3, t) + for _ in 0 ..< 7 { + update_hw_256(st, t) + } + + t0 = x86._mm_xor_si128(st.s0, st.s1) + t0 = x86._mm_xor_si128(t0, st.s2) + + t1 = x86._mm_xor_si128(st.s3, st.s4) + t1 = x86._mm_xor_si128(t1, st.s5) + } + switch len(tag) { + case TAG_SIZE_128: + t0 = x86._mm_xor_si128(t0, t1) + intrinsics.unaligned_store((^x86.__m128i)(&tag[0]), t0) + case TAG_SIZE_256: + intrinsics.unaligned_store((^x86.__m128i)(&tag[0]), t0) + intrinsics.unaligned_store((^x86.__m128i)(&tag[16]), t1) + } +} + +@(private) +reset_state_hw :: proc "contextless" (st: ^State_HW) { + mem.zero_explicit(st, size_of(st^)) +} diff --git a/core/crypto/aes/aes_ctr.odin b/core/crypto/aes/aes_ctr.odin index 20b75e57f..a74133235 100644 --- a/core/crypto/aes/aes_ctr.odin +++ b/core/crypto/aes/aes_ctr.odin @@ -21,9 +21,7 @@ Context_CTR :: struct { // init_ctr initializes a Context_CTR with the provided key and IV. init_ctr :: proc(ctx: ^Context_CTR, key, iv: []byte, impl := DEFAULT_IMPLEMENTATION) { - if len(iv) != CTR_IV_SIZE { - panic("crypto/aes: invalid CTR IV size") - } + ensure(len(iv) == CTR_IV_SIZE, "crypto/aes: invalid CTR IV size") init_impl(&ctx._impl, key, impl) ctx._off = BLOCK_SIZE @@ -36,16 +34,14 @@ init_ctr :: proc(ctx: ^Context_CTR, key, iv: []byte, impl := DEFAULT_IMPLEMENTAT // keystream, and writes the resulting output to dst. dst and src MUST // alias exactly or not at all. xor_bytes_ctr :: proc(ctx: ^Context_CTR, dst, src: []byte) { - assert(ctx._is_initialized) + ensure(ctx._is_initialized) src, dst := src, dst if dst_len := len(dst); dst_len < len(src) { src = src[:dst_len] } - if bytes.alias_inexactly(dst, src) { - panic("crypto/aes: dst and src alias inexactly") - } + ensure(!bytes.alias_inexactly(dst, src), "crypto/aes: dst and src alias inexactly") #no_bounds_check for remaining := len(src); remaining > 0; { // Process multiple blocks at once @@ -82,7 +78,7 @@ xor_bytes_ctr :: proc(ctx: ^Context_CTR, dst, src: []byte) { // keystream_bytes_ctr fills dst with the raw AES-CTR keystream output. keystream_bytes_ctr :: proc(ctx: ^Context_CTR, dst: []byte) { - assert(ctx._is_initialized) + ensure(ctx._is_initialized) dst := dst #no_bounds_check for remaining := len(dst); remaining > 0; { diff --git a/core/crypto/aes/aes_ecb.odin b/core/crypto/aes/aes_ecb.odin index 32476006c..cac62de5d 100644 --- a/core/crypto/aes/aes_ecb.odin +++ b/core/crypto/aes/aes_ecb.odin @@ -19,11 +19,9 @@ init_ecb :: proc(ctx: ^Context_ECB, key: []byte, impl := DEFAULT_IMPLEMENTATION) // encrypt_ecb encrypts the BLOCK_SIZE buffer src, and writes the result to dst. encrypt_ecb :: proc(ctx: ^Context_ECB, dst, src: []byte) { - assert(ctx._is_initialized) - - if len(dst) != BLOCK_SIZE || len(src) != BLOCK_SIZE { - panic("crypto/aes: invalid buffer size(s)") - } + ensure(ctx._is_initialized) + ensure(len(dst) == BLOCK_SIZE, "crypto/aes: invalid dst size") + ensure(len(dst) == BLOCK_SIZE, "crypto/aes: invalid src size") switch &impl in ctx._impl { case ct64.Context: @@ -35,11 +33,9 @@ encrypt_ecb :: proc(ctx: ^Context_ECB, dst, src: []byte) { // decrypt_ecb decrypts the BLOCK_SIZE buffer src, and writes the result to dst. decrypt_ecb :: proc(ctx: ^Context_ECB, dst, src: []byte) { - assert(ctx._is_initialized) - - if len(dst) != BLOCK_SIZE || len(src) != BLOCK_SIZE { - panic("crypto/aes: invalid buffer size(s)") - } + ensure(ctx._is_initialized) + ensure(len(dst) == BLOCK_SIZE, "crypto/aes: invalid dst size") + ensure(len(dst) == BLOCK_SIZE, "crypto/aes: invalid src size") switch &impl in ctx._impl { case ct64.Context: diff --git a/core/crypto/aes/aes_gcm.odin b/core/crypto/aes/aes_gcm.odin index 8616821ce..d349aa353 100644 --- a/core/crypto/aes/aes_gcm.odin +++ b/core/crypto/aes/aes_gcm.odin @@ -36,15 +36,11 @@ init_gcm :: proc(ctx: ^Context_GCM, key: []byte, impl := DEFAULT_IMPLEMENTATION) // // dst and plaintext MUST alias exactly or not at all. seal_gcm :: proc(ctx: ^Context_GCM, dst, tag, iv, aad, plaintext: []byte) { - assert(ctx._is_initialized) + ensure(ctx._is_initialized) gcm_validate_common_slice_sizes(tag, iv, aad, plaintext) - if len(dst) != len(plaintext) { - panic("crypto/aes: invalid destination ciphertext size") - } - if bytes.alias_inexactly(dst, plaintext) { - panic("crypto/aes: dst and plaintext alias inexactly") - } + ensure(len(dst) == len(plaintext), "crypto/aes: invalid destination ciphertext size") + ensure(!bytes.alias_inexactly(dst, plaintext), "crypto/aes: dst and plaintext alias inexactly") if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw { gcm_seal_hw(&impl, dst, tag, iv, aad, plaintext) @@ -76,15 +72,11 @@ seal_gcm :: proc(ctx: ^Context_GCM, dst, tag, iv, aad, plaintext: []byte) { // dst and plaintext MUST alias exactly or not at all. @(require_results) open_gcm :: proc(ctx: ^Context_GCM, dst, iv, aad, ciphertext, tag: []byte) -> bool { - assert(ctx._is_initialized) + ensure(ctx._is_initialized) gcm_validate_common_slice_sizes(tag, iv, aad, ciphertext) - if len(dst) != len(ciphertext) { - panic("crypto/aes: invalid destination plaintext size") - } - if bytes.alias_inexactly(dst, ciphertext) { - panic("crypto/aes: dst and ciphertext alias inexactly") - } + ensure(len(dst) == len(ciphertext), "crypto/aes: invalid destination plaintext size") + ensure(!bytes.alias_inexactly(dst, ciphertext), "crypto/aes: dst and ciphertext alias inexactly") if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw { return gcm_open_hw(&impl, dst, iv, aad, ciphertext, tag) @@ -122,21 +114,13 @@ reset_gcm :: proc "contextless" (ctx: ^Context_GCM) { @(private = "file") gcm_validate_common_slice_sizes :: proc(tag, iv, aad, text: []byte) { - if len(tag) != GCM_TAG_SIZE { - panic("crypto/aes: invalid GCM tag size") - } + ensure(len(tag) == GCM_TAG_SIZE, "crypto/aes: invalid GCM tag size") // The specification supports IVs in the range [1, 2^64) bits. - if l := len(iv); l == 0 || u64(l) >= GCM_IV_SIZE_MAX { - panic("crypto/aes: invalid GCM IV size") - } + ensure(len(iv) == 0 || u64(len(iv)) <= GCM_IV_SIZE_MAX, "crypto/aes: invalid GCM IV size") - if aad_len := u64(len(aad)); aad_len > GCM_A_MAX { - panic("crypto/aes: oversized GCM aad") - } - if text_len := u64(len(text)); text_len > GCM_P_MAX { - panic("crypto/aes: oversized GCM src data") - } + ensure(u64(len(aad)) <= GCM_A_MAX, "crypto/aes: oversized GCM aad") + ensure(u64(len(text)) <= GCM_P_MAX, "crypto/aes: oversized GCM data") } @(private = "file") diff --git a/core/crypto/aes/aes_gcm_hw_intel.odin b/core/crypto/aes/aes_gcm_hw_intel.odin index 4cb5ab3b2..3982d1452 100644 --- a/core/crypto/aes/aes_gcm_hw_intel.odin +++ b/core/crypto/aes/aes_gcm_hw_intel.odin @@ -235,7 +235,7 @@ gctr_hw :: proc( // BUG: Sticking this in gctr_hw (like the other implementations) crashes // the compiler. // -// src/check_expr.cpp(7892): Assertion Failure: `c->curr_proc_decl->entity` +// src/check_expr.cpp(8104): Assertion Failure: `c->curr_proc_decl->entity` @(private = "file", enable_target_feature = "sse4.1") hw_inc_ctr32 :: #force_inline proc "contextless" (src: ^x86.__m128i, ctr: u32) -> (x86.__m128i, u32) { ret := x86._mm_insert_epi32(src^, i32(intrinsics.byte_swap(ctr)), 3) diff --git a/core/crypto/blake2b/blake2b.odin b/core/crypto/blake2b/blake2b.odin index 74396b103..3b3fc6649 100644 --- a/core/crypto/blake2b/blake2b.odin +++ b/core/crypto/blake2b/blake2b.odin @@ -18,7 +18,7 @@ package blake2b import "../_blake2" // DIGEST_SIZE is the BLAKE2b digest size in bytes. -DIGEST_SIZE :: 64 +DIGEST_SIZE :: _blake2.BLAKE2B_SIZE // BLOCK_SIZE is the BLAKE2b block size in bytes. BLOCK_SIZE :: _blake2.BLAKE2B_BLOCK_SIZE @@ -27,9 +27,11 @@ BLOCK_SIZE :: _blake2.BLAKE2B_BLOCK_SIZE Context :: _blake2.Blake2b_Context // init initializes a Context with the default BLAKE2b config. -init :: proc(ctx: ^Context) { +init :: proc(ctx: ^Context, digest_size := DIGEST_SIZE) { + ensure(digest_size <= _blake2.MAX_SIZE, "crypto/blake2b: invalid digest size") + cfg: _blake2.Blake2_Config - cfg.size = _blake2.BLAKE2B_SIZE + cfg.size = u8(digest_size) _blake2.init(ctx, &cfg) } diff --git a/core/crypto/blake2s/blake2s.odin b/core/crypto/blake2s/blake2s.odin index 339ddf027..9bbd44541 100644 --- a/core/crypto/blake2s/blake2s.odin +++ b/core/crypto/blake2s/blake2s.odin @@ -18,7 +18,7 @@ package blake2s import "../_blake2" // DIGEST_SIZE is the BLAKE2s digest size in bytes. -DIGEST_SIZE :: 32 +DIGEST_SIZE :: _blake2.BLAKE2S_SIZE // BLOCK_SIZE is the BLAKE2s block size in bytes. BLOCK_SIZE :: _blake2.BLAKE2S_BLOCK_SIZE @@ -27,9 +27,11 @@ BLOCK_SIZE :: _blake2.BLAKE2S_BLOCK_SIZE Context :: _blake2.Blake2s_Context // init initializes a Context with the default BLAKE2s config. -init :: proc(ctx: ^Context) { +init :: proc(ctx: ^Context, digest_size := DIGEST_SIZE) { + ensure(digest_size <= _blake2.MAX_SIZE, "crypto/blake2s: invalid digest size") + cfg: _blake2.Blake2_Config - cfg.size = _blake2.BLAKE2S_SIZE + cfg.size = u8(digest_size) _blake2.init(ctx, &cfg) } diff --git a/core/crypto/chacha20/chacha20.odin b/core/crypto/chacha20/chacha20.odin index dfab2bc65..e8d67eb3e 100644 --- a/core/crypto/chacha20/chacha20.odin +++ b/core/crypto/chacha20/chacha20.odin @@ -27,12 +27,8 @@ Context :: struct { // init inititializes a Context for ChaCha20 or XChaCha20 with the provided // key and iv. init :: proc(ctx: ^Context, key, iv: []byte, impl := DEFAULT_IMPLEMENTATION) { - if len(key) != KEY_SIZE { - panic("crypto/chacha20: invalid (X)ChaCha20 key size") - } - if l := len(iv); l != IV_SIZE && l != XIV_SIZE { - panic("crypto/chacha20: invalid (X)ChaCha20 IV size") - } + ensure(len(key) == KEY_SIZE, "crypto/chacha20: invalid (X)ChaCha20 key size") + ensure(len(iv) == IV_SIZE || len(iv) == XIV_SIZE, "crypto/chacha20: invalid (X)ChaCha20 IV size") k, n := key, iv @@ -67,16 +63,14 @@ seek :: proc(ctx: ^Context, block_nr: u64) { // keystream, and writes the resulting output to dst. Dst and src MUST // alias exactly or not at all. xor_bytes :: proc(ctx: ^Context, dst, src: []byte) { - assert(ctx._state._is_initialized) + ensure(ctx._state._is_initialized) src, dst := src, dst if dst_len := len(dst); dst_len < len(src) { src = src[:dst_len] } - if bytes.alias_inexactly(dst, src) { - panic("crypto/chacha20: dst and src alias inexactly") - } + ensure(!bytes.alias_inexactly(dst, src), "crypto/chacha20: dst and src alias inexactly") st := &ctx._state #no_bounds_check for remaining := len(src); remaining > 0; { @@ -114,7 +108,7 @@ xor_bytes :: proc(ctx: ^Context, dst, src: []byte) { // keystream_bytes fills dst with the raw (X)ChaCha20 keystream output. keystream_bytes :: proc(ctx: ^Context, dst: []byte) { - assert(ctx._state._is_initialized) + ensure(ctx._state._is_initialized) dst, st := dst, &ctx._state #no_bounds_check for remaining := len(dst); remaining > 0; { diff --git a/core/crypto/chacha20poly1305/chacha20poly1305.odin b/core/crypto/chacha20poly1305/chacha20poly1305.odin index 3de2532dd..6706b3820 100644 --- a/core/crypto/chacha20poly1305/chacha20poly1305.odin +++ b/core/crypto/chacha20poly1305/chacha20poly1305.odin @@ -29,13 +29,9 @@ _P_MAX :: 64 * 0xffffffff // 64 * (2^32-1) @(private) _validate_common_slice_sizes :: proc (tag, iv, aad, text: []byte, is_xchacha: bool) { - if len(tag) != TAG_SIZE { - panic("crypto/chacha20poly1305: invalid destination tag size") - } expected_iv_len := is_xchacha ? XIV_SIZE : IV_SIZE - if len(iv) != expected_iv_len { - panic("crypto/chacha20poly1305: invalid IV size") - } + ensure(len(tag) == TAG_SIZE, "crypto/chacha20poly1305: invalid destination tag size") + ensure(len(iv) == expected_iv_len, "crypto/chacha20poly1305: invalid IV size") #assert(size_of(int) == 8 || size_of(int) <= 4) when size_of(int) == 8 { @@ -45,13 +41,11 @@ _validate_common_slice_sizes :: proc (tag, iv, aad, text: []byte, is_xchacha: bo // A_MAX is limited by size_of(int), so there is no need to // enforce it. P_MAX only needs to be checked on 64-bit targets, // for reasons that should be obvious. - if text_len := len(text); text_len > _P_MAX { - panic("crypto/chacha20poly1305: oversized src data") - } + ensure(len(text) <= _P_MAX, "crypto/chacha20poly1305: oversized src data") } } -@(private) +@(private, rodata) _PAD: [16]byte @(private) @@ -71,9 +65,7 @@ Context :: struct { // init initializes a Context with the provided key, for AEAD_CHACHA20_POLY1305. init :: proc(ctx: ^Context, key: []byte, impl := chacha20.DEFAULT_IMPLEMENTATION) { - if len(key) != KEY_SIZE { - panic("crypto/chacha20poly1305: invalid key size") - } + ensure(len(key) == KEY_SIZE, "crypto/chacha20poly1305: invalid key size") copy(ctx._key[:], key) ctx._impl = impl @@ -96,11 +88,11 @@ init_xchacha :: proc(ctx: ^Context, key: []byte, impl := chacha20.DEFAULT_IMPLEM // // dst and plaintext MUST alias exactly or not at all. seal :: proc(ctx: ^Context, dst, tag, iv, aad, plaintext: []byte) { + ensure(ctx._is_initialized) + ciphertext := dst _validate_common_slice_sizes(tag, iv, aad, plaintext, ctx._is_xchacha) - if len(ciphertext) != len(plaintext) { - panic("crypto/chacha20poly1305: invalid destination ciphertext size") - } + ensure(len(ciphertext) == len(plaintext), "crypto/chacha20poly1305: invalid destination ciphertext size") stream_ctx: chacha20.Context = --- chacha20.init(&stream_ctx, ctx._key[:],iv, ctx._impl) @@ -151,11 +143,11 @@ seal :: proc(ctx: ^Context, dst, tag, iv, aad, plaintext: []byte) { // dst and plaintext MUST alias exactly or not at all. @(require_results) open :: proc(ctx: ^Context, dst, iv, aad, ciphertext, tag: []byte) -> bool { + ensure(ctx._is_initialized) + plaintext := dst _validate_common_slice_sizes(tag, iv, aad, ciphertext, ctx._is_xchacha) - if len(ciphertext) != len(plaintext) { - panic("crypto/chacha20poly1305: invalid destination plaintext size") - } + ensure(len(ciphertext) == len(plaintext), "crypto/chacha20poly1305: invalid destination plaintext size") // Note: Unlike encrypt, this can fail early, so use defer for // sanitization rather than assuming control flow reaches certain diff --git a/core/crypto/deoxysii/deoxysii.odin b/core/crypto/deoxysii/deoxysii.odin new file mode 100644 index 000000000..cead770e2 --- /dev/null +++ b/core/crypto/deoxysii/deoxysii.odin @@ -0,0 +1,280 @@ +/* +package deoxysii implements the Deoxys-II-256 Authenticated Encryption +with Additional Data algorithm. + +- [[ https://sites.google.com/view/deoxyscipher ]] +- [[ https://thomaspeyrin.github.io/web/assets/docs/papers/Jean-etal-JoC2021.pdf ]] +*/ +package deoxysii + +import "base:intrinsics" +import "core:bytes" +import "core:crypto/aes" +import "core:mem" +import "core:simd" + +// KEY_SIZE is the Deoxys-II-256 key size in bytes. +KEY_SIZE :: 32 +// IV_SIZE iss the Deoxys-II-256 IV size in bytes. +IV_SIZE :: 15 // 120-bits +// TAG_SIZE is the Deoxys-II-256 tag size in bytes. +TAG_SIZE :: 16 + +@(private) +PREFIX_AD_BLOCK :: 0b0010 +@(private) +PREFIX_AD_FINAL :: 0b0110 +@(private) +PREFIX_MSG_BLOCK :: 0b0000 +@(private) +PREFIX_MSG_FINAL :: 0b0100 +@(private) +PREFIX_TAG :: 0b0001 +@(private) +PREFIX_SHIFT :: 4 + +@(private) +BC_ROUNDS :: 16 +@(private) +BLOCK_SIZE :: aes.BLOCK_SIZE + +@(private = "file") +_LFSR2_MASK :: simd.u8x16{ + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, +} +@(private = "file") +_LFSR3_MASK :: simd.u8x16{ + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, +} +@(private = "file") +_LFSR_SH1 :: _LFSR2_MASK +@(private = "file") +_LFSR_SH5 :: simd.u8x16{ + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, +} +@(private = "file") +_LFSR_SH7 :: simd.u8x16{ + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, +} +@(private = "file", rodata) +_RCONS := []byte { + 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, + 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, + 0x72, +} + +// Context is a keyed Deoxys-II-256 instance. +Context :: struct { + _subkeys: [BC_ROUNDS+1][16]byte, + _impl: aes.Implementation, + _is_initialized: bool, +} + +@(private) +_validate_common_slice_sizes :: proc (ctx: ^Context, tag, iv, aad, text: []byte) { + ensure(len(tag) == TAG_SIZE, "crypto/deoxysii: invalid tag size") + ensure(len(iv) == IV_SIZE, "crypto/deoxysii: invalid IV size") + + #assert(size_of(int) == 8 || size_of(int) <= 4) + // For the nonce-misuse resistant mode, the total size of the + // associated data and the total size of the message do not exceed + // `16 * 2^max_l * 2^max_m bytes`, thus 2^128 bytes for all variants + // of Deoxys-II. Moreover, the maximum number of messages that can + // be handled for a same key is 2^max_m, that is 2^64 for all variants + // of Deoxys. +} + +// init initializes a Context with the provided key. +init :: proc(ctx: ^Context, key: []byte, impl := aes.DEFAULT_IMPLEMENTATION) { + ensure(len(key) == KEY_SIZE, "crypto/deoxysii: invalid key size") + + ctx._impl = impl + if ctx._impl == .Hardware && !is_hardware_accelerated() { + ctx._impl = .Portable + } + + derive_ks(ctx, key) + + ctx._is_initialized = true +} + +// seal encrypts the plaintext and authenticates the aad and ciphertext, +// with the provided Context and iv, stores the output in dst and tag. +// +// dst and plaintext MUST alias exactly or not at all. +seal :: proc(ctx: ^Context, dst, tag, iv, aad, plaintext: []byte) { + ensure(ctx._is_initialized) + + _validate_common_slice_sizes(ctx, tag, iv, aad, plaintext) + ensure(len(dst) == len(plaintext), "crypto/deoxysii: invalid destination ciphertext size") + ensure(!bytes.alias_inexactly(dst, plaintext), "crypto/deoxysii: dst and plaintext alias inexactly") + + switch ctx._impl { + case .Hardware: + e_hw(ctx, dst, tag, iv, aad, plaintext) + case .Portable: + e_ref(ctx, dst, tag, iv, aad, plaintext) + } +} + +// open authenticates the aad and ciphertext, and decrypts the ciphertext, +// with the provided Context, iv, and tag, and stores the output in dst, +// returning true iff the authentication was successful. If authentication +// fails, the destination buffer will be zeroed. +// +// dst and plaintext MUST alias exactly or not at all. +@(require_results) +open :: proc(ctx: ^Context, dst, iv, aad, ciphertext, tag: []byte) -> bool { + ensure(ctx._is_initialized) + + _validate_common_slice_sizes(ctx, tag, iv, aad, ciphertext) + ensure(len(dst) == len(ciphertext), "crypto/deoxysii: invalid destination plaintext size") + ensure(!bytes.alias_inexactly(dst, ciphertext), "crypto/deoxysii: dst and ciphertext alias inexactly") + + ok: bool + switch ctx._impl { + case .Hardware: + ok = d_hw(ctx, dst, iv, aad, ciphertext, tag) + case .Portable: + ok = d_ref(ctx, dst, iv, aad, ciphertext, tag) + } + if !ok { + mem.zero_explicit(raw_data(dst), len(ciphertext)) + } + + return ok +} + +// reset sanitizes the Context. The Context must be +// re-initialized to be used again. +reset :: proc "contextless" (ctx: ^Context) { + mem.zero_explicit(&ctx._subkeys, len(ctx._subkeys)) + ctx._is_initialized = false +} + +@(private = "file") +derive_ks :: proc "contextless" (ctx: ^Context, key: []byte) { + // Derive the constant component of each subtweakkey. + // + // The key schedule is as thus: + // + // STK_i = TK1_i ^ TK2_i ^ TK3_i ^ RC_i + // + // TK1_i = h(TK1_(i-1)) + // TK2_i = h(LFSR2(TK2_(i-1))) + // TK3_i = h(LFSR3(TK2_(i-1))) + // + // where: + // + // KT = K || T + // W3 = KT[:16] + // W2 = KT[16:32] + // W1 = KT[32:] + // + // TK1_0 = W1 + // TK2_0 = W2 + // TK3_0 = W3 + // + // As `K` is fixed per Context, the XORs of `TK3_0 .. TK3_n`, + // `TK2_0 .. TK2_n` and RC_i can be precomputed in advance like + // thus: + // + // subkey_i = TK3_i ^ TK2_i ^ RC_i + // + // When it is time to actually call Deoxys-BC-384, it is then + // a simple matter of deriving each round subtweakkey via: + // + // TK1_0 = T (Tweak) + // STK_0 = subkey_0 ^ TK1_0 + // STK_i = subkey_i (precomputed) ^ H(TK1_(i-1)) + // + // We opt to use SIMD here and for the subtweakkey deriviation + // as `H()` is typically a single vector instruction. + + tk2 := intrinsics.unaligned_load((^simd.u8x16)(raw_data(key[16:]))) + tk3 := intrinsics.unaligned_load((^simd.u8x16)(raw_data(key))) + + // subkey_0 does not apply LFSR2/3 or H. + intrinsics.unaligned_store( + (^simd.u8x16)(&ctx._subkeys[0]), + simd.bit_xor( + tk2, + simd.bit_xor( + tk3, + rcon(0), + ), + ), + ) + + // Precompute k_1 .. k_16. + for i in 1 ..< BC_ROUNDS+1 { + tk2 = h(lfsr2(tk2)) + tk3 = h(lfsr3(tk3)) + intrinsics.unaligned_store( + (^simd.u8x16)(&ctx._subkeys[i]), + simd.bit_xor( + tk2, + simd.bit_xor( + tk3, + rcon(i), + ), + ), + ) + } +} + +@(private = "file") +lfsr2 :: #force_inline proc "contextless" (tk: simd.u8x16) -> simd.u8x16 { + // LFSR2 is a application of the following LFSR to each byte of input. + // (x7||x6||x5||x4||x3||x2||x1||x0) -> (x6||x5||x4||x3||x2||x1||x0||x7 ^ x5) + return simd.bit_or( + simd.shl(tk, _LFSR_SH1), + simd.bit_and( + simd.bit_xor( + simd.shr(tk, _LFSR_SH7), // x7 + simd.shr(tk, _LFSR_SH5), // x5 + ), + _LFSR2_MASK, + ), + ) +} + +@(private = "file") +lfsr3 :: #force_inline proc "contextless" (tk: simd.u8x16) -> simd.u8x16 { + // LFSR3 is a application of the following LFSR to each byte of input. + // (x7||x6||x5||x4||x3||x2||x1||x0) -> (x0 ^ x6||x7||x6||x5||x4||x3||x2||x1) + return simd.bit_or( + simd.shr(tk, _LFSR_SH1), + simd.bit_and( + simd.bit_xor( + simd.shl(tk, _LFSR_SH7), // x0 + simd.shl(tk, _LFSR_SH1), // x6 + ), + _LFSR3_MASK, + ), + ) +} + +@(private) +h :: #force_inline proc "contextless" (tk: simd.u8x16) -> simd.u8x16 { + return simd.swizzle( + tk, + 0x01, 0x06, 0x0b, 0x0c, 0x05, 0x0a, 0x0f, 0x00, + 0x09, 0x0e, 0x03, 0x04, 0x0d, 0x02, 0x07, 0x08, + ) +} + +@(private = "file") +rcon :: #force_inline proc "contextless" (rd: int) -> simd.u8x16 #no_bounds_check { + rc := _RCONS[rd] + return simd.u8x16{ + 1, 2, 4, 8, + rc, rc, rc, rc, + 0, 0, 0, 0, + 0, 0, 0, 0, + } +}
\ No newline at end of file diff --git a/core/crypto/deoxysii/deoxysii_impl_ct64.odin b/core/crypto/deoxysii/deoxysii_impl_ct64.odin new file mode 100644 index 000000000..c4d0edb03 --- /dev/null +++ b/core/crypto/deoxysii/deoxysii_impl_ct64.odin @@ -0,0 +1,399 @@ +package deoxysii + +import "base:intrinsics" +import "core:crypto" +import aes "core:crypto/_aes/ct64" +import "core:encoding/endian" +import "core:mem" +import "core:simd" + +// This uses the bitlsiced 64-bit general purpose register SWAR AES +// round function. The encryption pass skips orthogonalizing the +// AES round function input as it is aways going to be the leading 0 +// padded IV, and doing a 64-byte copy is faster. + +@(private = "file") +TWEAK_SIZE :: 16 + +@(private = "file") +State_SW :: struct { + ctx: ^Context, + q_stk, q_b: [8]u64, +} + +@(private = "file") +auth_tweak :: #force_inline proc "contextless" ( + dst: ^[TWEAK_SIZE]byte, + prefix: byte, + block_nr: int, +) { + endian.unchecked_put_u64be(dst[8:], u64(block_nr)) + endian.unchecked_put_u64le(dst[0:], u64(prefix) << PREFIX_SHIFT) // dst[0] = prefix << PREFIX_SHIFT +} + +@(private = "file") +enc_tweak :: #force_inline proc "contextless" ( + dst: ^[TWEAK_SIZE]byte, + tag: ^[TAG_SIZE]byte, + block_nr: int, +) { + tmp: [8]byte + endian.unchecked_put_u64be(tmp[:], u64(block_nr)) + + copy(dst[:], tag[:]) + dst[0] |= 0x80 + for i in 0 ..< 8 { + dst[i+8] ~= tmp[i] + } +} + +@(private = "file") +enc_plaintext :: #force_inline proc "contextless" ( + dst: ^[8]u64, + iv: []byte, +) { + tmp: [BLOCK_SIZE]byte = --- + tmp[0] = 0 + copy(tmp[1:], iv[:]) + + q_0, q_1 := aes.load_interleaved(tmp[:]) + for i in 0 ..< 4 { + dst[i], dst[i+4] = q_0, q_1 + } + aes.orthogonalize(dst) +} + +@(private = "file") +bc_x4 :: proc "contextless" ( + ctx: ^Context, + dst: []byte, + tweaks: ^[4][TWEAK_SIZE]byte, + q_stk: ^[8]u64, + q_b: ^[8]u64, // Orthogonalized + n: int, +) { + tk1s: [4]simd.u8x16 + for j in 0 ..< n { + tk1s[j] = intrinsics.unaligned_load((^simd.u8x16)(&tweaks[j])) + } + + // Deoxys-BC-384 + for i in 0 ..= BC_ROUNDS { + // Derive the round's subtweakkey + sk := intrinsics.unaligned_load((^simd.u8x16)(&ctx._subkeys[i])) + for j in 0 ..< n { + if i != 0 { + tk1s[j] = h(tk1s[j]) + } + intrinsics.unaligned_store( + (^simd.u8x16)(raw_data(dst)), + simd.bit_xor(sk, tk1s[j]), + ) + q_stk[j], q_stk[j+4] = aes.load_interleaved(dst[:]) + } + aes.orthogonalize(q_stk) + + if i != 0 { + aes.sub_bytes(q_b) + aes.shift_rows(q_b) + aes.mix_columns(q_b) + } + aes.add_round_key(q_b, q_stk[:]) + } + + aes.orthogonalize(q_b) + for i in 0 ..< n { + aes.store_interleaved(dst[i*BLOCK_SIZE:], q_b[i], q_b[i+4]) + } +} + +@(private = "file", require_results) +bc_absorb :: proc "contextless" ( + st: ^State_SW, + dst: []byte, + src: []byte, + tweak_prefix: byte, + stk_block_nr: int, +) -> int { + tweaks: [4][TWEAK_SIZE]byte = --- + tmp: [BLOCK_SIZE*4]byte = --- + + src, stk_block_nr := src, stk_block_nr + dst_ := intrinsics.unaligned_load((^simd.u8x16)(raw_data(dst))) + + nr_blocks := len(src) / BLOCK_SIZE + for nr_blocks > 0 { + // Derive the tweak(s), orthogonalize the plaintext + n := min(nr_blocks, 4) + for i in 0 ..< n { + auth_tweak(&tweaks[i], tweak_prefix, stk_block_nr + i) + st.q_b[i], st.q_b[i + 4] = aes.load_interleaved(src) + src = src[BLOCK_SIZE:] + } + aes.orthogonalize(&st.q_b) + + // Deoxys-BC-384 + bc_x4(st.ctx, tmp[:], &tweaks, &st.q_stk, &st.q_b, n) + + // XOR in the existing Auth/tag + for i in 0 ..< n { + dst_ = simd.bit_xor( + dst_, + intrinsics.unaligned_load((^simd.u8x16)(raw_data(tmp[i*BLOCK_SIZE:]))), + ) + } + + stk_block_nr += n + nr_blocks -= n + } + + intrinsics.unaligned_store((^simd.u8x16)(raw_data(dst)), dst_) + + mem.zero_explicit(&tweaks, size_of(tweaks)) + mem.zero_explicit(&tmp, size_of(tmp)) + + return stk_block_nr +} + +@(private = "file") +bc_final :: proc "contextless" ( + st: ^State_SW, + dst: []byte, + iv: []byte, +) { + tweaks: [4][TWEAK_SIZE]byte = --- + + tweaks[0][0] = PREFIX_TAG << PREFIX_SHIFT + copy(tweaks[0][1:], iv) + + st.q_b[0], st.q_b[4] = aes.load_interleaved(dst) + aes.orthogonalize(&st.q_b) + + bc_x4(st.ctx, dst, &tweaks, &st.q_stk, &st.q_b, 1) +} + +@(private = "file", require_results) +bc_encrypt :: proc "contextless" ( + st: ^State_SW, + dst: []byte, + src: []byte, + q_n: ^[8]u64, // Orthogonalized + tweak_tag: ^[TAG_SIZE]byte, + stk_block_nr: int, +) -> int { + tweaks: [4][TWEAK_SIZE]byte = --- + tmp: [BLOCK_SIZE*4]byte = --- + + dst, src, stk_block_nr := dst, src, stk_block_nr + + nr_blocks := len(src) / BLOCK_SIZE + for nr_blocks > 0 { + // Derive the tweak(s) + n := min(nr_blocks, 4) + for i in 0 ..< n { + enc_tweak(&tweaks[i], tweak_tag, stk_block_nr + i) + } + st.q_b = q_n^ // The plaintext is always `0^8 || N` + + // Deoxys-BC-384 + bc_x4(st.ctx, tmp[:], &tweaks, &st.q_stk, &st.q_b, n) + + // XOR the ciphertext + for i in 0 ..< n { + intrinsics.unaligned_store( + (^simd.u8x16)(raw_data(dst[i*BLOCK_SIZE:])), + simd.bit_xor( + intrinsics.unaligned_load((^simd.u8x16)(raw_data(src[i*BLOCK_SIZE:]))), + intrinsics.unaligned_load((^simd.u8x16)(raw_data(tmp[i*BLOCK_SIZE:]))), + ), + ) + } + + dst, src = dst[n*BLOCK_SIZE:], src[n*BLOCK_SIZE:] + stk_block_nr += n + nr_blocks -= n + } + + mem.zero_explicit(&tweaks, size_of(tweaks)) + mem.zero_explicit(&tmp, size_of(tmp)) + + return stk_block_nr +} + +@(private) +e_ref :: proc "contextless" (ctx: ^Context, dst, tag, iv, aad, plaintext: []byte) #no_bounds_check { + st: State_SW = --- + st.ctx = ctx + + // Algorithm 3 + // + // Associated data + // A_1 || ... || A_la || A_∗ <- A where each |A_i| = n and |A_∗| < n + // Auth <- 0^n + // for i = 0 to la − 1 do + // Auth <- Auth ^ EK(0010 || i, A_i+1) + // end + // if A_∗ != nil then + // Auth <- Auth ^ EK(0110 || la, pad10∗(A_∗)) + // end + auth: [TAG_SIZE]byte + aad := aad + n := bc_absorb(&st, auth[:], aad, PREFIX_AD_BLOCK, 0) + aad = aad[n*BLOCK_SIZE:] + if l := len(aad); l > 0 { + a_star: [BLOCK_SIZE]byte + + copy(a_star[:], aad) + a_star[l] = 0x80 + + _ = bc_absorb(&st, auth[:], a_star[:], PREFIX_AD_FINAL, n) + } + + // Message authentication and tag generation + // M_1 || ... || M_l || M_∗ <- M where each |M_j| = n and |M_∗| < n + // tag <- Auth + // for j = 0 to l − 1 do + // tag <- tag ^ EK(0000 || j, M_j+1) + // end + // if M_∗ != nil then + // tag <- tag ^ EK(0100 || l, pad10∗(M_∗)) + // end + // tag <- EK(0001 || 0^4 || N, tag) + m := plaintext + n = bc_absorb(&st, auth[:], m, PREFIX_MSG_BLOCK, 0) + m = m[n*BLOCK_SIZE:] + if l := len(m); l > 0 { + m_star: [BLOCK_SIZE]byte + + copy(m_star[:], m) + m_star[l] = 0x80 + + _ = bc_absorb(&st, auth[:], m_star[:], PREFIX_MSG_FINAL, n) + } + bc_final(&st, auth[:], iv) + + // Message encryption + // for j = 0 to l − 1 do + // C_j <- M_j ^ EK(1 || tag ^ j, 0^8 || N) + // end + // if M_∗ != nil then + // C_∗ <- M_* ^ EK(1 || tag ^ l, 0^8 || N) + // end + // + // return (C_1 || ... || C_l || C_∗, tag) + q_iv: [8]u64 = --- + enc_plaintext(&q_iv, iv) + + m = plaintext + n = bc_encrypt(&st, dst, m, &q_iv, &auth, 0) + m = m[n*BLOCK_SIZE:] + if l := len(m); l > 0 { + m_star: [BLOCK_SIZE]byte + + copy(m_star[:], m) + _ = bc_encrypt(&st, m_star[:], m_star[:], &q_iv, &auth, n) + + copy(dst[n*BLOCK_SIZE:], m_star[:]) + + mem.zero_explicit(&m_star, size_of(m_star)) + } + + copy(tag, auth[:]) + + mem.zero_explicit(&st.q_stk, size_of(st.q_stk)) + mem.zero_explicit(&st.q_b, size_of(st.q_b)) +} + +@(private, require_results) +d_ref :: proc "contextless" (ctx: ^Context, dst, iv, aad, ciphertext, tag: []byte) -> bool { + st: State_SW = --- + st.ctx = ctx + + // Algorithm 4 + // + // Message decryption + // C_1 || ... || C_l || C_∗ <- C where each |C_j| = n and |C_∗| < n + // for j = 0 to l − 1 do + // M_j <- C_j ^ EK(1 || tag ^ j, 0^8 || N) + // end + // if C_∗ != nil then + // M_∗ <- C_∗ ^ EK(1 || tag ^ l, 0^8 || N) + // end + q_iv: [8]u64 = --- + enc_plaintext(&q_iv, iv) + + auth: [TAG_SIZE]byte + copy(auth[:], tag) + + m := ciphertext + n := bc_encrypt(&st, dst, m, &q_iv, &auth, 0) + m = m[n*BLOCK_SIZE:] + if l := len(m); l > 0 { + m_star: [BLOCK_SIZE]byte + + copy(m_star[:], m) + _ = bc_encrypt(&st, m_star[:], m_star[:], &q_iv, &auth, n) + + copy(dst[n*BLOCK_SIZE:], m_star[:]) + + mem.zero_explicit(&m_star, size_of(m_star)) + } + + // Associated data + // A_1 || ... || Al_a || A_∗ <- A where each |Ai_| = n and |A_∗| < n + // Auth <- 0 + // for i = 0 to la − 1 do + // Auth <- Auth ^ EK(0010 || i, A_i+1) + // end + // if A∗ != nil then + // Auth <- Auth ^ EK(0110| | l_a, pad10∗(A_∗)) + // end + auth = 0 + aad := aad + n = bc_absorb(&st, auth[:], aad, PREFIX_AD_BLOCK, 0) + aad = aad[n*BLOCK_SIZE:] + if l := len(aad); l > 0 { + a_star: [BLOCK_SIZE]byte + + copy(a_star[:], aad) + a_star[l] = 0x80 + + _ = bc_absorb(&st, auth[:], a_star[:], PREFIX_AD_FINAL, n) + } + + // Message authentication and tag generation + // M_1 || ... || M_l || M_∗ <- M where each |M_j| = n and |M_∗| < n + // tag0 <- Auth + // for j = 0 to l − 1 do + // tag0 <- tag0 ^ EK(0000 || j, M_j+1) + // end + // if M_∗ != nil then + // tag0 <- tag0 ^ EK(0100 || l, pad10∗(M_∗)) + // end + // tag0 <- EK(0001 || 0^4 || N, tag0) + m = dst[:len(ciphertext)] + n = bc_absorb(&st, auth[:], m, PREFIX_MSG_BLOCK, 0) + m = m[n*BLOCK_SIZE:] + if l := len(m); l > 0 { + m_star: [BLOCK_SIZE]byte + + copy(m_star[:], m) + m_star[l] = 0x80 + + _ = bc_absorb(&st, auth[:], m_star[:], PREFIX_MSG_FINAL, n) + + mem.zero_explicit(&m_star, size_of(m_star)) + } + bc_final(&st, auth[:], iv) + + // Tag verification + // if tag0 = tag then return (M_1 || ... || M_l || M_∗) + // else return false + ok := crypto.compare_constant_time(auth[:], tag) == 1 + + mem.zero_explicit(&auth, size_of(auth)) + mem.zero_explicit(&st.q_stk, size_of(st.q_stk)) + mem.zero_explicit(&st.q_b, size_of(st.q_b)) + + return ok +} diff --git a/core/crypto/deoxysii/deoxysii_impl_hw_gen.odin b/core/crypto/deoxysii/deoxysii_impl_hw_gen.odin new file mode 100644 index 000000000..b0705ca62 --- /dev/null +++ b/core/crypto/deoxysii/deoxysii_impl_hw_gen.odin @@ -0,0 +1,21 @@ +#+build !amd64 +package deoxysii + +@(private = "file") +ERR_HW_NOT_SUPPORTED :: "crypto/deoxysii: hardware implementation unsupported" + +// is_hardware_accelerated returns true iff hardware accelerated Deoxys-II +// is supported. +is_hardware_accelerated :: proc "contextless" () -> bool { + return false +} + +@(private) +e_hw :: proc "contextless" (ctx: ^Context, dst, tag, iv, aad, plaintext: []byte) #no_bounds_check { + panic_contextless(ERR_HW_NOT_SUPPORTED) +} + +@(private, require_results) +d_hw :: proc "contextless" (ctx: ^Context, dst, iv, aad, ciphertext, tag: []byte) -> bool { + panic_contextless(ERR_HW_NOT_SUPPORTED) +} diff --git a/core/crypto/deoxysii/deoxysii_impl_hw_intel.odin b/core/crypto/deoxysii/deoxysii_impl_hw_intel.odin new file mode 100644 index 000000000..d268009a2 --- /dev/null +++ b/core/crypto/deoxysii/deoxysii_impl_hw_intel.odin @@ -0,0 +1,434 @@ +#+build amd64 +package deoxysii + +import "base:intrinsics" +import "core:crypto" +import "core:crypto/aes" +import "core:mem" +import "core:simd" +import "core:simd/x86" + +// This processes a maximum of 4 blocks at a time, as that is suitable +// for most current hardware that doesn't say "Xeon". + +@(private = "file") +_BIT_ENC :: x86.__m128i{0x80, 0} +@(private = "file") +_PREFIX_AD_BLOCK :: x86.__m128i{PREFIX_AD_BLOCK << PREFIX_SHIFT, 0} +@(private = "file") +_PREFIX_AD_FINAL :: x86.__m128i{PREFIX_AD_FINAL << PREFIX_SHIFT, 0} +@(private = "file") +_PREFIX_MSG_BLOCK :: x86.__m128i{PREFIX_MSG_BLOCK << PREFIX_SHIFT, 0} +@(private = "file") +_PREFIX_MSG_FINAL :: x86.__m128i{PREFIX_MSG_FINAL << PREFIX_SHIFT, 0} + +// is_hardware_accelerated returns true iff hardware accelerated Deoxys-II +// is supported. +is_hardware_accelerated :: proc "contextless" () -> bool { + return aes.is_hardware_accelerated() +} + +@(private = "file", enable_target_feature = "sse4.1", require_results) +auth_tweak :: #force_inline proc "contextless" ( + prefix: x86.__m128i, + block_nr: int, +) -> x86.__m128i { + return x86._mm_insert_epi64(prefix, i64(intrinsics.byte_swap(u64(block_nr))), 1) +} + +@(private = "file", enable_target_feature = "sse2", require_results) +enc_tweak :: #force_inline proc "contextless" ( + tag: x86.__m128i, + block_nr: int, +) -> x86.__m128i { + return x86._mm_xor_si128( + x86._mm_or_si128(tag, _BIT_ENC), + x86.__m128i{0, i64(intrinsics.byte_swap(u64(block_nr)))}, + ) +} + +@(private = "file", enable_target_feature = "ssse3", require_results) +h_ :: #force_inline proc "contextless" (tk1: x86.__m128i) -> x86.__m128i { + return transmute(x86.__m128i)h(transmute(simd.u8x16)tk1) +} + +@(private = "file", enable_target_feature = "sse2,ssse3,aes", require_results) +bc_x4 :: #force_inline proc "contextless" ( + ctx: ^Context, + s_0, s_1, s_2, s_3: x86.__m128i, + tweak_0, tweak_1, tweak_2, tweak_3: x86.__m128i, +) -> (x86.__m128i, x86.__m128i, x86.__m128i, x86.__m128i) #no_bounds_check { + s_0, s_1, s_2, s_3 := s_0, s_1, s_2, s_3 + tk1_0, tk1_1, tk1_2, tk1_3 := tweak_0, tweak_1, tweak_2, tweak_3 + + sk := intrinsics.unaligned_load((^x86.__m128i)(&ctx._subkeys[0])) + stk_0 := x86._mm_xor_si128(tk1_0, sk) + stk_1 := x86._mm_xor_si128(tk1_1, sk) + stk_2 := x86._mm_xor_si128(tk1_2, sk) + stk_3 := x86._mm_xor_si128(tk1_3, sk) + + s_0 = x86._mm_xor_si128(s_0, stk_0) + s_1 = x86._mm_xor_si128(s_1, stk_1) + s_2 = x86._mm_xor_si128(s_2, stk_2) + s_3 = x86._mm_xor_si128(s_3, stk_3) + + for i in 1 ..= BC_ROUNDS { + sk = intrinsics.unaligned_load((^x86.__m128i)(&ctx._subkeys[i])) + + tk1_0 = h_(tk1_0) + tk1_1 = h_(tk1_1) + tk1_2 = h_(tk1_2) + tk1_3 = h_(tk1_3) + + stk_0 = x86._mm_xor_si128(tk1_0, sk) + stk_1 = x86._mm_xor_si128(tk1_1, sk) + stk_2 = x86._mm_xor_si128(tk1_2, sk) + stk_3 = x86._mm_xor_si128(tk1_3, sk) + + s_0 = x86._mm_aesenc_si128(s_0, stk_0) + s_1 = x86._mm_aesenc_si128(s_1, stk_1) + s_2 = x86._mm_aesenc_si128(s_2, stk_2) + s_3 = x86._mm_aesenc_si128(s_3, stk_3) + } + + return s_0, s_1, s_2, s_3 +} + +@(private = "file", enable_target_feature = "sse2,ssse3,aes", require_results) +bc_x1 :: #force_inline proc "contextless" ( + ctx: ^Context, + s: x86.__m128i, + tweak: x86.__m128i, +) -> x86.__m128i #no_bounds_check { + s, tk1 := s, tweak + + sk := intrinsics.unaligned_load((^x86.__m128i)(&ctx._subkeys[0])) + stk := x86._mm_xor_si128(tk1, sk) + + s = x86._mm_xor_si128(s, stk) + + for i in 1 ..= BC_ROUNDS { + sk = intrinsics.unaligned_load((^x86.__m128i)(&ctx._subkeys[i])) + + tk1 = h_(tk1) + + stk = x86._mm_xor_si128(tk1, sk) + + s = x86._mm_aesenc_si128(s, stk) + } + + return s +} + +@(private = "file", enable_target_feature = "sse2,ssse3,sse4.1,aes", require_results) +bc_absorb :: proc "contextless" ( + ctx: ^Context, + tag: x86.__m128i, + src: []byte, + tweak_prefix: x86.__m128i, + stk_block_nr: int, +) -> (x86.__m128i, int) #no_bounds_check { + src, stk_block_nr, tag := src, stk_block_nr, tag + + nr_blocks := len(src) / BLOCK_SIZE + for nr_blocks >= 4 { + d_0, d_1, d_2, d_3 := bc_x4( + ctx, + intrinsics.unaligned_load((^x86.__m128i)(raw_data(src))), + intrinsics.unaligned_load((^x86.__m128i)(raw_data(src[BLOCK_SIZE:]))), + intrinsics.unaligned_load((^x86.__m128i)(raw_data(src[2*BLOCK_SIZE:]))), + intrinsics.unaligned_load((^x86.__m128i)(raw_data(src[3*BLOCK_SIZE:]))), + auth_tweak(tweak_prefix, stk_block_nr), + auth_tweak(tweak_prefix, stk_block_nr + 1), + auth_tweak(tweak_prefix, stk_block_nr + 2), + auth_tweak(tweak_prefix, stk_block_nr + 3), + ) + + tag = x86._mm_xor_si128(tag, d_0) + tag = x86._mm_xor_si128(tag, d_1) + tag = x86._mm_xor_si128(tag, d_2) + tag = x86._mm_xor_si128(tag, d_3) + + src = src[4*BLOCK_SIZE:] + stk_block_nr += 4 + nr_blocks -= 4 + } + + for nr_blocks > 0 { + d := bc_x1( + ctx, + intrinsics.unaligned_load((^x86.__m128i)(raw_data(src))), + auth_tweak(tweak_prefix, stk_block_nr), + ) + + tag = x86._mm_xor_si128(tag, d) + + src = src[BLOCK_SIZE:] + stk_block_nr += 1 + nr_blocks -= 1 + } + + return tag, stk_block_nr +} + +@(private = "file", enable_target_feature = "sse2,ssse3,aes", require_results) +bc_final :: proc "contextless" ( + ctx: ^Context, + tag: x86.__m128i, + iv: []byte, +) -> x86.__m128i { + tmp: [BLOCK_SIZE]byte + + tmp[0] = PREFIX_TAG << PREFIX_SHIFT + copy(tmp[1:], iv) + + tweak := intrinsics.unaligned_load((^x86.__m128i)(&tmp)) + + return bc_x1(ctx, tag, tweak) +} + +@(private = "file", enable_target_feature = "sse2,ssse3,aes", require_results) +bc_encrypt :: proc "contextless" ( + ctx: ^Context, + dst: []byte, + src: []byte, + iv: x86.__m128i, + tweak_tag: x86.__m128i, + stk_block_nr: int, +) -> int { + dst, src, stk_block_nr := dst, src, stk_block_nr + + nr_blocks := len(src) / BLOCK_SIZE + for nr_blocks >= 4 { + d_0, d_1, d_2, d_3 := bc_x4( + ctx, + iv, iv, iv, iv, + enc_tweak(tweak_tag, stk_block_nr), + enc_tweak(tweak_tag, stk_block_nr + 1), + enc_tweak(tweak_tag, stk_block_nr + 2), + enc_tweak(tweak_tag, stk_block_nr + 3), + ) + + intrinsics.unaligned_store( + (^x86.__m128i)(raw_data(dst)), + x86._mm_xor_si128( + d_0, + intrinsics.unaligned_load((^x86.__m128i)(raw_data(src))), + ), + ) + intrinsics.unaligned_store( + (^x86.__m128i)(raw_data(dst[BLOCK_SIZE:])), + x86._mm_xor_si128( + d_1, + intrinsics.unaligned_load((^x86.__m128i)(raw_data(src[BLOCK_SIZE:]))), + ), + ) + intrinsics.unaligned_store( + (^x86.__m128i)(raw_data(dst[2*BLOCK_SIZE:])), + x86._mm_xor_si128( + d_2, + intrinsics.unaligned_load((^x86.__m128i)(raw_data(src[2*BLOCK_SIZE:]))), + ), + ) + intrinsics.unaligned_store( + (^x86.__m128i)(raw_data(dst[3*BLOCK_SIZE:])), + x86._mm_xor_si128( + d_3, + intrinsics.unaligned_load((^x86.__m128i)(raw_data(src[3*BLOCK_SIZE:]))), + ), + ) + + src, dst = src[4*BLOCK_SIZE:], dst[4*BLOCK_SIZE:] + stk_block_nr += 4 + nr_blocks -= 4 + } + + for nr_blocks > 0 { + d := bc_x1( + ctx, + iv, + enc_tweak(tweak_tag, stk_block_nr), + ) + + intrinsics.unaligned_store( + (^x86.__m128i)(raw_data(dst)), + x86._mm_xor_si128( + d, + intrinsics.unaligned_load((^x86.__m128i)(raw_data(src))), + ), + ) + + src, dst = src[BLOCK_SIZE:], dst[BLOCK_SIZE:] + stk_block_nr += 1 + nr_blocks -= 1 + } + + return stk_block_nr +} + +@(private) +e_hw :: proc "contextless" (ctx: ^Context, dst, tag, iv, aad, plaintext: []byte) #no_bounds_check { + tmp: [BLOCK_SIZE]byte + copy(tmp[1:], iv) + iv_ := intrinsics.unaligned_load((^x86.__m128i)(raw_data(&tmp))) + + // Algorithm 3 + // + // Associated data + // A_1 || ... || A_la || A_∗ <- A where each |A_i| = n and |A_∗| < n + // Auth <- 0^n + // for i = 0 to la − 1 do + // Auth <- Auth ^ EK(0010 || i, A_i+1) + // end + // if A_∗ != nil then + // Auth <- Auth ^ EK(0110 || la, pad10∗(A_∗)) + // end + auth: x86.__m128i + n: int + + aad := aad + auth, n = bc_absorb(ctx, auth, aad, _PREFIX_AD_BLOCK, 0) + aad = aad[n*BLOCK_SIZE:] + if l := len(aad); l > 0 { + a_star: [BLOCK_SIZE]byte + + copy(a_star[:], aad) + a_star[l] = 0x80 + + auth, _ = bc_absorb(ctx, auth, a_star[:], _PREFIX_AD_FINAL, n) + } + + // Message authentication and tag generation + // M_1 || ... || M_l || M_∗ <- M where each |M_j| = n and |M_∗| < n + // tag <- Auth + // for j = 0 to l − 1 do + // tag <- tag ^ EK(0000 || j, M_j+1) + // end + // if M_∗ != nil then + // tag <- tag ^ EK(0100 || l, pad10∗(M_∗)) + // end + // tag <- EK(0001 || 0^4 ||N, tag) + m := plaintext + auth, n = bc_absorb(ctx, auth, m, _PREFIX_MSG_BLOCK, 0) + m = m[n*BLOCK_SIZE:] + if l := len(m); l > 0 { + m_star: [BLOCK_SIZE]byte + + copy(m_star[:], m) + m_star[l] = 0x80 + + auth, _ = bc_absorb(ctx, auth, m_star[:], _PREFIX_MSG_FINAL, n) + } + auth = bc_final(ctx, auth, iv) + + // Message encryption + // for j = 0 to l − 1 do + // C_j <- M_j ^ EK(1 || tag ^ j, 0^8 || N) + // end + // if M_∗ != nil then + // C_∗ <- M_* ^ EK(1 || tag ^ l, 0^8 || N) + // end + // + // return (C_1 || ... || C_l || C_∗, tag) + m = plaintext + n = bc_encrypt(ctx, dst, m, iv_, auth, 0) + m = m[n*BLOCK_SIZE:] + if l := len(m); l > 0 { + m_star: [BLOCK_SIZE]byte + + copy(m_star[:], m) + _ = bc_encrypt(ctx, m_star[:], m_star[:], iv_, auth, n) + + copy(dst[n*BLOCK_SIZE:], m_star[:]) + } + + intrinsics.unaligned_store((^x86.__m128i)(raw_data(tag)), auth) +} + +@(private, require_results) +d_hw :: proc "contextless" (ctx: ^Context, dst, iv, aad, ciphertext, tag: []byte) -> bool { + tmp: [BLOCK_SIZE]byte + copy(tmp[1:], iv) + iv_ := intrinsics.unaligned_load((^x86.__m128i)(raw_data(&tmp))) + + // Algorithm 4 + // + // Message decryption + // C_1 || ... || C_l || C_∗ <- C where each |C_j| = n and |C_∗| < n + // for j = 0 to l − 1 do + // M_j <- C_j ^ EK(1 || tag ^ j, 0^8 || N) + // end + // if C_∗ != nil then + // M_∗ <- C_∗ ^ EK(1 || tag ^ l, 0^8 || N) + // end + auth := intrinsics.unaligned_load((^x86.__m128i)(raw_data(tag))) + + m := ciphertext + n := bc_encrypt(ctx, dst, m, iv_, auth, 0) + m = m[n*BLOCK_SIZE:] + if l := len(m); l > 0 { + m_star: [BLOCK_SIZE]byte + + copy(m_star[:], m) + _ = bc_encrypt(ctx, m_star[:], m_star[:], iv_, auth, n) + + copy(dst[n*BLOCK_SIZE:], m_star[:]) + + mem.zero_explicit(&m_star, size_of(m_star)) + } + + // Associated data + // A_1 || ... || Al_a || A_∗ <- A where each |Ai_| = n and |A_∗| < n + // Auth <- 0 + // for i = 0 to la − 1 do + // Auth <- Auth ^ EK(0010 || i, A_i+1) + // end + // if A∗ != nil then + // Auth <- Auth ^ EK(0110| | l_a, pad10∗(A_∗)) + // end + auth = x86.__m128i{0, 0} + aad := aad + auth, n = bc_absorb(ctx, auth, aad, _PREFIX_AD_BLOCK, 0) + aad = aad[BLOCK_SIZE*n:] + if l := len(aad); l > 0 { + a_star: [BLOCK_SIZE]byte + + copy(a_star[:], aad) + a_star[l] = 0x80 + + auth, _ = bc_absorb(ctx, auth, a_star[:], _PREFIX_AD_FINAL, n) + } + + // Message authentication and tag generation + // M_1 || ... || M_l || M_∗ <- M where each |M_j| = n and |M_∗| < n + // tag0 <- Auth + // for j = 0 to l − 1 do + // tag0 <- tag0 ^ EK(0000 || j, M_j+1) + // end + // if M_∗ != nil then + // tag0 <- tag0 ^ EK(0100 || l, pad10∗(M_∗)) + // end + // tag0 <- EK(0001 || 0^4 || N, tag0) + m = dst[:len(ciphertext)] + auth, n = bc_absorb(ctx, auth, m, _PREFIX_MSG_BLOCK, 0) + m = m[n*BLOCK_SIZE:] + if l := len(m); l > 0 { + m_star: [BLOCK_SIZE]byte + + copy(m_star[:], m) + m_star[l] = 0x80 + + auth, _ = bc_absorb(ctx, auth, m_star[:], _PREFIX_MSG_FINAL, n) + } + auth = bc_final(ctx, auth, iv) + + // Tag verification + // if tag0 = tag then return (M_1 || ... || M_l || M_∗) + // else return false + intrinsics.unaligned_store((^x86.__m128i)(raw_data(&tmp)), auth) + ok := crypto.compare_constant_time(tmp[:], tag) == 1 + + mem.zero_explicit(&tmp, size_of(tmp)) + + return ok +} diff --git a/core/crypto/ed25519/ed25519.odin b/core/crypto/ed25519/ed25519.odin index 460a19563..deeb80685 100644 --- a/core/crypto/ed25519/ed25519.odin +++ b/core/crypto/ed25519/ed25519.odin @@ -81,12 +81,8 @@ private_key_set_bytes :: proc(priv_key: ^Private_Key, b: []byte) -> bool { // private_key_bytes sets dst to byte-encoding of priv_key. private_key_bytes :: proc(priv_key: ^Private_Key, dst: []byte) { - if !priv_key._is_initialized { - panic("crypto/ed25519: uninitialized private key") - } - if len(dst) != PRIVATE_KEY_SIZE { - panic("crypto/ed25519: invalid destination size") - } + ensure(priv_key._is_initialized, "crypto/ed25519: uninitialized private key") + ensure(len(dst) == PRIVATE_KEY_SIZE, "crypto/ed25519: invalid destination size") copy(dst, priv_key._b[:]) } @@ -98,12 +94,8 @@ private_key_clear :: proc "contextless" (priv_key: ^Private_Key) { // sign writes the signature by priv_key over msg to sig. sign :: proc(priv_key: ^Private_Key, msg, sig: []byte) { - if !priv_key._is_initialized { - panic("crypto/ed25519: uninitialized private key") - } - if len(sig) != SIGNATURE_SIZE { - panic("crypto/ed25519: invalid destination size") - } + ensure(priv_key._is_initialized, "crypto/ed25519: uninitialized private key") + ensure(len(sig) == SIGNATURE_SIZE, "crypto/ed25519: invalid destination size") // 1. Compute the hash of the private key d, H(d) = (h_0, h_1, ..., h_2b-1) // using SHA-512 for Ed25519. H(d) may be precomputed. @@ -178,9 +170,7 @@ public_key_set_bytes :: proc "contextless" (pub_key: ^Public_Key, b: []byte) -> // public_key_set_priv sets pub_key to the public component of priv_key. public_key_set_priv :: proc(pub_key: ^Public_Key, priv_key: ^Private_Key) { - if !priv_key._is_initialized { - panic("crypto/ed25519: uninitialized public key") - } + ensure(priv_key._is_initialized, "crypto/ed25519: uninitialized public key") src := &priv_key._pub_key copy(pub_key._b[:], src._b[:]) @@ -191,21 +181,15 @@ public_key_set_priv :: proc(pub_key: ^Public_Key, priv_key: ^Private_Key) { // public_key_bytes sets dst to byte-encoding of pub_key. public_key_bytes :: proc(pub_key: ^Public_Key, dst: []byte) { - if !pub_key._is_initialized { - panic("crypto/ed25519: uninitialized public key") - } - if len(dst) != PUBLIC_KEY_SIZE { - panic("crypto/ed25519: invalid destination size") - } + ensure(pub_key._is_initialized, "crypto/ed25519: uninitialized public key") + ensure(len(dst) == PUBLIC_KEY_SIZE, "crypto/ed25519: invalid destination size") copy(dst, pub_key._b[:]) } // public_key_equal returns true iff pub_key is equal to other. public_key_equal :: proc(pub_key, other: ^Public_Key) -> bool { - if !pub_key._is_initialized || !other._is_initialized { - panic("crypto/ed25519: uninitialized public key") - } + ensure(pub_key._is_initialized && other._is_initialized, "crypto/ed25519: uninitialized public key") return crypto.compare_constant_time(pub_key._b[:], other._b[:]) == 1 } diff --git a/core/crypto/hash/hash.odin b/core/crypto/hash/hash.odin index d47f0ab46..66d9201cd 100644 --- a/core/crypto/hash/hash.odin +++ b/core/crypto/hash/hash.odin @@ -21,8 +21,7 @@ hash_string :: proc(algorithm: Algorithm, data: string, allocator := context.all // in a newly allocated slice. hash_bytes :: proc(algorithm: Algorithm, data: []byte, allocator := context.allocator) -> []byte { dst := make([]byte, DIGEST_SIZES[algorithm], allocator) - hash_bytes_to_buffer(algorithm, data, dst) - return dst + return hash_bytes_to_buffer(algorithm, data, dst) } // hash_string_to_buffer will hash the given input and assign the @@ -46,7 +45,7 @@ hash_bytes_to_buffer :: proc(algorithm: Algorithm, data, hash: []byte) -> []byte update(&ctx, data) final(&ctx, hash) - return hash + return hash[:DIGEST_SIZES[algorithm]] } // hash_stream will incrementally fully consume a stream, and return the diff --git a/core/crypto/hmac/hmac.odin b/core/crypto/hmac/hmac.odin index 4813a9938..f74d6492f 100644 --- a/core/crypto/hmac/hmac.odin +++ b/core/crypto/hmac/hmac.odin @@ -56,7 +56,7 @@ init :: proc(ctx: ^Context, algorithm: hash.Algorithm, key: []byte) { // update adds more data to the Context. update :: proc(ctx: ^Context, data: []byte) { - assert(ctx._is_initialized) + ensure(ctx._is_initialized) hash.update(&ctx._i_hash, data) } @@ -64,13 +64,10 @@ update :: proc(ctx: ^Context, data: []byte) { // final finalizes the Context, writes the tag to dst, and calls // reset on the Context. final :: proc(ctx: ^Context, dst: []byte) { - assert(ctx._is_initialized) - defer (reset(ctx)) - if len(dst) != ctx._tag_sz { - panic("crypto/hmac: invalid destination tag size") - } + ensure(ctx._is_initialized) + ensure(len(dst) == ctx._tag_sz, "crypto/hmac: invalid destination tag size") hash.final(&ctx._i_hash, dst) // H((k ^ ipad) || text) @@ -105,14 +102,14 @@ reset :: proc(ctx: ^Context) { // algorithm returns the Algorithm used by a Context instance. algorithm :: proc(ctx: ^Context) -> hash.Algorithm { - assert(ctx._is_initialized) + ensure(ctx._is_initialized) return hash.algorithm(&ctx._i_hash) } // tag_size returns the tag size of a Context instance in bytes. tag_size :: proc(ctx: ^Context) -> int { - assert(ctx._is_initialized) + ensure(ctx._is_initialized) return ctx._tag_sz } diff --git a/core/crypto/kmac/kmac.odin b/core/crypto/kmac/kmac.odin index e8bf42946..6f58e20a7 100644 --- a/core/crypto/kmac/kmac.odin +++ b/core/crypto/kmac/kmac.odin @@ -36,6 +36,7 @@ sum :: proc(sec_strength: int, dst, msg, key, domain_sep: []byte) { // tag is valid. verify :: proc(sec_strength: int, tag, msg, key, domain_sep: []byte, allocator := context.temp_allocator) -> bool { derived_tag := make([]byte, len(tag), allocator) + defer(delete(derived_tag)) sum(sec_strength, derived_tag, msg, key, domain_sep) @@ -59,8 +60,6 @@ init_256 :: proc(ctx: ^Context, key, domain_sep: []byte) { // update adds more data to the Context. update :: proc(ctx: ^Context, data: []byte) { - assert(ctx.is_initialized) - shake.write((^shake.Context)(ctx), data) } @@ -68,12 +67,9 @@ update :: proc(ctx: ^Context, data: []byte) { // on the Context. This routine will panic if the dst length is less than // MIN_TAG_SIZE. final :: proc(ctx: ^Context, dst: []byte) { - assert(ctx.is_initialized) defer reset(ctx) - if len(dst) < MIN_TAG_SIZE { - panic("crypto/kmac: invalid KMAC tag_size, too short") - } + ensure(len(dst) >= MIN_TAG_SIZE, "crypto/kmac: invalid KMAC tag_size, too short") _sha3.final_cshake((^_sha3.Context)(ctx), dst) } @@ -103,14 +99,12 @@ _init_kmac :: proc(ctx: ^Context, key, s: []byte, sec_strength: int) { reset(ctx) } - if len(key) < sec_strength / 8 { - panic("crypto/kmac: invalid KMAC key, too short") - } + ensure(len(key) >= sec_strength / 8, "crypto/kmac: invalid KMAC key, too short") ctx_ := (^_sha3.Context)(ctx) _sha3.init_cshake(ctx_, N_KMAC, s, sec_strength) _sha3.bytepad(ctx_, [][]byte{key}, _sha3.rate_cshake(sec_strength)) } -@(private) +@(private, rodata) N_KMAC := []byte{'K', 'M', 'A', 'C'} diff --git a/core/crypto/legacy/keccak/keccak.odin b/core/crypto/legacy/keccak/keccak.odin index 6ca66b7ca..40fc2729f 100644 --- a/core/crypto/legacy/keccak/keccak.odin +++ b/core/crypto/legacy/keccak/keccak.odin @@ -40,37 +40,37 @@ BLOCK_SIZE_512 :: _sha3.RATE_512 Context :: distinct _sha3.Context // init_224 initializes a Context for Keccak-224. -init_224 :: proc(ctx: ^Context) { +init_224 :: proc "contextless" (ctx: ^Context) { ctx.mdlen = DIGEST_SIZE_224 _init(ctx) } // init_256 initializes a Context for Keccak-256. -init_256 :: proc(ctx: ^Context) { +init_256 :: proc "contextless" (ctx: ^Context) { ctx.mdlen = DIGEST_SIZE_256 _init(ctx) } // init_384 initializes a Context for Keccak-384. -init_384 :: proc(ctx: ^Context) { +init_384 :: proc "contextless" (ctx: ^Context) { ctx.mdlen = DIGEST_SIZE_384 _init(ctx) } // init_512 initializes a Context for Keccak-512. -init_512 :: proc(ctx: ^Context) { +init_512 :: proc "contextless" (ctx: ^Context) { ctx.mdlen = DIGEST_SIZE_512 _init(ctx) } @(private) -_init :: proc(ctx: ^Context) { +_init :: proc "contextless" (ctx: ^Context) { ctx.dsbyte = _sha3.DS_KECCAK _sha3.init((^_sha3.Context)(ctx)) } // update adds more data to the Context. -update :: proc(ctx: ^Context, data: []byte) { +update :: proc "contextless" (ctx: ^Context, data: []byte) { _sha3.update((^_sha3.Context)(ctx), data) } @@ -79,17 +79,17 @@ update :: proc(ctx: ^Context, data: []byte) { // // Iff finalize_clone is set, final will work on a copy of the Context, // which is useful for for calculating rolling digests. -final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) { +final :: proc "contextless" (ctx: ^Context, hash: []byte, finalize_clone: bool = false) { _sha3.final((^_sha3.Context)(ctx), hash, finalize_clone) } // clone clones the Context other into ctx. -clone :: proc(ctx, other: ^Context) { +clone :: proc "contextless" (ctx, other: ^Context) { _sha3.clone((^_sha3.Context)(ctx), (^_sha3.Context)(other)) } // reset sanitizes the Context. The Context must be re-initialized to // be used again. -reset :: proc(ctx: ^Context) { +reset :: proc "contextless" (ctx: ^Context) { _sha3.reset((^_sha3.Context)(ctx)) } diff --git a/core/crypto/legacy/md5/md5.odin b/core/crypto/legacy/md5/md5.odin index 28b47e0b3..050501d98 100644 --- a/core/crypto/legacy/md5/md5.odin +++ b/core/crypto/legacy/md5/md5.odin @@ -53,7 +53,7 @@ init :: proc(ctx: ^Context) { // update adds more data to the Context. update :: proc(ctx: ^Context, data: []byte) { - assert(ctx.is_initialized) + ensure(ctx.is_initialized) for i := 0; i < len(data); i += 1 { ctx.data[ctx.datalen] = data[i] @@ -72,11 +72,8 @@ update :: proc(ctx: ^Context, data: []byte) { // Iff finalize_clone is set, final will work on a copy of the Context, // which is useful for for calculating rolling digests. final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) { - assert(ctx.is_initialized) - - if len(hash) < DIGEST_SIZE { - panic("crypto/md5: invalid destination digest size") - } + ensure(ctx.is_initialized) + ensure(len(hash) >= DIGEST_SIZE, "crypto/md5: invalid destination digest size") ctx := ctx if finalize_clone { diff --git a/core/crypto/legacy/sha1/sha1.odin b/core/crypto/legacy/sha1/sha1.odin index 1025ecb5b..5a2b57005 100644 --- a/core/crypto/legacy/sha1/sha1.odin +++ b/core/crypto/legacy/sha1/sha1.odin @@ -60,7 +60,7 @@ init :: proc(ctx: ^Context) { // update adds more data to the Context. update :: proc(ctx: ^Context, data: []byte) { - assert(ctx.is_initialized) + ensure(ctx.is_initialized) for i := 0; i < len(data); i += 1 { ctx.data[ctx.datalen] = data[i] @@ -79,11 +79,8 @@ update :: proc(ctx: ^Context, data: []byte) { // Iff finalize_clone is set, final will work on a copy of the Context, // which is useful for for calculating rolling digests. final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) { - assert(ctx.is_initialized) - - if len(hash) < DIGEST_SIZE { - panic("crypto/sha1: invalid destination digest size") - } + ensure(ctx.is_initialized) + ensure(len(hash) >= DIGEST_SIZE, "crypto/sha1: invalid destination digest size") ctx := ctx if finalize_clone { diff --git a/core/crypto/poly1305/poly1305.odin b/core/crypto/poly1305/poly1305.odin index ea0e6c907..3dd915da7 100644 --- a/core/crypto/poly1305/poly1305.odin +++ b/core/crypto/poly1305/poly1305.odin @@ -60,9 +60,7 @@ Context :: struct { // init initializes a Context with the specified key. The key SHOULD be // unique and MUST be unpredictable for each invocation. init :: proc(ctx: ^Context, key: []byte) { - if len(key) != KEY_SIZE { - panic("crypto/poly1305: invalid key size") - } + ensure(len(key) == KEY_SIZE, "crypto/poly1305: invalid key size") // r = le_bytes_to_num(key[0..15]) // r = clamp(r) (r &= 0xffffffc0ffffffc0ffffffc0fffffff) @@ -85,7 +83,7 @@ init :: proc(ctx: ^Context, key: []byte) { // update adds more data to the Context. update :: proc(ctx: ^Context, data: []byte) { - assert(ctx._is_initialized) + ensure(ctx._is_initialized) msg := data msg_len := len(data) @@ -124,12 +122,10 @@ update :: proc(ctx: ^Context, data: []byte) { // final finalizes the Context, writes the tag to dst, and calls // reset on the Context. final :: proc(ctx: ^Context, dst: []byte) { - assert(ctx._is_initialized) defer reset(ctx) - if len(dst) != TAG_SIZE { - panic("poly1305: invalid destination tag size") - } + ensure(ctx._is_initialized) + ensure(len(dst) == TAG_SIZE, "poly1305: invalid destination tag size") // Process remaining block if ctx._leftover > 0 { diff --git a/core/crypto/ristretto255/ristretto255.odin b/core/crypto/ristretto255/ristretto255.odin index 7b0944e33..20a002900 100644 --- a/core/crypto/ristretto255/ristretto255.odin +++ b/core/crypto/ristretto255/ristretto255.odin @@ -16,7 +16,7 @@ ELEMENT_SIZE :: 32 // group element. WIDE_ELEMENT_SIZE :: 64 -@(private) +@(private, rodata) FE_NEG_ONE := field.Tight_Field_Element { 2251799813685228, 2251799813685247, @@ -24,7 +24,7 @@ FE_NEG_ONE := field.Tight_Field_Element { 2251799813685247, 2251799813685247, } -@(private) +@(private, rodata) FE_INVSQRT_A_MINUS_D := field.Tight_Field_Element { 278908739862762, 821645201101625, @@ -32,7 +32,7 @@ FE_INVSQRT_A_MINUS_D := field.Tight_Field_Element { 1777959178193151, 2118520810568447, } -@(private) +@(private, rodata) FE_ONE_MINUS_D_SQ := field.Tight_Field_Element { 1136626929484150, 1998550399581263, @@ -40,7 +40,7 @@ FE_ONE_MINUS_D_SQ := field.Tight_Field_Element { 118527312129759, 45110755273534, } -@(private) +@(private, rodata) FE_D_MINUS_ONE_SQUARED := field.Tight_Field_Element { 1507062230895904, 1572317787530805, @@ -48,7 +48,7 @@ FE_D_MINUS_ONE_SQUARED := field.Tight_Field_Element { 317374165784489, 1572899562415810, } -@(private) +@(private, rodata) FE_SQRT_AD_MINUS_ONE := field.Tight_Field_Element { 2241493124984347, 425987919032274, @@ -76,7 +76,7 @@ ge_clear :: proc "contextless" (ge: ^Group_Element) { // ge_set sets `ge = a`. ge_set :: proc(ge, a: ^Group_Element) { - _ge_assert_initialized([]^Group_Element{a}) + _ge_ensure_initialized([]^Group_Element{a}) grp.ge_set(&ge._p, &a._p) ge._is_initialized = true @@ -199,9 +199,7 @@ ge_set_bytes :: proc "contextless" (ge: ^Group_Element, b: []byte) -> bool { // ge_set_wide_bytes sets ge to the result of deriving a ristretto255 // group element, from a wide (512-bit) byte string. ge_set_wide_bytes :: proc(ge: ^Group_Element, b: []byte) { - if len(b) != WIDE_ELEMENT_SIZE { - panic("crypto/ristretto255: invalid wide input size") - } + ensure(len(b) == WIDE_ELEMENT_SIZE, "crypto/ristretto255: invalid wide input size") // The element derivation function on an input string b proceeds as // follows: @@ -222,10 +220,8 @@ ge_set_wide_bytes :: proc(ge: ^Group_Element, b: []byte) { // ge_bytes sets dst to the canonical encoding of ge. ge_bytes :: proc(ge: ^Group_Element, dst: []byte) { - _ge_assert_initialized([]^Group_Element{ge}) - if len(dst) != ELEMENT_SIZE { - panic("crypto/ristretto255: invalid destination size") - } + _ge_ensure_initialized([]^Group_Element{ge}) + ensure(len(dst) == ELEMENT_SIZE, "crypto/ristretto255: invalid destination size") x0, y0, z0, t0 := &ge._p.x, &ge._p.y, &ge._p.z, &ge._p.t @@ -306,7 +302,7 @@ ge_bytes :: proc(ge: ^Group_Element, dst: []byte) { // ge_add sets `ge = a + b`. ge_add :: proc(ge, a, b: ^Group_Element) { - _ge_assert_initialized([]^Group_Element{a, b}) + _ge_ensure_initialized([]^Group_Element{a, b}) grp.ge_add(&ge._p, &a._p, &b._p) ge._is_initialized = true @@ -314,7 +310,7 @@ ge_add :: proc(ge, a, b: ^Group_Element) { // ge_double sets `ge = a + a`. ge_double :: proc(ge, a: ^Group_Element) { - _ge_assert_initialized([]^Group_Element{a}) + _ge_ensure_initialized([]^Group_Element{a}) grp.ge_double(&ge._p, &a._p) ge._is_initialized = true @@ -322,7 +318,7 @@ ge_double :: proc(ge, a: ^Group_Element) { // ge_negate sets `ge = -a`. ge_negate :: proc(ge, a: ^Group_Element) { - _ge_assert_initialized([]^Group_Element{a}) + _ge_ensure_initialized([]^Group_Element{a}) grp.ge_negate(&ge._p, &a._p) ge._is_initialized = true @@ -330,7 +326,7 @@ ge_negate :: proc(ge, a: ^Group_Element) { // ge_scalarmult sets `ge = A * sc`. ge_scalarmult :: proc(ge, A: ^Group_Element, sc: ^Scalar) { - _ge_assert_initialized([]^Group_Element{A}) + _ge_ensure_initialized([]^Group_Element{A}) grp.ge_scalarmult(&ge._p, &A._p, sc) ge._is_initialized = true @@ -344,7 +340,7 @@ ge_scalarmult_generator :: proc "contextless" (ge: ^Group_Element, sc: ^Scalar) // ge_scalarmult_vartime sets `ge = A * sc` in variable time. ge_scalarmult_vartime :: proc(ge, A: ^Group_Element, sc: ^Scalar) { - _ge_assert_initialized([]^Group_Element{A}) + _ge_ensure_initialized([]^Group_Element{A}) grp.ge_scalarmult_vartime(&ge._p, &A._p, sc) ge._is_initialized = true @@ -358,7 +354,7 @@ ge_double_scalarmult_generator_vartime :: proc( A: ^Group_Element, b: ^Scalar, ) { - _ge_assert_initialized([]^Group_Element{A}) + _ge_ensure_initialized([]^Group_Element{A}) grp.ge_double_scalarmult_basepoint_vartime(&ge._p, a, &A._p, b) ge._is_initialized = true @@ -367,7 +363,7 @@ ge_double_scalarmult_generator_vartime :: proc( // ge_cond_negate sets `ge = a` iff `ctrl == 0` and `ge = -a` iff `ctrl == 1`. // Behavior for all other values of ctrl are undefined, ge_cond_negate :: proc(ge, a: ^Group_Element, ctrl: int) { - _ge_assert_initialized([]^Group_Element{a}) + _ge_ensure_initialized([]^Group_Element{a}) grp.ge_cond_negate(&ge._p, &a._p, ctrl) ge._is_initialized = true @@ -376,7 +372,7 @@ ge_cond_negate :: proc(ge, a: ^Group_Element, ctrl: int) { // ge_cond_assign sets `ge = ge` iff `ctrl == 0` and `ge = a` iff `ctrl == 1`. // Behavior for all other values of ctrl are undefined, ge_cond_assign :: proc(ge, a: ^Group_Element, ctrl: int) { - _ge_assert_initialized([]^Group_Element{ge, a}) + _ge_ensure_initialized([]^Group_Element{ge, a}) grp.ge_cond_assign(&ge._p, &a._p, ctrl) } @@ -384,7 +380,7 @@ ge_cond_assign :: proc(ge, a: ^Group_Element, ctrl: int) { // ge_cond_select sets `ge = a` iff `ctrl == 0` and `ge = b` iff `ctrl == 1`. // Behavior for all other values of ctrl are undefined, ge_cond_select :: proc(ge, a, b: ^Group_Element, ctrl: int) { - _ge_assert_initialized([]^Group_Element{a, b}) + _ge_ensure_initialized([]^Group_Element{a, b}) grp.ge_cond_select(&ge._p, &a._p, &b._p, ctrl) ge._is_initialized = true @@ -393,7 +389,7 @@ ge_cond_select :: proc(ge, a, b: ^Group_Element, ctrl: int) { // ge_equal returns 1 iff `a == b`, and 0 otherwise. @(require_results) ge_equal :: proc(a, b: ^Group_Element) -> int { - _ge_assert_initialized([]^Group_Element{a, b}) + _ge_ensure_initialized([]^Group_Element{a, b}) // CT_EQ(x1 * y2, y1 * x2) | CT_EQ(y1 * y2, x1 * x2) ax_by, ay_bx, ay_by, ax_bx: field.Tight_Field_Element = ---, ---, ---, --- @@ -501,10 +497,8 @@ ge_map :: proc "contextless" (ge: ^Group_Element, b: []byte) { } @(private) -_ge_assert_initialized :: proc(ges: []^Group_Element) { +_ge_ensure_initialized :: proc(ges: []^Group_Element) { for ge in ges { - if !ge._is_initialized { - panic("crypto/ristretto255: uninitialized group element") - } + ensure(ge._is_initialized, "crypto/ristretto255: uninitialized group element") } } diff --git a/core/crypto/ristretto255/ristretto255_scalar.odin b/core/crypto/ristretto255/ristretto255_scalar.odin index 1ecb490e0..75844b3f4 100644 --- a/core/crypto/ristretto255/ristretto255_scalar.odin +++ b/core/crypto/ristretto255/ristretto255_scalar.odin @@ -42,9 +42,7 @@ sc_set_bytes :: proc(sc: ^Scalar, b: []byte) -> bool { // scalar, from a wide (512-bit) byte string by interpreting b as a // little-endian value, and reducing it mod the group order. sc_set_bytes_wide :: proc(sc: ^Scalar, b: []byte) { - if len(b) != WIDE_SCALAR_SIZE { - panic("crypto/ristretto255: invalid wide input size") - } + ensure(len(b) == WIDE_SCALAR_SIZE, "crypto/ristretto255: invalid wide input size") b_ := (^[WIDE_SCALAR_SIZE]byte)(raw_data(b)) grp.sc_set_bytes_wide(sc, b_) @@ -52,9 +50,7 @@ sc_set_bytes_wide :: proc(sc: ^Scalar, b: []byte) { // sc_bytes sets dst to the canonical encoding of sc. sc_bytes :: proc(sc: ^Scalar, dst: []byte) { - if len(dst) != SCALAR_SIZE { - panic("crypto/ristretto255: invalid destination size") - } + ensure(len(dst) == SCALAR_SIZE, "crypto/ristretto255: invalid destination size") grp.sc_bytes(dst, sc) } diff --git a/core/crypto/sha2/sha2.odin b/core/crypto/sha2/sha2.odin index 4230851ab..bf9b81601 100644 --- a/core/crypto/sha2/sha2.odin +++ b/core/crypto/sha2/sha2.odin @@ -15,9 +15,9 @@ package sha2 zhibog, dotbmp: Initial implementation. */ -import "core:encoding/endian" +@(require) import "core:encoding/endian" import "core:math/bits" -import "core:mem" +@(require) import "core:mem" // DIGEST_SIZE_224 is the SHA-224 digest size in bytes. DIGEST_SIZE_224 :: 28 @@ -158,7 +158,7 @@ _init :: proc(ctx: ^$T) { // update adds more data to the Context. update :: proc(ctx: ^$T, data: []byte) { - assert(ctx.is_initialized) + ensure(ctx.is_initialized) when T == Context_256 { CURR_BLOCK_SIZE :: BLOCK_SIZE_256 @@ -194,11 +194,8 @@ update :: proc(ctx: ^$T, data: []byte) { // Iff finalize_clone is set, final will work on a copy of the Context, // which is useful for for calculating rolling digests. final :: proc(ctx: ^$T, hash: []byte, finalize_clone: bool = false) { - assert(ctx.is_initialized) - - if len(hash) * 8 < ctx.md_bits { - panic("crypto/sha2: invalid destination digest size") - } + ensure(ctx.is_initialized) + ensure(len(hash) * 8 >= ctx.md_bits, "crypto/sha2: invalid destination digest size") ctx := ctx if finalize_clone { @@ -238,7 +235,7 @@ final :: proc(ctx: ^$T, hash: []byte, finalize_clone: bool = false) { endian.unchecked_put_u64be(pad[8:], length_lo) update(ctx, pad[0:16]) } - assert(ctx.bitlength == 0) + assert(ctx.bitlength == 0) // Check for bugs when T == Context_256 { for i := 0; i < ctx.md_bits / 32; i += 1 { @@ -270,8 +267,8 @@ reset :: proc(ctx: ^$T) { SHA2 implementation */ -@(private) -sha256_k := [64]u32 { +@(private, rodata) +SHA256_K := [64]u32 { 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, @@ -290,8 +287,8 @@ sha256_k := [64]u32 { 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, } -@(private) -sha512_k := [80]u64 { +@(private, rodata) +SHA512_K := [80]u64 { 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, 0x3956c25bf348b538, 0x59f111f1b605d019, @@ -335,6 +332,11 @@ sha512_k := [80]u64 { } @(private) +SHA256_ROUNDS :: 64 +@(private) +SHA512_ROUNDS :: 80 + +@(private) SHA256_CH :: #force_inline proc "contextless" (x, y, z: u32) -> u32 { return (x & y) ~ (~x & z) } @@ -395,22 +397,29 @@ SHA512_F4 :: #force_inline proc "contextless" (x: u64) -> u64 { } @(private) -sha2_transf :: proc "contextless" (ctx: ^$T, data: []byte) { +sha2_transf :: proc "contextless" (ctx: ^$T, data: []byte) #no_bounds_check { when T == Context_256 { - w: [64]u32 + if is_hardware_accelerated_256() { + sha256_transf_hw(ctx, data) + return + } + + w: [SHA256_ROUNDS]u32 wv: [8]u32 t1, t2: u32 + CURR_BLOCK_SIZE :: BLOCK_SIZE_256 } else when T == Context_512 { - w: [80]u64 + w: [SHA512_ROUNDS]u64 wv: [8]u64 t1, t2: u64 + CURR_BLOCK_SIZE :: BLOCK_SIZE_512 } data := data for len(data) >= CURR_BLOCK_SIZE { - for i := 0; i < 16; i += 1 { + for i in 0 ..< 16 { when T == Context_256 { w[i] = endian.unchecked_get_u32be(data[i * 4:]) } else when T == Context_512 { @@ -419,22 +428,22 @@ sha2_transf :: proc "contextless" (ctx: ^$T, data: []byte) { } when T == Context_256 { - for i := 16; i < 64; i += 1 { + for i in 16 ..< SHA256_ROUNDS { w[i] = SHA256_F4(w[i - 2]) + w[i - 7] + SHA256_F3(w[i - 15]) + w[i - 16] } } else when T == Context_512 { - for i := 16; i < 80; i += 1 { + for i in 16 ..< SHA512_ROUNDS { w[i] = SHA512_F4(w[i - 2]) + w[i - 7] + SHA512_F3(w[i - 15]) + w[i - 16] } } - for i := 0; i < 8; i += 1 { + for i in 0 ..< 8 { wv[i] = ctx.h[i] } when T == Context_256 { - for i := 0; i < 64; i += 1 { - t1 = wv[7] + SHA256_F2(wv[4]) + SHA256_CH(wv[4], wv[5], wv[6]) + sha256_k[i] + w[i] + for i in 0 ..< SHA256_ROUNDS { + t1 = wv[7] + SHA256_F2(wv[4]) + SHA256_CH(wv[4], wv[5], wv[6]) + SHA256_K[i] + w[i] t2 = SHA256_F1(wv[0]) + SHA256_MAJ(wv[0], wv[1], wv[2]) wv[7] = wv[6] wv[6] = wv[5] @@ -446,8 +455,8 @@ sha2_transf :: proc "contextless" (ctx: ^$T, data: []byte) { wv[0] = t1 + t2 } } else when T == Context_512 { - for i := 0; i < 80; i += 1 { - t1 = wv[7] + SHA512_F2(wv[4]) + SHA512_CH(wv[4], wv[5], wv[6]) + sha512_k[i] + w[i] + for i in 0 ..< SHA512_ROUNDS { + t1 = wv[7] + SHA512_F2(wv[4]) + SHA512_CH(wv[4], wv[5], wv[6]) + SHA512_K[i] + w[i] t2 = SHA512_F1(wv[0]) + SHA512_MAJ(wv[0], wv[1], wv[2]) wv[7] = wv[6] wv[6] = wv[5] @@ -460,7 +469,7 @@ sha2_transf :: proc "contextless" (ctx: ^$T, data: []byte) { } } - for i := 0; i < 8; i += 1 { + for i in 0 ..< 8 { ctx.h[i] += wv[i] } diff --git a/core/crypto/sha2/sha2_impl_hw_gen.odin b/core/crypto/sha2/sha2_impl_hw_gen.odin new file mode 100644 index 000000000..85c7f8b28 --- /dev/null +++ b/core/crypto/sha2/sha2_impl_hw_gen.odin @@ -0,0 +1,15 @@ +#+build !amd64 +package sha2 + +@(private = "file") +ERR_HW_NOT_SUPPORTED :: "crypto/sha2: hardware implementation unsupported" + +// is_hardware_accelerated_256 returns true iff hardware accelerated +// SHA-224/SHA-256 is supported. +is_hardware_accelerated_256 :: proc "contextless" () -> bool { + return false +} + +sha256_transf_hw :: proc "contextless" (ctx: ^Context_256, data: []byte) { + panic_contextless(ERR_HW_NOT_SUPPORTED) +} diff --git a/core/crypto/sha2/sha2_impl_hw_intel.odin b/core/crypto/sha2/sha2_impl_hw_intel.odin new file mode 100644 index 000000000..cb29a3a20 --- /dev/null +++ b/core/crypto/sha2/sha2_impl_hw_intel.odin @@ -0,0 +1,260 @@ +#+build amd64 +package sha2 + +// Based on the public domain code by Jeffrey Walton, though +// realistically, there only is one sensible way to write this +// and Intel's whitepaper covers it. +// +// See: https://github.com/noloader/SHA-Intrinsics + +import "base:intrinsics" +import "core:simd" +import "core:simd/x86" +import "core:sys/info" + +@(private = "file") +MASK :: x86.__m128i{0x0405060700010203, 0x0c0d0e0f08090a0b} + +@(private = "file") +K_0 :: simd.u64x2{0x71374491428a2f98, 0xe9b5dba5b5c0fbcf} +@(private = "file") +K_1 :: simd.u64x2{0x59f111f13956c25b, 0xab1c5ed5923f82a4} +@(private = "file") +K_2 :: simd.u64x2{0x12835b01d807aa98, 0x550c7dc3243185be} +@(private = "file") +K_3 :: simd.u64x2{0x80deb1fe72be5d74, 0xc19bf1749bdc06a7} +@(private = "file") +K_4 :: simd.u64x2{0xefbe4786e49b69c1, 0x240ca1cc0fc19dc6} +@(private = "file") +K_5 :: simd.u64x2{0x4a7484aa2de92c6f, 0x76f988da5cb0a9dc} +@(private = "file") +K_6 :: simd.u64x2{0xa831c66d983e5152, 0xbf597fc7b00327c8} +@(private = "file") +K_7 :: simd.u64x2{0xd5a79147c6e00bf3, 0x1429296706ca6351} +@(private = "file") +K_8 :: simd.u64x2{0x2e1b213827b70a85, 0x53380d134d2c6dfc} +@(private = "file") +K_9 :: simd.u64x2{0x766a0abb650a7354, 0x92722c8581c2c92e} +@(private = "file") +K_10 :: simd.u64x2{0xa81a664ba2bfe8a1, 0xc76c51a3c24b8b70} +@(private = "file") +K_11 :: simd.u64x2{0xd6990624d192e819, 0x106aa070f40e3585} +@(private = "file") +K_12 :: simd.u64x2{0x1e376c0819a4c116, 0x34b0bcb52748774c} +@(private = "file") +K_13 :: simd.u64x2{0x4ed8aa4a391c0cb3, 0x682e6ff35b9cca4f} +@(private = "file") +K_14 :: simd.u64x2{0x78a5636f748f82ee, 0x8cc7020884c87814} +@(private = "file") +K_15 :: simd.u64x2{0xa4506ceb90befffa, 0xc67178f2bef9a3f7} + + +// is_hardware_accelerated_256 returns true iff hardware accelerated +// SHA-224/SHA-256 is supported. +is_hardware_accelerated_256 :: proc "contextless" () -> bool { + features, ok := info.cpu.features.? + if !ok { + return false + } + + req_features :: info.CPU_Features{ + .sse2, + .ssse3, + .sse41, + .sha, + } + return features >= req_features +} + +@(private, enable_target_feature="sse2,ssse3,sse4.1,sha") +sha256_transf_hw :: proc "contextless" (ctx: ^Context_256, data: []byte) #no_bounds_check { + // Load the state + tmp := intrinsics.unaligned_load((^x86.__m128i)(&ctx.h[0])) + state_1 := intrinsics.unaligned_load((^x86.__m128i)(&ctx.h[4])) + + tmp = x86._mm_shuffle_epi32(tmp, 0xb1) // CDAB + state_1 = x86._mm_shuffle_epi32(state_1, 0x1b) // EFGH + state_0 := x86._mm_alignr_epi8(tmp, state_1, 8) // ABEF + // state_1 = x86._mm_blend_epi16(state_1, tmp, 0xf0) // CDGH + state_1 = kludge_mm_blend_epi16_0xf0(state_1, tmp) + + data := data + for len(data) >= BLOCK_SIZE_256 { + state_0_save, state_1_save := state_0, state_1 + + // Rounds 0-3 + msg := intrinsics.unaligned_load((^x86.__m128i)(raw_data(data))) + msg_0 := x86._mm_shuffle_epi8(msg, MASK) + msg = x86._mm_add_epi32(msg_0, x86.__m128i(K_0)) + state_1 = x86._mm_sha256rnds2_epu32(state_1, state_0, msg) + msg = x86._mm_shuffle_epi32(msg, 0xe) + state_0 = x86._mm_sha256rnds2_epu32(state_0, state_1, msg) + + // Rounds 4-7 + msg_1 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(data[16:]))) + msg_1 = x86._mm_shuffle_epi8(msg_1, MASK) + msg = x86._mm_add_epi32(msg_1, x86.__m128i(K_1)) + state_1 = x86._mm_sha256rnds2_epu32(state_1, state_0, msg) + msg = x86._mm_shuffle_epi32(msg, 0xe) + state_0 = x86._mm_sha256rnds2_epu32(state_0, state_1, msg) + msg_0 = x86._mm_sha256msg1_epu32(msg_0, msg_1) + + // Rounds 8-11 + msg_2 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(data[32:]))) + msg_2 = x86._mm_shuffle_epi8(msg_2, MASK) + msg = x86._mm_add_epi32(msg_2, x86.__m128i(K_2)) + state_1 = x86._mm_sha256rnds2_epu32(state_1, state_0, msg) + msg = x86._mm_shuffle_epi32(msg, 0xe) + state_0 = x86._mm_sha256rnds2_epu32(state_0, state_1, msg) + msg_1 = x86._mm_sha256msg1_epu32(msg_1, msg_2) + + // Rounds 12-15 + msg_3 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(data[48:]))) + msg_3 = x86._mm_shuffle_epi8(msg_3, MASK) + msg = x86._mm_add_epi32(msg_3, x86.__m128i(K_3)) + state_1 = x86._mm_sha256rnds2_epu32(state_1, state_0, msg) + tmp = x86._mm_alignr_epi8(msg_3, msg_2, 4) + msg_0 = x86._mm_add_epi32(msg_0, tmp) + msg_0 = x86._mm_sha256msg2_epu32(msg_0, msg_3) + msg = x86._mm_shuffle_epi32(msg, 0x0e) + state_0 = x86._mm_sha256rnds2_epu32(state_0, state_1, msg) + msg_2 = x86._mm_sha256msg1_epu32(msg_2, msg_3) + + // Rounds 16-19 + msg = x86._mm_add_epi32(msg_0, x86.__m128i(K_4)) + state_1 = x86._mm_sha256rnds2_epu32(state_1, state_0, msg) + tmp = x86._mm_alignr_epi8(msg_0, msg_3, 4) + msg_1 = x86._mm_add_epi32(msg_1, tmp) + msg_1 = x86._mm_sha256msg2_epu32(msg_1, msg_0) + msg = x86._mm_shuffle_epi32(msg, 0x0e) + state_0 = x86._mm_sha256rnds2_epu32(state_0, state_1, msg) + msg_3 = x86._mm_sha256msg1_epu32(msg_3, msg_0) + + // Rounds 20-23 + msg = x86._mm_add_epi32(msg_1, x86.__m128i(K_5)) + state_1 = x86._mm_sha256rnds2_epu32(state_1, state_0, msg) + tmp = x86._mm_alignr_epi8(msg_1, msg_0, 4) + msg_2 = x86._mm_add_epi32(msg_2, tmp) + msg_2 = x86._mm_sha256msg2_epu32(msg_2, msg_1) + msg = x86._mm_shuffle_epi32(msg, 0x0e) + state_0 = x86._mm_sha256rnds2_epu32(state_0, state_1, msg) + msg_0 = x86._mm_sha256msg1_epu32(msg_0, msg_1) + + // Rounds 24-27 + msg = x86._mm_add_epi32(msg_2, x86.__m128i(K_6)) + state_1 = x86._mm_sha256rnds2_epu32(state_1, state_0, msg) + tmp = x86._mm_alignr_epi8(msg_2, msg_1, 4) + msg_3 = x86._mm_add_epi32(msg_3, tmp) + msg_3 = x86._mm_sha256msg2_epu32(msg_3, msg_2) + msg = x86._mm_shuffle_epi32(msg, 0x0e) + state_0 = x86._mm_sha256rnds2_epu32(state_0, state_1, msg) + msg_1 = x86._mm_sha256msg1_epu32(msg_1, msg_2) + + // Rounds 28-31 + msg = x86._mm_add_epi32(msg_3, x86.__m128i(K_7)) + state_1 = x86._mm_sha256rnds2_epu32(state_1, state_0, msg) + tmp = x86._mm_alignr_epi8(msg_3, msg_2, 4) + msg_0 = x86._mm_add_epi32(msg_0, tmp) + msg_0 = x86._mm_sha256msg2_epu32(msg_0, msg_3) + msg = x86._mm_shuffle_epi32(msg, 0x0e) + state_0 = x86._mm_sha256rnds2_epu32(state_0, state_1, msg) + msg_2 = x86._mm_sha256msg1_epu32(msg_2, msg_3) + + // Rounds 32-35 + msg = x86._mm_add_epi32(msg_0, x86.__m128i(K_8)) + state_1 = x86._mm_sha256rnds2_epu32(state_1, state_0, msg) + tmp = x86._mm_alignr_epi8(msg_0, msg_3, 4) + msg_1 = x86._mm_add_epi32(msg_1, tmp) + msg_1 = x86._mm_sha256msg2_epu32(msg_1, msg_0) + msg = x86._mm_shuffle_epi32(msg, 0x0e) + state_0 = x86._mm_sha256rnds2_epu32(state_0, state_1, msg) + msg_3 = x86._mm_sha256msg1_epu32(msg_3, msg_0) + + // Rounds 36-39 + msg = x86._mm_add_epi32(msg_1, x86.__m128i(K_9)) + state_1 = x86._mm_sha256rnds2_epu32(state_1, state_0, msg) + tmp = x86._mm_alignr_epi8(msg_1, msg_0, 4) + msg_2 = x86._mm_add_epi32(msg_2, tmp) + msg_2 = x86._mm_sha256msg2_epu32(msg_2, msg_1) + msg = x86._mm_shuffle_epi32(msg, 0x0e) + state_0 = x86._mm_sha256rnds2_epu32(state_0, state_1, msg) + msg_0 = x86._mm_sha256msg1_epu32(msg_0, msg_1) + + // Rounds 40-43 + msg = x86._mm_add_epi32(msg_2, x86.__m128i(K_10)) + state_1 = x86._mm_sha256rnds2_epu32(state_1, state_0, msg) + tmp = x86._mm_alignr_epi8(msg_2, msg_1, 4) + msg_3 = x86._mm_add_epi32(msg_3, tmp) + msg_3 = x86._mm_sha256msg2_epu32(msg_3, msg_2) + msg = x86._mm_shuffle_epi32(msg, 0x0e) + state_0 = x86._mm_sha256rnds2_epu32(state_0, state_1, msg) + msg_1 = x86._mm_sha256msg1_epu32(msg_1, msg_2) + + // Rounds 44-47 + msg = x86._mm_add_epi32(msg_3, x86.__m128i(K_11)) + state_1 = x86._mm_sha256rnds2_epu32(state_1, state_0, msg) + tmp = x86._mm_alignr_epi8(msg_3, msg_2, 4) + msg_0 = x86._mm_add_epi32(msg_0, tmp) + msg_0 = x86._mm_sha256msg2_epu32(msg_0, msg_3) + msg = x86._mm_shuffle_epi32(msg, 0x0e) + state_0 = x86._mm_sha256rnds2_epu32(state_0, state_1, msg) + msg_2 = x86._mm_sha256msg1_epu32(msg_2, msg_3) + + // Rounds 48-51 + msg = x86._mm_add_epi32(msg_0, x86.__m128i(K_12)) + state_1 = x86._mm_sha256rnds2_epu32(state_1, state_0, msg) + tmp = x86._mm_alignr_epi8(msg_0, msg_3, 4) + msg_1 = x86._mm_add_epi32(msg_1, tmp) + msg_1 = x86._mm_sha256msg2_epu32(msg_1, msg_0) + msg = x86._mm_shuffle_epi32(msg, 0x0e) + state_0 = x86._mm_sha256rnds2_epu32(state_0, state_1, msg) + msg_3 = x86._mm_sha256msg1_epu32(msg_3, msg_0) + + // Rounds 52-55 + msg = x86._mm_add_epi32(msg_1, x86.__m128i(K_13)) + state_1 = x86._mm_sha256rnds2_epu32(state_1, state_0, msg) + tmp = x86._mm_alignr_epi8(msg_1, msg_0, 4) + msg_2 = x86._mm_add_epi32(msg_2, tmp) + msg_2 = x86._mm_sha256msg2_epu32(msg_2, msg_1) + msg = x86._mm_shuffle_epi32(msg, 0x0e) + state_0 = x86._mm_sha256rnds2_epu32(state_0, state_1, msg) + + /* Rounds 56-59 */ + msg = x86._mm_add_epi32(msg_2, x86.__m128i(K_14)) + state_1 = x86._mm_sha256rnds2_epu32(state_1, state_0, msg) + tmp = x86._mm_alignr_epi8(msg_2, msg_1, 4) + msg_3 = x86._mm_add_epi32(msg_3, tmp) + msg_3 = x86._mm_sha256msg2_epu32(msg_3, msg_2) + msg = x86._mm_shuffle_epi32(msg, 0x0e) + state_0 = x86._mm_sha256rnds2_epu32(state_0, state_1, msg) + + // Rounds 60-63 + msg = x86._mm_add_epi32(msg_3, x86.__m128i(K_15)) + state_1 = x86._mm_sha256rnds2_epu32(state_1, state_0, msg) + msg = x86._mm_shuffle_epi32(msg, 0x0e) + state_0 = x86._mm_sha256rnds2_epu32(state_0, state_1, msg) + + state_0 = x86._mm_add_epi32(state_0, state_0_save) + state_1 = x86._mm_add_epi32(state_1, state_1_save) + + data = data[BLOCK_SIZE_256:] + } + + // Write back the updated state + tmp = x86._mm_shuffle_epi32(state_0, 0x1b) // FEBA + state_1 = x86._mm_shuffle_epi32(state_1, 0xb1) // DCHG + // state_0 = x86._mm_blend_epi16(tmp, state_1, 0xf0) // DCBA + state_0 = kludge_mm_blend_epi16_0xf0(tmp, state_1) + state_1 = x86._mm_alignr_epi8(state_1, tmp, 8) // ABEF + + intrinsics.unaligned_store((^x86.__m128i)(&ctx.h[0]), state_0) + intrinsics.unaligned_store((^x86.__m128i)(&ctx.h[4]), state_1) +} + +@(private = "file") +kludge_mm_blend_epi16_0xf0 :: #force_inline proc "contextless"(a, b: x86.__m128i) -> x86.__m128i { + // HACK HACK HACK: LLVM got rid of `llvm.x86.sse41.pblendw`. + a_ := simd.to_array(a) + b_ := simd.to_array(b) + return x86.__m128i{a_[0], b_[1]} +} diff --git a/core/crypto/siphash/siphash.odin b/core/crypto/siphash/siphash.odin index c145ab3f0..f9fe50cb0 100644 --- a/core/crypto/siphash/siphash.odin +++ b/core/crypto/siphash/siphash.odin @@ -219,18 +219,14 @@ verify_4_8 :: proc { */ init :: proc(ctx: ^Context, key: []byte, c_rounds, d_rounds: int) { - if len(key) != KEY_SIZE { - panic("crypto/siphash; invalid key size") - } + ensure(len(key) == KEY_SIZE,"crypto/siphash; invalid key size") ctx.c_rounds = c_rounds ctx.d_rounds = d_rounds is_valid_setting := (ctx.c_rounds == 1 && ctx.d_rounds == 3) || (ctx.c_rounds == 2 && ctx.d_rounds == 4) || (ctx.c_rounds == 4 && ctx.d_rounds == 8) - if !is_valid_setting { - panic("crypto/siphash: incorrect rounds set up") - } + ensure(is_valid_setting, "crypto/siphash: incorrect rounds set up") ctx.k0 = endian.unchecked_get_u64le(key[:8]) ctx.k1 = endian.unchecked_get_u64le(key[8:]) ctx.v0 = 0x736f6d6570736575 ~ ctx.k0 @@ -245,7 +241,7 @@ init :: proc(ctx: ^Context, key: []byte, c_rounds, d_rounds: int) { } update :: proc(ctx: ^Context, data: []byte) { - assert(ctx.is_initialized, "crypto/siphash: context is not initialized") + ensure(ctx.is_initialized) data := data ctx.total_length += len(data) @@ -269,7 +265,7 @@ update :: proc(ctx: ^Context, data: []byte) { } final :: proc(ctx: ^Context, dst: ^u64) { - assert(ctx.is_initialized, "crypto/siphash: context is not initialized") + ensure(ctx.is_initialized) tmp: [BLOCK_SIZE]byte copy(tmp[:], ctx.buf[:ctx.last_block]) @@ -336,9 +332,8 @@ _get_byte :: #force_inline proc "contextless" (byte_num: byte, into: u64) -> byt @(private) _collect_output :: #force_inline proc(dst: []byte, hash: u64) { - if len(dst) < DIGEST_SIZE { - panic("crypto/siphash: invalid tag size") - } + ensure(len(dst) >= DIGEST_SIZE, "crypto/siphash: invalid tag size") + dst[0] = _get_byte(7, hash) dst[1] = _get_byte(6, hash) dst[2] = _get_byte(5, hash) diff --git a/core/crypto/sm3/sm3.odin b/core/crypto/sm3/sm3.odin index f910d735b..6487c5e8c 100644 --- a/core/crypto/sm3/sm3.odin +++ b/core/crypto/sm3/sm3.odin @@ -53,7 +53,7 @@ init :: proc(ctx: ^Context) { // update adds more data to the Context. update :: proc(ctx: ^Context, data: []byte) { - assert(ctx.is_initialized) + ensure(ctx.is_initialized) data := data ctx.length += u64(len(data)) @@ -83,11 +83,8 @@ update :: proc(ctx: ^Context, data: []byte) { // Iff finalize_clone is set, final will work on a copy of the Context, // which is useful for for calculating rolling digests. final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) { - assert(ctx.is_initialized) - - if len(hash) < DIGEST_SIZE { - panic("crypto/sm3: invalid destination digest size") - } + ensure(ctx.is_initialized) + ensure(len(hash) >= DIGEST_SIZE, "crypto/sm3: invalid destination digest size") ctx := ctx if finalize_clone { @@ -110,7 +107,7 @@ final :: proc(ctx: ^Context, hash: []byte, finalize_clone: bool = false) { length <<= 3 endian.unchecked_put_u64be(pad[:], length) update(ctx, pad[0:8]) - assert(ctx.bitlength == 0) + assert(ctx.bitlength == 0) // Check for bugs for i := 0; i < DIGEST_SIZE / 4; i += 1 { endian.unchecked_put_u32be(hash[i * 4:], ctx.state[i]) @@ -136,7 +133,7 @@ reset :: proc(ctx: ^Context) { SM3 implementation */ -@(private) +@(private, rodata) IV := [8]u32 { 0x7380166f, 0x4914b2b9, 0x172442d7, 0xda8a0600, 0xa96f30bc, 0x163138aa, 0xe38dee4d, 0xb0fb0e4e, diff --git a/core/crypto/x25519/x25519.odin b/core/crypto/x25519/x25519.odin index 412a767b8..6805c3ff8 100644 --- a/core/crypto/x25519/x25519.odin +++ b/core/crypto/x25519/x25519.odin @@ -15,7 +15,7 @@ SCALAR_SIZE :: 32 // POINT_SIZE is the size of a X25519 point (public key/shared secret) in bytes. POINT_SIZE :: 32 -@(private) +@(private, rodata) _BASE_POINT: [32]byte = {9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} @(private) @@ -101,15 +101,9 @@ _scalarmult :: proc "contextless" (out, scalar, point: ^[32]byte) { // scalarmult "multiplies" the provided scalar and point, and writes the // resulting point to dst. scalarmult :: proc(dst, scalar, point: []byte) { - if len(scalar) != SCALAR_SIZE { - panic("crypto/x25519: invalid scalar size") - } - if len(point) != POINT_SIZE { - panic("crypto/x25519: invalid point size") - } - if len(dst) != POINT_SIZE { - panic("crypto/x25519: invalid destination point size") - } + ensure(len(scalar) == SCALAR_SIZE, "crypto/x25519: invalid scalar size") + ensure(len(point) == POINT_SIZE, "crypto/x25519: invalid point size") + ensure(len(dst) == POINT_SIZE, "crypto/x25519: invalid destination point size") // "clamp" the scalar e: [32]byte = --- diff --git a/core/crypto/x448/x448.odin b/core/crypto/x448/x448.odin new file mode 100644 index 000000000..43c5d25e0 --- /dev/null +++ b/core/crypto/x448/x448.odin @@ -0,0 +1,155 @@ +/* +package x448 implements the X448 (aka curve448) Elliptic-Curve +Diffie-Hellman key exchange protocol. + +See: +- [[ https://www.rfc-editor.org/rfc/rfc7748 ]] +*/ +package x448 + +import field "core:crypto/_fiat/field_curve448" +import "core:mem" + +// SCALAR_SIZE is the size of a X448 scalar (private key) in bytes. +SCALAR_SIZE :: 56 +// POINT_SIZE is the size of a X448 point (public key/shared secret) in bytes. +POINT_SIZE :: 56 + +@(private, rodata) +_BASE_POINT: [56]byte = { + 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +} + +@(private) +_scalar_bit :: #force_inline proc "contextless" (s: ^[56]byte, i: int) -> u8 { + if i < 0 { + return 0 + } + return (s[i >> 3] >> uint(i & 7)) & 1 +} + +@(private) +_scalarmult :: proc "contextless" (out, scalar, point: ^[56]byte) { + // Montgomery pseudo-multiplication, using the RFC 7748 formula. + t1, t2: field.Loose_Field_Element = ---, --- + + // x_1 = u + // x_2 = 1 + // z_2 = 0 + // x_3 = u + // z_3 = 1 + x1: field.Tight_Field_Element = --- + field.fe_from_bytes(&x1, point) + + x2, x3, z2, z3: field.Tight_Field_Element = ---, ---, ---, --- + field.fe_one(&x2) + field.fe_zero(&z2) + field.fe_set(&x3, &x1) + field.fe_one(&z3) + + // swap = 0 + swap: int + + // For t = bits-1 down to 0:a + for t := 448 - 1; t >= 0; t -= 1 { + // k_t = (k >> t) & 1 + k_t := int(_scalar_bit(scalar, t)) + // swap ^= k_t + swap ~= k_t + // Conditional swap; see text below. + // (x_2, x_3) = cswap(swap, x_2, x_3) + field.fe_cond_swap(&x2, &x3, swap) + // (z_2, z_3) = cswap(swap, z_2, z_3) + field.fe_cond_swap(&z2, &z3, swap) + // swap = k_t + swap = k_t + + // Note: This deliberately omits reductions after add/sub operations + // if the result is only ever used as the input to a mul/square since + // the implementations of those can deal with non-reduced inputs. + // + // fe_tighten_cast is only used to store a fully reduced + // output in a Loose_Field_Element, or to provide such a + // Loose_Field_Element as a Tight_Field_Element argument. + + // A = x_2 + z_2 + field.fe_add(&t1, &x2, &z2) + // B = x_2 - z_2 + field.fe_sub(&t2, &x2, &z2) + // D = x_3 - z_3 + field.fe_sub(field.fe_relax_cast(&z2), &x3, &z3) // (z2 unreduced) + // DA = D * A + field.fe_carry_mul(&x2, field.fe_relax_cast(&z2), &t1) + // C = x_3 + z_3 + field.fe_add(field.fe_relax_cast(&z3), &x3, &z3) // (z3 unreduced) + // CB = C * B + field.fe_carry_mul(&x3, &t2, field.fe_relax_cast(&z3)) + // z_3 = x_1 * (DA - CB)^2 + field.fe_sub(field.fe_relax_cast(&z3), &x2, &x3) // (z3 unreduced) + field.fe_carry_square(&z3, field.fe_relax_cast(&z3)) + field.fe_carry_mul(&z3, field.fe_relax_cast(&x1), field.fe_relax_cast(&z3)) + // x_3 = (DA + CB)^2 + field.fe_add(field.fe_relax_cast(&z2), &x2, &x3) // (z2 unreduced) + field.fe_carry_square(&x3, field.fe_relax_cast(&z2)) + + // AA = A^2 + field.fe_carry_square(&z2, &t1) + // BB = B^2 + field.fe_carry_square(field.fe_tighten_cast(&t1), &t2) // (t1 reduced) + // x_2 = AA * BB + field.fe_carry_mul(&x2, field.fe_relax_cast(&z2), &t1) + // E = AA - BB + field.fe_sub(&t2, &z2, field.fe_tighten_cast(&t1)) // (t1 (input) is reduced) + // z_2 = E * (AA + a24 * E) + field.fe_carry_mul_small(field.fe_tighten_cast(&t1), &t2, 39081) // (t1 reduced) + field.fe_add(&t1, &z2, field.fe_tighten_cast(&t1)) // (t1 (input) is reduced) + field.fe_carry_mul(&z2, &t2, &t1) + } + + // Conditional swap; see text below. + // (x_2, x_3) = cswap(swap, x_2, x_3) + field.fe_cond_swap(&x2, &x3, swap) + // (z_2, z_3) = cswap(swap, z_2, z_3) + field.fe_cond_swap(&z2, &z3, swap) + + // Return x_2 * (z_2^(p - 2)) + field.fe_carry_inv(&z2, field.fe_relax_cast(&z2)) + field.fe_carry_mul(&x2, field.fe_relax_cast(&x2), field.fe_relax_cast(&z2)) + field.fe_to_bytes(out, &x2) + + field.fe_clear_vec([]^field.Tight_Field_Element{&x1, &x2, &x3, &z2, &z3}) + field.fe_clear_vec([]^field.Loose_Field_Element{&t1, &t2}) +} + +// scalarmult "multiplies" the provided scalar and point, and writes the +// resulting point to dst. +scalarmult :: proc(dst, scalar, point: []byte) { + ensure(len(scalar) == SCALAR_SIZE, "crypto/x448: invalid scalar size") + ensure(len(point) == POINT_SIZE, "crypto/x448: invalid point size") + ensure(len(dst) == POINT_SIZE, "crypto/x448: invalid destination point size") + + // "clamp" the scalar + e: [56]byte = --- + copy_slice(e[:], scalar) + e[0] &= 252 + e[55] |= 128 + + p: [56]byte = --- + copy_slice(p[:], point) + + d: [56]byte = --- + _scalarmult(&d, &e, &p) + copy_slice(dst, d[:]) + + mem.zero_explicit(&e, size_of(e)) + mem.zero_explicit(&d, size_of(d)) +} + +// scalarmult_basepoint "multiplies" the provided scalar with the X448 +// base point and writes the resulting point to dst. +scalarmult_basepoint :: proc(dst, scalar: []byte) { + scalarmult(dst, scalar, _BASE_POINT[:]) +} diff --git a/core/debug/trace/trace_windows.odin b/core/debug/trace/trace_windows.odin index c9868e338..04e92f125 100644 --- a/core/debug/trace/trace_windows.odin +++ b/core/debug/trace/trace_windows.odin @@ -49,10 +49,12 @@ _resolve :: proc(ctx: ^Context, frame: Frame, allocator: runtime.Allocator) -> ( data: [size_of(win32.SYMBOL_INFOW) + size_of([256]win32.WCHAR)]byte symbol := (^win32.SYMBOL_INFOW)(&data[0]) - symbol.SizeOfStruct = size_of(symbol) + // The value of SizeOfStruct must be the size of the whole struct, + // not just the size of the pointer + symbol.SizeOfStruct = size_of(symbol^) symbol.MaxNameLen = 255 if win32.SymFromAddrW(ctx.impl.hProcess, win32.DWORD64(frame), &{}, symbol) { - fl.procedure, _ = win32.wstring_to_utf8(&symbol.Name[0], -1, allocator) + fl.procedure, _ = win32.wstring_to_utf8(cstring16(&symbol.Name[0]), -1, allocator) } else { fl.procedure = fmt.aprintf("(procedure: 0x%x)", frame, allocator=allocator) } diff --git a/core/dynlib/lb_haiku.odin b/core/dynlib/lb_haiku.odin new file mode 100644 index 000000000..79e05505a --- /dev/null +++ b/core/dynlib/lb_haiku.odin @@ -0,0 +1,23 @@ +#+build haiku +#+private +package dynlib + +import "base:runtime" + +_LIBRARY_FILE_EXTENSION :: "" + +_load_library :: proc(path: string, global_symbols: bool, allocator: runtime.Allocator) -> (Library, bool) { + return nil, false +} + +_unload_library :: proc(library: Library) -> bool { + return false +} + +_symbol_address :: proc(library: Library, symbol: string, allocator: runtime.Allocator) -> (ptr: rawptr, found: bool) { + return nil, false +} + +_last_error :: proc() -> string { + return "" +} diff --git a/core/dynlib/lib.odin b/core/dynlib/lib.odin index cd9eed5f8..e6b39be4b 100644 --- a/core/dynlib/lib.odin +++ b/core/dynlib/lib.odin @@ -145,6 +145,11 @@ initialize_symbols :: proc( } } + // No field for it in the struct. + if handle == nil { + handle = load_library(library_path) or_return + } + // Buffer to concatenate the prefix + symbol name. prefixed_symbol_buf: [2048]u8 = --- diff --git a/core/dynlib/lib_windows.odin b/core/dynlib/lib_windows.odin index 05cd2cb3c..95372dac6 100644 --- a/core/dynlib/lib_windows.odin +++ b/core/dynlib/lib_windows.odin @@ -13,7 +13,7 @@ _LIBRARY_FILE_EXTENSION :: "dll" _load_library :: proc(path: string, global_symbols: bool, allocator: runtime.Allocator) -> (Library, bool) { // NOTE(bill): 'global_symbols' is here only for consistency with POSIX which has RTLD_GLOBAL wide_path := win32.utf8_to_wstring(path, allocator) - defer free(wide_path, allocator) + defer free(rawptr(wide_path), allocator) handle := cast(Library)win32.LoadLibraryW(wide_path) return handle, handle != nil } diff --git a/core/encoding/base32/base32_test.odin b/core/encoding/base32/base32_test.odin index ea41ae36f..07d5c8080 100644 --- a/core/encoding/base32/base32_test.odin +++ b/core/encoding/base32/base32_test.odin @@ -1,3 +1,4 @@ +#+test package encoding_base32 import "core:testing" diff --git a/core/encoding/cbor/cbor.odin b/core/encoding/cbor/cbor.odin index 8eb829ed3..1fb7c34ab 100644 --- a/core/encoding/cbor/cbor.odin +++ b/core/encoding/cbor/cbor.odin @@ -385,17 +385,17 @@ to_diagnostic_format_writer :: proc(w: io.Writer, val: Value, padding := 0) -> i // which we want for the diagnostic format. case f16: buf: [64]byte - str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f16), 8*size_of(f16)) + str := strconv.write_float(buf[:], f64(v), 'f', 2*size_of(f16), 8*size_of(f16)) if str[0] == '+' && str != "+Inf" { str = str[1:] } io.write_string(w, str) or_return case f32: buf: [128]byte - str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f32), 8*size_of(f32)) + str := strconv.write_float(buf[:], f64(v), 'f', 2*size_of(f32), 8*size_of(f32)) if str[0] == '+' && str != "+Inf" { str = str[1:] } io.write_string(w, str) or_return case f64: buf: [256]byte - str := strconv.append_float(buf[:], f64(v), 'f', 2*size_of(f64), 8*size_of(f64)) + str := strconv.write_float(buf[:], f64(v), 'f', 2*size_of(f64), 8*size_of(f64)) if str[0] == '+' && str != "+Inf" { str = str[1:] } io.write_string(w, str) or_return diff --git a/core/encoding/cbor/marshal.odin b/core/encoding/cbor/marshal.odin index aca71deb2..b23087c90 100644 --- a/core/encoding/cbor/marshal.odin +++ b/core/encoding/cbor/marshal.odin @@ -612,6 +612,42 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er case: panic("unknown bit_size size") } + case runtime.Type_Info_Matrix: + count := info.column_count * info.elem_stride + err_conv(_encode_u64(e, u64(count), .Array)) or_return + + if impl, ok := _tag_implementations_type[info.elem.id]; ok { + for i in 0..<count { + data := uintptr(v.data) + uintptr(i*info.elem_size) + impl->marshal(e, any{rawptr(data), info.elem.id}) or_return + } + return + } + + elem_ti := runtime.type_info_core(type_info_of(info.elem.id)) + for i in 0..<count { + data := uintptr(v.data) + uintptr(i*info.elem_size) + _marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return + } + return + + case runtime.Type_Info_Simd_Vector: + err_conv(_encode_u64(e, u64(info.count), .Array)) or_return + + if impl, ok := _tag_implementations_type[info.elem.id]; ok { + for i in 0..<info.count { + data := uintptr(v.data) + uintptr(i*info.elem_size) + impl->marshal(e, any{rawptr(data), info.elem.id}) or_return + } + return + } + + elem_ti := runtime.type_info_core(type_info_of(info.elem.id)) + for i in 0..<info.count { + data := uintptr(v.data) + uintptr(i*info.elem_size) + _marshal_into_encoder(e, any{rawptr(data), info.elem.id}, elem_ti) or_return + } + return } return _unsupported(v.id, nil) diff --git a/core/encoding/cbor/tags.odin b/core/encoding/cbor/tags.odin index 17420af46..be07b926a 100644 --- a/core/encoding/cbor/tags.odin +++ b/core/encoding/cbor/tags.odin @@ -82,14 +82,16 @@ _tag_implementations_id: map[string]Tag_Implementation _tag_implementations_type: map[typeid]Tag_Implementation // Register a custom tag implementation to be used when marshalling that type and unmarshalling that tag number. -tag_register_type :: proc(impl: Tag_Implementation, nr: Tag_Number, type: typeid) { +tag_register_type :: proc "contextless" (impl: Tag_Implementation, nr: Tag_Number, type: typeid) { + context = runtime.default_context() _tag_implementations_nr[nr] = impl _tag_implementations_type[type] = impl } // Register a custom tag implementation to be used when marshalling that tag number or marshalling // a field with the struct tag `cbor_tag:"nr"`. -tag_register_number :: proc(impl: Tag_Implementation, nr: Tag_Number, id: string) { +tag_register_number :: proc "contextless" (impl: Tag_Implementation, nr: Tag_Number, id: string) { + context = runtime.default_context() _tag_implementations_nr[nr] = impl _tag_implementations_id[id] = impl } @@ -98,13 +100,13 @@ tag_register_number :: proc(impl: Tag_Implementation, nr: Tag_Number, id: string INITIALIZE_DEFAULT_TAGS :: #config(CBOR_INITIALIZE_DEFAULT_TAGS, !ODIN_DEFAULT_TO_PANIC_ALLOCATOR && !ODIN_DEFAULT_TO_NIL_ALLOCATOR) @(private, init, disabled=!INITIALIZE_DEFAULT_TAGS) -tags_initialize_defaults :: proc() { +tags_initialize_defaults :: proc "contextless" () { tags_register_defaults() } // Registers tags that have implementations provided by this package. // This is done by default and can be controlled with the `CBOR_INITIALIZE_DEFAULT_TAGS` define. -tags_register_defaults :: proc() { +tags_register_defaults :: proc "contextless" () { tag_register_number({nil, tag_time_unmarshal, tag_time_marshal}, TAG_EPOCH_TIME_NR, TAG_EPOCH_TIME_ID) tag_register_number({nil, tag_base64_unmarshal, tag_base64_marshal}, TAG_BASE64_NR, TAG_BASE64_ID) tag_register_number({nil, tag_cbor_unmarshal, tag_cbor_marshal}, TAG_CBOR_NR, TAG_CBOR_ID) @@ -298,7 +300,7 @@ tag_base64_unmarshal :: proc(_: ^Tag_Implementation, d: Decoder, _: Tag_Number, #partial switch t in ti.variant { case reflect.Type_Info_String: - + assert(t.encoding == .UTF_8) if t.is_cstring { length := base64.decoded_len(bytes) builder := strings.builder_make(0, length+1) diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index c39255d9d..043b2ec60 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -29,6 +29,7 @@ an input. unmarshal :: proc { unmarshal_from_reader, unmarshal_from_string, + unmarshal_from_bytes, } unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) { @@ -51,6 +52,11 @@ unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, all return } +// Unmarshals from a slice of bytes, see docs on the proc group `Unmarshal` for more info. +unmarshal_from_bytes :: proc(bytes: []byte, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) { + return unmarshal_from_string(string(bytes), ptr, flags, allocator, temp_allocator, loc) +} + unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) { d := d @@ -329,6 +335,8 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header, allocator := context.a _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, loc := #caller_location) -> (err: Unmarshal_Error) { #partial switch t in ti.variant { case reflect.Type_Info_String: + assert(t.encoding == .UTF_8) + bytes := err_conv(_decode_bytes(d, add, allocator=allocator, loc=loc)) or_return if t.is_cstring { @@ -487,7 +495,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, allocator=allocator, loc=loc) or_return defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) } - da := mem.Raw_Dynamic_Array{raw_data(data), 0, length, context.allocator } + da := mem.Raw_Dynamic_Array{raw_data(data), 0, scap, context.allocator } assign_array(d, &da, t.elem, length) or_return @@ -585,6 +593,31 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header if out_of_space { return _unsupported(v, hdr) } return + case reflect.Type_Info_Matrix: + count := t.column_count * t.elem_stride + length, _ := err_conv(_decode_len_container(d, add)) or_return + if length > count { + return _unsupported(v, hdr) + } + + da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator } + + out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return + if out_of_space { return _unsupported(v, hdr) } + return + + case reflect.Type_Info_Simd_Vector: + length, _ := err_conv(_decode_len_container(d, add)) or_return + if length > t.count { + return _unsupported(v, hdr) + } + + da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator } + + out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return + if out_of_space { return _unsupported(v, hdr) } + return + case: return _unsupported(v, hdr) } } diff --git a/core/encoding/csv/doc.odin b/core/encoding/csv/doc.odin index bfeadafd6..7abe2be49 100644 --- a/core/encoding/csv/doc.odin +++ b/core/encoding/csv/doc.odin @@ -63,8 +63,6 @@ Example: read_csv_from_string :: proc(filename: string) { r: csv.Reader r.trim_leading_space = true - r.reuse_record = true // Without it you have to delete(record) - r.reuse_record_buffer = true // Without it you have to each of the fields within it defer csv.reader_destroy(&r) csv_data, ok := os.read_entire_file(filename) diff --git a/core/encoding/csv/reader.odin b/core/encoding/csv/reader.odin index 5348624d5..577ef219d 100644 --- a/core/encoding/csv/reader.odin +++ b/core/encoding/csv/reader.odin @@ -130,7 +130,7 @@ reader_destroy :: proc(r: ^Reader) { for record, row_idx in csv.iterator_next(&r) { ... } TIP: If you process the results within the loop and don't need to own the results, - you can set the Reader's `reuse_record` and `reuse_record_reuse_record_buffer` to true; + you can set the Reader's `reuse_record` and `reuse_record_buffer` to true; you won't need to delete the record or its fields. */ iterator_next :: proc(r: ^Reader) -> (record: []string, idx: int, err: Error, more: bool) { diff --git a/core/encoding/entity/entity.odin b/core/encoding/entity/entity.odin index d2f1d46b2..cb8fa8611 100644 --- a/core/encoding/entity/entity.odin +++ b/core/encoding/entity/entity.odin @@ -108,7 +108,7 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := it couldn't have been part of an XML tag body to be decoded here. Keep in mind that we could already *be* inside a CDATA tag. - If so, write `>` as a literal and continue. + If so, write `<` as a literal and continue. */ if in_data { write_rune(&builder, '<') @@ -119,11 +119,9 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := case ']': // If we're unboxing _and_ decoding CDATA, we'll have to check for the end tag. if in_data { - if t.read_offset + len(CDATA_END) < len(t.src) { - if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END { - in_data = false - t.read_offset += len(CDATA_END) - 1 - } + if strings.has_prefix(t.src[t.offset:], CDATA_END) { + in_data = false + t.read_offset += len(CDATA_END) - 1 } continue } else { @@ -297,40 +295,40 @@ _handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: X assert(t != nil && t.r == '<') if t.read_offset + len(CDATA_START) >= len(t.src) { return false, .None } - if string(t.src[t.offset:][:len(CDATA_START)]) == CDATA_START { - t.read_offset += len(CDATA_START) - 1 - + s := string(t.src[t.offset:]) + if strings.has_prefix(s, CDATA_START) { if .Unbox_CDATA in options && .Decode_CDATA in options { // We're unboxing _and_ decoding CDATA + t.read_offset += len(CDATA_START) - 1 return true, .None } - // CDATA is passed through. - offset := t.offset - - // Scan until end of CDATA. + // CDATA is passed through. Scan until end of CDATA. + start_offset := t.offset + t.read_offset += len(CDATA_START) for { - advance(t) or_return - if t.r < 0 { return true, .CDATA_Not_Terminated } - - if t.read_offset + len(CDATA_END) < len(t.src) { - if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END { - t.read_offset += len(CDATA_END) - 1 + advance(t) + if t.r < 0 { + // error(t, offset, "[scan_string] CDATA was not terminated\n") + return true, .CDATA_Not_Terminated + } - cdata := string(t.src[offset : t.read_offset]) - - if .Unbox_CDATA in options { - cdata = cdata[len(CDATA_START):] - cdata = cdata[:len(cdata) - len(CDATA_END)] - } + // Scan until the end of a CDATA tag. + if s = string(t.src[t.read_offset:]); strings.has_prefix(s, CDATA_END) { + t.read_offset += len(CDATA_END) + cdata := string(t.src[start_offset:t.read_offset]) - write_string(builder, cdata) - return false, .None + if .Unbox_CDATA in options { + cdata = cdata[len(CDATA_START):] + cdata = cdata[:len(cdata) - len(CDATA_END)] } + write_string(builder, cdata) + return false, .None } } - } else if string(t.src[t.offset:][:len(COMMENT_START)]) == COMMENT_START { + + } else if strings.has_prefix(s, COMMENT_START) { t.read_offset += len(COMMENT_START) // Comment is passed through by default. offset := t.offset diff --git a/core/encoding/hxa/read.odin b/core/encoding/hxa/read.odin index a679946f8..6dde16848 100644 --- a/core/encoding/hxa/read.odin +++ b/core/encoding/hxa/read.odin @@ -79,7 +79,6 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato read_meta :: proc(r: ^Reader, capacity: u32le, allocator := context.allocator, loc := #caller_location) -> (meta_data: []Meta, err: Read_Error) { meta_data = make([]Meta, int(capacity), allocator=allocator) count := 0 - defer meta_data = meta_data[:count] for &m in meta_data { m.name = read_name(r) or_return @@ -105,6 +104,7 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato count += 1 } + meta_data = meta_data[:count] return } @@ -112,7 +112,6 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato stack_count := read_value(r, u32le) or_return layer_count := 0 layers = make(Layer_Stack, stack_count, allocator=allocator, loc=loc) - defer layers = layers[:layer_count] for &layer in layers { layer.name = read_name(r) or_return layer.components = read_value(r, u8) or_return @@ -136,6 +135,7 @@ read :: proc(data: []byte, filename := "<input>", print_error := false, allocato layer_count += 1 } + layers = layers[:layer_count] return } diff --git a/core/encoding/json/marshal.odin b/core/encoding/json/marshal.odin index ed6de2f52..2fb507edf 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -108,13 +108,13 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: if opt.write_uint_as_hex && (opt.spec == .JSON5 || opt.spec == .MJSON) { switch i in a { case u8, u16, u32, u64, u128: - s = strconv.append_bits_128(buf[:], u, 16, info.signed, 8*ti.size, "0123456789abcdef", { .Prefix }) + s = strconv.write_bits_128(buf[:], u, 16, info.signed, 8*ti.size, "0123456789abcdef", { .Prefix }) case: - s = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil) + s = strconv.write_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil) } } else { - s = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil) + s = strconv.write_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil) } io.write_string(w, s) or_return @@ -292,7 +292,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: case runtime.Type_Info_Integer: buf: [40]byte u := cast_any_int_to_u128(ka) - name = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*kti.size, "0123456789", nil) + name = strconv.write_bits_128(buf[:], u, 10, info.signed, 8*kti.size, "0123456789", nil) opt_write_key(w, opt, name) or_return case: return .Unsupported_Type @@ -359,10 +359,10 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: #partial switch info in ti.variant { case runtime.Type_Info_String: switch x in v { - case string: - return x == "" - case cstring: - return x == nil || x == "" + case string: return x == "" + case cstring: return x == nil || x == "" + case string16: return x == "" + case cstring16: return x == nil || x == "" } case runtime.Type_Info_Any: return v.(any) == nil diff --git a/core/encoding/json/tokenizer.odin b/core/encoding/json/tokenizer.odin index e46d879a7..ad928b7d9 100644 --- a/core/encoding/json/tokenizer.odin +++ b/core/encoding/json/tokenizer.odin @@ -101,7 +101,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) { } } - scan_espace :: proc(t: ^Tokenizer) -> bool { + scan_escape :: proc(t: ^Tokenizer) -> bool { switch t.r { case '"', '\'', '\\', '/', 'b', 'n', 'r', 't', 'f': next_rune(t) @@ -310,7 +310,7 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) { break } if r == '\\' { - scan_espace(t) + scan_escape(t) } } diff --git a/core/encoding/json/unmarshal.odin b/core/encoding/json/unmarshal.odin index 57371e360..0b65adaac 100644 --- a/core/encoding/json/unmarshal.odin +++ b/core/encoding/json/unmarshal.odin @@ -117,9 +117,25 @@ assign_int :: proc(val: any, i: $T) -> bool { case uint: dst = uint (i) case uintptr: dst = uintptr(i) case: + is_bit_set_different_endian_to_platform :: proc(ti: ^runtime.Type_Info) -> bool { + if ti == nil { + return false + } + t := runtime.type_info_base(ti) + #partial switch info in t.variant { + case runtime.Type_Info_Integer: + switch info.endianness { + case .Platform: return false + case .Little: return ODIN_ENDIAN != .Little + case .Big: return ODIN_ENDIAN != .Big + } + } + return false + } + ti := type_info_of(v.id) - if _, ok := ti.variant.(runtime.Type_Info_Bit_Set); ok { - do_byte_swap := !reflect.bit_set_is_big_endian(v) + if info, ok := ti.variant.(runtime.Type_Info_Bit_Set); ok { + do_byte_swap := is_bit_set_different_endian_to_platform(info.underlying) switch ti.size * 8 { case 0: // no-op. case 8: @@ -390,6 +406,9 @@ unmarshal_expect_token :: proc(p: ^Parser, kind: Token_Kind, loc := #caller_loca return prev } +// Struct tags can include not only the name of the JSON key, but also a tag such as `omitempty`. +// Example: `json:"key_name,omitempty"` +// This returns the first field as `json_name`, and the rest are returned as `extra`. @(private) json_name_from_tag_value :: proc(value: string) -> (json_name, extra: string) { json_name = value @@ -425,12 +444,6 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm defer delete(key, p.allocator) unmarshal_expect_token(p, .Colon) - - field_test :: #force_inline proc "contextless" (field_used: [^]byte, offset: uintptr) -> bool { - prev_set := field_used[offset/8] & byte(offset&7) != 0 - field_used[offset/8] |= byte(offset&7) - return prev_set - } field_used_bytes := (reflect.size_of_typeid(ti.id)+7)/8 field_used := intrinsics.alloca(field_used_bytes + 1, 1) // + 1 to not overflow on size_of 0 types. @@ -449,7 +462,9 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm if use_field_idx < 0 { for field, field_idx in fields { - if key == field.name { + tag_value := reflect.struct_tag_get(field.tag, "json") + json_name, _ := json_name_from_tag_value(tag_value) + if json_name == "" && key == field.name { use_field_idx = field_idx break } @@ -470,7 +485,9 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm } } - if field.name == key || (field.tag != "" && reflect.struct_tag_get(field.tag, "json") == key) { + tag_value := reflect.struct_tag_get(field.tag, "json") + json_name, _ := json_name_from_tag_value(tag_value) + if (json_name == "" && field.name == key) || json_name == key { offset = field.offset type = field.type found = true @@ -492,6 +509,11 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm } if field_found { + field_test :: #force_inline proc "contextless" (field_used: [^]byte, offset: uintptr) -> bool { + prev_set := field_used[offset/8] & byte(offset&7) != 0 + field_used[offset/8] |= byte(offset&7) + return prev_set + } if field_test(field_used, offset) { return .Multiple_Use_Field } @@ -548,7 +570,9 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm key_ptr: rawptr #partial switch tk in t.key.variant { - case runtime.Type_Info_String: + case runtime.Type_Info_String: + assert(tk.encoding == .UTF_8) + key_ptr = rawptr(&key) key_cstr: cstring if reflect.is_cstring(t.key) { diff --git a/core/encoding/uuid/generation.odin b/core/encoding/uuid/generation.odin index 7c9d4b80c..b210f6a52 100644 --- a/core/encoding/uuid/generation.odin +++ b/core/encoding/uuid/generation.odin @@ -240,7 +240,7 @@ Example: import "core:encoding/uuid" import "core:fmt" - main :: proc() { + generate_v8_hash_bytes_example :: proc() { my_uuid := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.odin-lang.org", .SHA256) my_uuid_string := uuid.to_string(my_uuid, context.temp_allocator) fmt.println(my_uuid_string) @@ -306,7 +306,7 @@ Example: import "core:encoding/uuid" import "core:fmt" - main :: proc() { + generate_v8_hash_string_example :: proc() { my_uuid := uuid.generate_v8_hash(uuid.Namespace_DNS, "www.odin-lang.org", .SHA256) my_uuid_string := uuid.to_string(my_uuid, context.temp_allocator) fmt.println(my_uuid_string) diff --git a/core/encoding/xml/tokenizer.odin b/core/encoding/xml/tokenizer.odin index a2bbaf28e..3ef9a6388 100644 --- a/core/encoding/xml/tokenizer.odin +++ b/core/encoding/xml/tokenizer.odin @@ -16,6 +16,7 @@ package encoding_xml import "core:fmt" import "core:unicode" import "core:unicode/utf8" +import "core:strings" Error_Handler :: #type proc(pos: Pos, fmt: string, args: ..any) @@ -121,7 +122,7 @@ default_error_handler :: proc(pos: Pos, msg: string, args: ..any) { error :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) { pos := offset_to_pos(t, offset) if t.err != nil { - t.err(pos, msg, ..args) + t.err(pos=pos, fmt=msg, args=args) } t.error_count += 1 } @@ -268,32 +269,27 @@ scan_comment :: proc(t: ^Tokenizer) -> (comment: string, err: Error) { // Skip CDATA skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) { - if t.read_offset + len(CDATA_START) >= len(t.src) { - // Can't be the start of a CDATA tag. + if s := string(t.src[t.offset:]); !strings.has_prefix(s, CDATA_START) { return .None } - if string(t.src[t.offset:][:len(CDATA_START)]) == CDATA_START { - t.read_offset += len(CDATA_START) - offset := t.offset + t.read_offset += len(CDATA_START) + offset := t.offset - cdata_scan: for { - advance_rune(t) - if t.ch < 0 { - error(t, offset, "[scan_string] CDATA was not terminated\n") - return .Premature_EOF - } + cdata_scan: for { + advance_rune(t) + if t.ch < 0 { + error(t, offset, "[scan_string] CDATA was not terminated\n") + return .Premature_EOF + } - // Scan until the end of a CDATA tag. - if t.read_offset + len(CDATA_END) < len(t.src) { - if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END { - t.read_offset += len(CDATA_END) - break cdata_scan - } - } + // Scan until the end of a CDATA tag. + if s := string(t.src[t.read_offset:]); strings.has_prefix(s, CDATA_END) { + t.read_offset += len(CDATA_END) + break cdata_scan } } - return + return .None } @(optimization_mode="favor_size") @@ -393,6 +389,8 @@ scan :: proc(t: ^Tokenizer, multiline_string := false) -> Token { case '/': kind = .Slash case '-': kind = .Dash case ':': kind = .Colon + case '[': kind = .Open_Bracket + case ']': kind = .Close_Bracket case '"', '\'': kind = .Invalid diff --git a/core/encoding/xml/xml_reader.odin b/core/encoding/xml/xml_reader.odin index b8c8b13a4..707d2b3f3 100644 --- a/core/encoding/xml/xml_reader.odin +++ b/core/encoding/xml/xml_reader.odin @@ -56,7 +56,7 @@ Option_Flag :: enum { Option_Flags :: bit_set[Option_Flag; u16] Document :: struct { - elements: [dynamic]Element, + elements: [dynamic]Element `fmt:"v,element_count"`, element_count: Element_ID, prologue: Attributes, @@ -70,15 +70,15 @@ Document :: struct { // If we encounter comments before the root node, and the option to intern comments is given, this is where they'll live. // Otherwise they'll be in the element tree. - comments: [dynamic]string, + comments: [dynamic]string `fmt:"-"`, // Internal - tokenizer: ^Tokenizer, - allocator: mem.Allocator, + tokenizer: ^Tokenizer `fmt:"-"`, + allocator: mem.Allocator `fmt:"-"`, // Input. Either the original buffer, or a copy if `.Input_May_Be_Modified` isn't specified. - input: []u8, - strings_to_free: [dynamic]string, + input: []u8 `fmt:"-"`, + strings_to_free: [dynamic]string `fmt:"-"`, } Element :: struct { @@ -175,7 +175,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha data = bytes.clone(data) } - t := &Tokenizer{} + t := new(Tokenizer) init(t, string(data), path, error_handler) doc = new(Document) @@ -195,7 +195,6 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha loop: for { skip_whitespace(t) - // NOTE(Jeroen): This is faster as a switch. switch t.ch { case '<': // Consume peeked `<` @@ -306,9 +305,17 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha } } + case .Open_Bracket: + // This could be a CDATA tag part of a tag's body. Unread the `<![` + t.offset -= 3 + + // Instead of calling `parse_body` here, we could also `continue loop` + // and fall through to the `case:` at the bottom of the outer loop. + // This makes the intent clearer. + parse_body(doc, element, opts) or_return + case: - error(t, t.offset, "Invalid Token after <!. Expected .Ident, got %#v\n", next) - return + error(t, t.offset, "Unexpected Token after <!: %#v", next) } } else if open.kind == .Question { @@ -341,38 +348,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha case: // This should be a tag's body text. - body_text := scan_string(t, t.offset) or_return - needs_processing := .Unbox_CDATA in opts.flags - needs_processing |= .Decode_SGML_Entities in opts.flags - - if !needs_processing { - append(&doc.elements[element].value, body_text) - continue - } - - decode_opts := entity.XML_Decode_Options{} - if .Keep_Tag_Body_Comments not_in opts.flags { - decode_opts += { .Comment_Strip } - } - - if .Decode_SGML_Entities not_in opts.flags { - decode_opts += { .No_Entity_Decode } - } - - if .Unbox_CDATA in opts.flags { - decode_opts += { .Unbox_CDATA } - if .Decode_SGML_Entities in opts.flags { - decode_opts += { .Decode_CDATA } - } - } - - decoded, decode_err := entity.decode_xml(body_text, decode_opts) - if decode_err == .None { - append(&doc.elements[element].value, decoded) - append(&doc.strings_to_free, decoded) - } else { - append(&doc.elements[element].value, body_text) - } + parse_body(doc, element, opts) or_return } } @@ -427,6 +403,7 @@ destroy :: proc(doc: ^Document) { } delete(doc.strings_to_free) + free(doc.tokenizer) free(doc) } @@ -457,8 +434,6 @@ parse_attribute :: proc(doc: ^Document) -> (attr: Attribute, offset: int, err: E t := doc.tokenizer key := expect(t, .Ident) or_return - offset = t.offset - len(key.text) - _ = expect(t, .Eq) or_return value := expect(t, .String, multiline_string=true) or_return @@ -591,6 +566,47 @@ parse_doctype :: proc(doc: ^Document) -> (err: Error) { return .None } +parse_body :: proc(doc: ^Document, element: Element_ID, opts: Options) -> (err: Error) { + assert(doc != nil) + context.allocator = doc.allocator + t := doc.tokenizer + + body_text := scan_string(t, t.offset) or_return + needs_processing := .Unbox_CDATA in opts.flags + needs_processing |= .Decode_SGML_Entities in opts.flags + + if !needs_processing { + append(&doc.elements[element].value, body_text) + return + } + + decode_opts := entity.XML_Decode_Options{} + if .Keep_Tag_Body_Comments not_in opts.flags { + decode_opts += { .Comment_Strip } + } + + if .Decode_SGML_Entities not_in opts.flags { + decode_opts += { .No_Entity_Decode } + } + + if .Unbox_CDATA in opts.flags { + decode_opts += { .Unbox_CDATA } + if .Decode_SGML_Entities in opts.flags { + decode_opts += { .Decode_CDATA } + } + } + + decoded, decode_err := entity.decode_xml(body_text, decode_opts) + if decode_err == .None { + append(&doc.elements[element].value, decoded) + append(&doc.strings_to_free, decoded) + } else { + append(&doc.elements[element].value, body_text) + } + + return +} + Element_ID :: u32 new_element :: proc(doc: ^Document) -> (id: Element_ID) { @@ -609,4 +625,4 @@ new_element :: proc(doc: ^Document) -> (id: Element_ID) { cur := doc.element_count doc.element_count += 1 return cur -} +}
\ No newline at end of file diff --git a/core/flags/constants.odin b/core/flags/constants.odin index 6f5281928..dc2663e2a 100644 --- a/core/flags/constants.odin +++ b/core/flags/constants.odin @@ -19,7 +19,7 @@ SUBTAG_NAME :: "name" SUBTAG_POS :: "pos" SUBTAG_REQUIRED :: "required" SUBTAG_HIDDEN :: "hidden" -SUBTAG_VARIADIC :: "variadic" +SUBTAG_MANIFOLD :: "manifold" SUBTAG_FILE :: "file" SUBTAG_PERMS :: "perms" SUBTAG_INDISTINCT :: "indistinct" @@ -28,7 +28,7 @@ TAG_USAGE :: "usage" UNDOCUMENTED_FLAG :: "<This flag has not been documented yet.>" -INTERNAL_VARIADIC_FLAG :: "varg" +INTERNAL_OVERFLOW_FLAG :: #config(ODIN_CORE_FLAGS_OVERFLOW_FLAG, "overflow") RESERVED_HELP_FLAG :: "help" RESERVED_HELP_FLAG_SHORT :: "h" diff --git a/core/flags/doc.odin b/core/flags/doc.odin index b97547806..440acd52c 100644 --- a/core/flags/doc.odin +++ b/core/flags/doc.odin @@ -20,6 +20,17 @@ The format is similar to the Odin binary's way of handling compiler flags. -<map>:<key>=<value> set map[key] to value +Unhandled Arguments: + +All unhandled positional arguments are placed into the `overflow` field on a +struct, if it exists. In UNIX-style parsing, the existence of a `--` on the +command line will also pass all arguments afterwards into this field. + +If desired, the name of the field may be changed from `overflow` to any string +by setting the `ODIN_CORE_FLAGS_OVERFLOW_FLAG` compile-time config option with +`-define:ODIN_CORE_FLAGS_OVERFLOW_FLAG=<name>`. + + Struct Tags: Users of the `core:encoding/json` package may be familiar with using tags to @@ -32,7 +43,7 @@ Under the `args` tag, there are the following subtags: - `pos=N`: place positional argument `N` into this flag. - `hidden`: hide this flag from the usage documentation. - `required`: cause verification to fail if this argument is not set. -- `variadic`: take all remaining arguments when set, UNIX-style only. +- `manifold=N`: take several arguments at once, UNIX-style only. - `file`: for `os.Handle` types, file open mode. - `perms`: for `os.Handle` types, file open permissions. - `indistinct`: allow the setting of distinct types by their base type. @@ -47,8 +58,9 @@ you want to require 3 and only 3 arguments in a dynamic array, you would specify `required=3<4`. -`variadic` may be given a number (`variadic=N`) above 1 to limit how many extra -arguments it consumes. +`manifold` may be given a number (`manifold=N`) above 1 to limit how many extra +arguments it consumes at once. If this number is not specified, it will take as +many arguments as can be converted to the underlying element type. `file` determines the file open mode for an `os.Handle`. @@ -160,7 +172,7 @@ at parse time. --flag --flag=argument --flag argument - --flag argument repeating-argument + --flag argument (manifold-argument) `-flag` may also be substituted for `--flag`. diff --git a/core/flags/errors.odin b/core/flags/errors.odin index 6e48f6ccf..3d34a95d3 100644 --- a/core/flags/errors.odin +++ b/core/flags/errors.odin @@ -4,7 +4,7 @@ import "core:os" Parse_Error_Reason :: enum { None, - // An extra positional argument was given, and there is no `varg` field. + // An extra positional argument was given, and there is no `overflow` field. Extra_Positional, // The underlying type does not support the string value it is being set to. Bad_Value, diff --git a/core/flags/example/example.odin b/core/flags/example/example.odin index b1d7b58d6..a3af44790 100644 --- a/core/flags/example/example.odin +++ b/core/flags/example/example.odin @@ -107,14 +107,14 @@ main :: proc() { // assignments: map[string]u8 `args:"name=assign" usage:"Number of jobs per worker."`, - // (Variadic) Only available in UNIX style: + // (Manifold) Only available in UNIX style: - // bots: [dynamic]string `args:"variadic=2,required"`, + // bots: [dynamic]string `args:"manifold=2,required"`, verbose: bool `usage:"Show verbose output."`, debug: bool `args:"hidden" usage:"print debug info"`, - varg: [dynamic]string `usage:"Any extra arguments go here."`, + overflow: [dynamic]string `usage:"Any extra arguments go here."`, } opt: Options diff --git a/core/flags/internal_assignment.odin b/core/flags/internal_assignment.odin index 12ddb876f..5999dbf2d 100644 --- a/core/flags/internal_assignment.odin +++ b/core/flags/internal_assignment.odin @@ -33,9 +33,9 @@ push_positional :: #force_no_inline proc (model: ^$T, parser: ^Parser, arg: stri field, index, has_pos_assigned := get_field_by_pos(model, pos) if !has_pos_assigned { - when intrinsics.type_has_field(T, INTERNAL_VARIADIC_FLAG) { + when intrinsics.type_has_field(T, INTERNAL_OVERFLOW_FLAG) { // Add it to the fallback array. - field = reflect.struct_field_by_name(T, INTERNAL_VARIADIC_FLAG) + field = reflect.struct_field_by_name(T, INTERNAL_OVERFLOW_FLAG) } else { return Parse_Error { .Extra_Positional, @@ -117,8 +117,8 @@ set_unix_flag :: proc(model: ^$T, parser: ^Parser, name: string) -> (future_args case runtime.Type_Info_Dynamic_Array: future_args = 1 if tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok { - if length, is_variadic := get_struct_subtag(tag, SUBTAG_VARIADIC); is_variadic { - // Variadic arrays may specify how many arguments they consume at once. + if length, is_manifold := get_struct_subtag(tag, SUBTAG_MANIFOLD); is_manifold { + // Manifold arrays may specify how many arguments they consume at once. // Otherwise, they take everything that's left. if value, value_ok := strconv.parse_u64_of_base(length, 10); value_ok { future_args = cast(int)value diff --git a/core/flags/internal_parsing.odin b/core/flags/internal_parsing.odin index 4e49f45b0..6d544e5af 100644 --- a/core/flags/internal_parsing.odin +++ b/core/flags/internal_parsing.odin @@ -95,7 +95,7 @@ parse_one_unix_arg :: proc(model: ^$T, parser: ^Parser, arg: string) -> ( // `--`, and only `--`. // Everything from now on will be treated as an argument. future_args = max(int) - current_flag = INTERNAL_VARIADIC_FLAG + current_flag = INTERNAL_OVERFLOW_FLAG return } } diff --git a/core/flags/internal_rtti.odin b/core/flags/internal_rtti.odin index 1c559ca55..a1b050597 100644 --- a/core/flags/internal_rtti.odin +++ b/core/flags/internal_rtti.odin @@ -127,6 +127,8 @@ parse_and_set_pointer_by_base_type :: proc(ptr: rawptr, str: string, type_info: } case runtime.Type_Info_String: + assert(specific_type_info.encoding == .UTF_8) + if specific_type_info.is_cstring { cstr_ptr := (^cstring)(ptr) if cstr_ptr != nil { diff --git a/core/flags/internal_validation.odin b/core/flags/internal_validation.odin index afd05331c..cd903c3e5 100644 --- a/core/flags/internal_validation.odin +++ b/core/flags/internal_validation.odin @@ -59,7 +59,8 @@ validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_ } } - if pos_str, has_pos := get_struct_subtag(args_tag, SUBTAG_POS); has_pos { + pos_str, has_pos := get_struct_subtag(args_tag, SUBTAG_POS) + if has_pos { #partial switch specific_type_info in field.type.variant { case runtime.Type_Info_Map: fmt.panicf("%T.%s has `%s` defined, and this does not make sense on a map type.", @@ -79,7 +80,7 @@ validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_ fmt.assertf(!reflect.is_boolean(field.type), "%T.%s is a required boolean. This is disallowed.", model_type, field.name, loc = loc) - fmt.assertf(field.name != INTERNAL_VARIADIC_FLAG, "%T.%s is defined as required. This is disallowed.", + fmt.assertf(field.name != INTERNAL_OVERFLOW_FLAG, "%T.%s is defined as required. This is disallowed.", model_type, field.name, loc = loc) if len(requirement) > 0 { @@ -109,24 +110,28 @@ validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_ } } - if length, is_variadic := get_struct_subtag(args_tag, SUBTAG_VARIADIC); is_variadic { + if length, is_manifold := get_struct_subtag(args_tag, SUBTAG_MANIFOLD); is_manifold { + fmt.assertf(!has_pos, + "%T.%s has both `%s` and `%s` defined. This is disallowed.\n\tSuggestion: Use a dynamic array field named `%s` to accept unspecified positional arguments.", + model_type, field.name, SUBTAG_POS, SUBTAG_MANIFOLD, INTERNAL_OVERFLOW_FLAG, loc = loc) + if value, parse_ok := strconv.parse_u64_of_base(length, 10); parse_ok { fmt.assertf(value > 0, "%T.%s has `%s` set to %i. It must be greater than zero.", - model_type, field.name, value, SUBTAG_VARIADIC, loc = loc) + model_type, field.name, value, SUBTAG_MANIFOLD, loc = loc) fmt.assertf(value != 1, - "%T.%s has `%s` set to 1. This has no effect.", - model_type, field.name, SUBTAG_VARIADIC, loc = loc) + "%T.%s has `%s` set to 1. This is equivalent to not defining `%s`.", + model_type, field.name, SUBTAG_MANIFOLD, SUBTAG_MANIFOLD, loc = loc) } #partial switch specific_type_info in field.type.variant { case runtime.Type_Info_Dynamic_Array: fmt.assertf(style != .Odin, "%T.%s has `%s` defined, but this only makes sense in UNIX-style parsing mode.", - model_type, field.name, SUBTAG_VARIADIC, loc = loc) + model_type, field.name, SUBTAG_MANIFOLD, loc = loc) case: fmt.panicf("%T.%s has `%s` defined, but this only makes sense on dynamic arrays.", - model_type, field.name, SUBTAG_VARIADIC, loc = loc) + model_type, field.name, SUBTAG_MANIFOLD, loc = loc) } } diff --git a/core/flags/parsing.odin b/core/flags/parsing.odin index 2d8ce34eb..989a9a1a6 100644 --- a/core/flags/parsing.odin +++ b/core/flags/parsing.odin @@ -6,7 +6,7 @@ package flags Parsing_Style :: enum { // Odin-style: `-flag`, `-flag:option`, `-map:key=value` Odin, - // UNIX-style: `-flag` or `--flag`, `--flag=argument`, `--flag argument repeating-argument` + // UNIX-style: `-flag` or `--flag`, `--flag=argument`, `--flag argument (manifold-argument)` Unix, } @@ -61,7 +61,7 @@ parse :: proc( } case .Unix: - // Support for `-flag argument (repeating-argument ...)` + // Support for `-flag argument (manifold-argument ...)` future_args: int current_flag: string diff --git a/core/flags/rtti.odin b/core/flags/rtti.odin index ce7a23773..058292698 100644 --- a/core/flags/rtti.odin +++ b/core/flags/rtti.odin @@ -38,6 +38,6 @@ Note that only one can be active at a time. Inputs: - setter: The type setter. Pass `nil` to disable any previously set setter. */ -register_type_setter :: proc(setter: Custom_Type_Setter) { +register_type_setter :: proc "contextless" (setter: Custom_Type_Setter) { global_custom_type_setter = setter } diff --git a/core/flags/usage.odin b/core/flags/usage.odin index c42df7576..a44baec81 100644 --- a/core/flags/usage.odin +++ b/core/flags/usage.odin @@ -30,18 +30,18 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty is_positional: bool, is_required: bool, is_boolean: bool, - is_variadic: bool, - variadic_length: int, + is_manifold: bool, + manifold_length: int, } // // POSITIONAL+REQUIRED, POSITIONAL, REQUIRED, NON_REQUIRED+NON_POSITIONAL, ... // sort_flags :: proc(i, j: Flag) -> slice.Ordering { - // `varg` goes to the end. - if i.name == INTERNAL_VARIADIC_FLAG { + // `overflow` goes to the end. + if i.name == INTERNAL_OVERFLOW_FLAG { return .Greater - } else if j.name == INTERNAL_VARIADIC_FLAG { + } else if j.name == INTERNAL_OVERFLOW_FLAG { return .Less } @@ -120,10 +120,10 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty flag.is_required = true flag.required_min, flag.required_max, _ = parse_requirements(requirement) } - if length_str, is_variadic := get_struct_subtag(args_tag, SUBTAG_VARIADIC); is_variadic { - flag.is_variadic = true + if length_str, is_manifold := get_struct_subtag(args_tag, SUBTAG_MANIFOLD); is_manifold { + flag.is_manifold = true if length, parse_ok := strconv.parse_u64_of_base(length_str, 10); parse_ok { - flag.variadic_length = cast(int)length + flag.manifold_length = cast(int)length } } } @@ -147,15 +147,15 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty case runtime.Type_Info_Dynamic_Array: requirement_spec := describe_array_requirements(flag) - if flag.is_variadic || flag.name == INTERNAL_VARIADIC_FLAG { - if flag.variadic_length == 0 { + if flag.is_manifold || flag.name == INTERNAL_OVERFLOW_FLAG { + if flag.manifold_length == 0 { flag.type_description = fmt.tprintf("<%v, ...>%s", specific_type_info.elem.id, requirement_spec) } else { flag.type_description = fmt.tprintf("<%v, %i at once>%s", specific_type_info.elem.id, - flag.variadic_length, + flag.manifold_length, requirement_spec) } } else { @@ -177,7 +177,7 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty } } - if flag.name == INTERNAL_VARIADIC_FLAG { + if flag.name == INTERNAL_OVERFLOW_FLAG { flag.full_length = len(flag.type_description) } else if flag.is_boolean { flag.full_length = len(flag_prefix) + len(flag.name) + len(flag.type_description) @@ -201,13 +201,13 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty strings.write_string(&builder, program) for flag in visible_flags { - if keep_it_short && !(flag.is_required || flag.is_positional || flag.name == INTERNAL_VARIADIC_FLAG) { + if keep_it_short && !(flag.is_required || flag.is_positional || flag.name == INTERNAL_OVERFLOW_FLAG) { continue } strings.write_byte(&builder, ' ') - if flag.name == INTERNAL_VARIADIC_FLAG { + if flag.name == INTERNAL_OVERFLOW_FLAG { strings.write_string(&builder, "...") continue } @@ -252,7 +252,7 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty strings.write_byte(&builder, '\t') - if flag.name == INTERNAL_VARIADIC_FLAG { + if flag.name == INTERNAL_OVERFLOW_FLAG { strings.write_string(&builder, flag.type_description) } else { strings.write_string(&builder, flag_prefix) diff --git a/core/flags/util.odin b/core/flags/util.odin index c182289be..ce7e2e36c 100644 --- a/core/flags/util.odin +++ b/core/flags/util.odin @@ -95,7 +95,7 @@ Example: import "core:flags" import "core:fmt" - subtag_example :: proc() { + get_subtag_example :: proc() { args_tag := "precision=3,signed" precision, has_precision := flags.get_subtag(args_tag, "precision") diff --git a/core/fmt/doc.odin b/core/fmt/doc.odin index d45e6c796..eeebd33f7 100644 --- a/core/fmt/doc.odin +++ b/core/fmt/doc.odin @@ -29,6 +29,8 @@ Integer: %x base 16, with lower-case letters for a-f %X base 16, with upper-case letters for A-F %U Unicode format: U+1234; same as "U+%04X" + %m number of bytes in the best unit of measurement, e.g. 123.45mib + %M number of bytes in the best unit of measurement, e.g. 123.45MiB Floating-point, complex numbers, and quaternions: %e scientific notation, e.g. -1.23456e+78 %E scientific notation, e.g. -1.23456E+78 @@ -38,8 +40,6 @@ Floating-point, complex numbers, and quaternions: %G synonym for %g %h hexadecimal (lower-case) representation with 0h prefix (0h01234abcd) %H hexadecimal (upper-case) representation with 0H prefix (0h01234ABCD) - %m number of bytes in the best unit of measurement, e.g. 123.45mib - %M number of bytes in the best unit of measurement, e.g. 123.45MiB String and slice of bytes %s the uninterpreted bytes of the string or slice %q a double-quoted string safely escaped with Odin syntax diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index b7b42ffa4..9c245de94 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -116,11 +116,12 @@ register_user_formatter :: proc(id: typeid, formatter: User_Formatter) -> Regist } // Creates a formatted string // -// *Allocates Using Context's Allocator* +// *Allocates Using Provided Allocator* // // Inputs: // - args: A variadic list of arguments to be formatted. // - sep: An optional separator string (default is a single space). +// - allocator: (default: context.allocator) // // Returns: A formatted string. // @@ -132,11 +133,12 @@ aprint :: proc(args: ..any, sep := " ", allocator := context.allocator) -> strin } // Creates a formatted string with a newline character at the end // -// *Allocates Using Context's Allocator* +// *Allocates Using Provided Allocator* // // Inputs: // - args: A variadic list of arguments to be formatted. // - sep: An optional separator string (default is a single space). +// - allocator: (default: context.allocator) // // Returns: A formatted string with a newline character at the end. // @@ -148,11 +150,12 @@ aprintln :: proc(args: ..any, sep := " ", allocator := context.allocator) -> str } // Creates a formatted string using a format string and arguments // -// *Allocates Using Context's Allocator* +// *Allocates Using Provided Allocator* // // Inputs: // - fmt: A format string with placeholders for the provided arguments. // - args: A variadic list of arguments to be formatted. +// - allocator: (default: context.allocator) // - newline: Whether the string should end with a newline. (See `aprintfln`.) // // Returns: A formatted string. The returned string must be freed accordingly. @@ -165,11 +168,12 @@ aprintf :: proc(fmt: string, args: ..any, allocator := context.allocator, newlin } // Creates a formatted string using a format string and arguments, followed by a newline. // -// *Allocates Using Context's Allocator* +// *Allocates Using Provided Allocator* // // Inputs: // - fmt: A format string with placeholders for the provided arguments. // - args: A variadic list of arguments to be formatted. +// - allocator: (default: context.allocator) // // Returns: A formatted string. The returned string must be freed accordingly. // @@ -359,11 +363,12 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! { // Creates a formatted C string // -// *Allocates Using Context's Allocator* +// *Allocates Using Provided Allocator* // // Inputs: // - args: A variadic list of arguments to be formatted. // - sep: An optional separator string (default is a single space). +// - allocator: (default: context.allocator) // // Returns: A formatted C string. // @@ -379,11 +384,12 @@ caprint :: proc(args: ..any, sep := " ", allocator := context.allocator) -> cstr // Creates a formatted C string // -// *Allocates Using Context's Allocator* +// *Allocates Using Provided Allocator* // // Inputs: // - format: A format string with placeholders for the provided arguments // - args: A variadic list of arguments to be formatted +// - allocator: (default: context.allocator) // - newline: Whether the string should end with a newline. (See `caprintfln`.) // // Returns: A formatted C string @@ -399,11 +405,12 @@ caprintf :: proc(format: string, args: ..any, allocator := context.allocator, ne } // Creates a formatted C string, followed by a newline. // -// *Allocates Using Context's Allocator* +// *Allocates Using Provided Allocator* // // Inputs: // - format: A format string with placeholders for the provided arguments // - args: A variadic list of arguments to be formatted +// - allocator: (default: context.allocator) // // Returns: A formatted C string // @@ -1115,7 +1122,7 @@ _fmt_int :: proc(fi: ^Info, u: u64, base: int, is_signed: bool, bit_size: int, d flags: strconv.Int_Flags if fi.hash && !fi.zero && start == 0 { flags += {.Prefix} } if fi.plus { flags += {.Plus} } - s := strconv.append_bits(buf[start:], u, base, is_signed, bit_size, digits, flags) + s := strconv.write_bits(buf[start:], u, base, is_signed, bit_size, digits, flags) prev_zero := fi.zero defer fi.zero = prev_zero fi.zero = false @@ -1200,7 +1207,7 @@ _fmt_int_128 :: proc(fi: ^Info, u: u128, base: int, is_signed: bool, bit_size: i flags: strconv.Int_Flags if fi.hash && !fi.zero && start == 0 { flags += {.Prefix} } if fi.plus { flags += {.Plus} } - s := strconv.append_bits_128(buf[start:], u, base, is_signed, bit_size, digits, flags) + s := strconv.write_bits_128(buf[start:], u, base, is_signed, bit_size, digits, flags) if fi.hash && fi.zero && fi.indent == 0 { c: byte = 0 @@ -1265,7 +1272,7 @@ _fmt_memory :: proc(fi: ^Info, u: u64, is_signed: bool, bit_size: int, units: st } buf: [256]byte - str := strconv.append_float(buf[:], amt, 'f', prec, 64) + str := strconv.write_float(buf[:], amt, 'f', prec, 64) // Add the unit at the end. copy(buf[len(str):], units[off:off+unit_len]) @@ -1417,7 +1424,7 @@ _fmt_float_as :: proc(fi: ^Info, v: f64, bit_size: int, verb: rune, float_fmt: b buf: [386]byte // Can return "NaN", "+Inf", "-Inf", "+<value>", "-<value>". - str := strconv.append_float(buf[:], v, float_fmt, prec, bit_size) + str := strconv.write_float(buf[:], v, float_fmt, prec, bit_size) if !fi.plus { // Strip sign from "+<value>" but not "+Inf". @@ -1442,9 +1449,12 @@ fmt_float :: proc(fi: ^Info, v: f64, bit_size: int, verb: rune) { _fmt_float_as(fi, v, bit_size, verb, 'g', -1) case 'f', 'F': _fmt_float_as(fi, v, bit_size, verb, 'f', 3) - case 'e', 'E': + case 'e': // BUG(): "%.3e" returns "3.000e+00" _fmt_float_as(fi, v, bit_size, verb, 'e', 6) + case 'E': + // BUG(): "%.3E" returns "3.000E+00" + _fmt_float_as(fi, v, bit_size, verb, 'E', 6) case 'h', 'H': prev_fi := fi^ @@ -1541,6 +1551,79 @@ fmt_string :: proc(fi: ^Info, s: string, verb: rune) { fmt_cstring :: proc(fi: ^Info, s: cstring, verb: rune) { fmt_string(fi, string(s), verb) } + +// Formats a string UTF-16 with a specific format. +// +// Inputs: +// - fi: Pointer to the Info struct containing format settings. +// - s: The string to format. +// - verb: The format specifier character (e.g. 's', 'v', 'q', 'x', 'X'). +// +fmt_string16 :: proc(fi: ^Info, s: string16, verb: rune) { + s, verb := s, verb + if ol, ok := fi.optional_len.?; ok { + s = s[:clamp(ol, 0, len(s))] + } + if !fi.in_bad && fi.record_level > 0 && verb == 'v' { + verb = 'q' + } + + switch verb { + case 's', 'v': + if fi.width_set { + if fi.width > len(s) { + if fi.minus { + io.write_string16(fi.writer, s, &fi.n) + } + + for _ in 0..<fi.width - len(s) { + io.write_byte(fi.writer, ' ', &fi.n) + } + + if !fi.minus { + io.write_string16(fi.writer, s, &fi.n) + } + } else { + io.write_string16(fi.writer, s, &fi.n) + } + } else { + io.write_string16(fi.writer, s, &fi.n) + } + + case 'q', 'w': // quoted string + io.write_quoted_string16(fi.writer, s, '"', &fi.n) + + case 'x', 'X': + space := fi.space + fi.space = false + defer fi.space = space + + for i in 0..<len(s) { + if i > 0 && space { + io.write_byte(fi.writer, ' ', &fi.n) + } + char_set := __DIGITS_UPPER + if verb == 'x' { + char_set = __DIGITS_LOWER + } + _fmt_int(fi, u64(s[i]), 16, false, bit_size=16, digits=char_set) + } + + case: + fmt_bad_verb(fi, verb) + } +} +// Formats a C-style UTF-16 string with a specific format. +// +// Inputs: +// - fi: Pointer to the Info struct containing format settings. +// - s: The C-style string to format. +// - verb: The format specifier character (Ref fmt_string). +// +fmt_cstring16 :: proc(fi: ^Info, s: cstring16, verb: rune) { + fmt_string16(fi, string16(s), verb) +} + // Formats a raw pointer with a specific format. // // Inputs: @@ -1795,11 +1878,8 @@ fmt_bit_set :: proc(fi: ^Info, v: any, name: string = "", verb: rune = 'v') { e, is_enum := et.variant.(runtime.Type_Info_Enum) commas := 0 - loop: for i in 0 ..< bit_size { - if bits & (1<<i) == 0 { - continue loop - } - + loop: for i in transmute(bit_set[0..<128])bits { + i := i64(i) + info.lower if commas > 0 { io.write_string(fi.writer, ", ", &fi.n) } @@ -1822,8 +1902,7 @@ fmt_bit_set :: proc(fi: ^Info, v: any, name: string = "", verb: rune = 'v') { } } } - v := i64(i) + info.lower - io.write_i64(fi.writer, v, 10, &fi.n) + io.write_i64(fi.writer, i, 10, &fi.n) commas += 1 } } @@ -2267,14 +2346,14 @@ fmt_array :: proc(fi: ^Info, data: rawptr, n: int, elem_size: int, elem: ^reflec } switch reflect.type_info_base(elem).id { - case byte: fmt_string(fi, string(([^]byte)(data)[:n]), verb); return - case u16: print_utf16(fi, ([^]u16)(data)[:n]); return - case u16le: print_utf16(fi, ([^]u16le)(data)[:n]); return - case u16be: print_utf16(fi, ([^]u16be)(data)[:n]); return - case u32: print_utf32(fi, ([^]u32)(data)[:n]); return - case u32le: print_utf32(fi, ([^]u32le)(data)[:n]); return - case u32be: print_utf32(fi, ([^]u32be)(data)[:n]); return - case rune: print_utf32(fi, ([^]rune)(data)[:n]); return + case byte: fmt_string(fi, string (([^]byte)(data)[:n]), verb); return + case u16: fmt_string16(fi, string16(([^]u16) (data)[:n]), verb); return + case u16le: print_utf16(fi, ([^]u16le)(data)[:n]); return + case u16be: print_utf16(fi, ([^]u16be)(data)[:n]); return + case u32: print_utf32(fi, ([^]u32)(data)[:n]); return + case u32le: print_utf32(fi, ([^]u32le)(data)[:n]); return + case u32be: print_utf32(fi, ([^]u32be)(data)[:n]); return + case rune: print_utf32(fi, ([^]rune)(data)[:n]); return } } if verb == 'p' { @@ -3204,6 +3283,9 @@ fmt_arg :: proc(fi: ^Info, arg: any, verb: rune) { case string: fmt_string(fi, a, verb) case cstring: fmt_cstring(fi, a, verb) + case string16: fmt_string16(fi, a, verb) + case cstring16: fmt_cstring16(fi, a, verb) + case typeid: reflect.write_typeid(fi.writer, a, &fi.n) case i16le: fmt_int(fi, u64(a), true, 16, verb) diff --git a/core/hash/crc32.odin b/core/hash/crc32.odin index a7f68207e..0a4617f6d 100644 --- a/core/hash/crc32.odin +++ b/core/hash/crc32.odin @@ -317,85 +317,4 @@ crc32_table := [8][256]u32{ 0xff6b144a, 0x33c114d4, 0xbd4e1337, 0x71e413a9, 0x7b211ab0, 0xb78b1a2e, 0x39041dcd, 0xf5ae1d53, 0x2c8e0fff, 0xe0240f61, 0x6eab0882, 0xa201081c, 0xa8c40105, 0x646e019b, 0xeae10678, 0x264b06e6, }, -} - - - -/* -@(optimization_mode="speed") -crc32 :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 { - result := ~u32(seed); - #no_bounds_check for b in data { - result = result>>8 ~ _crc32_table[(result ~ u32(b)) & 0xff]; - } - return ~result; -} - - -@private _crc32_table := [256]u32{ - 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, - 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, - 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, - 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, - 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, - 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, - 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, - 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, - 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, - 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, - 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, - 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, - 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, - 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, - 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, - 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, - 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, - 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, - 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, - 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, - 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, - 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, - 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, - 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, - 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, - 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, - 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, - 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, - 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, - 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, - 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, - 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, - 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, - 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, - 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, - 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, - 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, - 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, - 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, - 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, - 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, - 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, - 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, - 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, - 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, - 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, - 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, - 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, - 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, - 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, - 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, - 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, - 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, - 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, - 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, - 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, - 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, - 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, - 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, - 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, - 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, - 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, - 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, - 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, -}; -*/ +}
\ No newline at end of file diff --git a/core/hash/hash.odin b/core/hash/hash.odin index 45f524d8a..6c048c05b 100644 --- a/core/hash/hash.odin +++ b/core/hash/hash.odin @@ -127,7 +127,7 @@ jenkins :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 { } @(optimization_mode="favor_size") -murmur32 :: proc "contextless" (data: []byte, seed := u32(0)) -> u32 { +murmur32 :: proc "contextless" (data: []byte, seed := u32(0x9747b28c)) -> u32 { c1_32: u32 : 0xcc9e2d51 c2_32: u32 : 0x1b873593 diff --git a/core/hash/xxhash/common.odin b/core/hash/xxhash/common.odin index adfc1bac2..636393b52 100644 --- a/core/hash/xxhash/common.odin +++ b/core/hash/xxhash/common.odin @@ -101,3 +101,35 @@ XXH64_read64 :: #force_inline proc(buf: []u8, alignment := Alignment.Unaligned) return u64(b) } } + +XXH64_read64_simd :: #force_inline proc(buf: []$E, $W: uint, alignment := Alignment.Unaligned) -> (res: #simd[W]u64) { + if alignment == .Aligned { + res = (^#simd[W]u64)(raw_data(buf))^ + } else { + res = intrinsics.unaligned_load((^#simd[W]u64)(raw_data(buf))) + } + + when ODIN_ENDIAN == .Big { + bytes := transmute(#simd[W*8]u8)res + bytes = intrinsics.simd_lanes_reverse(bytes) + res = transmute(#simd[W]u64)bytes + res = intrinsics.simd_lanes_reverse(res) + } + return +} + +XXH64_write64_simd :: #force_inline proc(buf: []$E, value: $V/#simd[$W]u64, alignment := Alignment.Unaligned) { + value := value + when ODIN_ENDIAN == .Big { + bytes := transmute(#simd[W*8]u8)value + bytes = intrinsics.simd_lanes_reverse(bytes) + value = transmute(#simd[W]u64)bytes + value = intrinsics.simd_lanes_reverse(value) + } + + if alignment == .Aligned { + (^V)(raw_data(buf))^ = value + } else { + intrinsics.unaligned_store((^V)(raw_data(buf)), value) + } +} diff --git a/core/hash/xxhash/xxhash_3.odin b/core/hash/xxhash/xxhash_3.odin index 293e98528..fe92f16d9 100644 --- a/core/hash/xxhash/xxhash_3.odin +++ b/core/hash/xxhash/xxhash_3.odin @@ -52,6 +52,7 @@ XXH3_SECRET_SIZE_MIN :: 136 #assert(len(XXH3_kSecret) == 192 && len(XXH3_kSecret) > XXH3_SECRET_SIZE_MIN) XXH_ACC_ALIGN :: 8 /* scalar */ +XXH_MAX_WIDTH :: #config(XXH_MAX_WIDTH, 512) / 64 /* This is the optimal update size for incremental hashing. @@ -62,10 +63,11 @@ XXH3_INTERNAL_BUFFER_SIZE :: 256 Streaming state. IMPORTANT: This structure has a strict alignment requirement of 64 bytes!! ** - Do not allocate this with `make()` or `new`, it will not be sufficiently aligned. - Use`XXH3_create_state` and `XXH3_destroy_state, or stack allocation. + Default allocators will align it correctly if created via `new`, as will + placing this struct on the stack, but if using a custom allocator make sure + that it handles the alignment correctly! */ -XXH3_state :: struct { +XXH3_state :: struct #align(64) { acc: [8]u64, custom_secret: [XXH_SECRET_DEFAULT_SIZE]u8, buffer: [XXH3_INTERNAL_BUFFER_SIZE]u8, @@ -380,7 +382,6 @@ XXH3_INIT_ACC :: [XXH_ACC_NB]xxh_u64{ XXH_SECRET_MERGEACCS_START :: 11 -@(optimization_mode="favor_size") XXH3_hashLong_128b_internal :: #force_inline proc( input: []u8, secret: []u8, @@ -408,7 +409,6 @@ XXH3_hashLong_128b_internal :: #force_inline proc( /* * It's important for performance that XXH3_hashLong is not inlined. */ -@(optimization_mode="favor_size") XXH3_hashLong_128b_default :: #force_no_inline proc(input: []u8, seed: xxh_u64, secret: []u8) -> (res: XXH3_128_hash) { return XXH3_hashLong_128b_internal(input, XXH3_kSecret[:], XXH3_accumulate_512, XXH3_scramble_accumulator) } @@ -416,12 +416,10 @@ XXH3_hashLong_128b_default :: #force_no_inline proc(input: []u8, seed: xxh_u64, /* * It's important for performance that XXH3_hashLong is not inlined. */ -@(optimization_mode="favor_size") XXH3_hashLong_128b_withSecret :: #force_no_inline proc(input: []u8, seed: xxh_u64, secret: []u8) -> (res: XXH3_128_hash) { return XXH3_hashLong_128b_internal(input, secret, XXH3_accumulate_512, XXH3_scramble_accumulator) } -@(optimization_mode="favor_size") XXH3_hashLong_128b_withSeed_internal :: #force_inline proc( input: []u8, seed: xxh_u64, secret: []u8, f_acc512: XXH3_accumulate_512_f, @@ -442,7 +440,6 @@ XXH3_hashLong_128b_withSeed_internal :: #force_inline proc( /* * It's important for performance that XXH3_hashLong is not inlined. */ - @(optimization_mode="favor_size") XXH3_hashLong_128b_withSeed :: #force_no_inline proc(input: []u8, seed: xxh_u64, secret: []u8) -> (res: XXH3_128_hash) { return XXH3_hashLong_128b_withSeed_internal(input, seed, secret, XXH3_accumulate_512, XXH3_scramble_accumulator , XXH3_init_custom_secret) } @@ -477,7 +474,7 @@ XXH3_128bits_internal :: #force_inline proc( /* === Public XXH128 API === */ @(optimization_mode="favor_size") XXH3_128_default :: proc(input: []u8) -> (hash: XXH3_128_hash) { - return XXH3_128bits_internal(input, 0, XXH3_kSecret[:], XXH3_hashLong_128b_withSeed) + return XXH3_128bits_internal(input, 0, XXH3_kSecret[:], XXH3_hashLong_128b_default) } @(optimization_mode="favor_size") @@ -733,10 +730,6 @@ XXH3_accumulate_512_f :: #type proc(acc: []xxh_u64, input: []u8, secret: XXH3_scramble_accumulator_f :: #type proc(acc: []xxh_u64, secret: []u8) XXH3_init_custom_secret_f :: #type proc(custom_secret: []u8, seed64: xxh_u64) -XXH3_accumulate_512 : XXH3_accumulate_512_f = XXH3_accumulate_512_scalar -XXH3_scramble_accumulator : XXH3_scramble_accumulator_f = XXH3_scramble_accumulator_scalar -XXH3_init_custom_secret : XXH3_init_custom_secret_f = XXH3_init_custom_secret_scalar - /* scalar variants - universal */ @(optimization_mode="favor_size") XXH3_accumulate_512_scalar :: #force_inline proc(acc: []xxh_u64, input: []u8, secret: []u8) { @@ -751,7 +744,7 @@ XXH3_accumulate_512_scalar :: #force_inline proc(acc: []xxh_u64, input: []u8, se sec := XXH64_read64(xsecret[8 * i:]) data_key := data_val ~ sec xacc[i ~ 1] += data_val /* swap adjacent lanes */ - xacc[i ] += u64(u128(u32(data_key)) * u128(u64(data_key >> 32))) + xacc[i ] += u64(u32(data_key)) * u64(data_key >> 32) } } @@ -785,6 +778,87 @@ XXH3_init_custom_secret_scalar :: #force_inline proc(custom_secret: []u8, seed64 } } +/* generalized SIMD variants */ +XXH3_accumulate_512_simd_generic :: #force_inline proc(acc: []xxh_u64, input: []u8, secret: []u8, $W: uint) { + u32xW :: #simd[W]u32 + u64xW :: #simd[W]u64 + + #no_bounds_check for i in uint(0)..<XXH_ACC_NB/W { + data_val := XXH64_read64_simd(input[8 * W * i:], W) + sec := XXH64_read64_simd(secret[8 * W * i:], W) + data_key := data_val ~ sec + + // Swap adjacent lanes + when W == 2 { + data_val = swizzle(data_val, 1, 0) + } else when W == 4 { + data_val = swizzle(data_val, 1, 0, 3, 2) + } else when W == 8 { + data_val = swizzle(data_val, 1, 0, 3, 2, 5, 4, 7, 6) + } else { + #panic("Unsupported vector size!") + } + + a := XXH64_read64_simd(acc[W * i:], W) + a += data_val + a += u64xW(u32xW(data_key)) * intrinsics.simd_shr(data_key, 32) + XXH64_write64_simd(acc[W * i:], a) + } +} + +XXH3_scramble_accumulator_simd_generic :: #force_inline proc(acc: []xxh_u64, secret: []u8, $W: uint) { + u64xW :: #simd[W]u64 + #no_bounds_check for i in uint(0)..<XXH_ACC_NB/W { + key64 := XXH64_read64_simd(secret[8 * W * i:], W) + acc64 := XXH64_read64_simd(acc[W * i:], W) + acc64 ~= intrinsics.simd_shr(acc64, 47) + acc64 ~= key64 + acc64 *= XXH_PRIME32_1 + XXH64_write64_simd(acc[W * i:], acc64) + } +} + +XXH3_init_custom_secret_simd_generic :: #force_inline proc(custom_secret: []u8, seed64: xxh_u64, $W: uint) { + u64xW :: #simd[W]u64 + + seedVec := u64xW(seed64) + for i in 0..<W/2 { + j := 2*i + 1 + seedVec = intrinsics.simd_replace(seedVec, j, -intrinsics.simd_extract(seedVec, j)) + } + + nbRounds := XXH_SECRET_DEFAULT_SIZE / 8 / W + #no_bounds_check for i in uint(0)..<nbRounds { + block := XXH64_read64_simd(XXH3_kSecret[8 * W * i:], W) + block += seedVec + XXH64_write64_simd(custom_secret[8 * W * i:], block) + } +} + +XXH3_accumulate_512 :: #force_inline proc(acc: []xxh_u64, input: []u8, secret: []u8) { + when XXH_NATIVE_WIDTH > 1 { + XXH3_accumulate_512_simd_generic(acc, input, secret, XXH_NATIVE_WIDTH) + } else { + XXH3_accumulate_512_scalar(acc, input, secret) + } +} + +XXH3_scramble_accumulator :: #force_inline proc(acc: []xxh_u64, secret: []u8) { + when XXH_NATIVE_WIDTH > 1 { + XXH3_scramble_accumulator_simd_generic(acc, secret, XXH_NATIVE_WIDTH) + } else { + XXH3_scramble_accumulator_scalar(acc, secret) + } +} + +XXH3_init_custom_secret :: #force_inline proc(custom_secret: []u8, seed64: xxh_u64) { + when XXH_NATIVE_WIDTH > 1 { + XXH3_init_custom_secret_simd_generic(custom_secret, seed64, XXH_NATIVE_WIDTH) + } else { + XXH3_init_custom_secret_scalar(custom_secret, seed64) + } +} + XXH_PREFETCH_DIST :: 320 /* @@ -796,7 +870,7 @@ XXH_PREFETCH_DIST :: 320 XXH3_accumulate :: #force_inline proc( acc: []xxh_u64, input: []u8, secret: []u8, nbStripes: uint, f_acc512: XXH3_accumulate_512_f) { - for n := uint(0); n < nbStripes; n += 1 { + #no_bounds_check for n := uint(0); n < nbStripes; n += 1 { when !XXH_DISABLE_PREFETCH { in_ptr := &input[n * XXH_STRIPE_LEN] prefetch(in_ptr, XXH_PREFETCH_DIST) @@ -869,7 +943,6 @@ XXH3_hashLong_64b_internal :: #force_inline proc(input: []u8, secret: []u8, /* It's important for performance that XXH3_hashLong is not inlined. */ -@(optimization_mode="favor_size") XXH3_hashLong_64b_withSecret :: #force_no_inline proc(input: []u8, seed64: xxh_u64, secret: []u8) -> (hash: xxh_u64) { return XXH3_hashLong_64b_internal(input, secret, XXH3_accumulate_512, XXH3_scramble_accumulator) } @@ -881,24 +954,11 @@ XXH3_hashLong_64b_withSecret :: #force_no_inline proc(input: []u8, seed64: xxh_u This variant enforces that the compiler can detect that, and uses this opportunity to streamline the generated code for better performance. */ -@(optimization_mode="favor_size") XXH3_hashLong_64b_default :: #force_no_inline proc(input: []u8, seed64: xxh_u64, secret: []u8) -> (hash: xxh_u64) { return XXH3_hashLong_64b_internal(input, XXH3_kSecret[:], XXH3_accumulate_512, XXH3_scramble_accumulator) } -/* - XXH3_hashLong_64b_withSeed(): - Generate a custom key based on alteration of default XXH3_kSecret with the seed, - and then use this key for long mode hashing. - - This operation is decently fast but nonetheless costs a little bit of time. - Try to avoid it whenever possible (typically when seed==0). - - It's important for performance that XXH3_hashLong is not inlined. Not sure - why (uop cache maybe?), but the difference is large and easily measurable. -*/ -@(optimization_mode="favor_size") -XXH3_hashLong_64b_withSeed_internal :: #force_no_inline proc( +XXH3_hashLong_64b_withSeed_internal :: #force_inline proc( input: []u8, seed: xxh_u64, f_acc512: XXH3_accumulate_512_f, @@ -915,9 +975,16 @@ XXH3_hashLong_64b_withSeed_internal :: #force_no_inline proc( } /* - It's important for performance that XXH3_hashLong is not inlined. + XXH3_hashLong_64b_withSeed(): + Generate a custom key based on alteration of default XXH3_kSecret with the seed, + and then use this key for long mode hashing. + + This operation is decently fast but nonetheless costs a little bit of time. + Try to avoid it whenever possible (typically when seed==0). + + It's important for performance that XXH3_hashLong is not inlined. Not sure + why (uop cache maybe?), but the difference is large and easily measurable. */ -@(optimization_mode="favor_size") XXH3_hashLong_64b_withSeed :: #force_no_inline proc(input: []u8, seed: xxh_u64, secret: []u8) -> (hash: xxh_u64) { return XXH3_hashLong_64b_withSeed_internal(input, seed, XXH3_accumulate_512, XXH3_scramble_accumulator, XXH3_init_custom_secret) } @@ -926,7 +993,7 @@ XXH3_hashLong_64b_withSeed :: #force_no_inline proc(input: []u8, seed: xxh_u64, XXH3_hashLong64_f :: #type proc(input: []u8, seed: xxh_u64, secret: []u8) -> (res: xxh_u64) @(optimization_mode="favor_size") -XXH3_64bits_internal :: proc(input: []u8, seed: xxh_u64, secret: []u8, f_hashLong: XXH3_hashLong64_f) -> (hash: xxh_u64) { +XXH3_64bits_internal :: #force_inline proc(input: []u8, seed: xxh_u64, secret: []u8, f_hashLong: XXH3_hashLong64_f) -> (hash: xxh_u64) { assert(len(secret) >= XXH3_SECRET_SIZE_MIN) /* If an action is to be taken if len(secret) condition is not respected, it should be done here. diff --git a/core/hash/xxhash/xxhash_3_intel.odin b/core/hash/xxhash/xxhash_3_intel.odin new file mode 100644 index 000000000..3397fcef5 --- /dev/null +++ b/core/hash/xxhash/xxhash_3_intel.odin @@ -0,0 +1,13 @@ +#+build amd64, i386 +package xxhash + +import "base:intrinsics" + +@(private="file") SSE2_FEATURES :: "sse2" +@(private="file") AVX2_FEATURES :: "avx2" +@(private="file") AVX512_FEATURES :: "avx512dq,evex512" + +XXH_NATIVE_WIDTH :: min(XXH_MAX_WIDTH, + 8 when intrinsics.has_target_feature(AVX512_FEATURES) else + 4 when intrinsics.has_target_feature(AVX2_FEATURES) else + 2 when intrinsics.has_target_feature(SSE2_FEATURES) else 1) diff --git a/core/hash/xxhash/xxhash_3_other.odin b/core/hash/xxhash/xxhash_3_other.odin new file mode 100644 index 000000000..e1a5d0474 --- /dev/null +++ b/core/hash/xxhash/xxhash_3_other.odin @@ -0,0 +1,8 @@ +#+build !amd64 +#+build !i386 +package xxhash + +import "base:runtime" + +XXH_NATIVE_WIDTH :: min(XXH_MAX_WIDTH, + 2 when runtime.HAS_HARDWARE_SIMD else 1) diff --git a/core/image/bmp/bmp.odin b/core/image/bmp/bmp.odin index 057c2ffa0..d5a094e83 100644 --- a/core/image/bmp/bmp.odin +++ b/core/image/bmp/bmp.odin @@ -741,6 +741,6 @@ destroy :: proc(img: ^Image) { } @(init, private) -_register :: proc() { +_register :: proc "contextless" () { image.register(.BMP, load_from_bytes, destroy) } diff --git a/core/image/common.odin b/core/image/common.odin index 690ebc045..4014e2ae6 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -64,8 +64,16 @@ Image_Metadata :: union #shared_nil { ^QOI_Info, ^TGA_Info, ^BMP_Info, + ^JPEG_Info, } +Exif :: struct { + byte_order: enum { + little_endian, + big_endian, + }, + data: []u8 `fmt:"-"`, +} /* @@ -112,8 +120,7 @@ Image_Option: `.alpha_drop_if_present` If the image has an alpha channel, drop it. - You may want to use `.alpha_ - tiply` in this case. + You may want to use `.alpha_premultiply` in this case. NOTE: For PNG, this also skips handling of the tRNS chunk, if present, unless you select `alpha_premultiply`. @@ -163,6 +170,7 @@ Error :: union #shared_nil { PNG_Error, QOI_Error, BMP_Error, + JPEG_Error, compress.Error, compress.General_Error, @@ -575,6 +583,140 @@ TGA_Info :: struct { extension: Maybe(TGA_Extension), } + +/* + JPEG-specific +*/ +JFIF_Magic := [?]byte{0x4A, 0x46, 0x49, 0x46} // "JFIF" +JFXX_Magic := [?]byte{0x4A, 0x46, 0x58, 0x58} // "JFXX" +Exif_Magic := [?]byte{0x45, 0x78, 0x69, 0x66} // "Exif" + +JPEG_Error :: enum { + None = 0, + Duplicate_SOI_Marker, + Invalid_JFXX_Extension_Code, + Encountered_SOS_Before_SOF, + Invalid_Quantization_Table_Precision, + Invalid_Quantization_Table_Index, + Invalid_Huffman_Coefficient_Type, + Invalid_Huffman_Table_Index, + Unsupported_Frame_Type, + Invalid_Frame_Bit_Depth_Combo, + Invalid_Sampling_Factor, + Unsupported_12_Bit_Depth, + Multiple_SOS_Markers, + Encountered_RST_Marker_Outside_ECS, + Extra_Data_After_SOS, // Image seemed to have decoded okay, but there's more data after SOS + Invalid_Thumbnail_Size, + Huffman_Symbols_Exceeds_Max, +} + +JFIF_Unit :: enum byte { + None = 0, + Dots_Per_Inch = 1, + Dots_Per_Centimeter = 2, +} + +JFIF_APP0 :: struct { + version: u16be, + x_density: u16be, + y_density: u16be, + units: JFIF_Unit, + x_thumbnail: u8, + y_thumbnail: u8, + greyscale_thumbnail: bool, + thumbnail: []RGB_Pixel `fmt:"-"`, +} + +JFXX_APP0 :: struct { + extension_code: JFXX_Extension_Code, + x_thumbnail: u8, + y_thumbnail: u8, + thumbnail: []byte `fmt:"-"`, +} + +JFXX_Extension_Code :: enum u8 { + Thumbnail_JPEG = 0x10, + Thumbnail_1_Byte_Palette = 0x11, + Thumbnail_3_Byte_RGB = 0x13, +} + +JPEG_Marker :: enum u8 { + SOF0 = 0xC0, // Baseline sequential DCT + SOF1 = 0xC1, // Extended sequential DCT + SOF2 = 0xC2, // Progressive DCT + SOF3 = 0xC3, // Lossless (sequential) + SOF5 = 0xC5, // Differential sequential DCT + SOF6 = 0xC6, // Differential progressive DCT + SOF7 = 0xC7, // Differential lossless (sequential) + SOF9 = 0xC9, // Extended sequential DCT, Arithmetic coding + SOF10 = 0xCA, // Progressive DCT, Arithmetic coding + SOF11 = 0xCB, // Lossless (sequential), Arithmetic coding + SOF13 = 0xCD, // Differential sequential DCT, Arithmetic coding + SOF14 = 0xCE, // Differential progressive DCT, Arithmetic coding + SOF15 = 0xCF, // Differential lossless (sequential), Arithmetic coding + + DHT = 0xC4, + JPG = 0xC8, + DAC = 0xCC, + RST0 = 0xD0, + RST1 = 0xD1, + RST2 = 0xD2, + RST3 = 0xD3, + RST4 = 0xD4, + RST5 = 0xD5, + RST6 = 0xD6, + RST7 = 0xD7, + SOI = 0xD8, + EOI = 0xD9, + SOS = 0xDA, + DQT = 0xDB, + DNL = 0xDC, + DRI = 0xDD, + DHP = 0xDE, + EXP = 0xDF, + APP0 = 0xE0, + APP1 = 0xE1, + APP2 = 0xE2, + APP3 = 0xE3, + APP4 = 0xE4, + APP5 = 0xE5, + APP6 = 0xE6, + APP7 = 0xE7, + APP8 = 0xE8, + APP9 = 0xE9, + APP10 = 0xEA, + APP11 = 0xEB, + APP12 = 0xEC, + APP13 = 0xED, + APP14 = 0xEE, + APP15 = 0xEF, + JPG0 = 0xF0, + JPG1 = 0xF1, + JPG2 = 0xF2, + JPG3 = 0xF3, + JPG4 = 0xF4, + JPG5 = 0xF5, + JPG6 = 0xF6, + JPG7 = 0xF7, + JPG8 = 0xF8, + JPG9 = 0xF9, + JPG10 = 0xFA, + JPG11 = 0xFB, + JPG12 = 0xFC, + JPG13 = 0xFD, + COM = 0xFE, + TEM = 0x01, +} + +JPEG_Info :: struct { + jfif_app0: Maybe(JFIF_APP0), + jfxx_app0: Maybe(JFXX_APP0), + comments: [dynamic]string, + exif: [dynamic]Exif, + frame_type: JPEG_Marker, +} + // Function to help with image buffer calculations compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes := int(0)) -> (size: int) { size = ((((channels * width * depth) + 7) >> 3) + extra_row_bytes) * height diff --git a/core/image/general.odin b/core/image/general.odin index e92b54f18..1662cf14e 100644 --- a/core/image/general.odin +++ b/core/image/general.odin @@ -10,13 +10,13 @@ Destroy_Proc :: #type proc(img: ^Image) _internal_loaders: [Which_File_Type]Loader_Proc _internal_destroyers: [Which_File_Type]Destroy_Proc -register :: proc(kind: Which_File_Type, loader: Loader_Proc, destroyer: Destroy_Proc) { - assert(loader != nil) - assert(destroyer != nil) - assert(_internal_loaders[kind] == nil) +register :: proc "contextless" (kind: Which_File_Type, loader: Loader_Proc, destroyer: Destroy_Proc) { + assert_contextless(loader != nil) + assert_contextless(destroyer != nil) + assert_contextless(_internal_loaders[kind] == nil) _internal_loaders[kind] = loader - assert(_internal_destroyers[kind] == nil) + assert_contextless(_internal_destroyers[kind] == nil) _internal_destroyers[kind] = destroyer } @@ -147,7 +147,7 @@ which_bytes :: proc(data: []byte) -> Which_File_Type { return .JPEG case s[:3] == "\xff\xd8\xff": switch s[3] { - case 0xdb, 0xee, 0xe1, 0xe0: + case 0xdb, 0xee, 0xe1, 0xe0, 0xfe, 0xed: return .JPEG } switch { diff --git a/core/image/jpeg/jpeg.odin b/core/image/jpeg/jpeg.odin new file mode 100644 index 000000000..818bd13d5 --- /dev/null +++ b/core/image/jpeg/jpeg.odin @@ -0,0 +1,1106 @@ +package jpeg + +import "core:bytes" +import "core:compress" +import "core:math" +import "core:mem" +import "core:image" +import "core:slice" +import "core:strings" + +Image :: image.Image +Error :: image.Error +Options :: image.Options + +HUFFMAN_MAX_SYMBOLS :: 176 +HUFFMAN_MAX_BITS :: 16 +// 768 bytes of 24-bit RGB values. +THUMBNAIL_PALETTE_SIZE :: 768 +BLOCK_SIZE :: 8 +COEFFICIENT_COUNT :: BLOCK_SIZE * BLOCK_SIZE +SEGMENT_MAX_SIZE :: 65533 + +Coefficient :: enum u8 { + DC, + AC, +} + +Component :: enum u8 { + Y = 1, + Cb = 2, + Cr = 3, +} + +Huffman_Table :: struct { + symbols: [HUFFMAN_MAX_SYMBOLS]byte, + codes: [HUFFMAN_MAX_SYMBOLS]u32, + offsets: [HUFFMAN_MAX_BITS + 1]byte, +} + +Quantization_Table :: [COEFFICIENT_COUNT]u16be + +Color_Component :: struct { + dc_table_idx: u8, + ac_table_idx: u8, + quantization_table_idx: u8, + v_sampling_factor: int, + h_sampling_factor: int, +} + +// 8x8 block of pixels +Block :: [Component][COEFFICIENT_COUNT]i16 + +@(private="file") +zigzag := [?]byte{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, +} + +@(optimization_mode="favor_size", private="file") +refill_msb :: #force_inline proc(z: ^compress.Context_Memory_Input, width := i8(48)) { + refill := u64(width) + b := u64(0) + + if z.num_bits > refill { + return + } + + for { + if len(z.input_data) != 0 { + b = u64(z.input_data[0]) + + if len(z.input_data) > 1 && b == 0xFF { + next := u64(z.input_data[1]) + + if next == 0x00 { + // 0x00 is used as a stuffing to indicate that the 0xFF is part of the data and not + // the beginning of a marker + z.input_data = z.input_data[2:] + } else if next >= cast(u64)image.JPEG_Marker.RST0 && next <= cast(u64)image.JPEG_Marker.RST7 { + // Skip any RSTn markers if we encounter them + if len(z.input_data) > 2 { + b = u64(z.input_data[2]) + z.input_data = z.input_data[3:] + } else { + b = 0 + } + } + } else { + z.input_data = z.input_data[1:] + } + } else { + b = 0 + } + + z.code_buffer |= ((b << 56) >> u8(z.num_bits)) + z.num_bits += 8 + if z.num_bits > refill { + break + } + } +} + +@(optimization_mode="favor_size", private="file") +consume_bits_msb :: #force_inline proc(z: ^compress.Context_Memory_Input, width: u8) { + z.code_buffer <<= width + z.num_bits -= u64(width) +} + +@(private="file") +byte_align :: #force_inline proc(z: ^compress.Context_Memory_Input) { + skip := z.num_bits % 8 + consume_bits_msb(z, cast(u8)skip) +} + +@(optimization_mode="favor_size", private="file") +peek_bits_msb :: #force_inline proc(z: ^compress.Context_Memory_Input, width: u8) -> u32 { + if z.num_bits < u64(width) { + refill_msb(z) + } + return u32((z.code_buffer &~ (max(u64) >> width)) >> (64 - width)) +} + +@(optimization_mode="favor_size", private="file") +read_bits_msb :: #force_inline proc(z: ^compress.Context_Memory_Input, width: u8) -> u32 { + k := #force_inline peek_bits_msb(z, width) + #force_inline consume_bits_msb(z, width) + return k +} + +load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { + ctx := &compress.Context_Memory_Input{ + input_data = data, + } + + img, err = load_from_context(ctx, options, allocator) + return img, err +} + +@(private="file") +get_symbol :: proc(ctx: ^$C, huffman_table: Huffman_Table) -> byte { + possible_code: u32 = 0 + + for i in 0..<HUFFMAN_MAX_BITS { + bit := read_bits_msb(ctx, 1) + possible_code = (possible_code << 1) | bit + + for j := huffman_table.offsets[i]; j < huffman_table.offsets[i + 1]; j += 1 { + if possible_code == huffman_table.codes[j] { + return huffman_table.symbols[j] + } + } + } + + return 0 +} + +load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { + context.allocator = allocator + options := options + + // Precalculate IDCT scaling factors + m0 := 2.0 * math.cos_f32(1.0 / 16.0 * 2.0 * math.PI) + m1 := 2.0 * math.cos_f32(2.0 / 16.0 * 2.0 * math.PI) + m3 := 2.0 * math.cos_f32(2.0 / 16.0 * 2.0 * math.PI) + m5 := 2.0 * math.cos_f32(3.0 / 16.0 * 2.0 * math.PI) + m2 := m0 - m5 + m4 := m0 + m5 + + s0 := math.cos_f32(0.0 / 16.0 * math.PI) / math.sqrt_f32(8.0) + s1 := math.cos_f32(1.0 / 16.0 * math.PI) / 2.0 + s2 := math.cos_f32(2.0 / 16.0 * math.PI) / 2.0 + s3 := math.cos_f32(3.0 / 16.0 * math.PI) / 2.0 + s4 := math.cos_f32(4.0 / 16.0 * math.PI) / 2.0 + s5 := math.cos_f32(5.0 / 16.0 * math.PI) / 2.0 + s6 := math.cos_f32(6.0 / 16.0 * math.PI) / 2.0 + s7 := math.cos_f32(7.0 / 16.0 * math.PI) / 2.0 + + if .info in options { + options += {.return_metadata, .do_not_decompress_image} + options -= {.info} + } + + if .return_header in options && .return_metadata in options { + options -= {.return_header} + } + + if .do_not_expand_channels in options || .do_not_expand_grayscale in options { + return img, .Unsupported_Option + } + + first := compress.read_u8(ctx) or_return + soi := cast(image.JPEG_Marker)compress.read_u8(ctx) or_return + if first != 0xFF && soi != .SOI { + return img, .Invalid_Signature + } + + img = new(Image) or_return + img.which = .JPEG + + expect_EOI := false + zero_based_components := false + huffman: [Coefficient][4]Huffman_Table + quantization: [4]Quantization_Table + color_components: [Component]Color_Component + restart_interval: int + // Image width and height in MCUs + mcu_width: int + mcu_height: int + // Image width and height in blocks + block_width: int + block_height: int + blocks: []Block + defer delete(blocks) + + loop: for { + // Loop until we find 0xFF. + first = compress.read_u8(ctx) or_return + (first == 0xFF) or_continue + + marker := cast(image.JPEG_Marker)compress.read_u8(ctx) or_return + if expect_EOI && marker != .EOI { + return img, .Extra_Data_After_SOS + } + #partial switch marker { + case cast(image.JPEG_Marker)0xFF: + // If we encounter multiple FF bytes then just skip them + continue + case .SOI: + return img, .Duplicate_SOI_Marker + case .APP0: + ident := make([dynamic]byte, 0, 16, context.temp_allocator) or_return + length := cast(int)((compress.read_data(ctx, u16be) or_return) - 2) + for { + b := compress.read_u8(ctx) or_return + if b == 0x00 { + break + } + append(&ident, b) or_return + } + if slice.equal(ident[:], image.JFIF_Magic[:]) { + if length != 14 { + // Malformed APP0. Skip it + compress.read_slice(ctx, length - len(ident) - 1) or_return + continue + } + + version := compress.read_data(ctx, u16be) or_return + units := cast(image.JFIF_Unit)(compress.read_u8(ctx) or_return) + x_density := compress.read_data(ctx, u16be) or_return + y_density := compress.read_data(ctx, u16be) or_return + x_thumbnail := cast(int)compress.read_u8(ctx) or_return + y_thumbnail := cast(int)compress.read_u8(ctx) or_return + thumbnail: []image.RGB_Pixel + + if x_thumbnail * y_thumbnail != 0 { + greyscale_thumbnail := false + thumbnail_size := x_thumbnail * y_thumbnail * 3 + // According to the JFIF spec, the thumbnail should always be made of RGB pixels. + // But some jpegs encode single-channel thumbnails. + if thumbnail_size != length - 14 && thumbnail_size / 3 == length - 14 { + thumbnail_size = x_thumbnail * y_thumbnail + greyscale_thumbnail = true + } else { + return img, .Invalid_Thumbnail_Size + } + thumb_pixels := slice.reinterpret([]image.RGB_Pixel, compress.read_slice_from_memory(ctx, x_thumbnail * y_thumbnail) or_return) + + if .return_metadata in options { + thumbnail = make([]image.RGB_Pixel, x_thumbnail * y_thumbnail) or_return + copy(thumbnail, thumb_pixels) + + info: ^image.JPEG_Info + if img.metadata == nil { + info = new(image.JPEG_Info) or_return + } else { + info = img.metadata.(^image.JPEG_Info) + } + info.jfif_app0 = image.JFIF_APP0{ + version, + x_density, + y_density, + units, + cast(u8)x_thumbnail, + cast(u8)y_thumbnail, + greyscale_thumbnail, + thumbnail, + } + img.metadata = info + } + } + } else if slice.equal(ident[:], image.JFXX_Magic[:]) { + extension_code := cast(image.JFXX_Extension_Code)compress.read_u8(ctx) or_return + thumbnail: []byte + + switch extension_code { + // We return the JPEG-compressed bytes for this type of thumbnail. + // It's up to the user if they want to decode it by checking the extension code + // and calling image.load() on the thumbnail. + // Not sure where to document that though, maybe it's better if the thumbnail is always raw pixel data. + case .Thumbnail_JPEG: + // +1 for the NUL byte + thumbnail_len := length - (size_of(image.JFXX_Magic) + 1 + size_of(image.JFXX_Extension_Code)) + thumbnail_jpeg := compress.read_slice(ctx, thumbnail_len) or_return + + if .return_metadata in options { + thumbnail = make([]byte, thumbnail_len) or_return + copy(thumbnail, thumbnail_jpeg) + + info: ^image.JPEG_Info + if img.metadata == nil { + info = new(image.JPEG_Info) or_return + } else { + info = img.metadata.(^image.JPEG_Info) + } + info.jfxx_app0 = image.JFXX_APP0{ + extension_code, + 0, + 0, + thumbnail, + } + img.metadata = info + } + case .Thumbnail_3_Byte_RGB: + x_thumbnail := cast(int)compress.read_u8(ctx) or_return + y_thumbnail := cast(int)compress.read_u8(ctx) or_return + pixels := compress.read_slice(ctx, x_thumbnail * y_thumbnail * 3) or_return + + if .return_metadata in options { + thumbnail = make([]byte, x_thumbnail * y_thumbnail * 3) or_return + copy(thumbnail, pixels) + + info: ^image.JPEG_Info + if img.metadata == nil { + info = new(image.JPEG_Info) or_return + } else { + info = img.metadata.(^image.JPEG_Info) + } + info.jfxx_app0 = image.JFXX_APP0{ + extension_code, + cast(u8)x_thumbnail, + cast(u8)y_thumbnail, + thumbnail, + } + img.metadata = info + } + case .Thumbnail_1_Byte_Palette: // NOTE(illusionman1212): NOT TESTED. Couldn't find a jpeg to test this with. + x_thumbnail := cast(int)compress.read_u8(ctx) or_return + y_thumbnail := cast(int)compress.read_u8(ctx) or_return + palette := slice.reinterpret([]image.RGB_Pixel, compress.read_slice(ctx, THUMBNAIL_PALETTE_SIZE / 3) or_return) + old_pixels := compress.read_slice(ctx, x_thumbnail * y_thumbnail) or_return + + if .return_metadata in options { + pixels := make([]byte, x_thumbnail * y_thumbnail * 3) or_return + for i in 0..<x_thumbnail*y_thumbnail { + pixel := palette[old_pixels[i]] + pixels[i] = pixel.r + pixels[i + 1] = pixel.g + pixels[i + 2] = pixel.b + } + + info: ^image.JPEG_Info + if img.metadata == nil { + info = new(image.JPEG_Info) or_return + } else { + info = img.metadata.(^image.JPEG_Info) + } + info.jfxx_app0 = image.JFXX_APP0{ + extension_code, + cast(u8)x_thumbnail, + cast(u8)y_thumbnail, + pixels, + } + img.metadata = info + } + case: + return img, .Invalid_JFXX_Extension_Code + } + } else { + // - 1 for the NUL byte + compress.read_slice(ctx, length - len(ident) - 1) or_return + continue + } + case .APP1: // Metadata + length := cast(int)((compress.read_data(ctx, u16be) or_return) - 2) + if .return_metadata not_in options { + compress.read_slice(ctx, length) or_return + continue + } + info: ^image.JPEG_Info + if img.metadata == nil { + info = new(image.JPEG_Info) or_return + } else { + info = img.metadata.(^image.JPEG_Info) + } + + ident := make([dynamic]byte, 0, 16, context.temp_allocator) or_return + for { + b := compress.read_u8(ctx) or_return + if b == 0x00 { + break + } + append(&ident, b) or_return + } + + if slice.equal(ident[:], image.Exif_Magic[:]) { + // Padding byte according to section 4.7.2.2 in Exif spec 3.0 + compress.read_u8(ctx) or_return + + exif: image.Exif + peek := compress.peek_data(ctx, [4]byte) or_return + if peek[0] == 'M' && peek[1] == 'M' { + exif.byte_order = .big_endian + if peek[2] != 0 || peek[3] != 42 { + // - 2 for the NUL byte and padding byte + compress.read_slice(ctx, length - len(ident) - 2) or_return + continue + } + } else if peek[0] == 'I' && peek[1] == 'I' { + exif.byte_order = .little_endian + if peek[2] != 42 || peek[3] != 0 { + compress.read_slice(ctx, length - len(ident) - 2) or_return + continue + } + } else { + // If we can't determine the endianness then this Exif data is likely a continuation of the previous + // APP1 Exif data + + // We only treat it as such if a previous Exif entry exists and its data length is the max + if len(info.exif) > 0 && len(info.exif[len(info.exif) - 1].data) == SEGMENT_MAX_SIZE - len(ident) - 2 { + exif.byte_order = info.exif[len(info.exif) - 1].byte_order + } else { + compress.read_slice(ctx, length - len(ident) - 2) or_return + continue + } + } + + // - 2 for the NUL byte and padding byte + data := compress.read_slice(ctx, length - len(ident) - 2) or_return + exif.data = make([]byte, len(data)) or_return + copy(exif.data, data) + + append(&info.exif, exif) or_return + img.metadata = info + } else { + // - 1 for the NUL byte + compress.read_slice(ctx, length - len(ident) - 1) or_return + continue + } + case .COM: + length := (compress.read_data(ctx, u16be) or_return) - 2 + comment := string(compress.read_slice(ctx, cast(int)length) or_return) + if .return_metadata in options { + if info, ok := img.metadata.(^image.JPEG_Info); ok { + append(&info.comments, strings.clone(comment)) or_return + } + } + case .DQT: + length := cast(int)(compress.read_data(ctx, u16be) or_return) - 2 + + for length > 0 { + precision_and_index := compress.read_u8(ctx) or_return + precision := precision_and_index >> 4 + index := precision_and_index & 0xF + + if precision != 0 && precision != 1 { + return img, .Invalid_Quantization_Table_Precision + } + + if index < 0 || index > 3 { + return img, .Invalid_Quantization_Table_Index + } + + // When precision is 0, we read 64 u8s. + // when it's 1, we read 64 u16s. + table_bytes := 64 + if precision == 1 { + table_bytes = 128 + table := compress.read_slice(ctx, table_bytes) or_return + for v, i in slice.reinterpret([]u16be, table) { + quantization[index][i] = v + } + } else { + table := compress.read_slice(ctx, table_bytes) or_return + for v, i in table { + quantization[index][i] = cast(u16be)v + } + } + + length -= table_bytes + 1 + } + case .DHT: + length := (compress.read_data(ctx, u16be) or_return) - 2 + + for length > 0 { + type_index := compress.read_u8(ctx) or_return + type := cast(Coefficient)((type_index >> 4) & 0xF) + index := type_index & 0xF + + if type != .DC && type != .AC { + return img, .Invalid_Huffman_Coefficient_Type + } + + if index < 0 || index > 3 { + return img, .Invalid_Huffman_Table_Index + } + + lengths := compress.read_slice(ctx, HUFFMAN_MAX_BITS) or_return + num_symbols: u8 = 0 + for length, i in lengths { + num_symbols += length + huffman[type][index].offsets[i + 1] = num_symbols + } + + if num_symbols > HUFFMAN_MAX_SYMBOLS { + return img, .Huffman_Symbols_Exceeds_Max + } + + symbols := compress.read_slice(ctx, cast(int)num_symbols) or_return + copy(huffman[type][index].symbols[:], symbols) + + length -= cast(u16be)(1 + HUFFMAN_MAX_BITS + num_symbols) + + code: u32 = 0 + for i in 0..<HUFFMAN_MAX_BITS { + for j := huffman[type][index].offsets[i]; j < huffman[type][index].offsets[i + 1]; j += 1 { + huffman[type][index].codes[j] = code + code += 1 + } + code <<= 1 + } + } + case .EOI: + break loop + case .DRI: + // Length + compress.read_data(ctx, u16be) or_return + restart_interval = cast(int)compress.read_data(ctx, u16be) or_return + case .RST0..=.RST7: // Handled by the bit reader. These shouldn't appear outside the entropy coded stream. + return img, .Encountered_RST_Marker_Outside_ECS + case .SOF0, .SOF1: // Baseline sequential DCT, and extended sequential DCT + if img.channels != 0 { + return img, .Multiple_SOS_Markers + } + + // Length + compress.read_data(ctx, u16be) or_return + precision := compress.read_u8(ctx) or_return + height := compress.read_data(ctx, u16be) or_return + width := compress.read_data(ctx, u16be) or_return + components := compress.read_u8(ctx) or_return + img.width = cast(int)width + img.height = cast(int)height + img.depth = cast(int)precision + img.channels = cast(int)components + + // TODO: 12-bit precision is valid too but we don't support it. + if precision == 12 { + return img, .Unsupported_12_Bit_Depth + } + if precision != 8 { + return img, .Invalid_Frame_Bit_Depth_Combo + } + + // TODO: spec allows for the height to be 0 on the condition that a DNL marker MUST exist to define + // how many lines in the frame we have. + // ISO/IEC 10918-1: 1993. + // Section B.2.5 + if img.width == 0 || img.height == 0 { + return img, .Invalid_Image_Dimensions + } + + if u128(img.width) * u128(img.height) > image.MAX_DIMENSIONS { + return img, .Image_Dimensions_Too_Large + } + + // TODO: Some JPEGs use CMYK as the color model which means there will be 4 components + if components != 1 && components != 3 { + return img, .Invalid_Number_Of_Channels + } + + if img.metadata != nil { + info := img.metadata.(^image.JPEG_Info) + info.frame_type = marker + } + + mcu_width = (img.width + 7) / BLOCK_SIZE + mcu_height = (img.height + 7) / BLOCK_SIZE + block_width = mcu_width + block_height = mcu_height + + for _ in 0..<components { + id := cast(Component)compress.read_u8(ctx) or_return + + if id == Component(0) { + zero_based_components = true + } + + if zero_based_components { + id += Component(1) + } + + // TODO: while others that use CMYK have these IDs 67, 77, 89, 75 which are CMYK in ASCII + // TODO: even more weird ids. 82, 71, 66 which is RGB in ASCII + if id < .Y || id > .Cr { + return img, .Image_Does_Not_Adhere_to_Spec + } + + h_v_factors := compress.read_u8(ctx) or_return + horizontal_sampling := h_v_factors >> 4 + vertical_sampling := h_v_factors & 0xF + + // TODO: spec says the range for the sampling factors is 1-4 + // We only support 1,2 for now. + if horizontal_sampling < 1 || horizontal_sampling > 2 { + return img, .Invalid_Sampling_Factor + } + if vertical_sampling < 1 || vertical_sampling > 2 { + return img, .Invalid_Sampling_Factor + } + + if id == .Y { + if horizontal_sampling == 2 && mcu_width % 2 == 1 { + block_width += 1 + } + if vertical_sampling == 2 && mcu_height % 2 == 1 { + block_height += 1 + } + } else { + if horizontal_sampling != 1 && vertical_sampling != 1 { + return img, .Invalid_Sampling_Factor + } + } + + quantization_table_idx := compress.read_u8(ctx) or_return + + if quantization_table_idx < 0 || quantization_table_idx > 3 { + return img, .Invalid_Quantization_Table_Index + } + + color_components[id].quantization_table_idx = quantization_table_idx + color_components[id].v_sampling_factor = cast(int)vertical_sampling + color_components[id].h_sampling_factor = cast(int)horizontal_sampling + } + case .SOF2: // Progressive DCT + fallthrough + case .SOF3: // Lossless (sequential) + fallthrough + case .SOF5: // Differential sequential DCT + fallthrough + case .SOF6: // Differential progressive DCT + fallthrough + case .SOF7: // Differential lossless (sequential) + fallthrough + case .SOF9: // Extended sequential DCT, Arithmetic coding + fallthrough + case .SOF10: // Progressive DCT, Arithmetic coding + fallthrough + case .SOF11: // Lossless (sequential), Arithmetic coding + fallthrough + case .SOF13: // Differential sequential DCT, Arithmetic coding + fallthrough + case .SOF14: // Differential progressive DCT, Arithmetic coding + fallthrough + case .SOF15: // Differential lossless (sequential), Arithmetic coding + if img.metadata != nil { + info := img.metadata.(^image.JPEG_Info) + info.frame_type = marker + } + return img, .Unsupported_Frame_Type + case .SOS: + if img.channels == 0 && img.depth == 0 && img.width == 0 && img.height == 0 { + return img, .Encountered_SOS_Before_SOF + } + + if .do_not_decompress_image in options { + return img, nil + } + + // Length + compress.read_data(ctx, u16be) or_return + num_components := compress.read_u8(ctx) or_return + if num_components != 1 && num_components != 3 { + return img, .Invalid_Number_Of_Channels + } + + for _ in 0..<num_components { + component_id := cast(Component)compress.read_u8(ctx) or_return + if zero_based_components { + component_id += Component(1) + } + if component_id < .Y || component_id > .Cr { + return img, .Image_Does_Not_Adhere_to_Spec + } + + // high 4 is DC, low 4 is AC + coefficient_indices := compress.read_u8(ctx) or_return + dc_table_idx := coefficient_indices >> 4 + ac_table_idx := coefficient_indices & 0xF + + if (dc_table_idx < 0 || dc_table_idx > 3) || (ac_table_idx < 0 || ac_table_idx > 3) { + return img, .Invalid_Huffman_Table_Index + } + + color_components[component_id].dc_table_idx = dc_table_idx + color_components[component_id].ac_table_idx = ac_table_idx + } + // TODO: These aren't used for sequential DCT, only progressive and lossless. + Ss := compress.read_u8(ctx) or_return + _ = Ss + Se := compress.read_u8(ctx) or_return + _ = Se + Ah_Al := compress.read_u8(ctx) or_return + _ = Ah_Al + + blocks = make([]Block, block_height * block_width) or_return + + previous_dc: [Component]i16 + + luma_v_sampling_factor := color_components[.Y].v_sampling_factor + luma_h_sampling_factor := color_components[.Y].h_sampling_factor + + restart_interval *= luma_v_sampling_factor * luma_h_sampling_factor + #no_bounds_check for y := 0; y < mcu_height; y += luma_v_sampling_factor { + for x := 0; x < mcu_width; x += luma_h_sampling_factor { + blk := y * block_width + x + + if restart_interval != 0 && blk % restart_interval == 0 { + previous_dc[.Y] = 0 + previous_dc[.Cb] = 0 + previous_dc[.Cr] = 0 + byte_align(ctx) + } + for c in 1..=img.channels { + c := cast(Component)c + for v in 0..<color_components[c].v_sampling_factor { + h_loop: + for h in 0..<color_components[c].h_sampling_factor { + mcu := &blocks[(y + v) * block_width + (h + x)][c] + dc_table := huffman[.DC][color_components[c].dc_table_idx] + ac_table := huffman[.AC][color_components[c].ac_table_idx] + quantization_table := quantization[color_components[c].quantization_table_idx] + + length := get_symbol(ctx, dc_table) + + if length > 11 { + return img, .Corrupt + } + + dc_coeff := cast(i16)read_bits_msb(ctx, length) + + if length != 0 && dc_coeff < (1 << (length - 1)) { + dc_coeff -= (1 << length) - 1 + } + mcu[0] = (dc_coeff + previous_dc[c]) * cast(i16)quantization_table[0] + previous_dc[c] = dc_coeff + previous_dc[c] + + for i := 1; i < COEFFICIENT_COUNT; i += 1 { + // High nibble is amount of 0s to skip. + // Low nibble is length of coeff. + symbol := get_symbol(ctx, ac_table) + + // Special symbol used to indicate + // that the rest of the MCU is filled with 0s + if symbol == 0x00 { + continue h_loop + } + + amnt_zeros := int(symbol >> 4) + ac_coeff_len := symbol & 0xF + ac_coeff: i16 = 0 + + if i + amnt_zeros >= COEFFICIENT_COUNT || ac_coeff_len > 10 { + return img, .Corrupt + } + + i += amnt_zeros + + ac_coeff = cast(i16)read_bits_msb(ctx, ac_coeff_len) + if ac_coeff < (1 << (ac_coeff_len - 1)) { + ac_coeff -= (1 << ac_coeff_len) - 1 + } + + mcu[zigzag[i]] = ac_coeff * cast(i16)quantization_table[i] + } + } + } + } + + for c in 1..=img.channels { + c := cast(Component)c + + for v in 0..<color_components[c].v_sampling_factor { + for h in 0..< color_components[c].h_sampling_factor { + mcu := &blocks[(y + v) * block_width + (x + h)][c] + for i in 0..<BLOCK_SIZE { + g0 := cast(f32)mcu[0 * BLOCK_SIZE + i] * s0 + g1 := cast(f32)mcu[4 * BLOCK_SIZE + i] * s4 + g2 := cast(f32)mcu[2 * BLOCK_SIZE + i] * s2 + g3 := cast(f32)mcu[6 * BLOCK_SIZE + i] * s6 + g4 := cast(f32)mcu[5 * BLOCK_SIZE + i] * s5 + g5 := cast(f32)mcu[1 * BLOCK_SIZE + i] * s1 + g6 := cast(f32)mcu[7 * BLOCK_SIZE + i] * s7 + g7 := cast(f32)mcu[3 * BLOCK_SIZE + i] * s3 + + f4 := g4 - g7 + f5 := g5 + g6 + f6 := g5 - g6 + f7 := g4 + g7 + + e0 := g0 + e1 := g1 + e2 := g2 - g3 + e3 := g2 + g3 + e4 := f4 + e5 := f5 - f7 + e6 := f6 + e7 := f5 + f7 + e8 := f4 + f6 + + d0 := e0 + d1 := e1 + d2 := e2 * m1 + d3 := e3 + d4 := e4 * m2 + d5 := e5 * m3 + d6 := e6 * m4 + d7 := e7 + d8 := e8 * m5 + + c0 := d0 + d1 + c1 := d0 - d1 + c2 := d2 - d3 + c3 := d3 + c4 := d4 + d8 + c5 := d5 + d7 + c6 := d6 - d8 + c7 := d7 + c8 := c5 - c6 + + b0 := c0 + c3 + b1 := c1 + c2 + b2 := c1 - c2 + b3 := c0 - c3 + b4 := c4 - c8 + b5 := c8 + b6 := c6 - c7 + b7 := c7 + + mcu[0 * BLOCK_SIZE + i] = cast(i16)(b0 + b7) + mcu[1 * BLOCK_SIZE + i] = cast(i16)(b1 + b6) + mcu[2 * BLOCK_SIZE + i] = cast(i16)(b2 + b5) + mcu[3 * BLOCK_SIZE + i] = cast(i16)(b3 + b4) + mcu[4 * BLOCK_SIZE + i] = cast(i16)(b3 - b4) + mcu[5 * BLOCK_SIZE + i] = cast(i16)(b2 - b5) + mcu[6 * BLOCK_SIZE + i] = cast(i16)(b1 - b6) + mcu[7 * BLOCK_SIZE + i] = cast(i16)(b0 - b7) + } + + for i in 0..<BLOCK_SIZE { + g0 := cast(f32)mcu[i * BLOCK_SIZE + 0] * s0 + g1 := cast(f32)mcu[i * BLOCK_SIZE + 4] * s4 + g2 := cast(f32)mcu[i * BLOCK_SIZE + 2] * s2 + g3 := cast(f32)mcu[i * BLOCK_SIZE + 6] * s6 + g4 := cast(f32)mcu[i * BLOCK_SIZE + 5] * s5 + g5 := cast(f32)mcu[i * BLOCK_SIZE + 1] * s1 + g6 := cast(f32)mcu[i * BLOCK_SIZE + 7] * s7 + g7 := cast(f32)mcu[i * BLOCK_SIZE + 3] * s3 + + f4 := g4 - g7 + f5 := g5 + g6 + f6 := g5 - g6 + f7 := g4 + g7 + + e0 := g0 + e1 := g1 + e2 := g2 - g3 + e3 := g2 + g3 + e4 := f4 + e5 := f5 - f7 + e6 := f6 + e7 := f5 + f7 + e8 := f4 + f6 + + d0 := e0 + d1 := e1 + d2 := e2 * m1 + d3 := e3 + d4 := e4 * m2 + d5 := e5 * m3 + d6 := e6 * m4 + d7 := e7 + d8 := e8 * m5 + + c0 := d0 + d1 + c1 := d0 - d1 + c2 := d2 - d3 + c3 := d3 + c4 := d4 + d8 + c5 := d5 + d7 + c6 := d6 - d8 + c7 := d7 + c8 := c5 - c6 + + b0 := c0 + c3 + b1 := c1 + c2 + b2 := c1 - c2 + b3 := c0 - c3 + b4 := c4 - c8 + b5 := c8 + b6 := c6 - c7 + b7 := c7 + + mcu[i * BLOCK_SIZE + 0] = cast(i16)(b0 + b7) + mcu[i * BLOCK_SIZE + 1] = cast(i16)(b1 + b6) + mcu[i * BLOCK_SIZE + 2] = cast(i16)(b2 + b5) + mcu[i * BLOCK_SIZE + 3] = cast(i16)(b3 + b4) + mcu[i * BLOCK_SIZE + 4] = cast(i16)(b3 - b4) + mcu[i * BLOCK_SIZE + 5] = cast(i16)(b2 - b5) + mcu[i * BLOCK_SIZE + 6] = cast(i16)(b1 - b6) + mcu[i * BLOCK_SIZE + 7] = cast(i16)(b0 - b7) + } + } + } + } + + // Convert the YCbCr pixel data to RGB + cbcr_blk := &blocks[y * block_width + x] + for v := luma_v_sampling_factor - 1; v >= 0; v -= 1 { + for h := luma_h_sampling_factor - 1; h >= 0; h -= 1 { + y_blk := &blocks[(y + v) * block_width + (x + h)] + + for j := BLOCK_SIZE - 1; j >= 0; j -= 1 { + for k := BLOCK_SIZE - 1; k >= 0; k -= 1 { + i := j * BLOCK_SIZE + k + cbcr_pixel_row := j / luma_v_sampling_factor + 4 * v + cbcr_pixel_column := k / luma_h_sampling_factor + 4 * h + cbcr_pixel := cbcr_pixel_row * BLOCK_SIZE + cbcr_pixel_column + + r := cast(i16)clamp(cast(f32)y_blk[.Y][i] + 1.402 * cast(f32)cbcr_blk[.Cr][cbcr_pixel] + 128, 0, 255) + g := cast(i16)clamp(cast(f32)y_blk[.Y][i] - 0.344 * cast(f32)cbcr_blk[.Cb][cbcr_pixel] - 0.714 * cast(f32)cbcr_blk[.Cr][cbcr_pixel] + 128, 0, 255) + b := cast(i16)clamp(cast(f32)y_blk[.Y][i] + 1.772 * cast(f32)cbcr_blk[.Cb][cbcr_pixel] + 128, 0, 255) + + y_blk[.Y][i] = r + y_blk[.Cb][i] = g + y_blk[.Cr][i] = b + } + } + } + } + } + } + + orig_channels := img.channels + + // We automatically expand grayscale images to RGB + if img.channels == 1 { + img.channels += 2 + } + + if .alpha_add_if_missing in options { + img.channels += 1 + orig_channels += 1 + } + + if resize(&img.pixels.buf, img.width * img.height * img.channels) != nil { + return img, .Unable_To_Allocate_Or_Resize + } + + switch orig_channels { + case 1: // Grayscale JPEG expanded to RGB + out := mem.slice_data_cast([]image.RGB_Pixel, img.pixels.buf[:]) + out_idx := 0 + for y in 0..<img.height { + mcu_row := y / BLOCK_SIZE + pixel_row := y % BLOCK_SIZE + for x in 0..<img.width { + mcu_col := x / BLOCK_SIZE + pixel_col := x % BLOCK_SIZE + mcu_idx := mcu_row * block_width + mcu_col + pixel_idx := pixel_row * BLOCK_SIZE + pixel_col + + luma := cast(byte)blocks[mcu_idx][.Y][pixel_idx] + out[out_idx] = {luma, luma, luma} + + out_idx += 1 + } + } + + case 2: // Grayscale JPEG expanded to RGBA + out := mem.slice_data_cast([]image.RGBA_Pixel, img.pixels.buf[:]) + out_idx := 0 + for y in 0..<img.height { + mcu_row := y / BLOCK_SIZE + pixel_row := y % BLOCK_SIZE + + for x in 0..<img.width { + mcu_col := x / BLOCK_SIZE + pixel_col := x % BLOCK_SIZE + mcu_idx := mcu_row * block_width + mcu_col + pixel_idx := pixel_row * BLOCK_SIZE + pixel_col + + luma := cast(byte)blocks[mcu_idx][.Y][pixel_idx] + out[out_idx] = {luma, luma, luma, 255} + out_idx += 1 + } + } + + case 3: + out := mem.slice_data_cast([]image.RGB_Pixel, img.pixels.buf[:]) + out_idx := 0 + for y in 0..<img.height { + mcu_row := y / BLOCK_SIZE + pixel_row := y % BLOCK_SIZE + + for x in 0..<img.width { + mcu_col := x / BLOCK_SIZE + pixel_col := x % BLOCK_SIZE + mcu_idx := mcu_row * block_width + mcu_col + pixel_idx := pixel_row * BLOCK_SIZE + pixel_col + + out[out_idx] = { + cast(byte)blocks[mcu_idx][.Y][pixel_idx], + cast(byte)blocks[mcu_idx][.Cb][pixel_idx], + cast(byte)blocks[mcu_idx][.Cr][pixel_idx], + } + out_idx += 1 + } + } + + case 4: + out := mem.slice_data_cast([]image.RGBA_Pixel, img.pixels.buf[:]) + out_idx := 0 + for y in 0..<img.height { + mcu_row := y / BLOCK_SIZE + pixel_row := y % BLOCK_SIZE + + for x in 0..<img.width { + mcu_col := x / BLOCK_SIZE + pixel_col := x % BLOCK_SIZE + mcu_idx := mcu_row * block_width + mcu_col + pixel_idx := pixel_row * BLOCK_SIZE + pixel_col + + out[out_idx] = { + cast(byte)blocks[mcu_idx][.Y][pixel_idx], + cast(byte)blocks[mcu_idx][.Cb][pixel_idx], + cast(byte)blocks[mcu_idx][.Cr][pixel_idx], + 255, // Alpha + } + out_idx += 1 + } + } + } + + expect_EOI = true + + case .TEM: + // TEM doesn't have a length, continue to next marker + case: + length := (compress.read_data(ctx, u16be) or_return) - 2 + compress.read_slice_from_memory(ctx, cast(int)length) or_return + } + } + + return +} + +destroy :: proc(img: ^Image) { + if img == nil { + return + } + + bytes.buffer_destroy(&img.pixels) + + if v, ok := img.metadata.(^image.JPEG_Info); ok { + if jfxx, jfxx_ok := v.jfxx_app0.?; jfxx_ok { + delete(jfxx.thumbnail) + } + if jfif, jfif_ok := v.jfif_app0.?; jfif_ok { + delete(jfif.thumbnail) + } + + for comment in v.comments { + delete(comment) + } + delete(v.comments) + + for exif in v.exif { + delete(exif.data) + } + delete(v.exif) + + free(v) + } + free(img) +} + +@(init, private) +_register :: proc "contextless" () { + image.register(.JPEG, load_from_bytes, destroy) +}
\ No newline at end of file diff --git a/core/image/jpeg/jpeg_js.odin b/core/image/jpeg/jpeg_js.odin new file mode 100644 index 000000000..2a2de1d10 --- /dev/null +++ b/core/image/jpeg/jpeg_js.odin @@ -0,0 +1,3 @@ +package jpeg + +load :: proc{load_from_bytes, load_from_context} diff --git a/core/image/jpeg/jpeg_os.odin b/core/image/jpeg/jpeg_os.odin new file mode 100644 index 000000000..46e89c4c7 --- /dev/null +++ b/core/image/jpeg/jpeg_os.odin @@ -0,0 +1,18 @@ +package jpeg + +import "core:os" + +load :: proc{load_from_file, load_from_bytes, load_from_context} + +load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { + context.allocator = allocator + + data, ok := os.read_entire_file(filename) + defer delete(data) + + if ok { + return load_from_bytes(data, options) + } else { + return nil, .Unable_To_Read_File + } +} diff --git a/core/image/netpbm/netpbm.odin b/core/image/netpbm/netpbm.odin index a9dc6599a..25e0228b5 100644 --- a/core/image/netpbm/netpbm.odin +++ b/core/image/netpbm/netpbm.odin @@ -720,7 +720,7 @@ autoselect_pbm_format_from_image :: proc(img: ^Image, prefer_binary := true, for } @(init, private) -_register :: proc() { +_register :: proc "contextless" () { loader :: proc(data: []byte, options: image.Options, allocator: mem.Allocator) -> (img: ^Image, err: Error) { return load_from_bytes(data, allocator) } diff --git a/core/image/png/helpers.odin b/core/image/png/helpers.odin index a9495ed4d..97e70226c 100644 --- a/core/image/png/helpers.odin +++ b/core/image/png/helpers.odin @@ -366,7 +366,7 @@ chrm :: proc(c: image.PNG_Chunk) -> (res: cHRM, ok: bool) { return } -exif :: proc(c: image.PNG_Chunk) -> (res: Exif, ok: bool) { +exif :: proc(c: image.PNG_Chunk) -> (res: image.Exif, ok: bool) { ok = true @@ -396,4 +396,4 @@ exif :: proc(c: image.PNG_Chunk) -> (res: Exif, ok: bool) { General helper functions */ -compute_buffer_size :: image.compute_buffer_size
\ No newline at end of file +compute_buffer_size :: image.compute_buffer_size diff --git a/core/image/png/png.odin b/core/image/png/png.odin index 2d3665e94..3516fc8d3 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -138,14 +138,6 @@ Text :: struct { text: string, } -Exif :: struct { - byte_order: enum { - little_endian, - big_endian, - }, - data: []u8, -} - iCCP :: struct { name: string, profile: []u8, @@ -250,10 +242,14 @@ read_header :: proc(ctx: ^$C) -> (image.PNG_IHDR, Error) { header := (^image.PNG_IHDR)(raw_data(c.data))^ // Validate IHDR using header - if width == 0 || height == 0 || u128(width) * u128(height) > image.MAX_DIMENSIONS { + if width == 0 || height == 0 { return {}, .Invalid_Image_Dimensions } + if u128(width) * u128(height) > image.MAX_DIMENSIONS { + return {}, .Image_Dimensions_Too_Large + } + if compression_method != 0 { return {}, compress.General_Error.Unknown_Compression_Method } @@ -1212,7 +1208,6 @@ Filter_Params :: struct #packed { depth_scale_table :: []u8{0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01} -// @(optimization_mode="speed") defilter_8 :: proc(params: ^Filter_Params) -> (ok: bool) { using params @@ -1273,7 +1268,6 @@ defilter_8 :: proc(params: ^Filter_Params) -> (ok: bool) { return } -// @(optimization_mode="speed") defilter_less_than_8 :: proc(params: ^Filter_Params) -> bool #no_bounds_check { using params @@ -1436,7 +1430,6 @@ defilter_less_than_8 :: proc(params: ^Filter_Params) -> bool #no_bounds_check { return true } -// @(optimization_mode="speed") defilter_16 :: proc(params: ^Filter_Params) -> bool { using params @@ -1614,6 +1607,6 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH } @(init, private) -_register :: proc() { +_register :: proc "contextless" () { image.register(.PNG, load_from_bytes, destroy) } diff --git a/core/image/qoi/qoi.odin b/core/image/qoi/qoi.odin index 6b6149e60..ded8d7971 100644 --- a/core/image/qoi/qoi.odin +++ b/core/image/qoi/qoi.odin @@ -371,6 +371,6 @@ qoi_hash :: #force_inline proc(pixel: RGBA_Pixel) -> (index: u8) { } @(init, private) -_register :: proc() { +_register :: proc "contextless" () { image.register(.QOI, load_from_bytes, destroy) } diff --git a/core/image/tga/tga.odin b/core/image/tga/tga.odin index 46e37a0cf..5fda803c5 100644 --- a/core/image/tga/tga.odin +++ b/core/image/tga/tga.odin @@ -406,6 +406,6 @@ IMAGE_DESCRIPTOR_RIGHT_MASK :: 1<<4 IMAGE_DESCRIPTOR_TOP_MASK :: 1<<5 @(init, private) -_register :: proc() { +_register :: proc "contextless" () { image.register(.TGA, load_from_bytes, destroy) }
\ No newline at end of file diff --git a/core/io/io.odin b/core/io/io.odin index c2b44cbdb..c4eb6a073 100644 --- a/core/io/io.odin +++ b/core/io/io.odin @@ -5,6 +5,7 @@ package io import "base:intrinsics" import "core:unicode/utf8" +import "core:unicode/utf16" // Seek whence values Seek_From :: enum { @@ -314,6 +315,29 @@ write_string :: proc(s: Writer, str: string, n_written: ^int = nil) -> (n: int, return write(s, transmute([]byte)str, n_written) } +// write_string16 writes the contents of the string16 s to w reencoded as utf-8 +write_string16 :: proc(s: Writer, str: string16, n_written: ^int = nil) -> (n: int, err: Error) { + for i := 0; i < len(str); i += 1 { + r := rune(utf16.REPLACEMENT_CHAR) + switch c := str[i]; { + case c < utf16._surr1, utf16._surr3 <= c: + r = rune(c) + case utf16._surr1 <= c && c < utf16._surr2 && i+1 < len(str) && + utf16._surr2 <= str[i+1] && str[i+1] < utf16._surr3: + r = utf16.decode_surrogate_pair(rune(c), rune(str[i+1])) + i += 1 + } + + w: int + w, err = write_rune(s, r, n_written) + n += w + if err != nil { + return + } + } + return +} + // write_rune writes a UTF-8 encoded rune to w. write_rune :: proc(s: Writer, r: rune, n_written: ^int = nil) -> (size: int, err: Error) { defer if err == nil && n_written != nil { diff --git a/core/io/util.odin b/core/io/util.odin index fdbbd5b9f..a956a5975 100644 --- a/core/io/util.odin +++ b/core/io/util.odin @@ -22,12 +22,12 @@ write_ptr_at :: proc(w: Writer_At, p: rawptr, byte_size: int, offset: i64, n_wri write_u64 :: proc(w: Writer, i: u64, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) { buf: [32]byte - s := strconv.append_bits(buf[:], i, base, false, 64, strconv.digits, nil) + s := strconv.write_bits(buf[:], i, base, false, 64, strconv.digits, nil) return write_string(w, s, n_written) } write_i64 :: proc(w: Writer, i: i64, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) { buf: [32]byte - s := strconv.append_bits(buf[:], u64(i), base, true, 64, strconv.digits, nil) + s := strconv.write_bits(buf[:], u64(i), base, true, 64, strconv.digits, nil) return write_string(w, s, n_written) } @@ -40,18 +40,18 @@ write_int :: proc(w: Writer, i: int, base: int = 10, n_written: ^int = nil) -> ( write_u128 :: proc(w: Writer, i: u128, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) { buf: [39]byte - s := strconv.append_bits_128(buf[:], i, base, false, 128, strconv.digits, nil) + s := strconv.write_bits_128(buf[:], i, base, false, 128, strconv.digits, nil) return write_string(w, s, n_written) } write_i128 :: proc(w: Writer, i: i128, base: int = 10, n_written: ^int = nil) -> (n: int, err: Error) { buf: [40]byte - s := strconv.append_bits_128(buf[:], u128(i), base, true, 128, strconv.digits, nil) + s := strconv.write_bits_128(buf[:], u128(i), base, true, 128, strconv.digits, nil) return write_string(w, s, n_written) } write_f16 :: proc(w: Writer, val: f16, n_written: ^int = nil) -> (n: int, err: Error) { buf: [386]byte - str := strconv.append_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val)) + str := strconv.write_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val)) s := buf[:len(str)+1] if s[1] == '+' || s[1] == '-' { s = s[1:] @@ -67,7 +67,7 @@ write_f16 :: proc(w: Writer, val: f16, n_written: ^int = nil) -> (n: int, err: E write_f32 :: proc(w: Writer, val: f32, n_written: ^int = nil) -> (n: int, err: Error) { buf: [386]byte - str := strconv.append_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val)) + str := strconv.write_float(buf[1:], f64(val), 'f', 2*size_of(val), 8*size_of(val)) s := buf[:len(str)+1] if s[1] == '+' || s[1] == '-' { s = s[1:] @@ -83,7 +83,7 @@ write_f32 :: proc(w: Writer, val: f32, n_written: ^int = nil) -> (n: int, err: E write_f64 :: proc(w: Writer, val: f64, n_written: ^int = nil) -> (n: int, err: Error) { buf: [386]byte - str := strconv.append_float(buf[1:], val, 'f', 2*size_of(val), 8*size_of(val)) + str := strconv.write_float(buf[1:], val, 'f', 2*size_of(val), 8*size_of(val)) s := buf[:len(str)+1] if s[1] == '+' || s[1] == '-' { s = s[1:] @@ -130,7 +130,7 @@ write_encoded_rune :: proc(w: Writer, r: rune, write_quote := true, n_written: ^ write_string(w, `\x`, &n) or_return buf: [2]byte - s := strconv.append_bits(buf[:], u64(r), 16, true, 64, strconv.digits, nil) + s := strconv.write_bits(buf[:], u64(r), 16, true, 64, strconv.digits, nil) switch len(s) { case 0: write_string(w, "00", &n) or_return @@ -189,6 +189,23 @@ write_escaped_rune :: proc(w: Writer, r: rune, quote: byte, html_safe := false, write_encoded_rune(w, r, false, &n) or_return return } + if r < 32 && for_json { + switch r { + case '\b': write_string(w, `\b`, &n) or_return + case '\f': write_string(w, `\f`, &n) or_return + case '\n': write_string(w, `\n`, &n) or_return + case '\r': write_string(w, `\r`, &n) or_return + case '\t': write_string(w, `\t`, &n) or_return + case: + write_byte(w, '\\', &n) or_return + write_byte(w, 'u', &n) or_return + write_byte(w, '0', &n) or_return + write_byte(w, '0', &n) or_return + write_byte(w, DIGITS_LOWER[r>>4 & 0xf], &n) or_return + write_byte(w, DIGITS_LOWER[r & 0xf], &n) or_return + } + return + } switch r { case '\a': write_string(w, `\a`, &n) or_return case '\b': write_string(w, `\b`, &n) or_return @@ -264,6 +281,33 @@ write_quoted_string :: proc(w: Writer, str: string, quote: byte = '"', n_written return } +write_quoted_string16 :: proc(w: Writer, str: string16, quote: byte = '"', n_written: ^int = nil, for_json := false) -> (n: int, err: Error) { + defer if n_written != nil { + n_written^ += n + } + write_byte(w, quote, &n) or_return + for width, s := 0, str; len(s) > 0; s = s[width:] { + r := rune(s[0]) + width = 1 + if r >= utf8.RUNE_SELF { + r, width = utf16.decode_rune_in_string(s) + } + if width == 1 && r == utf8.RUNE_ERROR { + write_byte(w, '\\', &n) or_return + write_byte(w, 'x', &n) or_return + write_byte(w, DIGITS_LOWER[s[0]>>4], &n) or_return + write_byte(w, DIGITS_LOWER[s[0]&0xf], &n) or_return + continue + } + + n_wrapper(write_escaped_rune(w, r, quote, false, nil, for_json), &n) or_return + + } + write_byte(w, quote, &n) or_return + return +} + + // writer append a quoted rune into the byte buffer, return the written size write_quoted_rune :: proc(w: Writer, r: rune) -> (n: int) { _write_byte :: #force_inline proc(w: Writer, c: byte) -> int { diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin index 6d93fb879..f0acc8a22 100644 --- a/core/log/file_console_logger.odin +++ b/core/log/file_console_logger.odin @@ -2,10 +2,12 @@ #+build !orca package log -import "core:encoding/ansi" +import "base:runtime" import "core:fmt" import "core:strings" import "core:os" +import "core:terminal" +import "core:terminal/ansi" import "core:time" Level_Headers := [?]string{ @@ -37,11 +39,38 @@ File_Console_Logger_Data :: struct { ident: string, } +@(private) global_subtract_stdout_options: Options +@(private) global_subtract_stderr_options: Options + +@(init, private) +init_standard_stream_status :: proc "contextless" () { + // NOTE(Feoramund): While it is technically possible for these streams to + // be redirected during the runtime of the program, the cost of checking on + // every single log message is not worth it to support such an + // uncommonly-used feature. + if terminal.color_enabled { + context = runtime.default_context() + + // This is done this way because it's possible that only one of these + // streams could be redirected to a file. + if !terminal.is_terminal(os.stdout) { + global_subtract_stdout_options = {.Terminal_Color} + } + if !terminal.is_terminal(os.stderr) { + global_subtract_stderr_options = {.Terminal_Color} + } + } else { + // Override any terminal coloring. + global_subtract_stdout_options = {.Terminal_Color} + global_subtract_stderr_options = {.Terminal_Color} + } +} + create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger { data := new(File_Console_Logger_Data, allocator) data.file_handle = h data.ident = ident - return Logger{file_console_logger_proc, data, lowest, opt} + return Logger{file_logger_proc, data, lowest, opt} } destroy_file_logger :: proc(log: Logger, allocator := context.allocator) { @@ -56,19 +85,15 @@ create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logg data := new(File_Console_Logger_Data, allocator) data.file_handle = os.INVALID_HANDLE data.ident = ident - return Logger{file_console_logger_proc, data, lowest, opt} + return Logger{console_logger_proc, data, lowest, opt} } destroy_console_logger :: proc(log: Logger, allocator := context.allocator) { free(log.data, allocator) } -file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) { - data := cast(^File_Console_Logger_Data)logger_data - h: os.Handle = os.stdout if level <= Level.Error else os.stderr - if data.file_handle != os.INVALID_HANDLE { - h = data.file_handle - } +@(private) +_file_console_logger_proc :: proc(h: os.Handle, ident: string, level: Level, text: string, options: Options, location: runtime.Source_Code_Location) { backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths. buf := strings.builder_from_bytes(backing[:]) @@ -86,13 +111,32 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string fmt.sbprintf(&buf, "[{}] ", os.current_thread_id()) } - if data.ident != "" { - fmt.sbprintf(&buf, "[%s] ", data.ident) + if ident != "" { + fmt.sbprintf(&buf, "[%s] ", ident) } //TODO(Hoej): When we have better atomics and such, make this thread-safe fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text) } +file_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) { + data := cast(^File_Console_Logger_Data)logger_data + _file_console_logger_proc(data.file_handle, data.ident, level, text, options, location) +} + +console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) { + options := options + data := cast(^File_Console_Logger_Data)logger_data + h: os.Handle = --- + if level < Level.Error { + h = os.stdout + options -= global_subtract_stdout_options + } else { + h = os.stderr + options -= global_subtract_stderr_options + } + _file_console_logger_proc(h, data.ident, level, text, options, location) +} + do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) { RESET :: ansi.CSI + ansi.RESET + ansi.SGR diff --git a/core/log/log.odin b/core/log/log.odin index 2b6317060..b2efe8beb 100644 --- a/core/log/log.odin +++ b/core/log/log.odin @@ -193,7 +193,7 @@ log :: proc(level: Level, args: ..any, sep := " ", location := #caller_location) return } runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - str := fmt.tprint(..args, sep=sep) //NOTE(Hoej): While tprint isn't thread-safe, no logging is. + str := fmt.tprint(..args, sep=sep) logger.procedure(logger.data, level, str, logger.options, location) } diff --git a/core/math/big/api.odin b/core/math/big/api.odin index bf19e83b6..70e134a90 100644 --- a/core/math/big/api.odin +++ b/core/math/big/api.odin @@ -160,6 +160,5 @@ destroy :: proc { int_destroy :: proc(integers: ..^Int) */ int_destroy, -} - - + internal_rat_destroy, +}
\ No newline at end of file diff --git a/core/math/big/common.odin b/core/math/big/common.odin index 5428b00ee..22655293f 100644 --- a/core/math/big/common.odin +++ b/core/math/big/common.odin @@ -15,22 +15,11 @@ import "base:intrinsics" */ /* - ========================== TUNABLES ========================== - - `initialize_constants` returns `#config(MUL_KARATSUBA_CUTOFF, _DEFAULT_MUL_KARATSUBA_CUTOFF)` - and we initialize this cutoff that way so that the procedure is used and called, - because it handles initializing the constants ONE, ZERO, MINUS_ONE, NAN and INF. - - `initialize_constants` also replaces the other `_DEFAULT_*` cutoffs with custom compile-time values if so `#config`ured. - -*/ - -/* There is a bug with DLL globals. They don't get set. To allow tests to run we add `-define:MATH_BIG_EXE=false` to hardcode the cutoffs for now. */ when #config(MATH_BIG_EXE, true) { - MUL_KARATSUBA_CUTOFF := initialize_constants() + MUL_KARATSUBA_CUTOFF := _DEFAULT_MUL_KARATSUBA_CUTOFF SQR_KARATSUBA_CUTOFF := _DEFAULT_SQR_KARATSUBA_CUTOFF MUL_TOOM_CUTOFF := _DEFAULT_MUL_TOOM_CUTOFF SQR_TOOM_CUTOFF := _DEFAULT_SQR_TOOM_CUTOFF diff --git a/core/math/big/helpers.odin b/core/math/big/helpers.odin index ee09bb2c7..9ee35c45a 100644 --- a/core/math/big/helpers.odin +++ b/core/math/big/helpers.odin @@ -7,6 +7,7 @@ package math_big import "base:intrinsics" +import "base:runtime" import rnd "core:math/rand" /* @@ -777,32 +778,36 @@ int_from_bytes_little_python :: proc(a: ^Int, buf: []u8, signed := false, alloca */ INT_ONE, INT_ZERO, INT_MINUS_ONE, INT_INF, INT_MINUS_INF, INT_NAN := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{} +@(private) +constant_allocator: runtime.Allocator + @(init, private) -_init_constants :: proc() { - initialize_constants() -} +initialize_constants :: proc "contextless" () { + context = runtime.default_context() + constant_allocator = context.allocator -initialize_constants :: proc() -> (res: int) { - internal_set( INT_ZERO, 0); INT_ZERO.flags = {.Immutable} - internal_set( INT_ONE, 1); INT_ONE.flags = {.Immutable} - internal_set(INT_MINUS_ONE, -1); INT_MINUS_ONE.flags = {.Immutable} + internal_int_set_from_integer( INT_ZERO, 0); INT_ZERO.flags = {.Immutable} + internal_int_set_from_integer( INT_ONE, 1); INT_ONE.flags = {.Immutable} + internal_int_set_from_integer(INT_MINUS_ONE, -1); INT_MINUS_ONE.flags = {.Immutable} /* We set these special values to -1 or 1 so they don't get mistake for zero accidentally. This allows for shortcut tests of is_zero as .used == 0. */ - internal_set( INT_NAN, 1); INT_NAN.flags = {.Immutable, .NaN} - internal_set( INT_INF, 1); INT_INF.flags = {.Immutable, .Inf} - internal_set(INT_MINUS_INF, -1); INT_MINUS_INF.flags = {.Immutable, .Inf} - - return _DEFAULT_MUL_KARATSUBA_CUTOFF + internal_int_set_from_integer( INT_NAN, 1); INT_NAN.flags = {.Immutable, .NaN} + internal_int_set_from_integer( INT_INF, 1); INT_INF.flags = {.Immutable, .Inf} + internal_int_set_from_integer(INT_MINUS_INF, -1); INT_MINUS_INF.flags = {.Immutable, .Inf} } /* Destroy constants. Optional for an EXE, as this would be called at the very end of a process. */ -destroy_constants :: proc() { +@(fini, private) +destroy_constants :: proc "contextless" () { + context = runtime.default_context() + context.allocator = constant_allocator + internal_destroy(INT_ONE, INT_ZERO, INT_MINUS_ONE, INT_INF, INT_MINUS_INF, INT_NAN) } diff --git a/core/math/big/internal.odin b/core/math/big/internal.odin index c9b331e55..e0bc1ae06 100644 --- a/core/math/big/internal.odin +++ b/core/math/big/internal.odin @@ -27,10 +27,10 @@ package math_big -import "core:mem" +import "base:builtin" import "base:intrinsics" +import "core:mem" import rnd "core:math/rand" -import "base:builtin" /* Low-level addition, unsigned. Handbook of Applied Cryptography, algorithm 14.7. @@ -1660,13 +1660,13 @@ internal_int_sqrt :: proc(dest, src: ^Int, allocator := context.allocator) -> (e if internal_gte(y, x) { internal_swap(dest, x) - return nil + return internal_clamp(dest) } internal_swap(x, y) } internal_swap(dest, x) - return err + return internal_clamp(dest) } internal_sqrt :: proc { internal_int_sqrt, } @@ -2885,12 +2885,12 @@ internal_clear_if_uninitialized_multi :: proc(args: ..^Int, allocator := context } internal_clear_if_uninitialized :: proc {internal_clear_if_uninitialized_single, internal_clear_if_uninitialized_multi, } -internal_error_if_immutable_single :: proc(arg: ^Int) -> (err: Error) { +internal_error_if_immutable_single :: proc "contextless" (arg: ^Int) -> (err: Error) { if arg != nil && .Immutable in arg.flags { return .Assignment_To_Immutable } return nil } -internal_error_if_immutable_multi :: proc(args: ..^Int) -> (err: Error) { +internal_error_if_immutable_multi :: proc "contextless" (args: ..^Int) -> (err: Error) { for i in args { if i != nil && .Immutable in i.flags { return .Assignment_To_Immutable } } diff --git a/core/math/big/private.odin b/core/math/big/private.odin index bb6b9497c..c42bad0c6 100644 --- a/core/math/big/private.odin +++ b/core/math/big/private.odin @@ -1370,8 +1370,8 @@ _private_int_div_recursive :: proc(quotient, remainder, a, b: ^Int, allocator := /* Slower bit-bang division... also smaller. + Prefer `_int_div_school` for speed. */ -@(deprecated="Use `_int_div_school`, it's 3.5x faster.") _private_int_div_small :: proc(quotient, remainder, numerator, denominator: ^Int) -> (err: Error) { ta, tb, tq, q := &Int{}, &Int{}, &Int{}, &Int{} diff --git a/core/math/big/radix.odin b/core/math/big/radix.odin index a5100e478..73c4452ac 100644 --- a/core/math/big/radix.odin +++ b/core/math/big/radix.odin @@ -280,7 +280,7 @@ int_atoi :: proc(res: ^Int, input: string, radix := i8(10), allocator := context } pos := ch - '+' - if RADIX_TABLE_REVERSE_SIZE <= pos { + if RADIX_TABLE_REVERSE_SIZE <= u32(pos) { break } y := RADIX_TABLE_REVERSE[pos] @@ -310,7 +310,7 @@ int_atoi :: proc(res: ^Int, input: string, radix := i8(10), allocator := context res.sign = sign } - return nil + return internal_clamp(res) } diff --git a/core/math/big/rat.odin b/core/math/big/rat.odin index e0e58b80f..3c32d45cb 100644 --- a/core/math/big/rat.odin +++ b/core/math/big/rat.odin @@ -157,6 +157,8 @@ internal_rat_norm :: proc(z: ^Rat, allocator := context.allocator) -> (err: Erro z.b.sign = .Zero_or_Positive f := &Int{} + defer internal_int_destroy(f) + internal_int_gcd(f, &z.a, &z.b) or_return if !internal_int_equals_digit(f, 1) { f.sign = .Zero_or_Positive @@ -378,9 +380,6 @@ internal_rat_to_float :: proc($T: typeid, z: ^Rat, allocator := context.allocato } has_sign := a.sign != b.sign - defer if has_sign { - f = -builtin.abs(f) - } exp := alen - blen a2, b2 := &Int{}, &Int{} @@ -440,6 +439,9 @@ internal_rat_to_float :: proc($T: typeid, z: ^Rat, allocator := context.allocato if math.is_inf(f, 0) { exact = false } + if has_sign { + f = -builtin.abs(f) + } return } diff --git a/core/math/fixed/fixed.odin b/core/math/fixed/fixed.odin index b23090307..9af6d7599 100644 --- a/core/math/fixed/fixed.odin +++ b/core/math/fixed/fixed.odin @@ -103,7 +103,7 @@ round :: proc(x: $T/Fixed($Backing, $Fraction_Width)) -> Backing { } @(require_results) -append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string { +write :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string { Integer_Width :: 8*size_of(Backing) - Fraction_Width x := x @@ -124,16 +124,16 @@ append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string { when size_of(Backing) < 16 { T :: u64 - append_uint :: strconv.append_uint + write_uint :: strconv.write_uint } else { T :: u128 - append_uint :: strconv.append_u128 + write_uint :: strconv.write_u128 } integer := T(x.i) >> Fraction_Width fraction := T(x.i) & (1<<Fraction_Width - 1) - s := append_uint(buf[i:], integer, 10) + s := write_uint(buf[i:], integer, 10) i += len(s) if fraction != 0 { buf[i] = '.' @@ -155,7 +155,7 @@ append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string { @(require_results) to_string :: proc(x: $T/Fixed($Backing, $Fraction_Width), allocator := context.allocator) -> string { buf: [48]byte - s := append(buf[:], x) + s := write(buf[:], x) str := make([]byte, len(s), allocator) copy(str, s) return string(str) @@ -294,3 +294,8 @@ _power_of_two_table := [129]string{ "85070591730234615865843651857942052864", "170141183460469231731687303715884105728", } + +@(deprecated="Use write instead") +append :: proc(dst: []byte, x: $T/Fixed($Backing, $Fraction_Width)) -> string { + return write(dst, x) +} diff --git a/core/math/math.odin b/core/math/math.odin index 934842318..b99a97bc1 100644 --- a/core/math/math.odin +++ b/core/math/math.odin @@ -2296,7 +2296,7 @@ nextafter_f16 :: proc "contextless" (x, y: f16) -> (r: f16) { case x == y: r = x case x == 0: - r = copy_sign_f16(1, y) + r = copy_sign_f16(transmute(f16)u16(1), y) case (y > x) == (x > 0): r = transmute(f16)(transmute(u16)x + 1) case: @@ -2312,7 +2312,7 @@ nextafter_f32 :: proc "contextless" (x, y: f32) -> (r: f32) { case x == y: r = x case x == 0: - r = copy_sign_f32(1, y) + r = copy_sign_f32(transmute(f32)u32(1), y) case (y > x) == (x > 0): r = transmute(f32)(transmute(u32)x + 1) case: @@ -2328,7 +2328,7 @@ nextafter_f64 :: proc "contextless" (x, y: f64) -> (r: f64) { case x == y: r = x case x == 0: - r = copy_sign_f64(1, y) + r = copy_sign_f64(transmute(f64)u64(1), y) case (y > x) == (x > 0): r = transmute(f64)(transmute(u64)x + 1) case: @@ -2350,32 +2350,6 @@ nextafter :: proc{ } @(require_results) -signbit_f16 :: proc "contextless" (x: f16) -> bool { - return (transmute(u16)x)&(1<<15) != 0 -} -@(require_results) -signbit_f32 :: proc "contextless" (x: f32) -> bool { - return (transmute(u32)x)&(1<<31) != 0 -} -@(require_results) -signbit_f64 :: proc "contextless" (x: f64) -> bool { - return (transmute(u64)x)&(1<<63) != 0 -} -@(require_results) signbit_f16le :: proc "contextless" (x: f16le) -> bool { return signbit_f16(f16(x)) } -@(require_results) signbit_f32le :: proc "contextless" (x: f32le) -> bool { return signbit_f32(f32(x)) } -@(require_results) signbit_f64le :: proc "contextless" (x: f64le) -> bool { return signbit_f64(f64(x)) } -@(require_results) signbit_f16be :: proc "contextless" (x: f16be) -> bool { return signbit_f16(f16(x)) } -@(require_results) signbit_f32be :: proc "contextless" (x: f32be) -> bool { return signbit_f32(f32(x)) } -@(require_results) signbit_f64be :: proc "contextless" (x: f64be) -> bool { return signbit_f64(f64(x)) } - -signbit :: proc{ - signbit_f16, signbit_f16le, signbit_f16be, - signbit_f32, signbit_f32le, signbit_f32be, - signbit_f64, signbit_f64le, signbit_f64be, -} - - -@(require_results) hypot_f16 :: proc "contextless" (x, y: f16) -> (r: f16) { p, q := abs(x), abs(y) switch { diff --git a/core/math/math_gamma.odin b/core/math/math_gamma.odin index 9f5a364d3..9e8c2a909 100644 --- a/core/math/math_gamma.odin +++ b/core/math/math_gamma.odin @@ -132,7 +132,7 @@ gamma_f64 :: proc "contextless" (x: f64) -> f64 { case is_inf(x, 1): return inf_f64(1) case x == 0: - if signbit(x) { + if sign_bit(x) { return inf_f64(-1) } return inf_f64(1) diff --git a/core/math/rand/rand.odin b/core/math/rand/rand.odin index 537256d32..6b9a73395 100644 --- a/core/math/rand/rand.odin +++ b/core/math/rand/rand.odin @@ -33,7 +33,7 @@ Example: import "core:math/rand" import "core:fmt" - set_global_seed_example :: proc() { + reset_example :: proc() { rand.reset(1) fmt.println(rand.uint64()) } @@ -56,13 +56,6 @@ query_info :: proc(gen := context.random_generator) -> Generator_Query_Info { } -@(private) -_random_u64 :: proc(gen := context.random_generator) -> (res: u64) { - ok := runtime.random_generator_read_ptr(gen, &res, size_of(res)) - assert(ok, "uninitialized gen/context.random_generator") - return -} - /* Generates a random 32 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. @@ -84,7 +77,7 @@ Possible Output: */ @(require_results) -uint32 :: proc(gen := context.random_generator) -> (val: u32) { return u32(_random_u64(gen)) } +uint32 :: proc(gen := context.random_generator) -> (val: u32) {return u32(uint64(gen))} /* Generates a random 64 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. @@ -107,7 +100,11 @@ Possible Output: */ @(require_results) -uint64 :: proc(gen := context.random_generator) -> (val: u64) { return _random_u64(gen) } +uint64 :: proc(gen := context.random_generator) -> (val: u64) { + ok := runtime.random_generator_read_ptr(gen, &val, size_of(val)) + assert(ok, "uninitialized gen/context.random_generator") + return +} /* Generates a random 128 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. @@ -131,13 +128,13 @@ Possible Output: */ @(require_results) uint128 :: proc(gen := context.random_generator) -> (val: u128) { - a := u128(_random_u64(gen)) - b := u128(_random_u64(gen)) + a := u128(uint64(gen)) + b := u128(uint64(gen)) return (a<<64) | b } /* -Generates a random 31 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. +Generates a random 31 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. The sign bit will always be set to 0, thus all generated numbers will be positive. Returns: @@ -160,7 +157,7 @@ Possible Output: @(require_results) int31 :: proc(gen := context.random_generator) -> (val: i32) { return i32(uint32(gen) << 1 >> 1) } /* -Generates a random 63 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. +Generates a random 63 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. The sign bit will always be set to 0, thus all generated numbers will be positive. Returns: @@ -183,7 +180,7 @@ Possible Output: @(require_results) int63 :: proc(gen := context.random_generator) -> (val: i64) { return i64(uint64(gen) << 1 >> 1) } /* -Generates a random 127 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. +Generates a random 127 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. The sign bit will always be set to 0, thus all generated numbers will be positive. Returns: @@ -350,7 +347,7 @@ Example: Possible Output: 6 - 500 + 13 */ @(require_results) @@ -432,7 +429,7 @@ Example: Possible Output: 15.312 - 673.130 + 273.15 */ @(require_results) float64_range :: proc(low, high: f64, gen := context.random_generator) -> (val: f64) { @@ -467,7 +464,7 @@ Example: Possible Output: 15.312 - 273.130 + 273.15 */ @(require_results) float32_range :: proc(low, high: f32, gen := context.random_generator) -> (val: f32) { @@ -480,8 +477,8 @@ Possible Output: } /* -Fills a byte slice with random values using the provided random number generator. If no generator is provided the global random number generator will be used. -Due to floating point precision there is no guarantee if the upper and lower bounds are inclusive/exclusive with the exact floating point value. +Fills a byte slice with random values using the provided random number generator. If no generator is provided the global random number generator will be used. +Due to floating point precision there is no guarantee if the upper and lower bounds are inclusive/exclusive with the exact floating point value. Inputs: - p: The byte slice to fill @@ -508,22 +505,12 @@ Possible Output: */ @(require_results) read :: proc(p: []byte, gen := context.random_generator) -> (n: int) { - pos := i8(0) - val := i64(0) - for n = 0; n < len(p); n += 1 { - if pos == 0 { - val = int63(gen) - pos = 7 - } - p[n] = byte(val) - val >>= 8 - pos -= 1 - } - return + if !runtime.random_generator_read_bytes(gen, p) {return 0} + return len(p) } /* -Creates a slice of `int` filled with random values using the provided random number generator. If no generator is provided the global random number generator will be used. +Creates a slice of `int` filled with random values using the provided random number generator. If no generator is provided the global random number generator will be used. *Allocates Using Provided Allocator* @@ -566,7 +553,7 @@ perm :: proc(n: int, allocator := context.allocator, gen := context.random_gener } /* -Randomizes the ordering of elements for the provided slice. If no generator is provided the global random number generator will be used. +Randomizes the ordering of elements for the provided slice. If no generator is provided the global random number generator will be used. Inputs: - array: The slice to randomize @@ -607,7 +594,7 @@ shuffle :: proc(array: $T/[]$E, gen := context.random_generator) { } /* -Returns a random element from the provided slice. If no generator is provided the global random number generator will be used. +Returns a random element from the provided slice. If no generator is provided the global random number generator will be used. Inputs: - array: The slice to choose an element from diff --git a/core/mem/alloc.odin b/core/mem/alloc.odin index 1094e7381..48cc39245 100644 --- a/core/mem/alloc.odin +++ b/core/mem/alloc.odin @@ -888,6 +888,34 @@ make_aligned :: proc( return runtime.make_aligned(T, len, alignment, allocator, loc) } + +/* +Allocate a new slice with alignment for allocators that might not support the +specified alignment requirement. + +This procedure allocates a new slice of type `T` with length `len`, aligned +on a boundary specified by `alignment` from an allocator specified by +`allocator`, and returns the allocated slice. + +The user should `delete` the return `original_data` slice not the typed `slice`. +*/ +@(require_results) +make_over_aligned :: proc( + $T: typeid/[]$E, + #any_int len: int, + alignment: int, + allocator: runtime.Allocator, + loc := #caller_location, +) -> (slice: T, original_data: []byte, err: Allocator_Error) { + size := size_of(E)*len + alignment-1 + original_data, err = runtime.make([]byte, size, allocator, loc) + if err == nil { + ptr := align_forward(raw_data(original_data), uintptr(alignment)) + slice = ([^]E)(ptr)[:len] + } + return +} + /* Allocate a new slice. @@ -954,6 +982,22 @@ make_dynamic_array_len_cap :: proc( } /* +Create a map with no initial allocation. + +This procedure creates a map of type `T` with no initial allocation, which will +use the allocator specified by `allocator` as its backing allocator when it +allocates. +*/ +@(require_results) +make_map :: proc( + $T: typeid/map[$K]$E, + allocator := context.allocator, + loc := #caller_location, +) -> (m: T) { + return runtime.make_map(T, allocator, loc) +} + +/* Allocate a map. This procedure creates a map of type `T` with initial capacity specified by @@ -961,13 +1005,13 @@ This procedure creates a map of type `T` with initial capacity specified by allocator. */ @(require_results) -make_map :: proc( +make_map_cap :: proc( $T: typeid/map[$K]$E, - #any_int cap: int = 1<<runtime.MAP_MIN_LOG2_CAPACITY, + #any_int cap: int, allocator := context.allocator, loc := #caller_location, ) -> (m: T, err: Allocator_Error) { - return runtime.make_map(T, cap, allocator, loc) + return runtime.make_map_cap(T, cap, allocator, loc) } /* @@ -1060,6 +1104,7 @@ make :: proc{ make_dynamic_array_len, make_dynamic_array_len_cap, make_map, + make_map_cap, make_multi_pointer, make_soa_slice, make_soa_dynamic_array, diff --git a/core/mem/allocators.odin b/core/mem/allocators.odin index 028be58e3..5b0b178f8 100644 --- a/core/mem/allocators.odin +++ b/core/mem/allocators.odin @@ -3,6 +3,57 @@ package mem import "base:intrinsics" import "base:runtime" +// NOTE(Feoramund): Sanitizer usage in this package has been temporarily +// disabled pending a thorough review per allocator, as ASan is particular +// about the addresses and ranges it receives. +// +// In short, it keeps track only of 8-byte blocks. This can cause issues if an +// allocator poisons an entire range but an allocation for less than 8 bytes is +// desired or if the next allocation address would not be 8-byte aligned. +// +// This must be handled carefully on a per-allocator basis and some allocators +// may not be able to participate. +// +// Please see the following link for more information: +// +// https://github.com/google/sanitizers/wiki/AddressSanitizerAlgorithm#mapping +// +// import "base:sanitizer" + + +/* +This procedure checks if a byte slice `range` is poisoned and makes sure the +root address of the poison range is the base pointer of `range`. + +This can help guard against buggy allocators returning memory that they already returned. + +This has no effect if `-sanitize:address` is not enabled. +*/ +// @(disabled=.Address not_in ODIN_SANITIZER_FLAGS, private) +// ensure_poisoned :: proc(range: []u8, loc := #caller_location) { +// cond := sanitizer.address_region_is_poisoned(range) == raw_data(range) +// // If this fails, we've overlapped an allocation and it's our fault. +// ensure(cond, `This allocator has sliced a block of memory of which some part is not poisoned before returning. +// This is a bug in the core library and should be reported to the Odin developers with a stack trace and minimal example code if possible.`, loc) +// } + +/* +This procedure checks if a byte slice `range` is not poisoned. + +This can help guard against buggy allocators resizing memory that they should not. + +This has no effect if `-sanitize:address` is not enabled. +*/ +// @(disabled=.Address not_in ODIN_SANITIZER_FLAGS, private) +// ensure_not_poisoned :: proc(range: []u8, loc := #caller_location) { +// cond := sanitizer.address_region_is_poisoned(range) == nil +// // If this fails, we've tried to resize memory that is poisoned, which +// // could be user error caused by an incorrect `old_memory` pointer. +// ensure(cond, `This allocator has sliced a block of memory of which some part is poisoned before returning. +// This may be a bug in the core library, or it could be user error due to an invalid pointer passed to a resize operation. +// If after ensuring your own code is not responsible, report the problem to the Odin developers with a stack trace and minimal example code if possible.`, loc) +// } + /* Nil allocator. @@ -107,11 +158,10 @@ The arena allocator (also known as a linear allocator, bump allocator, region allocator) is an allocator that uses a single backing buffer for allocations. -The buffer is being used contiguously, from start by end. Each subsequent -allocation occupies the next adjacent region of memory in the buffer. Since -arena allocator does not keep track of any metadata associated with the -allocations and their locations, it is impossible to free individual -allocations. +The buffer is used contiguously, from start to end. Each subsequent allocation +occupies the next adjacent region of memory in the buffer. Since the arena +allocator does not keep track of any metadata associated with the allocations +and their locations, it is impossible to free individual allocations. The arena allocator can be used for temporary allocations in frame-based memory management. Games are one example of such applications. A global arena can be @@ -130,7 +180,7 @@ arena_allocator :: proc(arena: ^Arena) -> Allocator { /* Initialize an arena. -This procedure initializes the arena `a` with memory region `data` as it's +This procedure initializes the arena `a` with memory region `data` as its backing buffer. */ arena_init :: proc(a: ^Arena, data: []byte) { @@ -138,6 +188,7 @@ arena_init :: proc(a: ^Arena, data: []byte) { a.offset = 0 a.peak_used = 0 a.temp_count = 0 + // sanitizer.address_poison(a.data) } /* @@ -214,7 +265,7 @@ arena_alloc_bytes_non_zeroed :: proc( loc := #caller_location ) -> ([]byte, Allocator_Error) { if a.data == nil { - panic("Arena is not initialized", loc) + panic("Allocation on uninitialized Arena allocator.", loc) } #no_bounds_check end := &a.data[a.offset] ptr := align_forward(end, uintptr(alignment)) @@ -224,14 +275,18 @@ arena_alloc_bytes_non_zeroed :: proc( } a.offset += total_size a.peak_used = max(a.peak_used, a.offset) - return byte_slice(ptr, size), nil + result := byte_slice(ptr, size) + // ensure_poisoned(result) + // sanitizer.address_unpoison(result) + return result, nil } /* -Free all memory to an arena. +Free all memory back to the arena allocator. */ arena_free_all :: proc(a: ^Arena) { a.offset = 0 + // sanitizer.address_poison(a.data) } arena_allocator_proc :: proc( @@ -270,12 +325,12 @@ arena_allocator_proc :: proc( } /* -Temporary memory region of arena. +Temporary memory region of an `Arena` allocator. -Temporary memory regions of arena act as "savepoints" for arena. When one is -created, the subsequent allocations are done inside the temporary memory -region. When `end_arena_temp_memory` is called, the arena is rolled back, and -all of the memory that was allocated from the arena will be freed. +Temporary memory regions of an arena act as "save-points" for the allocator. +When one is created, the subsequent allocations are done inside the temporary +memory region. When `end_arena_temp_memory` is called, the arena is rolled +back, and all of the memory that was allocated from the arena will be freed. Multiple temporary memory regions can exist at the same time for an arena. */ @@ -309,6 +364,7 @@ allocations *inside* the temporary memory region will be freed to the arena. end_arena_temp_memory :: proc(tmp: Arena_Temp_Memory) { assert(tmp.arena.offset >= tmp.prev_offset) assert(tmp.arena.temp_count > 0) + // sanitizer.address_poison(tmp.arena.data[tmp.prev_offset:tmp.arena.offset]) tmp.arena.offset = tmp.prev_offset tmp.arena.temp_count -= 1 } @@ -322,29 +378,37 @@ scratch_allocator_destroy :: scratch_destroy Scratch allocator data. */ Scratch :: struct { - data: []byte, - curr_offset: int, - prev_allocation: rawptr, - backup_allocator: Allocator, - leaked_allocations: [dynamic][]byte, + data: []byte, + curr_offset: int, + prev_allocation: rawptr, + prev_allocation_root: rawptr, + backup_allocator: Allocator, + leaked_allocations: [dynamic][]byte, } /* Scratch allocator. The scratch allocator works in a similar way to the `Arena` allocator. The -scratch allocator has a backing buffer, that is being allocated in -contiguous regions, from start to end. +scratch allocator has a backing buffer that is allocated in contiguous regions, +from start to end. Each subsequent allocation will be the next adjacent region of memory in the backing buffer. If the allocation doesn't fit into the remaining space of the backing buffer, this allocation is put at the start of the buffer, and all -previous allocations will become invalidated. If the allocation doesn't fit -into the backing buffer as a whole, it will be allocated using a backing -allocator, and pointer to the allocated memory region will be put into the -`leaked_allocations` array. +previous allocations will become invalidated. + +If the allocation doesn't fit into the backing buffer as a whole, it will be +allocated using a backing allocator, and the pointer to the allocated memory +region will be put into the `leaked_allocations` array. A `Warning`-level log +message will be sent as well. + +Allocations which are resized will be resized in-place if they were the last +allocation. Otherwise, they are re-allocated to avoid overwriting previous +allocations. -The `leaked_allocations` array is managed by the `context` allocator. +The `leaked_allocations` array is managed by the `context` allocator if no +`backup_allocator` is specified in `scratch_init`. */ @(require_results) scratch_allocator :: proc(allocator: ^Scratch) -> Allocator { @@ -355,19 +419,24 @@ scratch_allocator :: proc(allocator: ^Scratch) -> Allocator { } /* -Initialize scratch allocator. +Initialize a scratch allocator. */ scratch_init :: proc(s: ^Scratch, size: int, backup_allocator := context.allocator) -> Allocator_Error { s.data = make_aligned([]byte, size, 2*align_of(rawptr), backup_allocator) or_return s.curr_offset = 0 s.prev_allocation = nil + s.prev_allocation_root = nil s.backup_allocator = backup_allocator s.leaked_allocations.allocator = backup_allocator + // sanitizer.address_poison(s.data) return nil } /* Free all data associated with a scratch allocator. + +This is distinct from `scratch_free_all` in that it deallocates all memory used +to setup the allocator, as opposed to all allocations made from that space. */ scratch_destroy :: proc(s: ^Scratch) { if s == nil { @@ -377,12 +446,13 @@ scratch_destroy :: proc(s: ^Scratch) { free_bytes(ptr, s.backup_allocator) } delete(s.leaked_allocations) + // sanitizer.address_unpoison(s.data) delete(s.data, s.backup_allocator) s^ = {} } /* -Allocate memory from scratch allocator. +Allocate memory from a scratch allocator. This procedure allocates `size` bytes of memory aligned on a boundary specified by `alignment`. The allocated memory region is zero-initialized. This procedure @@ -400,7 +470,7 @@ scratch_alloc :: proc( } /* -Allocate memory from scratch allocator. +Allocate memory from a scratch allocator. This procedure allocates `size` bytes of memory aligned on a boundary specified by `alignment`. The allocated memory region is zero-initialized. This procedure @@ -421,7 +491,7 @@ scratch_alloc_bytes :: proc( } /* -Allocate non-initialized memory from scratch allocator. +Allocate non-initialized memory from a scratch allocator. This procedure allocates `size` bytes of memory aligned on a boundary specified by `alignment`. The allocated memory region is not explicitly zero-initialized. @@ -439,7 +509,7 @@ scratch_alloc_non_zeroed :: proc( } /* -Allocate non-initialized memory from scratch allocator. +Allocate non-initialized memory from a scratch allocator. This procedure allocates `size` bytes of memory aligned on a boundary specified by `alignment`. The allocated memory region is not explicitly zero-initialized. @@ -455,37 +525,47 @@ scratch_alloc_bytes_non_zeroed :: proc( if s.data == nil { DEFAULT_BACKING_SIZE :: 4 * Megabyte if !(context.allocator.procedure != scratch_allocator_proc && context.allocator.data != s) { - panic("cyclic initialization of the scratch allocator with itself", loc) + panic("Cyclic initialization of the scratch allocator with itself.", loc) } scratch_init(s, DEFAULT_BACKING_SIZE) } - size := size - size = align_forward_int(size, alignment) - if size <= len(s.data) { + aligned_size := size + if alignment > 1 { + // It is possible to do this with less bytes, but this is the + // mathematically simpler solution, and this being a Scratch allocator, + // we don't need to be so strict about every byte. + aligned_size += alignment - 1 + } + if aligned_size <= len(s.data) { offset := uintptr(0) - if s.curr_offset+size <= len(s.data) { + if s.curr_offset+aligned_size <= len(s.data) { offset = uintptr(s.curr_offset) } else { + // The allocation will cause an overflow past the boundary of the + // space available, so reset to the starting offset. offset = 0 } start := uintptr(raw_data(s.data)) - ptr := align_forward_uintptr(offset+start, uintptr(alignment)) - s.prev_allocation = rawptr(ptr) - s.curr_offset = int(offset) + size - return byte_slice(rawptr(ptr), size), nil + ptr := rawptr(offset+start) + // We keep track of the original base pointer without extra alignment + // in order to later allow the free operation to work from that point. + s.prev_allocation_root = ptr + if !is_aligned(ptr, alignment) { + ptr = align_forward(ptr, uintptr(alignment)) + } + s.prev_allocation = ptr + s.curr_offset = int(offset) + aligned_size + result := byte_slice(ptr, size) + // ensure_poisoned(result) + // sanitizer.address_unpoison(result) + return result, nil } else { + // NOTE: No need to use `aligned_size` here, as the backup allocator will handle alignment for us. a := s.backup_allocator - if a.procedure == nil { - a = context.allocator - s.backup_allocator = a - } ptr, err := alloc_bytes_non_zeroed(size, alignment, a, loc) if err != nil { return ptr, err } - if s.leaked_allocations == nil { - s.leaked_allocations, err = make([dynamic][]byte, a) - } append(&s.leaked_allocations, ptr) if logger := context.logger; logger.lowest_level <= .Warning { if logger.procedure != nil { @@ -497,7 +577,7 @@ scratch_alloc_bytes_non_zeroed :: proc( } /* -Free memory to the scratch allocator. +Free memory back to the scratch allocator. This procedure frees the memory region allocated at pointer `ptr`. @@ -506,7 +586,7 @@ operation is a no-op. */ scratch_free :: proc(s: ^Scratch, ptr: rawptr, loc := #caller_location) -> Allocator_Error { if s.data == nil { - panic("Free on an uninitialized scratch allocator", loc) + panic("Free on an uninitialized Scratch allocator.", loc) } if ptr == nil { return nil @@ -515,8 +595,10 @@ scratch_free :: proc(s: ^Scratch, ptr: rawptr, loc := #caller_location) -> Alloc end := start + uintptr(len(s.data)) old_ptr := uintptr(ptr) if s.prev_allocation == ptr { - s.curr_offset = int(uintptr(s.prev_allocation) - start) + s.curr_offset = int(uintptr(s.prev_allocation_root) - start) + // sanitizer.address_poison(s.data[s.curr_offset:]) s.prev_allocation = nil + s.prev_allocation_root = nil return nil } if start <= old_ptr && old_ptr < end { @@ -537,7 +619,7 @@ scratch_free :: proc(s: ^Scratch, ptr: rawptr, loc := #caller_location) -> Alloc } /* -Free all memory to the scratch allocator. +Free all memory back to the scratch allocator. */ scratch_free_all :: proc(s: ^Scratch, loc := #caller_location) { s.curr_offset = 0 @@ -546,14 +628,15 @@ scratch_free_all :: proc(s: ^Scratch, loc := #caller_location) { free_bytes(ptr, s.backup_allocator, loc) } clear(&s.leaked_allocations) + // sanitizer.address_poison(s.data) } /* -Resize an allocation. +Resize an allocation owned by a scratch allocator. -This procedure resizes a memory region, defined by its location, `old_memory`, -and its size, `old_size` to have a size `size` and alignment `alignment`. The -newly allocated memory, if any is zero-initialized. +This procedure resizes a memory region defined by its location `old_memory` +and its size `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any, is zero-initialized. If `old_memory` is `nil`, this procedure acts just like `scratch_alloc()`, allocating a memory region `size` bytes in size, aligned on a boundary specified @@ -578,10 +661,10 @@ scratch_resize :: proc( } /* -Resize an allocation. +Resize an allocation owned by a scratch allocator. -This procedure resizes a memory region, specified by `old_data`, to have a size -`size` and alignment `alignment`. The newly allocated memory, if any is +This procedure resizes a memory region specified by `old_data` to have a size +`size` and alignment `alignment`. The newly allocated memory, if any, is zero-initialized. If `old_memory` is `nil`, this procedure acts just like `scratch_alloc()`, @@ -609,11 +692,11 @@ scratch_resize_bytes :: proc( } /* -Resize an allocation without zero-initialization. +Resize an allocation owned by a scratch allocator, without zero-initialization. -This procedure resizes a memory region, defined by its location, `old_memory`, -and its size, `old_size` to have a size `size` and alignment `alignment`. The -newly allocated memory, if any is not explicitly zero-initialized. +This procedure resizes a memory region defined by its location `old_memory` +and its size `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any, is not explicitly zero-initialized. If `old_memory` is `nil`, this procedure acts just like `scratch_alloc()`, allocating a memory region `size` bytes in size, aligned on a boundary specified @@ -638,10 +721,10 @@ scratch_resize_non_zeroed :: proc( } /* -Resize an allocation. +Resize an allocation owned by a scratch allocator. -This procedure resizes a memory region, specified by `old_data`, to have a size -`size` and alignment `alignment`. The newly allocated memory, if any is not +This procedure resizes a memory region specified by `old_data` to have a size +`size` and alignment `alignment`. The newly allocated memory, if any, is not explicitly zero-initialized. If `old_memory` is `nil`, this procedure acts just like `scratch_alloc()`, @@ -666,16 +749,25 @@ scratch_resize_bytes_non_zeroed :: proc( if s.data == nil { DEFAULT_BACKING_SIZE :: 4 * Megabyte if !(context.allocator.procedure != scratch_allocator_proc && context.allocator.data != s) { - panic("cyclic initialization of the scratch allocator with itself", loc) + panic("Cyclic initialization of the scratch allocator with itself.", loc) } scratch_init(s, DEFAULT_BACKING_SIZE) } begin := uintptr(raw_data(s.data)) end := begin + uintptr(len(s.data)) old_ptr := uintptr(old_memory) - if begin <= old_ptr && old_ptr < end && old_ptr+uintptr(size) < end { + // We can only sanely resize the last allocation; to do otherwise may + // overwrite memory that could very well just have been allocated. + // + // Also, the alignments must match, otherwise we must re-allocate to + // guarantee the user's request. + if s.prev_allocation == old_memory && is_aligned(old_memory, alignment) && old_ptr+uintptr(size) < end { + // ensure_not_poisoned(old_data) + // sanitizer.address_poison(old_memory) s.curr_offset = int(old_ptr-begin)+size - return byte_slice(old_memory, size), nil + result := byte_slice(old_memory, size) + // sanitizer.address_unpoison(result) + return result, nil } data, err := scratch_alloc_bytes_non_zeroed(s, size, alignment, loc) if err != nil { @@ -766,7 +858,7 @@ stack_allocator :: proc(stack: ^Stack) -> Allocator { } /* -Initialize the stack allocator. +Initialize a stack allocator. This procedure initializes the stack allocator with a backing buffer specified by `data` parameter. @@ -776,10 +868,11 @@ stack_init :: proc(s: ^Stack, data: []byte) { s.prev_offset = 0 s.curr_offset = 0 s.peak_used = 0 + // sanitizer.address_poison(data) } /* -Allocate memory from stack. +Allocate memory from a stack allocator. This procedure allocates `size` bytes of memory, aligned to the boundary specified by `alignment`. The allocated memory is zero-initialized. This @@ -797,7 +890,7 @@ stack_alloc :: proc( } /* -Allocate memory from stack. +Allocate memory from a stack allocator. This procedure allocates `size` bytes of memory, aligned to the boundary specified by `alignment`. The allocated memory is zero-initialized. This @@ -818,7 +911,7 @@ stack_alloc_bytes :: proc( } /* -Allocate memory from stack. +Allocate memory from a stack allocator. This procedure allocates `size` bytes of memory, aligned to the boundary specified by `alignment`. The allocated memory is not explicitly @@ -836,13 +929,13 @@ stack_alloc_non_zeroed :: proc( } /* -Allocate memory from stack. +Allocate memory from a stack allocator. This procedure allocates `size` bytes of memory, aligned to the boundary specified by `alignment`. The allocated memory is not explicitly zero-initialized. This procedure returns the slice of the allocated memory. */ -@(require_results) +@(require_results, no_sanitize_address) stack_alloc_bytes_non_zeroed :: proc( s: ^Stack, size: int, @@ -850,7 +943,7 @@ stack_alloc_bytes_non_zeroed :: proc( loc := #caller_location ) -> ([]byte, Allocator_Error) { if s.data == nil { - panic("Stack allocation on an uninitialized stack allocator", loc) + panic("Allocation on an uninitialized Stack allocator.", loc) } curr_addr := uintptr(raw_data(s.data)) + uintptr(s.curr_offset) padding := calc_padding_with_header( @@ -861,22 +954,26 @@ stack_alloc_bytes_non_zeroed :: proc( if s.curr_offset + padding + size > len(s.data) { return nil, .Out_Of_Memory } + old_offset := s.prev_offset s.prev_offset = s.curr_offset s.curr_offset += padding next_addr := curr_addr + uintptr(padding) header := (^Stack_Allocation_Header)(next_addr - size_of(Stack_Allocation_Header)) header.padding = padding - header.prev_offset = s.prev_offset + header.prev_offset = old_offset s.curr_offset += size s.peak_used = max(s.peak_used, s.curr_offset) - return byte_slice(rawptr(next_addr), size), nil + result := byte_slice(rawptr(next_addr), size) + // ensure_poisoned(result) + // sanitizer.address_unpoison(result) + return result, nil } /* -Free memory to the stack. +Free memory back to the stack allocator. This procedure frees the memory region starting at `old_memory` to the stack. -If the freeing does is an out of order freeing, the `.Invalid_Pointer` error +If the freeing is an out of order freeing, the `.Invalid_Pointer` error is returned. */ stack_free :: proc( @@ -885,7 +982,7 @@ stack_free :: proc( loc := #caller_location, ) -> (Allocator_Error) { if s.data == nil { - panic("Stack free on an uninitialized stack allocator", loc) + panic("Free on an uninitialized Stack allocator.", loc) } if old_memory == nil { return nil @@ -894,7 +991,7 @@ stack_free :: proc( end := start + uintptr(len(s.data)) curr_addr := uintptr(old_memory) if !(start <= curr_addr && curr_addr < end) { - panic("Out of bounds memory address passed to stack allocator (free)", loc) + panic("Out of bounds memory address passed to Stack allocator. (free)", loc) } if curr_addr >= start+uintptr(s.curr_offset) { // NOTE(bill): Allow double frees @@ -902,29 +999,32 @@ stack_free :: proc( } header := (^Stack_Allocation_Header)(curr_addr - size_of(Stack_Allocation_Header)) old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data))) - if old_offset != header.prev_offset { - // panic("Out of order stack allocator free"); + if old_offset != s.prev_offset { return .Invalid_Pointer } - s.curr_offset = old_offset + s.prev_offset = header.prev_offset + // sanitizer.address_poison(s.data[old_offset:s.curr_offset]) + s.curr_offset = old_offset + return nil } /* -Free all allocations to the stack. +Free all memory back to the stack allocator. */ stack_free_all :: proc(s: ^Stack, loc := #caller_location) { s.prev_offset = 0 s.curr_offset = 0 + // sanitizer.address_poison(s.data) } /* -Resize an allocation. +Resize an allocation owned by a stack allocator. -This procedure resizes a memory region, defined by its location, `old_memory`, -and its size, `old_size` to have a size `size` and alignment `alignment`. The -newly allocated memory, if any is zero-initialized. +This procedure resizes a memory region defined by its location `old_memory` +and its size `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any, is zero-initialized. If `old_memory` is `nil`, this procedure acts just like `stack_alloc()`, allocating a memory region `size` bytes in size, aligned on a boundary specified @@ -949,11 +1049,11 @@ stack_resize :: proc( } /* -Resize an allocation. +Resize an allocation owned by a stack allocator. -This procedure resizes a memory region, specified by the `old_data` parameter -to have a size `size` and alignment `alignment`. The newly allocated memory, -if any is zero-initialized. +This procedure resizes a memory region specified by `old_data` to have a size +`size` and alignment `alignment`. The newly allocated memory, if any, is +zero-initialized. If `old_memory` is `nil`, this procedure acts just like `stack_alloc()`, allocating a memory region `size` bytes in size, aligned on a boundary specified @@ -972,8 +1072,8 @@ stack_resize_bytes :: proc( alignment := DEFAULT_ALIGNMENT, loc := #caller_location, ) -> ([]byte, Allocator_Error) { - bytes, err := stack_alloc_bytes_non_zeroed(s, size, alignment, loc) - if bytes != nil { + bytes, err := stack_resize_bytes_non_zeroed(s, old_data, size, alignment, loc) + if err == nil { if old_data == nil { zero_slice(bytes) } else if size > len(old_data) { @@ -984,11 +1084,11 @@ stack_resize_bytes :: proc( } /* -Resize an allocation without zero-initialization. +Resize an allocation owned by a stack allocator, without zero-initialization. -This procedure resizes a memory region, defined by its location, `old_memory`, -and its size, `old_size` to have a size `size` and alignment `alignment`. The -newly allocated memory, if any is not explicitly zero-initialized. +This procedure resizes a memory region defined by its location `old_memory` +and its size `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any, is not explicitly zero-initialized. If `old_memory` is `nil`, this procedure acts just like `stack_alloc()`, allocating a memory region `size` bytes in size, aligned on a boundary specified @@ -1013,11 +1113,11 @@ stack_resize_non_zeroed :: proc( } /* -Resize an allocation without zero-initialization. +Resize an allocation owned by a stack allocator, without zero-initialization. -This procedure resizes a memory region, specified by the `old_data` parameter -to have a size `size` and alignment `alignment`. The newly allocated memory, -if any is not explicitly zero-initialized. +This procedure resizes a memory region specified by `old_data` to have a size +`size` and alignment `alignment`. The newly allocated memory, if any, is not +explicitly zero-initialized. If `old_memory` is `nil`, this procedure acts just like `stack_alloc()`, allocating a memory region `size` bytes in size, aligned on a boundary specified @@ -1039,24 +1139,34 @@ stack_resize_bytes_non_zeroed :: proc( old_memory := raw_data(old_data) old_size := len(old_data) if s.data == nil { - panic("Stack free all on an uninitialized stack allocator", loc) + panic("Resize on an uninitialized Stack allocator.", loc) } if old_memory == nil { return stack_alloc_bytes_non_zeroed(s, size, alignment, loc) } if size == 0 { - return nil, nil + return nil, stack_free(s, old_memory, loc) } start := uintptr(raw_data(s.data)) end := start + uintptr(len(s.data)) curr_addr := uintptr(old_memory) if !(start <= curr_addr && curr_addr < end) { - panic("Out of bounds memory address passed to stack allocator (resize)") + panic("Out of bounds memory address passed to Stack allocator. (resize)") } if curr_addr >= start+uintptr(s.curr_offset) { // NOTE(bill): Allow double frees return nil, nil } + if uintptr(old_memory) & uintptr(alignment-1) != 0 { + // A different alignment has been requested and the current address + // does not satisfy it. + data, err := stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + if err == nil { + runtime.copy(data, byte_slice(old_memory, old_size)) + // sanitizer.address_poison(old_memory) + } + return data, err + } if old_size == size { return byte_slice(old_memory, size), nil } @@ -1066,6 +1176,7 @@ stack_resize_bytes_non_zeroed :: proc( data, err := stack_alloc_bytes_non_zeroed(s, size, alignment, loc) if err == nil { runtime.copy(data, byte_slice(old_memory, old_size)) + // sanitizer.address_poison(old_memory) } return data, err } @@ -1075,8 +1186,13 @@ stack_resize_bytes_non_zeroed :: proc( s.curr_offset += diff // works for smaller sizes too if diff > 0 { zero(rawptr(curr_addr + uintptr(diff)), diff) + } else { + // sanitizer.address_poison(old_data[size:]) } - return byte_slice(old_memory, size), nil + result := byte_slice(old_memory, size) + // ensure_poisoned(result) + // sanitizer.address_unpoison(result) + return result, nil } stack_allocator_proc :: proc( @@ -1135,7 +1251,7 @@ Small_Stack :: struct { } /* -Initialize small stack. +Initialize a small stack allocator. This procedure initializes the small stack allocator with `data` as its backing buffer. @@ -1144,14 +1260,18 @@ small_stack_init :: proc(s: ^Small_Stack, data: []byte) { s.data = data s.offset = 0 s.peak_used = 0 + // sanitizer.address_poison(data) } /* Small stack allocator. -The small stack allocator is just like a stack allocator, with the only +The small stack allocator is just like a `Stack` allocator, with the only difference being an extremely small header size. Unlike the stack allocator, -small stack allows out-of order freeing of memory. +the small stack allows out-of order freeing of memory, with the stipulation +that all allocations made after the freed allocation will become invalidated +upon following allocations as they will begin to overwrite the memory formerly +used by the freed allocation. The memory is allocated in the backing buffer linearly, from start to end. Each subsequent allocation will get the next adjacent memory region. @@ -1169,7 +1289,7 @@ small_stack_allocator :: proc(stack: ^Small_Stack) -> Allocator { } /* -Allocate memory from small stack. +Allocate memory from a small stack allocator. This procedure allocates `size` bytes of memory aligned to a boundary specified by `alignment`. The allocated memory is zero-initialized. This procedure @@ -1187,7 +1307,7 @@ small_stack_alloc :: proc( } /* -Allocate memory from small stack. +Allocate memory from a small stack allocator. This procedure allocates `size` bytes of memory aligned to a boundary specified by `alignment`. The allocated memory is zero-initialized. This procedure @@ -1208,7 +1328,7 @@ small_stack_alloc_bytes :: proc( } /* -Allocate memory from small stack. +Allocate memory from a small stack allocator. This procedure allocates `size` bytes of memory aligned to a boundary specified by `alignment`. The allocated memory is not explicitly zero-initialized. This @@ -1226,13 +1346,13 @@ small_stack_alloc_non_zeroed :: proc( } /* -Allocate memory from small stack. +Allocate memory from a small stack allocator. This procedure allocates `size` bytes of memory aligned to a boundary specified by `alignment`. The allocated memory is not explicitly zero-initialized. This procedure returns a slice of the allocated memory region. */ -@(require_results) +@(require_results, no_sanitize_address) small_stack_alloc_bytes_non_zeroed :: proc( s: ^Small_Stack, size: int, @@ -1240,7 +1360,7 @@ small_stack_alloc_bytes_non_zeroed :: proc( loc := #caller_location, ) -> ([]byte, Allocator_Error) { if s.data == nil { - panic("Small stack is not initialized", loc) + panic("Allocation on an uninitialized Small Stack allocator.", loc) } alignment := alignment alignment = clamp(alignment, 1, 8*size_of(Stack_Allocation_Header{}.padding)/2) @@ -1252,14 +1372,21 @@ small_stack_alloc_bytes_non_zeroed :: proc( s.offset += padding next_addr := curr_addr + uintptr(padding) header := (^Small_Stack_Allocation_Header)(next_addr - size_of(Small_Stack_Allocation_Header)) - header.padding = auto_cast padding + header.padding = cast(u8)padding + // We must poison the header, no matter what its state is, because there + // may have been an out-of-order free before this point. + // sanitizer.address_poison(header) s.offset += size s.peak_used = max(s.peak_used, s.offset) - return byte_slice(rawptr(next_addr), size), nil + result := byte_slice(rawptr(next_addr), size) + // NOTE: We cannot ensure the poison state of this allocation, because this + // allocator allows out-of-order frees with overwriting. + // sanitizer.address_unpoison(result) + return result, nil } /* -Allocate memory from small stack. +Allocate memory from a small stack allocator. This procedure allocates `size` bytes of memory aligned to a boundary specified by `alignment`. The allocated memory is not explicitly zero-initialized. This @@ -1271,7 +1398,7 @@ small_stack_free :: proc( loc := #caller_location, ) -> Allocator_Error { if s.data == nil { - panic("Small stack is not initialized", loc) + panic("Free on an uninitialized Small Stack allocator.", loc) } if old_memory == nil { return nil @@ -1280,8 +1407,7 @@ small_stack_free :: proc( end := start + uintptr(len(s.data)) curr_addr := uintptr(old_memory) if !(start <= curr_addr && curr_addr < end) { - // panic("Out of bounds memory address passed to stack allocator (free)"); - return .Invalid_Pointer + panic("Out of bounds memory address passed to Small Stack allocator. (free)", loc) } if curr_addr >= start+uintptr(s.offset) { // NOTE(bill): Allow double frees @@ -1289,23 +1415,25 @@ small_stack_free :: proc( } header := (^Small_Stack_Allocation_Header)(curr_addr - size_of(Small_Stack_Allocation_Header)) old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data))) + // sanitizer.address_poison(s.data[old_offset:s.offset]) s.offset = old_offset return nil } /* -Free all memory to small stack. +Free all memory back to the small stack allocator. */ small_stack_free_all :: proc(s: ^Small_Stack) { s.offset = 0 + // sanitizer.address_poison(s.data) } /* -Resize an allocation. +Resize an allocation owned by a small stack allocator. -This procedure resizes a memory region, defined by its location, `old_memory`, -and its size, `old_size` to have a size `size` and alignment `alignment`. The -newly allocated memory, if any is zero-initialized. +This procedure resizes a memory region defined by its location `old_memory` +and its size `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any, is zero-initialized. If `old_memory` is `nil`, this procedure acts just like `small_stack_alloc()`, allocating a memory region `size` bytes in size, aligned on a boundary specified @@ -1330,11 +1458,11 @@ small_stack_resize :: proc( } /* -Resize an allocation. +Resize an allocation owned by a small stack allocator. -This procedure resizes a memory region, specified by the `old_data` parameter -to have a size `size` and alignment `alignment`. The newly allocated memory, -if any is zero-initialized. +This procedure resizes a memory region specified by `old_data` to have a size +`size` and alignment `alignment`. The newly allocated memory, if any, is +zero-initialized. If `old_memory` is `nil`, this procedure acts just like `small_stack_alloc()`, allocating a memory region `size` bytes in size, aligned on a boundary specified @@ -1365,11 +1493,11 @@ small_stack_resize_bytes :: proc( } /* -Resize an allocation without zero-initialization. +Resize an allocation owned by a small stack allocator, without zero-initialization. -This procedure resizes a memory region, defined by its location, `old_memory`, -and its size, `old_size` to have a size `size` and alignment `alignment`. The -newly allocated memory, if any is not explicitly zero-initialized. +This procedure resizes a memory region defined by its location `old_memory` +and its size `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any, is not explicitly zero-initialized. If `old_memory` is `nil`, this procedure acts just like `small_stack_alloc()`, allocating a memory region `size` bytes in size, aligned on a boundary specified @@ -1394,11 +1522,11 @@ small_stack_resize_non_zeroed :: proc( } /* -Resize an allocation without zero-initialization. +Resize an allocation owned by a small stack allocator, without zero-initialization. -This procedure resizes a memory region, specified by the `old_data` parameter -to have a size `size` and alignment `alignment`. The newly allocated memory, -if any is not explicitly zero-initialized. +This procedure resizes a memory region specified by `old_data` to have a size +`size` and alignment `alignment`. The newly allocated memory, if any, is not +explicitly zero-initialized. If `old_memory` is `nil`, this procedure acts just like `small_stack_alloc()`, allocating a memory region `size` bytes in size, aligned on a boundary specified @@ -1418,7 +1546,7 @@ small_stack_resize_bytes_non_zeroed :: proc( loc := #caller_location, ) -> ([]byte, Allocator_Error) { if s.data == nil { - panic("Small stack is not initialized", loc) + panic("Resize on an uninitialized Small Stack allocator.", loc) } old_memory := raw_data(old_data) old_size := len(old_data) @@ -1428,21 +1556,32 @@ small_stack_resize_bytes_non_zeroed :: proc( return small_stack_alloc_bytes_non_zeroed(s, size, alignment, loc) } if size == 0 { - return nil, nil + return nil, small_stack_free(s, old_memory, loc) } start := uintptr(raw_data(s.data)) end := start + uintptr(len(s.data)) curr_addr := uintptr(old_memory) if !(start <= curr_addr && curr_addr < end) { - // panic("Out of bounds memory address passed to stack allocator (resize)"); - return nil, .Invalid_Pointer + panic("Out of bounds memory address passed to Small Stack allocator. (resize)", loc) } if curr_addr >= start+uintptr(s.offset) { // NOTE(bill): Treat as a double free return nil, nil } + if uintptr(old_memory) & uintptr(alignment-1) != 0 { + // A different alignment has been requested and the current address + // does not satisfy it. + data, err := small_stack_alloc_bytes_non_zeroed(s, size, alignment, loc) + if err == nil { + runtime.copy(data, byte_slice(old_memory, old_size)) + // sanitizer.address_poison(old_memory) + } + return data, err + } if old_size == size { - return byte_slice(old_memory, size), nil + result := byte_slice(old_memory, size) + // sanitizer.address_unpoison(result) + return result, nil } data, err := small_stack_alloc_bytes_non_zeroed(s, size, alignment, loc) if err == nil { @@ -1559,18 +1698,18 @@ dynamic_arena_init :: proc( Dynamic arena allocator. The dynamic arena allocator uses blocks of a specific size, allocated on-demand -using the block allocator. This allocator acts similarly to arena. All +using the block allocator. This allocator acts similarly to `Arena`. All allocations in a block happen contiguously, from start to end. If an allocation -does not fit into the remaining space of the block, and its size is smaller +does not fit into the remaining space of the block and its size is smaller than the specified out-band size, a new block is allocated using the `block_allocator` and the allocation is performed from a newly-allocated block. -If an allocation has bigger size than the specified out-band size, a new block +If an allocation is larger than the specified out-band size, a new block is allocated such that the allocation fits into this new block. This is referred to as an *out-band allocation*. The out-band blocks are kept separately from normal blocks. -Just like arena, the dynamic arena does not support freeing of individual +Just like `Arena`, the dynamic arena does not support freeing of individual objects. */ @(require_results) @@ -1584,7 +1723,7 @@ dynamic_arena_allocator :: proc(a: ^Dynamic_Arena) -> Allocator { /* Destroy a dynamic arena. -This procedure frees all allocations, made on a dynamic arena, including the +This procedure frees all allocations made on a dynamic arena, including the unused blocks, as well as the arrays for storing blocks. */ dynamic_arena_destroy :: proc(a: ^Dynamic_Arena) { @@ -1598,7 +1737,7 @@ dynamic_arena_destroy :: proc(a: ^Dynamic_Arena) { @(private="file") _dynamic_arena_cycle_new_block :: proc(a: ^Dynamic_Arena, loc := #caller_location) -> (err: Allocator_Error) { if a.block_allocator.procedure == nil { - panic("You must call arena_init on a Pool before using it", loc) + panic("You must call `dynamic_arena_init` on a Dynamic Arena before using it.", loc) } if a.current_block != nil { append(&a.used_blocks, a.current_block, loc=loc) @@ -1616,6 +1755,7 @@ _dynamic_arena_cycle_new_block :: proc(a: ^Dynamic_Arena, loc := #caller_locatio nil, 0, ) + // sanitizer.address_poison(data) new_block = raw_data(data) } a.bytes_left = a.block_size @@ -1632,7 +1772,7 @@ by `alignment` from a dynamic arena `a`. The allocated memory is zero-initialized. This procedure returns a pointer to the newly allocated memory region. */ -@(private, require_results) +@(require_results) dynamic_arena_alloc :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> (rawptr, Allocator_Error) { data, err := dynamic_arena_alloc_bytes(a, size, loc) return raw_data(data), err @@ -1679,18 +1819,18 @@ memory region. */ @(require_results) dynamic_arena_alloc_bytes_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { - n := align_formula(size, a.alignment) - if n > a.block_size { - return nil, .Invalid_Argument - } - if n >= a.out_band_size { - assert(a.block_allocator.procedure != nil, "Backing block allocator must be initialized", loc=loc) - memory, err := alloc_bytes_non_zeroed(a.block_size, a.alignment, a.block_allocator, loc) + if size >= a.out_band_size { + assert(a.out_band_allocations.allocator.procedure != nil, "Backing array allocator must be initialized", loc=loc) + memory, err := alloc_bytes_non_zeroed(size, a.alignment, a.out_band_allocations.allocator, loc) if memory != nil { append(&a.out_band_allocations, raw_data(memory), loc = loc) } return memory, err } + n := align_formula(size, a.alignment) + if n > a.block_size { + return nil, .Invalid_Argument + } if a.bytes_left < n { err := _dynamic_arena_cycle_new_block(a, loc) if err != nil { @@ -1703,59 +1843,62 @@ dynamic_arena_alloc_bytes_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, loc : memory := a.current_pos a.current_pos = ([^]byte)(a.current_pos)[n:] a.bytes_left -= n - return ([^]byte)(memory)[:size], nil + result := ([^]byte)(memory)[:size] + // ensure_poisoned(result) + // sanitizer.address_unpoison(result) + return result, nil } /* -Reset the dynamic arena. +Reset a dynamic arena allocator. -This procedure frees all the allocations, owned by the dynamic arena, excluding +This procedure frees all the allocations owned by the dynamic arena, excluding the unused blocks. */ dynamic_arena_reset :: proc(a: ^Dynamic_Arena, loc := #caller_location) { if a.current_block != nil { + // sanitizer.address_poison(a.current_block, a.block_size) append(&a.unused_blocks, a.current_block, loc=loc) a.current_block = nil } for block in a.used_blocks { + // sanitizer.address_poison(block, a.block_size) append(&a.unused_blocks, block, loc=loc) } clear(&a.used_blocks) for allocation in a.out_band_allocations { - free(allocation, a.block_allocator, loc=loc) + free(allocation, a.out_band_allocations.allocator, loc=loc) } clear(&a.out_band_allocations) a.bytes_left = 0 // Make new allocations call `_dynamic_arena_cycle_new_block` again. } /* -Free all memory from a dynamic arena. +Free all memory back to the dynamic arena allocator. -This procedure frees all the allocations, owned by the dynamic arena, including +This procedure frees all the allocations owned by the dynamic arena, including the unused blocks. */ dynamic_arena_free_all :: proc(a: ^Dynamic_Arena, loc := #caller_location) { dynamic_arena_reset(a) for block in a.unused_blocks { + // sanitizer.address_unpoison(block, a.block_size) free(block, a.block_allocator, loc) } clear(&a.unused_blocks) } /* -Resize an allocation. +Resize an allocation owned by a dynamic arena allocator. -This procedure resizes a memory region, defined by its location, `old_memory`, -and its size, `old_size` to have a size `size` and alignment `alignment`. The -newly allocated memory, if any is zero-initialized. +This procedure resizes a memory region defined by its location `old_memory` +and its size `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any, is zero-initialized. If `old_memory` is `nil`, this procedure acts just like `dynamic_arena_alloc()`, allocating a memory region `size` bytes in size, aligned on a boundary specified by `alignment`. -If `size` is 0, this procedure acts just like `dynamic_arena_free()`, freeing -the memory region located at an address specified by `old_memory`. - This procedure returns the pointer to the resized memory region. */ @(require_results) @@ -1771,19 +1914,16 @@ dynamic_arena_resize :: proc( } /* -Resize an allocation. +Resize an allocation owned by a dynamic arena allocator. -This procedure resizes a memory region, specified by `old_data`, to have a size -`size` and alignment `alignment`. The newly allocated memory, if any is +This procedure resizes a memory region specified by `old_data` to have a size +`size` and alignment `alignment`. The newly allocated memory, if any, is zero-initialized. If `old_memory` is `nil`, this procedure acts just like `dynamic_arena_alloc()`, allocating a memory region `size` bytes in size, aligned on a boundary specified by `alignment`. -If `size` is 0, this procedure acts just like `dynamic_arena_free()`, freeing the -memory region located at an address specified by `old_memory`. - This procedure returns the slice of the resized memory region. */ @(require_results) @@ -1793,6 +1933,10 @@ dynamic_arena_resize_bytes :: proc( size: int, loc := #caller_location, ) -> ([]byte, Allocator_Error) { + if size == 0 { + // NOTE: This allocator has no Free mode. + return nil, nil + } bytes, err := dynamic_arena_resize_bytes_non_zeroed(a, old_data, size, loc) if bytes != nil { if old_data == nil { @@ -1805,19 +1949,16 @@ dynamic_arena_resize_bytes :: proc( } /* -Resize an allocation without zero-initialization. +Resize an allocation owned by a dynamic arena allocator, without zero-initialization. -This procedure resizes a memory region, defined by its location, `old_memory`, -and its size, `old_size` to have a size `size` and alignment `alignment`. The -newly allocated memory, if any is not explicitly zero-initialized. +This procedure resizes a memory region defined by its location `old_memory` +and its size `old_size` to have a size `size` and alignment `alignment`. The +newly allocated memory, if any, is not explicitly zero-initialized. If `old_memory` is `nil`, this procedure acts just like `dynamic_arena_alloc()`, allocating a memory region `size` bytes in size, aligned on a boundary specified by `alignment`. -If `size` is 0, this procedure acts just like `dynamic_arena_free()`, freeing the -memory region located at an address specified by `old_memory`. - This procedure returns the pointer to the resized memory region. */ @(require_results) @@ -1833,19 +1974,16 @@ dynamic_arena_resize_non_zeroed :: proc( } /* -Resize an allocation. +Resize an allocation owned by a dynamic arena allocator, without zero-initialization. -This procedure resizes a memory region, specified by `old_data`, to have a size -`size` and alignment `alignment`. The newly allocated memory, if any is not +This procedure resizes a memory region specified by `old_data` to have a size +`size` and alignment `alignment`. The newly allocated memory, if any, is not explicitly zero-initialized. If `old_memory` is `nil`, this procedure acts just like `dynamic_arena_alloc()`, allocating a memory region `size` bytes in size, aligned on a boundary specified by `alignment`. -If `size` is 0, this procedure acts just like `dynamic_arena_free()`, freeing -the memory region located at an address specified by `old_memory`. - This procedure returns the slice of the resized memory region. */ @(require_results) @@ -1855,11 +1993,18 @@ dynamic_arena_resize_bytes_non_zeroed :: proc( size: int, loc := #caller_location, ) -> ([]byte, Allocator_Error) { + if size == 0 { + // NOTE: This allocator has no Free mode. + return nil, nil + } old_memory := raw_data(old_data) old_size := len(old_data) if old_size >= size { + // sanitizer.address_poison(old_data[size:]) return byte_slice(old_memory, size), nil } + // No information is kept about allocations in this allocator, thus we + // cannot truly resize anything and must reallocate. data, err := dynamic_arena_alloc_bytes_non_zeroed(a, size, loc) if err == nil { runtime.copy(data, byte_slice(old_memory, old_size)) @@ -1920,7 +2065,7 @@ Buddy_Block :: struct #align(align_of(uint)) { /* Obtain the next buddy block. */ -@(require_results) +@(require_results, no_sanitize_address) buddy_block_next :: proc(block: ^Buddy_Block) -> ^Buddy_Block { return (^Buddy_Block)(([^]byte)(block)[block.size:]) } @@ -1928,7 +2073,7 @@ buddy_block_next :: proc(block: ^Buddy_Block) -> ^Buddy_Block { /* Split the block into two, by truncating the given block to a given size. */ -@(require_results) +@(require_results, no_sanitize_address) buddy_block_split :: proc(block: ^Buddy_Block, size: uint) -> ^Buddy_Block { block := block if block != nil && size != 0 { @@ -1951,6 +2096,7 @@ buddy_block_split :: proc(block: ^Buddy_Block, size: uint) -> ^Buddy_Block { /* Coalesce contiguous blocks in a range of blocks into one. */ +@(no_sanitize_address) buddy_block_coalescence :: proc(head, tail: ^Buddy_Block) { for { // Keep looping until there are no more buddies to coalesce @@ -1987,7 +2133,7 @@ buddy_block_coalescence :: proc(head, tail: ^Buddy_Block) { /* Find the best block for storing a given size in a range of blocks. */ -@(require_results) +@(require_results, no_sanitize_address) buddy_block_find_best :: proc(head, tail: ^Buddy_Block, size: uint) -> ^Buddy_Block { assert(size != 0) best_block: ^Buddy_Block @@ -2050,7 +2196,7 @@ The buddy allocator data. */ Buddy_Allocator :: struct { head: ^Buddy_Block, - tail: ^Buddy_Block, + tail: ^Buddy_Block `fmt:"-"`, alignment: uint, } @@ -2073,10 +2219,13 @@ buddy_allocator :: proc(b: ^Buddy_Allocator) -> Allocator { } /* -Initialize the buddy allocator. +Initialize a buddy allocator. This procedure initializes the buddy allocator `b` with a backing buffer `data` and block alignment specified by `alignment`. + +`alignment` may be any power of two, but the backing buffer must be aligned to +at least `size_of(Buddy_Block)`. */ buddy_allocator_init :: proc(b: ^Buddy_Allocator, data: []byte, alignment: uint, loc := #caller_location) { assert(data != nil) @@ -2087,12 +2236,14 @@ buddy_allocator_init :: proc(b: ^Buddy_Allocator, data: []byte, alignment: uint, alignment = size_of(Buddy_Block) } ptr := raw_data(data) - assert(uintptr(ptr) % uintptr(alignment) == 0, "data is not aligned to minimum alignment", loc) + assert(uintptr(ptr) % uintptr(alignment) == 0, "The data is not aligned to the minimum alignment, which must be at least `size_of(Buddy_Block)`.", loc) b.head = (^Buddy_Block)(ptr) b.head.size = len(data) b.head.is_free = true b.tail = buddy_block_next(b.head) b.alignment = alignment + assert(uint(len(data)) >= 2 * buddy_block_size_required(b, 1), "The size of the backing buffer must be large enough to hold at least two 1-byte allocations given the alignment requirements, otherwise it cannot split.", loc) + // sanitizer.address_poison(data) } /* @@ -2100,12 +2251,14 @@ Get required block size to fit in the allocation as well as the alignment paddin */ @(require_results) buddy_block_size_required :: proc(b: ^Buddy_Allocator, size: uint) -> uint { - size := size - actual_size := b.alignment - size += size_of(Buddy_Block) - size = align_forward_uint(size, b.alignment) - for size > actual_size { - actual_size <<= 1 + assert(size > 0) + // NOTE: `size_of(Buddy_Block)` will be accounted for in `b.alignment`. + // This calculation is also previously guarded against being given a `size` + // 0 by `buddy_allocator_alloc_bytes_non_zeroed` checking for that. + actual_size := b.alignment + size + if intrinsics.count_ones(actual_size) != 1 { + // We're not a power of two. Let's fix that. + actual_size = 1 << (size_of(uint) * 8 - intrinsics.count_leading_zeros(actual_size)) } return actual_size } @@ -2113,11 +2266,12 @@ buddy_block_size_required :: proc(b: ^Buddy_Allocator, size: uint) -> uint { /* Allocate memory from a buddy allocator. -This procedure allocates `size` bytes of memory aligned on a boundary specified -by `alignment`. The allocated memory region is zero-initialized. This procedure -returns a pointer to the allocated memory region. +This procedure allocates `size` bytes of memory. The allocation's alignment is +fixed to the `alignment` specified at initialization. The allocated memory +region is zero-initialized. This procedure returns a pointer to the allocated +memory region. */ -@(require_results) +@(require_results, no_sanitize_address) buddy_allocator_alloc :: proc(b: ^Buddy_Allocator, size: uint) -> (rawptr, Allocator_Error) { bytes, err := buddy_allocator_alloc_bytes(b, size) return raw_data(bytes), err @@ -2126,11 +2280,12 @@ buddy_allocator_alloc :: proc(b: ^Buddy_Allocator, size: uint) -> (rawptr, Alloc /* Allocate memory from a buddy allocator. -This procedure allocates `size` bytes of memory aligned on a boundary specified -by `alignment`. The allocated memory region is zero-initialized. This procedure -returns a slice of the allocated memory region. +This procedure allocates `size` bytes of memory. The allocation's alignment is +fixed to the `alignment` specified at initialization. The allocated memory +region is zero-initialized. This procedure returns a slice of the allocated +memory region. */ -@(require_results) +@(require_results, no_sanitize_address) buddy_allocator_alloc_bytes :: proc(b: ^Buddy_Allocator, size: uint) -> ([]byte, Allocator_Error) { bytes, err := buddy_allocator_alloc_bytes_non_zeroed(b, size) if bytes != nil { @@ -2142,11 +2297,12 @@ buddy_allocator_alloc_bytes :: proc(b: ^Buddy_Allocator, size: uint) -> ([]byte, /* Allocate non-initialized memory from a buddy allocator. -This procedure allocates `size` bytes of memory aligned on a boundary specified -by `alignment`. The allocated memory region is not explicitly zero-initialized. -This procedure returns a pointer to the allocated memory region. +This procedure allocates `size` bytes of memory. The allocation's alignment is +fixed to the `alignment` specified at initialization. The allocated memory +region is not explicitly zero-initialized. This procedure returns a pointer to +the allocated memory region. */ -@(require_results) +@(require_results, no_sanitize_address) buddy_allocator_alloc_non_zeroed :: proc(b: ^Buddy_Allocator, size: uint) -> (rawptr, Allocator_Error) { bytes, err := buddy_allocator_alloc_bytes_non_zeroed(b, size) return raw_data(bytes), err @@ -2155,16 +2311,17 @@ buddy_allocator_alloc_non_zeroed :: proc(b: ^Buddy_Allocator, size: uint) -> (ra /* Allocate non-initialized memory from a buddy allocator. -This procedure allocates `size` bytes of memory aligned on a boundary specified -by `alignment`. The allocated memory region is not explicitly zero-initialized. -This procedure returns a slice of the allocated memory region. +This procedure allocates `size` bytes of memory. The allocation's alignment is +fixed to the `alignment` specified at initialization. The allocated memory +region is not explicitly zero-initialized. This procedure returns a slice of +the allocated memory region. */ -@(require_results) +@(require_results, no_sanitize_address) buddy_allocator_alloc_bytes_non_zeroed :: proc(b: ^Buddy_Allocator, size: uint) -> ([]byte, Allocator_Error) { if size != 0 { actual_size := buddy_block_size_required(b, size) found := buddy_block_find_best(b.head, b.tail, actual_size) - if found != nil { + if found == nil { // Try to coalesce all the free buddy blocks and then search again buddy_block_coalescence(b.head, b.tail) found = buddy_block_find_best(b.head, b.tail, actual_size) @@ -2174,25 +2331,30 @@ buddy_allocator_alloc_bytes_non_zeroed :: proc(b: ^Buddy_Allocator, size: uint) } found.is_free = false data := ([^]byte)(found)[b.alignment:][:size] + assert(cast(uintptr)raw_data(data)+cast(uintptr)(size-1) < cast(uintptr)buddy_block_next(found), "Buddy_Allocator has made an allocation which overlaps a block header.") + // ensure_poisoned(data) + // sanitizer.address_unpoison(data) return data, nil } return nil, nil } /* -Free memory to the buddy allocator. +Free memory back to the buddy allocator. This procedure frees the memory region allocated at pointer `ptr`. If `ptr` is not the latest allocation and is not a leaked allocation, this operation is a no-op. */ +@(no_sanitize_address) buddy_allocator_free :: proc(b: ^Buddy_Allocator, ptr: rawptr) -> Allocator_Error { if ptr != nil { if !(b.head <= ptr && ptr <= b.tail) { return .Invalid_Pointer } block := (^Buddy_Block)(([^]byte)(ptr)[-b.alignment:]) + // sanitizer.address_poison(ptr, block.size) block.is_free = true buddy_block_coalescence(b.head, b.tail) } @@ -2200,16 +2362,18 @@ buddy_allocator_free :: proc(b: ^Buddy_Allocator, ptr: rawptr) -> Allocator_Erro } /* -Free all memory to the buddy allocator. +Free all memory back to the buddy allocator. */ +@(no_sanitize_address) buddy_allocator_free_all :: proc(b: ^Buddy_Allocator) { alignment := b.alignment head := ([^]byte)(b.head) tail := ([^]byte)(b.tail) data := head[:ptr_sub(tail, head)] - buddy_allocator_init(b, data, alignment) + buddy_allocator_init(b, data, alignment) } +@(no_sanitize_address) buddy_allocator_proc :: proc( allocator_data: rawptr, mode: Allocator_Mode, @@ -2263,7 +2427,7 @@ buddy_allocator_proc :: proc( // on the old size to work. // // The overhead of this allocator is an extra max(alignment, size_of(Header)) bytes allocated for each allocation, these bytes are -// used to store the size and original pointer. +// used to store the size and alignment. Compat_Allocator :: struct { parent: Allocator, } @@ -2272,6 +2436,7 @@ compat_allocator_init :: proc(rra: ^Compat_Allocator, allocator := context.alloc rra.parent = allocator } +@(require_results) compat_allocator :: proc(rra: ^Compat_Allocator) -> Allocator { return Allocator{ data = rra, @@ -2283,52 +2448,88 @@ compat_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, size, alignment: int, old_memory: rawptr, old_size: int, location := #caller_location) -> (data: []byte, err: Allocator_Error) { - size, old_size := size, old_size - Header :: struct { - size: int, - ptr: rawptr, + size: int, + alignment: int, + } + + @(no_sanitize_address) + get_unpoisoned_header :: #force_inline proc(ptr: rawptr) -> Header { + header := ([^]Header)(ptr)[-1] + // a := max(header.alignment, size_of(Header)) + // sanitizer.address_unpoison(rawptr(uintptr(ptr)-uintptr(a)), a) + return header } rra := (^Compat_Allocator)(allocator_data) switch mode { case .Alloc, .Alloc_Non_Zeroed: - a := max(alignment, size_of(Header)) - size += a - assert(size >= 0, "overflow") + a := max(alignment, size_of(Header)) + req_size := size + a + assert(req_size >= 0, "overflow") - allocation := rra.parent.procedure(rra.parent.data, mode, size, alignment, old_memory, old_size, location) or_return + allocation := rra.parent.procedure(rra.parent.data, mode, req_size, alignment, old_memory, old_size, location) or_return #no_bounds_check data = allocation[a:] ([^]Header)(raw_data(data))[-1] = { - size = size, - ptr = raw_data(allocation), + size = size, + alignment = alignment, } + + // sanitizer.address_poison(raw_data(allocation), a) return case .Free: - header := ([^]Header)(old_memory)[-1] - return rra.parent.procedure(rra.parent.data, mode, size, alignment, header.ptr, header.size, location) + header := get_unpoisoned_header(old_memory) + a := max(header.alignment, size_of(Header)) + orig_ptr := rawptr(uintptr(old_memory)-uintptr(a)) + orig_size := header.size + a + + return rra.parent.procedure(rra.parent.data, mode, orig_size, header.alignment, orig_ptr, orig_size, location) case .Resize, .Resize_Non_Zeroed: - header := ([^]Header)(old_memory)[-1] + header := get_unpoisoned_header(old_memory) + orig_a := max(header.alignment, size_of(Header)) + orig_ptr := rawptr(uintptr(old_memory)-uintptr(orig_a)) + orig_size := header.size + orig_a + + new_alignment := max(header.alignment, alignment) - a := max(alignment, size_of(header)) - size += a + a := max(new_alignment, size_of(header)) + req_size := size + a assert(size >= 0, "overflow") - allocation := rra.parent.procedure(rra.parent.data, mode, size, alignment, header.ptr, header.size, location) or_return + allocation := rra.parent.procedure(rra.parent.data, mode, req_size, new_alignment, orig_ptr, orig_size, location) or_return #no_bounds_check data = allocation[a:] ([^]Header)(raw_data(data))[-1] = { - size = size, - ptr = raw_data(allocation), + size = size, + alignment = new_alignment, } + + // sanitizer.address_poison(raw_data(allocation), a) return - case .Free_All, .Query_Info, .Query_Features: + case .Free_All: return rra.parent.procedure(rra.parent.data, mode, size, alignment, old_memory, old_size, location) + case .Query_Info: + info := (^Allocator_Query_Info)(old_memory) + if info != nil && info.pointer != nil { + header := get_unpoisoned_header(info.pointer) + info.size = header.size + info.alignment = header.alignment + } + return + + case .Query_Features: + data, err = rra.parent.procedure(rra.parent.data, mode, size, alignment, old_memory, old_size, location) + if err != nil { + set := (^Allocator_Mode_Set)(old_memory) + set^ += {.Query_Info} + } + return + case: unreachable() } } diff --git a/core/mem/mem.odin b/core/mem/mem.odin index b2a7158a1..96ec1990a 100644 --- a/core/mem/mem.odin +++ b/core/mem/mem.odin @@ -171,16 +171,15 @@ If the return value is: The comparison is performed as follows: 1. Each byte, upto `min(len(a), len(b))` bytes is compared between `a` and `b`. - - If the byte in slice `a` is smaller than a byte in slice `b`, then comparison - stops and this procedure returns `-1`. - - If the byte in slice `a` is bigger than a byte in slice `b`, then comparison - stops and this procedure returns `+1`. - - Otherwise the comparison continues until `min(len(a), len(b))` are compared. -2. If all the bytes in the range are equal, then the lengths of the slices are - compared. - - If the length of slice `a` is smaller than the length of slice `b`, then `-1` is returned. - - If the length of slice `b` is smaller than the length of slice `b`, then `+1` is returned. - - Otherwise `0` is returned. + - If the byte in slice `a` is smaller than a byte in slice `b`, then comparison + stops and this procedure returns `-1`. + - If the byte in slice `a` is bigger than a byte in slice `b`, then comparison + stops and this procedure returns `+1`. + - Otherwise the comparison continues until `min(len(a), len(b))` are compared. +2. If all the bytes in the range are equal, then the lengths of the slices are compared. + - If the length of slice `a` is smaller than the length of slice `b`, then `-1` is returned. + - If the length of slice `b` is smaller than the length of slice `b`, then `+1` is returned. + - Otherwise `0` is returned. */ @(require_results) compare :: proc "contextless" (a, b: []byte) -> int { @@ -207,11 +206,11 @@ If the return value is: The comparison is performed as follows: 1. Each byte, upto `n` bytes is compared between `a` and `b`. - - If the byte in `a` is smaller than a byte in `b`, then comparison stops - and this procedure returns `-1`. - - If the byte in `a` is bigger than a byte in `b`, then comparison stops - and this procedure returns `+1`. - - Otherwise the comparison continues until `n` bytes are compared. + - If the byte in `a` is smaller than a byte in `b`, then comparison stops + and this procedure returns `-1`. + - If the byte in `a` is bigger than a byte in `b`, then comparison stops + and this procedure returns `+1`. + - Otherwise the comparison continues until `n` bytes are compared. 2. If all the bytes in the range are equal, this procedure returns `0`. */ @(require_results) @@ -233,11 +232,11 @@ If the return value is: The comparison is performed as follows: 1. Each byte, upto `n` bytes is compared between `a` and `b`. - - If the byte in `a` is smaller than a byte in `b`, then comparison stops - and this procedure returns `-1`. - - If the byte in `a` is bigger than a byte in `b`, then comparison stops - and this procedure returns `+1`. - - Otherwise the comparison continues until `n` bytes are compared. + - If the byte in `a` is smaller than a byte in `b`, then comparison stops + and this procedure returns `-1`. + - If the byte in `a` is bigger than a byte in `b`, then comparison stops + and this procedure returns `+1`. + - Otherwise the comparison continues until `n` bytes are compared. 2. If all the bytes in the range are equal, this procedure returns `0`. */ @(require_results) @@ -316,18 +315,38 @@ check_zero_ptr :: proc(ptr: rawptr, len: int) -> bool { Offset a given pointer by a given amount. This procedure offsets the pointer `ptr` to an object of type `T`, by the amount -of bytes specified by `offset*size_of(T)`, and returns the pointer `ptr`. +of bytes specified by `offset * size_of(T)`, and returns the pointer `ptr`. **Note**: Prefer to use multipointer types, if possible. */ ptr_offset :: intrinsics.ptr_offset /* -Offset a given pointer by a given amount backwards. +Subtract two pointers of the same type, and return the number of `T` between them. -This procedure offsets the pointer `ptr` to an object of type `T`, by the amount -of bytes specified by `offset*size_of(T)` in the negative direction, and -returns the pointer `ptr`. +This procedure subtracts pointer `b` from pointer `a`, both of type `^T`, +and returns an integer count of the `T` between them. + +**Inputs** +- `a`: A pointer to a type T +- `b`: A pointer to a type T + +**Returns** +- `b` - `a` in items of T as an `int`. + +Example: + + import "core:mem" + import "core:fmt" + + ptr_sub_example :: proc() { + arr: [2]int + fmt.println(mem.ptr_sub(&arr[1], &arr[0])) + } + +Output: + + 1 */ ptr_sub :: intrinsics.ptr_sub diff --git a/core/mem/rollback_stack_allocator.odin b/core/mem/rollback_stack_allocator.odin index 43ef10fe9..3f16a2897 100644 --- a/core/mem/rollback_stack_allocator.odin +++ b/core/mem/rollback_stack_allocator.odin @@ -1,6 +1,7 @@ package mem import "base:runtime" +// import "base:sanitizer" /* Rollback stack default block size. @@ -47,14 +48,14 @@ Rollback_Stack :: struct { block_allocator: Allocator, } -@(private="file", require_results) +@(private="file", require_results, no_sanitize_address) rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool { start := raw_data(block.buffer) end := start[block.offset:] return start < ptr && ptr <= end } -@(private="file", require_results) +@(private="file", require_results, no_sanitize_address) rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( parent: ^Rollback_Stack_Block, block: ^Rollback_Stack_Block, @@ -71,7 +72,7 @@ rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( return nil, nil, nil, .Invalid_Pointer } -@(private="file", require_results) +@(private="file", require_results, no_sanitize_address) rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( block: ^Rollback_Stack_Block, header: ^Rollback_Stack_Header, @@ -86,9 +87,10 @@ rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( return nil, nil, false } -@(private="file") +@(private="file", no_sanitize_address) rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_Header) { header := header + for block.offset > 0 && header.is_free { block.offset = header.prev_offset block.last_alloc = raw_data(block.buffer)[header.prev_ptr:] @@ -99,9 +101,10 @@ rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_ /* Free memory to a rollback stack allocator. */ -@(private="file", require_results) +@(private="file", require_results, no_sanitize_address) rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error { parent, block, header := rb_find_ptr(stack, ptr) or_return + if header.is_free { return .Invalid_Pointer } @@ -120,7 +123,7 @@ rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error { /* Free all memory owned by the rollback stack allocator. */ -@(private="file") +@(private="file", no_sanitize_address) rb_free_all :: proc(stack: ^Rollback_Stack) { for block := stack.head.next_block; block != nil; /**/ { next_block := block.next_block @@ -131,12 +134,13 @@ rb_free_all :: proc(stack: ^Rollback_Stack) { stack.head.next_block = nil stack.head.last_alloc = nil stack.head.offset = 0 + // sanitizer.address_poison(stack.head.buffer) } /* Allocate memory using the rollback stack allocator. */ -@(require_results) +@(require_results, no_sanitize_address) rb_alloc :: proc( stack: ^Rollback_Stack, size: int, @@ -153,7 +157,7 @@ rb_alloc :: proc( /* Allocate memory using the rollback stack allocator. */ -@(require_results) +@(require_results, no_sanitize_address) rb_alloc_bytes :: proc( stack: ^Rollback_Stack, size: int, @@ -170,7 +174,7 @@ rb_alloc_bytes :: proc( /* Allocate non-initialized memory using the rollback stack allocator. */ -@(require_results) +@(require_results, no_sanitize_address) rb_alloc_non_zeroed :: proc( stack: ^Rollback_Stack, size: int, @@ -184,7 +188,7 @@ rb_alloc_non_zeroed :: proc( /* Allocate non-initialized memory using the rollback stack allocator. */ -@(require_results) +@(require_results, no_sanitize_address) rb_alloc_bytes_non_zeroed :: proc( stack: ^Rollback_Stack, size: int, @@ -194,6 +198,7 @@ rb_alloc_bytes_non_zeroed :: proc( assert(size >= 0, "Size must be positive or zero.", loc) assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", loc) parent: ^Rollback_Stack_Block + for block := stack.head; /**/; block = block.next_block { when !ODIN_DISABLE_ASSERT { allocated_new_block: bool @@ -235,7 +240,9 @@ rb_alloc_bytes_non_zeroed :: proc( // Prevent any further allocations on it. block.offset = cast(uintptr)len(block.buffer) } - #no_bounds_check return ptr[:size], nil + res := ptr[:size] + // sanitizer.address_unpoison(res) + return res, nil } return nil, .Out_Of_Memory } @@ -243,7 +250,7 @@ rb_alloc_bytes_non_zeroed :: proc( /* Resize an allocation owned by rollback stack allocator. */ -@(require_results) +@(require_results, no_sanitize_address) rb_resize :: proc( stack: ^Rollback_Stack, old_ptr: rawptr, @@ -266,7 +273,7 @@ rb_resize :: proc( /* Resize an allocation owned by rollback stack allocator. */ -@(require_results) +@(require_results, no_sanitize_address) rb_resize_bytes :: proc( stack: ^Rollback_Stack, old_memory: []byte, @@ -289,7 +296,7 @@ rb_resize_bytes :: proc( Resize an allocation owned by rollback stack allocator without explicit zero-initialization. */ -@(require_results) +@(require_results, no_sanitize_address) rb_resize_non_zeroed :: proc( stack: ^Rollback_Stack, old_ptr: rawptr, @@ -306,7 +313,7 @@ rb_resize_non_zeroed :: proc( Resize an allocation owned by rollback stack allocator without explicit zero-initialization. */ -@(require_results) +@(require_results, no_sanitize_address) rb_resize_bytes_non_zeroed :: proc( stack: ^Rollback_Stack, old_memory: []byte, @@ -330,7 +337,9 @@ rb_resize_bytes_non_zeroed :: proc( if len(block.buffer) <= stack.block_size { block.offset += cast(uintptr)size - cast(uintptr)old_size } - #no_bounds_check return (ptr)[:size], nil + res := (ptr)[:size] + // sanitizer.address_unpoison(res) + #no_bounds_check return res, nil } } } @@ -340,7 +349,7 @@ rb_resize_bytes_non_zeroed :: proc( return } -@(private="file", require_results) +@(private="file", require_results, no_sanitize_address) rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) { buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return block = cast(^Rollback_Stack_Block)raw_data(buffer) @@ -351,6 +360,7 @@ rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stac /* Initialize the rollback stack allocator using a fixed backing buffer. */ +@(no_sanitize_address) rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, location := #caller_location) { MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr) assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.", location) @@ -365,6 +375,7 @@ rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, loc /* Initialize the rollback stack alocator using a backing block allocator. */ +@(no_sanitize_address) rollback_stack_init_dynamic :: proc( stack: ^Rollback_Stack, block_size : int = ROLLBACK_STACK_DEFAULT_BLOCK_SIZE, @@ -396,6 +407,7 @@ rollback_stack_init :: proc { /* Destroy a rollback stack. */ +@(no_sanitize_address) rollback_stack_destroy :: proc(stack: ^Rollback_Stack) { if stack.block_allocator.procedure != nil { rb_free_all(stack) @@ -435,7 +447,7 @@ from the last allocation backwards. Each allocation has an overhead of 8 bytes and any extra bytes to satisfy the requested alignment. */ -@(require_results) +@(require_results, no_sanitize_address) rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator { return Allocator { data = stack, @@ -443,7 +455,7 @@ rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator { } } -@(require_results) +@(require_results, no_sanitize_address) rollback_stack_allocator_proc :: proc( allocator_data: rawptr, mode: Allocator_Mode, diff --git a/core/mem/tlsf/tlsf.odin b/core/mem/tlsf/tlsf.odin index 8ec5f52b7..0ae8c28e0 100644 --- a/core/mem/tlsf/tlsf.odin +++ b/core/mem/tlsf/tlsf.odin @@ -10,6 +10,7 @@ // package mem_tlsf implements a Two Level Segregated Fit memory allocator. package mem_tlsf +import "base:intrinsics" import "base:runtime" Error :: enum byte { @@ -21,7 +22,6 @@ Error :: enum byte { Backing_Allocator_Error = 5, } - Allocator :: struct { // Empty lists point at this block to indicate they are free. block_null: Block_Header, @@ -39,12 +39,13 @@ Allocator :: struct { // statistics like how much memory is still available, // fragmentation, etc. pool: Pool, + + // If we're expected to grow when we run out of memory, + // how much should we ask the backing allocator for? + new_pool_size: uint, } #assert(size_of(Allocator) % ALIGN_SIZE == 0) - - - @(require_results) allocator :: proc(t: ^Allocator) -> runtime.Allocator { return runtime.Allocator{ @@ -53,6 +54,21 @@ allocator :: proc(t: ^Allocator) -> runtime.Allocator { } } +// Tries to estimate a pool size sufficient for `count` allocations, each of `size` and with `alignment`. +estimate_pool_from_size_alignment :: proc(count: int, size: int, alignment: int) -> (pool_size: int) { + per_allocation := align_up(uint(size + alignment) + BLOCK_HEADER_OVERHEAD, ALIGN_SIZE) + return count * int(per_allocation) + int(INITIAL_POOL_OVERHEAD) +} + +// Tries to estimate a pool size sufficient for `count` allocations of `type`. +estimate_pool_from_typeid :: proc(count: int, type: typeid) -> (pool_size: int) { + ti := type_info_of(type) + return estimate_pool_size(count, ti.size, ti.align) +} + +estimate_pool_size :: proc{estimate_pool_from_size_alignment, estimate_pool_from_typeid} + + @(require_results) init_from_buffer :: proc(control: ^Allocator, buf: []byte) -> Error { assert(control != nil) @@ -60,21 +76,25 @@ init_from_buffer :: proc(control: ^Allocator, buf: []byte) -> Error { return .Invalid_Alignment } - pool_bytes := align_down(len(buf) - POOL_OVERHEAD, ALIGN_SIZE) + pool_bytes := align_down(len(buf) - INITIAL_POOL_OVERHEAD, ALIGN_SIZE) if pool_bytes < BLOCK_SIZE_MIN { return .Backing_Buffer_Too_Small } else if pool_bytes > BLOCK_SIZE_MAX { return .Backing_Buffer_Too_Large } - clear(control) - return pool_add(control, buf[:]) + control.pool = Pool{ + data = buf, + allocator = {}, + } + + return free_all(control) } @(require_results) init_from_allocator :: proc(control: ^Allocator, backing: runtime.Allocator, initial_pool_size: int, new_pool_size := 0) -> Error { assert(control != nil) - pool_bytes := align_up(uint(initial_pool_size) + POOL_OVERHEAD, ALIGN_SIZE) + pool_bytes := uint(estimate_pool_size(1, initial_pool_size, ALIGN_SIZE)) if pool_bytes < BLOCK_SIZE_MIN { return .Backing_Buffer_Too_Small } else if pool_bytes > BLOCK_SIZE_MAX { @@ -85,12 +105,15 @@ init_from_allocator :: proc(control: ^Allocator, backing: runtime.Allocator, ini if backing_err != nil { return .Backing_Allocator_Error } - err := init_from_buffer(control, buf) + control.pool = Pool{ data = buf, allocator = backing, } - return err + + control.new_pool_size = uint(new_pool_size) + + return free_all(control) } init :: proc{init_from_buffer, init_from_allocator} @@ -103,8 +126,6 @@ destroy :: proc(control: ^Allocator) { // No need to call `pool_remove` or anything, as they're they're embedded in the backing memory. // We do however need to free the `Pool` tracking entities and the backing memory itself. - // As `Allocator` is embedded in the first backing slice, the `control` pointer will be - // invalid after this call. for p := control.pool.next; p != nil; { next := p.next @@ -136,9 +157,8 @@ allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode, return nil, nil case .Free_All: - // NOTE: this doesn't work right at the moment, Jeroen has it on his to-do list :) - // clear(control) - return nil, .Mode_Not_Implemented + free_all(control) + return nil, nil case .Resize: return resize(control, old_memory, uint(old_size), uint(size), uint(alignment)) @@ -159,3 +179,23 @@ allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode, return nil, nil } + +// Exported solely to facilitate testing +@(require_results) +ffs :: proc "contextless" (word: u32) -> (bit: i32) { + return -1 if word == 0 else i32(intrinsics.count_trailing_zeros(word)) +} + +// Exported solely to facilitate testing +@(require_results) +fls :: proc "contextless" (word: u32) -> (bit: i32) { + N :: (size_of(u32) * 8) - 1 + return i32(N - intrinsics.count_leading_zeros(word)) +} + +// Exported solely to facilitate testing +@(require_results) +fls_uint :: proc "contextless" (size: uint) -> (bit: i32) { + N :: (size_of(uint) * 8) - 1 + return i32(N - intrinsics.count_leading_zeros(size)) +} diff --git a/core/mem/tlsf/tlsf_internal.odin b/core/mem/tlsf/tlsf_internal.odin index 66141fcbb..e53d76d61 100644 --- a/core/mem/tlsf/tlsf_internal.odin +++ b/core/mem/tlsf/tlsf_internal.odin @@ -7,12 +7,11 @@ Jeroen van Rijn: Source port */ - package mem_tlsf import "base:intrinsics" +// import "base:sanitizer" import "base:runtime" -// import "core:fmt" // log2 of number of linear subdivisions of block sizes. // Larger values require more memory in the control structure. @@ -58,7 +57,6 @@ Pool :: struct { next: ^Pool, } - /* Block header structure. @@ -95,6 +93,7 @@ The `prev_phys_block` field is stored *inside* the previous free block. BLOCK_HEADER_OVERHEAD :: uint(size_of(uint)) POOL_OVERHEAD :: 2 * BLOCK_HEADER_OVERHEAD +INITIAL_POOL_OVERHEAD :: 48 // User data starts directly after the size field in a used block. BLOCK_START_OFFSET :: offset_of(Block_Header, size) + size_of(Block_Header{}.size) @@ -107,6 +106,270 @@ bits for `FL_INDEX`. BLOCK_SIZE_MIN :: uint(size_of(Block_Header) - size_of(^Block_Header)) BLOCK_SIZE_MAX :: uint(1) << FL_INDEX_MAX +// Clear control structure and point all empty lists at the null block +@(private) +free_all :: proc(control: ^Allocator) -> (err: Error) { + // Clear internal structures + control.block_null.next_free = &control.block_null + control.block_null.prev_free = &control.block_null + control.fl_bitmap = 0 + for i in 0..<FL_INDEX_COUNT { + control.sl_bitmap[i] = 0 + for j in 0..<SL_INDEX_COUNT { + control.blocks[i][j] = &control.block_null + } + } + + // Add backing pool(s) + for p := &control.pool; p != nil; p = p.next { + pool_add(control, p.data) or_return + } + return +} + +@(private, require_results) +pool_add :: proc(control: ^Allocator, pool: []u8) -> (err: Error) { + assert(uintptr(raw_data(pool)) % ALIGN_SIZE == 0, "Added memory must be aligned") + + pool_overhead := POOL_OVERHEAD + pool_bytes := align_down(len(pool) - pool_overhead, ALIGN_SIZE) + + if pool_bytes < BLOCK_SIZE_MIN { + return .Backing_Buffer_Too_Small + } else if pool_bytes > BLOCK_SIZE_MAX { + return .Backing_Buffer_Too_Large + } + + // Create the main free block. Offset the start of the block slightly, + // so that the `prev_phys_block` field falls outside of the pool - + // it will never be used. + block := offset_to_block_backwards(raw_data(pool), BLOCK_HEADER_OVERHEAD) + + block_set_size(block, pool_bytes) + block_set_free(block) + block_set_prev_used(block) + block_insert(control, block) + + // Split the block to create a zero-size sentinel block + next := block_link_next(block) + block_set_size(next, 0) + block_set_used(next) + block_set_prev_free(next) + + return +} + +@(private) +pool_remove :: proc(control: ^Allocator, pool: []u8) { + block := offset_to_block_backwards(raw_data(pool), BLOCK_HEADER_OVERHEAD) + + assert(block_is_free(block), "Block should be free") + assert(!block_is_free(block_next(block)), "Next block should not be free") + assert(block_size(block_next(block)) == 0, "Next block size should be zero") + + fl, sl := mapping_insert(block_size(block)) + remove_free_block(control, block, fl, sl) +} + +@(private, require_results) +alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) { + assert(control != nil) + adjust := adjust_request_size(size, ALIGN_SIZE) + + GAP_MINIMUM :: size_of(Block_Header) + size_with_gap := adjust_request_size(adjust + align + GAP_MINIMUM, align) + + aligned_size := size_with_gap if adjust != 0 && align > ALIGN_SIZE else adjust + if aligned_size == 0 && size > 0 { + return nil, .Out_Of_Memory + } + + block := block_locate_free(control, aligned_size) + if block == nil { + // OOM: Couldn't find block of `aligned_size` bytes. + if control.new_pool_size > 0 && control.pool.allocator.procedure != nil { + // TLSF is configured to grow. + + /* + This implementation doesn't allow for out-of-band allocations to be passed through, as it's not designed to + track those. Nor is it able to signal those allocations then need to be freed on the backing allocator, + as opposed to regular allocations handled for you when you `destroy` the TLSF instance. + + So if we're asked for more than we're configured to grow by, we can fail with an OOM error early, without adding a new pool. + */ + if aligned_size > control.new_pool_size { + return nil, .Out_Of_Memory + } + + // Trying to allocate a new pool of `control.new_pool_size` bytes. + new_pool_buf := runtime.make_aligned([]byte, control.new_pool_size, ALIGN_SIZE, control.pool.allocator) or_return + + // Add new pool to control structure + if pool_add_err := pool_add(control, new_pool_buf); pool_add_err != .None { + delete(new_pool_buf, control.pool.allocator) + return nil, .Out_Of_Memory + } + + // sanitizer.address_poison(new_pool_buf) + + // Allocate a new link in the `control.pool` tracking structure. + new_pool := new_clone(Pool{ + data = new_pool_buf, + allocator = control.pool.allocator, + next = nil, + }, control.pool.allocator) or_return + + p := &control.pool + for p.next != nil { + p = p.next + } + p.next = new_pool + + // Try again to find free block + block = block_locate_free(control, aligned_size) + if block == nil { + return nil, .Out_Of_Memory + } + } else { + // TLSF is non-growing. We're done. + return nil, .Out_Of_Memory + } + } + ptr := block_to_ptr(block) + aligned := align_ptr(ptr, align) + gap := uint(int(uintptr(aligned)) - int(uintptr(ptr))) + + if gap != 0 && gap < GAP_MINIMUM { + gap_remain := GAP_MINIMUM - gap + offset := uintptr(max(gap_remain, align)) + next_aligned := rawptr(uintptr(aligned) + offset) + + aligned = align_ptr(next_aligned, align) + + gap = uint(int(uintptr(aligned)) - int(uintptr(ptr))) + } + + if gap != 0 { + assert(gap >= GAP_MINIMUM, "gap size too small") + block = block_trim_free_leading(control, block, gap) + } + + return block_prepare_used(control, block, adjust) +} + +@(private, require_results) +alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) { + res, err = alloc_bytes_non_zeroed(control, size, align) + if err == nil { + intrinsics.mem_zero(raw_data(res), len(res)) + } + return +} + + +@(no_sanitize_address) +free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) { + assert(control != nil) + // `size` is currently ignored + if ptr == nil { + return + } + + block := block_from_ptr(ptr) + assert(!block_is_free(block), "block already marked as free") // double free + // sanitizer.address_poison(ptr, block.size) + block_mark_as_free(block) + block = block_merge_prev(control, block) + block = block_merge_next(control, block) + block_insert(control, block) +} + + +@(private, require_results) +resize :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, alignment: uint) -> (res: []byte, err: runtime.Allocator_Error) { + assert(control != nil) + if ptr != nil && new_size == 0 { + free_with_size(control, ptr, old_size) + return + } else if ptr == nil { + return alloc_bytes(control, new_size, alignment) + } + + block := block_from_ptr(ptr) + next := block_next(block) + + curr_size := block_size(block) + combined := curr_size + block_size(next) + BLOCK_HEADER_OVERHEAD + adjust := adjust_request_size(new_size, max(ALIGN_SIZE, alignment)) + + assert(!block_is_free(block), "block already marked as free") // double free + + min_size := min(curr_size, new_size, old_size) + + if adjust > curr_size && (!block_is_free(next) || adjust > combined) { + res = alloc_bytes(control, new_size, alignment) or_return + if res != nil { + copy(res, ([^]byte)(ptr)[:min_size]) + free_with_size(control, ptr, curr_size) + } + return + } + if adjust > curr_size { + _ = block_merge_next(control, block) + block_mark_as_used(block) + } + + block_trim_used(control, block, adjust) + res = ([^]byte)(ptr)[:new_size] + // sanitizer.address_unpoison(res) + + if min_size < new_size { + to_zero := ([^]byte)(ptr)[min_size:new_size] + runtime.mem_zero(raw_data(to_zero), len(to_zero)) + } + return +} + +@(private, require_results) +resize_non_zeroed :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, alignment: uint) -> (res: []byte, err: runtime.Allocator_Error) { + assert(control != nil) + if ptr != nil && new_size == 0 { + free_with_size(control, ptr, old_size) + return + } else if ptr == nil { + return alloc_bytes_non_zeroed(control, new_size, alignment) + } + + block := block_from_ptr(ptr) + next := block_next(block) + + curr_size := block_size(block) + combined := curr_size + block_size(next) + BLOCK_HEADER_OVERHEAD + adjust := adjust_request_size(new_size, max(ALIGN_SIZE, alignment)) + + assert(!block_is_free(block), "block already marked as free") // double free + + min_size := min(curr_size, new_size, old_size) + + if adjust > curr_size && (!block_is_free(next) || adjust > combined) { + res = alloc_bytes_non_zeroed(control, new_size, alignment) or_return + if res != nil { + copy(res, ([^]byte)(ptr)[:min_size]) + free_with_size(control, ptr, old_size) + } + return + } + + if adjust > curr_size { + _ = block_merge_next(control, block) + block_mark_as_used(block) + } + + block_trim_used(control, block, adjust) + res = ([^]byte)(ptr)[:new_size] + return +} + /* TLSF achieves O(1) cost for `alloc` and `free` operations by limiting the search for a free block to a free list of guaranteed size @@ -114,109 +377,99 @@ BLOCK_SIZE_MAX :: uint(1) << FL_INDEX_MAX queries using bitmasks and architecture-specific bit-manipulation routines. - NOTE: TLSF spec relies on ffs/fls returning value 0..31. + NOTE: TLSF spec relies on ffs/fls returning a value in the range 0..31. */ -@(require_results) -ffs :: proc "contextless" (word: u32) -> (bit: i32) { - return -1 if word == 0 else i32(intrinsics.count_trailing_zeros(word)) -} - -@(require_results) -fls :: proc "contextless" (word: u32) -> (bit: i32) { - N :: (size_of(u32) * 8) - 1 - return i32(N - intrinsics.count_leading_zeros(word)) -} - -@(require_results) -fls_uint :: proc "contextless" (size: uint) -> (bit: i32) { - N :: (size_of(uint) * 8) - 1 - return i32(N - intrinsics.count_leading_zeros(size)) -} - -@(require_results) +@(private, require_results, no_sanitize_address) block_size :: proc "contextless" (block: ^Block_Header) -> (size: uint) { return block.size &~ (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE) } +@(private, no_sanitize_address) block_set_size :: proc "contextless" (block: ^Block_Header, size: uint) { old_size := block.size block.size = size | (old_size & (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE)) } -@(require_results) +@(private, require_results, no_sanitize_address) block_is_last :: proc "contextless" (block: ^Block_Header) -> (is_last: bool) { return block_size(block) == 0 } -@(require_results) +@(private, require_results, no_sanitize_address) block_is_free :: proc "contextless" (block: ^Block_Header) -> (is_free: bool) { return (block.size & BLOCK_HEADER_FREE) == BLOCK_HEADER_FREE } +@(private, no_sanitize_address) block_set_free :: proc "contextless" (block: ^Block_Header) { block.size |= BLOCK_HEADER_FREE } +@(private, no_sanitize_address) block_set_used :: proc "contextless" (block: ^Block_Header) { block.size &~= BLOCK_HEADER_FREE } -@(require_results) +@(private, require_results, no_sanitize_address) block_is_prev_free :: proc "contextless" (block: ^Block_Header) -> (is_prev_free: bool) { return (block.size & BLOCK_HEADER_PREV_FREE) == BLOCK_HEADER_PREV_FREE } +@(private, no_sanitize_address) block_set_prev_free :: proc "contextless" (block: ^Block_Header) { block.size |= BLOCK_HEADER_PREV_FREE } +@(private, no_sanitize_address) block_set_prev_used :: proc "contextless" (block: ^Block_Header) { block.size &~= BLOCK_HEADER_PREV_FREE } -@(require_results) +@(private, require_results, no_sanitize_address) block_from_ptr :: proc(ptr: rawptr) -> (block_ptr: ^Block_Header) { return (^Block_Header)(uintptr(ptr) - BLOCK_START_OFFSET) } -@(require_results) +@(private, require_results, no_sanitize_address) block_to_ptr :: proc(block: ^Block_Header) -> (ptr: rawptr) { return rawptr(uintptr(block) + BLOCK_START_OFFSET) } // Return location of next block after block of given size. -@(require_results) +@(private, require_results, no_sanitize_address) offset_to_block :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) { return (^Block_Header)(uintptr(ptr) + uintptr(size)) } -@(require_results) +@(private, require_results, no_sanitize_address) offset_to_block_backwards :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) { return (^Block_Header)(uintptr(ptr) - uintptr(size)) } // Return location of previous block. -@(require_results) +@(private, require_results, no_sanitize_address) block_prev :: proc(block: ^Block_Header) -> (prev: ^Block_Header) { assert(block_is_prev_free(block), "previous block must be free") + return block.prev_phys_block } // Return location of next existing block. -@(require_results) +@(private, require_results, no_sanitize_address) block_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) { return offset_to_block(block_to_ptr(block), block_size(block) - BLOCK_HEADER_OVERHEAD) } // Link a new block with its physical neighbor, return the neighbor. -@(require_results) +@(private, require_results, no_sanitize_address) block_link_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) { next = block_next(block) next.prev_phys_block = block return } +@(private, no_sanitize_address) block_mark_as_free :: proc(block: ^Block_Header) { // Link the block to the next block, first. next := block_link_next(block) @@ -224,25 +477,26 @@ block_mark_as_free :: proc(block: ^Block_Header) { block_set_free(block) } +@(private, no_sanitize_address) block_mark_as_used :: proc(block: ^Block_Header) { next := block_next(block) block_set_prev_used(next) block_set_used(block) } -@(require_results) +@(private, require_results) align_up :: proc(x, align: uint) -> (aligned: uint) { assert(0 == (align & (align - 1)), "must align to a power of two") return (x + (align - 1)) &~ (align - 1) } -@(require_results) +@(private, require_results) align_down :: proc(x, align: uint) -> (aligned: uint) { assert(0 == (align & (align - 1)), "must align to a power of two") return x - (x & (align - 1)) } -@(require_results) +@(private, require_results) align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) { assert(0 == (align & (align - 1)), "must align to a power of two") align_mask := uintptr(align) - 1 @@ -252,7 +506,7 @@ align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) { } // Adjust an allocation size to be aligned to word size, and no smaller than internal minimum. -@(require_results) +@(private, require_results) adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) { if size == 0 { return 0 @@ -266,7 +520,7 @@ adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) { } // Adjust an allocation size to be aligned to word size, and no smaller than internal minimum. -@(require_results) +@(private, require_results) adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: runtime.Allocator_Error) { if size == 0 { return 0, nil @@ -284,7 +538,7 @@ adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: // TLSF utility functions. In most cases these are direct translations of // the documentation in the research paper. -@(optimization_mode="favor_size", require_results) +@(optimization_mode="favor_size", private, require_results) mapping_insert :: proc(size: uint) -> (fl, sl: i32) { if size < SMALL_BLOCK_SIZE { // Store small blocks in first list. @@ -297,7 +551,7 @@ mapping_insert :: proc(size: uint) -> (fl, sl: i32) { return } -@(optimization_mode="favor_size", require_results) +@(optimization_mode="favor_size", private, require_results) mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) { rounded = size if size >= SMALL_BLOCK_SIZE { @@ -308,12 +562,12 @@ mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) { } // This version rounds up to the next block size (for allocations) -@(optimization_mode="favor_size", require_results) +@(optimization_mode="favor_size", private, require_results) mapping_search :: proc(size: uint) -> (fl, sl: i32) { return mapping_insert(mapping_round(size)) } -@(require_results) +@(private, require_results, no_sanitize_address) search_suitable_block :: proc(control: ^Allocator, fli, sli: ^i32) -> (block: ^Block_Header) { // First, search for a block in the list associated with the given fl/sl index. fl := fli^; sl := sli^ @@ -340,6 +594,7 @@ search_suitable_block :: proc(control: ^Allocator, fli, sli: ^i32) -> (block: ^B } // Remove a free block from the free list. +@(private, no_sanitize_address) remove_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) { prev := block.prev_free next := block.next_free @@ -365,6 +620,7 @@ remove_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl } // Insert a free block into the free block list. +@(private, no_sanitize_address) insert_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) { current := control.blocks[fl][sl] assert(current != nil, "free lists cannot have a nil entry") @@ -382,24 +638,26 @@ insert_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl } // Remove a given block from the free list. +@(private, no_sanitize_address) block_remove :: proc(control: ^Allocator, block: ^Block_Header) { fl, sl := mapping_insert(block_size(block)) remove_free_block(control, block, fl, sl) } // Insert a given block into the free list. +@(private, no_sanitize_address) block_insert :: proc(control: ^Allocator, block: ^Block_Header) { fl, sl := mapping_insert(block_size(block)) insert_free_block(control, block, fl, sl) } -@(require_results) +@(private, require_results, no_sanitize_address) block_can_split :: proc(block: ^Block_Header, size: uint) -> (can_split: bool) { return block_size(block) >= size_of(Block_Header) + size } // Split a block into two, the second of which is free. -@(require_results) +@(private, require_results, no_sanitize_address) block_split :: proc(block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) { // Calculate the amount of space left in the remaining block. remaining = offset_to_block(block_to_ptr(block), size - BLOCK_HEADER_OVERHEAD) @@ -420,9 +678,10 @@ block_split :: proc(block: ^Block_Header, size: uint) -> (remaining: ^Block_Head } // Absorb a free block's storage into an adjacent previous free block. -@(require_results) +@(private, require_results, no_sanitize_address) block_absorb :: proc(prev: ^Block_Header, block: ^Block_Header) -> (absorbed: ^Block_Header) { assert(!block_is_last(prev), "previous block can't be last") + // Note: Leaves flags untouched. prev.size += block_size(block) + BLOCK_HEADER_OVERHEAD _ = block_link_next(prev) @@ -430,7 +689,7 @@ block_absorb :: proc(prev: ^Block_Header, block: ^Block_Header) -> (absorbed: ^B } // Merge a just-freed block with an adjacent previous free block. -@(require_results) +@(private, require_results, no_sanitize_address) block_merge_prev :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) { merged = block if (block_is_prev_free(block)) { @@ -444,7 +703,7 @@ block_merge_prev :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: } // Merge a just-freed block with an adjacent free block. -@(require_results) +@(private, require_results, no_sanitize_address) block_merge_next :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) { merged = block next := block_next(block) @@ -459,6 +718,7 @@ block_merge_next :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: } // Trim any trailing block space off the end of a free block, return to pool. +@(private, no_sanitize_address) block_trim_free :: proc(control: ^Allocator, block: ^Block_Header, size: uint) { assert(block_is_free(block), "block must be free") if (block_can_split(block, size)) { @@ -470,6 +730,7 @@ block_trim_free :: proc(control: ^Allocator, block: ^Block_Header, size: uint) { } // Trim any trailing block space off the end of a used block, return to pool. +@(private, no_sanitize_address) block_trim_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) { assert(!block_is_free(block), "Block must be used") if (block_can_split(block, size)) { @@ -483,7 +744,7 @@ block_trim_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) { } // Trim leading block space, return to pool. -@(require_results) +@(private, require_results, no_sanitize_address) block_trim_free_leading :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) { remaining = block if block_can_split(block, size) { @@ -497,7 +758,7 @@ block_trim_free_leading :: proc(control: ^Allocator, block: ^Block_Header, size: return remaining } -@(require_results) +@(private, require_results, no_sanitize_address) block_locate_free :: proc(control: ^Allocator, size: uint) -> (block: ^Block_Header) { fl, sl: i32 if size != 0 { @@ -521,218 +782,14 @@ block_locate_free :: proc(control: ^Allocator, size: uint) -> (block: ^Block_Hea return block } -@(require_results) +@(private, require_results, no_sanitize_address) block_prepare_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (res: []byte, err: runtime.Allocator_Error) { if block != nil { assert(size != 0, "Size must be non-zero") block_trim_free(control, block, size) block_mark_as_used(block) res = ([^]byte)(block_to_ptr(block))[:size] + // sanitizer.address_unpoison(res) } return } - -// Clear control structure and point all empty lists at the null block -clear :: proc(control: ^Allocator) { - control.block_null.next_free = &control.block_null - control.block_null.prev_free = &control.block_null - - control.fl_bitmap = 0 - for i in 0..<FL_INDEX_COUNT { - control.sl_bitmap[i] = 0 - for j in 0..<SL_INDEX_COUNT { - control.blocks[i][j] = &control.block_null - } - } -} - -@(require_results) -pool_add :: proc(control: ^Allocator, pool: []u8) -> (err: Error) { - assert(uintptr(raw_data(pool)) % ALIGN_SIZE == 0, "Added memory must be aligned") - - pool_overhead := POOL_OVERHEAD - pool_bytes := align_down(len(pool) - pool_overhead, ALIGN_SIZE) - - if pool_bytes < BLOCK_SIZE_MIN { - return .Backing_Buffer_Too_Small - } else if pool_bytes > BLOCK_SIZE_MAX { - return .Backing_Buffer_Too_Large - } - - // Create the main free block. Offset the start of the block slightly, - // so that the `prev_phys_block` field falls outside of the pool - - // it will never be used. - block := offset_to_block_backwards(raw_data(pool), BLOCK_HEADER_OVERHEAD) - - block_set_size(block, pool_bytes) - block_set_free(block) - block_set_prev_used(block) - block_insert(control, block) - - // Split the block to create a zero-size sentinel block - next := block_link_next(block) - block_set_size(next, 0) - block_set_used(next) - block_set_prev_free(next) - return -} - -pool_remove :: proc(control: ^Allocator, pool: []u8) { - block := offset_to_block_backwards(raw_data(pool), BLOCK_HEADER_OVERHEAD) - - assert(block_is_free(block), "Block should be free") - assert(!block_is_free(block_next(block)), "Next block should not be free") - assert(block_size(block_next(block)) == 0, "Next block size should be zero") - - fl, sl := mapping_insert(block_size(block)) - remove_free_block(control, block, fl, sl) -} - -@(require_results) -alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) { - assert(control != nil) - adjust := adjust_request_size(size, ALIGN_SIZE) - - GAP_MINIMUM :: size_of(Block_Header) - size_with_gap := adjust_request_size(adjust + align + GAP_MINIMUM, align) - - aligned_size := size_with_gap if adjust != 0 && align > ALIGN_SIZE else adjust - if aligned_size == 0 && size > 0 { - return nil, .Out_Of_Memory - } - - block := block_locate_free(control, aligned_size) - if block == nil { - return nil, .Out_Of_Memory - } - ptr := block_to_ptr(block) - aligned := align_ptr(ptr, align) - gap := uint(int(uintptr(aligned)) - int(uintptr(ptr))) - - if gap != 0 && gap < GAP_MINIMUM { - gap_remain := GAP_MINIMUM - gap - offset := uintptr(max(gap_remain, align)) - next_aligned := rawptr(uintptr(aligned) + offset) - - aligned = align_ptr(next_aligned, align) - - gap = uint(int(uintptr(aligned)) - int(uintptr(ptr))) - } - - if gap != 0 { - assert(gap >= GAP_MINIMUM, "gap size too small") - block = block_trim_free_leading(control, block, gap) - } - - return block_prepare_used(control, block, adjust) -} - -@(require_results) -alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) { - res, err = alloc_bytes_non_zeroed(control, size, align) - if err == nil { - intrinsics.mem_zero(raw_data(res), len(res)) - } - return -} - - -free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) { - assert(control != nil) - // `size` is currently ignored - if ptr == nil { - return - } - - block := block_from_ptr(ptr) - assert(!block_is_free(block), "block already marked as free") // double free - block_mark_as_free(block) - block = block_merge_prev(control, block) - block = block_merge_next(control, block) - block_insert(control, block) -} - - -@(require_results) -resize :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, alignment: uint) -> (res: []byte, err: runtime.Allocator_Error) { - assert(control != nil) - if ptr != nil && new_size == 0 { - free_with_size(control, ptr, old_size) - return - } else if ptr == nil { - return alloc_bytes(control, new_size, alignment) - } - - block := block_from_ptr(ptr) - next := block_next(block) - - curr_size := block_size(block) - combined := curr_size + block_size(next) + BLOCK_HEADER_OVERHEAD - adjust := adjust_request_size(new_size, max(ALIGN_SIZE, alignment)) - - assert(!block_is_free(block), "block already marked as free") // double free - - min_size := min(curr_size, new_size, old_size) - - if adjust > curr_size && (!block_is_free(next) || adjust > combined) { - res = alloc_bytes(control, new_size, alignment) or_return - if res != nil { - copy(res, ([^]byte)(ptr)[:min_size]) - free_with_size(control, ptr, curr_size) - } - return - } - if adjust > curr_size { - _ = block_merge_next(control, block) - block_mark_as_used(block) - } - - block_trim_used(control, block, adjust) - res = ([^]byte)(ptr)[:new_size] - - if min_size < new_size { - to_zero := ([^]byte)(ptr)[min_size:new_size] - runtime.mem_zero(raw_data(to_zero), len(to_zero)) - } - return -} - -@(require_results) -resize_non_zeroed :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, alignment: uint) -> (res: []byte, err: runtime.Allocator_Error) { - assert(control != nil) - if ptr != nil && new_size == 0 { - free_with_size(control, ptr, old_size) - return - } else if ptr == nil { - return alloc_bytes_non_zeroed(control, new_size, alignment) - } - - block := block_from_ptr(ptr) - next := block_next(block) - - curr_size := block_size(block) - combined := curr_size + block_size(next) + BLOCK_HEADER_OVERHEAD - adjust := adjust_request_size(new_size, max(ALIGN_SIZE, alignment)) - - assert(!block_is_free(block), "block already marked as free") // double free - - min_size := min(curr_size, new_size, old_size) - - if adjust > curr_size && (!block_is_free(next) || adjust > combined) { - res = alloc_bytes_non_zeroed(control, new_size, alignment) or_return - if res != nil { - copy(res, ([^]byte)(ptr)[:min_size]) - free_with_size(control, ptr, old_size) - } - return - } - - if adjust > curr_size { - _ = block_merge_next(control, block) - block_mark_as_used(block) - } - - block_trim_used(control, block, adjust) - res = ([^]byte)(ptr)[:new_size] - return -} diff --git a/core/mem/tracking_allocator.odin b/core/mem/tracking_allocator.odin index 25c547471..01080075e 100644 --- a/core/mem/tracking_allocator.odin +++ b/core/mem/tracking_allocator.odin @@ -64,6 +64,7 @@ This procedure initializes the tracking allocator `t` with a backing allocator specified with `backing_allocator`. The `internals_allocator` will used to allocate the tracked data. */ +@(no_sanitize_address) tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Allocator, internals_allocator := context.allocator) { t.backing = backing_allocator t.allocation_map.allocator = internals_allocator @@ -77,6 +78,7 @@ tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Alloc /* Destroy the tracking allocator. */ +@(no_sanitize_address) tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) { delete(t.allocation_map) delete(t.bad_free_array) @@ -90,6 +92,7 @@ This procedure clears the tracked data from a tracking allocator. **Note**: This procedure clears only the current allocation data while keeping the totals intact. */ +@(no_sanitize_address) tracking_allocator_clear :: proc(t: ^Tracking_Allocator) { sync.mutex_lock(&t.mutex) clear(&t.allocation_map) @@ -103,6 +106,7 @@ Reset the tracking allocator. Reset all of a Tracking Allocator's allocation data back to zero. */ +@(no_sanitize_address) tracking_allocator_reset :: proc(t: ^Tracking_Allocator) { sync.mutex_lock(&t.mutex) clear(&t.allocation_map) @@ -124,6 +128,7 @@ Override Tracking_Allocator.bad_free_callback to have something else happen. For example, you can use tracking_allocator_bad_free_callback_add_to_array to return the tracking allocator to the old behavior, where the bad_free_array was used. */ +@(no_sanitize_address) tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) { runtime.print_caller_location(location) runtime.print_string(" Tracking allocator error: Bad free of pointer ") @@ -136,6 +141,7 @@ tracking_allocator_bad_free_callback_panic :: proc(t: ^Tracking_Allocator, memor Alternative behavior for a bad free: Store in `bad_free_array`. If you use this, then you must make sure to check Tracking_Allocator.bad_free_array at some point. */ +@(no_sanitize_address) tracking_allocator_bad_free_callback_add_to_array :: proc(t: ^Tracking_Allocator, memory: rawptr, location: runtime.Source_Code_Location) { append(&t.bad_free_array, Tracking_Allocator_Bad_Free_Entry { memory = memory, @@ -175,7 +181,7 @@ Example: } } */ -@(require_results) +@(require_results, no_sanitize_address) tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator { return Allocator{ data = data, @@ -183,6 +189,7 @@ tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator { } } +@(no_sanitize_address) tracking_allocator_proc :: proc( allocator_data: rawptr, mode: Allocator_Mode, @@ -191,6 +198,7 @@ tracking_allocator_proc :: proc( old_size: int, loc := #caller_location, ) -> (result: []byte, err: Allocator_Error) { + @(no_sanitize_address) track_alloc :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { data.total_memory_allocated += i64(entry.size) data.total_allocation_count += 1 @@ -200,6 +208,7 @@ tracking_allocator_proc :: proc( } } + @(no_sanitize_address) track_free :: proc(data: ^Tracking_Allocator, entry: ^Tracking_Allocator_Entry) { data.total_memory_freed += i64(entry.size) data.total_free_count += 1 diff --git a/core/mem/virtual/arena.odin b/core/mem/virtual/arena.odin index 5191505cf..b515aa3cf 100644 --- a/core/mem/virtual/arena.odin +++ b/core/mem/virtual/arena.odin @@ -3,6 +3,8 @@ package mem_virtual import "core:mem" import "core:sync" +// import "base:sanitizer" + Arena_Kind :: enum uint { Growing = 0, // Chained memory blocks (singly linked list). Static = 1, // Fixed reservation sized. @@ -43,7 +45,7 @@ DEFAULT_ARENA_STATIC_RESERVE_SIZE :: mem.Gigabyte when size_of(uintptr) == 8 els // Initialization of an `Arena` to be a `.Growing` variant. // A growing arena is a linked list of `Memory_Block`s allocated with virtual memory. -@(require_results) +@(require_results, no_sanitize_address) arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (err: Allocator_Error) { arena.kind = .Growing arena.curr_block = memory_block_alloc(0, reserved, {}) or_return @@ -53,24 +55,26 @@ arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING if arena.minimum_block_size == 0 { arena.minimum_block_size = reserved } + // sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed]) return } // Initialization of an `Arena` to be a `.Static` variant. // A static arena contains a single `Memory_Block` allocated with virtual memory. -@(require_results) +@(require_results, no_sanitize_address) arena_init_static :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_STATIC_RESERVE_SIZE, commit_size: uint = DEFAULT_ARENA_STATIC_COMMIT_SIZE) -> (err: Allocator_Error) { arena.kind = .Static arena.curr_block = memory_block_alloc(commit_size, reserved, {}) or_return arena.total_used = 0 arena.total_reserved = arena.curr_block.reserved + // sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed]) return } // Initialization of an `Arena` to be a `.Buffer` variant. // A buffer arena contains single `Memory_Block` created from a user provided []byte. -@(require_results) +@(require_results, no_sanitize_address) arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Error) { if len(buffer) < size_of(Memory_Block) { return .Out_Of_Memory @@ -78,7 +82,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro arena.kind = .Buffer - mem.zero_slice(buffer) + // sanitizer.address_poison(buffer[:]) block_base := raw_data(buffer) block := (^Memory_Block)(block_base) @@ -94,7 +98,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro } // Allocates memory from the provided arena. -@(require_results) +@(require_results, no_sanitize_address) arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc) @@ -104,11 +108,22 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l } sync.mutex_guard(&arena.mutex) + return arena_alloc_unguarded(arena, size, alignment, loc) +} + +// Allocates memory from the provided arena. +@(require_results, no_sanitize_address, private) +arena_alloc_unguarded :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { + size := size + if size == 0 { + return nil, nil + } switch arena.kind { case .Growing: - needed := mem.align_forward_uint(size, alignment) - if arena.curr_block == nil || (safe_add(arena.curr_block.used, needed) or_else 0) > arena.curr_block.reserved { + prev_used := 0 if arena.curr_block == nil else arena.curr_block.used + data, err = alloc_from_memory_block(arena.curr_block, size, alignment, default_commit_size=arena.default_commit_size) + if err == .Out_Of_Memory { if arena.minimum_block_size == 0 { arena.minimum_block_size = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE arena.minimum_block_size = mem.align_forward_uint(arena.minimum_block_size, DEFAULT_PAGE_SIZE) @@ -124,6 +139,7 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l max(arena.default_commit_size, arena.minimum_block_size) } + needed := mem.align_forward_uint(size, alignment) needed = max(needed, arena.default_commit_size) block_size := max(needed, arena.minimum_block_size) @@ -131,10 +147,10 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l new_block.prev = arena.curr_block arena.curr_block = new_block arena.total_reserved += new_block.reserved - } - prev_used := arena.curr_block.used - data, err = alloc_from_memory_block(arena.curr_block, size, alignment, default_commit_size=arena.default_commit_size) + prev_used = 0 + data, err = alloc_from_memory_block(arena.curr_block, size, alignment, default_commit_size=arena.default_commit_size) + } arena.total_used += arena.curr_block.used - prev_used case .Static: if arena.curr_block == nil { @@ -156,10 +172,13 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l data, err = alloc_from_memory_block(arena.curr_block, size, alignment, default_commit_size=0) arena.total_used = arena.curr_block.used } + + // sanitizer.address_unpoison(data) return } // Resets the memory of a Static or Buffer arena to a specific `position` (offset) and zeroes the previously used memory. +@(no_sanitize_address) arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) -> bool { sync.mutex_guard(&arena.mutex) @@ -173,6 +192,7 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) mem.zero_slice(arena.curr_block.base[arena.curr_block.used:][:prev_pos-pos]) } arena.total_used = arena.curr_block.used + // sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed]) return true } else if pos == 0 { arena.total_used = 0 @@ -182,6 +202,7 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) } // Frees the last memory block of a Growing Arena +@(no_sanitize_address) arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) { if free_block := arena.curr_block; free_block != nil { assert(arena.kind == .Growing, "expected a .Growing arena", loc) @@ -189,11 +210,13 @@ arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_locat arena.total_reserved -= free_block.reserved arena.curr_block = free_block.prev + // sanitizer.address_poison(free_block.base[:free_block.committed]) memory_block_dealloc(free_block) } } // Deallocates all but the first memory block of the arena and resets the allocator's usage to 0. +@(no_sanitize_address) arena_free_all :: proc(arena: ^Arena, loc := #caller_location) { switch arena.kind { case .Growing: @@ -206,7 +229,9 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) { if arena.curr_block != nil { curr_block_used := int(arena.curr_block.used) arena.curr_block.used = 0 + // sanitizer.address_unpoison(arena.curr_block.base[:curr_block_used]) mem.zero(arena.curr_block.base, curr_block_used) + // sanitizer.address_poison(arena.curr_block.base[:arena.curr_block.committed]) } arena.total_used = 0 case .Static, .Buffer: @@ -217,6 +242,7 @@ arena_free_all :: proc(arena: ^Arena, loc := #caller_location) { // Frees all of the memory allocated by the arena and zeros all of the values of an arena. // A buffer based arena does not `delete` the provided `[]byte` bufffer. +@(no_sanitize_address) arena_destroy :: proc(arena: ^Arena, loc := #caller_location) { sync.mutex_guard(&arena.mutex) switch arena.kind { @@ -248,7 +274,7 @@ arena_static_bootstrap_new :: proc{ } // Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy. -@(require_results) +@(require_results, no_sanitize_address) arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) { bootstrap: Arena bootstrap.kind = .Growing @@ -264,13 +290,13 @@ arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintp } // Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy. -@(require_results) +@(require_results, no_sanitize_address) arena_growing_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) { return arena_growing_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), minimum_block_size) } // Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy. -@(require_results) +@(require_results, no_sanitize_address) arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, reserved: uint) -> (ptr: ^T, err: Allocator_Error) { bootstrap: Arena bootstrap.kind = .Static @@ -286,19 +312,20 @@ arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintpt } // Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy. -@(require_results) +@(require_results, no_sanitize_address) arena_static_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, reserved: uint) -> (ptr: ^T, err: Allocator_Error) { return arena_static_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), reserved) } // Create an `Allocator` from the provided `Arena` -@(require_results) +@(require_results, no_sanitize_address) arena_allocator :: proc(arena: ^Arena) -> mem.Allocator { return mem.Allocator{arena_allocator_proc, arena} } // The allocator procedure used by an `Allocator` produced by `arena_allocator` +@(no_sanitize_address) arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, size, alignment: int, old_memory: rawptr, old_size: int, @@ -328,10 +355,15 @@ 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: + } + + sync.mutex_guard(&arena.mutex) + + if uintptr(old_data) & uintptr(alignment-1) == 0 { if size < old_size { // shrink data in-place data = old_data[:size] + // sanitizer.address_poison(old_data[size:old_size]) return } @@ -341,18 +373,22 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, new_end := start + size if start < old_end && old_end == block.used && new_end <= block.reserved { // grow data in-place, adjusting next allocation + prev_used := block.used _ = alloc_from_memory_block(block, new_end - old_end, 1, default_commit_size=arena.default_commit_size) or_return + arena.total_used += block.used - prev_used data = block.base[start:new_end] + // sanitizer.address_unpoison(data) return } } } - new_memory := arena_alloc(arena, size, alignment, location) or_return + new_memory := arena_alloc_unguarded(arena, size, alignment, location) or_return if new_memory == nil { return } copy(new_memory, old_data[:old_size]) + // sanitizer.address_poison(old_data[:old_size]) return new_memory, nil case .Query_Features: set := (^mem.Allocator_Mode_Set)(old_memory) @@ -378,7 +414,7 @@ Arena_Temp :: struct { } // Begins the section of temporary arena memory. -@(require_results) +@(require_results, no_sanitize_address) arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) { assert(arena != nil, "nil arena", loc) sync.mutex_guard(&arena.mutex) @@ -393,6 +429,7 @@ arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena } // Ends the section of temporary arena memory by resetting the memory to the stored position. +@(no_sanitize_address) arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { assert(temp.arena != nil, "nil arena", loc) arena := temp.arena @@ -428,6 +465,7 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { } // Ignore the use of a `arena_temp_begin` entirely by __not__ resetting to the stored position. +@(no_sanitize_address) arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) { assert(temp.arena != nil, "nil arena", loc) arena := temp.arena @@ -438,6 +476,7 @@ arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) { } // Asserts that all uses of `Arena_Temp` has been used by an `Arena` +@(no_sanitize_address) arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) { assert(arena.temp_count == 0, "Arena_Temp not been ended", loc) } diff --git a/core/mem/virtual/arena_util.odin b/core/mem/virtual/arena_util.odin index 4e28c17d6..fb778687b 100644 --- a/core/mem/virtual/arena_util.odin +++ b/core/mem/virtual/arena_util.odin @@ -20,6 +20,17 @@ new_aligned :: proc(arena: ^Arena, $T: typeid, alignment: uint, loc := #caller_l return } +// The `new_clone` procedure allocates memory for a type `T` from a `virtual.Arena`. The second argument is a value that +// is to be copied to the allocated data. The value returned is a pointer to a newly allocated value of that type using the specified allocator. +@(require_results) +new_clone :: proc(arena: ^Arena, data: $T, loc := #caller_location) -> (ptr: ^T, err: Allocator_Error) { + ptr, err = new_aligned(arena, T, align_of(T), loc) + if ptr != nil && err == nil { + ptr^ = data + } + return +} + // `make_slice` allocates and initializes a slice. Like `new`, the second argument is a type, not a value. // Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. // diff --git a/core/mem/virtual/virtual.odin b/core/mem/virtual/virtual.odin index 4afc33813..3f388acf3 100644 --- a/core/mem/virtual/virtual.odin +++ b/core/mem/virtual/virtual.odin @@ -2,39 +2,46 @@ package mem_virtual import "core:mem" import "base:intrinsics" +// import "base:sanitizer" import "base:runtime" _ :: runtime DEFAULT_PAGE_SIZE := uint(4096) @(init, private) -platform_memory_init :: proc() { +platform_memory_init :: proc "contextless" () { _platform_memory_init() } Allocator_Error :: mem.Allocator_Error -@(require_results) +@(require_results, no_sanitize_address) reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { return _reserve(size) } +@(no_sanitize_address) commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error { + // sanitizer.address_unpoison(data, size) return _commit(data, size) } -@(require_results) +@(require_results, no_sanitize_address) reserve_and_commit :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { data = reserve(size) or_return commit(raw_data(data), size) or_return return } +@(no_sanitize_address) decommit :: proc "contextless" (data: rawptr, size: uint) { + // sanitizer.address_poison(data, size) _decommit(data, size) } +@(no_sanitize_address) release :: proc "contextless" (data: rawptr, size: uint) { + // sanitizer.address_unpoison(data, size) _release(data, size) } @@ -46,13 +53,11 @@ Protect_Flag :: enum u32 { Protect_Flags :: distinct bit_set[Protect_Flag; u32] Protect_No_Access :: Protect_Flags{} +@(no_sanitize_address) protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool { return _protect(data, size, flags) } - - - Memory_Block :: struct { prev: ^Memory_Block, base: [^]byte, @@ -66,13 +71,13 @@ Memory_Block_Flag :: enum u32 { Memory_Block_Flags :: distinct bit_set[Memory_Block_Flag; u32] -@(private="file", require_results) +@(private="file", require_results, no_sanitize_address) align_formula :: #force_inline proc "contextless" (size, align: uint) -> uint { result := size + align-1 return result - result%align } -@(require_results) +@(require_results, no_sanitize_address) memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags: Memory_Block_Flags = {}) -> (block: ^Memory_Block, err: Allocator_Error) { page_size := DEFAULT_PAGE_SIZE assert(mem.is_power_of_two(uintptr(page_size))) @@ -84,8 +89,8 @@ memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags reserved = align_formula(reserved, page_size) committed = clamp(committed, 0, reserved) - total_size := uint(reserved + max(alignment, size_of(Platform_Memory_Block))) - base_offset := uintptr(max(alignment, size_of(Platform_Memory_Block))) + total_size := reserved + alignment + size_of(Platform_Memory_Block) + base_offset := mem.align_forward_uintptr(size_of(Platform_Memory_Block), max(uintptr(alignment), align_of(Platform_Memory_Block))) protect_offset := uintptr(0) do_protection := false @@ -116,8 +121,9 @@ memory_block_alloc :: proc(committed, reserved: uint, alignment: uint = 0, flags return &pmblock.block, nil } -@(require_results) +@(require_results, no_sanitize_address) alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint, default_commit_size: uint = 0) -> (data: []byte, err: Allocator_Error) { + @(no_sanitize_address) calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint { alignment_offset := uint(0) ptr := uintptr(block.base[block.used:]) @@ -128,6 +134,7 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint, return alignment_offset } + @(no_sanitize_address) do_commit_if_necessary :: proc(block: ^Memory_Block, size: uint, default_commit_size: uint) -> (err: Allocator_Error) { if block.committed - block.used < size { pmblock := (^Platform_Memory_Block)(block) @@ -172,10 +179,12 @@ alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint, data = block.base[block.used+alignment_offset:][:min_size] block.used += size + // sanitizer.address_unpoison(data) return } +@(no_sanitize_address) memory_block_dealloc :: proc(block_to_free: ^Memory_Block) { if block := (^Platform_Memory_Block)(block_to_free); block != nil { platform_memory_free(block) diff --git a/core/mem/virtual/virtual_darwin.odin b/core/mem/virtual/virtual_darwin.odin new file mode 100644 index 000000000..0635c83d4 --- /dev/null +++ b/core/mem/virtual/virtual_darwin.odin @@ -0,0 +1,20 @@ +package mem_virtual + +import "core:sys/posix" + +_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { + result := posix.mmap(nil, size, {}, {.ANONYMOUS, .PRIVATE}) + if result == posix.MAP_FAILED { + assert_contextless(posix.errno() == .ENOMEM) + return nil, .Out_Of_Memory + } + + return ([^]byte)(uintptr(result))[:size], nil +} + +_decommit :: proc "contextless" (data: rawptr, size: uint) { + MADV_FREE :: 5 + + posix.mprotect(data, size, {}) + posix.posix_madvise(data, size, transmute(posix.MAdvice)i32(MADV_FREE)) +} diff --git a/core/mem/virtual/virtual_freebsd.odin b/core/mem/virtual/virtual_freebsd.odin new file mode 100644 index 000000000..af0f31733 --- /dev/null +++ b/core/mem/virtual/virtual_freebsd.odin @@ -0,0 +1,26 @@ +package mem_virtual + +import "core:sys/posix" + +_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { + + PROT_MAX :: proc "contextless" (flags: posix.Prot_Flags) -> posix.Prot_Flags { + _PROT_MAX_SHIFT :: 16 + return transmute(posix.Prot_Flags)(transmute(i32)flags << _PROT_MAX_SHIFT) + } + + result := posix.mmap(nil, size, PROT_MAX({.READ, .WRITE, .EXEC}), {.ANONYMOUS, .PRIVATE}) + if result == posix.MAP_FAILED { + assert_contextless(posix.errno() == .ENOMEM) + return nil, .Out_Of_Memory + } + + return ([^]byte)(uintptr(result))[:size], nil +} + +_decommit :: proc "contextless" (data: rawptr, size: uint) { + MADV_FREE :: 5 + + posix.mprotect(data, size, {}) + posix.posix_madvise(data, size, transmute(posix.MAdvice)i32(MADV_FREE)) +} diff --git a/core/mem/virtual/virtual_linux.odin b/core/mem/virtual/virtual_linux.odin index 3e0d7668b..f819fbf86 100644 --- a/core/mem/virtual/virtual_linux.odin +++ b/core/mem/virtual/virtual_linux.odin @@ -43,10 +43,10 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) return errno == .NONE } -_platform_memory_init :: proc() { +_platform_memory_init :: proc "contextless" () { DEFAULT_PAGE_SIZE = 4096 // is power of two - assert(DEFAULT_PAGE_SIZE != 0 && (DEFAULT_PAGE_SIZE & (DEFAULT_PAGE_SIZE-1)) == 0) + assert_contextless(DEFAULT_PAGE_SIZE != 0 && (DEFAULT_PAGE_SIZE & (DEFAULT_PAGE_SIZE-1)) == 0) } diff --git a/core/mem/virtual/virtual_netbsd.odin b/core/mem/virtual/virtual_netbsd.odin new file mode 100644 index 000000000..588625cc7 --- /dev/null +++ b/core/mem/virtual/virtual_netbsd.odin @@ -0,0 +1,25 @@ +package mem_virtual + +import "core:sys/posix" + +_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { + + PROT_MPROTECT :: proc "contextless" (flags: posix.Prot_Flags) -> posix.Prot_Flags { + return transmute(posix.Prot_Flags)(transmute(i32)flags << 3) + } + + result := posix.mmap(nil, size, PROT_MPROTECT({.READ, .WRITE, .EXEC}), {.ANONYMOUS, .PRIVATE}) + if result == posix.MAP_FAILED { + assert_contextless(posix.errno() == .ENOMEM) + return nil, .Out_Of_Memory + } + + return ([^]byte)(uintptr(result))[:size], nil +} + +_decommit :: proc "contextless" (data: rawptr, size: uint) { + MADV_FREE :: 6 + + posix.mprotect(data, size, {}) + posix.posix_madvise(data, size, transmute(posix.MAdvice)i32(MADV_FREE)) +} diff --git a/core/mem/virtual/virtual_openbsd.odin b/core/mem/virtual/virtual_openbsd.odin new file mode 100644 index 000000000..83f7ca9ca --- /dev/null +++ b/core/mem/virtual/virtual_openbsd.odin @@ -0,0 +1,20 @@ +package mem_virtual + +import "core:sys/posix" + +_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { + result := posix.mmap(nil, size, {}, {.ANONYMOUS, .PRIVATE}) + if result == posix.MAP_FAILED { + assert_contextless(posix.errno() == .ENOMEM) + return nil, .Out_Of_Memory + } + + return ([^]byte)(uintptr(result))[:size], nil +} + +_decommit :: proc "contextless" (data: rawptr, size: uint) { + MADV_FREE :: 6 + + posix.mprotect(data, size, {}) + posix.posix_madvise(data, size, transmute(posix.MAdvice)i32(MADV_FREE)) +} diff --git a/core/mem/virtual/virtual_other.odin b/core/mem/virtual/virtual_other.odin index a57856975..c6386e842 100644 --- a/core/mem/virtual/virtual_other.odin +++ b/core/mem/virtual/virtual_other.odin @@ -25,7 +25,7 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) return false } -_platform_memory_init :: proc() { +_platform_memory_init :: proc "contextless" () { } _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { diff --git a/core/mem/virtual/virtual_platform.odin b/core/mem/virtual/virtual_platform.odin index 54c42ce4b..c9dde4e9d 100644 --- a/core/mem/virtual/virtual_platform.odin +++ b/core/mem/virtual/virtual_platform.odin @@ -7,6 +7,7 @@ Platform_Memory_Block :: struct { reserved: uint, } +@(no_sanitize_address) platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (block: ^Platform_Memory_Block, err: Allocator_Error) { to_commit, to_reserve := to_commit, to_reserve to_reserve = max(to_commit, to_reserve) @@ -15,7 +16,9 @@ platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (bl to_commit = clamp(to_commit, size_of(Platform_Memory_Block), total_to_reserved) data := reserve(total_to_reserved) or_return - commit(raw_data(data), to_commit) + + commit_err := commit(raw_data(data), to_commit) + assert_contextless(commit_err == nil) block = (^Platform_Memory_Block)(raw_data(data)) block.committed = to_commit @@ -24,12 +27,14 @@ platform_memory_alloc :: proc "contextless" (to_commit, to_reserve: uint) -> (bl } +@(no_sanitize_address) platform_memory_free :: proc "contextless" (block: ^Platform_Memory_Block) { if block != nil { release(block, block.reserved) } } +@(no_sanitize_address) platform_memory_commit :: proc "contextless" (block: ^Platform_Memory_Block, to_commit: uint) -> (err: Allocator_Error) { if to_commit < block.committed { return nil diff --git a/core/mem/virtual/virtual_posix.odin b/core/mem/virtual/virtual_posix.odin index c3d6a9095..4bb161770 100644 --- a/core/mem/virtual/virtual_posix.odin +++ b/core/mem/virtual/virtual_posix.odin @@ -4,36 +4,18 @@ package mem_virtual import "core:sys/posix" -// Define non-posix needed flags: -when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD { - MADV_FREE :: 5 /* pages unneeded, discard contents */ -} else when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { - MADV_FREE :: 6 -} - -_reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { - flags := posix.Map_Flags{ .ANONYMOUS, .PRIVATE } - result := posix.mmap(nil, size, {}, flags) - if result == posix.MAP_FAILED { - return nil, .Out_Of_Memory - } - - return ([^]byte)(uintptr(result))[:size], nil -} - _commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error { if posix.mprotect(data, size, { .READ, .WRITE }) != .OK { - return .Out_Of_Memory + #partial switch posix.errno() { + case .EACCES, .EPERM: return .Invalid_Pointer + case .ENOTSUP, .EINVAL: return .Invalid_Argument + case: return .Out_Of_Memory + } } return nil } -_decommit :: proc "contextless" (data: rawptr, size: uint) { - posix.mprotect(data, size, {}) - posix.posix_madvise(data, size, transmute(posix.MAdvice)i32(MADV_FREE)) -} - _release :: proc "contextless" (data: rawptr, size: uint) { posix.munmap(data, size) } @@ -46,13 +28,13 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) return posix.mprotect(data, size, transmute(posix.Prot_Flags)flags) == .OK } -_platform_memory_init :: proc() { +_platform_memory_init :: proc "contextless" () { // NOTE: `posix.PAGESIZE` due to legacy reasons could be wrong so we use `sysconf`. size := posix.sysconf(._PAGESIZE) DEFAULT_PAGE_SIZE = uint(max(size, posix.PAGESIZE)) // is power of two - assert(DEFAULT_PAGE_SIZE != 0 && (DEFAULT_PAGE_SIZE & (DEFAULT_PAGE_SIZE-1)) == 0) + assert_contextless(DEFAULT_PAGE_SIZE != 0 && (DEFAULT_PAGE_SIZE & (DEFAULT_PAGE_SIZE-1)) == 0) } _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { diff --git a/core/mem/virtual/virtual_windows.odin b/core/mem/virtual/virtual_windows.odin index acd30ae33..1d777af17 100644 --- a/core/mem/virtual/virtual_windows.odin +++ b/core/mem/virtual/virtual_windows.odin @@ -72,7 +72,7 @@ foreign Kernel32 { flProtect: u32, dwMaximumSizeHigh: u32, dwMaximumSizeLow: u32, - lpName: [^]u16, + lpName: cstring16, ) -> rawptr --- MapViewOfFile :: proc( @@ -83,6 +83,8 @@ foreign Kernel32 { dwNumberOfBytesToMap: uint, ) -> rawptr --- } + +@(no_sanitize_address) _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) { result := VirtualAlloc(nil, size, MEM_RESERVE, PAGE_READWRITE) if result == nil { @@ -93,6 +95,7 @@ _reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Err return } +@(no_sanitize_address) _commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error { result := VirtualAlloc(data, size, MEM_COMMIT, PAGE_READWRITE) if result == nil { @@ -107,12 +110,18 @@ _commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error { } return nil } + +@(no_sanitize_address) _decommit :: proc "contextless" (data: rawptr, size: uint) { VirtualFree(data, size, MEM_DECOMMIT) } + +@(no_sanitize_address) _release :: proc "contextless" (data: rawptr, size: uint) { VirtualFree(data, 0, MEM_RELEASE) } + +@(no_sanitize_address) _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) -> bool { pflags: u32 pflags = PAGE_NOACCESS @@ -136,17 +145,18 @@ _protect :: proc "contextless" (data: rawptr, size: uint, flags: Protect_Flags) } - -_platform_memory_init :: proc() { +@(no_sanitize_address) +_platform_memory_init :: proc "contextless" () { sys_info: SYSTEM_INFO GetSystemInfo(&sys_info) DEFAULT_PAGE_SIZE = max(DEFAULT_PAGE_SIZE, uint(sys_info.dwPageSize)) // is power of two - assert(DEFAULT_PAGE_SIZE != 0 && (DEFAULT_PAGE_SIZE & (DEFAULT_PAGE_SIZE-1)) == 0) + assert_contextless(DEFAULT_PAGE_SIZE != 0 && (DEFAULT_PAGE_SIZE & (DEFAULT_PAGE_SIZE-1)) == 0) } +@(no_sanitize_address) _map_file :: proc "contextless" (fd: uintptr, size: i64, flags: Map_File_Flags) -> (data: []byte, error: Map_File_Error) { page_flags: u32 if flags == {.Read} { diff --git a/core/net/common.odin b/core/net/common.odin index 12add8225..7b33350c0 100644 --- a/core/net/common.odin +++ b/core/net/common.odin @@ -53,8 +53,6 @@ ODIN_NET_TCP_NODELAY_DEFAULT :: #config(ODIN_NET_TCP_NODELAY_DEFAULT, true) Maybe :: runtime.Maybe Network_Error :: union #shared_nil { - General_Error, - Platform_Error, Create_Socket_Error, Dial_Error, Listen_Error, @@ -65,6 +63,8 @@ Network_Error :: union #shared_nil { TCP_Recv_Error, UDP_Recv_Error, Shutdown_Error, + Interfaces_Error, + Socket_Info_Error, Socket_Option_Error, Set_Blocking_Error, Parse_Endpoint_Error, @@ -74,14 +74,13 @@ Network_Error :: union #shared_nil { #assert(size_of(Network_Error) == 8) -General_Error :: enum u32 { - None = 0, - Unable_To_Enumerate_Network_Interfaces = 1, +Interfaces_Error :: enum u32 { + None, + Unable_To_Enumerate_Network_Interfaces, + Allocation_Failure, + Unknown, } -// `Platform_Error` is used to wrap errors returned by the different platforms that don't fit a common error. -Platform_Error :: enum u32 {} - Parse_Endpoint_Error :: enum u32 { None = 0, Bad_Port = 1, @@ -109,7 +108,7 @@ TCP_Options :: struct { no_delay: bool, } -default_tcp_options := TCP_Options { +DEFAULT_TCP_OPTIONS :: TCP_Options { no_delay = ODIN_NET_TCP_NODELAY_DEFAULT, } @@ -262,6 +261,9 @@ DNS_Configuration :: struct { resolv_conf: string, hosts_file: string, + resolv_conf_buf: [128]u8, + hosts_file_buf: [128]u8, + // TODO: Allow loading these up with `reload_configuration()` call or the like, // so we don't have to do it each call. name_servers: []Endpoint, diff --git a/core/net/dns.odin b/core/net/dns.odin index 7eb543db3..ed8c00d6a 100644 --- a/core/net/dns.odin +++ b/core/net/dns.odin @@ -22,69 +22,43 @@ package net Haesbaert: Security fixes */ +@(require) import "base:runtime" import "core:mem" import "core:strings" import "core:time" import "core:os" import "core:math/rand" -/* - Default configuration for DNS resolution. -*/ +@(require) import "core:sync" + +dns_config_initialized: sync.Once when ODIN_OS == .Windows { - DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{ - resolv_conf = "", - hosts_file = "%WINDIR%\\system32\\drivers\\etc\\hosts", + dns_configuration := DNS_Configuration{ + resolv_conf = "", + hosts_file = "%WINDIR%\\system32\\drivers\\etc\\hosts", } } else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { - DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{ - resolv_conf = "/etc/resolv.conf", - hosts_file = "/etc/hosts", + dns_configuration := DNS_Configuration{ + resolv_conf = "/etc/resolv.conf", + hosts_file = "/etc/hosts", } } else { #panic("Please add a configuration for this OS.") } -@(init) +/* + Replaces environment placeholders in `dns_configuration`. Only necessary on Windows. + Is automatically called, once, by `get_dns_records_*`. +*/ +@(private) init_dns_configuration :: proc() { - /* - Resolve %ENVIRONMENT% placeholders in their paths. - */ - dns_configuration.resolv_conf, _ = replace_environment_path(dns_configuration.resolv_conf) - dns_configuration.hosts_file, _ = replace_environment_path(dns_configuration.hosts_file) -} - -@(fini, private) -destroy_dns_configuration :: proc() { - delete(dns_configuration.resolv_conf) - dns_configuration.resolv_conf = "" - delete(dns_configuration.hosts_file) - dns_configuration.hosts_file = "" -} - -dns_configuration := DEFAULT_DNS_CONFIGURATION - -// Always allocates for consistency. -replace_environment_path :: proc(path: string, allocator := context.allocator) -> (res: string, ok: bool) { - // Nothing to replace. Return a clone of the original. - if strings.count(path, "%") != 2 { - return strings.clone(path, allocator), true + when ODIN_OS == .Windows { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + val := os.replace_environment_placeholders(dns_configuration.hosts_file, context.temp_allocator) + copy(dns_configuration.hosts_file_buf[:], val) + dns_configuration.hosts_file = string(dns_configuration.hosts_file_buf[:len(val)]) } - - left := strings.index(path, "%") + 1 - assert(left > 0 && left <= len(path)) // should be covered by there being two % - - right := strings.index(path[left:], "%") + 1 - assert(right > 0 && right <= len(path)) // should be covered by there being two % - - env_key := path[left: right] - env_val := os.get_env(env_key, allocator) - defer delete(env_val) - - res, _ = strings.replace(path, path[left - 1: right + 1], env_val, 1, allocator) - return res, true } - /* Resolves a hostname to exactly one IP4 and IP6 endpoint. It's then up to you which one you use. @@ -204,6 +178,9 @@ resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Net See `destroy_records`. */ get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { + when ODIN_OS == .Windows { + sync.once_do(&dns_config_initialized, init_dns_configuration) + } return _get_dns_records_os(hostname, type, allocator) } @@ -219,6 +196,9 @@ get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocat See `destroy_records`. */ get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type, name_servers: []Endpoint, host_overrides: []DNS_Record, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { + when ODIN_OS == .Windows { + sync.once_do(&dns_config_initialized, init_dns_configuration) + } context.allocator = allocator if type != .SRV { @@ -440,6 +420,8 @@ load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> ( splits := strings.fields(line) defer delete(splits) + (len(splits) >= 2) or_continue + ip_str := splits[0] addr := parse_address(ip_str) if addr == nil { @@ -886,4 +868,4 @@ parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator xid = hdr.id return _records[:], xid, true -} +}
\ No newline at end of file diff --git a/core/net/dns_unix.odin b/core/net/dns_unix.odin index e4336e410..351d26dbc 100644 --- a/core/net/dns_unix.odin +++ b/core/net/dns_unix.odin @@ -79,4 +79,4 @@ _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator : } return get_dns_records_from_nameservers(hostname, type, name_servers, host_overrides[:]) -} +}
\ No newline at end of file diff --git a/core/net/errors.odin b/core/net/errors.odin new file mode 100644 index 000000000..4853327b0 --- /dev/null +++ b/core/net/errors.odin @@ -0,0 +1,292 @@ +package net + +/* +Retrieve a platform specific error code, for when the categorized cross-platform errors are not enough. + +Platforms specific returns: +- Darwin: `posix.Errno` (`core:sys/posix`) +- Linux: `linux.Errno` (`core:sys/linux`) +- FreeBSD: `freebsd.Errno` (`core:sys/freebsd`) +- Windows: `windows.System_Error` (`core:sys/windows`) +*/ +@(require_results) +last_platform_error :: proc() -> i32 { + return _last_platform_error() +} + +/* +Retrieve a stringified version of the last platform error. +*/ +@(require_results) +last_platform_error_string :: proc() -> string { + return _last_platform_error_string() +} + +set_last_platform_error :: proc(err: i32) { + _set_last_platform_error(err) +} + +Create_Socket_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given. + Insufficient_Resources, + // Invalid/unsupported family or protocol. + Invalid_Argument, + // The user has no permission to create a socket of this type and/or protocol. + Insufficient_Permissions, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} + +Dial_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given. + Insufficient_Resources, + // Invalid endpoint and/or options. + Invalid_Argument, + // An attempt was made to connect to a broadcast socket on a socket that doesn't support it. + Broadcast_Not_Supported, + // The socket is already connected. + Already_Connected, + // The socket is already in the progress of making a connection. + Already_Connecting, + // The address is already in use. + Address_In_Use, + // Could not reach the remote host. + Host_Unreachable, + // The remote host refused the connection or isn't listening. + Refused, + // The connection was reset by the remote host. + Reset, + // Timed out before making a connection. + Timeout, + // Non-blocking socket that would need to block waiting to connect. + Would_Block, + // Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows. + Interrupted, + // Endpoint given without a port, which is required. + Port_Required, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} + +Bind_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given. + Insufficient_Resources, + // Invalid socket or endpoint, or invalid combination of the two. + Invalid_Argument, + // The socket is already bound to an address. + Already_Bound, + // The address is protected and the current user has insufficient permissions to access it. + Insufficient_Permissions_For_Address, + // The address is already in use. + Address_In_Use, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} + +Listen_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given. + Insufficient_Resources, + // The socket or backlog is invalid. + Invalid_Argument, + // The socket is valid, but does not support listening. + Unsupported_Socket, + // The socket is already connected. + Already_Connected, + // The address is already in use. + Address_In_Use, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} + +Accept_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given. + Insufficient_Resources, + // Invalid socket, or options. + Invalid_Argument, + // The given socket does not support accepting connections. + Unsupported_Socket, + // accept called on a socket which is not listening. + Not_Listening, + // A connection arrived but was closed while in the listen queue. + Aborted, + // Timed out before being able to accept a connection. + Timeout, + // Non-blocking socket that would need to block waiting for a connection. + Would_Block, + // Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows. + Interrupted, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} + +TCP_Recv_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given. + Insufficient_Resources, + // Invalid socket or buffer given. + Invalid_Argument, + // The socket is not connected. + Not_Connected, + // Connection was closed/broken/shutdown while receiving data. + Connection_Closed, + // Timed out before being able to receive any data. + Timeout, + // Non-blocking socket that would need to block waiting on data. + Would_Block, + // Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows. + Interrupted, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} + +UDP_Recv_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given. + Insufficient_Resources, + // Invalid socket or buffer given. + Invalid_Argument, + // "Connection" was refused by remote, or closed/broken/shutdown while receiving data. + Connection_Refused, + // Timed out before being able to receive any data. + Timeout, + // Non-blocking socket that would need to block waiting on data. + Would_Block, + // Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows. + Interrupted, + // Linux and UDP only: indicates the buffer was too small to receive all data, and the excess is truncated and discarded. + Excess_Truncated, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} + +TCP_Send_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given. + Insufficient_Resources, + // Invalid socket or buffer given. + Invalid_Argument, + // Connection was closed/broken/shutdown while receiving data. + Connection_Closed, + // The socket is not connected. + Not_Connected, + // Could not reach the remote host. + Host_Unreachable, + // Timed out before being able to send any data. + Timeout, + // Non-blocking socket that would need to block waiting on the remote to be able to receive the data. + Would_Block, + // Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows. + Interrupted, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} + +UDP_Send_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given. + Insufficient_Resources, + // Invalid socket or buffer given. + Invalid_Argument, + // Could not reach the remote host. + Host_Unreachable, + // "Connection" was refused by remote, or closed/broken/shutdown while sending data. + Connection_Refused, + // Timed out before being able to send any data. + Timeout, + // Non-blocking socket that would need to block waiting on the remote to be able to receive the data. + Would_Block, + // Interrupted by a signal or other method of cancellation like WSACancelBlockingCall on Windows. + Interrupted, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} + +Shutdown_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Socket is invalid or not connected, or the manner given is invalid. + Invalid_Argument, + // Connection was closed/aborted/shutdown. + Connection_Closed, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} + +Socket_Info_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given. + Insufficient_Resources, + // Socket is invalid or not connected, or the manner given is invalid. + Invalid_Argument, + // The socket is valid, but unsupported by this opperation. + Unsupported_Socket, + // Connection was closed/aborted/shutdown. + Connection_Closed, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} + +Socket_Option_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Not enough space in internal tables/buffers to create a new socket, or an unsupported protocol is given. + Insufficient_Resources, + // Socket is invalid, not connected, or the connection has been closed/reset/shutdown. + Invalid_Socket, + // Unknown or unsupported option for the socket. + Invalid_Option, + // Invalid level or value. + Invalid_Value, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} + +Set_Blocking_Error :: enum i32 { + None, + // No network connection, or the network stack is not initialized. + Network_Unreachable, + // Socket is invalid. + Invalid_Argument, + + // An error unable to be categorized in above categories, `last_platform_error` may have more info. + Unknown, +} diff --git a/core/net/errors_darwin.odin b/core/net/errors_darwin.odin index 2905b44bc..255bd351f 100644 --- a/core/net/errors_darwin.odin +++ b/core/net/errors_darwin.odin @@ -20,189 +20,249 @@ package net Feoramund: FreeBSD platform code */ -import "core:c" -import "core:os" - -Create_Socket_Error :: enum c.int { - None = 0, - Family_Not_Supported_For_This_Socket = c.int(os.EAFNOSUPPORT), - No_Socket_Descriptors_Available = c.int(os.EMFILE), - No_Buffer_Space_Available = c.int(os.ENOBUFS), - No_Memory_Available_Available = c.int(os.ENOMEM), - Protocol_Unsupported_By_System = c.int(os.EPROTONOSUPPORT), - Wrong_Protocol_For_Socket = c.int(os.EPROTONOSUPPORT), - Family_And_Socket_Type_Mismatch = c.int(os.EPROTONOSUPPORT), -} - -Dial_Error :: enum c.int { - None = 0, - Port_Required = -1, // Attempted to dial an endpointing without a port being set. - - Address_In_Use = c.int(os.EADDRINUSE), - In_Progress = c.int(os.EINPROGRESS), - Cannot_Use_Any_Address = c.int(os.EADDRNOTAVAIL), - Wrong_Family_For_Socket = c.int(os.EAFNOSUPPORT), - Refused = c.int(os.ECONNREFUSED), - Is_Listening_Socket = c.int(os.EACCES), - Already_Connected = c.int(os.EISCONN), - Network_Unreachable = c.int(os.ENETUNREACH), // Device is offline - Host_Unreachable = c.int(os.EHOSTUNREACH), // Remote host cannot be reached - No_Buffer_Space_Available = c.int(os.ENOBUFS), - Not_Socket = c.int(os.ENOTSOCK), - Timeout = c.int(os.ETIMEDOUT), - - // TODO: we may need special handling for this; maybe make a socket a struct with metadata? - Would_Block = c.int(os.EWOULDBLOCK), -} - -Bind_Error :: enum c.int { - None = 0, - Privileged_Port_Without_Root = -1, // Attempted to bind to a port less than 1024 without root access. - - Address_In_Use = c.int(os.EADDRINUSE), // Another application is currently bound to this endpoint. - Given_Nonlocal_Address = c.int(os.EADDRNOTAVAIL), // The address is not a local address on this machine. - Broadcast_Disabled = c.int(os.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set. - Address_Family_Mismatch = c.int(os.EFAULT), // The address family of the address does not match that of the socket. - Already_Bound = c.int(os.EINVAL), // The socket is already bound to an address. - No_Ports_Available = c.int(os.ENOBUFS), // There are not enough ephemeral ports available. -} - -Listen_Error :: enum c.int { - None = 0, - Address_In_Use = c.int(os.EADDRINUSE), - Already_Connected = c.int(os.EISCONN), - No_Socket_Descriptors_Available = c.int(os.EMFILE), - No_Buffer_Space_Available = c.int(os.ENOBUFS), - Nonlocal_Address = c.int(os.EADDRNOTAVAIL), - Not_Socket = c.int(os.ENOTSOCK), - Listening_Not_Supported_For_This_Socket = c.int(os.EOPNOTSUPP), -} - -Accept_Error :: enum c.int { - None = 0, - // TODO(tetra): Is this error actually possible here? Or is like Linux, in which case we can remove it. - Reset = c.int(os.ECONNRESET), - Not_Listening = c.int(os.EINVAL), - No_Socket_Descriptors_Available_For_Client_Socket = c.int(os.EMFILE), - No_Buffer_Space_Available = c.int(os.ENOBUFS), - Not_Socket = c.int(os.ENOTSOCK), - Not_Connection_Oriented_Socket = c.int(os.EOPNOTSUPP), - - // TODO: we may need special handling for this; maybe make a socket a struct with metadata? - Would_Block = c.int(os.EWOULDBLOCK), -} - -TCP_Recv_Error :: enum c.int { - None = 0, - Shutdown = c.int(os.ESHUTDOWN), - Not_Connected = c.int(os.ENOTCONN), - - // TODO(tetra): Is this error actually possible here? - Connection_Broken = c.int(os.ENETRESET), - Not_Socket = c.int(os.ENOTSOCK), - Aborted = c.int(os.ECONNABORTED), - - // TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them? - Connection_Closed = c.int(os.ECONNRESET), - Offline = c.int(os.ENETDOWN), - Host_Unreachable = c.int(os.EHOSTUNREACH), - Interrupted = c.int(os.EINTR), - - // NOTE: No, really. Presumably this means something different for nonblocking sockets... - Timeout = c.int(os.EWOULDBLOCK), -} - -UDP_Recv_Error :: enum c.int { - None = 0, - Buffer_Too_Small = c.int(os.EMSGSIZE), // The buffer is too small to fit the entire message, and the message was truncated. When this happens, the rest of message is lost. - Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket. - Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor. - Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory. - Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7). - - // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. - // NOTE: No, really. Presumably this means something different for nonblocking sockets... - Timeout = c.int(os.EWOULDBLOCK), - Socket_Not_Bound = c.int(os.EINVAL), // The socket must be bound for this operation, but isn't. -} - -TCP_Send_Error :: enum c.int { - None = 0, - - Aborted = c.int(os.ECONNABORTED), - Connection_Closed = c.int(os.ECONNRESET), - Not_Connected = c.int(os.ENOTCONN), - Shutdown = c.int(os.ESHUTDOWN), - - // The send queue was full. - // This is usually a transient issue. - // - // This also shouldn't normally happen on Linux, as data is dropped if it - // doesn't fit in the send queue. - No_Buffer_Space_Available = c.int(os.ENOBUFS), - Offline = c.int(os.ENETDOWN), - Host_Unreachable = c.int(os.EHOSTUNREACH), - Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7). - - // NOTE: No, really. Presumably this means something different for nonblocking sockets... - // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. - Timeout = c.int(os.EWOULDBLOCK), - Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket. -} - -// TODO -UDP_Send_Error :: enum c.int { - None = 0, - Message_Too_Long = c.int(os.EMSGSIZE), // The message is larger than the maximum UDP packet size. No data was sent. - - // TODO: not sure what the exact circumstances for this is yet - Network_Unreachable = c.int(os.ENETUNREACH), - No_Outbound_Ports_Available = c.int(os.EAGAIN), // There are no more emphemeral outbound ports available to bind the socket to, in order to send. - - // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. - // NOTE: No, really. Presumably this means something different for nonblocking sockets... - Timeout = c.int(os.EWOULDBLOCK), - Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket. - Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor. - Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory. - Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7). - - // The send queue was full. - // This is usually a transient issue. - // - // This also shouldn't normally happen on Linux, as data is dropped if it - // doesn't fit in the send queue. - No_Buffer_Space_Available = c.int(os.ENOBUFS), - No_Memory_Available = c.int(os.ENOMEM), // No memory was available to properly manage the send queue. -} - -Shutdown_Manner :: enum c.int { - Receive = c.int(os.SHUT_RD), - Send = c.int(os.SHUT_WR), - Both = c.int(os.SHUT_RDWR), -} - -Shutdown_Error :: enum c.int { - None = 0, - Aborted = c.int(os.ECONNABORTED), - Reset = c.int(os.ECONNRESET), - Offline = c.int(os.ENETDOWN), - Not_Connected = c.int(os.ENOTCONN), - Not_Socket = c.int(os.ENOTSOCK), - Invalid_Manner = c.int(os.EINVAL), -} - -Socket_Option_Error :: enum c.int { - None = 0, - Offline = c.int(os.ENETDOWN), - Timeout_When_Keepalive_Set = c.int(os.ENETRESET), - Invalid_Option_For_Socket = c.int(os.ENOPROTOOPT), - Reset_When_Keepalive_Set = c.int(os.ENOTCONN), - Not_Socket = c.int(os.ENOTSOCK), -} - -Set_Blocking_Error :: enum c.int { - None = 0, - - // TODO: Add errors for `set_blocking` -}
\ No newline at end of file +import "core:reflect" +import "core:sys/posix" + +_last_platform_error :: proc() -> i32 { + return i32(posix.errno()) +} + +_last_platform_error_string :: proc() -> string { + description, _ := reflect.enum_name_from_value(posix.errno()) + return description +} + +_set_last_platform_error :: proc(err: i32) { + posix.errno(posix.Errno(err)) +} + +_create_socket_error :: proc() -> Create_Socket_Error { + #partial switch posix.errno() { + case .EMFILE, .ENOBUFS, .ENOMEM, .EPROTONOSUPPORT, .EISCONN, .ENFILE: + return .Insufficient_Resources + case .EAFNOSUPPORT, .EPROTOTYPE: + return .Invalid_Argument + case .EACCES: + return .Insufficient_Permissions + case: + return .Unknown + } +} + +_dial_error :: proc() -> Dial_Error { + #partial switch posix.errno() { + case .ENOBUFS: + return .Insufficient_Resources + case .EAFNOSUPPORT, .EBADF, .EFAULT, .EINVAL, .ENOTSOCK, .EPROTOTYPE, .EADDRNOTAVAIL: + return .Invalid_Argument + case .EISCONN: + return .Already_Connected + case .EALREADY: + return .Already_Connecting + case .EADDRINUSE: + return .Address_In_Use + case .ENETDOWN: + return .Network_Unreachable + case .EHOSTUNREACH: + return .Host_Unreachable + case .ECONNREFUSED: + return .Refused + case .ECONNRESET: + return .Reset + case .ETIMEDOUT: + return .Timeout + case .EINPROGRESS: + return .Would_Block + case .EINTR: + return .Interrupted + case .EACCES: + return .Broadcast_Not_Supported + case: + return .Unknown + } +} + +_bind_error :: proc() -> Bind_Error { + #partial switch posix.errno() { + case .EADDRNOTAVAIL, .EAFNOSUPPORT, .EBADF, .EDESTADDRREQ, .EFAULT, .ENOTSOCK, .EOPNOTSUPP: + return .Invalid_Argument + case .EINVAL: + return .Already_Bound + case .EACCES: + return .Insufficient_Permissions_For_Address + case .EADDRINUSE: + return .Address_In_Use + case: + return .Unknown + } +} + +_listen_error :: proc() -> Listen_Error { + #partial switch posix.errno() { + case .EBADF, .ENOTSOCK: + return .Invalid_Argument + case .EDESTADDRREQ, .EOPNOTSUPP: + return .Unsupported_Socket + case .EINVAL: + return .Already_Connected + case: + return .Unknown + } +} + +_accept_error :: proc() -> Accept_Error { + #partial switch posix.errno() { + case .EMFILE, .ENFILE, .ENOMEM: + return .Insufficient_Resources + case .EBADF, .ENOTSOCK, .EFAULT: + return .Invalid_Argument + case .EOPNOTSUPP: + return .Unsupported_Socket + case .ECONNABORTED: + return .Aborted + case .EWOULDBLOCK: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } +} + +_tcp_recv_error :: proc() -> TCP_Recv_Error { + #partial switch posix.errno() { + case .EBADF, .EFAULT, .EINVAL, .ENOTSOCK, .EOPNOTSUPP: + return .Invalid_Argument + case .ENOBUFS: + return .Insufficient_Resources + case .ENOTCONN: + return .Not_Connected + case .ECONNRESET: + return .Connection_Closed + case .ETIMEDOUT: + return .Timeout + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } +} + +_udp_recv_error :: proc() -> UDP_Recv_Error { + #partial switch posix.errno() { + case .EBADF, .EFAULT, .EINVAL, .ENOTSOCK, .EOPNOTSUPP, .EMSGSIZE: + return .Invalid_Argument + case .ENOBUFS, .ENOMEM: + return .Insufficient_Resources + case .ECONNRESET, .ENOTCONN: + return .Connection_Refused + case .ETIMEDOUT: + return .Timeout + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } +} + +_tcp_send_error :: proc() -> TCP_Send_Error { + #partial switch posix.errno() { + case .EACCES, .EBADF, .EFAULT, .EMSGSIZE, .ENOTSOCK, .EOPNOTSUPP: + return .Invalid_Argument + case .ENOBUFS: + return .Insufficient_Resources + case .ECONNRESET, .EPIPE: + return .Connection_Closed + case .ENOTCONN: + return .Not_Connected + case .EHOSTUNREACH: + return .Host_Unreachable + case .ENETDOWN, .ENETUNREACH: + return .Network_Unreachable + case .ETIMEDOUT: + return .Timeout + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } +} + +_udp_send_error :: proc() -> UDP_Send_Error { + #partial switch posix.errno() { + case .EACCES, .EBADF, .EFAULT, .EMSGSIZE, .ENOTSOCK, .EOPNOTSUPP, .EAFNOSUPPORT, .EDESTADDRREQ: + return .Invalid_Argument + case .ENOBUFS, .ENOMEM: + return .Insufficient_Resources + case .ECONNRESET, .EPIPE: + return .Connection_Refused + case .EHOSTUNREACH: + return .Host_Unreachable + case .ENETDOWN, .ENETUNREACH: + return .Network_Unreachable + case .ETIMEDOUT: + return .Timeout + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } +} + +_shutdown_error :: proc() -> Shutdown_Error { + #partial switch posix.errno() { + case .EBADF, .EINVAL, .ENOTSOCK, .ENOTCONN: + return .Invalid_Argument + case: + return .Unknown + } +} + +_socket_info_error :: proc() -> Socket_Info_Error { + #partial switch posix.errno() { + case .EBADF, .ENOTSOCK: + return .Invalid_Argument + case .ENOTCONN: + return .Network_Unreachable + case .EOPNOTSUPP: + return .Unsupported_Socket + case .EINVAL: + return .Connection_Closed + case .ENOBUFS: + return .Insufficient_Resources + case: + return .Unknown + } +} + +_socket_option_error :: proc() -> Socket_Option_Error { + #partial switch posix.errno() { + case .ENOBUFS: + return .Insufficient_Resources + case .EBADF, .ENOTSOCK, .EISCONN: + return .Invalid_Socket + case .EINVAL, .ENOPROTOOPT: + return .Invalid_Option + case .EFAULT, .EDOM: + return .Invalid_Value + case: + return .Unknown + } +} + +_set_blocking_error :: proc() -> Set_Blocking_Error { + #partial switch posix.errno() { + case .EBADF: + return .Invalid_Argument + case: + return .Unknown + } +} diff --git a/core/net/errors_freebsd.odin b/core/net/errors_freebsd.odin index 486732a95..05de41746 100644 --- a/core/net/errors_freebsd.odin +++ b/core/net/errors_freebsd.odin @@ -20,198 +20,285 @@ package net Feoramund: FreeBSD platform code */ -import "core:c" +import "core:reflect" import "core:sys/freebsd" -Create_Socket_Error :: enum c.int { - None = 0, - Access_Denied = cast(c.int)freebsd.Errno.EACCES, - Family_Not_Supported_For_This_Socket = cast(c.int)freebsd.Errno.EAFNOSUPPORT, - Full_Per_Process_Descriptor_Table = cast(c.int)freebsd.Errno.EMFILE, - Full_System_File_Table = cast(c.int)freebsd.Errno.ENFILE, - No_Buffer_Space_Available = cast(c.int)freebsd.Errno.ENOBUFS, - Insufficient_Permission = cast(c.int)freebsd.Errno.EPERM, - Protocol_Unsupported_In_Family = cast(c.int)freebsd.Errno.EPROTONOSUPPORT, - Socket_Type_Unsupported_By_Protocol = cast(c.int)freebsd.Errno.EPROTOTYPE, -} - -Dial_Error :: enum c.int { - None = 0, - Port_Required = -1, - Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, - Invalid_Namelen = cast(c.int)freebsd.Errno.EINVAL, - Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, - Address_Unavailable = cast(c.int)freebsd.Errno.EADDRNOTAVAIL, - Wrong_Family_For_Socket = cast(c.int)freebsd.Errno.EAFNOSUPPORT, - Already_Connected = cast(c.int)freebsd.Errno.EISCONN, - Timeout = cast(c.int)freebsd.Errno.ETIMEDOUT, - Refused_By_Remote_Host = cast(c.int)freebsd.Errno.ECONNREFUSED, - // `Refused` alias for `core:net` tests. - // The above default name `Refused_By_Remote_Host` is more explicit. - Refused = Refused_By_Remote_Host, - Reset_By_Remote_Host = cast(c.int)freebsd.Errno.ECONNRESET, - Network_Unreachable = cast(c.int)freebsd.Errno.ENETUNREACH, - Host_Unreachable = cast(c.int)freebsd.Errno.EHOSTUNREACH, - Address_In_Use = cast(c.int)freebsd.Errno.EADDRINUSE, - Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT, - In_Progress = cast(c.int)freebsd.Errno.EINPROGRESS, - Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR, - Previous_Attempt_Incomplete = cast(c.int)freebsd.Errno.EALREADY, - Broadcast_Unavailable = cast(c.int)freebsd.Errno.EACCES, - Auto_Port_Unavailable = cast(c.int)freebsd.Errno.EAGAIN, - - // NOTE: There are additional connect() error possibilities, but they are - // strictly for addresses in the UNIX domain. -} - -Bind_Error :: enum c.int { - None = 0, - Kernel_Resources_Unavailable = cast(c.int)freebsd.Errno.EAGAIN, - Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, - - // NOTE: bind() can also return EINVAL if the underlying `addrlen` is an - // invalid length for the address family. This shouldn't happen for the net - // package, but it's worth noting. - Already_Bound = cast(c.int)freebsd.Errno.EINVAL, - Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, - Given_Nonlocal_Address = cast(c.int)freebsd.Errno.EADDRNOTAVAIL, - Address_In_Use = cast(c.int)freebsd.Errno.EADDRINUSE, - Address_Family_Mismatch = cast(c.int)freebsd.Errno.EAFNOSUPPORT, - Protected_Address = cast(c.int)freebsd.Errno.EACCES, - Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT, - - // NOTE: There are additional bind() error possibilities, but they are - // strictly for addresses in the UNIX domain. -} - -Listen_Error :: enum c.int { - None = 0, - Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, - Socket_Not_Bound = cast(c.int)freebsd.Errno.EDESTADDRREQ, - Already_Connected = cast(c.int)freebsd.Errno.EINVAL, - Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, - Listening_Not_Supported_For_This_Socket = cast(c.int)freebsd.Errno.EOPNOTSUPP, -} - -Accept_Error :: enum c.int { - None = 0, - Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, - Interrupted = cast(c.int)freebsd.Errno.EINTR, - Full_Per_Process_Descriptor_Table = cast(c.int)freebsd.Errno.EMFILE, - Full_System_File_Table = cast(c.int)freebsd.Errno.ENFILE, - Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, - Listen_Not_Called_On_Socket_Yet = cast(c.int)freebsd.Errno.EINVAL, - Address_Not_Writable = cast(c.int)freebsd.Errno.EFAULT, - - // NOTE: This is the same as EWOULDBLOCK. - No_Connections_Available = cast(c.int)freebsd.Errno.EAGAIN, - // `Would_Block` alias for `core:net` tests. - Would_Block = cast(c.int)freebsd.Errno.EAGAIN, - - New_Connection_Aborted = cast(c.int)freebsd.Errno.ECONNABORTED, -} - -TCP_Recv_Error :: enum c.int { - None = 0, - Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, - Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET, - Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN, - Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, - - // NOTE(Feoramund): The next two errors are only relevant for recvmsg(), - // but I'm including them for completeness's sake. - Full_Table_And_Pending_Data = cast(c.int)freebsd.Errno.EMFILE, - Invalid_Message_Size = cast(c.int)freebsd.Errno.EMSGSIZE, - - Timeout = cast(c.int)freebsd.Errno.EAGAIN, - Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR, - Buffer_Pointer_Outside_Address_Space = cast(c.int)freebsd.Errno.EFAULT, -} - -UDP_Recv_Error :: enum c.int { - None = 0, - Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, - Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET, - Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN, - Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, - - // NOTE(Feoramund): The next two errors are only relevant for recvmsg(), - // but I'm including them for completeness's sake. - Full_Table_And_Data_Discarded = cast(c.int)freebsd.Errno.EMFILE, - Invalid_Message_Size = cast(c.int)freebsd.Errno.EMSGSIZE, - - Timeout = cast(c.int)freebsd.Errno.EAGAIN, - Interrupted_By_Signal = cast(c.int)freebsd.Errno.EINTR, - Buffer_Pointer_Outside_Address_Space = cast(c.int)freebsd.Errno.EFAULT, -} - -TCP_Send_Error :: enum c.int { - None = 0, - Connection_Closed = cast(c.int)freebsd.Errno.ECONNRESET, - Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, - Broadcast_Status_Mismatch = cast(c.int)freebsd.Errno.EACCES, - Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN, - Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, - Argument_In_Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT, - - Message_Size_Breaks_Atomicity = cast(c.int)freebsd.Errno.EMSGSIZE, - - /* The socket is marked non-blocking, or MSG_DONTWAIT is - specified, and the requested operation would block. */ - Would_Block = cast(c.int)freebsd.Errno.EAGAIN, - - /* NOTE: This error arises for two distinct reasons: - - 1. The system was unable to allocate an internal buffer. - The operation may succeed when buffers become available. - - 2. The output queue for a network interface was full. - This generally indicates that the interface has stopped - sending, but may be caused by transient congestion. - */ - No_Buffer_Space_Available = cast(c.int)freebsd.Errno.ENOBUFS, - - Host_Unreachable = cast(c.int)freebsd.Errno.EHOSTUNREACH, - Already_Connected = cast(c.int)freebsd.Errno.EISCONN, - ICMP_Unreachable = cast(c.int)freebsd.Errno.ECONNREFUSED, - Host_Down = cast(c.int)freebsd.Errno.EHOSTDOWN, - Network_Down = cast(c.int)freebsd.Errno.ENETDOWN, - Jailed_Socket_Tried_To_Escape = cast(c.int)freebsd.Errno.EADDRNOTAVAIL, - Cannot_Send_More_Data = cast(c.int)freebsd.Errno.EPIPE, -} - -// NOTE(Feoramund): The same as TCP errors go, as far as I'm aware. -UDP_Send_Error :: distinct TCP_Send_Error - -Shutdown_Manner :: enum c.int { - Receive = cast(c.int)freebsd.Shutdown_Method.RD, - Send = cast(c.int)freebsd.Shutdown_Method.WR, - Both = cast(c.int)freebsd.Shutdown_Method.RDWR, -} - -Shutdown_Error :: enum c.int { - None = 0, - Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, - Invalid_Manner = cast(c.int)freebsd.Errno.EINVAL, - Not_Connected = cast(c.int)freebsd.Errno.ENOTCONN, - Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, -} - -Socket_Option_Error :: enum c.int { - None = 0, - Value_Out_Of_Range = -1, - Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, - Not_Socket = cast(c.int)freebsd.Errno.ENOTSOCK, - Unknown_Option_For_Level = cast(c.int)freebsd.Errno.ENOPROTOOPT, - Argument_In_Invalid_Address_Space = cast(c.int)freebsd.Errno.EFAULT, - // This error can arise for many different reasons. - Invalid_Value = cast(c.int)freebsd.Errno.EINVAL, - System_Memory_Allocation_Failed = cast(c.int)freebsd.Errno.ENOMEM, - Insufficient_System_Resources = cast(c.int)freebsd.Errno.ENOBUFS, -} - -Set_Blocking_Error :: enum c.int { - None = 0, - Not_Descriptor = cast(c.int)freebsd.Errno.EBADF, - Wrong_Descriptor = cast(c.int)freebsd.Errno.ENOTTY, +@(private="file", thread_local) +_last_error: freebsd.Errno + +_last_platform_error :: proc() -> i32 { + return i32(_last_error) +} + +_last_platform_error_string :: proc() -> string { + description, _ := reflect.enum_name_from_value(_last_error) + return description +} + +_set_last_platform_error :: proc(err: i32) { + _last_error = freebsd.Errno(err) +} + +_create_socket_error :: proc(errno: freebsd.Errno) -> Create_Socket_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EMFILE, .ENFILE, .ENOBUFS, .EPROTONOSUPPORT: + return .Insufficient_Resources + case .EAFNOSUPPORT, .EPROTOTYPE: + return .Invalid_Argument + case .EACCES, .EPERM: + return .Insufficient_Permissions + case: + return .Unknown + } +} + +_dial_error :: proc(errno: freebsd.Errno) -> Dial_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .EINVAL, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT, .EAGAIN: + return .Invalid_Argument + case .EISCONN: + return .Already_Connected + case .EALREADY: + return .Already_Connecting + case .EADDRINUSE: + return .Address_In_Use + case .ENETUNREACH: + return .Network_Unreachable + case .EHOSTUNREACH: + return .Host_Unreachable + case .ECONNREFUSED: + return .Refused + case .ECONNRESET: + return .Reset + case .ETIMEDOUT: + return .Timeout + case .EINPROGRESS: + return .Would_Block + case .EINTR: + return .Interrupted + case .EACCES: + return .Broadcast_Not_Supported + case: + return .Unknown + } +} + +_bind_error :: proc(errno: freebsd.Errno) -> Bind_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EAGAIN, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT: + return .Insufficient_Resources + case .EBADF: + return .Invalid_Argument + case .EINVAL: + return .Already_Bound + case .EACCES: + return .Insufficient_Permissions_For_Address + case .EADDRINUSE: + return .Address_In_Use + case: + return .Unknown + } +} + +_listen_error :: proc(errno: freebsd.Errno) -> Listen_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .ENOTSOCK: + return .Invalid_Argument + case .EDESTADDRREQ, .EOPNOTSUPP: + return .Unsupported_Socket + case .EINVAL: + return .Already_Connected + case: + return .Unknown + } +} + +_accept_error :: proc(errno: freebsd.Errno) -> Accept_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EMFILE, .ENFILE: + return .Insufficient_Resources + case .EBADF, .ENOTSOCK, .EFAULT: + return .Invalid_Argument + case .EINVAL: + return .Not_Listening + case .ECONNABORTED: + return .Aborted + case .EWOULDBLOCK: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } +} + +_tcp_recv_error :: proc(errno: freebsd.Errno) -> TCP_Recv_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .ENOTSOCK, .EFAULT: + return .Invalid_Argument + case .ENOTCONN: + return .Not_Connected + case .ECONNRESET: + return .Connection_Closed + case .ETIMEDOUT: + return .Timeout + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } +} + +_udp_recv_error :: proc(errno: freebsd.Errno) -> UDP_Recv_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .ENOTSOCK, .EFAULT: + return .Invalid_Argument + case .ECONNRESET, .ENOTCONN: + return .Connection_Refused + case .ETIMEDOUT: + return .Timeout + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } +} + +_tcp_send_error :: proc(errno: freebsd.Errno) -> TCP_Send_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE: + return .Invalid_Argument + case .ENOBUFS: + return .Insufficient_Resources + case .ECONNRESET, .EPIPE: + return .Connection_Closed + case .ENOTCONN: + return .Not_Connected + case .EHOSTUNREACH: + return .Host_Unreachable + case .EHOSTDOWN: + return .Host_Unreachable + case .ENETDOWN: + return .Network_Unreachable + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } +} + +_udp_send_error :: proc(errno: freebsd.Errno) -> UDP_Send_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE: + return .Invalid_Argument + case .ENOBUFS: + return .Insufficient_Resources + case .ECONNRESET, .EPIPE: + return .Connection_Refused + case .EHOSTUNREACH: + return .Host_Unreachable + case .EHOSTDOWN: + return .Host_Unreachable + case .ENETDOWN: + return .Network_Unreachable + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } +} + +_shutdown_error :: proc(errno: freebsd.Errno) -> Shutdown_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .EINVAL, .ENOTSOCK, .ENOTCONN: + return .Invalid_Argument + case: + return .Unknown + } +} + +_socket_info_error :: proc(errno: freebsd.Errno) -> Socket_Info_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .ENOTSOCK, .EINVAL, .EFAULT: + return .Invalid_Argument + case .ENOTCONN: + return .Network_Unreachable + case .ECONNRESET: + return .Connection_Closed + case .ENOBUFS: + return .Insufficient_Resources + case: + return .Unknown + } +} + +_socket_option_error :: proc(errno: freebsd.Errno) -> Socket_Option_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .ENOMEM, .ENOBUFS: + return .Insufficient_Resources + case .EBADF, .ENOTSOCK: + return .Invalid_Socket + case .ENOPROTOOPT: + return .Invalid_Option + case .EINVAL, .EFAULT: + return .Invalid_Value + case: + return .Unknown + } +} + +_set_blocking_error :: proc(errno: freebsd.Errno) -> Set_Blocking_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .ENOTTY: + return .Invalid_Argument + case: + return .Unknown + } } diff --git a/core/net/errors_linux.odin b/core/net/errors_linux.odin index 3cd51e6fd..258560595 100644 --- a/core/net/errors_linux.odin +++ b/core/net/errors_linux.odin @@ -21,181 +21,285 @@ package net Feoramund: FreeBSD platform code */ -import "core:c" +import "core:reflect" import "core:sys/linux" -Create_Socket_Error :: enum c.int { - None = 0, - Family_Not_Supported_For_This_Socket = c.int(linux.Errno.EAFNOSUPPORT), - No_Socket_Descriptors_Available = c.int(linux.Errno.EMFILE), - No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS), - No_Memory_Available_Available = c.int(linux.Errno.ENOMEM), - Protocol_Unsupported_By_System = c.int(linux.Errno.EPROTONOSUPPORT), - Wrong_Protocol_For_Socket = c.int(linux.Errno.EPROTONOSUPPORT), - Family_And_Socket_Type_Mismatch = c.int(linux.Errno.EPROTONOSUPPORT), -} - -Dial_Error :: enum c.int { - None = 0, - Port_Required = -1, - - Address_In_Use = c.int(linux.Errno.EADDRINUSE), - In_Progress = c.int(linux.Errno.EINPROGRESS), - Cannot_Use_Any_Address = c.int(linux.Errno.EADDRNOTAVAIL), - Wrong_Family_For_Socket = c.int(linux.Errno.EAFNOSUPPORT), - Refused = c.int(linux.Errno.ECONNREFUSED), - Is_Listening_Socket = c.int(linux.Errno.EACCES), - Already_Connected = c.int(linux.Errno.EISCONN), - Network_Unreachable = c.int(linux.Errno.ENETUNREACH), // Device is offline - Host_Unreachable = c.int(linux.Errno.EHOSTUNREACH), // Remote host cannot be reached - No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS), - Not_Socket = c.int(linux.Errno.ENOTSOCK), - Timeout = c.int(linux.Errno.ETIMEDOUT), - - // TODO: we may need special handling for this; maybe make a socket a struct with metadata? - Would_Block = c.int(linux.Errno.EWOULDBLOCK), -} - -Bind_Error :: enum c.int { - None = 0, - Address_In_Use = c.int(linux.Errno.EADDRINUSE), // Another application is currently bound to this endpoint. - Given_Nonlocal_Address = c.int(linux.Errno.EADDRNOTAVAIL), // The address is not a local address on this machine. - Broadcast_Disabled = c.int(linux.Errno.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set. - Address_Family_Mismatch = c.int(linux.Errno.EFAULT), // The address family of the address does not match that of the socket. - Already_Bound = c.int(linux.Errno.EINVAL), // The socket is already bound to an address. - No_Ports_Available = c.int(linux.Errno.ENOBUFS), // There are not enough ephemeral ports available. -} - -Listen_Error :: enum c.int { - None = 0, - Address_In_Use = c.int(linux.Errno.EADDRINUSE), - Already_Connected = c.int(linux.Errno.EISCONN), - No_Socket_Descriptors_Available = c.int(linux.Errno.EMFILE), - No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS), - Nonlocal_Address = c.int(linux.Errno.EADDRNOTAVAIL), - Not_Socket = c.int(linux.Errno.ENOTSOCK), - Listening_Not_Supported_For_This_Socket = c.int(linux.Errno.EOPNOTSUPP), -} - -Accept_Error :: enum c.int { - None = 0, - Not_Listening = c.int(linux.Errno.EINVAL), - No_Socket_Descriptors_Available_For_Client_Socket = c.int(linux.Errno.EMFILE), - No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS), - Not_Socket = c.int(linux.Errno.ENOTSOCK), - Not_Connection_Oriented_Socket = c.int(linux.Errno.EOPNOTSUPP), - - // TODO: we may need special handling for this; maybe make a socket a struct with metadata? - Would_Block = c.int(linux.Errno.EWOULDBLOCK), -} - -TCP_Recv_Error :: enum c.int { - None = 0, - Shutdown = c.int(linux.Errno.ESHUTDOWN), - Not_Connected = c.int(linux.Errno.ENOTCONN), - Connection_Broken = c.int(linux.Errno.ENETRESET), - Not_Socket = c.int(linux.Errno.ENOTSOCK), - Aborted = c.int(linux.Errno.ECONNABORTED), - - // TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them? - Connection_Closed = c.int(linux.Errno.ECONNRESET), - Offline = c.int(linux.Errno.ENETDOWN), - Host_Unreachable = c.int(linux.Errno.EHOSTUNREACH), - Interrupted = c.int(linux.Errno.EINTR), - Timeout = c.int(linux.Errno.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets... -} - -UDP_Recv_Error :: enum c.int { - None = 0, - - Buffer_Too_Small = c.int(linux.Errno.EMSGSIZE), // The buffer is too small to fit the entire message, and the message was truncated. When this happens, the rest of message is lost. - Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket. - Not_Descriptor = c.int(linux.Errno.EBADF), // The so-called socket is, in fact, not even a valid descriptor. - Bad_Buffer = c.int(linux.Errno.EFAULT), // The buffer did not point to a valid location in memory. - Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7). - - // The send timeout duration passed before all data was received. See Socket_Option.Receive_Timeout. - // NOTE: No, really. Presumably this means something different for nonblocking sockets... - Timeout = c.int(linux.Errno.EWOULDBLOCK), - Socket_Not_Bound = c.int(linux.Errno.EINVAL), // The socket must be bound for this operation, but isn't. -} - -TCP_Send_Error :: enum c.int { - None = 0, - Aborted = c.int(linux.Errno.ECONNABORTED), - Connection_Closed = c.int(linux.Errno.ECONNRESET), - Not_Connected = c.int(linux.Errno.ENOTCONN), - Shutdown = c.int(linux.Errno.ESHUTDOWN), - - // The send queue was full. - // This is usually a transient issue. - // - // This also shouldn't normally happen on Linux, as data is dropped if it - // doesn't fit in the send queue. - No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS), - Offline = c.int(linux.Errno.ENETDOWN), - Host_Unreachable = c.int(linux.Errno.EHOSTUNREACH), - Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7). - Timeout = c.int(linux.Errno.EWOULDBLOCK), // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. - Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket. -} - -// TODO -UDP_Send_Error :: enum c.int { - None = 0, - Message_Too_Long = c.int(linux.Errno.EMSGSIZE), // The message is larger than the maximum UDP packet size. No data was sent. - - // TODO: not sure what the exact circumstances for this is yet - Network_Unreachable = c.int(linux.Errno.ENETUNREACH), - No_Outbound_Ports_Available = c.int(linux.Errno.EAGAIN), // There are no more emphemeral outbound ports available to bind the socket to, in order to send. - - // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout. - // NOTE: No, really. Presumably this means something different for nonblocking sockets... - Timeout = c.int(linux.Errno.EWOULDBLOCK), - Not_Socket = c.int(linux.Errno.ENOTSOCK), // The so-called socket is not an open socket. - Not_Descriptor = c.int(linux.Errno.EBADF), // The so-called socket is, in fact, not even a valid descriptor. - Bad_Buffer = c.int(linux.Errno.EFAULT), // The buffer did not point to a valid location in memory. - Interrupted = c.int(linux.Errno.EINTR), // A signal occurred before any data was transmitted. See signal(7). - - // The send queue was full. - // This is usually a transient issue. - // - // This also shouldn't normally happen on Linux, as data is dropped if it - // doesn't fit in the send queue. - No_Buffer_Space_Available = c.int(linux.Errno.ENOBUFS), - No_Memory_Available = c.int(linux.Errno.ENOMEM), // No memory was available to properly manage the send queue. -} - -// TODO(flysand): slight regression -Shutdown_Manner :: enum c.int { - Receive = c.int(linux.Shutdown_How.RD), - Send = c.int(linux.Shutdown_How.WR), - Both = c.int(linux.Shutdown_How.RDWR), -} - -Shutdown_Error :: enum c.int { - None = 0, - Aborted = c.int(linux.Errno.ECONNABORTED), - Reset = c.int(linux.Errno.ECONNRESET), - Offline = c.int(linux.Errno.ENETDOWN), - Not_Connected = c.int(linux.Errno.ENOTCONN), - Not_Socket = c.int(linux.Errno.ENOTSOCK), - Invalid_Manner = c.int(linux.Errno.EINVAL), -} - -Socket_Option_Error :: enum c.int { - None = 0, - Offline = c.int(linux.Errno.ENETDOWN), - Timeout_When_Keepalive_Set = c.int(linux.Errno.ENETRESET), - Invalid_Option_For_Socket = c.int(linux.Errno.ENOPROTOOPT), - Reset_When_Keepalive_Set = c.int(linux.Errno.ENOTCONN), - Not_Socket = c.int(linux.Errno.ENOTSOCK), -} - -Set_Blocking_Error :: enum c.int { - None = 0, - - // TODO: add errors occuring on followig calls: - // flags, _ := linux.Errno.fcntl(sd, linux.Errno.F_GETFL, 0) - // linux.Errno.fcntl(sd, linux.Errno.F_SETFL, flags | int(linux.Errno.O_NONBLOCK)) -}
\ No newline at end of file +@(private="file", thread_local) +_last_error: linux.Errno + +_last_platform_error :: proc() -> i32 { + return i32(_last_error) +} + +_last_platform_error_string :: proc() -> string { + description, _ := reflect.enum_name_from_value(_last_error) + return description +} + +_set_last_platform_error :: proc(err: i32) { + _last_error = linux.Errno(err) +} + +_create_socket_error :: proc(errno: linux.Errno) -> Create_Socket_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EMFILE, .ENFILE, .ENOBUFS, .EPROTONOSUPPORT: + return .Insufficient_Resources + case .EAFNOSUPPORT, .EPROTOTYPE: + return .Invalid_Argument + case .EACCES, .EPERM: + return .Insufficient_Permissions + case: + return .Unknown + } +} + +_dial_error :: proc(errno: linux.Errno) -> Dial_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EAGAIN: + return .Insufficient_Resources + case .EBADF, .EINVAL, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT: + return .Invalid_Argument + case .EISCONN: + return .Already_Connected + case .EALREADY: + return .Already_Connecting + case .EADDRINUSE: + return .Address_In_Use + case .ENETUNREACH: + return .Network_Unreachable + case .EHOSTUNREACH: + return .Host_Unreachable + case .ECONNREFUSED: + return .Refused + case .ECONNRESET: + return .Reset + case .ETIMEDOUT: + return .Timeout + case .EINPROGRESS: + return .Would_Block + case .EINTR: + return .Interrupted + case .EACCES: + return .Broadcast_Not_Supported + case: + return .Unknown + } +} + +_bind_error :: proc(errno: linux.Errno) -> Bind_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EAGAIN, .ENOTSOCK, .EADDRNOTAVAIL, .EAFNOSUPPORT, .EFAULT: + return .Insufficient_Resources + case .EINVAL: + return .Already_Bound + case .EBADF: + return .Invalid_Argument + case .EACCES: + return .Insufficient_Permissions_For_Address + case .EADDRINUSE: + return .Address_In_Use + case: + return .Unknown + } +} + +_listen_error :: proc(errno: linux.Errno) -> Listen_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .ENOTSOCK: + return .Invalid_Argument + case .EDESTADDRREQ, .EOPNOTSUPP: + return .Unsupported_Socket + case .EINVAL: + return .Already_Connected + case: + return .Unknown + } +} + +_accept_error :: proc(errno: linux.Errno) -> Accept_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EMFILE, .ENFILE, .ENOBUFS, .ENOMEM: + return .Insufficient_Resources + case .EBADF, .ENOTSOCK, .EFAULT: + return .Invalid_Argument + case .EINVAL: + return .Not_Listening + case .ECONNABORTED: + return .Aborted + case .EWOULDBLOCK: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } +} + +_tcp_recv_error :: proc(errno: linux.Errno) -> TCP_Recv_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .ENOTSOCK, .EFAULT: + return .Invalid_Argument + case .ENOTCONN: + return .Not_Connected + case .ECONNREFUSED, .ECONNRESET: + return .Connection_Closed + case .ETIMEDOUT: + return .Timeout + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } +} + +_udp_recv_error :: proc(errno: linux.Errno) -> UDP_Recv_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .ENOTSOCK, .EFAULT: + return .Invalid_Argument + case .ECONNREFUSED, .ENOTCONN, .ECONNRESET: + return .Connection_Refused + case .ETIMEDOUT: + return .Timeout + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } +} + +_tcp_send_error :: proc(errno: linux.Errno) -> TCP_Send_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE, .EDESTADDRREQ, .EINVAL, .EISCONN, .EOPNOTSUPP: + return .Invalid_Argument + case .ENOBUFS, .ENOMEM: + return .Insufficient_Resources + case .ECONNRESET, .EPIPE: + return .Connection_Closed + case .ENOTCONN: + return .Not_Connected + case .EHOSTUNREACH: + return .Host_Unreachable + case .EHOSTDOWN: + return .Host_Unreachable + case .ENETDOWN: + return .Network_Unreachable + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } +} + +_udp_send_error :: proc(errno: linux.Errno) -> UDP_Send_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .EACCES, .ENOTSOCK, .EFAULT, .EMSGSIZE, .EDESTADDRREQ, .EINVAL, .EISCONN, .EOPNOTSUPP: + return .Invalid_Argument + case .ENOBUFS, .ENOMEM: + return .Insufficient_Resources + case .ECONNRESET, .EPIPE: + return .Connection_Refused + case .EHOSTUNREACH: + return .Host_Unreachable + case .EHOSTDOWN: + return .Host_Unreachable + case .ENETDOWN: + return .Network_Unreachable + case .EAGAIN: + return .Would_Block + case .EINTR: + return .Interrupted + case: + return .Unknown + } +} + +_shutdown_error :: proc(errno: linux.Errno) -> Shutdown_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .EINVAL, .ENOTSOCK, .ENOTCONN: + return .Invalid_Argument + case: + return .Unknown + } +} + +_socket_info_error :: proc(errno: linux.Errno) -> Socket_Info_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF, .ENOTSOCK, .EFAULT, .EINVAL: + return .Invalid_Argument + case .ENOTCONN: + return .Network_Unreachable + case .ENOBUFS: + return .Insufficient_Resources + case: + return .Unknown + } +} + +_socket_option_error :: proc(errno: linux.Errno) -> Socket_Option_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .ENOMEM, .ENOBUFS: + return .Insufficient_Resources + case .EBADF, .ENOTSOCK: + return .Invalid_Socket + case .ENOPROTOOPT, .EINVAL: + return .Invalid_Option + case .EFAULT, .EDOM: + return .Invalid_Value + case: + return .Unknown + } +} + +_set_blocking_error :: proc(errno: linux.Errno) -> Set_Blocking_Error { + assert(errno != nil) + _last_error = errno + + #partial switch errno { + case .EBADF: + return .Invalid_Argument + case: + return .Unknown + } +} diff --git a/core/net/errors_others.odin b/core/net/errors_others.odin new file mode 100644 index 000000000..b80ead79c --- /dev/null +++ b/core/net/errors_others.odin @@ -0,0 +1,27 @@ +#+build !darwin +#+build !linux +#+build !freebsd +#+build !windows +package net + +@(private="file", thread_local) +_last_error: i32 + +_last_platform_error :: proc() -> i32 { + return _last_error +} + +_last_platform_error_string :: proc() -> string { + return "" +} + +_set_last_platform_error :: proc(err: i32) { + _last_error = err +} + +Parse_Endpoint_Error :: enum u32 { + None = 0, + Bad_Port = 1, + Bad_Address, + Bad_Hostname, +}
\ No newline at end of file diff --git a/core/net/errors_windows.odin b/core/net/errors_windows.odin index f41bcf888..8e0e4cda3 100644 --- a/core/net/errors_windows.odin +++ b/core/net/errors_windows.odin @@ -20,250 +20,253 @@ package net Feoramund: FreeBSD platform code */ -import "core:c" +import "core:reflect" import win "core:sys/windows" -Create_Socket_Error :: enum c.int { - None = 0, - Network_Subsystem_Failure = win.WSAENETDOWN, - Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT, - No_Socket_Descriptors_Available = win.WSAEMFILE, - No_Buffer_Space_Available = win.WSAENOBUFS, - Protocol_Unsupported_By_System = win.WSAEPROTONOSUPPORT, - Wrong_Protocol_For_Socket = win.WSAEPROTOTYPE, - Family_And_Socket_Type_Mismatch = win.WSAESOCKTNOSUPPORT, +_last_platform_error :: proc() -> i32 { + return i32(win.WSAGetLastError()) } -Dial_Error :: enum c.int { - None = 0, - Port_Required = -1, - Address_In_Use = win.WSAEADDRINUSE, - In_Progress = win.WSAEALREADY, - Cannot_Use_Any_Address = win.WSAEADDRNOTAVAIL, - Wrong_Family_For_Socket = win.WSAEAFNOSUPPORT, - Refused = win.WSAECONNREFUSED, - Is_Listening_Socket = win.WSAEINVAL, - Already_Connected = win.WSAEISCONN, - Network_Unreachable = win.WSAENETUNREACH, // Device is offline - Host_Unreachable = win.WSAEHOSTUNREACH, // Remote host cannot be reached - No_Buffer_Space_Available = win.WSAENOBUFS, - Not_Socket = win.WSAENOTSOCK, - Timeout = win.WSAETIMEDOUT, - Would_Block = win.WSAEWOULDBLOCK, // TODO: we may need special handling for this; maybe make a socket a struct with metadata? +_last_platform_error_string :: proc() -> string { + description, _ := reflect.enum_name_from_value(win.System_Error(win.WSAGetLastError())) + return description } -Bind_Error :: enum c.int { - None = 0, - Address_In_Use = win.WSAEADDRINUSE, // Another application is currently bound to this endpoint. - Given_Nonlocal_Address = win.WSAEADDRNOTAVAIL, // The address is not a local address on this machine. - Broadcast_Disabled = win.WSAEACCES, // To bind a UDP socket to the broadcast address, the appropriate socket option must be set. - Address_Family_Mismatch = win.WSAEFAULT, // The address family of the address does not match that of the socket. - Already_Bound = win.WSAEINVAL, // The socket is already bound to an address. - No_Ports_Available = win.WSAENOBUFS, // There are not enough ephemeral ports available. +_set_last_platform_error :: proc(err: i32) { + win.WSASetLastError(err) } -Listen_Error :: enum c.int { - None = 0, - Address_In_Use = win.WSAEADDRINUSE, - Already_Connected = win.WSAEISCONN, - No_Socket_Descriptors_Available = win.WSAEMFILE, - No_Buffer_Space_Available = win.WSAENOBUFS, - Nonlocal_Address = win.WSAEADDRNOTAVAIL, - Not_Socket = win.WSAENOTSOCK, - Listening_Not_Supported_For_This_Socket = win.WSAEOPNOTSUPP, +_create_socket_error :: proc() -> Create_Socket_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSANOTINITIALISED, .WSAENETDOWN, .WSAEINVALIDPROVIDER, .WSAEINVALIDPROCTABLE, .WSAEPROVIDERFAILEDINIT: + return .Network_Unreachable + case .WSAEAFNOSUPPORT, .WSAEINPROGRESS, .WSAEINVAL, .WSAEPROTOTYPE, .WSAESOCKTNOSUPPORT: + return .Invalid_Argument + case .WSAEMFILE, .WSAENOBUFS, .WSAEPROTONOSUPPORT: + return .Insufficient_Resources + case: + return .Unknown + } } -Accept_Error :: enum c.int { - None = 0, - Not_Listening = win.WSAEINVAL, - No_Socket_Descriptors_Available_For_Client_Socket = win.WSAEMFILE, - No_Buffer_Space_Available = win.WSAENOBUFS, - Not_Socket = win.WSAENOTSOCK, - Not_Connection_Oriented_Socket = win.WSAEOPNOTSUPP, - - // TODO: we may need special handling for this; maybe make a socket a struct with metadata? - Would_Block = win.WSAEWOULDBLOCK, +_dial_error :: proc() -> Dial_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSANOTINITIALISED, .WSAENETDOWN: + return .Network_Unreachable + case .WSAEADDRINUSE: + return .Address_In_Use + case .WSAEINTR: + return .Interrupted + case .WSAEWOULDBLOCK: + return .Would_Block + case .WSAEALREADY: + return .Already_Connecting + case .WSAEADDRNOTAVAIL, .WSAEAFNOSUPPORT, .WSAEFAULT, .WSAENOTSOCK, .WSAEINPROGRESS, .WSAEINVAL: + return .Invalid_Argument + case .WSAECONNREFUSED: + return .Refused + case .WSAEISCONN: + return .Already_Connected + case .WSAEHOSTUNREACH: + return .Host_Unreachable + case .WSAENOBUFS: + return .Insufficient_Resources + case .WSAETIMEDOUT: + return .Timeout + case .WSAEACCES: + return .Broadcast_Not_Supported + case: + return .Unknown + } } -TCP_Recv_Error :: enum c.int { - None = 0, - Network_Subsystem_Failure = win.WSAENETDOWN, - Not_Connected = win.WSAENOTCONN, - Bad_Buffer = win.WSAEFAULT, - Keepalive_Failure = win.WSAENETRESET, - Not_Socket = win.WSAENOTSOCK, - Shutdown = win.WSAESHUTDOWN, - Would_Block = win.WSAEWOULDBLOCK, - Aborted = win.WSAECONNABORTED, - Timeout = win.WSAETIMEDOUT, - - // TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them? - Connection_Closed = win.WSAECONNRESET, - - // TODO: verify can actually happen - Host_Unreachable = win.WSAEHOSTUNREACH, +_bind_error :: proc() -> Bind_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSANOTINITIALISED, .WSAENETDOWN: + return .Network_Unreachable + case .WSAEADDRINUSE: + return .Address_In_Use + case .WSAEADDRNOTAVAIL, .WSAEFAULT, .WSAEINPROGRESS, .WSAEACCES, .WSAEINVAL, .WSAENOTSOCK: + return .Invalid_Argument + case: + return .Unknown + } } -UDP_Recv_Error :: enum c.int { - None = 0, - Network_Subsystem_Failure = win.WSAENETDOWN, - Aborted = win.WSAECONNABORTED, - Buffer_Too_Small = win.WSAEMSGSIZE, // The buffer is too small to fit the entire message, and the message was truncated. When this happens, the rest of message is lost. - Remote_Not_Listening = win.WSAECONNRESET, // The machine at the remote endpoint doesn't have the given port open to receiving UDP data. - Shutdown = win.WSAESHUTDOWN, - Broadcast_Disabled = win.WSAEACCES, // A broadcast address was specified, but the .Broadcast socket option isn't set. - Bad_Buffer = win.WSAEFAULT, - No_Buffer_Space_Available = win.WSAENOBUFS, - Not_Socket = win.WSAENOTSOCK, // The socket is not valid socket handle. - Would_Block = win.WSAEWOULDBLOCK, - Host_Unreachable = win.WSAEHOSTUNREACH, // The remote host cannot be reached from this host at this time. - Offline = win.WSAENETUNREACH, // The network cannot be reached from this host at this time. - Timeout = win.WSAETIMEDOUT, - - // TODO: can this actually happen? The socket isn't bound; an unknown flag specified; or MSG_OOB specified with SO_OOBINLINE enabled. - Incorrectly_Configured = win.WSAEINVAL, - TTL_Expired = win.WSAENETRESET, // The message took more hops than was allowed (the Time To Live) to reach the remote endpoint. +_listen_error :: proc() -> Listen_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSANOTINITIALISED, .WSAENETDOWN: + return .Network_Unreachable + case .WSAEMFILE, .WSAENOBUFS: + return .Insufficient_Resources + case .WSAEADDRINUSE: + return .Address_In_Use + case .WSAEINPROGRESS, .WSAENOTSOCK: + return .Invalid_Argument + case .WSAEISCONN: + return .Already_Connected + case .WSAEOPNOTSUPP, .WSAEINVAL: + return .Unsupported_Socket + case: + return .Unknown + } } -// TODO: consider merging some errors to make handling them easier -// TODO: verify once more what errors to actually expose -TCP_Send_Error :: enum c.int { - None = 0, - - Aborted = win.WSAECONNABORTED, - Not_Connected = win.WSAENOTCONN, - Shutdown = win.WSAESHUTDOWN, - Connection_Closed = win.WSAECONNRESET, - No_Buffer_Space_Available = win.WSAENOBUFS, - Network_Subsystem_Failure = win.WSAENETDOWN, - Host_Unreachable = win.WSAEHOSTUNREACH, - Would_Block = win.WSAEWOULDBLOCK, - - // TODO: verify possible, as not mentioned in docs - Offline = win.WSAENETUNREACH, - Timeout = win.WSAETIMEDOUT, - - // A broadcast address was specified, but the .Broadcast socket option isn't set. - Broadcast_Disabled = win.WSAEACCES, - Bad_Buffer = win.WSAEFAULT, - - // Connection is broken due to keepalive activity detecting a failure during the operation. - Keepalive_Failure = win.WSAENETRESET, // TODO: not functionally different from Reset; merge? - Not_Socket = win.WSAENOTSOCK, // The so-called socket is not an open socket. +_accept_error :: proc() -> Accept_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSANOTINITIALISED, .WSAENETDOWN: + return .Network_Unreachable + case .WSAEMFILE, .WSAENOBUFS: + return .Insufficient_Resources + case .WSAECONNRESET: + return .Aborted + case .WSAEFAULT, .WSAEINPROGRESS, .WSAENOTSOCK: + return .Invalid_Argument + case .WSAEINTR: + return .Interrupted + case .WSAEINVAL: + return .Not_Listening + case .WSAEWOULDBLOCK: + return .Would_Block + case .WSAEOPNOTSUPP: + return .Unsupported_Socket + case: + return .Unknown + } } -UDP_Send_Error :: enum c.int { - None = 0, - Network_Subsystem_Failure = win.WSAENETDOWN, - - Aborted = win.WSAECONNABORTED, - Message_Too_Long = win.WSAEMSGSIZE, // The message is larger than the maximum UDP packet size. - Remote_Not_Listening = win.WSAECONNRESET, // The machine at the remote endpoint doesn't have the given port open to receiving UDP data. - Shutdown = win.WSAESHUTDOWN, // A broadcast address was specified, but the .Broadcast socket option isn't set. - Broadcast_Disabled = win.WSAEACCES, - Bad_Buffer = win.WSAEFAULT, // Connection is broken due to keepalive activity detecting a failure during the operation. - - // TODO: not functionally different from Reset; merge? - Keepalive_Failure = win.WSAENETRESET, - No_Buffer_Space_Available = win.WSAENOBUFS, - Not_Socket = win.WSAENOTSOCK, // The socket is not valid socket handle. - - // This socket is unidirectional and cannot be used to send any data. - // TODO: verify possible; decide whether to keep if not - Receive_Only = win.WSAEOPNOTSUPP, - Would_Block = win.WSAEWOULDBLOCK, - Host_Unreachable = win.WSAEHOSTUNREACH, // The remote host cannot be reached from this host at this time. - Cannot_Use_Any_Address = win.WSAEADDRNOTAVAIL, // Attempt to send to the Any address. - Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT, // The address is of an incorrect address family for this socket. - Offline = win.WSAENETUNREACH, // The network cannot be reached from this host at this time. - Timeout = win.WSAETIMEDOUT, +_tcp_recv_error :: proc() -> TCP_Recv_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSANOTINITIALISED, .WSAENETDOWN: + return .Network_Unreachable + case .WSAEFAULT, .WSAEINPROGRESS, .WSAENOTSOCK, .WSAEMSGSIZE, .WSAEINVAL, .WSAEOPNOTSUPP: + return .Invalid_Argument + case .WSAENOTCONN: + return .Not_Connected + case .WSAEINTR: + return .Interrupted + case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNABORTED, .WSAECONNRESET: + return .Connection_Closed + case .WSAEWOULDBLOCK: + return .Would_Block + case .WSAETIMEDOUT: + return .Timeout + case: + return .Unknown + } } -Shutdown_Manner :: enum c.int { - Receive = win.SD_RECEIVE, - Send = win.SD_SEND, - Both = win.SD_BOTH, +_udp_recv_error :: proc() -> UDP_Recv_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSANOTINITIALISED, .WSAENETDOWN: + return .Network_Unreachable + case .WSAEFAULT, .WSAEINPROGRESS, .WSAEINVAL, .WSAEISCONN, .WSAENOTSOCK, .WSAEOPNOTSUPP, .WSAEMSGSIZE: + return .Invalid_Argument + case .WSAEINTR: + return .Interrupted + case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNRESET: + return .Connection_Refused + case .WSAEWOULDBLOCK: + return .Would_Block + case .WSAETIMEDOUT: + return .Timeout + case: + return .Unknown + } } -Shutdown_Error :: enum c.int { - None = 0, - Aborted = win.WSAECONNABORTED, - Reset = win.WSAECONNRESET, - Offline = win.WSAENETDOWN, - Not_Connected = win.WSAENOTCONN, - Not_Socket = win.WSAENOTSOCK, - Invalid_Manner = win.WSAEINVAL, +_tcp_send_error :: proc() -> TCP_Send_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSANOTINITIALISED, .WSAENETDOWN: + return .Network_Unreachable + case .WSAENOBUFS: + return .Insufficient_Resources + case .WSAEACCES, .WSAEINPROGRESS, .WSAEFAULT, .WSAENOTSOCK, .WSAEOPNOTSUPP, .WSAEMSGSIZE, .WSAEINVAL: + return .Invalid_Argument + case .WSAEINTR: + return .Interrupted + case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNABORTED, .WSAECONNRESET: + return .Connection_Closed + case .WSAENOTCONN: + return .Not_Connected + case .WSAEWOULDBLOCK: + return .Would_Block + case .WSAETIMEDOUT: + return .Timeout + case .WSAEHOSTUNREACH: + return .Host_Unreachable + case: + return .Unknown + } } -Socket_Option :: enum c.int { - // bool: Whether the address that this socket is bound to can be reused by other sockets. - // This allows you to bypass the cooldown period if a program dies while the socket is bound. - Reuse_Address = win.SO_REUSEADDR, - - // bool: Whether other programs will be inhibited from binding the same endpoint as this socket. - Exclusive_Addr_Use = win.SO_EXCLUSIVEADDRUSE, - - // bool: When true, keepalive packets will be automatically be sent for this connection. TODO: verify this understanding - Keep_Alive = win.SO_KEEPALIVE, - - // bool: When true, client connections will immediately be sent a TCP/IP RST response, rather than being accepted. - Conditional_Accept = win.SO_CONDITIONAL_ACCEPT, - - // bool: If true, when the socket is closed, but data is still waiting to be sent, discard that data. - Dont_Linger = win.SO_DONTLINGER, - - // bool: When true, 'out-of-band' data sent over the socket will be read by a normal net.recv() call, the same as normal 'in-band' data. - Out_Of_Bounds_Data_Inline = win.SO_OOBINLINE, - - // bool: When true, disables send-coalescing, therefore reducing latency. - TCP_Nodelay = win.TCP_NODELAY, - - // win.LINGER: Customizes how long (if at all) the socket will remain open when there - // is some remaining data waiting to be sent, and net.close() is called. - Linger = win.SO_LINGER, - - // win.DWORD: The size, in bytes, of the OS-managed receive-buffer for this socket. - Receive_Buffer_Size = win.SO_RCVBUF, - - // win.DWORD: The size, in bytes, of the OS-managed send-buffer for this socket. - Send_Buffer_Size = win.SO_SNDBUF, - - // win.DWORD: For blocking sockets, the time in milliseconds to wait for incoming data to be received, before giving up and returning .Timeout. - // For non-blocking sockets, ignored. - // Use a value of zero to potentially wait forever. - Receive_Timeout = win.SO_RCVTIMEO, - - // win.DWORD: For blocking sockets, the time in milliseconds to wait for outgoing data to be sent, before giving up and returning .Timeout. - // For non-blocking sockets, ignored. - // Use a value of zero to potentially wait forever. - Send_Timeout = win.SO_SNDTIMEO, - - // bool: Allow sending to, receiving from, and binding to, a broadcast address. - Broadcast = win.SO_BROADCAST, +_udp_send_error :: proc() -> UDP_Send_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSANOTINITIALISED, .WSAENETDOWN, .WSAENETUNREACH: + return .Network_Unreachable + case .WSAENOBUFS: + return .Insufficient_Resources + case .WSAEACCES, .WSAEINVAL, .WSAEINPROGRESS, .WSAEFAULT, .WSAENOTCONN, .WSAENOTSOCK, .WSAEOPNOTSUPP, .WSAEADDRNOTAVAIL, .WSAEAFNOSUPPORT, .WSAEDESTADDRREQ: + return .Invalid_Argument + case .WSAEINTR: + return .Interrupted + case .WSAENETRESET, .WSAESHUTDOWN, .WSAECONNRESET: + return .Connection_Refused + case .WSAEWOULDBLOCK: + return .Would_Block + case .WSAETIMEDOUT: + return .Timeout + case: + return .Unknown + } } -Socket_Option_Error :: enum c.int { - None = 0, - Linger_Only_Supports_Whole_Seconds = 1, - - // The given value is too big or small to be given to the OS. - Value_Out_Of_Range, - - Network_Subsystem_Failure = win.WSAENETDOWN, - Timeout_When_Keepalive_Set = win.WSAENETRESET, - Invalid_Option_For_Socket = win.WSAENOPROTOOPT, - Reset_When_Keepalive_Set = win.WSAENOTCONN, - Not_Socket = win.WSAENOTSOCK, +_shutdown_error :: proc() -> Shutdown_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSAENETDOWN, .WSANOTINITIALISED: + return .Network_Unreachable + case .WSAECONNABORTED, .WSAECONNRESET: + return .Connection_Closed + case .WSAEINPROGRESS, .WSAEINVAL, .WSAENOTCONN, .WSAENOTSOCK: + return .Invalid_Argument + case: + return .Unknown + } } -Set_Blocking_Error :: enum c.int { - None = 0, +_socket_info_error :: proc() -> Socket_Info_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSAEFAULT, .WSAEINPROGRESS, .WSAENOTSOCK, .WSAEINVAL: + return .Invalid_Argument + case .WSANOTINITIALISED, .WSAENETDOWN, .WSAENOTCONN: + return .Network_Unreachable + case: + return .Unknown + } +} - Network_Subsystem_Failure = win.WSAENETDOWN, - Blocking_Call_In_Progress = win.WSAEINPROGRESS, - Not_Socket = win.WSAENOTSOCK, +_socket_option_error :: proc() -> Socket_Option_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSAENETDOWN, .WSANOTINITIALISED: + return .Network_Unreachable + case .WSAEFAULT, .WSAEINVAL: + return .Invalid_Value + case .WSAENETRESET, .WSAENOTCONN, .WSAENOTSOCK: + return .Invalid_Socket + case .WSAENOPROTOOPT: + return .Invalid_Option + case: + return .Unknown + } +} - // TODO: are those errors possible? - Network_Subsystem_Not_Initialized = win.WSAENOTINITIALISED, - Invalid_Argument_Pointer = win.WSAEFAULT, -}
\ No newline at end of file +_set_blocking_error :: proc() -> Set_Blocking_Error { + #partial switch win.System_Error(win.WSAGetLastError()) { + case .WSAENETDOWN, .WSANOTINITIALISED: + return .Network_Unreachable + case .WSAEINPROGRESS, .WSAENOTSOCK, .WSAEFAULT: + return .Invalid_Argument + case: + return .Unknown + } +} diff --git a/core/net/interface.odin b/core/net/interface.odin index 775a812f3..4d499a008 100644 --- a/core/net/interface.odin +++ b/core/net/interface.odin @@ -27,7 +27,7 @@ MAX_INTERFACE_ENUMERATION_TRIES :: 3 /* `enumerate_interfaces` retrieves a list of network interfaces with their associated properties. */ -enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { +enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) { return _enumerate_interfaces(allocator) } diff --git a/core/net/interface_darwin.odin b/core/net/interface_darwin.odin index 4921bc3fe..9883c10af 100644 --- a/core/net/interface_darwin.odin +++ b/core/net/interface_darwin.odin @@ -20,60 +20,57 @@ package net Feoramund: FreeBSD platform code */ -import "core:os" import "core:strings" +import "core:sys/posix" + +foreign import lib "system:System" @(private) -_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { +_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) { context.allocator = allocator - head: ^os.ifaddrs - - if res := os._getifaddrs(&head); res < 0 { + head: ^ifaddrs + if getifaddrs(&head) != .OK { return {}, .Unable_To_Enumerate_Network_Interfaces } + defer freeifaddrs(head) - /* - Unlike Windows, *nix regrettably doesn't return all it knows about an interface in one big struct. - We're going to have to iterate over a list and coalesce information as we go. - */ - ifaces: map[string]^Network_Interface + ifaces: map[string]Network_Interface defer delete(ifaces) for ifaddr := head; ifaddr != nil; ifaddr = ifaddr.next { adapter_name := string(ifaddr.name) - /* - Check if we have seen this interface name before so we can reuse the `Network_Interface`. - Else, create a new one. - */ - if adapter_name not_in ifaces { - ifaces[adapter_name] = new(Network_Interface) - ifaces[adapter_name].adapter_name = strings.clone(adapter_name) + key_ptr, iface, inserted, mem_err := map_entry(&ifaces, adapter_name) + if mem_err == nil && inserted { + key_ptr^, mem_err = strings.clone(adapter_name) + iface.adapter_name = key_ptr^ + } + if mem_err != nil { + return {}, .Allocation_Failure } - iface := ifaces[adapter_name] address: Address netmask: Netmask - if ifaddr.address != nil { - switch int(ifaddr.address.family) { - case os.AF_INET, os.AF_INET6: - address = _sockaddr_basic_to_endpoint(ifaddr.address).address + if ifaddr.addr != nil { + #partial switch ifaddr.addr.sa_family { + case .INET, .INET6: + address = _sockaddr_basic_to_endpoint(ifaddr.addr).address } } if ifaddr.netmask != nil { - switch int(ifaddr.netmask.family) { - case os.AF_INET, os.AF_INET6: + #partial switch ifaddr.netmask.sa_family { + case .INET, .INET6: netmask = Netmask(_sockaddr_basic_to_endpoint(ifaddr.netmask).address) } } - if ifaddr.broadcast_or_dest != nil && .BROADCAST in ifaddr.flags { - switch int(ifaddr.broadcast_or_dest.family) { - case os.AF_INET, os.AF_INET6: - broadcast := _sockaddr_basic_to_endpoint(ifaddr.broadcast_or_dest).address + if ifaddr.dstaddr != nil && .BROADCAST in ifaddr.flags { + #partial switch ifaddr.dstaddr.sa_family { + case .INET, .INET6: + broadcast := _sockaddr_basic_to_endpoint(ifaddr.dstaddr).address append(&iface.multicast, broadcast) } } @@ -105,18 +102,51 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: [] iface.link.state = state } - /* - Free the OS structures. - */ - os._freeifaddrs(head) - - /* - Turn the map into a slice to return. - */ - _interfaces := make([dynamic]Network_Interface, 0, allocator) + interfaces = make([]Network_Interface, len(ifaces)) + i: int for _, iface in ifaces { - append(&_interfaces, iface^) - free(iface) + interfaces[i] = iface + i += 1 } - return _interfaces[:], {} + return interfaces, nil +} + +@(private) +IF_Flag :: enum u32 { + UP, + BROADCAST, + DEBUG, + LOOPBACK, + POINTTOPOINT, + NOTRAILERS, + RUNNING, + NOARP, + PROMISC, + ALLMULTI, + OACTIVE, + SIMPLEX, + LINK0, + LINK1, + LINK2, + MULTICAST, +} + +@(private) +IF_Flags :: bit_set[IF_Flag; u32] + +@(private) +ifaddrs :: struct { + next: ^ifaddrs, + name: cstring, + flags: IF_Flags, + addr: ^posix.sockaddr, + netmask: ^posix.sockaddr, + dstaddr: ^posix.sockaddr, + data: rawptr, +} + +@(private) +foreign lib { + getifaddrs :: proc(ifap: ^^ifaddrs) -> posix.result --- + freeifaddrs :: proc(ifp: ^ifaddrs) --- } diff --git a/core/net/interface_freebsd.odin b/core/net/interface_freebsd.odin index 50e2d1a96..90a538a04 100644 --- a/core/net/interface_freebsd.odin +++ b/core/net/interface_freebsd.odin @@ -25,7 +25,7 @@ import "core:strings" import "core:sys/freebsd" @(private) -_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { +_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) { // This is a simplified implementation of `getifaddrs` from the FreeBSD // libc using only Odin and syscalls. context.allocator = allocator @@ -50,7 +50,7 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: [] // Allocate and get the entries. buf, alloc_err := make([]byte, needed) if alloc_err != nil { - return nil, .Unable_To_Enumerate_Network_Interfaces + return nil, .Allocation_Failure } defer delete(buf) diff --git a/core/net/interface_linux.odin b/core/net/interface_linux.odin index 28724735b..e329803c5 100644 --- a/core/net/interface_linux.odin +++ b/core/net/interface_linux.odin @@ -30,7 +30,7 @@ package net // NOTE(flysand): https://man7.org/linux/man-pages/man7/netlink.7.html // apparently musl libc uses this to enumerate network interfaces @(private) -_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { +_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) { context.allocator = allocator // head: ^os.ifaddrs @@ -143,4 +143,4 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: [] // } // return _interfaces[:], {} return nil, {} -}
\ No newline at end of file +} diff --git a/core/net/interface_windows.odin b/core/net/interface_windows.odin index a6eb72846..571fb322f 100644 --- a/core/net/interface_windows.odin +++ b/core/net/interface_windows.odin @@ -23,7 +23,7 @@ package net import sys "core:sys/windows" import strings "core:strings" -_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) { +_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Interfaces_Error) { context.allocator = allocator buf: []u8 @@ -52,7 +52,8 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: [] case 0: break gaa case: - return {}, Platform_Error(res) + set_last_platform_error(i32(res)) + return {}, .Unknown } } @@ -63,13 +64,13 @@ _enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: [] _interfaces := make([dynamic]Network_Interface, 0, allocator) for adapter := (^sys.IP_Adapter_Addresses)(raw_data(buf)); adapter != nil; adapter = adapter.Next { friendly_name, err1 := sys.wstring_to_utf8(sys.wstring(adapter.FriendlyName), 256, allocator) - if err1 != nil { return {}, Platform_Error(err1) } + if err1 != nil { return {}, .Allocation_Failure } description, err2 := sys.wstring_to_utf8(sys.wstring(adapter.Description), 256, allocator) - if err2 != nil { return {}, Platform_Error(err2) } + if err2 != nil { return {}, .Allocation_Failure } dns_suffix, err3 := sys.wstring_to_utf8(sys.wstring(adapter.DnsSuffix), 256, allocator) - if err3 != nil { return {}, Platform_Error(err3) } + if err3 != nil { return {}, .Allocation_Failure } interface := Network_Interface{ adapter_name = strings.clone(string(adapter.AdapterName)), @@ -176,4 +177,4 @@ parse_socket_address :: proc(addr_in: sys.SOCKET_ADDRESS) -> (addr: Endpoint) { case: return // Empty or invalid address type } unreachable() -}
\ No newline at end of file +} diff --git a/core/net/socket.odin b/core/net/socket.odin index c5ea11e11..f68508169 100644 --- a/core/net/socket.odin +++ b/core/net/socket.odin @@ -35,8 +35,10 @@ any_socket_to_socket :: proc "contextless" (socket: Any_Socket) -> Socket { `a.host.name:9999`, or as `1.2.3.4:9999`, or IP6 equivalent. Calls `parse_hostname_or_endpoint` and `dial_tcp_from_host_or_endpoint`. + + Errors that can be returned: `Parse_Endpoint_Error`, `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error` */ -dial_tcp_from_hostname_and_port_string :: proc(hostname_and_port: string, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { +dial_tcp_from_hostname_and_port_string :: proc(hostname_and_port: string, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) { target := parse_hostname_or_endpoint(hostname_and_port) or_return return dial_tcp_from_host_or_endpoint(target, options) @@ -47,8 +49,10 @@ dial_tcp_from_hostname_and_port_string :: proc(hostname_and_port: string, option `parse_hostname_or_endpoint` is called and the `hostname` will be resolved into an IP. If a `hostname` of form `a.host.name:9999` is given, the port will be ignored in favor of the explicit `port` param. + + Errors that can be returned: `Parse_Endpoint_Error`, `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error` */ -dial_tcp_from_hostname_with_port_override :: proc(hostname: string, port: int, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { +dial_tcp_from_hostname_with_port_override :: proc(hostname: string, port: int, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) { target := parse_hostname_or_endpoint(hostname) or_return switch &t in target { case Endpoint: @@ -62,8 +66,10 @@ dial_tcp_from_hostname_with_port_override :: proc(hostname: string, port: int, o /* Expects the `host` as Host. + + Errors that can be returned: `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error` */ -dial_tcp_from_host :: proc(host: Host, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { +dial_tcp_from_host :: proc(host: Host, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) { if host.port == 0 { return 0, .Port_Required } @@ -76,8 +82,10 @@ dial_tcp_from_host :: proc(host: Host, options := default_tcp_options) -> (socke /* Expects the `target` as a Host_OrEndpoint. Unwraps the underlying type and calls `dial_tcp_from_host` or `dial_tcp_from_endpoint`. + + Errors that can be returned: `Parse_Endpoint_Error`, `Resolve_Error`, `DNS_Error`, `Create_Socket_Error`, or `Dial_Error` */ -dial_tcp_from_host_or_endpoint :: proc(target: Host_Or_Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { +dial_tcp_from_host_or_endpoint :: proc(target: Host_Or_Endpoint, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) { switch t in target { case Endpoint: return dial_tcp_from_endpoint(t, options) @@ -87,12 +95,21 @@ dial_tcp_from_host_or_endpoint :: proc(target: Host_Or_Endpoint, options := defa unreachable() } -// Dial from an Address -dial_tcp_from_address_and_port :: proc(address: Address, port: int, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { +/* + Dial from an Address. + + Errors that can be returned: `Create_Socket_Error`, or `Dial_Error` +*/ +dial_tcp_from_address_and_port :: proc(address: Address, port: int, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) { return dial_tcp_from_endpoint({address, port}, options) } -dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { +/* + Dial from an Endpoint. + + Errors that can be returned: `Create_Socket_Error`, or `Dial_Error` +*/ +dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) { return _dial_tcp_from_endpoint(endpoint, options) } @@ -105,11 +122,11 @@ dial_tcp :: proc{ dial_tcp_from_host_or_endpoint, } -create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { +create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) { return _create_socket(family, protocol) } -bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) { +bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Bind_Error) { return _bind(socket, ep) } @@ -119,7 +136,7 @@ bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) { This is like a client TCP socket, except that it can send data to any remote endpoint without needing to establish a connection first. */ -make_unbound_udp_socket :: proc(family: Address_Family) -> (socket: UDP_Socket, err: Network_Error) { +make_unbound_udp_socket :: proc(family: Address_Family) -> (socket: UDP_Socket, err: Create_Socket_Error) { sock := create_socket(family, .UDP) or_return socket = sock.(UDP_Socket) return @@ -131,6 +148,8 @@ make_unbound_udp_socket :: proc(family: Address_Family) -> (socket: UDP_Socket, This is like a listening TCP socket, except that data packets can be sent and received without needing to establish a connection first. The `bound_address` is the address of the network interface that you want to use, or a loopback address if you don't care which to use. + + Errors that can be returned: `Parse_Endpoint_Error`, `Create_Socket_Error`, or `Bind_Error` */ make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (socket: UDP_Socket, err: Network_Error) { if bound_address == nil { @@ -141,6 +160,11 @@ make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (socket: UDP return } +/* + Creates a TCP socket and starts listening on the given endpoint. + + Errors that can be returned: `Create_Socket_Error`, `Bind_Error`, or `Listen_Error` +*/ listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, err: Network_Error) { assert(backlog > 0 && backlog < int(max(i32))) @@ -150,11 +174,18 @@ listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TC /* Returns the endpoint that the given socket is listening / bound on. */ -bound_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Network_Error) { +bound_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Socket_Info_Error) { return _bound_endpoint(socket) } -accept_tcp :: proc(socket: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { +/* + Returns the endpoint that the given socket is connected to. (Peer's endpoint) +*/ +peer_endpoint :: proc(socket: Any_Socket) -> (endpoint: Endpoint, err: Socket_Info_Error) { + return _peer_endpoint(socket) +} + +accept_tcp :: proc(socket: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) { return _accept_tcp(socket, options) } @@ -162,11 +193,11 @@ close :: proc(socket: Any_Socket) { _close(socket) } -recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { +recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) { return _recv_tcp(socket, buf) } -recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { +recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) { return _recv_udp(socket, buf) } @@ -175,6 +206,8 @@ recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_en Note: `remote_endpoint` parameter is non-nil only if the socket type is UDP. On TCP sockets it will always return `nil`. + + Errors that can be returned: `TCP_Recv_Error`, or `UDP_Recv_Error` */ recv_any :: proc(socket: Any_Socket, buf: []byte) -> ( bytes_read: int, @@ -197,7 +230,7 @@ recv :: proc{recv_tcp, recv_udp, recv_any} Repeatedly sends data until the entire buffer is sent. If a send fails before all data is sent, returns the amount sent up to that point. */ -send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { +send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) { return _send_tcp(socket, buf) } @@ -207,10 +240,15 @@ send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: N Datagrams are limited in size; attempting to send more than this limit at once will result in a Message_Too_Long error. UDP packets are not guarenteed to be received in order. */ -send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { +send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) { return _send_udp(socket, buf, to) } +/* + Sends data over the socket. + + Errors that can be returned: `TCP_Send_Error`, or `UDP_Send_Error` +*/ send_any :: proc(socket: Any_Socket, buf: []byte, to: Maybe(Endpoint) = nil) -> ( bytes_written: int, err: Network_Error, @@ -226,14 +264,14 @@ send_any :: proc(socket: Any_Socket, buf: []byte, to: Maybe(Endpoint) = nil) -> send :: proc{send_tcp, send_udp, send_any} -shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { +shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) { return _shutdown(socket, manner) } -set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { +set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error { return _set_option(socket, option, value, loc) } -set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) { +set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) { return _set_blocking(socket, should_block) } diff --git a/core/net/socket_darwin.odin b/core/net/socket_darwin.odin index 27927e973..fe4b4c3b5 100644 --- a/core/net/socket_darwin.odin +++ b/core/net/socket_darwin.odin @@ -21,44 +21,51 @@ package net */ import "core:c" -import "core:os" import "core:sys/posix" import "core:time" Socket_Option :: enum c.int { - Broadcast = c.int(os.SO_BROADCAST), - Reuse_Address = c.int(os.SO_REUSEADDR), - Keep_Alive = c.int(os.SO_KEEPALIVE), - Out_Of_Bounds_Data_Inline = c.int(os.SO_OOBINLINE), - TCP_Nodelay = c.int(os.TCP_NODELAY), - Linger = c.int(os.SO_LINGER), - Receive_Buffer_Size = c.int(os.SO_RCVBUF), - Send_Buffer_Size = c.int(os.SO_SNDBUF), - Receive_Timeout = c.int(os.SO_RCVTIMEO), - Send_Timeout = c.int(os.SO_SNDTIMEO), + Broadcast = c.int(posix.Sock_Option.BROADCAST), + Reuse_Address = c.int(posix.Sock_Option.REUSEADDR), + Keep_Alive = c.int(posix.Sock_Option.KEEPALIVE), + Out_Of_Bounds_Data_Inline = c.int(posix.Sock_Option.OOBINLINE), + TCP_Nodelay = c.int(posix.TCP_NODELAY), + Linger = c.int(posix.Sock_Option.LINGER), + Receive_Buffer_Size = c.int(posix.Sock_Option.RCVBUF), + Send_Buffer_Size = c.int(posix.Sock_Option.SNDBUF), + Receive_Timeout = c.int(posix.Sock_Option.RCVTIMEO), + Send_Timeout = c.int(posix.Sock_Option.SNDTIMEO), +} + +Shutdown_Manner :: enum c.int { + Receive = c.int(posix.SHUT_RD), + Send = c.int(posix.SHUT_WR), + Both = c.int(posix.SHUT_RDWR), } @(private) -_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { - c_type, c_protocol, c_family: int +_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) { + c_type: posix.Sock + c_protocol: posix.Protocol + c_family: posix.AF switch family { - case .IP4: c_family = os.AF_INET - case .IP6: c_family = os.AF_INET6 + case .IP4: c_family = .INET + case .IP6: c_family = .INET6 case: unreachable() } switch protocol { - case .TCP: c_type = os.SOCK_STREAM; c_protocol = os.IPPROTO_TCP - case .UDP: c_type = os.SOCK_DGRAM; c_protocol = os.IPPROTO_UDP + case .TCP: c_type = .STREAM; c_protocol = .TCP + case .UDP: c_type = .DGRAM; c_protocol = .UDP case: unreachable() } - sock, sock_err := os.socket(c_family, c_type, c_protocol) - if sock_err != nil { - err = Create_Socket_Error(os.is_platform_error(sock_err) or_else -1) + sock := posix.socket(c_family, c_type, c_protocol) + if sock < 0 { + err = _create_socket_error() return } @@ -71,7 +78,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so } @(private) -_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) { +_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := DEFAULT_TCP_OPTIONS) -> (skt: TCP_Socket, err: Network_Error) { if endpoint.port == 0 { return 0, .Port_Required } @@ -86,30 +93,22 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio _ = set_option(skt, .Reuse_Address, true) sockaddr := _endpoint_to_sockaddr(endpoint) - res := os.connect(os.Socket(skt), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len)) - if res != nil { + if posix.connect(posix.FD(skt), (^posix.sockaddr)(&sockaddr), posix.socklen_t(sockaddr.ss_len)) != .OK { + err = _dial_error() close(skt) - return {}, Dial_Error(os.is_platform_error(res) or_else -1) } return } -// On Darwin, any port below 1024 is 'privileged' - which means that you need root access in order to use it. -MAX_PRIVILEGED_PORT :: 1023 - @(private) -_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) { +_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Bind_Error) { sockaddr := _endpoint_to_sockaddr(ep) s := any_socket_to_socket(skt) - res := os.bind(os.Socket(s), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len)) - if res != nil { - if res == os.EACCES && ep.port <= MAX_PRIVILEGED_PORT { - err = .Privileged_Port_Without_Root - } else { - err = Bind_Error(os.is_platform_error(res) or_else -1) - } + if posix.bind(posix.FD(s), (^posix.sockaddr)(&sockaddr), posix.socklen_t(sockaddr.ss_len)) != .OK { + err = _bind_error() } + return } @@ -126,78 +125,90 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_ // bypass the cooldown period, and allow the next run of the program to // use the same address immediately. // - // TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address! - set_option(sock, .Reuse_Address, true) or_return + _ = set_option(sock, .Reuse_Address, true) bind(sock, interface_endpoint) or_return - res := os.listen(os.Socket(skt), backlog) - if res != nil { - err = Listen_Error(os.is_platform_error(res) or_else -1) - return + if posix.listen(posix.FD(skt), i32(backlog)) != .OK { + err = _listen_error() } return } @(private) -_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { +_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) { addr: posix.sockaddr_storage addr_len := posix.socklen_t(size_of(addr)) - res := posix.getsockname(posix.FD(any_socket_to_socket(sock)), (^posix.sockaddr)(&addr), &addr_len) - if res != .OK { - err = Listen_Error(posix.errno()) + if posix.getsockname(posix.FD(any_socket_to_socket(sock)), (^posix.sockaddr)(&addr), &addr_len) != .OK { + err = _socket_info_error() return } - ep = _sockaddr_to_endpoint((^os.SOCKADDR_STORAGE_LH)(&addr)) + + ep = _sockaddr_to_endpoint(&addr) return } @(private) -_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { - sockaddr: os.SOCKADDR_STORAGE_LH - sockaddrlen := c.int(size_of(sockaddr)) +_peer_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) { + addr: posix.sockaddr_storage + addr_len := posix.socklen_t(size_of(addr)) + if posix.getpeername(posix.FD(any_socket_to_socket(sock)), (^posix.sockaddr)(&addr), &addr_len) != .OK { + err = _socket_info_error() + return + } + + ep = _sockaddr_to_endpoint(&addr) + return +} - client_sock, client_sock_err := os.accept(os.Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen) - if client_sock_err != nil { - err = Accept_Error(os.is_platform_error(client_sock_err) or_else -1) +@(private) +_accept_tcp :: proc(sock: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) { + addr: posix.sockaddr_storage + addr_len := posix.socklen_t(size_of(addr)) + client_sock := posix.accept(posix.FD(sock), (^posix.sockaddr)(&addr), &addr_len) + if client_sock < 0 { + err = _accept_error() return } + client = TCP_Socket(client_sock) - source = _sockaddr_to_endpoint(&sockaddr) + source = _sockaddr_to_endpoint(&addr) return } @(private) _close :: proc(skt: Any_Socket) { s := any_socket_to_socket(skt) - os.close(os.Handle(os.Socket(s))) + posix.close(posix.FD(s)) } @(private) -_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { +_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) { if len(buf) <= 0 { return } - res, res_err := os.recv(os.Socket(skt), buf, 0) - if res_err != nil { - err = TCP_Recv_Error(os.is_platform_error(res_err) or_else -1) + + res := posix.recv(posix.FD(skt), raw_data(buf), len(buf), {}) + if res < 0 { + err = _tcp_recv_error() return } + return int(res), nil } @(private) -_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { +_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) { if len(buf) <= 0 { return } - from: os.SOCKADDR_STORAGE_LH - fromsize := c.int(size_of(from)) - res, res_err := os.recvfrom(os.Socket(skt), buf, 0, cast(^os.SOCKADDR) &from, &fromsize) - if res_err != nil { - err = UDP_Recv_Error(os.is_platform_error(res_err) or_else -1) + from: posix.sockaddr_storage + fromsize := posix.socklen_t(size_of(from)) + res := posix.recvfrom(posix.FD(skt), raw_data(buf), len(buf), {}, (^posix.sockaddr)(&from), &fromsize) + if res < 0 { + err = _udp_recv_error() return } @@ -207,67 +218,60 @@ _recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endp } @(private) -_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { +_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) { for bytes_written < len(buf) { limit := min(int(max(i32)), len(buf) - bytes_written) remaining := buf[bytes_written:][:limit] - res, res_err := os.send(os.Socket(skt), remaining, os.MSG_NOSIGNAL) - if res_err == os.EPIPE { - // EPIPE arises if the socket has been closed remotely. - err = TCP_Send_Error.Connection_Closed - return - } else if res_err != nil { - err = TCP_Send_Error(os.is_platform_error(res_err) or_else -1) + res := posix.send(posix.FD(skt), raw_data(remaining), len(remaining), {.NOSIGNAL}) + if res < 0 { + err = _tcp_send_error() return } + bytes_written += int(res) } return } @(private) -_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { +_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) { toaddr := _endpoint_to_sockaddr(to) for bytes_written < len(buf) { limit := min(1<<31, len(buf) - bytes_written) remaining := buf[bytes_written:][:limit] - res, res_err := os.sendto(os.Socket(skt), remaining, os.MSG_NOSIGNAL, cast(^os.SOCKADDR)&toaddr, i32(toaddr.len)) - if res_err == os.EPIPE { - // EPIPE arises if the socket has been closed remotely. - err = UDP_Send_Error.Not_Socket - return - } else if res_err != nil { - err = UDP_Send_Error(os.is_platform_error(res_err) or_else -1) + res := posix.sendto(posix.FD(skt), raw_data(remaining), len(remaining), {.NOSIGNAL}, (^posix.sockaddr)(&toaddr), posix.socklen_t(toaddr.ss_len)) + if res < 0 { + err = _udp_send_error() return } + bytes_written += int(res) } return } @(private) -_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { +_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) { s := any_socket_to_socket(skt) - res := os.shutdown(os.Socket(s), int(manner)) - if res != nil { - return Shutdown_Error(os.is_platform_error(res) or_else -1) + if posix.shutdown(posix.FD(s), posix.Shut(manner)) != .OK { + err = _shutdown_error() } return } @(private) -_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { - level := os.SOL_SOCKET if option != .TCP_Nodelay else os.IPPROTO_TCP +_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error { + level := posix.SOL_SOCKET if option != .TCP_Nodelay else posix.IPPROTO_TCP // NOTE(tetra, 2022-02-15): On Linux, you cannot merely give a single byte for a bool; // it _has_ to be a b32. // I haven't tested if you can give more than that. bool_value: b32 - int_value: i32 - timeval_value: os.Timeval + int_value: posix.socklen_t + timeval_value: posix.timeval ptr: rawptr - len: os.socklen_t + len: posix.socklen_t switch option { case @@ -302,8 +306,8 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca t := value.(time.Duration) or_else panic("set_option() value must be a time.Duration here", loc) micros := i64(time.duration_microseconds(t)) - timeval_value.microseconds = int(micros % 1e6) - timeval_value.seconds = (micros - i64(timeval_value.microseconds)) / 1e6 + timeval_value.tv_usec = posix.suseconds_t(micros % 1e6) + timeval_value.tv_sec = posix.time_t(micros - i64(timeval_value.tv_usec)) / 1e6 ptr = &timeval_value len = size_of(timeval_value) @@ -312,12 +316,12 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca .Send_Buffer_Size: // TODO: check for out of range values and return .Value_Out_Of_Range? switch i in value { - case i8, u8: i2 := i; int_value = os.socklen_t((^u8)(&i2)^) - case i16, u16: i2 := i; int_value = os.socklen_t((^u16)(&i2)^) - case i32, u32: i2 := i; int_value = os.socklen_t((^u32)(&i2)^) - case i64, u64: i2 := i; int_value = os.socklen_t((^u64)(&i2)^) - case i128, u128: i2 := i; int_value = os.socklen_t((^u128)(&i2)^) - case int, uint: i2 := i; int_value = os.socklen_t((^uint)(&i2)^) + case i8, u8: i2 := i; int_value = posix.socklen_t((^u8)(&i2)^) + case i16, u16: i2 := i; int_value = posix.socklen_t((^u16)(&i2)^) + case i32, u32: i2 := i; int_value = posix.socklen_t((^u32)(&i2)^) + case i64, u64: i2 := i; int_value = posix.socklen_t((^u64)(&i2)^) + case i128, u128: i2 := i; int_value = posix.socklen_t((^u128)(&i2)^) + case int, uint: i2 := i; int_value = posix.socklen_t((^uint)(&i2)^) case: panic("set_option() value must be an integer here", loc) } @@ -326,54 +330,53 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca } skt := any_socket_to_socket(s) - res := os.setsockopt(os.Socket(skt), int(level), int(option), ptr, len) - if res != nil { - return Socket_Option_Error(os.is_platform_error(res) or_else -1) + if posix.setsockopt(posix.FD(skt), i32(level), posix.Sock_Option(option), ptr, len) != .OK { + return _socket_option_error() } return nil } @(private) -_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) { +_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) { socket := any_socket_to_socket(socket) - flags, getfl_err := os.fcntl(int(socket), os.F_GETFL, 0) - if getfl_err != nil { - return Set_Blocking_Error(os.is_platform_error(getfl_err) or_else -1) + flags_ := posix.fcntl(posix.FD(socket), .GETFL, 0) + if flags_ < 0 { + return _set_blocking_error() } + flags := transmute(posix.O_Flags)flags_ if should_block { - flags &~= int(os.O_NONBLOCK) + flags -= {.NONBLOCK} } else { - flags |= int(os.O_NONBLOCK) + flags += {.NONBLOCK} } - _, setfl_err := os.fcntl(int(socket), os.F_SETFL, flags) - if setfl_err != nil { - return Set_Blocking_Error(os.is_platform_error(setfl_err) or_else -1) + if posix.fcntl(posix.FD(socket), .SETFL, flags) < 0 { + return _set_blocking_error() } return nil } @private -_endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH) { +_endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: posix.sockaddr_storage) { switch a in ep.address { case IP4_Address: - (^os.sockaddr_in)(&sockaddr)^ = os.sockaddr_in { + (^posix.sockaddr_in)(&sockaddr)^ = posix.sockaddr_in { sin_port = u16be(ep.port), - sin_addr = transmute(os.in_addr) a, - sin_family = u8(os.AF_INET), - sin_len = size_of(os.sockaddr_in), + sin_addr = transmute(posix.in_addr)a, + sin_family = .INET, + sin_len = size_of(posix.sockaddr_in), } return case IP6_Address: - (^os.sockaddr_in6)(&sockaddr)^ = os.sockaddr_in6 { + (^posix.sockaddr_in6)(&sockaddr)^ = posix.sockaddr_in6 { sin6_port = u16be(ep.port), - sin6_addr = transmute(os.in6_addr) a, - sin6_family = u8(os.AF_INET6), - sin6_len = size_of(os.sockaddr_in6), + sin6_addr = transmute(posix.in6_addr)a, + sin6_family = .INET6, + sin6_len = size_of(posix.sockaddr_in6), } return } @@ -381,21 +384,21 @@ _endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH } @private -_sockaddr_to_endpoint :: proc(native_addr: ^os.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) { - switch native_addr.family { - case u8(os.AF_INET): - addr := cast(^os.sockaddr_in) native_addr +_sockaddr_to_endpoint :: proc(native_addr: ^posix.sockaddr_storage) -> (ep: Endpoint) { + #partial switch native_addr.ss_family { + case .INET: + addr := cast(^posix.sockaddr_in)native_addr port := int(addr.sin_port) ep = Endpoint { - address = IP4_Address(transmute([4]byte) addr.sin_addr), - port = port, + address = IP4_Address(transmute([4]byte)addr.sin_addr), + port = port, } - case u8(os.AF_INET6): - addr := cast(^os.sockaddr_in6) native_addr + case .INET6: + addr := cast(^posix.sockaddr_in6)native_addr port := int(addr.sin6_port) ep = Endpoint { - address = IP6_Address(transmute([8]u16be) addr.sin6_addr), - port = port, + address = IP6_Address(transmute([8]u16be)addr.sin6_addr), + port = port, } case: panic("native_addr is neither IP4 or IP6 address") @@ -404,21 +407,21 @@ _sockaddr_to_endpoint :: proc(native_addr: ^os.SOCKADDR_STORAGE_LH) -> (ep: Endp } @(private) -_sockaddr_basic_to_endpoint :: proc(native_addr: ^os.SOCKADDR) -> (ep: Endpoint) { - switch u16(native_addr.family) { - case u16(os.AF_INET): - addr := cast(^os.sockaddr_in) native_addr +_sockaddr_basic_to_endpoint :: proc(native_addr: ^posix.sockaddr) -> (ep: Endpoint) { + #partial switch native_addr.sa_family { + case .INET: + addr := cast(^posix.sockaddr_in)native_addr port := int(addr.sin_port) ep = Endpoint { - address = IP4_Address(transmute([4]byte) addr.sin_addr), - port = port, + address = IP4_Address(transmute([4]byte)addr.sin_addr), + port = port, } - case u16(os.AF_INET6): - addr := cast(^os.sockaddr_in6) native_addr + case .INET6: + addr := cast(^posix.sockaddr_in6)native_addr port := int(addr.sin6_port) ep = Endpoint { - address = IP6_Address(transmute([8]u16be) addr.sin6_addr), - port = port, + address = IP6_Address(transmute([8]u16be)addr.sin6_addr), + port = port, } case: panic("native_addr is neither IP4 or IP6 address") diff --git a/core/net/socket_freebsd.odin b/core/net/socket_freebsd.odin index 3a3774007..78bd1cdae 100644 --- a/core/net/socket_freebsd.odin +++ b/core/net/socket_freebsd.odin @@ -54,8 +54,14 @@ Socket_Option :: enum c.int { Receive_Timeout = cast(c.int)freebsd.Socket_Option.RCVTIMEO, } +Shutdown_Manner :: enum c.int { + Receive = cast(c.int)freebsd.Shutdown_Method.RD, + Send = cast(c.int)freebsd.Shutdown_Method.WR, + Both = cast(c.int)freebsd.Shutdown_Method.RDWR, +} + @(private) -_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { +_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) { sys_family: freebsd.Protocol_Family = --- sys_protocol: freebsd.Protocol = --- sys_socket_type: freebsd.Socket_Type = --- @@ -72,24 +78,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so new_socket, errno := freebsd.socket(sys_family, sys_socket_type, sys_protocol) if errno != nil { - err = cast(Create_Socket_Error)errno - return - } - - // NOTE(Feoramund): By default, FreeBSD will generate SIGPIPE if an EPIPE - // error is raised during the writing of a socket that may be closed. - // This behavior is unlikely to be expected by general users. - // - // There are two workarounds. One is to apply the .NOSIGNAL flag when using - // the `sendto` syscall. However, that would prevent users of this library - // from re-enabling the SIGPIPE-raising functionality, if they really - // wanted it. - // - // So I have disabled it here with this socket option for all sockets. - truth: b32 = true - errno = freebsd.setsockopt(new_socket, .SOCKET, .NOSIGPIPE, &truth, size_of(truth)) - if errno != nil { - err = cast(Socket_Option_Error)errno + err = _create_socket_error(errno) return } @@ -102,7 +91,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so } @(private) -_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { +_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) { if endpoint.port == 0 { return 0, .Port_Required } @@ -115,19 +104,19 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio errno := freebsd.connect(cast(Fd)socket, &sockaddr, cast(freebsd.socklen_t)sockaddr.len) if errno != nil { close(socket) - return {}, cast(Dial_Error)errno + return {}, _dial_error(errno) } return } @(private) -_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) { +_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Bind_Error) { sockaddr := _endpoint_to_sockaddr(ep) real_socket := any_socket_to_socket(socket) errno := freebsd.bind(cast(Fd)real_socket, &sockaddr, cast(freebsd.socklen_t)sockaddr.len) if errno != nil { - err = cast(Bind_Error)errno + err = _bind_error(errno) } return } @@ -143,7 +132,7 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T errno := freebsd.listen(cast(Fd)socket, backlog) if errno != nil { - err = cast(Listen_Error)errno + err = _listen_error(errno) return } @@ -151,12 +140,26 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T } @(private) -_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { +_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) { sockaddr: freebsd.Socket_Address_Storage errno := freebsd.getsockname(cast(Fd)any_socket_to_socket(sock), &sockaddr) if errno != nil { - err = cast(Listen_Error)errno + err = _socket_info_error(errno) + return + } + + ep = _sockaddr_to_endpoint(&sockaddr) + return +} + +@(private) +_peer_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) { + sockaddr: freebsd.Socket_Address_Storage + + errno := freebsd.getpeername(cast(Fd)any_socket_to_socket(sock), &sockaddr) + if errno != nil { + err = _socket_info_error(errno) return } @@ -165,12 +168,12 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) } @(private) -_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { +_accept_tcp :: proc(sock: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) { sockaddr: freebsd.Socket_Address_Storage result, errno := freebsd.accept(cast(Fd)sock, &sockaddr) if errno != nil { - err = cast(Accept_Error)errno + err = _accept_error(errno) return } @@ -187,20 +190,20 @@ _close :: proc(socket: Any_Socket) { } @(private) -_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { +_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) { if len(buf) == 0 { return } result, errno := freebsd.recv(cast(Fd)socket, buf, .NONE) if errno != nil { - err = cast(TCP_Recv_Error)errno + err = _tcp_recv_error(errno) return } return result, nil } @(private) -_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { +_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) { if len(buf) == 0 { return } @@ -208,21 +211,21 @@ _recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_e result, errno := freebsd.recvfrom(cast(Fd)socket, buf, .NONE, &from) if errno != nil { - err = cast(UDP_Recv_Error)errno + err = _udp_recv_error(errno) return } return result, _sockaddr_to_endpoint(&from), nil } @(private) -_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { +_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) { for bytes_written < len(buf) { limit := min(int(max(i32)), len(buf) - bytes_written) remaining := buf[bytes_written:][:limit] - result, errno := freebsd.send(cast(Fd)socket, remaining, .NONE) + result, errno := freebsd.send(cast(Fd)socket, remaining, .NOSIGNAL) if errno != nil { - err = cast(TCP_Send_Error)errno + err = _tcp_send_error(errno) return } bytes_written += result @@ -231,15 +234,15 @@ _send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: } @(private) -_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { +_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) { toaddr := _endpoint_to_sockaddr(to) for bytes_written < len(buf) { limit := min(int(max(i32)), len(buf) - bytes_written) remaining := buf[bytes_written:][:limit] - result, errno := freebsd.sendto(cast(Fd)socket, remaining, .NONE, &toaddr) + result, errno := freebsd.sendto(cast(Fd)socket, remaining, .NOSIGNAL, &toaddr) if errno != nil { - err = cast(UDP_Send_Error)errno + err = _udp_send_error(errno) return } bytes_written += result @@ -248,17 +251,17 @@ _send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_writt } @(private) -_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { +_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) { real_socket := cast(Fd)any_socket_to_socket(socket) errno := freebsd.shutdown(real_socket, cast(freebsd.Shutdown_Method)manner) if errno != nil { - return cast(Shutdown_Error)errno + return _shutdown_error(errno) } return } @(private) -_set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { +_set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error { // NOTE(Feoramund): I found that FreeBSD, like Linux, requires at least 32 // bits for a boolean socket option value. Nothing less will work. bool_value: b32 @@ -315,25 +318,25 @@ _set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc : case u16: int_value = cast(i32)real case i32: int_value = real case u32: - if real > u32(max(i32)) { return .Value_Out_Of_Range } + if real > u32(max(i32)) { return .Invalid_Value } int_value = cast(i32)real case i64: - if real > i64(max(i32)) || real < i64(min(i32)) { return .Value_Out_Of_Range } + if real > i64(max(i32)) || real < i64(min(i32)) { return .Invalid_Value } int_value = cast(i32)real case u64: - if real > u64(max(i32)) { return .Value_Out_Of_Range } + if real > u64(max(i32)) { return .Invalid_Value } int_value = cast(i32)real case i128: - if real > i128(max(i32)) || real < i128(min(i32)) { return .Value_Out_Of_Range } + if real > i128(max(i32)) || real < i128(min(i32)) { return .Invalid_Value } int_value = cast(i32)real case u128: - if real > u128(max(i32)) { return .Value_Out_Of_Range } + if real > u128(max(i32)) { return .Invalid_Value } int_value = cast(i32)real case int: - if real > int(max(i32)) || real < int(min(i32)) { return .Value_Out_Of_Range } + if real > int(max(i32)) || real < int(min(i32)) { return .Invalid_Value } int_value = cast(i32)real case uint: - if real > uint(max(i32)) { return .Value_Out_Of_Range } + if real > uint(max(i32)) { return .Invalid_Value } int_value = cast(i32)real case: panic("set_option() value must be an integer here", loc) @@ -347,19 +350,19 @@ _set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc : real_socket := any_socket_to_socket(socket) errno := freebsd.setsockopt(cast(Fd)real_socket, .SOCKET, cast(freebsd.Socket_Option)option, ptr, len) if errno != nil { - return cast(Socket_Option_Error)errno + return _socket_option_error(errno) } return nil } @(private) -_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) { +_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) { real_socket := any_socket_to_socket(socket) flags, errno := freebsd.fcntl_getfl(cast(freebsd.Fd)real_socket) if errno != nil { - return cast(Set_Blocking_Error)errno + return _set_blocking_error(errno) } if should_block { @@ -370,7 +373,7 @@ _set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_E errno = freebsd.fcntl_setfl(cast(freebsd.Fd)real_socket, flags) if errno != nil { - return cast(Set_Blocking_Error)errno + return _set_blocking_error(errno) } return diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin index cafec747d..bdb48fce8 100644 --- a/core/net/socket_linux.odin +++ b/core/net/socket_linux.odin @@ -38,15 +38,21 @@ Socket_Option :: enum c.int { Broadcast = c.int(linux.Socket_Option.BROADCAST), } +Shutdown_Manner :: enum c.int { + Receive = c.int(linux.Shutdown_How.RD), + Send = c.int(linux.Shutdown_How.WR), + Both = c.int(linux.Shutdown_How.RDWR), +} + // Wrappers and unwrappers for system-native types @(private="file") -_unwrap_os_socket :: proc "contextless" (sock: Any_Socket)->linux.Fd { +_unwrap_os_socket :: proc "contextless" (sock: Any_Socket) -> linux.Fd { return linux.Fd(any_socket_to_socket(sock)) } @(private="file") -_wrap_os_socket :: proc "contextless" (sock: linux.Fd, protocol: Socket_Protocol)->Any_Socket { +_wrap_os_socket :: proc "contextless" (sock: linux.Fd, protocol: Socket_Protocol) -> Any_Socket { switch protocol { case .TCP: return TCP_Socket(Socket(sock)) case .UDP: return UDP_Socket(Socket(sock)) @@ -56,7 +62,7 @@ _wrap_os_socket :: proc "contextless" (sock: linux.Fd, protocol: Socket_Protocol } @(private="file") -_unwrap_os_family :: proc "contextless" (family: Address_Family)->linux.Address_Family { +_unwrap_os_family :: proc "contextless" (family: Address_Family) -> linux.Address_Family { switch family { case .IP4: return .INET case .IP6: return .INET6 @@ -66,7 +72,7 @@ _unwrap_os_family :: proc "contextless" (family: Address_Family)->linux.Address_ } @(private="file") -_unwrap_os_proto_socktype :: proc "contextless" (protocol: Socket_Protocol)->(linux.Protocol, linux.Socket_Type) { +_unwrap_os_proto_socktype :: proc "contextless" (protocol: Socket_Protocol) -> (linux.Protocol, linux.Socket_Type) { switch protocol { case .TCP: return .TCP, .STREAM case .UDP: return .UDP, .DGRAM @@ -76,7 +82,7 @@ _unwrap_os_proto_socktype :: proc "contextless" (protocol: Socket_Protocol)->(li } @(private="file") -_unwrap_os_addr :: proc "contextless" (endpoint: Endpoint)->(linux.Sock_Addr_Any) { +_unwrap_os_addr :: proc "contextless" (endpoint: Endpoint) -> linux.Sock_Addr_Any { switch address in endpoint.address { case IP4_Address: return { @@ -100,7 +106,7 @@ _unwrap_os_addr :: proc "contextless" (endpoint: Endpoint)->(linux.Sock_Addr_Any } @(private="file") -_wrap_os_addr :: proc "contextless" (addr: linux.Sock_Addr_Any)->(Endpoint) { +_wrap_os_addr :: proc "contextless" (addr: linux.Sock_Addr_Any) -> Endpoint { #partial switch addr.family { case .INET: return { @@ -117,18 +123,18 @@ _wrap_os_addr :: proc "contextless" (addr: linux.Sock_Addr_Any)->(Endpoint) { } } -_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (Any_Socket, Network_Error) { +_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (Any_Socket, Create_Socket_Error) { family := _unwrap_os_family(family) proto, socktype := _unwrap_os_proto_socktype(protocol) sock, errno := linux.socket(family, socktype, {.CLOEXEC}, proto) if errno != .NONE { - return {}, Create_Socket_Error(errno) + return {}, _create_socket_error(errno) } return _wrap_os_socket(sock, protocol), nil } @(private) -_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (TCP_Socket, Network_Error) { +_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := DEFAULT_TCP_OPTIONS) -> (TCP_Socket, Network_Error) { errno: linux.Errno if endpoint.port == 0 { return 0, .Port_Required @@ -138,7 +144,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio os_sock, errno = linux.socket(_unwrap_os_family(family_from_endpoint(endpoint)), .STREAM, {.CLOEXEC}, .TCP) if errno != .NONE { // TODO(flysand): should return invalid file descriptor here casted as TCP_Socket - return {}, Create_Socket_Error(errno) + return {}, _create_socket_error(errno) } // NOTE(tetra): This is so that if we crash while the socket is open, we can // bypass the cooldown period, and allow the next run of the program to @@ -149,7 +155,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio errno = linux.connect(linux.Fd(os_sock), &addr) if errno != .NONE { close(cast(TCP_Socket) os_sock) - return {}, Dial_Error(errno) + return {}, _dial_error(errno) } // NOTE(tetra): Not vital to succeed; error ignored no_delay: b32 = cast(b32) options.no_delay @@ -158,11 +164,11 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio } @(private) -_bind :: proc(sock: Any_Socket, endpoint: Endpoint) -> (Network_Error) { +_bind :: proc(sock: Any_Socket, endpoint: Endpoint) -> (Bind_Error) { addr := _unwrap_os_addr(endpoint) errno := linux.bind(_unwrap_os_socket(sock), &addr) if errno != .NONE { - return Bind_Error(errno) + return _bind_error(errno) } return nil } @@ -180,7 +186,7 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, os_sock: linux.Fd os_sock, errno = linux.socket(ep_family, .STREAM, {.CLOEXEC}, .TCP) if errno != .NONE { - err = Create_Socket_Error(errno) + err = _create_socket_error(errno) return } socket = cast(TCP_Socket)os_sock @@ -193,31 +199,43 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, // TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address! do_reuse_addr: b32 = true if errno = linux.setsockopt(os_sock, linux.SOL_SOCKET, linux.Socket_Option.REUSEADDR, &do_reuse_addr); errno != .NONE { - err = Listen_Error(errno) + err = _listen_error(errno) return } // Bind the socket to endpoint address if errno = linux.bind(os_sock, &ep_address); errno != .NONE { - err = Bind_Error(errno) + err = _bind_error(errno) return } // Listen on bound socket if errno = linux.listen(os_sock, cast(i32) backlog); errno != .NONE { - err = Listen_Error(errno) - return + err = _listen_error(errno) } return } @(private) -_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { +_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) { addr: linux.Sock_Addr_Any errno := linux.getsockname(_unwrap_os_socket(sock), &addr) if errno != .NONE { - err = Listen_Error(errno) + err = _socket_info_error(errno) + return + } + + ep = _wrap_os_addr(addr) + return +} + +@(private) +_peer_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) { + addr: linux.Sock_Addr_Any + errno := linux.getpeername(_unwrap_os_socket(sock), &addr) + if errno != .NONE { + err = _socket_info_error(errno) return } @@ -226,11 +244,11 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) } @(private) -_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (tcp_client: TCP_Socket, endpoint: Endpoint, err: Network_Error) { +_accept_tcp :: proc(sock: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (tcp_client: TCP_Socket, endpoint: Endpoint, err: Accept_Error) { addr: linux.Sock_Addr_Any client_sock, errno := linux.accept(linux.Fd(sock), &addr) if errno != .NONE { - return {}, {}, Accept_Error(errno) + return {}, {}, _accept_error(errno) } // NOTE(tetra): Not vital to succeed; error ignored val: b32 = cast(b32) options.no_delay @@ -244,19 +262,19 @@ _close :: proc(sock: Any_Socket) { } @(private) -_recv_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) { +_recv_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, TCP_Recv_Error) { if len(buf) <= 0 { return 0, nil } bytes_read, errno := linux.recv(linux.Fd(tcp_sock), buf, {}) if errno != .NONE { - return 0, TCP_Recv_Error(errno) + return 0, _tcp_recv_error(errno) } return int(bytes_read), nil } @(private) -_recv_udp :: proc(udp_sock: UDP_Socket, buf: []byte) -> (int, Endpoint, Network_Error) { +_recv_udp :: proc(udp_sock: UDP_Socket, buf: []byte) -> (int, Endpoint, UDP_Recv_Error) { if len(buf) <= 0 { // NOTE(flysand): It was returning no error, I didn't change anything return 0, {}, {} @@ -268,28 +286,24 @@ _recv_udp :: proc(udp_sock: UDP_Socket, buf: []byte) -> (int, Endpoint, Network_ from_addr: linux.Sock_Addr_Any bytes_read, errno := linux.recvfrom(linux.Fd(udp_sock), buf, {.TRUNC}, &from_addr) if errno != .NONE { - return 0, {}, UDP_Recv_Error(errno) + return 0, {}, _udp_recv_error(errno) } if bytes_read > len(buf) { // NOTE(tetra): The buffer has been filled, with a partial message. - return len(buf), {}, .Buffer_Too_Small + return len(buf), {}, .Excess_Truncated } return bytes_read, _wrap_os_addr(from_addr), nil } @(private) -_send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) { +_send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, TCP_Send_Error) { total_written := 0 for total_written < len(buf) { limit := min(int(max(i32)), len(buf) - total_written) remaining := buf[total_written:][:limit] res, errno := linux.send(linux.Fd(tcp_sock), remaining, {.NOSIGNAL}) - if errno == .EPIPE { - // If the peer is disconnected when we are trying to send we will get an `EPIPE` error, - // so we turn that into a clearer error - return total_written, TCP_Send_Error.Connection_Closed - } else if errno != .NONE { - return total_written, TCP_Send_Error(errno) + if errno != .NONE { + return total_written, _tcp_send_error(errno) } total_written += int(res) } @@ -297,28 +311,28 @@ _send_tcp :: proc(tcp_sock: TCP_Socket, buf: []byte) -> (int, Network_Error) { } @(private) -_send_udp :: proc(udp_sock: UDP_Socket, buf: []byte, to: Endpoint) -> (int, Network_Error) { +_send_udp :: proc(udp_sock: UDP_Socket, buf: []byte, to: Endpoint) -> (int, UDP_Send_Error) { to_addr := _unwrap_os_addr(to) bytes_written, errno := linux.sendto(linux.Fd(udp_sock), buf, {}, &to_addr) if errno != .NONE { - return bytes_written, UDP_Send_Error(errno) + return bytes_written, _udp_send_error(errno) } return int(bytes_written), nil } @(private) -_shutdown :: proc(sock: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { +_shutdown :: proc(sock: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) { os_sock := _unwrap_os_socket(sock) errno := linux.shutdown(os_sock, cast(linux.Shutdown_How) manner) if errno != .NONE { - return Shutdown_Error(errno) + return _shutdown_error(errno) } return nil } // TODO(flysand): Figure out what we want to do with this on core:sys/ level. @(private) -_set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { +_set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error { level: int if option == .TCP_Nodelay { level = int(linux.SOL_TCP) @@ -388,19 +402,19 @@ _set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc := errno = linux.setsockopt(os_sock, level, int(option), &int_value) } if errno != .NONE { - return Socket_Option_Error(errno) + return _socket_option_error(errno) } return nil } @(private) -_set_blocking :: proc(sock: Any_Socket, should_block: bool) -> (err: Network_Error) { +_set_blocking :: proc(sock: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) { errno: linux.Errno flags: linux.Open_Flags os_sock := _unwrap_os_socket(sock) flags, errno = linux.fcntl(os_sock, linux.F_GETFL) if errno != .NONE { - return Set_Blocking_Error(errno) + return _set_blocking_error(errno) } if should_block { flags -= {.NONBLOCK} @@ -409,7 +423,7 @@ _set_blocking :: proc(sock: Any_Socket, should_block: bool) -> (err: Network_Err } errno = linux.fcntl(os_sock, linux.F_SETFL, flags) if errno != .NONE { - return Set_Blocking_Error(errno) + return _set_blocking_error(errno) } return nil } diff --git a/core/net/socket_windows.odin b/core/net/socket_windows.odin index f19be536a..9127874de 100644 --- a/core/net/socket_windows.odin +++ b/core/net/socket_windows.odin @@ -24,13 +24,67 @@ import "core:c" import win "core:sys/windows" import "core:time" +Socket_Option :: enum c.int { + // bool: Whether the address that this socket is bound to can be reused by other sockets. + // This allows you to bypass the cooldown period if a program dies while the socket is bound. + Reuse_Address = win.SO_REUSEADDR, + + // bool: Whether other programs will be inhibited from binding the same endpoint as this socket. + Exclusive_Addr_Use = win.SO_EXCLUSIVEADDRUSE, + + // bool: When true, keepalive packets will be automatically be sent for this connection. TODO: verify this understanding + Keep_Alive = win.SO_KEEPALIVE, + + // bool: When true, client connections will immediately be sent a TCP/IP RST response, rather than being accepted. + Conditional_Accept = win.SO_CONDITIONAL_ACCEPT, + + // bool: If true, when the socket is closed, but data is still waiting to be sent, discard that data. + Dont_Linger = win.SO_DONTLINGER, + + // bool: When true, 'out-of-band' data sent over the socket will be read by a normal net.recv() call, the same as normal 'in-band' data. + Out_Of_Bounds_Data_Inline = win.SO_OOBINLINE, + + // bool: When true, disables send-coalescing, therefore reducing latency. + TCP_Nodelay = win.TCP_NODELAY, + + // win.LINGER: Customizes how long (if at all) the socket will remain open when there + // is some remaining data waiting to be sent, and net.close() is called. + Linger = win.SO_LINGER, + + // win.DWORD: The size, in bytes, of the OS-managed receive-buffer for this socket. + Receive_Buffer_Size = win.SO_RCVBUF, + + // win.DWORD: The size, in bytes, of the OS-managed send-buffer for this socket. + Send_Buffer_Size = win.SO_SNDBUF, + + // win.DWORD: For blocking sockets, the time in milliseconds to wait for incoming data to be received, before giving up and returning .Timeout. + // For non-blocking sockets, ignored. + // Use a value of zero to potentially wait forever. + Receive_Timeout = win.SO_RCVTIMEO, + + // win.DWORD: For blocking sockets, the time in milliseconds to wait for outgoing data to be sent, before giving up and returning .Timeout. + // For non-blocking sockets, ignored. + // Use a value of zero to potentially wait forever. + Send_Timeout = win.SO_SNDTIMEO, + + // bool: Allow sending to, receiving from, and binding to, a broadcast address. + Broadcast = win.SO_BROADCAST, +} + + +Shutdown_Manner :: enum c.int { + Receive = win.SD_RECEIVE, + Send = win.SD_SEND, + Both = win.SD_BOTH, +} + @(init, private) -ensure_winsock_initialized :: proc() { +ensure_winsock_initialized :: proc "contextless" () { win.ensure_winsock_initialized() } @(private) -_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) { +_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Create_Socket_Error) { c_type, c_protocol, c_family: c.int switch family { @@ -49,7 +103,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so sock := win.socket(c_family, c_type, c_protocol) if sock == win.INVALID_SOCKET { - err = Create_Socket_Error(win.WSAGetLastError()) + err = _create_socket_error() return } @@ -62,7 +116,7 @@ _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (so } @(private) -_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) { +_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := DEFAULT_TCP_OPTIONS) -> (socket: TCP_Socket, err: Network_Error) { if endpoint.port == 0 { err = .Port_Required return @@ -80,7 +134,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio sockaddr := _endpoint_to_sockaddr(endpoint) res := win.connect(win.SOCKET(socket), &sockaddr, size_of(sockaddr)) if res < 0 { - err = Dial_Error(win.WSAGetLastError()) + err = _dial_error() close(socket) return {}, err } @@ -93,12 +147,12 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio } @(private) -_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) { +_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Bind_Error) { sockaddr := _endpoint_to_sockaddr(ep) sock := any_socket_to_socket(socket) res := win.bind(win.SOCKET(sock), &sockaddr, size_of(sockaddr)) if res < 0 { - err = Bind_Error(win.WSAGetLastError()) + err = _bind_error() } return } @@ -117,17 +171,17 @@ _listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: T bind(sock, interface_endpoint) or_return if res := win.listen(win.SOCKET(socket), i32(backlog)); res == win.SOCKET_ERROR { - err = Listen_Error(win.WSAGetLastError()) + err = _listen_error() } return } @(private) -_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) { +_bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) { sockaddr: win.SOCKADDR_STORAGE_LH sockaddrlen := c.int(size_of(sockaddr)) if win.getsockname(win.SOCKET(any_socket_to_socket(sock)), &sockaddr, &sockaddrlen) == win.SOCKET_ERROR { - err = Listen_Error(win.WSAGetLastError()) + err = _socket_info_error() return } @@ -136,7 +190,21 @@ _bound_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Network_Error) } @(private) -_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) { +_peer_endpoint :: proc(sock: Any_Socket) -> (ep: Endpoint, err: Socket_Info_Error) { + sockaddr: win.SOCKADDR_STORAGE_LH + sockaddrlen := c.int(size_of(sockaddr)) + res := win.getpeername(win.SOCKET(any_socket_to_socket(sock)), &sockaddr, &sockaddrlen) + if res < 0 { + err = _socket_info_error() + return + } + + ep = _sockaddr_to_endpoint(&sockaddr) + return +} + +@(private) +_accept_tcp :: proc(sock: TCP_Socket, options := DEFAULT_TCP_OPTIONS) -> (client: TCP_Socket, source: Endpoint, err: Accept_Error) { for { sockaddr: win.SOCKADDR_STORAGE_LH sockaddrlen := c.int(size_of(sockaddr)) @@ -150,7 +218,7 @@ _accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client // can do this to match the behaviour. continue } - err = Accept_Error(e) + err = _accept_error() return } client = TCP_Socket(client_sock) @@ -170,20 +238,20 @@ _close :: proc(socket: Any_Socket) { } @(private) -_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) { +_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) { if len(buf) <= 0 { return } res := win.recv(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0) if res < 0 { - err = TCP_Recv_Error(win.WSAGetLastError()) + err = _tcp_recv_error() return } return int(res), nil } @(private) -_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) { +_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) { if len(buf) <= 0 { return } @@ -192,7 +260,7 @@ _recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_e fromsize := c.int(size_of(from)) res := win.recvfrom(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &from, &fromsize) if res < 0 { - err = UDP_Recv_Error(win.WSAGetLastError()) + err = _udp_recv_error() return } @@ -202,13 +270,13 @@ _recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_e } @(private) -_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) { +_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: TCP_Send_Error) { for bytes_written < len(buf) { limit := min(int(max(i32)), len(buf) - bytes_written) remaining := buf[bytes_written:] res := win.send(win.SOCKET(socket), raw_data(remaining), c.int(limit), 0) if res < 0 { - err = TCP_Send_Error(win.WSAGetLastError()) + err = _tcp_send_error() return } bytes_written += int(res) @@ -217,34 +285,34 @@ _send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: } @(private) -_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) { - if len(buf) > int(max(c.int)) { - // NOTE(tetra): If we don't guard this, we'll return (0, nil) instead, which is misleading. - err = .Message_Too_Long - return - } +_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: UDP_Send_Error) { toaddr := _endpoint_to_sockaddr(to) - res := win.sendto(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &toaddr, size_of(toaddr)) - if res < 0 { - err = UDP_Send_Error(win.WSAGetLastError()) - return + for bytes_written < len(buf) { + limit := min(int(max(i32)), len(buf) - bytes_written) + remaining := buf[bytes_written:] + res := win.sendto(win.SOCKET(socket), raw_data(remaining), c.int(limit), 0, &toaddr, size_of(toaddr)) + if res < 0 { + err = _udp_send_error() + return + } + + bytes_written += int(res) } - bytes_written = int(res) return } @(private) -_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) { +_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Shutdown_Error) { s := any_socket_to_socket(socket) res := win.shutdown(win.SOCKET(s), c.int(manner)) if res < 0 { - return Shutdown_Error(win.WSAGetLastError()) + return _shutdown_error() } return } @(private) -_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error { +_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Socket_Option_Error { level := win.SOL_SOCKET if option != .TCP_Nodelay else win.IPPROTO_TCP bool_value: b32 @@ -283,11 +351,8 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca t := value.(time.Duration) or_else panic("set_option() value must be a time.Duration here", loc) num_secs := i64(time.duration_seconds(t)) - if time.Duration(num_secs * 1e9) != t { - return .Linger_Only_Supports_Whole_Seconds - } if num_secs > i64(max(u16)) { - return .Value_Out_Of_Range + return .Invalid_Value } linger_value.l_onoff = 1 linger_value.l_linger = c.ushort(num_secs) @@ -323,19 +388,19 @@ _set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #ca socket := any_socket_to_socket(s) res := win.setsockopt(win.SOCKET(socket), c.int(level), c.int(option), ptr, len) if res < 0 { - return Socket_Option_Error(win.WSAGetLastError()) + return _socket_option_error() } return nil } @(private) -_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) { +_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Set_Blocking_Error) { socket := any_socket_to_socket(socket) arg: win.DWORD = 0 if should_block else 1 res := win.ioctlsocket(win.SOCKET(socket), transmute(win.c_long)win.FIONBIO, &arg) if res == win.SOCKET_ERROR { - return Set_Blocking_Error(win.WSAGetLastError()) + return _set_blocking_error() } return nil diff --git a/core/net/url.odin b/core/net/url.odin index aadcf5e48..41c315585 100644 --- a/core/net/url.odin +++ b/core/net/url.odin @@ -125,7 +125,7 @@ percent_encode :: proc(s: string, allocator := context.allocator) -> string { bytes, n := utf8.encode_rune(ch) for byte in bytes[:n] { buf: [2]u8 = --- - t := strconv.append_int(buf[:], i64(byte), 16) + t := strconv.write_int(buf[:], i64(byte), 16) strings.write_rune(&b, '%') strings.write_string(&b, t) } diff --git a/core/odin/doc-format/doc_format.odin b/core/odin/doc-format/doc_format.odin index c2d86a0ba..e6804c981 100644 --- a/core/odin/doc-format/doc_format.odin +++ b/core/odin/doc-format/doc_format.odin @@ -180,7 +180,7 @@ Type_Kind :: enum u32le { Struct = 10, Union = 11, Enum = 12, - Tuple = 13, + Parameters = 13, Proc = 14, Bit_Set = 15, Simd_Vector = 16, @@ -256,10 +256,10 @@ Type :: struct { types: Array(Type_Index), // Used by: - // .Named - 1 field for the definition - // .Struct - fields - // .Enum - fields - // .Tuple - parameters (procedures only) + // .Named - 1 field for the definition + // .Struct - fields + // .Enum - fields + // .Parameters - parameters (procedures only) entities: Array(Entity_Index), // Used By: .Struct, .Union diff --git a/core/odin/parser/file_tags.odin b/core/odin/parser/file_tags.odin index c5c6637c3..24aea3b9e 100644 --- a/core/odin/parser/file_tags.odin +++ b/core/odin/parser/file_tags.odin @@ -30,14 +30,27 @@ File_Tags :: struct { } @require_results -get_build_os_from_string :: proc(str: string) -> runtime.Odin_OS_Type { +get_build_os_from_string :: proc(str: string) -> (found_os: runtime.Odin_OS_Type, found_subtarget: runtime.Odin_Platform_Subtarget_Type) { + str_os, _, str_subtarget := strings.partition(str, ":") + fields := reflect.enum_fields_zipped(runtime.Odin_OS_Type) for os in fields { - if strings.equal_fold(os.name, str) { - return runtime.Odin_OS_Type(os.value) + if strings.equal_fold(os.name, str_os) { + found_os = runtime.Odin_OS_Type(os.value) + break } } - return .Unknown + if str_subtarget != "" { + st_fields := reflect.enum_fields_zipped(runtime.Odin_Platform_Subtarget_Type) + for subtarget in st_fields { + if strings.equal_fold(subtarget.name, str_subtarget) { + found_subtarget = runtime.Odin_Platform_Subtarget_Type(subtarget.value) + break + } + } + } + + return } @require_results get_build_arch_from_string :: proc(str: string) -> runtime.Odin_Arch_Type { @@ -187,7 +200,8 @@ parse_file_tags :: proc(file: ast.File, allocator := context.allocator) -> (tags if value == "ignore" { tags.ignore = true - } else if os := get_build_os_from_string(value); os != .Unknown { + } else if os, subtarget := get_build_os_from_string(value); os != .Unknown { + _ = subtarget // TODO(bill): figure out how to handle the subtarget logic if is_notted { os_negative += {os} } else { diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index 63c7e388f..dab2d5d6a 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -348,27 +348,30 @@ consume_comment_group :: proc(p: ^Parser, n: int) -> (comments: ^ast.Comment_Gro } consume_comment_groups :: proc(p: ^Parser, prev: tokenizer.Token) { - if p.curr_tok.kind == .Comment { - comment: ^ast.Comment_Group - end_line := 0 - - if p.curr_tok.pos.line == prev.pos.line { - comment, end_line = consume_comment_group(p, 0) - if p.curr_tok.pos.line != end_line || p.curr_tok.kind == .EOF { - p.line_comment = comment - } - } + if p.curr_tok.kind != .Comment { + return + } + comment: ^ast.Comment_Group + end_line := 0 - end_line = -1 - for p.curr_tok.kind == .Comment { - comment, end_line = consume_comment_group(p, 1) - } - if end_line+1 >= p.curr_tok.pos.line || end_line < 0 { - p.lead_comment = comment + if p.curr_tok.pos.line == prev.pos.line { + comment, end_line = consume_comment_group(p, 0) + if p.curr_tok.pos.line != end_line || + p.curr_tok.pos.line == prev.pos.line+1 || + p.curr_tok.kind == .EOF { + p.line_comment = comment } + } - assert(p.curr_tok.kind != .Comment) + end_line = -1 + for p.curr_tok.kind == .Comment { + comment, end_line = consume_comment_group(p, 1) + } + if end_line+1 >= p.curr_tok.pos.line || end_line < 0 { + p.lead_comment = comment } + + assert(p.curr_tok.kind != .Comment) } advance_token :: proc(p: ^Parser) -> tokenizer.Token { @@ -1276,28 +1279,28 @@ parse_unrolled_for_loop :: proc(p: ^Parser, inline_tok: tokenizer.Token) -> ^ast args = make([dynamic]^ast.Expr) for p.curr_tok.kind != .Close_Paren && p.curr_tok.kind != .EOF { - arg := parse_value(p) - - if p.curr_tok.kind == .Eq { - eq := expect_token(p, .Eq) - if arg != nil { - if _, ok := arg.derived.(^ast.Ident); !ok { - error(p, arg.pos, "expected an identifier for 'key=value'") - } - } - value := parse_value(p) - fv := ast.new(ast.Field_Value, arg.pos, value) - fv.field = arg - fv.sep = eq.pos - fv.value = value - - arg = fv - } - - append(&args, arg) + arg := parse_value(p) + + if p.curr_tok.kind == .Eq { + eq := expect_token(p, .Eq) + if arg != nil { + if _, ok := arg.derived.(^ast.Ident); !ok { + error(p, arg.pos, "expected an identifier for 'key=value'") + } + } + value := parse_value(p) + fv := ast.new(ast.Field_Value, arg.pos, value) + fv.field = arg + fv.sep = eq.pos + fv.value = value + + arg = fv + } + + append(&args, arg) allow_token(p, .Comma) or_break - } + } } p.expr_level -= 1 @@ -2307,6 +2310,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { open := expect_token(p, .Open_Paren) p.expr_level += 1 expr := parse_expr(p, false) + skip_possible_newline(p) p.expr_level -= 1 close := expect_token(p, .Close_Paren) @@ -2481,7 +2485,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { allow_token(p, .Comma) or_break } - close := expect_token(p, .Close_Brace) + close := expect_closing_brace_of_field_list(p) if len(args) == 0 { error(p, tok.pos, "expected at least 1 argument in procedure group") @@ -2922,6 +2926,8 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { fields: [dynamic]^ast.Bit_Field_Field for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF { + docs := p.lead_comment + name := parse_ident(p) expect_token(p, .Colon) type := parse_type(p) @@ -2932,6 +2938,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { if p.curr_tok.kind == .String { tag = expect_token(p, .String) } + ok := allow_token(p, .Comma) field := ast.new(ast.Bit_Field_Field, name.pos, bit_size) @@ -2939,10 +2946,14 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { field.type = type field.bit_size = bit_size field.tag = tag + field.docs = docs + field.comments = p.line_comment append(&fields, field) - allow_token(p, .Comma) or_break + if !ok { + break + } } close := expect_closing_brace_of_field_list(p) @@ -3526,6 +3537,7 @@ parse_binary_expr :: proc(p: ^Parser, lhs: bool, prec_in: int) -> ^ast.Expr { case .When: x := expr cond := parse_expr(p, lhs) + skip_possible_newline(p) else_tok := expect_token(p, .Else) y := parse_expr(p, lhs) te := ast.new(ast.Ternary_When_Expr, expr.pos, end_pos(p.prev_tok)) @@ -3780,10 +3792,6 @@ parse_import_decl :: proc(p: ^Parser, kind := Import_Decl_Kind.Standard) -> ^ast import_name.pos = p.curr_tok.pos } - if !is_using && is_blank_ident(import_name) { - error(p, import_name.pos, "illegal import name: '_'") - } - path := expect_token_after(p, .String, "import") decl := ast.new(ast.Import_Decl, tok.pos, end_pos(path)) diff --git a/core/odin/tokenizer/tokenizer.odin b/core/odin/tokenizer/tokenizer.odin index d4da82c56..a9d367a4d 100644 --- a/core/odin/tokenizer/tokenizer.odin +++ b/core/odin/tokenizer/tokenizer.odin @@ -209,14 +209,14 @@ scan_comment :: proc(t: ^Tokenizer) -> string { scan_file_tag :: proc(t: ^Tokenizer) -> string { offset := t.offset - 1 - for t.ch != '\n' { + for t.ch != '\n' && t.ch != utf8.RUNE_EOF { if t.ch == '/' { next := peek_byte(t, 0) if next == '/' || next == '*' { break } - } + } advance_rune(t) } diff --git a/core/os/dir_unix.odin b/core/os/dir_unix.odin index f06bf8b37..c3dd844ef 100644 --- a/core/os/dir_unix.odin +++ b/core/os/dir_unix.odin @@ -5,9 +5,10 @@ import "core:strings" @(require_results) read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Error) { - dupfd := _dup(fd) or_return + context.allocator = allocator - dirp := _fdopendir(dupfd) or_return + dupfd := _dup(fd) or_return + dirp := _fdopendir(dupfd) or_return defer _closedir(dirp) dirpath := absolute_path_from_handle(dupfd) or_return diff --git a/core/os/dir_windows.odin b/core/os/dir_windows.odin index ae3e6922c..40f4b9e9b 100644 --- a/core/os/dir_windows.odin +++ b/core/os/dir_windows.odin @@ -87,7 +87,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F defer delete(path) find_data := &win32.WIN32_FIND_DATAW{} - find_handle := win32.FindFirstFileW(raw_data(wpath_search), find_data) + find_handle := win32.FindFirstFileW(cstring16(raw_data(wpath_search)), find_data) if find_handle == win32.INVALID_HANDLE_VALUE { err = get_last_error() return dfi[:], err diff --git a/core/os/env_windows.odin b/core/os/env_windows.odin index efd002342..ef658b0a1 100644 --- a/core/os/env_windows.odin +++ b/core/os/env_windows.odin @@ -8,7 +8,7 @@ import "base:runtime" // Otherwise the returned value will be empty and the boolean will be false // NOTE: the value will be allocated with the supplied allocator @(require_results) -lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { if key == "" { return } @@ -29,17 +29,54 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin return } +// This version of `lookup_env` doesn't allocate and instead requires the user to provide a buffer. +// Note that it is limited to environment names and values of 512 utf-16 values each +// due to the necessary utf-8 <> utf-16 conversion. +@(require_results) +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + key_buf: [513]u16 + wkey := win32.utf8_to_wstring(key_buf[:], key) + if wkey == nil { + return "", .Buffer_Full + } + + n2 := win32.GetEnvironmentVariableW(wkey, nil, 0) + if n2 == 0 { + return "", .Env_Var_Not_Found + } + + val_buf: [513]u16 + n2 = win32.GetEnvironmentVariableW(wkey, raw_data(val_buf[:]), u32(len(val_buf[:]))) + if n2 == 0 { + return "", .Env_Var_Not_Found + } else if int(n2) > len(buf) { + return "", .Buffer_Full + } + + value = win32.utf16_to_utf8(buf, val_buf[:n2]) + + return value, nil +} +lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} // get_env retrieves the value of the environment variable named by the key // It returns the value, which will be empty if the variable is not present // To distinguish between an empty value and an unset value, use lookup_env // NOTE: the value will be allocated with the supplied allocator @(require_results) -get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { +get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } +@(require_results) +get_env_buf :: proc(buf: []u8, key: string) -> (value: string) { + value, _ = lookup_env(buf, key) + return +} +get_env :: proc{get_env_alloc, get_env_buf} + + // set_env sets the value of the environment variable named by the key set_env :: proc(key, value: string) -> Error { k := win32.utf8_to_wstring(key) diff --git a/core/os/errors.odin b/core/os/errors.odin index 691397f4b..bf4cf27ff 100644 --- a/core/os/errors.odin +++ b/core/os/errors.odin @@ -35,6 +35,9 @@ General_Error :: enum u32 { File_Is_Pipe, Not_Dir, + + // Environment variable not found. + Env_Var_Not_Found, } @@ -82,6 +85,7 @@ error_string :: proc "contextless" (ferr: Error) -> string { case .Pattern_Has_Separator: return "pattern has separator" case .File_Is_Pipe: return "file is pipe" case .Not_Dir: return "file is not directory" + case .Env_Var_Not_Found: return "environment variable not found" } case io.Error: switch e { diff --git a/core/os/os.odin b/core/os/os.odin index 30b86d4cd..fe08edff4 100644 --- a/core/os/os.odin +++ b/core/os/os.odin @@ -4,6 +4,7 @@ import "base:intrinsics" import "base:runtime" import "core:io" import "core:strconv" +import "core:strings" import "core:unicode/utf8" @@ -57,7 +58,7 @@ write_encoded_rune :: proc(f: Handle, r: rune) -> (n: int, err: Error) { if r < 32 { if wrap(write_string(f, "\\x"), &n, &err) { return } b: [2]byte - s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil) + s := strconv.write_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil) switch len(s) { case 0: if wrap(write_string(f, "00"), &n, &err) { return } case 1: if wrap(write_rune(f, '0'), &n, &err) { return } @@ -210,3 +211,55 @@ heap_free :: runtime.heap_free processor_core_count :: proc() -> int { return _processor_core_count() } + +// Always allocates for consistency. +replace_environment_placeholders :: proc(path: string, allocator := context.allocator) -> (res: string) { + path := path + + sb: strings.Builder + strings.builder_init_none(&sb, allocator) + for len(path) > 0 { + switch path[0] { + case '%': // Windows + when ODIN_OS == .Windows { + for r, i in path[1:] { + if r == '%' { + env_key := path[1:i+1] + env_val := get_env(env_key, context.temp_allocator) + strings.write_string(&sb, env_val) + path = path[i+1:] // % is part of key, so skip 1 character extra + } + } + } else { + strings.write_rune(&sb, rune(path[0])) + } + + case '$': // Posix + when ODIN_OS != .Windows { + env_key := "" + dollar_loop: for r, i in path[1:] { + switch r { + case 'A'..='Z', 'a'..='z', '0'..='9', '_': // Part of key ident + case: + env_key = path[1:i+1] + break dollar_loop + } + } + if len(env_key) > 0 { + env_val := get_env(env_key, context.temp_allocator) + strings.write_string(&sb, env_val) + path = path[len(env_key):] + } + + } else { + strings.write_rune(&sb, rune(path[0])) + } + + case: + strings.write_rune(&sb, rune(path[0])) + } + + path = path[1:] + } + return strings.to_string(sb) +}
\ No newline at end of file diff --git a/core/os/os2/allocators.odin b/core/os/os2/allocators.odin index 864532850..36a7d72be 100644 --- a/core/os/os2/allocators.odin +++ b/core/os/os2/allocators.odin @@ -8,66 +8,67 @@ file_allocator :: proc() -> runtime.Allocator { return heap_allocator() } -temp_allocator_proc :: runtime.arena_allocator_proc - @(private="file") MAX_TEMP_ARENA_COUNT :: 2 - +@(private="file") +MAX_TEMP_ARENA_COLLISIONS :: MAX_TEMP_ARENA_COUNT - 1 @(private="file", thread_local) global_default_temp_allocator_arenas: [MAX_TEMP_ARENA_COUNT]runtime.Arena -@(private="file", thread_local) -global_default_temp_allocator_index: uint - - -@(require_results) -temp_allocator :: proc() -> runtime.Allocator { - arena := &global_default_temp_allocator_arenas[global_default_temp_allocator_index] - if arena.backing_allocator.procedure == nil { - arena.backing_allocator = heap_allocator() - } - - return runtime.Allocator{ - procedure = temp_allocator_proc, - data = arena, - } -} - - - -@(require_results) -temp_allocator_temp_begin :: proc(loc := #caller_location) -> (temp: runtime.Arena_Temp) { - temp = runtime.arena_temp_begin(&global_default_temp_allocator_arenas[global_default_temp_allocator_index], loc) - return -} - -temp_allocator_temp_end :: proc(temp: runtime.Arena_Temp, loc := #caller_location) { - runtime.arena_temp_end(temp, loc) -} - @(fini, private) -temp_allocator_fini :: proc() { +temp_allocator_fini :: proc "contextless" () { for &arena in global_default_temp_allocator_arenas { runtime.arena_destroy(&arena) } global_default_temp_allocator_arenas = {} } -TEMP_ALLOCATOR_GUARD_END :: proc(temp: runtime.Arena_Temp, loc := #caller_location) { - runtime.arena_temp_end(temp, loc) - if temp.arena != nil { - global_default_temp_allocator_index = (global_default_temp_allocator_index-1)%MAX_TEMP_ARENA_COUNT - } +Temp_Allocator :: struct { + using arena: ^runtime.Arena, + using allocator: runtime.Allocator, + tmp: runtime.Arena_Temp, + loc: runtime.Source_Code_Location, +} + +TEMP_ALLOCATOR_GUARD_END :: proc(temp: Temp_Allocator) { + runtime.arena_temp_end(temp.tmp, temp.loc) } @(deferred_out=TEMP_ALLOCATOR_GUARD_END) -TEMP_ALLOCATOR_GUARD :: #force_inline proc(loc := #caller_location) -> (runtime.Arena_Temp, runtime.Source_Code_Location) { - global_default_temp_allocator_index = (global_default_temp_allocator_index+1)%MAX_TEMP_ARENA_COUNT - tmp := temp_allocator_temp_begin(loc) - return tmp, loc +TEMP_ALLOCATOR_GUARD :: #force_inline proc(collisions: []runtime.Allocator, loc := #caller_location) -> Temp_Allocator { + assert(len(collisions) <= MAX_TEMP_ARENA_COLLISIONS, "Maximum collision count exceeded. MAX_TEMP_ARENA_COUNT must be increased!") + good_arena: ^runtime.Arena + for i in 0..<MAX_TEMP_ARENA_COUNT { + good_arena = &global_default_temp_allocator_arenas[i] + for c in collisions { + if good_arena == c.data { + good_arena = nil + } + } + if good_arena != nil { + break + } + } + assert(good_arena != nil) + if good_arena.backing_allocator.procedure == nil { + good_arena.backing_allocator = heap_allocator() + } + tmp := runtime.arena_temp_begin(good_arena, loc) + return { good_arena, runtime.arena_allocator(good_arena), tmp, loc } +} + +temp_allocator_begin :: runtime.arena_temp_begin +temp_allocator_end :: runtime.arena_temp_end +@(deferred_out=_temp_allocator_end) +temp_allocator_scope :: proc(tmp: Temp_Allocator) -> (runtime.Arena_Temp) { + return temp_allocator_begin(tmp.arena) +} +@(private="file") +_temp_allocator_end :: proc(tmp: runtime.Arena_Temp) { + temp_allocator_end(tmp) } @(init, private) -init_thread_local_cleaner :: proc() { +init_thread_local_cleaner :: proc "contextless" () { runtime.add_thread_local_cleaner(temp_allocator_fini) } diff --git a/core/os/os2/dir.odin b/core/os/os2/dir.odin index 4a7762ded..10b06a8ce 100644 --- a/core/os/os2/dir.odin +++ b/core/os/os2/dir.odin @@ -2,6 +2,7 @@ package os2 import "base:runtime" import "core:slice" +import "core:strings" read_dir :: read_directory @@ -18,12 +19,12 @@ read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files size = 100 } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) it := read_directory_iterator_create(f) defer _read_directory_iterator_destroy(&it) - dfi := make([dynamic]File_Info, 0, size, temp_allocator()) + dfi := make([dynamic]File_Info, 0, size, temp_allocator) defer if err != nil { for fi in dfi { file_info_delete(fi, allocator) @@ -192,3 +193,56 @@ read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, return _read_directory_iterator(it) } + +// Recursively copies a directory to `dst` from `src` +copy_directory_all :: proc(dst, src: string, dst_perm := 0o755) -> Error { + when #defined(_copy_directory_all_native) { + return _copy_directory_all_native(dst, src, dst_perm) + } else { + return _copy_directory_all(dst, src, dst_perm) + } +} + +@(private) +_copy_directory_all :: proc(dst, src: string, dst_perm := 0o755) -> Error { + err := make_directory(dst, dst_perm) + if err != nil && err != .Exist { + return err + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + abs_src := get_absolute_path(src, temp_allocator) or_return + abs_dst := get_absolute_path(dst, temp_allocator) or_return + + dst_buf := make([dynamic]byte, 0, len(abs_dst) + 256, temp_allocator) or_return + + w: Walker + walker_init_path(&w, src) + defer walker_destroy(&w) + + for info in walker_walk(&w) { + _ = walker_error(&w) or_break + + rel := strings.trim_prefix(info.fullpath, abs_src) + + non_zero_resize(&dst_buf, 0) + reserve(&dst_buf, len(abs_dst) + len(Path_Separator_String) + len(rel)) or_return + append(&dst_buf, abs_dst) + append(&dst_buf, Path_Separator_String) + append(&dst_buf, rel) + + if info.type == .Directory { + err = make_directory(string(dst_buf[:]), dst_perm) + if err != nil && err != .Exist { + return err + } + } else { + copy_file(string(dst_buf[:]), info.fullpath) or_return + } + } + + _ = walker_error(&w) or_return + + return nil +} diff --git a/core/os/os2/dir_linux.odin b/core/os/os2/dir_linux.odin index a868a02c4..34346c02f 100644 --- a/core/os/os2/dir_linux.odin +++ b/core/os/os2/dir_linux.odin @@ -78,7 +78,8 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info it.impl.prev_fi = fi if err != nil { - path, _ := _get_full_path(entry_fd, temp_allocator()) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + path, _ := _get_full_path(entry_fd, temp_allocator) read_directory_iterator_set_error(it, path, err) } diff --git a/core/os/os2/dir_posix_darwin.odin b/core/os/os2/dir_posix_darwin.odin new file mode 100644 index 000000000..3cae50d25 --- /dev/null +++ b/core/os/os2/dir_posix_darwin.odin @@ -0,0 +1,17 @@ +#+private +package os2 + +import "core:sys/darwin" + +_copy_directory_all_native :: proc(dst, src: string, dst_perm := 0o755) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + csrc := clone_to_cstring(src, temp_allocator) or_return + cdst := clone_to_cstring(dst, temp_allocator) or_return + + if darwin.copyfile(csrc, cdst, nil, darwin.COPYFILE_ALL + {.RECURSIVE}) < 0 { + err = _get_platform_error() + } + + return +} diff --git a/core/os/os2/dir_windows.odin b/core/os/os2/dir_windows.odin index d592e8036..6c754a677 100644 --- a/core/os/os2/dir_windows.odin +++ b/core/os/os2/dir_windows.odin @@ -14,7 +14,9 @@ find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, al if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 { return } - path := concatenate({base_path, `\`, win32_utf16_to_utf8(d.cFileName[:], temp_allocator()) or_else ""}, allocator) or_return + + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + path := concatenate({base_path, `\`, win32_wstring_to_utf8(cstring16(raw_data(d.cFileName[:])), temp_allocator) or_else ""}, allocator) or_return handle := win32.HANDLE(_open_internal(path, {.Read}, 0o666) or_else 0) defer win32.CloseHandle(handle) @@ -49,8 +51,6 @@ Read_Directory_Iterator_Impl :: struct { @(require_results) _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { - TEMP_ALLOCATOR_GUARD() - for !it.impl.no_more_files { err: Error file_info_delete(it.impl.prev_fi, file_allocator()) @@ -107,24 +107,16 @@ _read_directory_iterator_init :: proc(it: ^Read_Directory_Iterator, f: ^File) { return } - wpath: []u16 - { - i := 0 - for impl.wname[i] != 0 { - i += 1 - } - wpath = impl.wname[:i] - } - - TEMP_ALLOCATOR_GUARD() + wpath := string16(impl.wname) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) - wpath_search := make([]u16, len(wpath)+3, temp_allocator()) + wpath_search := make([]u16, len(wpath)+3, temp_allocator) copy(wpath_search, wpath) wpath_search[len(wpath)+0] = '\\' wpath_search[len(wpath)+1] = '*' wpath_search[len(wpath)+2] = 0 - it.impl.find_handle = win32.FindFirstFileW(raw_data(wpath_search), &it.impl.find_data) + it.impl.find_handle = win32.FindFirstFileW(cstring16(raw_data(wpath_search)), &it.impl.find_data) if it.impl.find_handle == win32.INVALID_HANDLE_VALUE { read_directory_iterator_set_error(it, impl.name, _get_platform_error()) return diff --git a/core/os/os2/env.odin b/core/os/os2/env.odin index 13c107f3c..310d45af1 100644 --- a/core/os/os2/env.odin +++ b/core/os/os2/env.odin @@ -1,26 +1,49 @@ package os2 import "base:runtime" +import "core:strings" -// get_env retrieves the value of the environment variable named by the key +// `get_env` retrieves the value of the environment variable named by the key // It returns the value, which will be empty if the variable is not present // To distinguish between an empty value and an unset value, use lookup_env // NOTE: the value will be allocated with the supplied allocator @(require_results) -get_env :: proc(key: string, allocator: runtime.Allocator) -> string { +get_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> string { value, _ := lookup_env(key, allocator) return value } -// lookup_env gets the value of the environment variable named by the key +// `get_env` retrieves the value of the environment variable named by the key +// It returns the value, which will be empty if the variable is not present +// To distinguish between an empty value and an unset value, use lookup_env +// NOTE: this version takes a backing buffer for the string value +@(require_results) +get_env_buf :: proc(buf: []u8, key: string) -> string { + value, _ := lookup_env(buf, key) + return value +} + +get_env :: proc{get_env_alloc, get_env_buf} + +// `lookup_env` gets the value of the environment variable named by the key // If the variable is found in the environment the value (which can be empty) is returned and the boolean is true // Otherwise the returned value will be empty and the boolean will be false // NOTE: the value will be allocated with the supplied allocator @(require_results) -lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { - return _lookup_env(key, allocator) +lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { + return _lookup_env_alloc(key, allocator) +} + +// This version of `lookup_env` doesn't allocate and instead requires the user to provide a buffer. +// Note that it is limited to environment names and values of 512 utf-16 values each +// due to the necessary utf-8 <> utf-16 conversion. +@(require_results) +lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + return _lookup_env_buf(buf, key) } +lookup_env :: proc{lookup_env_alloc, lookup_env_buf} + // set_env sets the value of the environment variable named by the key // Returns Error on failure set_env :: proc(key, value: string) -> Error { @@ -45,4 +68,55 @@ environ :: proc(allocator: runtime.Allocator) -> ([]string, Error) { return _environ(allocator) } +// Always allocates for consistency. +replace_environment_placeholders :: proc(path: string, allocator: runtime.Allocator) -> (res: string) { + path := path + + sb: strings.Builder + strings.builder_init_none(&sb, allocator) + + for len(path) > 0 { + switch path[0] { + case '%': // Windows + when ODIN_OS == .Windows { + for r, i in path[1:] { + if r == '%' { + env_key := path[1:i+1] + env_val := get_env(env_key, context.temp_allocator) + strings.write_string(&sb, env_val) + path = path[i+1:] // % is part of key, so skip 1 character extra + } + } + } else { + strings.write_rune(&sb, rune(path[0])) + } + + case '$': // Posix + when ODIN_OS != .Windows { + env_key := "" + dollar_loop: for r, i in path[1:] { + switch r { + case 'A'..='Z', 'a'..='z', '0'..='9', '_': // Part of key ident + case: + env_key = path[1:i+1] + break dollar_loop + } + } + if len(env_key) > 0 { + env_val := get_env(env_key, context.temp_allocator) + strings.write_string(&sb, env_val) + path = path[len(env_key):] + } + + } else { + strings.write_rune(&sb, rune(path[0])) + } + + case: + strings.write_rune(&sb, rune(path[0])) + } + path = path[1:] + } + return strings.to_string(sb) +}
\ No newline at end of file diff --git a/core/os/os2/env_linux.odin b/core/os/os2/env_linux.odin index f24bd8d84..e003b0717 100644 --- a/core/os/os2/env_linux.odin +++ b/core/os/os2/env_linux.odin @@ -20,19 +20,18 @@ NOT_FOUND :: -1 // the environment is a 0 delimited list of <key>=<value> strings _env: [dynamic]string -_env_mutex: sync.Mutex +_env_mutex: sync.Recursive_Mutex // We need to be able to figure out if the environment variable // is contained in the original environment or not. This also // serves as a flag to determine if we have built _env. -_org_env_begin: uintptr -_org_env_end: uintptr +_org_env_begin: uintptr // atomic +_org_env_end: uintptr // guarded by _env_mutex // Returns value + index location into _env // or -1 if not found _lookup :: proc(key: string) -> (value: string, idx: int) { - sync.mutex_lock(&_env_mutex) - defer sync.mutex_unlock(&_env_mutex) + sync.guard(&_env_mutex) for entry, i in _env { if k, v := _kv_from_entry(entry); k == key { @@ -42,8 +41,8 @@ _lookup :: proc(key: string) -> (value: string, idx: int) { return "", -1 } -_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { - if _org_env_begin == 0 { +_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { + if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { _build_env() } @@ -54,10 +53,28 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string return } +_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { + _build_env() + } + + if v, idx := _lookup(key); idx != -1 { + if len(buf) >= len(v) { + copy(buf, v) + return string(buf[:len(v)]), nil + } + return "", .Buffer_Full + } + return "", .Env_Var_Not_Found +} + +_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf} + _set_env :: proc(key, v_new: string) -> Error { - if _org_env_begin == 0 { + if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { _build_env() } + sync.guard(&_env_mutex) // all key values are stored as "key=value\x00" kv_size := len(key) + len(v_new) + 2 @@ -65,8 +82,6 @@ _set_env :: proc(key, v_new: string) -> Error { if v_curr == v_new { return nil } - sync.mutex_lock(&_env_mutex) - defer sync.mutex_unlock(&_env_mutex) unordered_remove(&_env, idx) @@ -101,16 +116,15 @@ _set_env :: proc(key, v_new: string) -> Error { intrinsics.mem_copy_non_overlapping(&val_slice[0], raw_data(v_new), len(v_new)) val_slice[len(v_new)] = 0 - sync.mutex_lock(&_env_mutex) append(&_env, string(k_addr[:kv_size - 1])) - sync.mutex_unlock(&_env_mutex) return nil } _unset_env :: proc(key: string) -> bool { - if _org_env_begin == 0 { + if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { _build_env() } + sync.guard(&_env_mutex) v: string i: int @@ -118,15 +132,13 @@ _unset_env :: proc(key: string) -> bool { return false } - sync.mutex_lock(&_env_mutex) unordered_remove(&_env, i) - sync.mutex_unlock(&_env_mutex) if _is_in_org_env(v) { return true } - // if we got this far, the envrionment variable + // if we got this far, the environment variable // existed AND was allocated by us. k_addr, _ := _kv_addr_from_val(v, key) runtime.heap_free(k_addr) @@ -134,8 +146,7 @@ _unset_env :: proc(key: string) -> bool { } _clear_env :: proc() { - sync.mutex_lock(&_env_mutex) - defer sync.mutex_unlock(&_env_mutex) + sync.guard(&_env_mutex) for kv in _env { if !_is_in_org_env(kv) { @@ -145,14 +156,16 @@ _clear_env :: proc() { clear(&_env) // nothing resides in the original environment either - _org_env_begin = ~uintptr(0) + intrinsics.atomic_store_explicit(&_org_env_begin, ~uintptr(0), .Release) _org_env_end = ~uintptr(0) } _environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error) { - if _org_env_begin == 0 { + if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { _build_env() } + sync.guard(&_env_mutex) + env := make([dynamic]string, 0, len(_env), allocator) or_return defer if err != nil { for e in env { @@ -161,8 +174,6 @@ _environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error delete(env) } - sync.mutex_lock(&_env_mutex) - defer sync.mutex_unlock(&_env_mutex) for entry in _env { s := clone_string(entry, allocator) or_return append(&env, s) @@ -174,7 +185,7 @@ _environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error // The entire environment is stored as 0 terminated strings, // so there is no need to clone/free individual variables export_cstring_environment :: proc(allocator: runtime.Allocator) -> []cstring { - if _org_env_begin == 0 { + if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) == 0 { // The environment has not been modified, so we can just // send the original environment org_env := _get_original_env() @@ -182,12 +193,11 @@ export_cstring_environment :: proc(allocator: runtime.Allocator) -> []cstring { for ; org_env[n] != nil; n += 1 {} return slice.clone(org_env[:n + 1], allocator) } + sync.guard(&_env_mutex) // NOTE: already terminated by nil pointer via + 1 env := make([]cstring, len(_env) + 1, allocator) - sync.mutex_lock(&_env_mutex) - defer sync.mutex_unlock(&_env_mutex) for entry, i in _env { env[i] = cstring(raw_data(entry)) } @@ -195,15 +205,14 @@ export_cstring_environment :: proc(allocator: runtime.Allocator) -> []cstring { } _build_env :: proc() { - sync.mutex_lock(&_env_mutex) - defer sync.mutex_unlock(&_env_mutex) - if _org_env_begin != 0 { + sync.guard(&_env_mutex) + if intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) != 0 { return } _env = make(type_of(_env), runtime.heap_allocator()) cstring_env := _get_original_env() - _org_env_begin = uintptr(rawptr(cstring_env[0])) + intrinsics.atomic_store_explicit(&_org_env_begin, uintptr(rawptr(cstring_env[0])), .Release) for i := 0; cstring_env[i] != nil; i += 1 { bytes := ([^]u8)(cstring_env[i]) n := len(cstring_env[i]) @@ -235,5 +244,5 @@ _kv_addr_from_val :: #force_inline proc(val: string, key: string) -> ([^]u8, [^] _is_in_org_env :: #force_inline proc(env_data: string) -> bool { addr := uintptr(raw_data(env_data)) - return addr >= _org_env_begin && addr < _org_env_end + return addr >= intrinsics.atomic_load_explicit(&_org_env_begin, .Acquire) && addr < _org_env_end } diff --git a/core/os/os2/env_posix.odin b/core/os/os2/env_posix.odin index 3db8d817a..84500d139 100644 --- a/core/os/os2/env_posix.odin +++ b/core/os/os2/env_posix.odin @@ -7,14 +7,14 @@ import "base:runtime" import "core:strings" import "core:sys/posix" -_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { +_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { if key == "" { return } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - ckey := strings.clone_to_cstring(key, temp_allocator()) + ckey := strings.clone_to_cstring(key, temp_allocator) cval := posix.getenv(ckey) if cval == nil { return @@ -26,11 +26,41 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string return } +_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, error: Error) { + if key == "" { + return + } + + if len(key) + 1 > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, key) + } + + cval := posix.getenv(cstring(raw_data(buf))) + if cval == nil { + return + } + + if value = string(cval); value == "" { + return "", .Env_Var_Not_Found + } else { + if len(value) > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, value) + return string(buf[:len(value)]), nil + } + } +} + +_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf} + _set_env :: proc(key, value: string) -> (err: Error) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) - ckey := strings.clone_to_cstring(key, temp_allocator()) or_return - cval := strings.clone_to_cstring(key, temp_allocator()) or_return + ckey := strings.clone_to_cstring(key, temp_allocator) or_return + cval := strings.clone_to_cstring(value, temp_allocator) or_return if posix.setenv(ckey, cval, true) != nil { err = _get_platform_error_from_errno() @@ -39,9 +69,9 @@ _set_env :: proc(key, value: string) -> (err: Error) { } _unset_env :: proc(key: string) -> (ok: bool) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) - ckey := strings.clone_to_cstring(key, temp_allocator()) + ckey := strings.clone_to_cstring(key, temp_allocator) ok = posix.unsetenv(ckey) == .OK return @@ -57,7 +87,7 @@ _clear_env :: proc() { } _environ :: proc(allocator: runtime.Allocator) -> (environ: []string, err: Error) { - n := 0 + n := 0 for entry := posix.environ[0]; entry != nil; n, entry = n+1, posix.environ[n] {} r := make([dynamic]string, 0, n, allocator) or_return diff --git a/core/os/os2/env_wasi.odin b/core/os/os2/env_wasi.odin index 305192c92..b126d014e 100644 --- a/core/os/os2/env_wasi.odin +++ b/core/os/os2/env_wasi.odin @@ -39,9 +39,9 @@ build_env :: proc() -> (err: Error) { g_env_buf = make([]byte, size_of_envs, file_allocator()) or_return defer if err != nil { delete(g_env_buf, file_allocator()) } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) - envs := make([]cstring, num_envs, temp_allocator()) or_return + envs := make([]cstring, num_envs, temp_allocator) or_return _err = wasi.environ_get(raw_data(envs), raw_data(g_env_buf)) if _err != nil { @@ -67,7 +67,7 @@ delete_string_if_not_original :: proc(str: string) { } @(require_results) -_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { +_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { if err := build_env(); err != nil { return } @@ -79,6 +79,34 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string return } +_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, error: Error) { + if key == "" { + return + } + + if len(key) + 1 > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, key) + } + + sync.shared_guard(&g_env_mutex) + + val, ok := g_env[key] + + if !ok { + return "", .Env_Var_Not_Found + } else { + if len(val) > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, val) + return string(buf[:len(val)]), nil + } + } +} +_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf} + @(require_results) _set_env :: proc(key, value: string) -> (err: Error) { build_env() or_return diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin index 3ac26a261..d389f8860 100644 --- a/core/os/os2/env_windows.odin +++ b/core/os/os2/env_windows.odin @@ -4,12 +4,12 @@ package os2 import win32 "core:sys/windows" import "base:runtime" -_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { +_lookup_env_alloc :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { if key == "" { return } - TEMP_ALLOCATOR_GUARD() - wkey, _ := win32_utf8_to_wstring(key, temp_allocator()) + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + wkey, _ := win32_utf8_to_wstring(key, temp_allocator) n := win32.GetEnvironmentVariableW(wkey, nil, 0) if n == 0 { @@ -20,7 +20,7 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string return "", true } - b := make([]u16, n+1, temp_allocator()) + b := make([]u16, n+1, temp_allocator) n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b))) if n == 0 { @@ -31,15 +31,45 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string return "", false } - value = win32_utf16_to_utf8(b[:n], allocator) or_else "" + value = win32_utf16_to_utf8(string16(b[:n]), allocator) or_else "" found = true return } +// This version of `lookup_env` doesn't allocate and instead requires the user to provide a buffer. +// Note that it is limited to environment names and values of 512 utf-16 values each +// due to the necessary utf-8 <> utf-16 conversion. +@(require_results) +_lookup_env_buf :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + key_buf: [513]u16 + wkey := win32.utf8_to_wstring(key_buf[:], key) + if wkey == nil { + return "", .Buffer_Full + } + + n2 := win32.GetEnvironmentVariableW(wkey, nil, 0) + if n2 == 0 { + return "", .Env_Var_Not_Found + } + + val_buf: [513]u16 + n2 = win32.GetEnvironmentVariableW(wkey, raw_data(val_buf[:]), u32(len(val_buf[:]))) + if n2 == 0 { + return "", .Env_Var_Not_Found + } else if int(n2) > len(buf) { + return "", .Buffer_Full + } + + value = win32.utf16_to_utf8(buf, val_buf[:n2]) + + return value, nil +} +_lookup_env :: proc{_lookup_env_alloc, _lookup_env_buf} + _set_env :: proc(key, value: string) -> Error { - TEMP_ALLOCATOR_GUARD() - k := win32_utf8_to_wstring(key, temp_allocator()) or_return - v := win32_utf8_to_wstring(value, temp_allocator()) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + k := win32_utf8_to_wstring(key, temp_allocator) or_return + v := win32_utf8_to_wstring(value, temp_allocator) or_return if !win32.SetEnvironmentVariableW(k, v) { return _get_platform_error() @@ -48,14 +78,14 @@ _set_env :: proc(key, value: string) -> Error { } _unset_env :: proc(key: string) -> bool { - TEMP_ALLOCATOR_GUARD() - k, _ := win32_utf8_to_wstring(key, temp_allocator()) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + k, _ := win32_utf8_to_wstring(key, temp_allocator) return bool(win32.SetEnvironmentVariableW(k, nil)) } _clear_env :: proc() { - TEMP_ALLOCATOR_GUARD() - envs, _ := environ(temp_allocator()) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + envs, _ := environ(temp_allocator) for env in envs { for j in 1..<len(env) { if env[j] == '=' { diff --git a/core/os/os2/errors.odin b/core/os/os2/errors.odin index 8a2163634..1cf7d765c 100644 --- a/core/os/os2/errors.odin +++ b/core/os/os2/errors.odin @@ -27,6 +27,9 @@ General_Error :: enum u32 { Pattern_Has_Separator, + No_HOME_Variable, + Env_Var_Not_Found, + Unsupported, } @@ -59,20 +62,22 @@ error_string :: proc(ferr: Error) -> string { case General_Error: switch e { case .None: return "" - case .Permission_Denied: return "permission denied" - case .Exist: return "file already exists" - case .Not_Exist: return "file does not exist" - case .Closed: return "file already closed" - case .Timeout: return "i/o timeout" - case .Broken_Pipe: return "Broken pipe" - case .No_Size: return "file has no definite size" - case .Invalid_File: return "invalid file" - case .Invalid_Dir: return "invalid directory" - case .Invalid_Path: return "invalid path" - case .Invalid_Callback: return "invalid callback" - case .Invalid_Command: return "invalid command" - case .Unsupported: return "unsupported" - case .Pattern_Has_Separator: return "pattern has separator" + case .Permission_Denied: return "permission denied" + case .Exist: return "file already exists" + case .Not_Exist: return "file does not exist" + case .Closed: return "file already closed" + case .Timeout: return "i/o timeout" + case .Broken_Pipe: return "Broken pipe" + case .No_Size: return "file has no definite size" + case .Invalid_File: return "invalid file" + case .Invalid_Dir: return "invalid directory" + case .Invalid_Path: return "invalid path" + case .Invalid_Callback: return "invalid callback" + case .Invalid_Command: return "invalid command" + case .Unsupported: return "unsupported" + case .Pattern_Has_Separator: return "pattern has separator" + case .No_HOME_Variable: return "no $HOME variable" + case .Env_Var_Not_Found: return "environment variable not found" } case io.Error: switch e { @@ -108,12 +113,12 @@ error_string :: proc(ferr: Error) -> string { } print_error :: proc(f: ^File, ferr: Error, msg: string) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) err_str := error_string(ferr) // msg + ": " + err_str + '\n' length := len(msg) + 2 + len(err_str) + 1 - buf := make([]u8, length, temp_allocator()) + buf := make([]u8, length, temp_allocator) copy(buf, msg) buf[len(msg)] = ':' diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 28d2bc69b..a9878a563 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -291,8 +291,8 @@ exists :: proc(path: string) -> bool { @(require_results) is_file :: proc(path: string) -> bool { - TEMP_ALLOCATOR_GUARD() - fi, err := stat(path, temp_allocator()) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + fi, err := stat(path, temp_allocator) if err != nil { return false } @@ -303,8 +303,8 @@ is_dir :: is_directory @(require_results) is_directory :: proc(path: string) -> bool { - TEMP_ALLOCATOR_GUARD() - fi, err := stat(path, temp_allocator()) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + fi, err := stat(path, temp_allocator) if err != nil { return false } @@ -313,6 +313,15 @@ is_directory :: proc(path: string) -> bool { copy_file :: proc(dst_path, src_path: string) -> Error { + when #defined(_copy_file_native) { + return _copy_file_native(dst_path, src_path) + } else { + return _copy_file(dst_path, src_path) + } +} + +@(private) +_copy_file :: proc(dst_path, src_path: string) -> Error { src := open(src_path) or_return defer close(src) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 811ee7055..92f0c1541 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -45,8 +45,8 @@ _stderr := File{ } @init -_standard_stream_init :: proc() { - new_std :: proc(impl: ^File_Impl, fd: linux.Fd, name: string) -> ^File { +_standard_stream_init :: proc "contextless" () { + new_std :: proc "contextless" (impl: ^File_Impl, fd: linux.Fd, name: string) -> ^File { impl.file.impl = impl impl.fd = linux.Fd(fd) impl.allocator = runtime.nil_allocator() @@ -66,8 +66,8 @@ _standard_stream_init :: proc() { } _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) { - TEMP_ALLOCATOR_GUARD() - name_cstr := temp_cstring(name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr := clone_to_cstring(name, temp_allocator) or_return // Just default to using O_NOCTTY because needing to open a controlling // terminal would be incredibly rare. This has no effect on files while @@ -269,6 +269,7 @@ _write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (nt: i64, err: Error return } +@(no_sanitize_memory) _file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) { // TODO: Identify 0-sized "pseudo" files and return No_Size. This would // eliminate the need for the _read_entire_pseudo_file procs. @@ -299,8 +300,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error { } _remove :: proc(name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - name_cstr := temp_cstring(name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr := clone_to_cstring(name, temp_allocator) or_return if fd, errno := linux.open(name_cstr, _OPENDIR_FLAGS + {.NOFOLLOW}); errno == .NONE { linux.close(fd) @@ -311,25 +312,25 @@ _remove :: proc(name: string) -> Error { } _rename :: proc(old_name, new_name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - old_name_cstr := temp_cstring(old_name) or_return - new_name_cstr := temp_cstring(new_name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return + new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return return _get_platform_error(linux.rename(old_name_cstr, new_name_cstr)) } _link :: proc(old_name, new_name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - old_name_cstr := temp_cstring(old_name) or_return - new_name_cstr := temp_cstring(new_name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return + new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return return _get_platform_error(linux.link(old_name_cstr, new_name_cstr)) } _symlink :: proc(old_name, new_name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - old_name_cstr := temp_cstring(old_name) or_return - new_name_cstr := temp_cstring(new_name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + old_name_cstr := clone_to_cstring(old_name, temp_allocator) or_return + new_name_cstr := clone_to_cstring(new_name, temp_allocator) or_return return _get_platform_error(linux.symlink(old_name_cstr, new_name_cstr)) } @@ -352,14 +353,14 @@ _read_link_cstr :: proc(name_cstr: cstring, allocator: runtime.Allocator) -> (st } _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, e: Error) { - TEMP_ALLOCATOR_GUARD() - name_cstr := temp_cstring(name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + name_cstr := clone_to_cstring(name, temp_allocator) or_return return _read_link_cstr(name_cstr, allocator) } _chdir :: proc(name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - name_cstr := temp_cstring(name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr := clone_to_cstring(name, temp_allocator) or_return return _get_platform_error(linux.chdir(name_cstr)) } @@ -369,8 +370,8 @@ _fchdir :: proc(f: ^File) -> Error { } _chmod :: proc(name: string, mode: int) -> Error { - TEMP_ALLOCATOR_GUARD() - name_cstr := temp_cstring(name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr := clone_to_cstring(name, temp_allocator) or_return return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode)))) } @@ -381,15 +382,15 @@ _fchmod :: proc(f: ^File, mode: int) -> Error { // NOTE: will throw error without super user priviledges _chown :: proc(name: string, uid, gid: int) -> Error { - TEMP_ALLOCATOR_GUARD() - name_cstr := temp_cstring(name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr := clone_to_cstring(name, temp_allocator) or_return return _get_platform_error(linux.chown(name_cstr, linux.Uid(uid), linux.Gid(gid))) } // NOTE: will throw error without super user priviledges _lchown :: proc(name: string, uid, gid: int) -> Error { - TEMP_ALLOCATOR_GUARD() - name_cstr := temp_cstring(name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr := clone_to_cstring(name, temp_allocator) or_return return _get_platform_error(linux.lchown(name_cstr, linux.Uid(uid), linux.Gid(gid))) } @@ -400,8 +401,8 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error { } _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { - TEMP_ALLOCATOR_GUARD() - name_cstr := temp_cstring(name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr := clone_to_cstring(name, temp_allocator) or_return times := [2]linux.Time_Spec { { uint(atime._nsec) / uint(time.Second), @@ -431,8 +432,8 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { } _exists :: proc(name: string) -> bool { - TEMP_ALLOCATOR_GUARD() - name_cstr, _ := temp_cstring(name) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + name_cstr, _ := clone_to_cstring(name, temp_allocator) return linux.access(name_cstr, linux.F_OK) == .NONE } @@ -440,8 +441,8 @@ _exists :: proc(name: string) -> bool { _read_entire_pseudo_file :: proc { _read_entire_pseudo_file_string, _read_entire_pseudo_file_cstring } _read_entire_pseudo_file_string :: proc(name: string, allocator: runtime.Allocator) -> (b: []u8, e: Error) { - TEMP_ALLOCATOR_GUARD() - name_cstr := clone_to_cstring(name, temp_allocator()) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + name_cstr := clone_to_cstring(name, temp_allocator) or_return return _read_entire_pseudo_file_cstring(name_cstr, allocator) } diff --git a/core/os/os2/file_posix.odin b/core/os/os2/file_posix.odin index 43d5866b1..fed8d766c 100644 --- a/core/os/os2/file_posix.odin +++ b/core/os/os2/file_posix.odin @@ -25,8 +25,8 @@ File_Impl :: struct { } @(init) -init_std_files :: proc() { - new_std :: proc(impl: ^File_Impl, fd: posix.FD, name: cstring) -> ^File { +init_std_files :: proc "contextless" () { + new_std :: proc "contextless" (impl: ^File_Impl, fd: posix.FD, name: cstring) -> ^File { impl.file.impl = impl impl.fd = fd impl.allocator = runtime.nil_allocator() @@ -69,8 +69,8 @@ _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Err if .Trunc in flags { sys_flags += {.TRUNC} } if .Inheritable in flags { sys_flags -= {.CLOEXEC} } - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return fd := posix.open(cname, sys_flags, transmute(posix.mode_t)posix._mode_t(perm)) if fd < 0 { @@ -183,39 +183,39 @@ _truncate :: proc(f: ^File, size: i64) -> Error { return nil } -_remove :: proc(name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) +_remove :: proc(name: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return if posix.remove(cname) != 0 { return _get_platform_error() } return nil } -_rename :: proc(old_path, new_path: string) -> Error { - TEMP_ALLOCATOR_GUARD() - cold := temp_cstring(old_path) - cnew := temp_cstring(new_path) +_rename :: proc(old_path, new_path: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cold := clone_to_cstring(old_path, temp_allocator) or_return + cnew := clone_to_cstring(new_path, temp_allocator) or_return if posix.rename(cold, cnew) != 0 { return _get_platform_error() } return nil } -_link :: proc(old_name, new_name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - cold := temp_cstring(old_name) - cnew := temp_cstring(new_name) +_link :: proc(old_name, new_name: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cold := clone_to_cstring(old_name, temp_allocator) or_return + cnew := clone_to_cstring(new_name, temp_allocator) or_return if posix.link(cold, cnew) != .OK { return _get_platform_error() } return nil } -_symlink :: proc(old_name, new_name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - cold := temp_cstring(old_name) - cnew := temp_cstring(new_name) +_symlink :: proc(old_name, new_name: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cold := clone_to_cstring(old_name, temp_allocator) or_return + cnew := clone_to_cstring(new_name, temp_allocator) or_return if posix.symlink(cold, cnew) != .OK { return _get_platform_error() } @@ -223,8 +223,8 @@ _symlink :: proc(old_name, new_name: string) -> Error { } _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) { - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + cname := clone_to_cstring(name, temp_allocator) or_return buf: [dynamic]byte buf.allocator = allocator @@ -268,9 +268,9 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er } } -_chdir :: proc(name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) +_chdir :: proc(name: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return if posix.chdir(cname) != .OK { return _get_platform_error() } @@ -291,9 +291,9 @@ _fchmod :: proc(f: ^File, mode: int) -> Error { return nil } -_chmod :: proc(name: string, mode: int) -> Error { - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) +_chmod :: proc(name: string, mode: int) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return if posix.chmod(cname, transmute(posix.mode_t)posix._mode_t(mode)) != .OK { return _get_platform_error() } @@ -307,9 +307,9 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error { return nil } -_chown :: proc(name: string, uid, gid: int) -> Error { - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) +_chown :: proc(name: string, uid, gid: int) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return if posix.chown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK { return _get_platform_error() } @@ -317,15 +317,15 @@ _chown :: proc(name: string, uid, gid: int) -> Error { } _lchown :: proc(name: string, uid, gid: int) -> Error { - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return if posix.lchown(cname, posix.uid_t(uid), posix.gid_t(gid)) != .OK { return _get_platform_error() } return nil } -_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { +_chtimes :: proc(name: string, atime, mtime: time.Time) -> (err: Error) { times := [2]posix.timeval{ { tv_sec = posix.time_t(atime._nsec/1e9), /* seconds */ @@ -337,8 +337,8 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { }, } - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return if posix.utimes(cname, ×) != .OK { return _get_platform_error() @@ -365,8 +365,9 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { } _exists :: proc(path: string) -> bool { - TEMP_ALLOCATOR_GUARD() - cpath := temp_cstring(path) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cpath, err := clone_to_cstring(path, temp_allocator) + if err != nil { return false } return posix.access(cpath) == .OK } diff --git a/core/os/os2/file_posix_darwin.odin b/core/os/os2/file_posix_darwin.odin index 920a63a71..aed3e56f5 100644 --- a/core/os/os2/file_posix_darwin.odin +++ b/core/os/os2/file_posix_darwin.odin @@ -3,6 +3,7 @@ package os2 import "base:runtime" +import "core:sys/darwin" import "core:sys/posix" _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { @@ -16,3 +17,30 @@ _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allo return clone_to_cstring(string(cstring(&buf[0])), allocator) } + +_copy_file_native :: proc(dst_path, src_path: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + csrc := clone_to_cstring(src_path, temp_allocator) or_return + cdst := clone_to_cstring(dst_path, temp_allocator) or_return + + // Disallow directories, as specified by the generic implementation. + + stat: posix.stat_t + if posix.stat(csrc, &stat) != .OK { + err = _get_platform_error() + return + } + + if posix.S_ISDIR(stat.st_mode) { + err = .Invalid_File + return + } + + ret := darwin.copyfile(csrc, cdst, nil, darwin.COPYFILE_ALL) + if ret < 0 { + err = _get_platform_error() + } + + return +} diff --git a/core/os/os2/file_posix_other.odin b/core/os/os2/file_posix_other.odin index 74b6374ec..d2946098b 100644 --- a/core/os/os2/file_posix_other.odin +++ b/core/os/os2/file_posix_other.odin @@ -7,8 +7,8 @@ import "base:runtime" import "core:sys/posix" _posix_absolute_path :: proc(fd: posix.FD, name: string, allocator: runtime.Allocator) -> (path: cstring, err: Error) { - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + cname := clone_to_cstring(name, temp_allocator) buf: [posix.PATH_MAX]byte path = posix.realpath(cname, raw_data(buf[:])) diff --git a/core/os/os2/file_util.odin b/core/os/os2/file_util.odin index 8af46fab3..13d6db661 100644 --- a/core/os/os2/file_util.odin +++ b/core/os/os2/file_util.odin @@ -59,7 +59,7 @@ write_encoded_rune :: proc(f: ^File, r: rune) -> (n: int, err: Error) { if r < 32 { if wrap(write_string(f, "\\x"), &n, &err) { return } b: [2]byte - s := strconv.append_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil) + s := strconv.write_bits(b[:], u64(r), 16, true, 64, strconv.digits, nil) switch len(s) { case 0: if wrap(write_string(f, "00"), &n, &err) { return } case 1: if wrap(write_rune(f, '0'), &n, &err) { return } @@ -151,7 +151,7 @@ read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator) -> (d n: int n, err = read(f, buffer[:]) total += n - append_elems(&out_buffer, ..buffer[:n]) + append_elems(&out_buffer, ..buffer[:n]) or_return if err != nil { if err == .EOF || err == .Broken_Pipe { err = nil diff --git a/core/os/os2/file_wasi.odin b/core/os/os2/file_wasi.odin index 0245841e3..1d417ffb1 100644 --- a/core/os/os2/file_wasi.odin +++ b/core/os/os2/file_wasi.odin @@ -30,8 +30,8 @@ Preopen :: struct { preopens: []Preopen @(init) -init_std_files :: proc() { - new_std :: proc(impl: ^File_Impl, fd: wasi.fd_t, name: string) -> ^File { +init_std_files :: proc "contextless" () { + new_std :: proc "contextless" (impl: ^File_Impl, fd: wasi.fd_t, name: string) -> ^File { impl.file.impl = impl impl.allocator = runtime.nil_allocator() impl.fd = fd diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index b123330e0..b39e65fe2 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -43,8 +43,8 @@ File_Impl :: struct { } @(init) -init_std_files :: proc() { - new_std :: proc(impl: ^File_Impl, code: u32, name: string) -> ^File { +init_std_files :: proc "contextless" () { + new_std :: proc "contextless" (impl: ^File_Impl, code: u32, name: string) -> ^File { impl.file.impl = impl impl.allocator = runtime.nil_allocator() @@ -77,7 +77,7 @@ init_std_files :: proc() { stderr = new_std(&files[2], win32.STD_ERROR_HANDLE, "<stderr>") } -_handle :: proc(f: ^File) -> win32.HANDLE { +_handle :: proc "contextless" (f: ^File) -> win32.HANDLE { return win32.HANDLE(_fd(f)) } @@ -86,9 +86,9 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: u err = .Not_Exist return } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) - path := _fix_long_path(name, temp_allocator()) or_return + path := _fix_long_path(name, temp_allocator) or_return access: u32 switch flags & {.Read, .Write} { case {.Read}: access = win32.FILE_GENERIC_READ @@ -234,7 +234,7 @@ _clone :: proc(f: ^File) -> (clone: ^File, err: Error) { return _new_file(uintptr(clonefd), name(f), file_allocator()) } -_fd :: proc(f: ^File) -> uintptr { +_fd :: proc "contextless" (f: ^File) -> uintptr { if f == nil || f.impl == nil { return INVALID_HANDLE } @@ -247,11 +247,11 @@ _destroy :: proc(f: ^File_Impl) -> Error { } a := f.allocator - err0 := free(f.wname, a) + err0 := free(rawptr(f.wname), a) err1 := delete(f.name, a) - err2 := free(f, a) - err3 := delete(f.r_buf, a) - err4 := delete(f.w_buf, a) + err2 := delete(f.r_buf, a) + err3 := delete(f.w_buf, a) + err4 := free(f, a) err0 or_return err1 or_return err2 or_return @@ -506,10 +506,16 @@ _write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error) _file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) { length: win32.LARGE_INTEGER + handle := _handle(&f.file) if f.kind == .Pipe { - return 0, .No_Size + bytes_available: u32 + if win32.PeekNamedPipe(handle, nil, 0, nil, &bytes_available, nil) { + return i64(bytes_available), nil + } else { + err = _get_platform_error() + return + } } - handle := _handle(&f.file) if !win32.GetFileSizeEx(handle, &length) { err = _get_platform_error() } @@ -551,8 +557,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error { } _remove :: proc(name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - p := _fix_long_path(name, temp_allocator()) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + p := _fix_long_path(name, temp_allocator) or_return err, err1: Error if !win32.DeleteFileW(p) { err = _get_platform_error() @@ -589,9 +595,9 @@ _remove :: proc(name: string) -> Error { } _rename :: proc(old_path, new_path: string) -> Error { - TEMP_ALLOCATOR_GUARD() - from := _fix_long_path(old_path, temp_allocator()) or_return - to := _fix_long_path(new_path, temp_allocator()) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + from := _fix_long_path(old_path, temp_allocator) or_return + to := _fix_long_path(new_path, temp_allocator) or_return if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) { return nil } @@ -600,9 +606,9 @@ _rename :: proc(old_path, new_path: string) -> Error { } _link :: proc(old_name, new_name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - o := _fix_long_path(old_name, temp_allocator()) or_return - n := _fix_long_path(new_name, temp_allocator()) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + o := _fix_long_path(old_name, temp_allocator) or_return + n := _fix_long_path(new_name, temp_allocator) or_return if win32.CreateHardLinkW(n, o, nil) { return nil } @@ -613,7 +619,7 @@ _symlink :: proc(old_name, new_name: string) -> Error { return .Unsupported } -_open_sym_link :: proc(p: [^]u16) -> (handle: win32.HANDLE, err: Error) { +_open_sym_link :: proc(p: cstring16) -> (handle: win32.HANDLE, err: Error) { attrs := u32(win32.FILE_FLAG_BACKUP_SEMANTICS) attrs |= win32.FILE_FLAG_OPEN_REPARSE_POINT handle = win32.CreateFileW(p, 0, 0, nil, win32.OPEN_EXISTING, attrs, nil) @@ -655,7 +661,7 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st } - handle := _open_sym_link(raw_data(p)) or_return + handle := _open_sym_link(cstring16(raw_data(p))) or_return defer win32.CloseHandle(handle) n := win32.GetFinalPathNameByHandleW(handle, nil, 0, win32.VOLUME_NAME_DOS) @@ -663,10 +669,10 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st return "", _get_platform_error() } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - buf := make([]u16, n+1, temp_allocator()) - n = win32.GetFinalPathNameByHandleW(handle, raw_data(buf), u32(len(buf)), win32.VOLUME_NAME_DOS) + buf := make([]u16, n+1, temp_allocator) + n = win32.GetFinalPathNameByHandleW(handle, cstring16(raw_data(buf)), u32(len(buf)), win32.VOLUME_NAME_DOS) if n == 0 { return "", _get_platform_error() } @@ -689,9 +695,9 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er @thread_local rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - p := _fix_long_path(name, temp_allocator()) or_return + p := _fix_long_path(name, temp_allocator) or_return handle := _open_sym_link(p) or_return defer win32.CloseHandle(handle) @@ -707,7 +713,7 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er switch rdb.ReparseTag { case win32.IO_REPARSE_TAG_SYMLINK: rb := (^win32.SYMBOLIC_LINK_REPARSE_BUFFER)(&rdb.rest) - pb := win32.wstring(&rb.PathBuffer) + pb := ([^]u16)(&rb.PathBuffer) pb[rb.SubstituteNameOffset+rb.SubstituteNameLength] = 0 p := pb[rb.SubstituteNameOffset:][:rb.SubstituteNameLength] if rb.Flags & win32.SYMLINK_FLAG_RELATIVE != 0 { @@ -717,7 +723,7 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er case win32.IO_REPARSE_TAG_MOUNT_POINT: rb := (^win32.MOUNT_POINT_REPARSE_BUFFER)(&rdb.rest) - pb := win32.wstring(&rb.PathBuffer) + pb := ([^]u16)(&rb.PathBuffer) pb[rb.SubstituteNameOffset+rb.SubstituteNameLength] = 0 p := pb[rb.SubstituteNameOffset:][:rb.SubstituteNameLength] return _normalize_link_path(p, allocator) @@ -766,8 +772,8 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error { } _chdir :: proc(name: string) -> Error { - TEMP_ALLOCATOR_GUARD() - p := _fix_long_path(name, temp_allocator()) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + p := _fix_long_path(name, temp_allocator) or_return if !win32.SetCurrentDirectoryW(p) { return _get_platform_error() } @@ -794,19 +800,11 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { defer close(f) return _fchtimes(f, atime, mtime) } + _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { if f == nil || f.impl == nil { return nil } - d: win32.BY_HANDLE_FILE_INFORMATION - if !win32.GetFileInformationByHandle(_handle(f), &d) { - return _get_platform_error() - } - - to_windows_time :: #force_inline proc(t: time.Time) -> win32.LARGE_INTEGER { - // a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC) - return win32.LARGE_INTEGER(time.time_to_unix_nano(t) * 100 + 116444736000000000) - } atime, mtime := atime, mtime if time.time_to_unix_nano(atime) < time.time_to_unix_nano(mtime) { @@ -814,17 +812,17 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { } info: win32.FILE_BASIC_INFO - info.LastAccessTime = to_windows_time(atime) - info.LastWriteTime = to_windows_time(mtime) - if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(d)) { + info.LastAccessTime = time_as_filetime(atime) + info.LastWriteTime = time_as_filetime(mtime) + if !win32.SetFileInformationByHandle(_handle(f), .FileBasicInfo, &info, size_of(info)) { return _get_platform_error() } return nil } _exists :: proc(path: string) -> bool { - TEMP_ALLOCATOR_GUARD() - wpath, _ := _fix_long_path(path, temp_allocator()) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + wpath, _ := _fix_long_path(path, temp_allocator) attribs := win32.GetFileAttributesW(wpath) return attribs != win32.INVALID_FILE_ATTRIBUTES } @@ -876,8 +874,8 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, @(private="package", require_results) -win32_utf8_to_wstring :: proc(s: string, allocator: runtime.Allocator) -> (ws: [^]u16, err: runtime.Allocator_Error) { - ws = raw_data(win32_utf8_to_utf16(s, allocator) or_return) +win32_utf8_to_wstring :: proc(s: string, allocator: runtime.Allocator) -> (ws: cstring16, err: runtime.Allocator_Error) { + ws = cstring16(raw_data(win32_utf8_to_utf16(s, allocator) or_return)) return } @@ -911,24 +909,60 @@ win32_utf8_to_utf16 :: proc(s: string, allocator: runtime.Allocator) -> (ws: []u } @(private="package", require_results) -win32_wstring_to_utf8 :: proc(s: [^]u16, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) { - if s == nil || s[0] == 0 { +win32_wstring_to_utf8 :: proc(s: cstring16, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) { + if s == nil || s == "" { return "", nil } - n := 0 - for s[n] != 0 { - n += 1 + return win32_utf16_to_utf8(string16(s), allocator) +} + +@(private="package") +win32_utf16_to_utf8 :: proc{ + win32_utf16_string16_to_utf8, + win32_utf16_u16_to_utf8, +} + +@(private="package", require_results) +win32_utf16_string16_to_utf8 :: proc(s: string16, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) { + if len(s) == 0 { + return } - return win32_utf16_to_utf8(s[:n], allocator) + + n := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, cstring16(raw_data(s)), i32(len(s)), nil, 0, nil, nil) + if n == 0 { + return + } + + // If N < 0 the call to WideCharToMultiByte assume the wide string is null terminated + // and will scan it to find the first null terminated character. The resulting string will + // also be null terminated. + // If N > 0 it assumes the wide string is not null terminated and the resulting string + // will not be null terminated. + text := make([]byte, n, allocator) or_return + + n1 := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, cstring16(raw_data(s)), i32(len(s)), raw_data(text), n, nil, nil) + if n1 == 0 { + delete(text, allocator) + return + } + + for i in 0..<n { + if text[i] == 0 { + n = i + break + } + } + res = string(text[:n]) + return } @(private="package", require_results) -win32_utf16_to_utf8 :: proc(s: []u16, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) { +win32_utf16_u16_to_utf8 :: proc(s: []u16, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) { if len(s) == 0 { return } - n := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, raw_data(s), i32(len(s)), nil, 0, nil, nil) + n := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, cstring16(raw_data(s)), i32(len(s)), nil, 0, nil, nil) if n == 0 { return } @@ -940,7 +974,7 @@ win32_utf16_to_utf8 :: proc(s: []u16, allocator: runtime.Allocator) -> (res: str // will not be null terminated. text := make([]byte, n, allocator) or_return - n1 := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, raw_data(s), i32(len(s)), raw_data(text), n, nil, nil) + n1 := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, cstring16(raw_data(s)), i32(len(s)), raw_data(text), n, nil, nil) if n1 == 0 { delete(text, allocator) return diff --git a/core/os/os2/internal_util.odin b/core/os/os2/internal_util.odin index 164e1e1be..9616af8b0 100644 --- a/core/os/os2/internal_util.odin +++ b/core/os/os2/internal_util.odin @@ -3,6 +3,7 @@ package os2 import "base:intrinsics" import "base:runtime" +import "core:math/rand" // Splits pattern by the last wildcard "*", if it exists, and returns the prefix and suffix @@ -43,11 +44,6 @@ clone_to_cstring :: proc(s: string, allocator: runtime.Allocator) -> (res: cstri } @(require_results) -temp_cstring :: proc(s: string) -> (cstring, runtime.Allocator_Error) #optional_allocator_error { - return clone_to_cstring(s, temp_allocator()) -} - -@(require_results) string_from_null_terminated_bytes :: proc(b: []byte) -> (res: string) { s := string(b) i := 0 @@ -84,45 +80,15 @@ concatenate :: proc(strings: []string, allocator: runtime.Allocator) -> (res: st return string(buf), nil } - - -@(private="file") -random_string_seed: [2]u64 - -@(init, private="file") -init_random_string_seed :: proc() { - seed := u64(intrinsics.read_cycle_counter()) - s := &random_string_seed - s[0] = 0 - s[1] = (seed << 1) | 1 - _ = next_random(s) - s[1] += seed - _ = next_random(s) -} - -@(require_results) -next_random :: proc(r: ^[2]u64) -> u64 { - old_state := r[0] - r[0] = old_state * 6364136223846793005 + (r[1]|1) - xor_shifted := (((old_state >> 59) + 5) ~ old_state) * 12605985483714917081 - rot := (old_state >> 59) - return (xor_shifted >> rot) | (xor_shifted << ((-rot) & 63)) -} - @(require_results) random_string :: proc(buf: []byte) -> string { - @(static, rodata) digits := "0123456789" - - u := next_random(&random_string_seed) - - b :: 10 - i := len(buf) - for u >= b { - i -= 1 - buf[i] = digits[u % b] - u /= b + for i := 0; i < len(buf); i += 16 { + n := rand.uint64() + end := min(i + 16, len(buf)) + for j := i; j < end; j += 1 { + buf[j] = '0' + u8(n) % 10 + n >>= 4 + } } - i -= 1 - buf[i] = digits[u % b] - return string(buf[i:]) + return string(buf) } diff --git a/core/os/os2/path.odin b/core/os/os2/path.odin index 9231307f5..e12aa3c9c 100644 --- a/core/os/os2/path.odin +++ b/core/os/os2/path.odin @@ -2,12 +2,18 @@ package os2 import "base:runtime" -import "core:path/filepath" +import "core:strings" Path_Separator :: _Path_Separator // OS-Specific Path_Separator_String :: _Path_Separator_String // OS-Specific Path_List_Separator :: _Path_List_Separator // OS-Specific +#assert(_Path_Separator <= rune(0x7F), "The system-specific path separator rune is expected to be within the 7-bit ASCII character set.") + +/* +Return true if `c` is a character used to separate paths into directory and +file hierarchies on the current system. +*/ @(require_results) is_path_separator :: proc(c: byte) -> bool { return _is_path_separator(c) @@ -15,22 +21,42 @@ is_path_separator :: proc(c: byte) -> bool { mkdir :: make_directory +/* +Make a new directory. + +If `path` is relative, it will be relative to the process's current working directory. +*/ make_directory :: proc(name: string, perm: int = 0o755) -> Error { return _mkdir(name, perm) } mkdir_all :: make_directory_all +/* +Make a new directory, creating new intervening directories when needed. + +If `path` is relative, it will be relative to the process's current working directory. +*/ make_directory_all :: proc(path: string, perm: int = 0o755) -> Error { return _mkdir_all(path, perm) } +/* +Delete `path` and all files and directories inside of `path` if it is a directory. + +If `path` is relative, it will be relative to the process's current working directory. +*/ remove_all :: proc(path: string) -> Error { return _remove_all(path) } getwd :: get_working_directory +/* +Get the working directory of the current process. + +*Allocates Using Provided Allocator* +*/ @(require_results) get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { return _get_working_directory(allocator) @@ -38,16 +64,399 @@ get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err setwd :: set_working_directory +/* +Change the working directory of the current process. + +*Allocates Using Provided Allocator* +*/ set_working_directory :: proc(dir: string) -> (err: Error) { return _set_working_directory(dir) } +/* +Get the path for the currently running executable. + +*Allocates Using Provided Allocator* +*/ +@(require_results) get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { return _get_executable_path(allocator) } +/* +Get the directory for the currently running executable. + +*Allocates Using Provided Allocator* +*/ +@(require_results) get_executable_directory :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { path = _get_executable_path(allocator) or_return - path, _ = filepath.split(path) + path, _ = split_path(path) + return +} + +/* +Compare two paths for exactness without normalization. + +This procedure takes into account case-sensitivity on differing systems. +*/ +@(require_results) +are_paths_identical :: proc(a, b: string) -> (identical: bool) { + return _are_paths_identical(a, b) +} + +/* +Normalize a path. + +*Allocates Using Provided Allocator* + +This will remove duplicate separators and unneeded references to the current or +parent directory. +*/ +@(require_results) +clean_path :: proc(path: string, allocator: runtime.Allocator) -> (cleaned: string, err: Error) { + if path == "" || path == "." { + return strings.clone(".", allocator) + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + // The extra byte is to simplify appending path elements by letting the + // loop to end each with a separator. We'll trim the last one when we're done. + buffer := make([]u8, len(path) + 1, temp_allocator) or_return + + // This is the only point where Windows and POSIX differ, as Windows has + // alphabet-based volumes for root paths. + rooted, start := _clean_path_handle_start(path, buffer) + + head, buffer_i := start, start + for i, j := start, start; i <= len(path); i += 1 { + if i == len(path) || _is_path_separator(path[i]) { + elem := path[j:i] + j = i + 1 + + switch elem { + case "", ".": + // Skip duplicate path separators and current directory references. + case "..": + if !rooted && buffer_i == head { + // Only allow accessing further parent directories when the path is relative. + buffer[buffer_i] = '.' + buffer[buffer_i+1] = '.' + buffer[buffer_i+2] = _Path_Separator + buffer_i += 3 + head = buffer_i + } else { + // Roll back to the last separator or the head of the buffer. + back_to := head + // `buffer_i` will be equal to 1 + the last set byte, so + // skipping two bytes avoids the final separator we just + // added. + for k := buffer_i-2; k >= head; k -= 1 { + if _is_path_separator(buffer[k]) { + back_to = k + 1 + break + } + } + buffer_i = back_to + } + case: + // Copy the path element verbatim and add a separator. + copy(buffer[buffer_i:], elem) + buffer_i += len(elem) + buffer[buffer_i] = _Path_Separator + buffer_i += 1 + } + } + } + + // Trim the final separator. + // NOTE: No need to check if the last byte is a separator, as we always add it. + if buffer_i > start { + buffer_i -= 1 + } + + if buffer_i == 0 { + return strings.clone(".", allocator) + } + + compact := make([]u8, buffer_i, allocator) or_return + copy(compact, buffer) // NOTE(bill): buffer[:buffer_i] is redundant here + return string(compact), nil +} + +/* +Return true if `path` is an absolute path as opposed to a relative one. +*/ +@(require_results) +is_absolute_path :: proc(path: string) -> bool { + return _is_absolute_path(path) +} + +/* +Get the absolute path to `path` with respect to the process's current directory. + +*Allocates Using Provided Allocator* +*/ +@(require_results) +get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) { + return _get_absolute_path(path, allocator) +} + +/* +Get the relative path needed to change directories from `base` to `target`. + +*Allocates Using Provided Allocator* + +The result is such that `join_path(base, get_relative_path(base, target))` is equivalent to `target`. + +NOTE: This procedure expects both `base` and `target` to be normalized first, +which can be done by calling `clean_path` on them if needed. + +This procedure will return an `Invalid_Path` error if `base` begins with a +reference to the parent directory (`".."`). Use `get_working_directory` with +`join_path` to construct absolute paths for both arguments instead. +*/ +@(require_results) +get_relative_path :: proc(base, target: string, allocator: runtime.Allocator) -> (path: string, err: Error) { + if _are_paths_identical(base, target) { + return strings.clone(".", allocator) + } + if base == "." { + return strings.clone(target, allocator) + } + + // This is the first point where Windows and POSIX differ, as Windows has + // alphabet-based volumes for root paths. + if !_get_relative_path_handle_start(base, target) { + return "", .Invalid_Path + } + if strings.has_prefix(base, "..") && (len(base) == 2 || _is_path_separator(base[2])) { + // We could do the work for the user of getting absolute paths for both + // arguments, but that could make something costly (repeatedly + // normalizing paths) convenient, when it would be better for the user + // to store already-finalized paths and operate on those instead. + return "", .Invalid_Path + } + + // This is the other point where Windows and POSIX differ, as Windows is + // case-insensitive. + common := _get_common_path_len(base, target) + + // Get the result of splitting `base` and `target` on _Path_Separator, + // comparing them up to their most common elements, then count how many + // unshared parts are in the split `base`. + seps := 0 + size := 0 + if len(base)-common > 0 { + seps = 1 + size = 2 + } + // This range skips separators on the ends of the string. + for i in common+1..<len(base)-1 { + if _is_path_separator(base[i]) { + seps += 1 + size += 3 + } + } + + // Handle the rest of the size calculations. + trailing := target[common:] + if len(trailing) > 0 { + // Account for leading separators on the target after cutting the common part. + // (i.e. base == `/home`, target == `/home/a`) + if _is_path_separator(trailing[0]) { + trailing = trailing[1:] + } + size += len(trailing) + if seps > 0 { + size += 1 + } + } + if trailing == "." { + trailing = "" + size -= 2 + } + + // Build the string. + buf := make([]u8, size, allocator) or_return + n := 0 + if seps > 0 { + buf[0] = '.' + buf[1] = '.' + n = 2 + } + for _ in 1..<seps { + buf[n] = _Path_Separator + buf[n+1] = '.' + buf[n+2] = '.' + n += 3 + } + if len(trailing) > 0 { + if seps > 0 { + buf[n] = _Path_Separator + n += 1 + } + copy(buf[n:], trailing) + } + + path = string(buf) + return } + +/* +Split a path into a directory hierarchy and a filename. + +For example, `split_path("/home/foo/bar.tar.gz")` will return `"/home/foo"` and `"bar.tar.gz"`. +*/ +@(require_results) +split_path :: proc(path: string) -> (dir, filename: string) { + return _split_path(path) +} + +/* +Join all `elems` with the system's path separator and normalize the result. + +*Allocates Using Provided Allocator* + +For example, `join_path({"/home", "foo", "bar.txt"})` will result in `"/home/foo/bar.txt"`. +*/ +@(require_results) +join_path :: proc(elems: []string, allocator: runtime.Allocator) -> (joined: string, err: Error) { + for e, i in elems { + if e != "" { + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + p := strings.join(elems[i:], Path_Separator_String, temp_allocator) or_return + return clean_path(p, allocator) + } + } + return "", nil +} + +/* +Split a filename from its extension. + +This procedure splits on the last separator. + +If the filename begins with a separator, such as `".readme.txt"`, the separator +will be included in the filename, resulting in `".readme"` and `"txt"`. + +For example, `split_filename("foo.tar.gz")` will return `"foo.tar"` and `"gz"`. +*/ +@(require_results) +split_filename :: proc(filename: string) -> (base, ext: string) { + i := strings.last_index_byte(filename, '.') + if i <= 0 { + return filename, "" + } + return filename[:i], filename[i+1:] +} + +/* +Split a filename from its extension. + +This procedure splits on the first separator. + +If the filename begins with a separator, such as `".readme.txt.gz"`, the separator +will be included in the filename, resulting in `".readme"` and `"txt.gz"`. + +For example, `split_filename_all("foo.tar.gz")` will return `"foo"` and `"tar.gz"`. +*/ +@(require_results) +split_filename_all :: proc(filename: string) -> (base, ext: string) { + i := strings.index_byte(filename, '.') + if i == 0 { + j := strings.index_byte(filename[1:], '.') + if j != -1 { + j += 1 + } + i = j + } + if i == -1 { + return filename, "" + } + return filename[:i], filename[i+1:] +} + +/* +Join `base` and `ext` with the system's filename extension separator. + +*Allocates Using Provided Allocator* + +For example, `join_filename("foo", "tar.gz")` will result in `"foo.tar.gz"`. +*/ +@(require_results) +join_filename :: proc(base: string, ext: string, allocator: runtime.Allocator) -> (joined: string, err: Error) { + if len(base) == 0 { + return strings.clone(ext, allocator) + } else if len(ext) == 0 { + return strings.clone(base, allocator) + } + + buf := make([]u8, len(base) + 1 + len(ext), allocator) or_return + copy(buf, base) + buf[len(base)] = '.' + copy(buf[1+len(base):], ext) + + return string(buf), nil +} + +/* +Split a string that is separated by a system-specific separator, typically used +for environment variables specifying multiple directories. + +*Allocates Using Provided Allocator* + +For example, there is the "PATH" environment variable on POSIX systems which +this procedure can split into separate entries. +*/ +@(require_results) +split_path_list :: proc(path: string, allocator: runtime.Allocator) -> (list: []string, err: Error) { + if path == "" { + return nil, nil + } + + start: int + quote: bool + + start, quote = 0, false + count := 0 + + for i := 0; i < len(path); i += 1 { + c := path[i] + switch { + case c == '"': + quote = !quote + case c == Path_List_Separator && !quote: + count += 1 + } + } + + start, quote = 0, false + list = make([]string, count + 1, allocator) or_return + index := 0 + for i := 0; i < len(path); i += 1 { + c := path[i] + switch { + case c == '"': + quote = !quote + case c == Path_List_Separator && !quote: + list[index] = path[start:i] + index += 1 + start = i + 1 + } + } + assert(index == count) + list[index] = path[start:] + + for s0, i in list { + s, new := strings.replace_all(s0, `"`, ``, allocator) + if !new { + s = strings.clone(s, allocator) or_return + } + list[i] = s + } + + return list, nil +} diff --git a/core/os/os2/path_freebsd.odin b/core/os/os2/path_freebsd.odin index 577108eca..e7e4f63c9 100644 --- a/core/os/os2/path_freebsd.odin +++ b/core/os/os2/path_freebsd.odin @@ -25,5 +25,5 @@ _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err return } - return string(buf[:size]), nil + return string(buf[:size-1]), nil } diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index e3e7f8a7c..1c9927843 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -14,12 +14,12 @@ _Path_List_Separator :: ':' _OPENDIR_FLAGS : linux.Open_Flags : {.NONBLOCK, .DIRECTORY, .LARGEFILE, .CLOEXEC} _is_path_separator :: proc(c: byte) -> bool { - return c == '/' + return c == _Path_Separator } _mkdir :: proc(path: string, perm: int) -> Error { - TEMP_ALLOCATOR_GUARD() - path_cstr := temp_cstring(path) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + path_cstr := clone_to_cstring(path, temp_allocator) or_return return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)u32(perm))) } @@ -52,9 +52,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error { } return _get_platform_error(errno) } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) // need something we can edit, and use to generate cstrings - path_bytes := make([]u8, len(path) + 1, temp_allocator()) + path_bytes := make([]u8, len(path) + 1, temp_allocator) // zero terminate the byte slice to make it a valid cstring copy(path_bytes, path) @@ -129,8 +129,8 @@ _remove_all :: proc(path: string) -> Error { return nil } - TEMP_ALLOCATOR_GUARD() - path_cstr := temp_cstring(path) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + path_cstr := clone_to_cstring(path, temp_allocator) or_return fd, errno := linux.open(path_cstr, _OPENDIR_FLAGS) #partial switch errno { @@ -168,14 +168,16 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (string, Error) } _set_working_directory :: proc(dir: string) -> Error { - dir_cstr := temp_cstring(dir) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + + dir_cstr := clone_to_cstring(dir, temp_allocator) or_return return _get_platform_error(linux.chdir(dir_cstr)) } _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - buf := make([dynamic]byte, 1024, temp_allocator()) or_return + buf := make([dynamic]byte, 1024, temp_allocator) or_return for { n, errno := linux.readlink("/proc/self/exe", buf[:]) if errno != .NONE { @@ -197,7 +199,7 @@ _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath: buf: [32]u8 copy(buf[:], PROC_FD_PATH) - strconv.itoa(buf[len(PROC_FD_PATH):], int(fd)) + strconv.write_int(buf[len(PROC_FD_PATH):], i64(fd), 10) if fullpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || fullpath[0] != '/' { delete(fullpath, allocator) @@ -205,3 +207,21 @@ _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath: } return } + +_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) { + rel := path + if rel == "" { + rel = "." + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + fd, errno := linux.open(clone_to_cstring(path, temp_allocator) or_return, {}) + if errno != nil { + err = _get_platform_error(errno) + return + } + defer linux.close(fd) + + return _get_full_path(fd, allocator) +} diff --git a/core/os/os2/path_netbsd.odin b/core/os/os2/path_netbsd.odin index f56a91fd6..815102dea 100644 --- a/core/os/os2/path_netbsd.odin +++ b/core/os/os2/path_netbsd.odin @@ -5,9 +5,9 @@ import "base:runtime" import "core:sys/posix" _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - buf := make([dynamic]byte, 1024, temp_allocator()) or_return + buf := make([dynamic]byte, 1024, temp_allocator) or_return for { n := posix.readlink("/proc/curproc/exe", raw_data(buf), len(buf)) if n < 0 { diff --git a/core/os/os2/path_openbsd.odin b/core/os/os2/path_openbsd.odin index 37b5de927..cbc0346d4 100644 --- a/core/os/os2/path_openbsd.odin +++ b/core/os/os2/path_openbsd.odin @@ -35,11 +35,11 @@ _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err return real(arg, allocator) } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - buf := strings.builder_make(temp_allocator()) + buf := strings.builder_make(temp_allocator) - paths := get_env("PATH", temp_allocator()) + paths := get_env("PATH", temp_allocator) for dir in strings.split_iterator(&paths, ":") { strings.builder_reset(&buf) strings.write_string(&buf, dir) diff --git a/core/os/os2/path_posix.odin b/core/os/os2/path_posix.odin index e6b95c0d4..f22cd446b 100644 --- a/core/os/os2/path_posix.odin +++ b/core/os/os2/path_posix.odin @@ -3,7 +3,6 @@ package os2 import "base:runtime" -import "core:path/filepath" import "core:sys/posix" @@ -15,9 +14,9 @@ _is_path_separator :: proc(c: byte) -> bool { return c == _Path_Separator } -_mkdir :: proc(name: string, perm: int) -> Error { - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) +_mkdir :: proc(name: string, perm: int) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cname := clone_to_cstring(name, temp_allocator) or_return if posix.mkdir(cname, transmute(posix.mode_t)posix._mode_t(perm)) != .OK { return _get_platform_error() } @@ -29,17 +28,17 @@ _mkdir_all :: proc(path: string, perm: int) -> Error { return .Invalid_Path } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) if exists(path) { return .Exist } - clean_path := filepath.clean(path, temp_allocator()) + clean_path := clean_path(path, temp_allocator) or_return return internal_mkdir_all(clean_path, perm) internal_mkdir_all :: proc(path: string, perm: int) -> Error { - dir, file := filepath.split(path) + dir, file := split_path(path) if file != path && dir != "/" { if len(dir) > 1 && dir[len(dir) - 1] == '/' { dir = dir[:len(dir) - 1] @@ -53,9 +52,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error { } } -_remove_all :: proc(path: string) -> Error { - TEMP_ALLOCATOR_GUARD() - cpath := temp_cstring(path) +_remove_all :: proc(path: string) -> (err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cpath := clone_to_cstring(path, temp_allocator) or_return dir := posix.opendir(cpath) if dir == nil { @@ -79,7 +78,7 @@ _remove_all :: proc(path: string) -> Error { continue } - fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator()) + fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator) if entry.d_type == .DIR { _remove_all(fullpath[:len(fullpath)-1]) or_return } else { @@ -96,10 +95,10 @@ _remove_all :: proc(path: string) -> Error { } _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) buf: [dynamic]byte - buf.allocator = temp_allocator() + buf.allocator = temp_allocator size := uint(posix.PATH_MAX) cwd: cstring @@ -117,10 +116,27 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, er } _set_working_directory :: proc(dir: string) -> (err: Error) { - TEMP_ALLOCATOR_GUARD() - cdir := temp_cstring(dir) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + cdir := clone_to_cstring(dir, temp_allocator) or_return if posix.chdir(cdir) != .OK { err = _get_platform_error() } return } + +_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) { + rel := path + if rel == "" { + rel = "." + } + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + rel_cstr := clone_to_cstring(rel, temp_allocator) or_return + path_ptr := posix.realpath(rel_cstr, nil) + if path_ptr == nil { + return "", Platform_Error(posix.errno()) + } + defer posix.free(path_ptr) + + path_str := clone_string(string(path_ptr), allocator) or_return + return path_str, nil +} diff --git a/core/os/os2/path_posixfs.odin b/core/os/os2/path_posixfs.odin new file mode 100644 index 000000000..0736e73d1 --- /dev/null +++ b/core/os/os2/path_posixfs.odin @@ -0,0 +1,57 @@ +#+private +#+build linux, darwin, netbsd, freebsd, openbsd, wasi +package os2 + +// This implementation is for all systems that have POSIX-compliant filesystem paths. + +_are_paths_identical :: proc(a, b: string) -> (identical: bool) { + return a == b +} + +_clean_path_handle_start :: proc(path: string, buffer: []u8) -> (rooted: bool, start: int) { + // Preserve rooted paths. + if _is_path_separator(path[0]) { + rooted = true + buffer[0] = _Path_Separator + start = 1 + } + return +} + +_is_absolute_path :: proc(path: string) -> bool { + return len(path) > 0 && _is_path_separator(path[0]) +} + +_get_relative_path_handle_start :: proc(base, target: string) -> bool { + base_rooted := len(base) > 0 && _is_path_separator(base[0]) + target_rooted := len(target) > 0 && _is_path_separator(target[0]) + return base_rooted == target_rooted +} + +_get_common_path_len :: proc(base, target: string) -> int { + i := 0 + end := min(len(base), len(target)) + for j in 0..=end { + if j == end || _is_path_separator(base[j]) { + if base[i:j] == target[i:j] { + i = j + } else { + break + } + } + } + return i +} + +_split_path :: proc(path: string) -> (dir, file: string) { + i := len(path) - 1 + for i >= 0 && !_is_path_separator(path[i]) { + i -= 1 + } + if i == 0 { + return path[:i+1], path[i+1:] + } else if i > 0 { + return path[:i], path[i+1:] + } + return "", path +} diff --git a/core/os/os2/path_wasi.odin b/core/os/os2/path_wasi.odin index 1c4fafa17..b8240e188 100644 --- a/core/os/os2/path_wasi.odin +++ b/core/os/os2/path_wasi.odin @@ -3,7 +3,6 @@ package os2 import "base:runtime" -import "core:path/filepath" import "core:sync" import "core:sys/wasm/wasi" @@ -29,17 +28,17 @@ _mkdir_all :: proc(path: string, perm: int) -> Error { return .Invalid_Path } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) if exists(path) { return .Exist } - clean_path := filepath.clean(path, temp_allocator()) + clean_path := clean_path(path, temp_allocator) return internal_mkdir_all(clean_path) internal_mkdir_all :: proc(path: string) -> Error { - dir, file := filepath.split(path) + dir, file := split_path(path) if file != path && dir != "/" { if len(dir) > 1 && dir[len(dir) - 1] == '/' { dir = dir[:len(dir) - 1] diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index 041a4d1e3..e5a1545ec 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -1,8 +1,9 @@ #+private package os2 -import win32 "core:sys/windows" import "base:runtime" +import "core:strings" +import win32 "core:sys/windows" _Path_Separator :: '\\' _Path_Separator_String :: "\\" @@ -13,8 +14,8 @@ _is_path_separator :: proc(c: byte) -> bool { } _mkdir :: proc(name: string, perm: int) -> Error { - TEMP_ALLOCATOR_GUARD() - if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator()) or_return, nil) { + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator) or_return, nil) { return _get_platform_error() } return nil @@ -32,9 +33,9 @@ _mkdir_all :: proc(path: string, perm: int) -> Error { return p, false, nil } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) - dir_stat, err := stat(path, temp_allocator()) + dir_stat, err := stat(path, temp_allocator) if err == nil { if dir_stat.type == .Directory { return nil @@ -62,7 +63,7 @@ _mkdir_all :: proc(path: string, perm: int) -> Error { err = mkdir(path, perm) if err != nil { - new_dir_stat, err1 := lstat(path, temp_allocator()) + new_dir_stat, err1 := lstat(path, temp_allocator) if err1 == nil && new_dir_stat.type == .Directory { return nil } @@ -81,8 +82,8 @@ _remove_all :: proc(path: string) -> Error { return nil } - TEMP_ALLOCATOR_GUARD() - dir := win32_utf8_to_wstring(path, temp_allocator()) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + dir := win32_utf8_to_wstring(path, temp_allocator) or_return empty: [1]u16 @@ -90,11 +91,11 @@ _remove_all :: proc(path: string) -> Error { nil, win32.FO_DELETE, dir, - &empty[0], + cstring16(&empty[0]), win32.FOF_NOCONFIRMATION | win32.FOF_NOERRORUI | win32.FOF_SILENT, false, nil, - &empty[0], + cstring16(&empty[0]), } res := win32.SHFileOperationW(&file_op) if res != 0 { @@ -108,10 +109,10 @@ _remove_all :: proc(path: string) -> Error { _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { win32.AcquireSRWLockExclusive(&cwd_lock) - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) sz_utf16 := win32.GetCurrentDirectoryW(0, nil) - dir_buf_wstr := make([]u16, sz_utf16, temp_allocator()) or_return + dir_buf_wstr := make([]u16, sz_utf16, temp_allocator) or_return sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr)) assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL. @@ -122,8 +123,8 @@ _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, er } _set_working_directory :: proc(dir: string) -> (err: Error) { - TEMP_ALLOCATOR_GUARD() - wstr := win32_utf8_to_wstring(dir, temp_allocator()) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + wstr := win32_utf8_to_wstring(dir, temp_allocator) or_return win32.AcquireSRWLockExclusive(&cwd_lock) @@ -137,9 +138,9 @@ _set_working_directory :: proc(dir: string) -> (err: Error) { } _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - buf := make([dynamic]u16, 512, temp_allocator()) or_return + buf := make([dynamic]u16, 512, temp_allocator) or_return for { ret := win32.GetModuleFileNameW(nil, raw_data(buf), win32.DWORD(len(buf))) if ret == 0 { @@ -159,7 +160,7 @@ _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err can_use_long_paths: bool @(init) -init_long_path_support :: proc() { +init_long_path_support :: proc "contextless" () { can_use_long_paths = false key: win32.HKEY @@ -186,7 +187,6 @@ init_long_path_support :: proc() { if value == 1 { can_use_long_paths = true } - } @(require_results) @@ -217,14 +217,14 @@ _fix_long_path_internal :: proc(path: string) -> string { return path } - if !_is_abs(path) { // relative path + if !_is_absolute_path(path) { // relative path return path } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) PREFIX :: `\\?` - path_buf := make([]byte, len(PREFIX)+len(path)+1, temp_allocator()) + path_buf := make([]byte, len(PREFIX)+len(path)+1, temp_allocator) copy(path_buf, PREFIX) n := len(path) r, w := 0, len(PREFIX) @@ -257,3 +257,98 @@ _fix_long_path_internal :: proc(path: string) -> string { return string(path_buf[:w]) } + +_are_paths_identical :: strings.equal_fold + +_clean_path_handle_start :: proc(path: string, buffer: []u8) -> (rooted: bool, start: int) { + // Preserve rooted paths. + start = _volume_name_len(path) + if start > 0 { + rooted = true + if len(path) > start && _is_path_separator(path[start]) { + // Take `C:` to `C:\`. + start += 1 + } + copy(buffer, path[:start]) + for n in 0..<start { + if _is_path_separator(buffer[n]) { + buffer[n] = _Path_Separator + } + } + } + return +} + +_is_absolute_path :: proc(path: string) -> bool { + if _is_reserved_name(path) { + return true + } + l := _volume_name_len(path) + if l == 0 { + return false + } + + path := path + path = path[l:] + if path == "" { + return false + } + return _is_path_separator(path[0]) +} + +_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) { + rel := path + if rel == "" { + rel = "." + } + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + rel_utf16 := win32.utf8_to_utf16(rel, temp_allocator) + n := win32.GetFullPathNameW(cstring16(raw_data(rel_utf16)), 0, nil, nil) + if n == 0 { + return "", Platform_Error(win32.GetLastError()) + } + + buf := make([]u16, n, temp_allocator) or_return + n = win32.GetFullPathNameW(cstring16(raw_data(rel_utf16)), u32(n), cstring16(raw_data(buf)), nil) + if n == 0 { + return "", Platform_Error(win32.GetLastError()) + } + + return win32.utf16_to_utf8(buf, allocator) +} + +_get_relative_path_handle_start :: proc(base, target: string) -> bool { + base_root := base[:_volume_name_len(base)] + target_root := target[:_volume_name_len(target)] + return strings.equal_fold(base_root, target_root) +} + +_get_common_path_len :: proc(base, target: string) -> int { + i := 0 + end := min(len(base), len(target)) + for j in 0..=end { + if j == end || _is_path_separator(base[j]) { + if strings.equal_fold(base[i:j], target[i:j]) { + i = j + } else { + break + } + } + } + return i +} + +_split_path :: proc(path: string) -> (dir, file: string) { + vol_len := _volume_name_len(path) + + i := len(path) - 1 + for i >= vol_len && !_is_path_separator(path[i]) { + i -= 1 + } + if i == vol_len { + return path[:i+1], path[i+1:] + } else if i > vol_len { + return path[:i], path[i+1:] + } + return "", path +} diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index c90e3add2..635befc64 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -16,7 +16,8 @@ Arguments to the current process. args := get_args() @(private="file") -get_args :: proc() -> []string { +get_args :: proc "contextless" () -> []string { + context = runtime.default_context() result := make([]string, len(runtime.args__), heap_allocator()) for rt_arg, i in runtime.args__ { result[i] = string(rt_arg) @@ -24,6 +25,12 @@ get_args :: proc() -> []string { return result } +@(fini, private="file") +delete_args :: proc "contextless" () { + context = runtime.default_context() + delete(args, heap_allocator()) +} + /* Exit the current process. */ @@ -264,7 +271,7 @@ specific process, even after it has died. **Note(linux)**: The `handle` will be referring to pidfd. */ Process :: struct { - pid: int, + pid: int, handle: uintptr, } @@ -290,21 +297,10 @@ process_open :: proc(pid: int, flags := Process_Open_Flags {}) -> (Process, Erro return _process_open(pid, flags) } - -/* -OS-specific process attributes. -*/ -Process_Attributes :: struct { - sys_attr: _Sys_Process_Attributes, -} - /* The description of how a process should be created. */ Process_Desc :: struct { - // OS-specific attributes. - sys_attr: Process_Attributes, - // The working directory of the process. If the string has length 0, the // working directory is assumed to be the current working directory of the // current process. @@ -407,11 +403,9 @@ process_exec :: proc( { stdout_b: [dynamic]byte stdout_b.allocator = allocator - defer stdout = stdout_b[:] stderr_b: [dynamic]byte stderr_b.allocator = allocator - defer stderr = stderr_b[:] buf: [1024]u8 = --- @@ -450,6 +444,9 @@ process_exec :: proc( } } } + + stdout = stdout_b[:] + stderr = stderr_b[:] } if err != nil { diff --git a/core/os/os2/process_linux.odin b/core/os/os2/process_linux.odin index 632bde6ba..170f0ea1a 100644 --- a/core/os/os2/process_linux.odin +++ b/core/os/os2/process_linux.odin @@ -10,7 +10,6 @@ import "core:slice" import "core:strings" import "core:strconv" import "core:sys/linux" -import "core:path/filepath" PIDFD_UNASSIGNED :: ~uintptr(0) @@ -51,7 +50,7 @@ _get_ppid :: proc() -> int { @(private="package") _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) dir_fd, errno := linux.open("/proc/", _OPENDIR_FLAGS) #partial switch errno { @@ -69,9 +68,9 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) } defer linux.close(dir_fd) - dynamic_list := make([dynamic]int, temp_allocator()) or_return + dynamic_list := make([dynamic]int, temp_allocator) or_return - buf := make([dynamic]u8, 128, 128, temp_allocator()) or_return + buf := make([dynamic]u8, 128, 128, temp_allocator) or_return loop: for { buflen: int buflen, errno = linux.getdents(dir_fd, buf[:]) @@ -101,7 +100,7 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) @(private="package") _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) info.pid = pid @@ -127,7 +126,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator passwd_bytes: []u8 passwd_err: Error - passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator()) + passwd_bytes, passwd_err = _read_entire_pseudo_file_cstring("/etc/passwd", temp_allocator) if passwd_err != nil { err = passwd_err break username_if @@ -163,13 +162,13 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator } } - cmdline_if: if selection & {.Working_Dir, .Command_Line, .Command_Args, .Executable_Path} != {} { + cmdline_if: if selection & {.Working_Dir, .Command_Line, .Command_Args} != {} { strings.builder_reset(&path_builder) strings.write_string(&path_builder, "/proc/") strings.write_int(&path_builder, pid) strings.write_string(&path_builder, "/cmdline") - cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator()) + cmdline_bytes, cmdline_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator) if cmdline_err != nil || len(cmdline_bytes) == 0 { err = cmdline_err break cmdline_if @@ -179,18 +178,18 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator terminator := strings.index_byte(cmdline, 0) assert(terminator > 0) - command_line_exec := cmdline[:terminator] + // command_line_exec := cmdline[:terminator] // Still need cwd if the execution on the command line is relative. cwd: string cwd_err: Error - if .Working_Dir in selection || (.Executable_Path in selection && command_line_exec[0] != '/') { + if .Working_Dir in selection { strings.builder_reset(&path_builder) strings.write_string(&path_builder, "/proc/") strings.write_int(&path_builder, pid) strings.write_string(&path_builder, "/cwd") - cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder) or_return, temp_allocator()) // allowed to fail + cwd, cwd_err = _read_link_cstr(strings.to_cstring(&path_builder) or_return, temp_allocator) // allowed to fail if cwd_err == nil && .Working_Dir in selection { info.working_dir = strings.clone(cwd, allocator) or_return info.fields += {.Working_Dir} @@ -200,18 +199,6 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator } } - if .Executable_Path in selection { - if cmdline[0] == '/' { - info.executable_path = strings.clone(cmdline[:terminator], allocator) or_return - info.fields += {.Executable_Path} - } else if cwd_err == nil { - info.executable_path = filepath.join({ cwd, cmdline[:terminator] }, allocator) or_return - info.fields += {.Executable_Path} - } else { - break cmdline_if - } - } - if selection & {.Command_Line, .Command_Args} != {} { // skip to first arg //cmdline = cmdline[terminator + 1:] @@ -258,7 +245,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator strings.write_int(&path_builder, pid) strings.write_string(&path_builder, "/stat") - proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator()) + proc_stat_bytes, stat_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator) if stat_err != nil { err = stat_err break stat_if @@ -297,7 +284,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator Nice, //... etc, } - stat_fields := strings.split(stats, " ", temp_allocator()) or_return + stat_fields := strings.split(stats, " ", temp_allocator) or_return if len(stat_fields) <= int(Fields.Nice) { break stat_if @@ -324,13 +311,37 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator } } + if .Executable_Path in selection { + /* + NOTE(Jeroen): + + The old version returned the wrong executable path for things like `bash` or `sh`, + for whom `/proc/<pid>/cmdline` will just report "bash" or "sh", + resulting in misleading paths like `$PWD/sh`, even though that executable doesn't exist there. + + Thanks to Yawning for suggesting `/proc/self/exe`. + */ + + strings.builder_reset(&path_builder) + strings.write_string(&path_builder, "/proc/") + strings.write_int(&path_builder, pid) + strings.write_string(&path_builder, "/exe") + + if exe_bytes, exe_err := _read_link(strings.to_string(path_builder), temp_allocator); exe_err == nil { + info.executable_path = strings.clone(string(exe_bytes), allocator) or_return + info.fields += {.Executable_Path} + } else { + err = exe_err + } + } + if .Environment in selection { strings.builder_reset(&path_builder) strings.write_string(&path_builder, "/proc/") strings.write_int(&path_builder, pid) strings.write_string(&path_builder, "/environ") - if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator()); env_err == nil { + if env_bytes, env_err := _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator); env_err == nil { env := string(env_bytes) env_list := make([dynamic]string, allocator) or_return @@ -380,11 +391,8 @@ _process_open :: proc(pid: int, _: Process_Open_Flags) -> (process: Process, err } @(private="package") -_Sys_Process_Attributes :: struct {} - -@(private="package") _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) if len(desc.command) == 0 { return process, .Invalid_Command @@ -393,7 +401,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { dir_fd := linux.AT_FDCWD errno: linux.Errno if desc.working_dir != "" { - dir_cstr := temp_cstring(desc.working_dir) or_return + dir_cstr := clone_to_cstring(desc.working_dir, temp_allocator) or_return if dir_fd, errno = linux.open(dir_cstr, _OPENDIR_FLAGS); errno != .NONE { return process, _get_platform_error(errno) } @@ -406,10 +414,10 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { exe_path: cstring executable_name := desc.command[0] if strings.index_byte(executable_name, '/') < 0 { - path_env := get_env("PATH", temp_allocator()) - path_dirs := filepath.split_list(path_env, temp_allocator()) or_return + path_env := get_env("PATH", temp_allocator) + path_dirs := split_path_list(path_env, temp_allocator) or_return - exe_builder := strings.builder_make(temp_allocator()) or_return + exe_builder := strings.builder_make(temp_allocator) or_return found: bool for dir in path_dirs { @@ -436,7 +444,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { } } } else { - exe_path = temp_cstring(executable_name) or_return + exe_path = clone_to_cstring(executable_name, temp_allocator) or_return if linux.access(exe_path, linux.X_OK) != .NONE { return process, .Not_Exist } @@ -444,20 +452,20 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { // args and environment need to be a list of cstrings // that are terminated by a nil pointer. - cargs := make([]cstring, len(desc.command) + 1, temp_allocator()) or_return + cargs := make([]cstring, len(desc.command) + 1, temp_allocator) or_return for command, i in desc.command { - cargs[i] = temp_cstring(command) or_return + cargs[i] = clone_to_cstring(command, temp_allocator) or_return } // Use current process' environment if description didn't provide it. env: [^]cstring if desc.env == nil { // take this process's current environment - env = raw_data(export_cstring_environment(temp_allocator())) + env = raw_data(export_cstring_environment(temp_allocator)) } else { - cenv := make([]cstring, len(desc.env) + 1, temp_allocator()) or_return + cenv := make([]cstring, len(desc.env) + 1, temp_allocator) or_return for env, i in desc.env { - cenv[i] = temp_cstring(env) or_return + cenv[i] = clone_to_cstring(env, temp_allocator) or_return } env = &cenv[0] } @@ -585,7 +593,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { } _process_state_update_times :: proc(state: ^Process_State) -> (err: Error) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) stat_path_buf: [48]u8 path_builder := strings.builder_from_bytes(stat_path_buf[:]) @@ -594,7 +602,7 @@ _process_state_update_times :: proc(state: ^Process_State) -> (err: Error) { strings.write_string(&path_builder, "/stat") stat_buf: []u8 - stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator()) + stat_buf, err = _read_entire_pseudo_file(strings.to_cstring(&path_builder) or_return, temp_allocator) if err != nil { return } diff --git a/core/os/os2/process_posix.odin b/core/os/os2/process_posix.odin index 3fa429cbe..fcacdf654 100644 --- a/core/os/os2/process_posix.odin +++ b/core/os/os2/process_posix.odin @@ -6,7 +6,6 @@ import "base:runtime" import "core:time" import "core:strings" -import "core:path/filepath" import kq "core:sys/kqueue" import "core:sys/posix" @@ -47,22 +46,20 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime return _process_info_by_pid(_get_pid(), selection, allocator) } -_Sys_Process_Attributes :: struct {} - _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { if len(desc.command) == 0 { err = .Invalid_Path return } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) // search PATH if just a plain name is provided. - exe_builder := strings.builder_make(temp_allocator()) + exe_builder := strings.builder_make(temp_allocator) exe_name := desc.command[0] if strings.index_byte(exe_name, '/') < 0 { - path_env := get_env("PATH", temp_allocator()) - path_dirs := filepath.split_list(path_env, temp_allocator()) + path_env := get_env("PATH", temp_allocator) + path_dirs := split_path_list(path_env, temp_allocator) or_return found: bool for dir in path_dirs { @@ -111,12 +108,12 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { } cwd: cstring; if desc.working_dir != "" { - cwd = temp_cstring(desc.working_dir) + cwd = clone_to_cstring(desc.working_dir, temp_allocator) or_return } - cmd := make([]cstring, len(desc.command) + 1, temp_allocator()) + cmd := make([]cstring, len(desc.command) + 1, temp_allocator) for part, i in desc.command { - cmd[i] = temp_cstring(part) + cmd[i] = clone_to_cstring(part, temp_allocator) or_return } env: [^]cstring @@ -124,9 +121,9 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { // take this process's current environment env = posix.environ } else { - cenv := make([]cstring, len(desc.env) + 1, temp_allocator()) + cenv := make([]cstring, len(desc.env) + 1, temp_allocator) for env, i in desc.env { - cenv[i] = temp_cstring(env) + cenv[i] = clone_to_cstring(env, temp_allocator) or_return } env = raw_data(cenv) } diff --git a/core/os/os2/process_posix_darwin.odin b/core/os/os2/process_posix_darwin.odin index 0ea1f643c..f655d42a9 100644 --- a/core/os/os2/process_posix_darwin.odin +++ b/core/os/os2/process_posix_darwin.odin @@ -10,10 +10,10 @@ import "core:sys/posix" import "core:sys/unix" import "core:time" -foreign import lib "system:System.framework" +foreign import lib "system:System" foreign lib { - sysctl :: proc( + sysctl :: proc "c" ( name: [^]i32, namelen: u32, oldp: rawptr, oldlenp: ^uint, newp: rawptr, newlen: uint, @@ -50,6 +50,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator } + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) info.pid = pid // Thought on errors is: allocation failures return immediately (also why the non-allocation stuff is done first), @@ -127,7 +128,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator break args } - buf := runtime.make_aligned([]byte, length, 4, temp_allocator()) + buf := runtime.make_aligned([]byte, length, 4, temp_allocator) if sysctl(raw_data(mib), 3, raw_data(buf), &length, nil, 0) != .OK { if err == nil { err = _get_platform_error() @@ -239,9 +240,9 @@ _process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) return } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - buffer := make([]i32, ret, temp_allocator()) + buffer := make([]i32, ret, temp_allocator) ret = darwin.proc_listallpids(raw_data(buffer), ret*size_of(i32)) if ret < 0 { err = _get_platform_error() diff --git a/core/os/os2/process_wasi.odin b/core/os/os2/process_wasi.odin index 6ebfe3788..9f4d61649 100644 --- a/core/os/os2/process_wasi.odin +++ b/core/os/os2/process_wasi.odin @@ -44,8 +44,6 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime return } -_Sys_Process_Attributes :: struct {} - _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { err = .Unsupported return diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index 536930ee1..990da6616 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -162,9 +162,10 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator if err != nil { break read_peb } + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) if selection >= {.Command_Line, .Command_Args} { - TEMP_ALLOCATOR_GUARD() - cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return + temp_allocator_scope(temp_allocator) + cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return _, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) if err != nil { break read_peb @@ -174,14 +175,14 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator info.fields += {.Command_Line} } if .Command_Args in selection { - info.command_args = _parse_command_line(raw_data(cmdline_w), allocator) or_return + info.command_args = _parse_command_line(cstring16(raw_data(cmdline_w)), allocator) or_return info.fields += {.Command_Args} } } if .Environment in selection { - TEMP_ALLOCATOR_GUARD() + temp_allocator_scope(temp_allocator) env_len := process_params.EnvironmentSize / 2 - envs_w := make([]u16, env_len, temp_allocator()) or_return + envs_w := make([]u16, env_len, temp_allocator) or_return _, err = read_memory_as_slice(ph, process_params.Environment, envs_w) if err != nil { break read_peb @@ -190,8 +191,8 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator info.fields += {.Environment} } if .Working_Dir in selection { - TEMP_ALLOCATOR_GUARD() - cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return + temp_allocator_scope(temp_allocator) + cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return _, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) if err != nil { break read_peb @@ -272,9 +273,10 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields if err != nil { break read_peb } + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) if selection >= {.Command_Line, .Command_Args} { - TEMP_ALLOCATOR_GUARD() - cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return + temp_allocator_scope(temp_allocator) + cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator) or_return _, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) if err != nil { break read_peb @@ -284,14 +286,14 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields info.fields += {.Command_Line} } if .Command_Args in selection { - info.command_args = _parse_command_line(raw_data(cmdline_w), allocator) or_return + info.command_args = _parse_command_line(cstring16(raw_data(cmdline_w)), allocator) or_return info.fields += {.Command_Args} } } if .Environment in selection { - TEMP_ALLOCATOR_GUARD() + temp_allocator_scope(temp_allocator) env_len := process_params.EnvironmentSize / 2 - envs_w := make([]u16, env_len, temp_allocator()) or_return + envs_w := make([]u16, env_len, temp_allocator) or_return _, err = read_memory_as_slice(ph, process_params.Environment, envs_w) if err != nil { break read_peb @@ -300,8 +302,8 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields info.fields += {.Environment} } if .Working_Dir in selection { - TEMP_ALLOCATOR_GUARD() - cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return + temp_allocator_scope(temp_allocator) + cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator) or_return _, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) if err != nil { break read_peb @@ -418,34 +420,63 @@ _process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, } @(private="package") -_Sys_Process_Attributes :: struct {} - -@(private="package") _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { - TEMP_ALLOCATOR_GUARD() - command_line := _build_command_line(desc.command, temp_allocator()) - command_line_w := win32_utf8_to_wstring(command_line, temp_allocator()) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + command_line := _build_command_line(desc.command, temp_allocator) + command_line_w := win32_utf8_to_wstring(command_line, temp_allocator) or_return environment := desc.env if desc.env == nil { - environment = environ(temp_allocator()) or_return + environment = environ(temp_allocator) or_return + } + environment_block := _build_environment_block(environment, temp_allocator) + environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator) or_return + + stderr_handle: win32.HANDLE + stdout_handle: win32.HANDLE + stdin_handle: win32.HANDLE + + null_handle: win32.HANDLE + if desc.stdout == nil || desc.stderr == nil || desc.stdin == nil { + null_handle = win32.CreateFileW( + win32.L("NUL"), + win32.GENERIC_READ|win32.GENERIC_WRITE, + win32.FILE_SHARE_READ|win32.FILE_SHARE_WRITE, + &win32.SECURITY_ATTRIBUTES{ + nLength = size_of(win32.SECURITY_ATTRIBUTES), + bInheritHandle = true, + }, + win32.OPEN_EXISTING, + win32.FILE_ATTRIBUTE_NORMAL, + nil, + ) + // Opening NUL should always succeed. + assert(null_handle != nil) + } + // NOTE(laytan): I believe it is fine to close this handle right after CreateProcess, + // and we don't have to hold onto this until the process exits. + defer if null_handle != nil { + win32.CloseHandle(null_handle) } - environment_block := _build_environment_block(environment, temp_allocator()) - environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator()) or_return - stderr_handle := win32.GetStdHandle(win32.STD_ERROR_HANDLE) - stdout_handle := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE) - stdin_handle := win32.GetStdHandle(win32.STD_INPUT_HANDLE) - if desc.stdout != nil { + if desc.stdout == nil { + stdout_handle = null_handle + } else { stdout_handle = win32.HANDLE((^File_Impl)(desc.stdout.impl).fd) } - if desc.stderr != nil { + + if desc.stderr == nil { + stderr_handle = null_handle + } else { stderr_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd) } - if desc.stdin != nil { + + if desc.stdin == nil { + stdin_handle = null_handle + } else { stdin_handle = win32.HANDLE((^File_Impl)(desc.stdin.impl).fd) } - working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator()) or_else nil) if len(desc.working_dir) > 0 else nil + working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator) or_else nil) if len(desc.working_dir) > 0 else nil process_info: win32.PROCESS_INFORMATION ok := win32.CreateProcessW( nil, @@ -579,11 +610,11 @@ _process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path err =_get_platform_error() return } - return win32_wstring_to_utf8(raw_data(entry.szExePath[:]), allocator) + return win32_wstring_to_utf8(cstring16(raw_data(entry.szExePath[:])), allocator) } _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) { - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) token_handle: win32.HANDLE if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) { err = _get_platform_error() @@ -598,7 +629,7 @@ _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Alloc } err = nil } - token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator()) or_return)) + token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator) or_return)) if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) { err = _get_platform_error() return @@ -614,12 +645,12 @@ _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Alloc err = _get_platform_error() return } - username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator()) or_return - domain := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator()) or_return + username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator) or_return + domain := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator) or_return return strings.concatenate({domain, "\\", username}, allocator) } -_parse_command_line :: proc(cmd_line_w: [^]u16, allocator: runtime.Allocator) -> (argv: []string, err: Error) { +_parse_command_line :: proc(cmd_line_w: cstring16, allocator: runtime.Allocator) -> (argv: []string, err: Error) { argc: i32 argv_w := win32.CommandLineToArgvW(cmd_line_w, &argc) if argv_w == nil { diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin index b53ebb3ab..d6b524684 100644 --- a/core/os/os2/stat.odin +++ b/core/os/os2/stat.odin @@ -1,7 +1,6 @@ package os2 import "base:runtime" -import "core:path/filepath" import "core:strings" import "core:time" @@ -24,8 +23,8 @@ File_Info :: struct { @(require_results) file_info_clone :: proc(fi: File_Info, allocator: runtime.Allocator) -> (cloned: File_Info, err: runtime.Allocator_Error) { cloned = fi - cloned.fullpath = strings.clone(fi.fullpath) or_return - cloned.name = filepath.base(cloned.fullpath) + cloned.fullpath = strings.clone(fi.fullpath, allocator) or_return + _, cloned.name = split_path(cloned.fullpath) return } @@ -74,14 +73,14 @@ last_write_time_by_name :: modification_time_by_path @(require_results) modification_time :: proc(f: ^File) -> (time.Time, Error) { - TEMP_ALLOCATOR_GUARD() - fi, err := fstat(f, temp_allocator()) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + fi, err := fstat(f, temp_allocator) return fi.modification_time, err } @(require_results) modification_time_by_path :: proc(path: string) -> (time.Time, Error) { - TEMP_ALLOCATOR_GUARD() - fi, err := stat(path, temp_allocator()) + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + fi, err := stat(path, temp_allocator) return fi.modification_time, err } diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index 0433c1a61..373765be5 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -4,7 +4,6 @@ package os2 import "core:time" import "base:runtime" import "core:sys/linux" -import "core:path/filepath" _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { impl := (^File_Impl)(f.impl) @@ -42,14 +41,14 @@ _fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fi: File creation_time = time.Time{i64(s.ctime.time_sec) * i64(time.Second) + i64(s.ctime.time_nsec)}, // regular stat does not provide this } fi.creation_time = fi.modification_time - fi.name = filepath.base(fi.fullpath) + _, fi.name = split_path(fi.fullpath) return } // NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { - TEMP_ALLOCATOR_GUARD() - name_cstr := temp_cstring(name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + name_cstr := clone_to_cstring(name, temp_allocator) or_return fd, errno := linux.open(name_cstr, {}) if errno != .NONE { @@ -60,8 +59,8 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err } _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { - TEMP_ALLOCATOR_GUARD() - name_cstr := temp_cstring(name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + name_cstr := clone_to_cstring(name, temp_allocator) or_return fd, errno := linux.open(name_cstr, {.PATH, .NOFOLLOW}) if errno != .NONE { diff --git a/core/os/os2/stat_posix.odin b/core/os/os2/stat_posix.odin index 88029c1f5..6ffbdf1da 100644 --- a/core/os/os2/stat_posix.odin +++ b/core/os/os2/stat_posix.odin @@ -4,13 +4,12 @@ package os2 import "base:runtime" -import "core:path/filepath" import "core:sys/posix" import "core:time" internal_stat :: proc(stat: posix.stat_t, fullpath: string) -> (fi: File_Info) { fi.fullpath = fullpath - fi.name = filepath.base(fi.fullpath) + _, fi.name = split_path(fi.fullpath) fi.inode = u128(stat.st_ino) fi.size = i64(stat.st_size) @@ -70,8 +69,8 @@ _stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err return } - TEMP_ALLOCATOR_GUARD() - cname := temp_cstring(name) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + cname := clone_to_cstring(name, temp_allocator) or_return fd := posix.open(cname, {}) if fd == -1 { @@ -97,33 +96,34 @@ _lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, er return } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) // NOTE: can't use realpath or open (+ fcntl F_GETPATH) here because it tries to resolve symlinks. // NOTE: This might not be correct when given "/symlink/foo.txt", // you would want that to resolve "/symlink", but not resolve "foo.txt". - fullpath := filepath.clean(name, temp_allocator()) + fullpath := clean_path(name, temp_allocator) or_return assert(len(fullpath) > 0) switch { case fullpath[0] == '/': // nothing. case fullpath == ".": - fullpath = getwd(temp_allocator()) or_return + fullpath = getwd(temp_allocator) or_return case len(fullpath) > 1 && fullpath[0] == '.' && fullpath[1] == '/': fullpath = fullpath[2:] fallthrough case: fullpath = concatenate({ - getwd(temp_allocator()) or_return, + getwd(temp_allocator) or_return, "/", fullpath, - }, temp_allocator()) or_return + }, temp_allocator) or_return } stat: posix.stat_t - if posix.lstat(temp_cstring(fullpath), &stat) != .OK { + c_fullpath := clone_to_cstring(fullpath, temp_allocator) or_return + if posix.lstat(c_fullpath, &stat) != .OK { err = _get_platform_error() return } diff --git a/core/os/os2/stat_wasi.odin b/core/os/os2/stat_wasi.odin index 2992c6267..bf18d8273 100644 --- a/core/os/os2/stat_wasi.odin +++ b/core/os/os2/stat_wasi.odin @@ -3,13 +3,12 @@ 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.name = split_path(fi.fullpath) fi.inode = u128(stat.ino) fi.size = i64(stat.size) diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index 31f5d9e88..3dee42be6 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -45,16 +45,16 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path name = "." } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - p := win32_utf8_to_utf16(name, temp_allocator()) or_return + p := win32_utf8_to_utf16(name, temp_allocator) or_return - n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil) + n := win32.GetFullPathNameW(cstring16(raw_data(p)), 0, nil, nil) if n == 0 { return "", _get_platform_error() } - buf := make([]u16, n+1, temp_allocator()) - n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil) + buf := make([]u16, n+1, temp_allocator) + n = win32.GetFullPathNameW(cstring16(raw_data(p)), u32(len(buf)), cstring16(raw_data(buf)), nil) if n == 0 { return "", _get_platform_error() } @@ -65,9 +65,9 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt if len(name) == 0 { return {}, .Not_Exist } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - wname := _fix_long_path(name, temp_allocator()) or_return + wname := _fix_long_path(name, temp_allocator) or_return fa: win32.WIN32_FILE_ATTRIBUTE_DATA ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa) if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { @@ -137,11 +137,11 @@ _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (strin return "", _get_platform_error() } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - buf := make([]u16, max(n, 260)+1, temp_allocator()) - n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0) - return _cleanpath_from_buf(buf[:n], allocator) + buf := make([]u16, max(n, 260)+1, temp_allocator) + n = win32.GetFinalPathNameByHandleW(h, cstring16(raw_data(buf)), u32(len(buf)), 0) + return _cleanpath_from_buf(string16(buf[:n]), allocator) } _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { @@ -155,15 +155,15 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { return nil, _get_platform_error() } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({}) - buf := make([]u16, max(n, 260)+1, temp_allocator()) - n = win32.GetFinalPathNameByHandleW(h, raw_data(buf), u32(len(buf)), 0) + buf := make([]u16, max(n, 260)+1, temp_allocator) + n = win32.GetFinalPathNameByHandleW(h, cstring16(raw_data(buf)), u32(len(buf)), 0) return _cleanpath_strip_prefix(buf[:n]), nil } -_cleanpath_from_buf :: proc(buf: []u16, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { - buf := buf +_cleanpath_from_buf :: proc(buf: string16, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { + buf := transmute([]u16)buf buf = _cleanpath_strip_prefix(buf) return win32_utf16_to_utf8(buf, allocator) } @@ -236,14 +236,30 @@ _file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: wi return } +// a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC) +time_as_filetime :: #force_inline proc(t: time.Time) -> (ft: win32.LARGE_INTEGER) { + win := u64(t._nsec / 100) + 116444736000000000 + return win32.LARGE_INTEGER(win) +} + +filetime_as_time_li :: #force_inline proc(ft: win32.LARGE_INTEGER) -> (t: time.Time) { + return {_nsec=(i64(ft) - 116444736000000000) * 100} +} + +filetime_as_time_ft :: #force_inline proc(ft: win32.FILETIME) -> (t: time.Time) { + return filetime_as_time_li(win32.LARGE_INTEGER(ft.dwLowDateTime) + win32.LARGE_INTEGER(ft.dwHighDateTime) << 32) +} + +filetime_as_time :: proc{filetime_as_time_ft, filetime_as_time_li} + _file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0) fi.type = type fi.mode |= mode - fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) - fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) - fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) + fi.creation_time = filetime_as_time(d.ftCreationTime) + fi.modification_time = filetime_as_time(d.ftLastWriteTime) + fi.access_time = filetime_as_time(d.ftLastAccessTime) fi.fullpath, e = full_path_from_name(name, allocator) fi.name = basename(fi.fullpath) return @@ -254,9 +270,9 @@ _file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0) fi.type = type fi.mode |= mode - fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) - fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) - fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) + fi.creation_time = filetime_as_time(d.ftCreationTime) + fi.modification_time = filetime_as_time(d.ftLastWriteTime) + fi.access_time = filetime_as_time(d.ftLastAccessTime) fi.fullpath, e = full_path_from_name(name, allocator) fi.name = basename(fi.fullpath) return @@ -286,9 +302,9 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, h, 0) fi.type = type fi.mode |= mode - fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) - fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) - fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) + fi.creation_time = filetime_as_time(d.ftCreationTime) + fi.modification_time = filetime_as_time(d.ftLastWriteTime) + fi.access_time = filetime_as_time(d.ftLastAccessTime) return fi, nil } @@ -310,62 +326,68 @@ _is_reserved_name :: proc(path: string) -> bool { return false } -_is_UNC :: proc(path: string) -> bool { - return _volume_name_len(path) > 2 -} +_volume_name_len :: proc(path: string) -> (length: int) { + if len(path) < 2 { + return 0 + } -_volume_name_len :: proc(path: string) -> int { - if ODIN_OS == .Windows { - if len(path) < 2 { - return 0 + if path[1] == ':' { + switch path[0] { + case 'a'..='z', 'A'..='Z': + return 2 } - c := path[0] - if path[1] == ':' { - switch c { - case 'a'..='z', 'A'..='Z': - return 2 - } + } + + /* + See: URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx + Further allowed paths can be of the form of: + - \\server\share or \\server\share\more\path + - \\?\C:\... + - \\.\PhysicalDriveX + */ + // Any remaining kind of path has to start with two slashes. + if !_is_path_separator(path[0]) || !_is_path_separator(path[1]) { + return 0 + } + + // Device path. The volume name is the whole string + if len(path) >= 5 && path[2] == '.' && _is_path_separator(path[3]) { + return len(path) + } + + // We're a UNC share `\\host\share`, file namespace `\\?\C:` or UNC in file namespace `\\?\\host\share` + prefix := 2 + + // File namespace. + if len(path) >= 5 && path[2] == '?' && _is_path_separator(path[3]) { + if _is_path_separator(path[4]) { + // `\\?\\` UNC path in file namespace + prefix = 5 } - // URL: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx - if l := len(path); l >= 5 && _is_path_separator(path[0]) && _is_path_separator(path[1]) && - !_is_path_separator(path[2]) && path[2] != '.' { - for n := 3; n < l-1; n += 1 { - if _is_path_separator(path[n]) { - n += 1 - if !_is_path_separator(path[n]) { - if path[n] == '.' { - break - } - } - for ; n < l; n += 1 { - if _is_path_separator(path[n]) { - break - } - } - return n - } - break + if len(path) >= 6 && path[5] == ':' { + switch path[4] { + case 'a'..='z', 'A'..='Z': + return 6 + case: + return 0 } } } - return 0 -} -_is_abs :: proc(path: string) -> bool { - if _is_reserved_name(path) { - return true - } - l := _volume_name_len(path) - if l == 0 { - return false - } + // UNC path, minimum version of the volume is `\\h\s` for host, share. + // Can also contain an IP address in the host position. + slash_count := 0 + for i in prefix..<len(path) { + // Host needs to be at least 1 character + if _is_path_separator(path[i]) && i > 0 { + slash_count += 1 - path := path - path = path[l:] - if path == "" { - return false + if slash_count == 2 { + return i + } + } } - return is_path_separator(path[0]) -} + return len(path) +}
\ No newline at end of file diff --git a/core/os/os2/temp_file.odin b/core/os/os2/temp_file.odin index 5ca4e1453..e3e74bd11 100644 --- a/core/os/os2/temp_file.odin +++ b/core/os/os2/temp_file.odin @@ -15,13 +15,13 @@ MAX_ATTEMPTS :: 1<<13 // Should be enough for everyone, right? // The caller must `close` the file once finished with. @(require_results) create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) { - TEMP_ALLOCATOR_GUARD() - dir := dir if dir != "" else temp_directory(temp_allocator()) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({}) + dir := dir if dir != "" else temp_directory(temp_allocator) or_return prefix, suffix := _prefix_and_suffix(pattern) or_return - prefix = temp_join_path(dir, prefix) or_return + prefix = temp_join_path(dir, prefix, temp_allocator) or_return - rand_buf: [32]byte - name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator()) + rand_buf: [10]byte + name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator) attempts := 0 for { @@ -47,13 +47,13 @@ mkdir_temp :: make_directory_temp // If `dir` is an empty tring, `temp_directory()` will be used. @(require_results) make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) -> (temp_path: string, err: Error) { - TEMP_ALLOCATOR_GUARD() - dir := dir if dir != "" else temp_directory(temp_allocator()) or_return + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + dir := dir if dir != "" else temp_directory(temp_allocator) or_return prefix, suffix := _prefix_and_suffix(pattern) or_return - prefix = temp_join_path(dir, prefix) or_return + prefix = temp_join_path(dir, prefix, temp_allocator) or_return - rand_buf: [32]byte - name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator()) + rand_buf: [10]byte + name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator) attempts := 0 for { @@ -70,7 +70,7 @@ make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) return "", err } if err == .Not_Exist { - if _, serr := stat(dir, temp_allocator()); serr == .Not_Exist { + if _, serr := stat(dir, temp_allocator); serr == .Not_Exist { return "", serr } } @@ -88,10 +88,10 @@ temp_directory :: proc(allocator: runtime.Allocator) -> (string, Error) { @(private="file") -temp_join_path :: proc(dir, name: string) -> (string, runtime.Allocator_Error) { +temp_join_path :: proc(dir, name: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { if len(dir) > 0 && is_path_separator(dir[len(dir)-1]) { - return concatenate({dir, name}, temp_allocator(),) + return concatenate({dir, name}, allocator) } - return concatenate({dir, Path_Separator_String, name}, temp_allocator()) + return concatenate({dir, Path_Separator_String, name}, allocator) } diff --git a/core/os/os2/temp_file_linux.odin b/core/os/os2/temp_file_linux.odin index 4eacbc54a..310720cbe 100644 --- a/core/os/os2/temp_file_linux.odin +++ b/core/os/os2/temp_file_linux.odin @@ -4,8 +4,8 @@ package os2 import "base:runtime" _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { - TEMP_ALLOCATOR_GUARD() - tmpdir := get_env("TMPDIR", temp_allocator()) + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + tmpdir := get_env("TMPDIR", temp_allocator) if tmpdir == "" { tmpdir = "/tmp" } diff --git a/core/os/os2/temp_file_windows.odin b/core/os/os2/temp_file_windows.odin index 3e3e1285c..91ea284a1 100644 --- a/core/os/os2/temp_file_windows.odin +++ b/core/os/os2/temp_file_windows.odin @@ -9,15 +9,15 @@ _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Er if n == 0 { return "", nil } - TEMP_ALLOCATOR_GUARD() + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - b := make([]u16, max(win32.MAX_PATH, n), temp_allocator()) - n = win32.GetTempPathW(u32(len(b)), raw_data(b)) + b := make([]u16, max(win32.MAX_PATH, n), temp_allocator) + n = win32.GetTempPathW(u32(len(b)), cstring16(raw_data(b))) if n == 3 && b[1] == ':' && b[2] == '\\' { } else if n > 0 && b[n-1] == '\\' { n -= 1 } - return win32_utf16_to_utf8(b[:n], allocator) + return win32_utf16_to_utf8(string16(b[:n]), allocator) } diff --git a/core/os/os2/user.odin b/core/os/os2/user.odin index a0a7a839d..e2a4ec4d0 100644 --- a/core/os/os2/user.odin +++ b/core/os/os2/user.odin @@ -2,78 +2,148 @@ package os2 import "base:runtime" +// ``` +// Windows: C:\Users\Alice +// macOS: /Users/Alice +// Linux: /home/alice +// ``` +@(require_results) +user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return _user_home_dir(allocator) +} + +// Files that applications can regenerate/refetch at a loss of speed, e.g. shader caches +// +// Sometimes deleted for system maintenance +// +// ``` +// Windows: C:\Users\Alice\AppData\Local +// macOS: /Users/Alice/Library/Caches +// Linux: /home/alice/.cache +// ``` @(require_results) user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - TEMP_ALLOCATOR_GUARD() + return _user_cache_dir(allocator) +} + +// User-hidden application data +// +// ``` +// Windows: C:\Users\Alice\AppData\Local ("C:\Users\Alice\AppData\Roaming" if `roaming`) +// macOS: /Users/Alice/Library/Application Support +// Linux: /home/alice/.local/share +// ``` +// +// NOTE: (Windows only) `roaming` is for syncing across multiple devices within a *domain network* +@(require_results) +user_data_dir :: proc(allocator: runtime.Allocator, roaming := false) -> (dir: string, err: Error) { + return _user_data_dir(allocator, roaming) +} - #partial switch ODIN_OS { - case .Windows: - dir = get_env("LocalAppData", temp_allocator()) - if dir != "" { - dir = clone_string(dir, allocator) or_return - } - case .Darwin: - dir = get_env("HOME", temp_allocator()) - if dir != "" { - dir = concatenate({dir, "/Library/Caches"}, allocator) or_return - } - case: // All other UNIX systems - dir = get_env("XDG_CACHE_HOME", allocator) - if dir == "" { - dir = get_env("HOME", temp_allocator()) - if dir == "" { - return - } - dir = concatenate({dir, "/.cache"}, allocator) or_return - } - } - if dir == "" { - err = .Invalid_Path - } - return +// Non-essential application data, e.g. history, ui layout state +// +// ``` +// Windows: C:\Users\Alice\AppData\Local +// macOS: /Users/Alice/Library/Application Support +// Linux: /home/alice/.local/state +// ``` +@(require_results) +user_state_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return _user_state_dir(allocator) } +// Application log files +// +// ``` +// Windows: C:\Users\Alice\AppData\Local +// macOS: /Users/Alice/Library/Logs +// Linux: /home/alice/.local/state +// ``` @(require_results) -user_config_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - TEMP_ALLOCATOR_GUARD() +user_log_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return _user_log_dir(allocator) +} - #partial switch ODIN_OS { - case .Windows: - dir = get_env("AppData", temp_allocator()) - if dir != "" { - dir = clone_string(dir, allocator) or_return - } - case .Darwin: - dir = get_env("HOME", temp_allocator()) - if dir != "" { - dir = concatenate({dir, "/.config"}, allocator) or_return - } - case: // All other UNIX systems - dir = get_env("XDG_CACHE_HOME", allocator) - if dir == "" { - dir = get_env("HOME", temp_allocator()) - if dir == "" { - return - } - dir = concatenate({dir, "/.config"}, allocator) or_return - } - } - if dir == "" { - err = .Invalid_Path - } - return +// Application settings/preferences +// +// ``` +// Windows: C:\Users\Alice\AppData\Local ("C:\Users\Alice\AppData\Roaming" if `roaming`) +// macOS: /Users/Alice/Library/Application Support +// Linux: /home/alice/.config +// ``` +// +// NOTE: (Windows only) `roaming` is for syncing across multiple devices within a *domain network* +@(require_results) +user_config_dir :: proc(allocator: runtime.Allocator, roaming := false) -> (dir: string, err: Error) { + return _user_config_dir(allocator, roaming) } +// ``` +// Windows: C:\Users\Alice\Music +// macOS: /Users/Alice/Music +// Linux: /home/alice/Music +// ``` @(require_results) -user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - env := "HOME" - #partial switch ODIN_OS { - case .Windows: - env = "USERPROFILE" - } - if v := get_env(env, allocator); v != "" { - return v, nil - } - return "", .Invalid_Path +user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return _user_music_dir(allocator) +} + +// ``` +// Windows: C:\Users\Alice\Desktop +// macOS: /Users/Alice/Desktop +// Linux: /home/alice/Desktop +// ``` +@(require_results) +user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return _user_desktop_dir(allocator) +} + +// ``` +// Windows: C:\Users\Alice\Documents +// macOS: /Users/Alice/Documents +// Linux: /home/alice/Documents +// ``` +@(require_results) +user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return _user_documents_dir(allocator) +} + +// ``` +// Windows: C:\Users\Alice\Downloads +// macOS: /Users/Alice/Downloads +// Linux: /home/alice/Downloads +// ``` +@(require_results) +user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return _user_downloads_dir(allocator) } +// ``` +// Windows: C:\Users\Alice\Pictures +// macOS: /Users/Alice/Pictures +// Linux: /home/alice/Pictures +// ``` +@(require_results) +user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return _user_pictures_dir(allocator) +} + +// ``` +// Windows: C:\Users\Alice\Public +// macOS: /Users/Alice/Public +// Linux: /home/alice/Public +// ``` +@(require_results) +user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return _user_public_dir(allocator) +} + +// ``` +// Windows: C:\Users\Alice\Videos +// macOS: /Users/Alice/Movies +// Linux: /home/alice/Videos +// ``` +@(require_results) +user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return _user_videos_dir(allocator) +}
\ No newline at end of file diff --git a/core/os/os2/user_posix.odin b/core/os/os2/user_posix.odin new file mode 100644 index 000000000..691745b7a --- /dev/null +++ b/core/os/os2/user_posix.odin @@ -0,0 +1,175 @@ +#+build !windows +package os2 + +import "base:runtime" +import "core:encoding/ini" +import "core:strings" + +_user_cache_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Library/Caches", allocator) + case: // Unix + return _xdg_lookup("XDG_CACHE_HOME", "/.cache", allocator) + } +} + +_user_config_dir :: proc(allocator: runtime.Allocator, _roaming: bool) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Library/Application Support", allocator) + case: // Unix + return _xdg_lookup("XDG_CONFIG_HOME", "/.config", allocator) + } +} + +_user_state_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Library/Application Support", allocator) + case: // Unix + return _xdg_lookup("XDG_STATE_HOME", "/.local/state", allocator) + } +} + +_user_log_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Library/Logs", allocator) + case: // Unix + return _xdg_lookup("XDG_STATE_HOME", "/.local/state", allocator) + } +} + +_user_data_dir :: proc(allocator: runtime.Allocator, _roaming: bool) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Library/Application Support", allocator) + case: // Unix + return _xdg_lookup("XDG_DATA_HOME", "/.local/share", allocator) + } +} + +_user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Music", allocator) + case: // Unix + return _xdg_lookup("XDG_MUSIC_DIR", "/Music", allocator) + } +} + +_user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Desktop", allocator) + case: // Unix + return _xdg_lookup("XDG_DESKTOP_DIR", "/Desktop", allocator) + } +} + +_user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Documents", allocator) + case: // Unix + return _xdg_lookup("XDG_DOCUMENTS_DIR", "/Documents", allocator) + } +} + +_user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Downloads", allocator) + case: // Unix + return _xdg_lookup("XDG_DOWNLOAD_DIR", "/Downloads", allocator) + } +} + +_user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Pictures", allocator) + case: // Unix + return _xdg_lookup("XDG_PICTURES_DIR", "/Pictures", allocator) + } +} + +_user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Public", allocator) + case: // Unix + return _xdg_lookup("XDG_PUBLICSHARE_DIR", "/Public", allocator) + } +} + +_user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + #partial switch ODIN_OS { + case .Darwin: + return _xdg_lookup("", "/Movies", allocator) + case: // Unix + return _xdg_lookup("XDG_VIDEOS_DIR", "/Videos", allocator) + } +} + +_user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + if v := get_env("HOME", allocator); v != "" { + return v, nil + } + err = .No_HOME_Variable + return +} + +_xdg_lookup :: proc(xdg_key: string, fallback_suffix: string, allocator: runtime.Allocator) -> (dir: string, err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + if xdg_key == "" { // Darwin doesn't have XDG paths. + dir = get_env("HOME", temp_allocator) + if dir == "" { + err = .No_HOME_Variable + return + } + return concatenate({dir, fallback_suffix}, allocator) + } else { + if strings.ends_with(xdg_key, "_DIR") { + dir = _xdg_user_dirs_lookup(xdg_key, allocator) or_return + } else { + dir = get_env(xdg_key, allocator) + } + + if dir == "" { + dir = get_env("HOME", temp_allocator) + if dir == "" { + err = .No_HOME_Variable + return + } + dir = concatenate({dir, fallback_suffix}, allocator) or_return + } + return + } +} + +// If `<config-dir>/user-dirs.dirs` doesn't exist, or `xdg_key` can't be found there: returns `""` +_xdg_user_dirs_lookup :: proc(xdg_key: string, allocator: runtime.Allocator) -> (dir: string, err: Error) { + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + config_dir := user_config_dir(temp_allocator) or_return + user_dirs_path := concatenate({config_dir, "/user-dirs.dirs"}, temp_allocator) or_return + content := read_entire_file(user_dirs_path, temp_allocator) or_return + + it := ini.Iterator{ + section = "", + _src = string(content), + options = ini.Options{ + comment = "#", + key_lower_case = false, + }, + } + + for k, v in ini.iterate(&it) { + if k == xdg_key { + return replace_environment_placeholders(v, allocator), nil + } + } + return +}
\ No newline at end of file diff --git a/core/os/os2/user_windows.odin b/core/os/os2/user_windows.odin new file mode 100644 index 000000000..75d0ba6ac --- /dev/null +++ b/core/os/os2/user_windows.odin @@ -0,0 +1,78 @@ +package os2 + +import "base:runtime" +@(require) import win32 "core:sys/windows" + +_local_appdata :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + guid := win32.FOLDERID_LocalAppData + return _get_known_folder_path(&guid, allocator) +} + +_local_appdata_or_roaming :: proc(allocator: runtime.Allocator, roaming: bool) -> (dir: string, err: Error) { + guid := win32.FOLDERID_LocalAppData + if roaming { + guid = win32.FOLDERID_RoamingAppData + } + return _get_known_folder_path(&guid, allocator) +} + +_user_config_dir :: _local_appdata_or_roaming +_user_data_dir :: _local_appdata_or_roaming + +_user_state_dir :: _local_appdata +_user_log_dir :: _local_appdata +_user_cache_dir :: _local_appdata + +_user_home_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + guid := win32.FOLDERID_Profile + return _get_known_folder_path(&guid, allocator) +} + +_user_music_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + guid := win32.FOLDERID_Music + return _get_known_folder_path(&guid, allocator) +} + +_user_desktop_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + guid := win32.FOLDERID_Desktop + return _get_known_folder_path(&guid, allocator) +} + +_user_documents_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + guid := win32.FOLDERID_Documents + return _get_known_folder_path(&guid, allocator) +} + +_user_downloads_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + guid := win32.FOLDERID_Downloads + return _get_known_folder_path(&guid, allocator) +} + +_user_pictures_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + guid := win32.FOLDERID_Pictures + return _get_known_folder_path(&guid, allocator) +} + +_user_public_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + guid := win32.FOLDERID_Public + return _get_known_folder_path(&guid, allocator) +} + +_user_videos_dir :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + guid := win32.FOLDERID_Videos + return _get_known_folder_path(&guid, allocator) +} + +_get_known_folder_path :: proc(rfid: win32.REFKNOWNFOLDERID, allocator: runtime.Allocator) -> (dir: string, err: Error) { + // https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath + // See also `known_folders.odin` in `core:sys/windows` for the GUIDs. + path_w: win32.LPWSTR + res := win32.SHGetKnownFolderPath(rfid, 0, nil, &path_w) + defer win32.CoTaskMemFree(path_w) + + if res != 0 { + return "", .Invalid_Path + } + + return win32_wstring_to_utf8(cstring16(path_w), allocator) +}
\ No newline at end of file diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index bbffc46d7..77b5825dd 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -1,8 +1,8 @@ package os foreign import dl "system:dl" -foreign import libc "system:System.framework" -foreign import pthread "system:System.framework" +foreign import libc "system:System" +foreign import pthread "system:System" import "base:runtime" import "core:strings" @@ -1055,9 +1055,10 @@ flush :: proc(fd: Handle) -> Error { } @(require_results) -lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator) + // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. cstr := _unix_getenv(path_str) if cstr == nil { return "", false @@ -1066,11 +1067,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin } @(require_results) -get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + if len(key) + 1 > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, key) + } + + if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" { + return "", .Env_Var_Not_Found + } else { + if len(value) > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, value) + return string(buf[:len(value)]), nil + } + } +} +lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} + +@(require_results) +get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } +@(require_results) +get_env_buf :: proc(buf: []u8, key: string) -> (value: string) { + value, _ = lookup_env(buf, key) + return +} +get_env :: proc{get_env_alloc, get_env_buf} + set_env :: proc(key, value: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() key_cstring := strings.clone_to_cstring(key, context.temp_allocator) @@ -1196,8 +1225,9 @@ _processor_core_count :: proc() -> int { return 1 } -@(require_results) -_alloc_command_line_arguments :: proc() -> []string { +@(private, require_results) +_alloc_command_line_arguments :: proc "contextless" () -> []string { + context = runtime.default_context() res := make([]string, len(runtime.args__)) for _, i in res { res[i] = string(runtime.args__[i]) @@ -1205,6 +1235,12 @@ _alloc_command_line_arguments :: proc() -> []string { return res } +@(private, fini) +_delete_command_line_arguments :: proc "contextless" () { + context = runtime.default_context() + delete(args) +} + socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) { result := _unix_socket(c.int(domain), c.int(type), c.int(protocol)) if result < 0 { diff --git a/core/os/os_freebsd.odin b/core/os/os_freebsd.odin index 87a56b057..0542e10dc 100644 --- a/core/os/os_freebsd.odin +++ b/core/os/os_freebsd.odin @@ -662,7 +662,7 @@ last_write_time_by_name :: proc(name: string) -> (File_Time, Error) { return File_Time(modified), nil } -@(private, require_results) +@(private, require_results, no_sanitize_memory) _stat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -674,7 +674,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) { return s, nil } -@(private, require_results) +@(private, require_results, no_sanitize_memory) _lstat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -688,7 +688,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) { return s, nil } -@(private, require_results) +@(private, require_results, no_sanitize_memory) _fstat :: proc(fd: Handle) -> (OS_Stat, Error) { s: OS_Stat = --- result := _unix_fstat(fd, &s) @@ -827,10 +827,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) { } @(require_results) -lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - path_str := strings.clone_to_cstring(key, context.temp_allocator) + // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. cstr := _unix_getenv(path_str) if cstr == nil { return "", false @@ -839,12 +839,40 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin } @(require_results) -get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + if len(key) + 1 > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, key) + } + + if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" { + return "", .Env_Var_Not_Found + } else { + if len(value) > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, value) + return string(buf[:len(value)]), nil + } + } +} +lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} + +@(require_results) +get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } @(require_results) +get_env_buf :: proc(buf: []u8, key: string) -> (value: string) { + value, _ = lookup_env(buf, key) + return +} +get_env :: proc{get_env_alloc, get_env_buf} + +@(require_results) get_current_directory :: proc(allocator := context.allocator) -> string { context.allocator = allocator // NOTE(tetra): I would use PATH_MAX here, but I was not able to find @@ -936,11 +964,18 @@ _processor_core_count :: proc() -> int { } -@(require_results) -_alloc_command_line_arguments :: proc() -> []string { +@(private, require_results) +_alloc_command_line_arguments :: proc "contextless" () -> []string { + context = runtime.default_context() res := make([]string, len(runtime.args__)) - for arg, i in runtime.args__ { - res[i] = string(arg) + for _, i in res { + res[i] = string(runtime.args__[i]) } return res } + +@(private, fini) +_delete_command_line_arguments :: proc "contextless" () { + context = runtime.default_context() + delete(args) +} diff --git a/core/os/os_haiku.odin b/core/os/os_haiku.odin index 4a57afb87..e7c71338b 100644 --- a/core/os/os_haiku.odin +++ b/core/os/os_haiku.odin @@ -316,8 +316,9 @@ file_size :: proc(fd: Handle) -> (i64, Error) { // "Argv" arguments converted to Odin strings args := _alloc_command_line_arguments() -@(require_results) -_alloc_command_line_arguments :: proc() -> []string { +@(private, require_results) +_alloc_command_line_arguments :: proc "contextless" () -> []string { + context = runtime.default_context() res := make([]string, len(runtime.args__)) for arg, i in runtime.args__ { res[i] = string(arg) @@ -325,7 +326,13 @@ _alloc_command_line_arguments :: proc() -> []string { return res } -@(private, require_results) +@(private, fini) +_delete_command_line_arguments :: proc "contextless" () { + context = runtime.default_context() + delete(args) +} + +@(private, require_results, no_sanitize_memory) _stat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -339,7 +346,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) { return s, nil } -@(private, require_results) +@(private, require_results, no_sanitize_memory) _lstat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -353,7 +360,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) { return s, nil } -@(private, require_results) +@(private, require_results, no_sanitize_memory) _fstat :: proc(fd: Handle) -> (OS_Stat, Error) { // deliberately uninitialized s: OS_Stat = --- @@ -463,9 +470,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) { } @(require_results) -lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator) + // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. cstr := _unix_getenv(path_str) if cstr == nil { return "", false @@ -474,11 +482,40 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin } @(require_results) -get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + if len(key) + 1 > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, key) + } + + if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" { + return "", .Env_Var_Not_Found + } else { + if len(value) > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, value) + return string(buf[:len(value)]), nil + } + } +} +lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} + +@(require_results) +get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } +@(require_results) +get_env_buf :: proc(buf: []u8, key: string) -> (value: string) { + value, _ = lookup_env(buf, key) + return +} +get_env :: proc{get_env_alloc, get_env_buf} + + @(private, require_results) _processor_core_count :: proc() -> int { info: haiku.system_info diff --git a/core/os/os_js.odin b/core/os/os_js.odin index 348554728..1870218d3 100644 --- a/core/os/os_js.odin +++ b/core/os/os_js.odin @@ -249,3 +249,27 @@ exit :: proc "contextless" (code: int) -> ! { current_thread_id :: proc "contextless" () -> int { return 0 } + +@(require_results) +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + return "", false +} + +@(require_results) +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + return "", .Env_Var_Not_Found +} +lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} + +@(require_results) +get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) { + value, _ = lookup_env(key, allocator) + return +} + +@(require_results) +get_env_buf :: proc(buf: []u8, key: string) -> (value: string) { + value, _ = lookup_env(buf, key) + return +} +get_env :: proc{get_env_alloc, get_env_buf}
\ No newline at end of file diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index 2281e6a82..f0f9b401c 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -674,7 +674,7 @@ seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Error) { return i64(res), nil } -@(require_results) +@(require_results, no_sanitize_memory) file_size :: proc(fd: Handle) -> (i64, Error) { // deliberately uninitialized; the syscall fills this buffer for us s: OS_Stat = --- @@ -794,7 +794,7 @@ last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) { return File_Time(modified), nil } -@(private, require_results) +@(private, require_results, no_sanitize_memory) _stat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -808,7 +808,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) { return s, nil } -@(private, require_results) +@(private, require_results, no_sanitize_memory) _lstat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -822,7 +822,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) { return s, nil } -@(private, require_results) +@(private, require_results, no_sanitize_memory) _fstat :: proc(fd: Handle) -> (OS_Stat, Error) { // deliberately uninitialized; the syscall fills this buffer for us s: OS_Stat = --- @@ -908,7 +908,7 @@ _dup :: proc(fd: Handle) -> (Handle, Error) { @(require_results) absolute_path_from_handle :: proc(fd: Handle) -> (string, Error) { buf : [256]byte - fd_str := strconv.itoa( buf[:], cast(int)fd ) + fd_str := strconv.write_int( buf[:], cast(i64)fd, 10 ) procfs_path := strings.concatenate( []string{ "/proc/self/fd/", fd_str } ) defer delete(procfs_path) @@ -946,7 +946,7 @@ access :: proc(path: string, mask: int) -> (bool, Error) { } @(require_results) -lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator) // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. @@ -958,11 +958,39 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin } @(require_results) -get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + if len(key) + 1 > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, key) + } + + if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" { + return "", .Env_Var_Not_Found + } else { + if len(value) > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, value) + return string(buf[:len(value)]), nil + } + } +} +lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} + +@(require_results) +get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } +@(require_results) +get_env_buf :: proc(buf: []u8, key: string) -> (value: string) { + value, _ = lookup_env(buf, key) + return +} +get_env :: proc{get_env_alloc, get_env_buf} + set_env :: proc(key, value: string) -> Error { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() key_cstring := strings.clone_to_cstring(key, context.temp_allocator) @@ -1069,15 +1097,22 @@ _processor_core_count :: proc() -> int { return int(_unix_get_nprocs()) } -@(require_results) -_alloc_command_line_arguments :: proc() -> []string { +@(private, require_results) +_alloc_command_line_arguments :: proc "contextless" () -> []string { + context = runtime.default_context() res := make([]string, len(runtime.args__)) - for arg, i in runtime.args__ { - res[i] = string(arg) + for _, i in res { + res[i] = string(runtime.args__[i]) } return res } +@(private, fini) +_delete_command_line_arguments :: proc "contextless" () { + context = runtime.default_context() + delete(args) +} + @(require_results) socket :: proc(domain: int, type: int, protocol: int) -> (Socket, Error) { result := unix.sys_socket(domain, type, protocol) diff --git a/core/os/os_netbsd.odin b/core/os/os_netbsd.odin index e3ba760a4..30511012f 100644 --- a/core/os/os_netbsd.odin +++ b/core/os/os_netbsd.odin @@ -724,7 +724,7 @@ last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) { return File_Time(modified), nil } -@(private, require_results) +@(private, require_results, no_sanitize_memory) _stat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -736,7 +736,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) { return s, nil } -@(private, require_results) +@(private, require_results, no_sanitize_memory) _lstat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -750,7 +750,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) { return s, nil } -@(private, require_results) +@(private, require_results, no_sanitize_memory) _fstat :: proc(fd: Handle) -> (OS_Stat, Error) { s: OS_Stat = --- result := _unix_fstat(fd, &s) @@ -874,10 +874,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) { } @(require_results) -lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) - path_str := strings.clone_to_cstring(key, context.temp_allocator) + // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. cstr := _unix_getenv(path_str) if cstr == nil { return "", false @@ -886,12 +886,40 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin } @(require_results) -get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + if len(key) + 1 > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, key) + } + + if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" { + return "", .Env_Var_Not_Found + } else { + if len(value) > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, value) + return string(buf[:len(value)]), nil + } + } +} +lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} + +@(require_results) +get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } @(require_results) +get_env_buf :: proc(buf: []u8, key: string) -> (value: string) { + value, _ = lookup_env(buf, key) + return +} +get_env :: proc{get_env_alloc, get_env_buf} + +@(require_results) get_current_directory :: proc(allocator := context.allocator) -> string { context.allocator = allocator // NOTE(tetra): I would use PATH_MAX here, but I was not able to find @@ -986,11 +1014,18 @@ _processor_core_count :: proc() -> int { return 1 } -@(require_results) -_alloc_command_line_arguments :: proc() -> []string { +@(private, require_results) +_alloc_command_line_arguments :: proc "contextless" () -> []string { + context = runtime.default_context() res := make([]string, len(runtime.args__)) - for arg, i in runtime.args__ { - res[i] = string(arg) + for _, i in res { + res[i] = string(runtime.args__[i]) } return res } + +@(private, fini) +_delete_command_line_arguments :: proc "contextless" () { + context = runtime.default_context() + delete(args) +} diff --git a/core/os/os_openbsd.odin b/core/os/os_openbsd.odin index 3c377968c..50ee37dff 100644 --- a/core/os/os_openbsd.odin +++ b/core/os/os_openbsd.odin @@ -343,7 +343,7 @@ AT_REMOVEDIR :: 0x08 @(default_calling_convention="c") foreign libc { - @(link_name="__error") __error :: proc() -> ^c.int --- + @(link_name="__errno") __error :: proc() -> ^c.int --- @(link_name="fork") _unix_fork :: proc() -> pid_t --- @(link_name="getthrid") _unix_getthrid :: proc() -> int --- @@ -639,7 +639,7 @@ last_write_time_by_name :: proc(name: string) -> (time: File_Time, err: Error) { return File_Time(modified), nil } -@(private, require_results) +@(private, require_results, no_sanitize_memory) _stat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -653,7 +653,7 @@ _stat :: proc(path: string) -> (OS_Stat, Error) { return s, nil } -@(private, require_results) +@(private, require_results, no_sanitize_memory) _lstat :: proc(path: string) -> (OS_Stat, Error) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() cstr := strings.clone_to_cstring(path, context.temp_allocator) @@ -667,7 +667,7 @@ _lstat :: proc(path: string) -> (OS_Stat, Error) { return s, nil } -@(private, require_results) +@(private, require_results, no_sanitize_memory) _fstat :: proc(fd: Handle) -> (OS_Stat, Error) { // deliberately uninitialized s: OS_Stat = --- @@ -787,9 +787,10 @@ access :: proc(path: string, mask: int) -> (bool, Error) { } @(require_results) -lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) path_str := strings.clone_to_cstring(key, context.temp_allocator) + // NOTE(tetra): Lifetime of 'cstr' is unclear, but _unix_free(cstr) segfaults. cstr := _unix_getenv(path_str) if cstr == nil { return "", false @@ -798,12 +799,40 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin } @(require_results) -get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + if len(key) + 1 > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, key) + } + + if value = string(_unix_getenv(cstring(raw_data(buf)))); value == "" { + return "", .Env_Var_Not_Found + } else { + if len(value) > len(buf) { + return "", .Buffer_Full + } else { + copy(buf, value) + return string(buf[:len(value)]), nil + } + } +} +lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} + +@(require_results) +get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) { value, _ = lookup_env(key, allocator) return } @(require_results) +get_env_buf :: proc(buf: []u8, key: string) -> (value: string) { + value, _ = lookup_env(buf, key) + return +} +get_env :: proc{get_env_alloc, get_env_buf} + +@(require_results) get_current_directory :: proc(allocator := context.allocator) -> string { context.allocator = allocator buf := make([dynamic]u8, MAX_PATH) @@ -885,11 +914,18 @@ _processor_core_count :: proc() -> int { return int(_sysconf(_SC_NPROCESSORS_ONLN)) } -@(require_results) -_alloc_command_line_arguments :: proc() -> []string { +@(private, require_results) +_alloc_command_line_arguments :: proc "contextless" () -> []string { + context = runtime.default_context() res := make([]string, len(runtime.args__)) - for arg, i in runtime.args__ { - res[i] = string(arg) + for _, i in res { + res[i] = string(runtime.args__[i]) } return res } + +@(private, fini) +_delete_command_line_arguments :: proc "contextless" () { + context = runtime.default_context() + delete(args) +} diff --git a/core/os/os_wasi.odin b/core/os/os_wasi.odin index 28f470357..fe0a1fb3e 100644 --- a/core/os/os_wasi.odin +++ b/core/os/os_wasi.odin @@ -27,13 +27,20 @@ stderr: Handle = 2 args := _alloc_command_line_arguments() -@(require_results) -_alloc_command_line_arguments :: proc() -> (args: []string) { - args = make([]string, len(runtime.args__)) - for &arg, i in args { +@(private, require_results) +_alloc_command_line_arguments :: proc "contextless" () -> []string { + context = runtime.default_context() + cmd_args := make([]string, len(runtime.args__)) + for &arg, i in cmd_args { arg = string(runtime.args__[i]) } - return + return cmd_args +} + +@(private, fini) +_delete_command_line_arguments :: proc "contextless" () { + context = runtime.default_context() + delete(args) } // WASI works with "preopened" directories, the environment retrieves directories @@ -52,9 +59,8 @@ Preopen :: struct { preopens: []Preopen @(init, private) -init_preopens :: proc() { - - strip_prefixes :: proc(path: string) -> string { +init_preopens :: proc "contextless" () { + strip_prefixes :: proc "contextless"(path: string) -> string { path := path loop: for len(path) > 0 { switch { @@ -71,6 +77,8 @@ init_preopens :: proc() { return path } + context = runtime.default_context() + dyn_preopens: [dynamic]Preopen loop: for fd := wasi.fd_t(3); ; fd += 1 { desc, err := wasi.fd_prestat_get(fd) @@ -239,3 +247,27 @@ exit :: proc "contextless" (code: int) -> ! { runtime._cleanup_runtime_contextless() wasi.proc_exit(wasi.exitcode_t(code)) } + +@(require_results) +lookup_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + return "", false +} + +@(require_results) +lookup_env_buffer :: proc(buf: []u8, key: string) -> (value: string, err: Error) { + return "", .Env_Var_Not_Found +} +lookup_env :: proc{lookup_env_alloc, lookup_env_buffer} + +@(require_results) +get_env_alloc :: proc(key: string, allocator := context.allocator) -> (value: string) { + value, _ = lookup_env(key, allocator) + return +} + +@(require_results) +get_env_buf :: proc(buf: []u8, key: string) -> (value: string) { + value, _ = lookup_env(buf, key) + return +} +get_env :: proc{get_env_alloc, get_env_buf}
\ No newline at end of file diff --git a/core/os/os_windows.odin b/core/os/os_windows.odin index 0c532bf14..03c194596 100644 --- a/core/os/os_windows.odin +++ b/core/os/os_windows.odin @@ -193,8 +193,9 @@ current_thread_id :: proc "contextless" () -> int { -@(require_results) -_alloc_command_line_arguments :: proc() -> []string { +@(private, require_results) +_alloc_command_line_arguments :: proc "contextless" () -> []string { + context = runtime.default_context() arg_count: i32 arg_list_ptr := win32.CommandLineToArgvW(win32.GetCommandLineW(), &arg_count) arg_list := make([]string, int(arg_count)) @@ -215,6 +216,15 @@ _alloc_command_line_arguments :: proc() -> []string { return arg_list } +@(private, fini) +_delete_command_line_arguments :: proc "contextless" () { + context = runtime.default_context() + for s in args { + delete(s) + } + delete(args) +} + /* Windows 11 (preview) has the same major and minor version numbers as Windows 10: 10 and 0 respectively. diff --git a/core/os/stat_windows.odin b/core/os/stat_windows.odin index ca4f87668..662c9f9e6 100644 --- a/core/os/stat_windows.odin +++ b/core/os/stat_windows.odin @@ -17,7 +17,7 @@ full_path_from_name :: proc(name: string, allocator := context.allocator) -> (pa buf := make([dynamic]u16, 100) defer delete(buf) for { - n := win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil) + n := win32.GetFullPathNameW(cstring16(raw_data(p)), u32(len(buf)), cstring16(raw_data(buf)), nil) if n == 0 { return "", get_last_error() } @@ -154,7 +154,7 @@ cleanpath_from_handle_u16 :: proc(fd: Handle, allocator: runtime.Allocator) -> ( return nil, get_last_error() } buf := make([]u16, max(n, win32.DWORD(260))+1, allocator) - buf_len := win32.GetFinalPathNameByHandleW(h, raw_data(buf), n, 0) + buf_len := win32.GetFinalPathNameByHandleW(h, cstring16(raw_data(buf)), n, 0) return buf[:buf_len], nil } @(private, require_results) diff --git a/core/path/filepath/match.odin b/core/path/filepath/match.odin index 1f0ac9287..3eaa7c6fe 100644 --- a/core/path/filepath/match.odin +++ b/core/path/filepath/match.odin @@ -1,4 +1,5 @@ #+build !wasi +#+build !js package filepath import "core:os" diff --git a/core/path/filepath/path_js.odin b/core/path/filepath/path_js.odin new file mode 100644 index 000000000..3b5ac04f5 --- /dev/null +++ b/core/path/filepath/path_js.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 +}
\ No newline at end of file diff --git a/core/path/filepath/path_windows.odin b/core/path/filepath/path_windows.odin index 0dcb28cf8..b3f4eee9e 100644 --- a/core/path/filepath/path_windows.odin +++ b/core/path/filepath/path_windows.odin @@ -32,7 +32,6 @@ is_UNC :: proc(path: string) -> bool { return volume_name_len(path) > 2 } - is_abs :: proc(path: string) -> bool { if is_reserved_name(path) { return true @@ -50,7 +49,6 @@ is_abs :: proc(path: string) -> bool { return is_slash(path[0]) } - @(private) temp_full_path :: proc(name: string) -> (path: string, err: os.Error) { ta := context.temp_allocator @@ -61,13 +59,13 @@ temp_full_path :: proc(name: string) -> (path: string, err: os.Error) { } p := win32.utf8_to_utf16(name, ta) - n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil) + n := win32.GetFullPathNameW(cstring16(raw_data(p)), 0, nil, nil) if n == 0 { return "", os.get_last_error() } buf := make([]u16, n, ta) - n = win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil) + n = win32.GetFullPathNameW(cstring16(raw_data(p)), u32(len(buf)), cstring16(raw_data(buf)), nil) if n == 0 { delete(buf) return "", os.get_last_error() @@ -76,8 +74,6 @@ temp_full_path :: proc(name: string) -> (path: string, err: os.Error) { return win32.utf16_to_utf8(buf[:n], ta) } - - abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator) full_path, err := temp_full_path(path) @@ -88,17 +84,16 @@ abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { return p, true } - -join :: proc(elems: []string, allocator := context.allocator) -> string { +join :: proc(elems: []string, allocator := context.allocator) -> (string, runtime.Allocator_Error) #optional_allocator_error { for e, i in elems { if e != "" { return join_non_empty(elems[i:], allocator) } } - return "" + return "", nil } -join_non_empty :: proc(elems: []string, allocator := context.allocator) -> string { +join_non_empty :: proc(elems: []string, allocator := context.allocator) -> (joined: string, err: runtime.Allocator_Error) { context.allocator = allocator runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator) @@ -110,23 +105,25 @@ join_non_empty :: proc(elems: []string, allocator := context.allocator) -> strin break } } - s := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) - s = strings.concatenate({elems[0], s}, context.temp_allocator) + s := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) or_return + s = strings.concatenate({elems[0], s}, context.temp_allocator) or_return return clean(s) } - p := clean(strings.join(elems, SEPARATOR_STRING, context.temp_allocator)) + p := strings.join(elems, SEPARATOR_STRING, context.temp_allocator) or_return + p = clean(p) or_return if !is_UNC(p) { - return p + return p, nil } - - head := clean(elems[0], context.temp_allocator) + + head := clean(elems[0], context.temp_allocator) or_return if is_UNC(head) { - return p + return p, nil } delete(p) // It is not needed now - tail := clean(strings.join(elems[1:], SEPARATOR_STRING, context.temp_allocator), context.temp_allocator) + tail := strings.join(elems[1:], SEPARATOR_STRING, context.temp_allocator) or_return + tail = clean(tail, context.temp_allocator) or_return if head[len(head)-1] == SEPARATOR { return strings.concatenate({head, tail}) } diff --git a/core/path/filepath/walk.odin b/core/path/filepath/walk.odin index 53b10eed7..05d67daf0 100644 --- a/core/path/filepath/walk.odin +++ b/core/path/filepath/walk.odin @@ -1,4 +1,5 @@ #+build !wasi +#+build !js package filepath import "core:os" diff --git a/core/prof/spall/spall.odin b/core/prof/spall/spall.odin index 12f082b2c..dc53dc3dc 100644 --- a/core/prof/spall/spall.odin +++ b/core/prof/spall/spall.odin @@ -8,29 +8,36 @@ import "base:intrinsics" MANUAL_MAGIC :: u64le(0x0BADF00D) -Manual_Header :: struct #packed { +Manual_Stream_Header :: struct #packed { magic: u64le, version: u64le, timestamp_scale: f64le, reserved: u64le, } +Manual_Buffer_Header :: struct #packed { + size: u32le, + tid: u32le, + pid: u32le, + first_ts: u64le, +} + Manual_Event_Type :: enum u8 { - Invalid = 0, + Invalid = 0, + + Begin = 3, + End = 4, + Instant = 5, - Begin = 3, - End = 4, - Instant = 5, + Pad_Skip = 7, - Pad_Skip = 7, + Name_Process = 8, + Name_Thread = 9, } Begin_Event :: struct #packed { type: Manual_Event_Type, - category: u8, - pid: u32le, - tid: u32le, - ts: f64le, + ts: u64le, name_len: u8, args_len: u8, } @@ -38,9 +45,7 @@ BEGIN_EVENT_MAX :: size_of(Begin_Event) + 255 + 255 End_Event :: struct #packed { type: Manual_Event_Type, - pid: u32le, - tid: u32le, - ts: f64le, + ts: u64le, } Pad_Skip :: struct #packed { @@ -48,6 +53,12 @@ Pad_Skip :: struct #packed { size: u32le, } +Name_Event :: struct #packed { + type: Manual_Event_Type, + name_len: u8, +} +NAME_EVENT_MAX :: size_of(Name_Event) + 255 + // User Interface Context :: struct { @@ -61,6 +72,7 @@ Buffer :: struct { head: int, tid: u32, pid: u32, + first_ts: u64, } BUFFER_DEFAULT_SIZE :: 0x10_0000 @@ -76,8 +88,8 @@ context_create_with_scale :: proc(filename: string, precise_time: bool, timestam ctx.precise_time = precise_time ctx.timestamp_scale = timestamp_scale - temp := [size_of(Manual_Header)]u8{} - _build_header(temp[:], ctx.timestamp_scale) + temp := [size_of(Manual_Stream_Header)]u8{} + _build_stream_header(temp[:], ctx.timestamp_scale) os.write(ctx.fd, temp[:]) ok = true return @@ -85,12 +97,13 @@ context_create_with_scale :: proc(filename: string, precise_time: bool, timestam context_create_with_sleep :: proc(filename: string, sleep := 2 * time.Second) -> (ctx: Context, ok: bool) #optional_ok { freq, freq_ok := time.tsc_frequency(sleep) - timestamp_scale: f64 = ((1 / f64(freq)) * 1_000_000) if freq_ok else 1 + timestamp_scale: f64 = ((1 / f64(freq)) * 1_000_000_000) if freq_ok else 1 return context_create_with_scale(filename, freq_ok, timestamp_scale) } context_create :: proc{context_create_with_scale, context_create_with_sleep} +@(no_instrumentation) context_destroy :: proc(ctx: ^Context) { if ctx == nil { return @@ -102,25 +115,39 @@ context_destroy :: proc(ctx: ^Context) { buffer_create :: proc(data: []byte, tid: u32 = 0, pid: u32 = 0) -> (buffer: Buffer, ok: bool) #optional_ok { assert(len(data) >= 1024) - buffer.data = data - buffer.tid = tid - buffer.pid = pid - buffer.head = 0 + buffer.data = data + buffer.tid = tid + buffer.pid = pid + buffer.first_ts = 0 + buffer.head = size_of(Manual_Buffer_Header) ok = true return } @(no_instrumentation) buffer_flush :: proc "contextless" (ctx: ^Context, buffer: ^Buffer) #no_bounds_check /* bounds check would segfault instrumentation */ { + if len(buffer.data) == 0 { + return + } + + buffer_size := buffer.head - size_of(Manual_Buffer_Header) + hdr := (^Manual_Buffer_Header)(raw_data(buffer.data)) + hdr.size = u32le(buffer_size) + hdr.pid = u32le(buffer.pid) + hdr.tid = u32le(buffer.tid) + hdr.first_ts = u64le(buffer.first_ts) + start := _trace_now(ctx) write(ctx.fd, buffer.data[:buffer.head]) - buffer.head = 0 + buffer.head = size_of(Manual_Buffer_Header) end := _trace_now(ctx) - buffer.head += _build_begin(buffer.data[buffer.head:], "Spall Trace Buffer Flush", "", start, buffer.tid, buffer.pid) - buffer.head += _build_end(buffer.data[buffer.head:], end, buffer.tid, buffer.pid) + buffer.head += _build_begin(buffer.data[buffer.head:], "Spall Trace Buffer Flush", "", start) + buffer.head += _build_end(buffer.data[buffer.head:], end) + buffer.first_ts = end } +@(no_instrumentation) buffer_destroy :: proc(ctx: ^Context, buffer: ^Buffer) { buffer_flush(ctx, buffer) @@ -130,35 +157,37 @@ buffer_destroy :: proc(ctx: ^Context, buffer: ^Buffer) { @(deferred_in=_scoped_buffer_end) +@(no_instrumentation) SCOPED_EVENT :: proc(ctx: ^Context, buffer: ^Buffer, name: string, args: string = "", location := #caller_location) -> bool { _buffer_begin(ctx, buffer, name, args, location) return true } @(private) +@(no_instrumentation) _scoped_buffer_end :: proc(ctx: ^Context, buffer: ^Buffer, _, _: string, _ := #caller_location) { _buffer_end(ctx, buffer) } @(no_instrumentation) -_trace_now :: proc "contextless" (ctx: ^Context) -> f64 { +_trace_now :: proc "contextless" (ctx: ^Context) -> u64 { if !ctx.precise_time { - return f64(tick_now()) / 1_000 + return u64(tick_now()) } - return f64(intrinsics.read_cycle_counter()) + return u64(intrinsics.read_cycle_counter()) } @(no_instrumentation) -_build_header :: proc "contextless" (buffer: []u8, timestamp_scale: f64) -> (header_size: int, ok: bool) #optional_ok { - header_size = size_of(Manual_Header) +_build_stream_header :: proc "contextless" (buffer: []u8, timestamp_scale: f64) -> (header_size: int, ok: bool) #optional_ok { + header_size = size_of(Manual_Stream_Header) if header_size > len(buffer) { return 0, false } - hdr := (^Manual_Header)(raw_data(buffer)) + hdr := (^Manual_Stream_Header)(raw_data(buffer)) hdr.magic = MANUAL_MAGIC - hdr.version = 1 + hdr.version = 3 hdr.timestamp_scale = f64le(timestamp_scale) hdr.reserved = 0 ok = true @@ -166,7 +195,7 @@ _build_header :: proc "contextless" (buffer: []u8, timestamp_scale: f64) -> (hea } @(no_instrumentation) -_build_begin :: #force_inline proc "contextless" (buffer: []u8, name: string, args: string, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok #no_bounds_check /* bounds check would segfault instrumentation */ { +_build_begin :: #force_inline proc "contextless" (buffer: []u8, name: string, args: string, ts: u64) -> (event_size: int, ok: bool) #optional_ok #no_bounds_check /* bounds check would segfault instrumentation */ { ev := (^Begin_Event)(raw_data(buffer)) name_len := min(len(name), 255) args_len := min(len(args), 255) @@ -177,9 +206,7 @@ _build_begin :: #force_inline proc "contextless" (buffer: []u8, name: string, ar } ev.type = .Begin - ev.pid = u32le(pid) - ev.tid = u32le(tid) - ev.ts = f64le(ts) + ev.ts = u64le(ts) ev.name_len = u8(name_len) ev.args_len = u8(args_len) intrinsics.mem_copy_non_overlapping(raw_data(buffer[size_of(Begin_Event):]), raw_data(name), name_len) @@ -190,7 +217,7 @@ _build_begin :: #force_inline proc "contextless" (buffer: []u8, name: string, ar } @(no_instrumentation) -_build_end :: proc "contextless" (buffer: []u8, ts: f64, tid: u32, pid: u32) -> (event_size: int, ok: bool) #optional_ok { +_build_end :: proc "contextless" (buffer: []u8, ts: u64) -> (event_size: int, ok: bool) #optional_ok { ev := (^End_Event)(raw_data(buffer)) event_size = size_of(End_Event) if event_size > len(buffer) { @@ -198,9 +225,28 @@ _build_end :: proc "contextless" (buffer: []u8, ts: f64, tid: u32, pid: u32) -> } ev.type = .End - ev.pid = u32le(pid) - ev.tid = u32le(tid) - ev.ts = f64le(ts) + ev.ts = u64le(ts) + ok = true + + return +} + +@(no_instrumentation) +_build_name_event :: #force_inline proc "contextless" (buffer: []u8, name: string, type: Manual_Event_Type) -> (event_size: int, ok: bool) #optional_ok #no_bounds_check /* bounds check would segfault instrumentation */ { + ev := (^Name_Event)(raw_data(buffer)) + name_len := min(len(name), 255) + + event_size = size_of(Name_Event) + name_len + if event_size > len(buffer) { + return 0, false + } + if type != .Name_Process && type != .Name_Thread { + return 0, false + } + + ev.type = type + ev.name_len = u8(name_len) + intrinsics.mem_copy_non_overlapping(raw_data(buffer[size_of(Name_Event):]), raw_data(name), name_len) ok = true return @@ -212,7 +258,7 @@ _buffer_begin :: proc "contextless" (ctx: ^Context, buffer: ^Buffer, name: strin buffer_flush(ctx, buffer) } name := location.procedure if name == "" else name - buffer.head += _build_begin(buffer.data[buffer.head:], name, args, _trace_now(ctx), buffer.tid, buffer.pid) + buffer.head += _build_begin(buffer.data[buffer.head:], name, args, _trace_now(ctx)) } @(no_instrumentation) @@ -223,7 +269,23 @@ _buffer_end :: proc "contextless" (ctx: ^Context, buffer: ^Buffer) #no_bounds_ch buffer_flush(ctx, buffer) } - buffer.head += _build_end(buffer.data[buffer.head:], ts, buffer.tid, buffer.pid) + buffer.head += _build_end(buffer.data[buffer.head:], ts) +} + +@(no_instrumentation) +_buffer_name_thread :: proc "contextless" (ctx: ^Context, buffer: ^Buffer, name: string, location := #caller_location) #no_bounds_check /* bounds check would segfault instrumentation */ { + if buffer.head + NAME_EVENT_MAX > len(buffer.data) { + buffer_flush(ctx, buffer) + } + buffer.head += _build_name_event(buffer.data[buffer.head:], name, .Name_Thread) +} + +@(no_instrumentation) +_buffer_name_process :: proc "contextless" (ctx: ^Context, buffer: ^Buffer, name: string, location := #caller_location) #no_bounds_check /* bounds check would segfault instrumentation */ { + if buffer.head + NAME_EVENT_MAX > len(buffer.data) { + buffer_flush(ctx, buffer) + } + buffer.head += _build_name_event(buffer.data[buffer.head:], name, .Name_Process) } @(no_instrumentation) diff --git a/core/reflect/reflect.odin b/core/reflect/reflect.odin index 7f79acb77..b3315a0c3 100644 --- a/core/reflect/reflect.odin +++ b/core/reflect/reflect.odin @@ -24,7 +24,6 @@ Type_Info_Enumerated_Array :: runtime.Type_Info_Enumerated_Array Type_Info_Dynamic_Array :: runtime.Type_Info_Dynamic_Array Type_Info_Slice :: runtime.Type_Info_Slice Type_Info_Parameters :: runtime.Type_Info_Parameters -Type_Info_Tuple :: runtime.Type_Info_Parameters Type_Info_Struct :: runtime.Type_Info_Struct Type_Info_Union :: runtime.Type_Info_Union Type_Info_Enum :: runtime.Type_Info_Enum @@ -58,7 +57,7 @@ Type_Kind :: enum { Enumerated_Array, Dynamic_Array, Slice, - Tuple, + Parameters, Struct, Union, Enum, @@ -93,7 +92,7 @@ type_kind :: proc(T: typeid) -> Type_Kind { case Type_Info_Enumerated_Array: return .Enumerated_Array case Type_Info_Dynamic_Array: return .Dynamic_Array case Type_Info_Slice: return .Slice - case Type_Info_Parameters: return .Tuple + case Type_Info_Parameters: return .Parameters case Type_Info_Struct: return .Struct case Type_Info_Union: return .Union case Type_Info_Enum: return .Enum @@ -176,6 +175,7 @@ typeid_elem :: proc(id: typeid) -> typeid { case Type_Info_Enumerated_Array: return v.elem.id case Type_Info_Slice: return v.elem.id case Type_Info_Dynamic_Array: return v.elem.id + case Type_Info_Simd_Vector: return v.elem.id } return id } @@ -260,7 +260,11 @@ length :: proc(val: any) -> int { } else { return (^runtime.Raw_String)(val.data).len } + + case Type_Info_Simd_Vector: + return a.count } + return 0 } @@ -286,7 +290,11 @@ capacity :: proc(val: any) -> int { case Type_Info_Map: return runtime.map_cap((^runtime.Raw_Map)(val.data)^) + + case Type_Info_Simd_Vector: + return a.count } + return 0 } @@ -1431,6 +1439,11 @@ as_f64 :: proc(a: any) -> (value: f64, valid: bool) { case Type_Info_Complex: switch v in a { + case complex32: + if imag(v) == 0 { + value = f64(real(v)) + valid = true + } case complex64: if imag(v) == 0 { value = f64(real(v)) @@ -1445,6 +1458,11 @@ as_f64 :: proc(a: any) -> (value: f64, valid: bool) { case Type_Info_Quaternion: switch v in a { + case quaternion64: + if imag(v) == 0 && jmag(v) == 0 && kmag(v) == 0 { + value = f64(real(v)) + valid = true + } case quaternion128: if imag(v) == 0 && jmag(v) == 0 && kmag(v) == 0 { value = f64(real(v)) @@ -1638,13 +1656,40 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_ return equal(va, vb, including_indirect_array_recursion, recursion_level+1) case Type_Info_Map: return false + case Type_Info_Float: + x, _ := as_f64(a) + y, _ := as_f64(b) + return x == y + case Type_Info_Complex: + switch x in a { + case complex32: + #no_type_assert y := b.(complex32) + return x == y + case complex64: + #no_type_assert y := b.(complex64) + return x == y + case complex128: + #no_type_assert y := b.(complex128) + return x == y + } + return false + case Type_Info_Quaternion: + switch x in a { + case quaternion64: + #no_type_assert y := b.(quaternion64) + return x == y + case quaternion128: + #no_type_assert y := b.(quaternion128) + return x == y + case quaternion256: + #no_type_assert y := b.(quaternion256) + return x == y + } + return false case Type_Info_Boolean, Type_Info_Integer, Type_Info_Rune, - Type_Info_Float, - Type_Info_Complex, - Type_Info_Quaternion, Type_Info_Type_Id, Type_Info_Pointer, Type_Info_Multi_Pointer, diff --git a/core/reflect/types.odin b/core/reflect/types.odin index cb31a27e2..98b7b368f 100644 --- a/core/reflect/types.odin +++ b/core/reflect/types.odin @@ -348,12 +348,6 @@ is_parameters :: proc(info: ^Type_Info) -> bool { _, ok := type_info_base(info).variant.(Type_Info_Parameters) return ok } -@(require_results, deprecated="prefer is_parameters") -is_tuple :: proc(info: ^Type_Info) -> bool { - if info == nil { return false } - _, ok := type_info_base(info).variant.(Type_Info_Parameters) - return ok -} @(require_results) is_struct :: proc(info: ^Type_Info) -> bool { if info == nil { return false } @@ -517,9 +511,12 @@ write_type_writer :: #force_no_inline proc(w: io.Writer, ti: ^Type_Info, n_writt io.write_i64(w, i64(8*ti.size), 10, &n) or_return case Type_Info_String: if info.is_cstring { - io.write_string(w, "cstring", &n) or_return - } else { - io.write_string(w, "string", &n) or_return + io.write_byte(w, 'c', &n) or_return + } + io.write_string(w, "string", &n) or_return + switch info.encoding { + case .UTF_8: /**/ + case .UTF_16: io.write_string(w, "16", &n) or_return } case Type_Info_Boolean: switch ti.id { @@ -636,7 +633,7 @@ write_type_writer :: #force_no_inline proc(w: io.Writer, ti: ^Type_Info, n_writt io.write_string(w, "struct ", &n) or_return if .packed in info.flags { io.write_string(w, "#packed ", &n) or_return } if .raw_union in info.flags { io.write_string(w, "#raw_union ", &n) or_return } - if .no_copy in info.flags { io.write_string(w, "#no_copy ", &n) or_return } + // if .no_copy in info.flags { io.write_string(w, "#no_copy ", &n) or_return } if .align in info.flags { io.write_string(w, "#align(", &n) or_return io.write_i64(w, i64(ti.align), 10, &n) or_return diff --git a/core/simd/simd.odin b/core/simd/simd.odin index 0bce4e16a..303eceb97 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -21,20 +21,17 @@ package simd import "base:builtin" import "base:intrinsics" +import "base:runtime" /* Check if SIMD is software-emulated on a target platform. -This value is `false`, when the compile-time target has the hardware support for -at 128-bit (or wider) SIMD. If the compile-time target lacks the hardware support -for 128-bit SIMD, this value is `true`, and all SIMD operations will likely be +This value is `true`, when the compile-time target has the hardware support for +at least 128-bit (or wider) SIMD. If the compile-time target lacks the hardware support +for 128-bit SIMD, this value is `false`, and all SIMD operations will likely be emulated. */ -IS_EMULATED :: true when (ODIN_ARCH == .amd64 || ODIN_ARCH == .i386) && !intrinsics.has_target_feature("sse2") else - true when (ODIN_ARCH == .arm64 || ODIN_ARCH == .arm32) && !intrinsics.has_target_feature("neon") else - true when (ODIN_ARCH == .wasm64p32 || ODIN_ARCH == .wasm32) && !intrinsics.has_target_feature("simd128") else - true when (ODIN_ARCH == .riscv64) && !intrinsics.has_target_feature("v") else - false +HAS_HARDWARE_SIMD :: runtime.HAS_HARDWARE_SIMD /* Vector of 16 `u8` lanes (128 bits). @@ -1328,13 +1325,18 @@ Example: // to load valid positions of the `ptrs` array, and the array of defaults which // will have `127` in each position as the default value. - v1 := [4] f32 {1, 2, 3, 4}; - v2 := [4] f32 {9, 10,11,12}; - ptrs := #simd [4]rawptr { &v1[1], nil, &v2[1], nil } - mask := #simd [4]bool { true, false, true, false } - defaults := #simd [4]f32 { 0x7f, 0x7f, 0x7f, 0x7f } - res := simd.gather(ptrs, defaults, mask) - fmt.println(res) + import "core:fmt" + import "core:simd" + + simd_gather_example :: proc() { + v1 := [4] f32 {1, 2, 3, 4}; + v2 := [4] f32 {9, 10,11,12}; + ptrs := #simd [4]rawptr { &v1[1], nil, &v2[1], nil } + mask := #simd [4]bool { true, false, true, false } + defaults := #simd [4]f32 { 0x7f, 0x7f, 0x7f, 0x7f } + res := simd.gather(ptrs, defaults, mask) + fmt.println(res) + } Output: @@ -1396,14 +1398,19 @@ Example: // vectors. The addresses of store destinations are written to the first and the // third argument of the `ptr` vector, and the `mask` is set accordingly. - v1 := [4] f32 {1, 2, 3, 4}; - v2 := [4] f32 {5, 6, 7, 8}; - ptrs := #simd [4]rawptr { &v1[1], nil, &v2[1], nil } - mask := #simd [4]bool { true, false, true, false } - vals := #simd [4]f32 { 0x7f, 0x7f, 0x7f, 0x7f } - simd.scatter(ptrs, vals, mask) - fmt.println(v1) - fmt.println(v2) + import "core:fmt" + import "core:simd" + + simd_scatter_example :: proc() { + v1 := [4] f32 {1, 2, 3, 4}; + v2 := [4] f32 {5, 6, 7, 8}; + ptrs := #simd [4]rawptr { &v1[1], nil, &v2[1], nil } + mask := #simd [4]bool { true, false, true, false } + vals := #simd [4]f32 { 0x7f, 0x7f, 0x7f, 0x7f } + simd.scatter(ptrs, vals, mask) + fmt.println(v1) + fmt.println(v2) + } Output: @@ -1467,11 +1474,16 @@ Example: // third value (selected by the mask). The masked-off values are given the value // of 127 (`0x7f`). - src := [4] f32 {1, 2, 3, 4}; - mask := #simd [4]bool { true, false, true, false } - vals := #simd [4]f32 { 0x7f, 0x7f, 0x7f, 0x7f } - res := simd.masked_load(&src, vals, mask) - fmt.println(res) + import "core:fmt" + import "core:simd" + + simd_masked_load_example :: proc() { + src := [4] f32 {1, 2, 3, 4}; + mask := #simd [4]bool { true, false, true, false } + vals := #simd [4]f32 { 0x7f, 0x7f, 0x7f, 0x7f } + res := simd.masked_load(&src, vals, mask) + fmt.println(res) + } Output: @@ -1526,11 +1538,16 @@ Example: // Example below stores the value 127 into the first and the third slot of the // vector `v`. - v := [4] f32 {1, 2, 3, 4}; - mask := #simd [4]bool { true, false, true, false } - vals := #simd [4]f32 { 0x7f, 0x7f, 0x7f, 0x7f } - simd.masked_store(&v, vals, mask) - fmt.println(v) + import "core:fmt" + import "core:simd" + + simd_masked_store_example :: proc() { + v := [4] f32 {1, 2, 3, 4}; + mask := #simd [4]bool { true, false, true, false } + vals := #simd [4]f32 { 0x7f, 0x7f, 0x7f, 0x7f } + simd.masked_store(&v, vals, mask) + fmt.println(v) + } Output: @@ -1600,11 +1617,16 @@ Example: // the third lane of the result vector. All the other lanes of the result vector // will be initialized to the default value `127`. - v := [2] f64 {1, 2}; - mask := #simd [4]bool { true, false, true, false } - vals := #simd [4]f64 { 0x7f, 0x7f, 0x7f, 0x7f } - res := simd.masked_expand_load(&v, vals, mask) - fmt.println(res) + import "core:fmt" + import "core:simd" + + simd_masked_expand_load_example :: proc() { + v := [2] f64 {1, 2}; + mask := #simd [4]bool { true, false, true, false } + vals := #simd [4]f64 { 0x7f, 0x7f, 0x7f, 0x7f } + res := simd.masked_expand_load(&v, vals, mask) + fmt.println(res) + } Output: @@ -1661,11 +1683,16 @@ Example: // vector, the first and the third value. The items in the mask are set to `true` // in those lanes. - v := [2] f64 { }; - mask := #simd [4]bool { true, false, true, false } - vals := #simd [4]f64 { 1, 2, 3, 4 } - simd.masked_compress_store(&v, vals, mask) - fmt.println(v) + import "core:fmt" + import "core:simd" + + simd_masked_compress_store_example :: proc() { + v := [2] f64 { }; + mask := #simd [4]bool { true, false, true, false } + vals := #simd [4]f64 { 1, 2, 3, 4 } + simd.masked_compress_store(&v, vals, mask) + fmt.println(v) + } Output: @@ -1729,7 +1756,103 @@ Returns: replace :: intrinsics.simd_replace /* -Reduce a vector to a scalar by adding up all the lanes. +Reduce a vector to a scalar by adding up all the lanes in a bisecting fashion. + +This procedure returns a scalar that is the sum of all lanes, calculated by +bisecting the vector into two parts, where the first contains lanes [0, N/2) +and the second contains lanes [N/2, N), and adding the two halves element-wise +to produce N/2 values. This is repeated until only a single element remains. +This order may be faster to compute than the ordered sum for floats, as it can +often be better parallelized. + +The order of the sum may be important for accounting for precision errors in +floating-point computation, as floating-point addition is not associative, that +is `(a+b)+c` may not be equal to `a+(b+c)`. + +Inputs: +- `v`: The vector to reduce. + +Result: +- Sum of all lanes, as a scalar. + +**Operation**: + + for n > 1 { + n = n / 2 + for i in 0 ..< n { + a[i] += a[i+n] + } + } + res := a[0] + +Graphical representation of the operation for N=4: + + +-----------------------+ + | v0 | v1 | v2 | v3 | + +-----------------------+ + | | | | + [+]<-- | ---' | + | [+]<--------' + | | + `>[+]<' + | + v + +-----+ + result: | y0 | + +-----+ +*/ +reduce_add_bisect :: intrinsics.simd_reduce_add_bisect + +/* +Reduce a vector to a scalar by multiplying up all the lanes in a bisecting fashion. + +This procedure returns a scalar that is the product of all lanes, calculated by +bisecting the vector into two parts, where the first contains indices [0, N/2) +and the second contains indices [N/2, N), and multiplying the two halves +together element-wise to produce N/2 values. This is repeated until only a +single element remains. This order may be faster to compute than the ordered +product for floats, as it can often be better parallelized. + +The order of the product may be important for accounting for precision errors +in floating-point computation, as floating-point multiplication is not +associative, that is `(a*b)*c` may not be equal to `a*(b*c)`. + +Inputs: +- `v`: The vector to reduce. + +Result: +- Product of all lanes, as a scalar. + +**Operation**: + + for n > 1 { + n = n / 2 + for i in 0 ..< n { + a[i] *= a[i+n] + } + } + res := a[0] + +Graphical representation of the operation for N=4: + + +-----------------------+ + | v0 | v1 | v2 | v3 | + +-----------------------+ + | | | | + [x]<-- | ---' | + | [x]<--------' + | | + `>[x]<' + | + v + +-----+ + result: | y0 | + +-----+ +*/ +reduce_mul_bisect :: intrinsics.simd_reduce_mul_bisect + +/* +Reduce a vector to a scalar by adding up all the lanes in an ordered fashion. This procedure returns a scalar that is the ordered sum of all lanes. The ordered sum may be important for accounting for precision errors in @@ -1752,7 +1875,7 @@ Result: reduce_add_ordered :: intrinsics.simd_reduce_add_ordered /* -Reduce a vector to a scalar by multiplying all the lanes. +Reduce a vector to a scalar by multiplying all the lanes in an ordered fashion. This procedure returns a scalar that is the ordered product of all lanes. The ordered product may be important for accounting for precision errors in @@ -1775,6 +1898,100 @@ Result: reduce_mul_ordered :: intrinsics.simd_reduce_mul_ordered /* +Reduce a vector to a scalar by adding up all the lanes in a pairwise fashion. + +This procedure returns a scalar that is the sum of all lanes, calculated by +adding each even-indexed element with the following odd-indexed element to +produce N/2 values. This is repeated until only a single element remains. This +order is supported by hardware instructions for some types/architectures (e.g. +i16/i32/f32/f64 on x86 SSE, i8/i16/i32/f32 on ARM NEON). + +The order of the sum may be important for accounting for precision errors in +floating-point computation, as floating-point addition is not associative, that +is `(a+b)+c` may not be equal to `a+(b+c)`. + +Inputs: +- `v`: The vector to reduce. + +Result: +- Sum of all lanes, as a scalar. + +**Operation**: + + for n > 1 { + n = n / 2 + for i in 0 ..< n { + a[i] = a[2*i+0] + a[2*i+1] + } + } + res := a[0] + +Graphical representation of the operation for N=4: + + +-----------------------+ + v: | v0 | v1 | v2 | v3 | + +-----------------------+ + | | | | + `>[+]<' `>[+]<' + | | + `--->[+]<--' + | + v + +-----+ + result: | y0 | + +-----+ +*/ +reduce_add_pairs :: intrinsics.simd_reduce_add_pairs + +/* +Reduce a vector to a scalar by multiplying all the lanes in a pairwise fashion. + +This procedure returns a scalar that is the product of all lanes, calculated by +bisecting the vector into two parts, where the first contains lanes [0, N/2) +and the second contains lanes [N/2, N), and multiplying the two halves together +multiplying each even-indexed element with the following odd-indexed element to +produce N/2 values. This is repeated until only a single element remains. This +order may be faster to compute than the ordered product for floats, as it can +often be better parallelized. + +The order of the product may be important for accounting for precision errors +in floating-point computation, as floating-point multiplication is not +associative, that is `(a*b)*c` may not be equal to `a*(b*c)`. + +Inputs: +- `v`: The vector to reduce. + +Result: +- Product of all lanes, as a scalar. + +**Operation**: + + for n > 1 { + n = n / 2 + for i in 0 ..< n { + a[i] = a[2*i+0] * a[2*i+1] + } + } + res := a[0] + +Graphical representation of the operation for N=4: + + +-----------------------+ + v: | v0 | v1 | v2 | v3 | + +-----------------------+ + | | | | + `>[x]<' `>[x]<' + | | + `--->[x]<--' + | + v + +-----+ + result: | y0 | + +-----+ +*/ +reduce_mul_pairs :: intrinsics.simd_reduce_mul_pairs + +/* Reduce a vector to a scalar by finding the minimum value between all of the lanes. This procedure returns a scalar that is the minimum value of all the lanes @@ -1949,14 +2166,19 @@ Example: // The example below shows how the indices are used to determine which lanes of the // input vector get written into the result vector. - - x := #simd [4]f32 { 1.5, 2.5, 3.5, 4.5 } - res := simd.swizzle(x, 0, 3, 1, 1) - fmt.println("res") + + import "core:fmt" + import "core:simd" + + swizzle_example :: proc() { + x := #simd [4]f32 { 1.5, 2.5, 3.5, 4.5 } + res := simd.swizzle(x, 0, 3, 1, 1) + fmt.println(res) + } Output: - [ 1.5, 3.5, 2.5, 2.5 ] + <1.5, 4.5, 2.5, 2.5> The graphical representation of the operation is as follows. The `idx` vector in the picture represents the `indices` parameter: @@ -2013,8 +2235,14 @@ Example: // Since lanes 0, 1, 4, 7 contain negative numbers, the most significant // bits for them will be set. - v := #simd [8]i32 { -1, -2, +3, +4, -5, +6, +7, -8 } - fmt.println(simd.extract_msbs(v)) + + import "core:fmt" + import "core:simd" + + simd_extract_msbs_example :: proc() { + v := #simd [8]i32 { -1, -2, +3, +4, -5, +6, +7, -8 } + fmt.println(simd.extract_msbs(v)) + } Output: @@ -2052,8 +2280,14 @@ Example: // Since lanes 0, 2, 4, 6 contain odd integers, the least significant bits // for these lanes are set. - v := #simd [8]i32 { -1, -2, +3, +4, -5, +6, +7, -8 } - fmt.println(simd.extract_lsbs(v)) + + import "core:fmt" + import "core:simd" + + simd_extract_lsbs_example :: proc() { + v := #simd [8]i32 { -1, -2, +3, +4, -5, +6, +7, -8 } + fmt.println(simd.extract_lsbs(v)) + } Output: @@ -2097,15 +2331,20 @@ Example: // The example below shows how the indices are used to determine lanes of the // input vector that are shuffled into the result vector. - - a := #simd [4]f32 { 1, 2, 3, 4 } - b := #simd [4]f32 { 5, 6, 7, 8 } - res := simd.shuffle(a, b, 0, 4, 2, 5) - fmt.println("res") + + import "core:fmt" + import "core:simd" + + simd_shuffle_example :: proc() { + a := #simd [4]f32 { 1, 2, 3, 4 } + b := #simd [4]f32 { 5, 6, 7, 8 } + res := simd.shuffle(a, b, 0, 4, 2, 5) + fmt.println(res) + } Output: - [ 1, 5, 3, 6 ] + <1, 5, 3, 6> The graphical representation of the operation is as follows. The `idx` vector in the picture represents the `indices` parameter: @@ -2163,14 +2402,20 @@ Example: // The following example selects values from the two input vectors, `a` and `b` // into a single vector. - a := #simd [4] f64 { 1,2,3,4 } - b := #simd [4] f64 { 5,6,7,8 } - cond := #simd[4] int { 1, 0, 1, 0 } - fmt.println(simd.select(cond,a,b)) + + import "core:fmt" + import "core:simd" + + simd_select_example :: proc() { + a := #simd [4] f64 { 1,2,3,4 } + b := #simd [4] f64 { 5,6,7,8 } + cond := #simd[4] int { 1, 0, 1, 0 } + fmt.println(simd.select(cond,a,b)) + } Output: - [ 1, 6, 3, 8 ] + <1, 6, 3, 8> Graphically, the operation looks as follows. The `t` and `f` represent the `true` and `false` vectors respectively: @@ -2196,6 +2441,57 @@ Graphically, the operation looks as follows. The `t` and `f` represent the select :: intrinsics.simd_select /* +Runtime Equivalent to Shuffle. + +Performs element-wise table lookups using runtime indices. +Each element in the indices vector selects an element from the table vector. +The indices are automatically masked to prevent out-of-bounds access. + +This operation is hardware-accelerated on most platforms when using 8-bit +integer vectors. For other element types or unsupported vector sizes, it +falls back to software emulation. + +Inputs: +- `table`: The lookup table vector (should be power-of-2 size for correct masking). +- `indices`: The indices vector (automatically masked to valid range). + +Returns: +- A vector where `result[i] = table[indices[i] & (table_size-1)]`. + +Operation: + + for i in 0 ..< len(indices) { + masked_index := indices[i] & (len(table) - 1) + result[i] = table[masked_index] + } + return result + +Implementation: + + | Platform | Lane Size | Implementation | + |-------------|-------------------------------------------|---------------------| + | x86-64 | pshufb (16B), vpshufb (32B), AVX512 (64B) | Single vector | + | ARM64 | tbl1 (16B), tbl2 (32B), tbl4 (64B) | Automatic splitting | + | ARM32 | vtbl1 (8B), vtbl2 (16B), vtbl4 (32B) | Automatic splitting | + | WebAssembly | i8x16.swizzle (16B), Emulation (>16B) | Mixed | + | Other | Emulation | Software | + +Example: + + import "core:simd" + import "core:fmt" + + runtime_swizzle_example :: proc() { + table := simd.u8x16{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} + indices := simd.u8x16{15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14} + result := simd.runtime_swizzle(table, indices) + fmt.println(result) // Expected: {15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14} + } + +*/ +runtime_swizzle :: intrinsics.simd_runtime_swizzle + +/* Compute the square root of each lane in a SIMD vector. */ sqrt :: intrinsics.sqrt @@ -2452,3 +2748,17 @@ Example: recip :: #force_inline proc "contextless" (v: $T/#simd[$LANES]$E) -> T where intrinsics.type_is_float(E) { return T(1) / v } + + +/* +Create a vector where each lane contains the index of that lane. +Inputs: +- `V`: The type of the vector to create. +Result: +- A vector of the given type, where each lane contains the index of that lane. +**Operation**: + for i in 0 ..< N { + res[i] = i + } +*/ +indices :: intrinsics.simd_indices
\ No newline at end of file diff --git a/core/simd/x86/bmi.odin b/core/simd/x86/bmi.odin new file mode 100644 index 000000000..661272dbf --- /dev/null +++ b/core/simd/x86/bmi.odin @@ -0,0 +1,79 @@ +#+build i386, amd64 +package simd_x86 + +import "base:intrinsics" + +@(require_results, enable_target_feature="bmi") +_andn_u32 :: #force_inline proc "c" (a, b: u32) -> u32 { + return a &~ b +} +@(require_results, enable_target_feature="bmi") +_andn_u64 :: #force_inline proc "c" (a, b: u64) -> u64 { + return a &~ b +} + +@(require_results, enable_target_feature="bmi") +_bextr_u32 :: #force_inline proc "c" (a, start, len: u32) -> u32 { + return bextr_u32(a, (start & 0xff) | (len << 8)) +} +@(require_results, enable_target_feature="bmi") +_bextr_u64 :: #force_inline proc "c" (a: u64, start, len: u32) -> u64 { + return bextr_u64(a, cast(u64)((start & 0xff) | (len << 8))) +} + +@(require_results, enable_target_feature="bmi") +_bextr2_u32 :: #force_inline proc "c" (a, control: u32) -> u32 { + return bextr_u32(a, control) +} +@(require_results, enable_target_feature="bmi") +_bextr2_u64 :: #force_inline proc "c" (a, control: u64) -> u64 { + return bextr_u64(a, control) +} + +@(require_results, enable_target_feature="bmi") +_blsi_u32 :: #force_inline proc "c" (a: u32) -> u32 { + return a & -a +} +@(require_results, enable_target_feature="bmi") +_blsi_u64 :: #force_inline proc "c" (a: u64) -> u64 { + return a & -a +} + +@(require_results, enable_target_feature="bmi") +_blsmsk_u32 :: #force_inline proc "c" (a: u32) -> u32 { + return a ~ (a-1) +} +@(require_results, enable_target_feature="bmi") +_blsmsk_u64 :: #force_inline proc "c" (a: u64) -> u64 { + return a ~ (a-1) +} + +@(require_results, enable_target_feature="bmi") +_blsr_u32 :: #force_inline proc "c" (a: u32) -> u32 { + return a & (a-1) +} +@(require_results, enable_target_feature="bmi") +_blsr_u64 :: #force_inline proc "c" (a: u64) -> u64 { + return a & (a-1) +} + +@(require_results, enable_target_feature = "bmi") +_tzcnt_u16 :: #force_inline proc "c" (a: u16) -> u16 { + return intrinsics.count_trailing_zeros(a) +} +@(require_results, enable_target_feature = "bmi") +_tzcnt_u32 :: #force_inline proc "c" (a: u32) -> u32 { + return intrinsics.count_trailing_zeros(a) +} +@(require_results, enable_target_feature = "bmi") +_tzcnt_u64 :: #force_inline proc "c" (a: u64) -> u64 { + return intrinsics.count_trailing_zeros(a) +} + +@(private, default_calling_convention = "none") +foreign _ { + @(link_name = "llvm.x86.bmi.bextr.32") + bextr_u32 :: proc(a, control: u32) -> u32 --- + @(link_name = "llvm.x86.bmi.bextr.64") + bextr_u64 :: proc(a, control: u64) -> u64 --- +} diff --git a/core/simd/x86/bmi2.odin b/core/simd/x86/bmi2.odin new file mode 100644 index 000000000..65ce7f77c --- /dev/null +++ b/core/simd/x86/bmi2.odin @@ -0,0 +1,46 @@ +#+build i386, amd64 +package simd_x86 + +@(require_results, enable_target_feature = "bmi2") +_bzhi_u32 :: #force_inline proc "c" (a, index: u32) -> u32 { + return bzhi_u32(a, index) +} +@(require_results, enable_target_feature = "bmi2") +_bzhi_u64 :: #force_inline proc "c" (a, index: u64) -> u64 { + return bzhi_u64(a, index) +} + +@(require_results, enable_target_feature = "bmi2") +_pdep_u32 :: #force_inline proc "c" (a, mask: u32) -> u32 { + return pdep_u32(a, mask) +} +@(require_results, enable_target_feature = "bmi2") +_pdep_u64 :: #force_inline proc "c" (a, mask: u64) -> u64 { + return pdep_u64(a, mask) +} + +@(require_results, enable_target_feature = "bmi2") +_pext_u32 :: #force_inline proc "c" (a, mask: u32) -> u32 { + return pext_u32(a, mask) +} +@(require_results, enable_target_feature = "bmi2") +_pext_u64 :: #force_inline proc "c" (a, mask: u64) -> u64 { + return pext_u64(a, mask) +} + + +@(private, default_calling_convention = "none") +foreign _ { + @(link_name = "llvm.x86.bmi.bzhi.32") + bzhi_u32 :: proc(a, index: u32) -> u32 --- + @(link_name = "llvm.x86.bmi.bzhi.64") + bzhi_u64 :: proc(a, index: u64) -> u64 --- + @(link_name = "llvm.x86.bmi.pdep.32") + pdep_u32 :: proc(a, mask: u32) -> u32 --- + @(link_name = "llvm.x86.bmi.pdep.64") + pdep_u64 :: proc(a, mask: u64) -> u64 --- + @(link_name = "llvm.x86.bmi.pext.32") + pext_u32 :: proc(a, mask: u32) -> u32 --- + @(link_name = "llvm.x86.bmi.pext.64") + pext_u64 :: proc(a, mask: u64) -> u64 --- +} diff --git a/core/simd/x86/ssse3.odin b/core/simd/x86/ssse3.odin index 07c846e7b..03ba5dcfb 100644 --- a/core/simd/x86/ssse3.odin +++ b/core/simd/x86/ssse3.odin @@ -21,7 +21,7 @@ _mm_abs_epi32 :: #force_inline proc "c" (a: __m128i) -> __m128i { _mm_shuffle_epi8 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return transmute(__m128i)pshufb128(transmute(u8x16)a, transmute(u8x16)b) } -@(require_results, enable_target_feature="ssse3") +@(require_results, enable_target_feature="sse2,ssse3") _mm_alignr_epi8 :: #force_inline proc "c" (a, b: __m128i, $IMM8: u32) -> __m128i { shift :: IMM8 diff --git a/core/slice/slice.odin b/core/slice/slice.odin index c328fd267..8337a9728 100644 --- a/core/slice/slice.odin +++ b/core/slice/slice.odin @@ -387,6 +387,25 @@ has_prefix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_c return false } +/* + return the suffix length common between slices `a` and `b`. + + slice.suffix_length([]u8{1, 2, 3, 4}, []u8{1, 2, 3, 4}) -> 4 + slice.suffix_length([]u8{1, 2, 3, 4}, []u8{3, 4}) -> 2 + slice.suffix_length([]u8{1, 2, 3, 4}, []u8{1}) -> 0 + slice.suffix_length([]u8{1, 2, 3, 4}, []u8{1, 3, 5}) -> 0 + slice.suffix_length([]u8{3, 4, 5}, []u8{3, 5}) -> 1 +*/ +@(require_results) +suffix_length :: proc(a, b: $T/[]$E) -> (n: int) where intrinsics.type_is_comparable(E) { + len_a, len_b := len(a), len(b) + _len := builtin.min(len_a, len_b) + + #no_bounds_check for i := 1; i <= _len && a[len_a - i] == b[len_b - i]; i += 1 { + n += 1 + } + return +} @(require_results) has_suffix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_comparable(E) { diff --git a/core/sort/sort.odin b/core/sort/sort.odin index 322613cc4..c4aca4188 100644 --- a/core/sort/sort.odin +++ b/core/sort/sort.odin @@ -30,14 +30,6 @@ sort :: proc(it: Interface) { _quick_sort(it, 0, n, max_depth(n)) } - -@(deprecated="use slice.sort") -slice :: proc(array: $T/[]$E) where ORD(E) { - _slice.sort(array) - // s := array; - // sort(slice_interface(&s)); -} - slice_interface :: proc(s: ^$T/[]$E) -> Interface where ORD(E) { return Interface{ collection = rawptr(s), @@ -80,31 +72,6 @@ reverse_sort :: proc(it: Interface) { sort(reverse_interface(&it)) } -@(deprecated="use slice.reverse") -reverse_slice :: proc(array: $T/[]$E) where ORD(E) { - _slice.reverse(array) - /* - s := array; - sort(Interface{ - collection = rawptr(&s), - len = proc(it: Interface) -> int { - s := (^T)(it.collection); - return len(s^); - }, - less = proc(it: Interface, i, j: int) -> bool { - s := (^T)(it.collection); - return s[j] < s[i]; // manual set up - }, - swap = proc(it: Interface, i, j: int) { - s := (^T)(it.collection); - s[i], s[j] = s[j], s[i]; - }, - }); - */ -} - - - is_sorted :: proc(it: Interface) -> bool { n := it->len() for i := n-1; i > 0; i -= 1 { @@ -294,11 +261,6 @@ _insertion_sort :: proc(it: Interface, a, b: int) { } } - - - - -// @(deprecated="use sort.sort or slice.sort_by") bubble_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) { assert(f != nil) count := len(array) @@ -327,7 +289,6 @@ bubble_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) { } } -// @(deprecated="use sort.sort_slice or slice.sort") bubble_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) { count := len(array) @@ -355,7 +316,6 @@ bubble_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) { } } -// @(deprecated="use sort.sort or slice.sort_by") quick_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) { assert(f != nil) a := array @@ -384,7 +344,6 @@ quick_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) { quick_sort_proc(a[i:n], f) } -// @(deprecated="use sort.sort_slice or slice.sort") quick_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) { a := array n := len(a) @@ -420,7 +379,6 @@ _log2 :: proc(x: int) -> int { return res } -// @(deprecated="use sort.sort or slice.sort_by") merge_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) { merge :: proc(a: A, start, mid, end: int, f: proc(T, T) -> int) { s, m := start, mid @@ -462,7 +420,6 @@ merge_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) { internal_sort(array, 0, len(array)-1, f) } -// @(deprecated="use sort.sort_slice or slice.sort") merge_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) { merge :: proc(a: A, start, mid, end: int) { s, m := start, mid @@ -504,8 +461,6 @@ merge_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) { internal_sort(array, 0, len(array)-1) } - -// @(deprecated="use sort.sort or slice.sort_by") heap_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) { sift_proc :: proc(a: A, pi: int, n: int, f: proc(T, T) -> int) #no_bounds_check { p := pi @@ -540,7 +495,6 @@ heap_sort_proc :: proc(array: $A/[]$T, f: proc(T, T) -> int) { } } -// @(deprecated="use sort.sort_slice or slice.sort") heap_sort :: proc(array: $A/[]$T) where intrinsics.type_is_ordered(T) { sift :: proc(a: A, pi: int, n: int) #no_bounds_check { p := pi diff --git a/core/strconv/decimal/decimal.odin b/core/strconv/decimal/decimal.odin index 06503d01a..5a878d0e1 100644 --- a/core/strconv/decimal/decimal.odin +++ b/core/strconv/decimal/decimal.odin @@ -12,11 +12,11 @@ Decimal :: struct { Sets a Decimal from a given string `s`. The string is expected to represent a float. Stores parsed number in the given Decimal structure. If parsing fails, the Decimal will be left in an undefined state. -**Inputs** +**Inputs** - d: Pointer to a Decimal struct where the parsed result will be stored - s: The input string representing the floating-point number -**Returns** +**Returns** - ok: A boolean indicating whether the parsing was successful */ set :: proc(d: ^Decimal, s: string) -> (ok: bool) { @@ -104,11 +104,11 @@ set :: proc(d: ^Decimal, s: string) -> (ok: bool) { /* Converts a Decimal to a string representation, using the provided buffer as storage. -**Inputs** +**Inputs** - buf: A byte slice buffer to hold the resulting string - a: The struct to be converted to a string -**Returns** +**Returns** - A string representation of the Decimal */ decimal_to_string :: proc(buf: []byte, a: ^Decimal) -> string { @@ -150,7 +150,7 @@ decimal_to_string :: proc(buf: []byte, a: ^Decimal) -> string { /* Trims trailing zeros in the given Decimal, updating the count and decimal_point values as needed. -**Inputs** +**Inputs** - a: Pointer to the Decimal struct to be trimmed */ trim :: proc(a: ^Decimal) { @@ -166,7 +166,7 @@ Converts a given u64 integer `idx` to its Decimal representation in the provided **Used for internal Decimal Operations.** -**Inputs** +**Inputs** - a: Where the result will be stored - idx: The value to be assigned to the Decimal */ @@ -190,11 +190,11 @@ assign :: proc(a: ^Decimal, idx: u64) { trim(a) } /* -Shifts the Decimal value to the right by k positions. +Shifts the Decimal value to the right by k positions. **Used for internal Decimal Operations.** -**Inputs** +**Inputs** - a: The Decimal struct to be shifted - k: The number of positions to shift right */ @@ -344,7 +344,7 @@ Shifts the decimal of the input value to the left by `k` places WARNING: asserts `k < 61` -**Inputs** +**Inputs** - a: The Decimal to be modified - k: The number of places to shift the decimal to the left */ @@ -399,13 +399,13 @@ shift_left :: proc(a: ^Decimal, k: uint) #no_bounds_check { a.decimal_point += delta - a.count = clamp(a.count, 0, len(a.digits)) + a.count = clamp(a.count+delta, 0, len(a.digits)) trim(a) } /* Shifts the decimal of the input value by the specified number of places -**Inputs** +**Inputs** - a: The Decimal to be modified - i: The number of places to shift the decimal (positive for left shift, negative for right shift) */ @@ -435,7 +435,7 @@ shift :: proc(a: ^Decimal, i: int) { /* Determines if the Decimal can be rounded up at the given digit index -**Inputs** +**Inputs** - a: The Decimal to check - nd: The digit index to consider for rounding up @@ -455,7 +455,7 @@ can_round_up :: proc(a: ^Decimal, nd: int) -> bool { /* Rounds the Decimal at the given digit index -**Inputs** +**Inputs** - a: The Decimal to be modified - nd: The digit index to round */ @@ -470,7 +470,7 @@ round :: proc(a: ^Decimal, nd: int) { /* Rounds the Decimal up at the given digit index -**Inputs** +**Inputs** - a: The Decimal to be modified - nd: The digit index to round up */ @@ -493,7 +493,7 @@ round_up :: proc(a: ^Decimal, nd: int) { /* Rounds down the decimal value to the specified number of decimal places -**Inputs** +**Inputs** - a: The Decimal value to be rounded down - nd: The number of decimal places to round down to @@ -522,7 +522,7 @@ round_down :: proc(a: ^Decimal, nd: int) { /* Extracts the rounded integer part of a decimal value -**Inputs** +**Inputs** - a: A pointer to the Decimal value to extract the rounded integer part from WARNING: There are no guarantees about overflow. @@ -562,4 +562,3 @@ rounded_integer :: proc(a: ^Decimal) -> u64 { } return n } - diff --git a/core/strconv/deprecated.odin b/core/strconv/deprecated.odin new file mode 100644 index 000000000..c644d331e --- /dev/null +++ b/core/strconv/deprecated.odin @@ -0,0 +1,62 @@ +package strconv + +// (2025-06-05) These procedures are to be removed at a later release. + +@(deprecated="Use write_bits instead") +append_bits :: proc(buf: []byte, x: u64, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string { + return write_bits(buf, x, base, is_signed, bit_size, digits, flags) +} + +@(deprecated="Use write_bits_128 instead") +append_bits_128 :: proc(buf: []byte, x: u128, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string { + return write_bits_128(buf, x, base, is_signed, bit_size, digits, flags) +} + +@(deprecated="Use write_bool instead") +append_bool :: proc(buf: []byte, b: bool) -> string { + return write_bool(buf, b) +} + +@(deprecated="Use write_uint instead") +append_uint :: proc(buf: []byte, u: u64, base: int) -> string { + return write_uint(buf, u, base) +} + +@(deprecated="Use write_int instead") +append_int :: proc(buf: []byte, i: i64, base: int) -> string { + return write_int(buf, i, base) +} + +@(deprecated="Use write_u128 instead") +append_u128 :: proc(buf: []byte, u: u128, base: int) -> string { + return write_u128(buf, u, base) +} + +@(deprecated="Use write_float instead") +append_float :: proc(buf: []byte, f: f64, fmt: byte, prec, bit_size: int) -> string { + return write_float(buf, f, fmt, prec, bit_size) +} + +// 2025-10-03 Deprecated C short names and implementations + +@(deprecated="Use strconv.write_int() instead") +itoa :: proc(buf: []byte, i: int) -> string { + return write_int(buf, i64(i), 10) +} + +@(deprecated="Use strconv.parse_int() instead") +atoi :: proc(s: string) -> int { + v, _ := parse_int(s) + return v +} + +@(deprecated="Use strconv.parse_f64() instead") +atof :: proc(s: string) -> f64 { + v, _ := parse_f64(s) + return v +} + +@(deprecated="Use strconv.write_float() instead") +ftoa :: proc(buf: []byte, f: f64, fmt: byte, prec, bit_size: int) -> string { + return string(generic_ftoa(buf, f, fmt, prec, bit_size)) +}
\ No newline at end of file diff --git a/core/strconv/generic_float.odin b/core/strconv/generic_float.odin index b049f0fe1..163148156 100644 --- a/core/strconv/generic_float.odin +++ b/core/strconv/generic_float.odin @@ -23,7 +23,7 @@ _f64_info := Float_Info{52, 11, -1023} /* Converts a floating-point number to a string with the specified format and precision. -**Inputs** +**Inputs** buf: A byte slice to store the resulting string val: The floating-point value to be converted @@ -40,7 +40,7 @@ Example: bit_size := 64 result := strconv.generic_ftoa(buf[:], val, fmt, precision, bit_size) -> "3.14" -**Returns** +**Returns** - A byte slice containing the formatted string */ generic_ftoa :: proc(buf: []byte, val: f64, fmt: byte, precision, bit_size: int) -> []byte { @@ -122,7 +122,7 @@ generic_ftoa :: proc(buf: []byte, val: f64, fmt: byte, precision, bit_size: int) /* Converts a decimal floating-point number into a byte buffer with the given format -**Inputs** +**Inputs** - buf: The byte buffer to store the formatted number - shortest: If true, generates the shortest representation of the number - neg: If true, the number is negative @@ -130,7 +130,7 @@ Converts a decimal floating-point number into a byte buffer with the given forma - precision: The number of digits after the decimal point - fmt: The format specifier (accepted values: 'f', 'F', 'e', 'E', 'g', 'G') -**Returns** +**Returns** - A byte slice containing the formatted decimal floating-point number */ format_digits :: proc(buf: []byte, shortest: bool, neg: bool, digs: Decimal_Slice, precision: int, fmt: byte) -> []byte { @@ -256,7 +256,7 @@ format_digits :: proc(buf: []byte, shortest: bool, neg: bool, digs: Decimal_Slic /* Rounds the given decimal number to its shortest representation, considering the provided floating-point format -**Inputs** +**Inputs** - d: The decimal number to round - mant: The mantissa of the floating-point number - exp: The exponent of the floating-point number @@ -331,53 +331,45 @@ round_shortest :: proc(d: ^decimal.Decimal, mant: u64, exp: int, flt: ^Float_Inf /* Converts a decimal number to its floating-point representation with the given format and returns the resulting bits -**Inputs** +**Inputs** - d: Pointer to the decimal number to convert - info: Pointer to the Float_Info structure containing information about the floating-point format -**Returns** +**Returns** - b: The bits representing the floating-point number - overflow: A boolean indicating whether an overflow occurred during conversion */ -@(private) decimal_to_float_bits :: proc(d: ^decimal.Decimal, info: ^Float_Info) -> (b: u64, overflow: bool) { - end :: proc "contextless" (d: ^decimal.Decimal, mant: u64, exp: int, info: ^Float_Info) -> (bits: u64) { + overflow_end :: proc "contextless" (d: ^decimal.Decimal, info: ^Float_Info) -> (u64, bool) { + mant: u64 = 0 + exp: int = 1<<info.expbits - 1 + info.bias + return end(d, mant, exp, info, true) + } + end :: proc "contextless" (d: ^decimal.Decimal, mant: u64, exp: int, info: ^Float_Info, is_overflow: bool) -> (bits: u64, overflow: bool) { bits = mant & (u64(1)<<info.mantbits - 1) bits |= u64((exp-info.bias) & (1<<info.expbits - 1)) << info.mantbits if d.neg { - bits |= 1<< info.mantbits << info.expbits + bits |= 1 << info.mantbits << info.expbits } + overflow = is_overflow return } - set_overflow :: proc "contextless" (mant: ^u64, exp: ^int, info: ^Float_Info) -> bool { - mant^ = 0 - exp^ = 1<<info.expbits - 1 + info.bias - return true - } - mant: u64 - exp: int if d.count == 0 { - mant = 0 - exp = info.bias - b = end(d, mant, exp, info) - return + return end(d, 0, info.bias, info, false) } + if d.decimal_point > 310 { - set_overflow(&mant, &exp, info) - b = end(d, mant, exp, info) - return + return overflow_end(d, info) } else if d.decimal_point < -330 { - mant = 0 - exp = info.bias - b = end(d, mant, exp, info) - return + return end(d, 0, info.bias, info, false) } @(static, rodata) power_table := [?]int{1, 3, 6, 9, 13, 16, 19, 23, 26} - exp = 0 + + exp := 0 for d.decimal_point > 0 { n := 27 if d.decimal_point >= len(power_table) else power_table[d.decimal_point] decimal.shift(d, -n) @@ -392,35 +384,34 @@ decimal_to_float_bits :: proc(d: ^decimal.Decimal, info: ^Float_Info) -> (b: u64 // go from [0.5, 1) to [1, 2) exp -= 1 + // Min rep exp is 1+bias if exp < info.bias + 1 { n := info.bias + 1 - exp - decimal.shift(d, n) + decimal.shift(d, -n) exp += n } if (exp-info.bias) >= (1<<info.expbits - 1) { - set_overflow(&mant, &exp, info) - b = end(d, mant, exp, info) - return + return overflow_end(d, info) } + // Extract 1 + mantbits decimal.shift(d, int(1 + info.mantbits)) - mant = decimal.rounded_integer(d) + mant := decimal.rounded_integer(d) + // Rounding for shift down if mant == 2<<info.mantbits { mant >>= 1 exp += 1 if (exp-info.bias) >= (1<<info.expbits - 1) { - set_overflow(&mant, &exp, info) - b = end(d, mant, exp, info) - return + return overflow_end(d, info) } } + // Check for denormalized mantissa if mant & (1<<info.mantbits) == 0 { exp = info.bias } - b = end(d, mant, exp, info) - return + return end(d, mant, exp, info, false) } diff --git a/core/strconv/integers.odin b/core/strconv/integers.odin index 98a432ac5..16c31ac42 100644 --- a/core/strconv/integers.odin +++ b/core/strconv/integers.odin @@ -12,12 +12,12 @@ digits := "0123456789abcdefghijklmnopqrstuvwxyz" /* Determines whether the given unsigned 64-bit integer is a negative value by interpreting it as a signed integer with the specified bit size. -**Inputs** +**Inputs** - x: The unsigned 64-bit integer to check for negativity - is_signed: A boolean indicating if the input should be treated as a signed integer - bit_size: The bit size of the signed integer representation (8, 16, 32, or 64) -**Returns** +**Returns** - u: The absolute value of the input integer - neg: A boolean indicating whether the input integer is negative */ @@ -48,9 +48,9 @@ is_integer_negative :: proc(x: u64, is_signed: bool, bit_size: int) -> (u: u64, return } /* -Appends the string representation of an integer to a buffer with specified base, flags, and digit set. +Writes the string representation of an integer to a buffer with specified base, flags, and digit set. -**Inputs** +**Inputs** - buf: The buffer to append the integer representation to - x: The integer value to convert - base: The base for the integer representation (2 <= base <= MAX_BASE) @@ -59,12 +59,12 @@ Appends the string representation of an integer to a buffer with specified base, - digits: The digit set used for the integer representation - flags: The Int_Flags bit set to control integer formatting -**Returns** +**Returns** - The string containing the integer representation appended to the buffer */ -append_bits :: proc(buf: []byte, x: u64, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string { +write_bits :: proc(buf: []byte, x: u64, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string { if base < 2 || base > MAX_BASE { - panic("strconv: illegal base passed to append_bits") + panic("strconv: illegal base passed to write_bits") } a: [129]byte @@ -106,12 +106,12 @@ append_bits :: proc(buf: []byte, x: u64, base: int, is_signed: bool, bit_size: i /* Determines whether the given unsigned 128-bit integer is a negative value by interpreting it as a signed integer with the specified bit size. -**Inputs** +**Inputs** - x: The unsigned 128-bit integer to check for negativity - is_signed: A boolean indicating if the input should be treated as a signed integer - bit_size: The bit size of the signed integer representation (8, 16, 32, 64, or 128) -**Returns** +**Returns** - u: The absolute value of the input integer - neg: A boolean indicating whether the input integer is negative */ @@ -146,9 +146,9 @@ is_integer_negative_128 :: proc(x: u128, is_signed: bool, bit_size: int) -> (u: return } /* -Appends the string representation of a 128-bit integer to a buffer with specified base, flags, and digit set. +Writes the string representation of a 128-bit integer to a buffer with specified base, flags, and digit set. -**Inputs** +**Inputs** - buf: The buffer to append the integer representation to - x: The 128-bit integer value to convert - base: The base for the integer representation (2 <= base <= MAX_BASE) @@ -157,12 +157,12 @@ Appends the string representation of a 128-bit integer to a buffer with specifie - digits: The digit set used for the integer representation - flags: The Int_Flags bit set to control integer formatting -**Returns** -- The string containing the integer representation appended to the buffer +**Returns** +- The string containing the integer representation written to the buffer */ -append_bits_128 :: proc(buf: []byte, x: u128, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string { +write_bits_128 :: proc(buf: []byte, x: u128, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flags) -> string { if base < 2 || base > MAX_BASE { - panic("strconv: illegal base passed to append_bits") + panic("strconv: illegal base passed to write_bits") } a: [140]byte diff --git a/core/strconv/strconv.odin b/core/strconv/strconv.odin index 26a737bd1..7fcb578f7 100644 --- a/core/strconv/strconv.odin +++ b/core/strconv/strconv.odin @@ -5,8 +5,8 @@ import "decimal" /* Parses a boolean value from the input string -**Inputs** -- s: The input string +**Inputs** +- s: The input string - true: "1", "t", "T", "true", "TRUE", "True" - false: "0", "f", "F", "false", "FALSE", "False" - n: An optional pointer to an int to store the length of the parsed substring (default: nil) @@ -386,7 +386,7 @@ Parses an unsigned integer value from the input string, using the specified base - If base is not 0, it will be used for parsing regardless of any prefix in the input string Example: - + import "core:fmt" import "core:strconv" parse_uint_example :: proc() { @@ -399,14 +399,14 @@ Example: n, ok = strconv.parse_uint("0xffff") // with prefix and inferred base fmt.println(n,ok) } - + Output: 1234 true 65535 true 65535 true -**Returns** +**Returns** value: The parsed uint value ok: `false` if no appropriate value could be found; the value was negative; he input string contained more than just the number @@ -423,7 +423,7 @@ parse_uint :: proc(s: string, base := 0, n: ^int = nil) -> (value: uint, ok: boo /* Parses an integer value from a string in the given base, without any prefix -**Inputs** +**Inputs** - str: The input string containing the integer value - base: The base (radix) to use for parsing the integer (1-16) - n: An optional pointer to an int to store the length of the parsed substring (default: nil) @@ -436,12 +436,12 @@ Example: n, ok := strconv.parse_i128_of_base("-1234eeee", 10) fmt.println(n,ok) } - + Output: -1234 false -**Returns** +**Returns** - value: The parsed i128 value - ok: false if no numeric value of the appropriate base could be found, or if the input string contained more than just the number. */ @@ -491,7 +491,7 @@ parse_i128_of_base :: proc(str: string, base: int, n: ^int = nil) -> (value: i12 /* Parses an integer value from a string in base 10, unless there's a prefix -**Inputs** +**Inputs** - str: The input string containing the integer value - n: An optional pointer to an int to store the length of the parsed substring (default: nil) @@ -506,13 +506,13 @@ Example: n, ok = strconv.parse_i128_maybe_prefixed("0xeeee") fmt.println(n, ok) } - + Output: 1234 true 61166 true - -**Returns** + +**Returns** - value: The parsed i128 value - ok: `false` if a valid integer could not be found, or if the input string contained more than just the number. */ @@ -574,7 +574,7 @@ parse_i128 :: proc{parse_i128_maybe_prefixed, parse_i128_of_base} /* Parses an unsigned integer value from a string in the given base, without any prefix -**Inputs** +**Inputs** - str: The input string containing the integer value - base: The base (radix) to use for parsing the integer (1-16) - n: An optional pointer to an int to store the length of the parsed substring (default: nil) @@ -590,13 +590,13 @@ Example: n, ok = strconv.parse_u128_of_base("5678eeee", 16) fmt.println(n, ok) } - + Output: 1234 false 1450766062 true - -**Returns** + +**Returns** - value: The parsed u128 value - ok: `false` if no numeric value of the appropriate base could be found, or if the input string contained more than just the number. */ @@ -634,7 +634,7 @@ parse_u128_of_base :: proc(str: string, base: int, n: ^int = nil) -> (value: u12 /* Parses an unsigned integer value from a string in base 10, unless there's a prefix -**Inputs** +**Inputs** - str: The input string containing the integer value - n: An optional pointer to an int to store the length of the parsed substring (default: nil) @@ -649,13 +649,13 @@ Example: n, ok = strconv.parse_u128_maybe_prefixed("5678eeee") fmt.println(n, ok) } - + Output: 1234 true 5678 false - -**Returns** + +**Returns** - value: The parsed u128 value - ok: false if a valid integer could not be found, if the value was negative, or if the input string contained more than just the number. */ @@ -706,10 +706,10 @@ parse_u128 :: proc{parse_u128_maybe_prefixed, parse_u128_of_base} /* Converts a byte to lowercase -**Inputs** +**Inputs** - ch: A byte character to be converted to lowercase. -**Returns** +**Returns** - A lowercase byte character. */ @(private) @@ -717,7 +717,7 @@ lower :: #force_inline proc "contextless" (ch: byte) -> byte { return ('a' - 'A' /* Parses a 32-bit floating point number from a string -**Inputs** +**Inputs** - s: The input string containing a 32-bit floating point number. - n: An optional pointer to an int to store the length of the parsed substring (default: nil). @@ -732,13 +732,13 @@ Example: n, ok = strconv.parse_f32("5678e2") fmt.printfln("%.3f %v", n, ok) } - + Output: 0.000 false 567800.000 true - -**Returns** + +**Returns** - value: The parsed 32-bit floating point number. - ok: `false` if a base 10 float could not be found, or if the input string contained more than just the number. */ @@ -750,7 +750,7 @@ parse_f32 :: proc(s: string, n: ^int = nil) -> (value: f32, ok: bool) { /* Parses a 64-bit floating point number from a string -**Inputs** +**Inputs** - str: The input string containing a 64-bit floating point number. - n: An optional pointer to an int to store the length of the parsed substring (default: nil). @@ -765,13 +765,13 @@ Example: n, ok = strconv.parse_f64("5678e2") fmt.printfln("%.3f %v", n, ok) } - + Output: 0.000 false 567800.000 true - -**Returns** + +**Returns** - value: The parsed 64-bit floating point number. - ok: `false` if a base 10 float could not be found, or if the input string contained more than just the number. */ @@ -787,7 +787,7 @@ parse_f64 :: proc(str: string, n: ^int = nil) -> (value: f64, ok: bool) { /* Parses a 32-bit floating point number from a string and returns the parsed number, the length of the parsed substring, and a boolean indicating whether the parsing was successful -**Inputs** +**Inputs** - str: The input string containing a 32-bit floating point number. Example: @@ -801,14 +801,14 @@ Example: n, _, ok = strconv.parse_f32_prefix("5678e2") fmt.printfln("%.3f %v", n, ok) } - + Output: 0.000 false 567800.000 true - -**Returns** + +**Returns** - value: The parsed 32-bit floating point number. - nr: The length of the parsed substring. - ok: A boolean indicating whether the parsing was successful. @@ -822,7 +822,7 @@ parse_f32_prefix :: proc(str: string) -> (value: f32, nr: int, ok: bool) { /* Parses a 64-bit floating point number from a string and returns the parsed number, the length of the parsed substring, and a boolean indicating whether the parsing was successful -**Inputs** +**Inputs** - str: The input string containing a 64-bit floating point number. Example: @@ -846,7 +846,7 @@ Output: 1234.000 true 13.370 true -**Returns** +**Returns** - value: The parsed 64-bit floating point number. - nr: The length of the parsed substring. - ok: `false` if a base 10 float could not be found @@ -1095,6 +1095,39 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { return transmute(f64)bits, ok } + if len(str) > 2 && str[0] == '0' && str[1] == 'h' { + nr = 2 + + as_int: u64 + digits: int + for r in str[2:] { + if r == '_' { + nr += 1 + continue + } + v := u64(_digit_value(r)) + if v >= 16 { + break + } + as_int *= 16 + as_int += v + digits += 1 + } + nr += digits + ok = len(str) == nr + + switch digits { + case 4: + value = cast(f64)transmute(f16)cast(u16)as_int + case 8: + value = cast(f64)transmute(f32)cast(u32)as_int + case 16: + value = transmute(f64)as_int + case: + ok = false + } + return + } if value, nr, ok = check_special(str); ok { return @@ -1151,7 +1184,7 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { /* Parses a 128-bit complex number from a string -**Inputs** +**Inputs** - str: The input string containing a 128-bit complex number. - n: An optional pointer to an int to store the length of the parsed substring (default: nil). @@ -1167,13 +1200,13 @@ Example: c, ok = strconv.parse_complex128("5+7i hellope", &n) fmt.printfln("%v %i %t", c, n, ok) } - + Output: 3+1i 4 true 5+7i 4 false - -**Returns** + +**Returns** - value: The parsed 128-bit complex number. - ok: `false` if a complex number could not be found, or if the input string contained more than just the number. */ @@ -1199,12 +1232,12 @@ parse_complex128 :: proc(str: string, n: ^int = nil) -> (value: complex128, ok: } value = complex(real_value, imag_value) - return + return } /* Parses a 64-bit complex number from a string -**Inputs** +**Inputs** - str: The input string containing a 64-bit complex number. - n: An optional pointer to an int to store the length of the parsed substring (default: nil). @@ -1220,13 +1253,13 @@ Example: c, ok = strconv.parse_complex64("5+7i hellope", &n) fmt.printfln("%v %i %t", c, n, ok) } - + Output: 3+1i 4 true 5+7i 4 false - -**Returns** + +**Returns** - value: The parsed 64-bit complex number. - ok: `false` if a complex number could not be found, or if the input string contained more than just the number. */ @@ -1238,7 +1271,7 @@ parse_complex64 :: proc(str: string, n: ^int = nil) -> (value: complex64, ok: bo /* Parses a 32-bit complex number from a string -**Inputs** +**Inputs** - str: The input string containing a 32-bit complex number. - n: An optional pointer to an int to store the length of the parsed substring (default: nil). @@ -1254,13 +1287,13 @@ Example: c, ok = strconv.parse_complex32("5+7i hellope", &n) fmt.printfln("%v %i %t", c, n, ok) } - + Output: 3+1i 4 true 5+7i 4 false - -**Returns** + +**Returns** - value: The parsed 32-bit complex number. - ok: `false` if a complex number could not be found, or if the input string contained more than just the number. */ @@ -1272,7 +1305,7 @@ parse_complex32 :: proc(str: string, n: ^int = nil) -> (value: complex32, ok: bo /* Parses a 256-bit quaternion from a string -**Inputs** +**Inputs** - str: The input string containing a 256-bit quaternion. - n: An optional pointer to an int to store the length of the parsed substring (default: nil). @@ -1288,13 +1321,13 @@ Example: q, ok = strconv.parse_quaternion256("1+2i+3j+4k hellope", &n) fmt.printfln("%v %i %t", q, n, ok) } - + Output: 1+2i+3j+4k 10 true 1+2i+3j+4k 10 false - -**Returns** + +**Returns** - value: The parsed 256-bit quaternion. - ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion. */ @@ -1352,7 +1385,7 @@ parse_quaternion256 :: proc(str: string, n: ^int = nil) -> (value: quaternion256 /* Parses a 128-bit quaternion from a string -**Inputs** +**Inputs** - str: The input string containing a 128-bit quaternion. - n: An optional pointer to an int to store the length of the parsed substring (default: nil). @@ -1368,13 +1401,13 @@ Example: q, ok = strconv.parse_quaternion128("1+2i+3j+4k hellope", &n) fmt.printfln("%v %i %t", q, n, ok) } - + Output: 1+2i+3j+4k 10 true 1+2i+3j+4k 10 false - -**Returns** + +**Returns** - value: The parsed 128-bit quaternion. - ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion. */ @@ -1386,7 +1419,7 @@ parse_quaternion128 :: proc(str: string, n: ^int = nil) -> (value: quaternion128 /* Parses a 64-bit quaternion from a string -**Inputs** +**Inputs** - str: The input string containing a 64-bit quaternion. - n: An optional pointer to an int to store the length of the parsed substring (default: nil). @@ -1402,13 +1435,13 @@ Example: q, ok = strconv.parse_quaternion64("1+2i+3j+4k hellope", &n) fmt.printfln("%v %i %t", q, n, ok) } - + Output: 1+2i+3j+4k 10 true 1+2i+3j+4k 10 false - -**Returns** + +**Returns** - value: The parsed 64-bit quaternion. - ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion. */ @@ -1417,20 +1450,20 @@ parse_quaternion64 :: proc(str: string, n: ^int = nil) -> (value: quaternion64, v, ok = parse_quaternion256(str, n) return cast(quaternion64)v, ok } -/* -Appends a boolean value as a string to the given buffer +/* +Writes a boolean value as a string to the given buffer -**Inputs** -- buf: The buffer to append the boolean value to -- b: The boolean value to be appended +**Inputs** +- buf: The buffer to write the boolean value to +- b: The boolean value to be written Example: import "core:fmt" import "core:strconv" - append_bool_example :: proc() { + write_bool_example :: proc() { buf: [6]byte - result := strconv.append_bool(buf[:], true) + result := strconv.write_bool(buf[:], true) fmt.println(result, buf) } @@ -1438,10 +1471,10 @@ Output: true [116, 114, 117, 101, 0, 0] -**Returns** -- The resulting string after appending the boolean value +**Returns** +- The resulting string after writing the boolean value */ -append_bool :: proc(buf: []byte, b: bool) -> string { +write_bool :: proc(buf: []byte, b: bool) -> string { n := 0 if b { n = copy(buf, "true") @@ -1450,21 +1483,21 @@ append_bool :: proc(buf: []byte, b: bool) -> string { } return string(buf[:n]) } -/* -Appends an unsigned integer value as a string to the given buffer with the specified base +/* +Writes an unsigned integer value as a string to the given buffer with the specified base -**Inputs** -- buf: The buffer to append the unsigned integer value to -- u: The unsigned integer value to be appended +**Inputs** +- buf: The buffer to write the unsigned integer value to +- u: The unsigned integer value to be written - base: The base to use for converting the integer value Example: import "core:fmt" import "core:strconv" - append_uint_example :: proc() { + write_uint_example :: proc() { buf: [4]byte - result := strconv.append_uint(buf[:], 42, 16) + result := strconv.write_uint(buf[:], 42, 16) fmt.println(result, buf) } @@ -1472,27 +1505,27 @@ Output: 2a [50, 97, 0, 0] -**Returns** -- The resulting string after appending the unsigned integer value +**Returns** +- The resulting string after writing the unsigned integer value */ -append_uint :: proc(buf: []byte, u: u64, base: int) -> string { - return append_bits(buf, u, base, false, 8*size_of(uint), digits, nil) +write_uint :: proc(buf: []byte, u: u64, base: int) -> string { + return write_bits(buf, u, base, false, 8*size_of(uint), digits, nil) } -/* -Appends a signed integer value as a string to the given buffer with the specified base +/* +Writes a signed integer value as a string to the given buffer with the specified base -**Inputs** -- buf: The buffer to append the signed integer value to -- i: The signed integer value to be appended +**Inputs** +- buf: The buffer to write the signed integer value to +- i: The signed integer value to be written - base: The base to use for converting the integer value Example: import "core:fmt" import "core:strconv" - append_int_example :: proc() { + write_int_example :: proc() { buf: [4]byte - result := strconv.append_int(buf[:], -42, 10) + result := strconv.write_int(buf[:], -42, 10) fmt.println(result, buf) } @@ -1500,104 +1533,27 @@ Output: -42 [45, 52, 50, 0] -**Returns** -- The resulting string after appending the signed integer value +**Returns** +- The resulting string after writing the signed integer value */ -append_int :: proc(buf: []byte, i: i64, base: int) -> string { - return append_bits(buf, u64(i), base, true, 8*size_of(int), digits, nil) +write_int :: proc(buf: []byte, i: i64, base: int) -> string { + return write_bits(buf, u64(i), base, true, 8*size_of(int), digits, nil) } -append_u128 :: proc(buf: []byte, u: u128, base: int) -> string { - return append_bits_128(buf, u, base, false, 8*size_of(uint), digits, nil) +write_u128 :: proc(buf: []byte, u: u128, base: int) -> string { + return write_bits_128(buf, u, base, false, 8*size_of(uint), digits, nil) } -/* -Converts an integer value to a string and stores it in the given buffer - -**Inputs** -- buf: The buffer to store the resulting string -- i: The integer value to be converted - -Example: - - import "core:fmt" - import "core:strconv" - itoa_example :: proc() { - buf: [4]byte - result := strconv.itoa(buf[:], 42) - fmt.println(result, buf) // "42" - } - -Output: - - 42 [52, 50, 0, 0] - -**Returns** -- The resulting string after converting the integer value -*/ -itoa :: proc(buf: []byte, i: int) -> string { - return append_int(buf, i64(i), 10) -} /* -Converts a string to an integer value - -**Inputs** -- s: The string to be converted - -Example: - - import "core:fmt" - import "core:strconv" - atoi_example :: proc() { - fmt.println(strconv.atoi("42")) - } - -Output: - - 42 +`ftoa` C name deprecated, use `write_float` instead (same procedure) -**Returns** -- The resulting integer value -*/ -atoi :: proc(s: string) -> int { - v, _ := parse_int(s) - return v -} -/* -Converts a string to a float64 value +Writes a float64 value as a string to the given buffer with the specified format and precision -**Inputs** -- s: The string to be converted - -Example: - - import "core:fmt" - import "core:strconv" - atof_example :: proc() { - fmt.printfln("%.3f", strconv.atof("3.14")) - } - -Output: - - 3.140 - -**Returns** -- The resulting float64 value after converting the string -*/ -atof :: proc(s: string) -> f64 { - v, _ := parse_f64(s) - return v -} -// Alias to `append_float` -ftoa :: append_float -/* -Appends a float64 value as a string to the given buffer with the specified format and precision - -**Inputs** -- buf: The buffer to append the float64 value to -- f: The float64 value to be appended +**Inputs** +- buf: The buffer to write the float64 value to +- f: The float64 value to be written - fmt: The byte specifying the format to use for the conversion - prec: The precision to use for the conversion - bit_size: The size of the float in bits (32 or 64) @@ -1606,9 +1562,9 @@ Example: import "core:fmt" import "core:strconv" - append_float_example :: proc() { + write_float_example :: proc() { buf: [8]byte - result := strconv.append_float(buf[:], 3.14159, 'f', 2, 64) + result := strconv.write_float(buf[:], 3.14159, 'f', 2, 64) fmt.println(result, buf) } @@ -1616,20 +1572,27 @@ Output: +3.14 [43, 51, 46, 49, 52, 0, 0, 0] -**Returns** -- The resulting string after appending the float +**Returns** +- The resulting string after writing the float */ -append_float :: proc(buf: []byte, f: f64, fmt: byte, prec, bit_size: int) -> string { +write_float :: proc(buf: []byte, f: f64, fmt: byte, prec, bit_size: int) -> string { return string(generic_ftoa(buf, f, fmt, prec, bit_size)) } +// Accepts '0'..='9', otherwise returns ok = false +digit_to_int :: proc(r: rune) -> (value: int, ok: bool) { + if '0' <= r && r <= '9' { + return int(r - '0'), true + } + return -1, false +} /* -Appends a quoted string representation of the input string to a given byte slice and returns the result as a string +Writes a quoted string representation of the input string to a given byte slice and returns the result as a string -**Inputs** -- buf: The byte slice to which the quoted string will be appended +**Inputs** +- buf: The byte slice to which the quoted string will be written - str: The input string to be quoted -!! ISSUE !! NOT EXPECTED -- "\"hello\"" was expected +!! ISSUE !! NOT EXPECTED -- "\"hello\"" was expected Example: @@ -1645,8 +1608,8 @@ Output: "'h''e''l''l''o'" [34, 39, 104, 39, 39, 101, 39, 39, 108, 39, 39, 108, 39, 39, 111, 39, 34, 0, 0, 0] -**Returns** -- The resulting string after appending the quoted string representation +**Returns** +- The resulting string after writing the quoted string representation */ quote :: proc(buf: []byte, str: string) -> string { write_byte :: proc(buf: []byte, i: ^int, bytes: ..byte) { @@ -1686,10 +1649,10 @@ quote :: proc(buf: []byte, str: string) -> string { return string(buf[:i]) } /* -Appends a quoted rune representation of the input rune to a given byte slice and returns the result as a string +Writes a quoted rune representation of the input rune to a given byte slice and returns the result as a string -**Inputs** -- buf: The byte slice to which the quoted rune will be appended +**Inputs** +- buf: The byte slice to which the quoted rune will be written - r: The input rune to be quoted Example: @@ -1706,8 +1669,8 @@ Output: 'A' [39, 65, 39, 0] -**Returns** -- The resulting string after appending the quoted rune representation +**Returns** +- The resulting string after writing the quoted rune representation */ quote_rune :: proc(buf: []byte, r: rune) -> string { write_byte :: proc(buf: []byte, i: ^int, bytes: ..byte) { @@ -1750,7 +1713,7 @@ quote_rune :: proc(buf: []byte, r: rune) -> string { if r < 32 { write_string(buf, &i, "\\x") b: [2]byte - s := append_bits(b[:], u64(r), 16, true, 64, digits, nil) + s := write_bits(b[:], u64(r), 16, true, 64, digits, nil) switch len(s) { case 0: write_string(buf, &i, "00") case 1: write_rune(buf, &i, '0') @@ -1767,11 +1730,11 @@ quote_rune :: proc(buf: []byte, r: rune) -> string { /* Unquotes a single character from the input string, considering the given quote character -**Inputs** +**Inputs** - str: The input string containing the character to unquote - quote: The quote character to consider (e.g., '"') -Example: +Example: import "core:fmt" import "core:strconv" @@ -1782,12 +1745,12 @@ Example: fmt.printf("r: <%v>, multiple_bytes:%v, tail_string:<%s>, success:%v\n",r, multiple_bytes, tail_string, success) } -Output: +Output: Source: 'The' raven r: <'>, multiple_bytes:false, tail_string:<The' raven>, success:true -**Returns** +**Returns** - r: The unquoted rune - multiple_bytes: A boolean indicating if the rune has multiple bytes - tail_string: The remaining portion of the input string after unquoting the character @@ -1890,13 +1853,13 @@ unquote_char :: proc(str: string, quote: byte) -> (r: rune, multiple_bytes: bool /* Unquotes the input string considering any type of quote character and returns the unquoted string -**Inputs** +**Inputs** - lit: The input string to unquote - allocator: (default: context.allocator) WARNING: This procedure gives unexpected results if the quotes are not the first and last characters. -Example: +Example: import "core:fmt" import "core:strconv" @@ -1914,10 +1877,10 @@ Example: src="The raven \'Huginn\' is black." s, allocated, ok = strconv.unquote_string(src) // Will produce undesireable results fmt.println(src) - fmt.printf("Unquoted: <%s>, alloc:%v, ok:%v\n", s, allocated, ok) + fmt.printf("Unquoted: <%s>, alloc:%v, ok:%v\n", s, allocated, ok) } -Output: +Output: "The raven Huginn is black." Unquoted: <The raven Huginn is black.>, alloc:false, ok:true @@ -1928,7 +1891,7 @@ Output: The raven 'Huginn' is black. Unquoted: <he raven 'Huginn' is black>, alloc:false, ok:true -**Returns** +**Returns** - res: The resulting unquoted string - allocated: A boolean indicating if the resulting string was allocated using the provided allocator - success: A boolean indicating whether the unquoting was successful @@ -1969,7 +1932,7 @@ unquote_string :: proc(lit: string, allocator := context.allocator) -> (res: str return s, false, true } } - + context.allocator = allocator buf_len := 3*len(s) / 2 diff --git a/core/strings/builder.odin b/core/strings/builder.odin index e5a88527a..285ced9ce 100644 --- a/core/strings/builder.odin +++ b/core/strings/builder.odin @@ -296,8 +296,8 @@ Inputs: Returns: - res: A cstring of the Builder's buffer */ -unsafe_to_cstring :: proc(b: ^Builder) -> (res: cstring) { - append(&b.buf, 0) +unsafe_to_cstring :: proc(b: ^Builder, loc := #caller_location) -> (res: cstring) { + append(&b.buf, 0, loc) pop(&b.buf) return cstring(raw_data(b.buf)) } @@ -311,8 +311,8 @@ Returns: - res: A cstring of the Builder's buffer upon success - err: An optional allocator error if one occured, `nil` otherwise */ -to_cstring :: proc(b: ^Builder) -> (res: cstring, err: mem.Allocator_Error) { - n := append(&b.buf, 0) or_return +to_cstring :: proc(b: ^Builder, loc := #caller_location) -> (res: cstring, err: mem.Allocator_Error) #optional_allocator_error { + n := append(&b.buf, 0, loc) or_return if n != 1 { return nil, .Out_Of_Memory } @@ -518,9 +518,9 @@ Output: abc */ -write_string :: proc(b: ^Builder, s: string) -> (n: int) { +write_string :: proc(b: ^Builder, s: string, loc := #caller_location) -> (n: int) { n0 := len(b.buf) - append(&b.buf, s) + append(&b.buf, s, loc) n1 := len(b.buf) return n1-n0 } @@ -675,7 +675,7 @@ Returns: */ write_float :: proc(b: ^Builder, f: f64, fmt: byte, prec, bit_size: int, always_signed := false) -> (n: int) { buf: [384]byte - s := strconv.append_float(buf[:], f, fmt, prec, bit_size) + s := strconv.write_float(buf[:], f, fmt, prec, bit_size) // If the result starts with a `+` then unless we always want signed results, // we skip it unless it's followed by an `I` (because of +Inf). if !always_signed && (buf[0] == '+' && buf[1] != 'I') { @@ -699,7 +699,7 @@ Returns: */ write_f16 :: proc(b: ^Builder, f: f16, fmt: byte, always_signed := false) -> (n: int) { buf: [384]byte - s := strconv.append_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f)) + s := strconv.write_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f)) if !always_signed && (buf[0] == '+' && buf[1] != 'I') { s = s[1:] } @@ -739,7 +739,7 @@ Output: */ write_f32 :: proc(b: ^Builder, f: f32, fmt: byte, always_signed := false) -> (n: int) { buf: [384]byte - s := strconv.append_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f)) + s := strconv.write_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f)) if !always_signed && (buf[0] == '+' && buf[1] != 'I') { s = s[1:] } @@ -761,7 +761,7 @@ Returns: */ write_f64 :: proc(b: ^Builder, f: f64, fmt: byte, always_signed := false) -> (n: int) { buf: [384]byte - s := strconv.append_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f)) + s := strconv.write_float(buf[:], f64(f), fmt, 2*size_of(f), 8*size_of(f)) if !always_signed && (buf[0] == '+' && buf[1] != 'I') { s = s[1:] } @@ -782,7 +782,7 @@ Returns: */ write_u64 :: proc(b: ^Builder, i: u64, base: int = 10) -> (n: int) { buf: [32]byte - s := strconv.append_bits(buf[:], i, base, false, 64, strconv.digits, nil) + s := strconv.write_bits(buf[:], i, base, false, 64, strconv.digits, nil) return write_string(b, s) } /* @@ -800,7 +800,7 @@ Returns: */ write_i64 :: proc(b: ^Builder, i: i64, base: int = 10) -> (n: int) { buf: [32]byte - s := strconv.append_bits(buf[:], u64(i), base, true, 64, strconv.digits, nil) + s := strconv.write_bits(buf[:], u64(i), base, true, 64, strconv.digits, nil) return write_string(b, s) } /* diff --git a/core/strings/strings.odin b/core/strings/strings.odin index e99a1bfb4..ffa11f219 100644 --- a/core/strings/strings.odin +++ b/core/strings/strings.odin @@ -2,6 +2,7 @@ package strings import "base:intrinsics" +import "base:runtime" import "core:bytes" import "core:io" import "core:mem" @@ -25,26 +26,9 @@ Returns: clone :: proc(s: string, allocator := context.allocator, loc := #caller_location) -> (res: string, err: mem.Allocator_Error) #optional_allocator_error { c := make([]byte, len(s), allocator, loc) or_return copy(c, s) - return string(c[:len(s)]), nil + return string(c), nil } -/* -Clones a string safely (returns early with an allocation error on failure) - -*Allocates Using Provided Allocator* - -Inputs: -- s: The string to be cloned -- allocator: (default: context.allocator) -- loc: The caller location for debugging purposes (default: #caller_location) -Returns: -- res: The cloned string -- err: An allocator error if one occured, `nil` otherwise -*/ -@(deprecated="Prefer clone. It now returns an optional allocator error") -clone_safe :: proc(s: string, allocator := context.allocator, loc := #caller_location) -> (res: string, err: mem.Allocator_Error) { - return clone(s, allocator, loc) -} /* Clones a string and appends a null-byte to make it a cstring @@ -65,6 +49,7 @@ clone_to_cstring :: proc(s: string, allocator := context.allocator, loc := #call c[len(s)] = 0 return cstring(&c[0]), nil } + /* Transmutes a raw pointer into a string. Non-allocating. @@ -80,6 +65,7 @@ Returns: string_from_ptr :: proc(ptr: ^byte, len: int) -> (res: string) { return transmute(string)mem.Raw_String{ptr, len} } + /* Transmutes a raw pointer (null-terminated) into a string. Non-allocating. Searches for a null-byte from `0..<len`, otherwise `len` will be the end size @@ -98,20 +84,7 @@ string_from_null_terminated_ptr :: proc "contextless" (ptr: [^]byte, len: int) - s = truncate_to_byte(s, 0) return s } -/* -Gets the raw byte pointer for the start of a string `str` - -Inputs: -- str: The input string -Returns: -- res: A pointer to the start of the string's bytes -*/ -@(deprecated="Prefer the builtin raw_data.") -ptr_from_string :: proc(str: string) -> (res: ^byte) { - d := transmute(mem.Raw_String)str - return d.data -} /* Converts a string `str` to a cstring @@ -127,6 +100,7 @@ unsafe_string_to_cstring :: proc(str: string) -> (res: cstring) { d := transmute(mem.Raw_String)str return cstring(d.data) } + /* Truncates a string `str` at the first occurrence of char/byte `b` @@ -146,6 +120,7 @@ truncate_to_byte :: proc "contextless" (str: string, b: byte) -> (res: string) { } return str[:n] } + /* Truncates a string `str` at the first occurrence of rune `r` as a slice of the original, entire string if not found @@ -163,6 +138,7 @@ truncate_to_rune :: proc(str: string, r: rune) -> (res: string) { } return str[:n] } + /* Clones a byte array `s` and appends a null-byte @@ -183,6 +159,7 @@ clone_from_bytes :: proc(s: []byte, allocator := context.allocator, loc := #call c[len(s)] = 0 return string(c[:len(s)]), nil } + /* Clones a cstring `s` as a string @@ -200,6 +177,7 @@ Returns: clone_from_cstring :: proc(s: cstring, allocator := context.allocator, loc := #caller_location) -> (res: string, err: mem.Allocator_Error) #optional_allocator_error { return clone(string(s), allocator, loc) } + /* Clones a string from a byte pointer `ptr` and a byte length `len` @@ -221,6 +199,7 @@ clone_from_ptr :: proc(ptr: ^byte, len: int, allocator := context.allocator, loc s := string_from_ptr(ptr, len) return clone(s, allocator, loc) } + // Overloaded procedure to clone from a string, `[]byte`, `cstring` or a `^byte` + length clone_from :: proc{ clone, @@ -228,6 +207,7 @@ clone_from :: proc{ clone_from_cstring, clone_from_ptr, } + /* Clones a string from a null-terminated cstring `ptr` and a byte length `len` @@ -250,6 +230,7 @@ clone_from_cstring_bounded :: proc(ptr: cstring, len: int, allocator := context. s = truncate_to_byte(s, 0) return clone(s, allocator, loc) } + /* Compares two strings, returning a value representing which one comes first lexicographically. -1 for `lhs`; 1 for `rhs`, or 0 if they are equal. @@ -264,6 +245,7 @@ Returns: compare :: proc "contextless" (lhs, rhs: string) -> (result: int) { return mem.compare(transmute([]byte)lhs, transmute([]byte)rhs) } + /* Checks if rune `r` in the string `s` @@ -282,6 +264,7 @@ contains_rune :: proc(s: string, r: rune) -> (result: bool) { } return false } + /* Returns true when the string `substr` is contained inside the string `s` @@ -313,6 +296,7 @@ Output: contains :: proc(s, substr: string) -> (res: bool) { return index(s, substr) >= 0 } + /* Returns `true` when the string `s` contains any of the characters inside the string `chars` @@ -385,6 +369,7 @@ Output: rune_count :: proc(s: string) -> (res: int) { return utf8.rune_count_in_string(s) } + /* Returns whether the strings `u` and `v` are the same alpha characters, ignoring different casings Works with UTF-8 string content @@ -458,6 +443,7 @@ equal_fold :: proc(u, v: string) -> (res: bool) { return s == t } + /* Returns the prefix length common between strings `a` and `b` @@ -488,29 +474,58 @@ Output: 0 */ -prefix_length :: proc(a, b: string) -> (n: int) { - _len := min(len(a), len(b)) - - // Scan for matches including partial codepoints. - #no_bounds_check for n < _len && a[n] == b[n] { - n += 1 - } +prefix_length :: proc "contextless" (a, b: string) -> (n: int) { + RUNE_ERROR :: '\ufffd' + RUNE_SELF :: 0x80 + UTF_MAX :: 4 - // Now scan to ignore partial codepoints. - if n > 0 { - s := a[:n] - n = 0 - for { - r0, w := utf8.decode_rune(s[n:]) - if r0 != utf8.RUNE_ERROR { - n += w - } else { - break + n = runtime.memory_prefix_length(raw_data(a), raw_data(b), min(len(a), len(b))) + lim := max(n - UTF_MAX + 1, 0) + for l := n; l > lim; l -= 1 { + r, _ := runtime.string_decode_rune(a[l - 1:]) + if r != RUNE_ERROR { + if l > 0 && (a[l - 1] & 0xc0 == 0xc0) { + return l - 1 } + return l } } return } + +/* +Returns the common prefix between strings `a` and `b` + +Inputs: +- a: The first input string +- b: The second input string + +Returns: +- n: The string prefix common between strings `a` and `b` + +Example: + + import "core:fmt" + import "core:strings" + + common_prefix_example :: proc() { + fmt.println(strings.common_prefix("testing", "test")) + fmt.println(strings.common_prefix("testing", "te")) + fmt.println(strings.common_prefix("telephone", "te")) + } + +Output: + + test + te + te + + +*/ +common_prefix :: proc(a, b: string) -> string { + return a[:prefix_length(a, b)] +} + /* Determines if a string `s` starts with a given `prefix` @@ -632,24 +647,7 @@ join :: proc(a: []string, sep: string, allocator := context.allocator, loc := #c } return string(b), nil } -/* -Joins a slice of strings `a` with a `sep` string, returns an error on allocation failure -*Allocates Using Provided Allocator* - -Inputs: -- a: A slice of strings to join -- sep: The separator string -- allocator: (default is context.allocator) - -Returns: -- str: A combined string from the slice of strings `a` separated with the `sep` string -- err: An allocator error if one occured, `nil` otherwise -*/ -@(deprecated="Prefer join. It now returns an optional allocator error") -join_safe :: proc(a: []string, sep: string, allocator := context.allocator) -> (res: string, err: mem.Allocator_Error) { - return join(a, sep, allocator) -} /* Returns a combined string from the slice of strings `a` without a separator @@ -694,22 +692,6 @@ concatenate :: proc(a: []string, allocator := context.allocator, loc := #caller_ } return string(b), nil } -/* -Returns a combined string from the slice of strings `a` without a separator, or an error if allocation fails - -*Allocates Using Provided Allocator* - -Inputs: -- a: A slice of strings to concatenate -- allocator: (default is context.allocator) - -Returns: -The concatenated string, and an error if allocation fails -*/ -@(deprecated="Prefer concatenate. It now returns an optional allocator error") -concatenate_safe :: proc(a: []string, allocator := context.allocator) -> (res: string, err: mem.Allocator_Error) { - return concatenate(a, allocator) -} /* Returns a substring of the input string `s` with the specified rune offset and length @@ -787,7 +769,7 @@ Example: import "core:fmt" import "core:strings" - cut_example :: proc() { + cut_clone_example :: proc() { fmt.println(strings.cut_clone("some example text", 0, 4)) // -> "some" fmt.println(strings.cut_clone("some example text", 2, 2)) // -> "me" fmt.println(strings.cut_clone("some example text", 5, 7)) // -> "example" @@ -872,6 +854,7 @@ _split :: proc(s_, sep: string, sep_save, n_: int, allocator := context.allocato return res[:i+1], nil } + /* Splits a string into parts based on a separator. @@ -907,6 +890,7 @@ Output: split :: proc(s, sep: string, allocator := context.allocator) -> (res: []string, err: mem.Allocator_Error) #optional_allocator_error { return _split(s, sep, 0, -1, allocator) } + /* Splits a string into parts based on a separator. If n < count of seperators, the remainder of the string is returned in the last entry. @@ -943,6 +927,7 @@ Output: split_n :: proc(s, sep: string, n: int, allocator := context.allocator) -> (res: []string, err: mem.Allocator_Error) #optional_allocator_error { return _split(s, sep, 0, n, allocator) } + /* Splits a string into parts after the separator, retaining it in the substrings. @@ -978,6 +963,7 @@ Output: split_after :: proc(s, sep: string, allocator := context.allocator) -> (res: []string, err: mem.Allocator_Error) #optional_allocator_error { return _split(s, sep, len(sep), -1, allocator) } + /* Splits a string into a total of `n` parts after the separator. @@ -1014,6 +1000,7 @@ Output: split_after_n :: proc(s, sep: string, n: int, allocator := context.allocator) -> (res: []string, err: mem.Allocator_Error) #optional_allocator_error { return _split(s, sep, len(sep), n, allocator) } + /* Searches for the first occurrence of `sep` in the given string and returns the substring up to (but not including) the separator, as well as a boolean indicating success. @@ -1054,6 +1041,7 @@ _split_iterator :: proc(s: ^string, sep: string, sep_save: int) -> (res: string, } return } + /* Splits the input string by the byte separator in an iterator fashion. @@ -1100,6 +1088,7 @@ split_by_byte_iterator :: proc(s: ^string, sep: u8) -> (res: string, ok: bool) { } return } + /* Splits the input string by the separator string in an iterator fashion. @@ -1135,6 +1124,7 @@ Output: split_iterator :: proc(s: ^string, sep: string) -> (res: string, ok: bool) { return _split_iterator(s, sep, 0) } + /* Splits the input string after every separator string in an iterator fashion. @@ -1170,6 +1160,7 @@ Output: split_after_iterator :: proc(s: ^string, sep: string) -> (res: string, ok: bool) { return _split_iterator(s, sep, len(sep)) } + /* Trims the carriage return character from the end of the input string. @@ -1191,6 +1182,7 @@ _trim_cr :: proc(s: string) -> (res: string) { } return s } + /* Splits the input string at every line break `\n`. @@ -1228,6 +1220,7 @@ split_lines :: proc(s: string, allocator := context.allocator) -> (res: []string } return lines, nil } + /* Splits the input string at every line break `\n` for `n` parts. @@ -1268,6 +1261,7 @@ split_lines_n :: proc(s: string, n: int, allocator := context.allocator) -> (res } return lines, nil } + /* Splits the input string at every line break `\n` leaving the `\n` in the resulting strings. @@ -1307,6 +1301,7 @@ split_lines_after :: proc(s: string, allocator := context.allocator) -> (res: [] } return lines, nil } + /* Splits the input string at every line break `\n` leaving the `\n` in the resulting strings. Only runs for n parts. @@ -1348,6 +1343,7 @@ split_lines_after_n :: proc(s: string, n: int, allocator := context.allocator) - } return lines, nil } + /* Splits the input string at every line break `\n`. Returns the current split string every iteration until the string is consumed. @@ -1382,6 +1378,7 @@ split_lines_iterator :: proc(s: ^string) -> (line: string, ok: bool) { line = _split_iterator(s, sep, 0) or_return return _trim_cr(line), true } + /* Splits the input string at every line break `\n`. Returns the current split string with line breaks included every iteration until the string is consumed. @@ -1419,6 +1416,7 @@ split_lines_after_iterator :: proc(s: ^string) -> (line: string, ok: bool) { line = _split_iterator(s, sep, len(sep)) or_return return _trim_cr(line), true } + /* Returns the byte offset of the first byte `c` in the string s it finds, -1 when not found. NOTE: Can't find UTF-8 based runes. @@ -1453,6 +1451,7 @@ Output: index_byte :: proc "contextless" (s: string, c: byte) -> (res: int) { return #force_inline bytes.index_byte(transmute([]u8)s, c) } + /* Returns the byte offset of the last byte `c` in the string `s`, -1 when not found. @@ -1488,6 +1487,7 @@ Output: last_index_byte :: proc "contextless" (s: string, c: byte) -> (res: int) { return #force_inline bytes.last_index_byte(transmute([]u8)s, c) } + /* Returns the byte offset of the first rune `r` in the string `s` it finds, -1 when not found. Invalid runes return -1 @@ -1628,6 +1628,7 @@ index :: proc "contextless" (s, substr: string) -> (res: int) { } return -1 } + /* Returns the last byte offset of the string `substr` in the string `s`, -1 when not found. @@ -1705,6 +1706,7 @@ last_index :: proc(s, substr: string) -> (res: int) { } return -1 } + /* Returns the index of any first char of `chars` found in `s`, -1 if not found. @@ -1768,6 +1770,7 @@ index_any :: proc(s, chars: string) -> (res: int) { } return -1 } + /* Finds the last occurrence of any character in `chars` within `s`. Iterates in reverse. @@ -1849,6 +1852,7 @@ last_index_any :: proc(s, chars: string) -> (res: int) { } return -1 } + /* Finds the first occurrence of any substring in `substrs` within `s` @@ -1890,6 +1894,7 @@ index_multi :: proc(s: string, substrs: []string) -> (idx: int, width: int) { } return } + /* Counts the number of non-overlapping occurrences of `substr` in `s` @@ -1956,6 +1961,7 @@ count :: proc(s, substr: string) -> (res: int) { } return n } + /* Repeats the string `s` `count` times, concatenating the result @@ -2001,6 +2007,7 @@ repeat :: proc(s: string, count: int, allocator := context.allocator, loc := #ca } return string(b), nil } + /* Replaces all occurrences of `old` in `s` with `new` @@ -2034,9 +2041,11 @@ Output: zzzz true */ + replace_all :: proc(s, old, new: string, allocator := context.allocator) -> (output: string, was_allocation: bool) { return replace(s, old, new, -1, allocator) } + /* Replaces n instances of old in the string s with the new string @@ -2115,6 +2124,7 @@ replace :: proc(s, old, new: string, n: int, allocator := context.allocator, loc output = string(t[0:w]) return } + /* Removes the key string `n` times from the `s` string @@ -2153,6 +2163,7 @@ Output: remove :: proc(s, key: string, n: int, allocator := context.allocator) -> (output: string, was_allocation: bool) { return replace(s, key, "", n, allocator) } + /* Removes all the `key` string instances from the `s` string @@ -2188,6 +2199,7 @@ Output: remove_all :: proc(s, key: string, allocator := context.allocator) -> (output: string, was_allocation: bool) { return remove(s, key, -1, allocator) } + // Returns true if is an ASCII space character ('\t', '\n', '\v', '\f', '\r', ' ') @(private) _ascii_space := [256]bool{'\t' = true, '\n' = true, '\v' = true, '\f' = true, '\r' = true, ' ' = true} @@ -2291,6 +2303,7 @@ index_proc :: proc(s: string, p: proc(rune) -> bool, truth := true) -> (res: int } return -1 } + // Same as `index_proc`, but the procedure p takes a raw pointer for state index_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, state: rawptr, truth := true) -> (res: int) { for r, i in s { @@ -2300,6 +2313,7 @@ index_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, state: r } return -1 } + // Finds the index of the *last* rune in the string s for which the procedure p returns the same value as truth last_index_proc :: proc(s: string, p: proc(rune) -> bool, truth := true) -> (res: int) { // TODO(bill): Probably use Rabin-Karp Search @@ -2312,6 +2326,7 @@ last_index_proc :: proc(s: string, p: proc(rune) -> bool, truth := true) -> (res } return -1 } + // Same as `index_proc_with_state`, runs through the string in reverse last_index_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, state: rawptr, truth := true) -> (res: int) { // TODO(bill): Probably use Rabin-Karp Search @@ -2324,6 +2339,7 @@ last_index_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, sta } return -1 } + /* Trims the input string `s` from the left until the procedure `p` returns false @@ -2358,6 +2374,7 @@ trim_left_proc :: proc(s: string, p: proc(rune) -> bool) -> (res: string) { } return s[i:] } + /* Trims the input string `s` from the left until the procedure `p` with state returns false @@ -2376,6 +2393,7 @@ trim_left_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, stat } return s[i:] } + /* Trims the input string `s` from the right until the procedure `p` returns `false` @@ -2413,6 +2431,7 @@ trim_right_proc :: proc(s: string, p: proc(rune) -> bool) -> (res: string) { } return s[0:i] } + /* Trims the input string `s` from the right until the procedure `p` with state returns `false` @@ -2434,6 +2453,7 @@ trim_right_proc_with_state :: proc(s: string, p: proc(rawptr, rune) -> bool, sta } return s[0:i] } + // Procedure for `trim_*_proc` variants, which has a string rawptr cast + rune comparison is_in_cutset :: proc(state: rawptr, r: rune) -> (res: bool) { cutset := (^string)(state)^ @@ -2444,6 +2464,7 @@ is_in_cutset :: proc(state: rawptr, r: rune) -> (res: bool) { } return false } + /* Trims the cutset string from the `s` string @@ -2461,6 +2482,7 @@ trim_left :: proc(s: string, cutset: string) -> (res: string) { state := cutset return trim_left_proc_with_state(s, is_in_cutset, &state) } + /* Trims the cutset string from the `s` string from the right @@ -2478,6 +2500,7 @@ trim_right :: proc(s: string, cutset: string) -> (res: string) { state := cutset return trim_right_proc_with_state(s, is_in_cutset, &state) } + /* Trims the cutset string from the `s` string, both from left and right @@ -2491,6 +2514,7 @@ Returns: trim :: proc(s: string, cutset: string) -> (res: string) { return trim_right(trim_left(s, cutset), cutset) } + /* Trims until a valid non-space rune from the left, "\t\txyz\t\t" -> "xyz\t\t" @@ -2503,6 +2527,7 @@ Returns: trim_left_space :: proc(s: string) -> (res: string) { return trim_left_proc(s, is_space) } + /* Trims from the right until a valid non-space rune, "\t\txyz\t\t" -> "\t\txyz" @@ -2515,6 +2540,7 @@ Returns: trim_right_space :: proc(s: string) -> (res: string) { return trim_right_proc(s, is_space) } + /* Trims from both sides until a valid non-space rune, "\t\txyz\t\t" -> "xyz" @@ -2527,6 +2553,7 @@ Returns: trim_space :: proc(s: string) -> (res: string) { return trim_right_space(trim_left_space(s)) } + /* Trims null runes from the left, "\x00\x00testing\x00\x00" -> "testing\x00\x00" @@ -2539,6 +2566,7 @@ Returns: trim_left_null :: proc(s: string) -> (res: string) { return trim_left_proc(s, is_null) } + /* Trims null runes from the right, "\x00\x00testing\x00\x00" -> "\x00\x00testing" @@ -2551,6 +2579,7 @@ Returns: trim_right_null :: proc(s: string) -> (res: string) { return trim_right_proc(s, is_null) } + /* Trims null runes from both sides, "\x00\x00testing\x00\x00" -> "testing" @@ -2562,6 +2591,7 @@ Returns: trim_null :: proc(s: string) -> (res: string) { return trim_right_null(trim_left_null(s)) } + /* Trims a `prefix` string from the start of the `s` string and returns the trimmed string @@ -2594,6 +2624,7 @@ trim_prefix :: proc(s, prefix: string) -> (res: string) { } return s } + /* Trims a `suffix` string from the end of the `s` string and returns the trimmed string @@ -2626,6 +2657,7 @@ trim_suffix :: proc(s, suffix: string) -> (res: string) { } return s } + /* Splits the input string `s` by all possible `substrs` and returns an allocated array of strings @@ -2698,6 +2730,7 @@ split_multi :: proc(s: string, substrs: []string, allocator := context.allocator assert(len(results) == n) return results[:], nil } + /* Splits the input string `s` by all possible `substrs` in an iterator fashion. The full string is returned if no match. @@ -2757,6 +2790,7 @@ split_multi_iterate :: proc(it: ^string, substrs: []string) -> (res: string, ok: ok = true return } + /* Replaces invalid UTF-8 characters in the input string with a specified replacement string. Adjacent invalid bytes are only replaced once. @@ -2817,6 +2851,7 @@ scrub :: proc(s: string, replacement: string, allocator := context.allocator) -> return to_string(b), nil } + /* Reverses the input string `s` @@ -2860,6 +2895,7 @@ reverse :: proc(s: string, allocator := context.allocator, loc := #caller_locati } return string(buf), nil } + /* Expands the input string by replacing tab characters with spaces to align to a specified tab size @@ -2891,6 +2927,7 @@ Output: abc1 abc2 abc3 */ + expand_tabs :: proc(s: string, tab_size: int, allocator := context.allocator) -> (res: string, err: mem.Allocator_Error) #optional_allocator_error { if tab_size <= 0 { panic("tab size must be positive") @@ -2932,6 +2969,7 @@ expand_tabs :: proc(s: string, tab_size: int, allocator := context.allocator) -> return to_string(b), nil } + /* Splits the input string `str` by the separator `sep` string and returns 3 parts. The values are slices of the original string. @@ -2982,8 +3020,10 @@ partition :: proc(str, sep: string) -> (head, match, tail: string) { tail = str[i+len(sep):] return } + // Alias for centre_justify center_justify :: centre_justify // NOTE(bill): Because Americans exist + /* Centers the input string within a field of specified length by adding pad string on both sides, if its length is less than the target length. @@ -3019,6 +3059,7 @@ centre_justify :: proc(str: string, length: int, pad: string, allocator := conte return to_string(b), nil } + /* Left-justifies the input string within a field of specified length by adding pad string on the right side, if its length is less than the target length. @@ -3044,7 +3085,6 @@ left_justify :: proc(str: string, length: int, pad: string, allocator := context pad_len := rune_count(pad) b: Builder - builder_init(&b, allocator) builder_init(&b, 0, len(str) + (remains/pad_len + 1)*len(pad), allocator) or_return w := to_writer(&b) @@ -3054,6 +3094,7 @@ left_justify :: proc(str: string, length: int, pad: string, allocator := context return to_string(b), nil } + /* Right-justifies the input string within a field of specified length by adding pad string on the left side, if its length is less than the target length. @@ -3079,7 +3120,6 @@ right_justify :: proc(str: string, length: int, pad: string, allocator := contex pad_len := rune_count(pad) b: Builder - builder_init(&b, allocator) builder_init(&b, 0, len(str) + (remains/pad_len + 1)*len(pad), allocator) or_return w := to_writer(&b) @@ -3089,6 +3129,7 @@ right_justify :: proc(str: string, length: int, pad: string, allocator := contex return to_string(b), nil } + /* Writes a given pad string a specified number of times to an `io.Writer` @@ -3115,6 +3156,7 @@ write_pad_string :: proc(w: io.Writer, pad: string, pad_len, remains: int) { p = p[width:] } } + /* Splits a string into a slice of substrings at each instance of one or more consecutive white space characters, as defined by `unicode.is_space` @@ -3176,6 +3218,7 @@ fields :: proc(s: string, allocator := context.allocator, loc := #caller_locatio } return a, nil } + /* Splits a string into a slice of substrings at each run of unicode code points `r` satisfying the predicate `f(r)` @@ -3218,6 +3261,7 @@ fields_proc :: proc(s: string, f: proc(rune) -> bool, allocator := context.alloc return substrings[:], nil } + /* Retrieves the first non-space substring from a mutable string reference and advances the reference. `s` is advanced from any space after the substring, or be an empty string if the substring was the remaining characters @@ -3256,6 +3300,7 @@ fields_iterator :: proc(s: ^string) -> (field: string, ok: bool) { s^ = s[len(s):] return } + /* Computes the Levenshtein edit distance between two strings @@ -3433,4 +3478,4 @@ substring_to :: proc(s: string, rune_end: int) -> (sub: string, ok: bool) { } return internal_substring(s, -1, rune_end) -} +}
\ No newline at end of file diff --git a/core/sync/chan/chan.odin b/core/sync/chan/chan.odin index c470d15f3..05312e5a2 100644 --- a/core/sync/chan/chan.odin +++ b/core/sync/chan/chan.odin @@ -7,16 +7,70 @@ import "core:mem" import "core:sync" import "core:math/rand" +when ODIN_TEST { +/* +Hook for testing _try_select_raw allowing the test harness to manipulate the +channels prior to the select actually operating on them. +*/ +__try_select_raw_pause : proc() = nil +} + +/* +Determines what operations `Chan` supports. +*/ Direction :: enum { Send = -1, Both = 0, Recv = +1, } +/* +A typed wrapper around `Raw_Chan` which should be used +preferably. + +Note: all procedures accepting `Raw_Chan` also accept `Chan`. + +**Inputs** +- `$T`: The type of the messages +- `Direction`: what `Direction` the channel supports + +Example: + + import "core:sync/chan" + + chan_example :: proc() { + // Create an unbuffered channel with messages of type int, + // supporting both sending and receiving. + // Creating unidirectional channels, although possible, is useless. + c, _ := chan.create(chan.Chan(int), context.allocator) + defer chan.destroy(c) + + // This channel can now only be used for receiving messages + recv_only_channel: chan.Chan(int, .Recv) = chan.as_recv(c) + // This channel can now only be used for sending messages + send_only_channel: chan.Chan(int, .Send) = chan.as_send(c) + } +*/ Chan :: struct($T: typeid, $D: Direction = Direction.Both) { #subtype impl: ^Raw_Chan `fmt:"-"`, } +/* +`Raw_Chan` allows for thread-safe communication using fixed-size messages. +This is the low-level implementation of `Chan`, which does not include +the concept of Direction. + +Example: + + import "core:sync/chan" + + raw_chan_example :: proc() { + // Create an unbuffered channel with messages of type int, + c, _ := chan.create_raw(size_of(int), align_of(int), context.allocator) + defer chan.destroy(c) + } + +*/ Raw_Chan :: struct { // Shared allocator: runtime.Allocator, @@ -29,6 +83,8 @@ Raw_Chan :: struct { r_waiting: int, // guarded by `mutex` w_waiting: int, // guarded by `mutex` + did_read: bool, // lets a sender know if the value was read + // Buffered queue: ^Raw_Queue, @@ -36,12 +92,66 @@ Raw_Chan :: struct { unbuffered_data: rawptr, } +/* +Creates a buffered or unbuffered `Chan` instance. + +*Allocates Using Provided Allocator* + +**Inputs** +- `$C`: Type of `Chan` to create +- [`cap`: The capacity of the channel] omit for creating unbuffered channels +- `allocator`: The allocator to use + +**Returns**: +- The initialized `Chan` +- An `Allocator_Error` + +Example: + + import "core:sync/chan" + + create_example :: proc() { + unbuffered: chan.Chan(int) + buffered: chan.Chan(int) + err: runtime.Allocator_Error + + unbuffered, err = chan.create(chan.Chan(int), context.allocator) + assert(err == .None) + defer chan.destroy(unbuffered) + buffered, err = chan.create(chan.Chan(int), 10, context.allocator) + assert(err == .None) + defer chan.destroy(buffered) + } +*/ create :: proc{ create_unbuffered, create_buffered, } +/* +Creates an unbuffered version of the specified `Chan` type. + +*Allocates Using Provided Allocator* + +**Inputs** +- `$C`: Type of `Chan` to create +- `allocator`: The allocator to use + +**Returns**: +- The initialized `Chan` +- An `Allocator_Error` + +Example: + + import "core:sync/chan" + + create_unbuffered_example :: proc() { + c, err := chan.create_unbuffered(chan.Chan(int), context.allocator) + assert(err == .None) + defer chan.destroy(c) + } +*/ @(require_results) create_unbuffered :: proc($C: typeid/Chan($T), allocator: runtime.Allocator) -> (c: C, err: runtime.Allocator_Error) where size_of(T) <= int(max(u16)) { @@ -49,6 +159,30 @@ create_unbuffered :: proc($C: typeid/Chan($T), allocator: runtime.Allocator) -> return } +/* +Creates a buffered version of the specified `Chan` type. + +*Allocates Using Provided Allocator* + +**Inputs** +- `$C`: Type of `Chan` to create +- `cap`: The capacity of the channel +- `allocator`: The allocator to use + +**Returns**: +- The initialized `Chan` +- An `Allocator_Error` + +Example: + + import "core:sync/chan" + + create_buffered_example :: proc() { + c, err := chan.create_buffered(chan.Chan(int), 10, context.allocator) + assert(err == .None) + defer chan.destroy(c) + } +*/ @(require_results) create_buffered :: proc($C: typeid/Chan($T), #any_int cap: int, allocator: runtime.Allocator) -> (c: C, err: runtime.Allocator_Error) where size_of(T) <= int(max(u16)) { @@ -56,11 +190,70 @@ create_buffered :: proc($C: typeid/Chan($T), #any_int cap: int, allocator: runti return } +/* +Creates a buffered or unbuffered `Raw_Chan` for messages of the specified +size and alignment. + +*Allocates Using Provided Allocator* + +**Inputs** +- `msg_size`: The size of the messages the messages being sent +- `msg_alignment`: The alignment of the messages being sent +- [`cap`: The capacity of the channel] omit for creating unbuffered channels +- `allocator`: The allocator to use + +**Returns**: +- The initialized `Raw_Chan` +- An `Allocator_Error` + +Example: + + import "core:sync/chan" + + create_raw_example :: proc() { + unbuffered: ^chan.Raw_Chan + buffered: ^chan.Raw_Chan + err: runtime.Allocator_Error + + unbuffered, err = chan.create_raw(size_of(int), align_of(int), context.allocator) + assert(err == .None) + defer chan.destroy(unbuffered) + + buffered, err = chan.create_raw(size_of(int), align_of(int), 10, context.allocator) + assert(err == .None) + defer chan.destroy(buffered) + } +*/ create_raw :: proc{ create_raw_unbuffered, create_raw_buffered, } +/* +Creates an unbuffered `Raw_Chan` for messages of the specified +size and alignment. + +*Allocates Using Provided Allocator* + +**Inputs** +- `msg_size`: The size of the messages the messages being sent +- `msg_alignment`: The alignment of the messages being sent +- `allocator`: The allocator to use + +**Returns**: +- The initialized `Raw_Chan` +- An `Allocator_Error` + +Example: + + import "core:sync/chan" + + create_raw_unbuffered_example :: proc() { + unbuffered, err := chan.create_raw(size_of(int), align_of(int), context.allocator) + assert(err == .None) + defer chan.destroy(unbuffered) + } +*/ @(require_results) create_raw_unbuffered :: proc(#any_int msg_size, msg_alignment: int, allocator: runtime.Allocator) -> (c: ^Raw_Chan, err: runtime.Allocator_Error) { assert(msg_size <= int(max(u16))) @@ -80,6 +273,32 @@ create_raw_unbuffered :: proc(#any_int msg_size, msg_alignment: int, allocator: return } +/* +Creates a buffered `Raw_Chan` for messages of the specified +size and alignment. + +*Allocates Using Provided Allocator* + +**Inputs** +- `msg_size`: The size of the messages the messages being sent +- `msg_alignment`: The alignment of the messages being sent +- `cap`: The capacity of the channel +- `allocator`: The allocator to use + +**Returns**: +- The initialized `Raw_Chan` +- An `Allocator_Error` + +Example: + + import "core:sync/chan" + + create_raw_unbuffered_example :: proc() { + c, err := chan.create_raw_buffered(size_of(int), align_of(int), 10, context.allocator) + assert(err == .None) + defer chan.destroy(c) + } +*/ @(require_results) create_raw_buffered :: proc(#any_int msg_size, msg_alignment: int, #any_int cap: int, allocator: runtime.Allocator) -> (c: ^Raw_Chan, err: runtime.Allocator_Error) { assert(msg_size <= int(max(u16))) @@ -110,6 +329,16 @@ create_raw_buffered :: proc(#any_int msg_size, msg_alignment: int, #any_int cap: return } + +/* +Destroys the Channel. + +**Inputs** +- `c`: The channel to destroy + +**Returns**: +- An `Allocator_Error` +*/ destroy :: proc(c: ^Raw_Chan) -> (err: runtime.Allocator_Error) { if c != nil { allocator := c.allocator @@ -118,22 +347,142 @@ destroy :: proc(c: ^Raw_Chan) -> (err: runtime.Allocator_Error) { return } +/* +Creates a version of a channel that can only be used for sending +not receiving. + +**Inputs** +- `c`: The channel + +**Returns**: +- An `Allocator_Error` + +Example: + + import "core:sync/chan" + + as_send_example :: proc() { + // this procedure takes a channel that can only + // be used for sending not receiving. + producer :: proc(c: chan.Chan(int, .Send)) { + chan.send(c, 112) + + // compile-time error: + // value, ok := chan.recv(c) + } + + c, err := chan.create(chan.Chan(int), 1, context.allocator) + assert(err == .None) + defer chan.destroy(c) + + producer(chan.as_send(c)) + } +*/ @(require_results) as_send :: #force_inline proc "contextless" (c: $C/Chan($T, $D)) -> (s: Chan(T, .Send)) where C.D <= .Both { return transmute(type_of(s))c } + +/* +Creates a version of a channel that can only be used for receiving +not sending. + +**Inputs** +- `c`: The channel + +**Returns**: +- An `Allocator_Error` + +Example: + + import "core:sync/chan" + + as_recv_example :: proc() { + consumer :: proc(c: chan.Chan(int, .Recv)) { + value, ok := chan.recv(c) + + // compile-time error: + // chan.send(c, 22) + } + + c, err := chan.create(chan.Chan(int), 1, context.allocator) + assert(err == .None) + defer chan.destroy(c) + + chan.send(c, 112) + consumer(chan.as_recv(c)) + } +*/ @(require_results) as_recv :: #force_inline proc "contextless" (c: $C/Chan($T, $D)) -> (r: Chan(T, .Recv)) where C.D >= .Both { return transmute(type_of(r))c } +/* +Sends the specified message, blocking the current thread if: +- the channel is unbuffered +- the channel's buffer is full +until the channel is being read from or the channel is closed. `send` will +return `false` when attempting to send on an already closed channel. + +**Inputs** +- `c`: The channel +- `data`: The message to send + +**Returns** +- `true` if the message was sent, `false` when the channel was already closed + +Example: + + import "core:sync/chan" + send_example :: proc() { + c, err := chan.create(chan.Chan(int), 1, context.allocator) + assert(err == .None) + defer chan.destroy(c) + + assert(chan.send(c, 2)) + + // this would block since the channel has a buffersize of 1 + // assert(chan.send(c, 2)) + + // sending on a closed channel returns false + chan.close(c) + assert(! chan.send(c, 2)) + } +*/ send :: proc "contextless" (c: $C/Chan($T, $D), data: T) -> (ok: bool) where C.D <= .Both { data := data ok = send_raw(c, &data) return } +/* +Tries sending the specified message which is: +- blocking: given the channel is unbuffered +- non-blocking: given the channel is buffered + +**Inputs** +- `c`: The channel +- `data`: The message to send + +**Returns** +- `true` if the message was sent, `false` when the channel was +already closed or the channel's buffer was full + +Example: + + import "core:sync/chan" + + try_send_example :: proc() { + c, err := chan.create(chan.Chan(int), 1, context.allocator) + assert(err == .None) + defer chan.destroy(c) + + assert(chan.try_send(c, 2), "there is enough space") + assert(!chan.try_send(c, 2), "the buffer is already full") + } +*/ @(require_results) try_send :: proc "contextless" (c: $C/Chan($T, $D), data: T) -> (ok: bool) where C.D <= .Both { data := data @@ -141,6 +490,44 @@ try_send :: proc "contextless" (c: $C/Chan($T, $D), data: T) -> (ok: bool) where return } +/* +Reads a message from the channel, blocking the current thread if: +- the channel is unbuffered +- the channel's buffer is empty +until the channel is being written to or the channel is closed. `recv` will +return `false` when attempting to receive a message on an already closed +channel. + +**Inputs** +- `c`: The channel + +**Returns** +- The message +- `true` if a message was received, `false` when the channel was already closed + +Example: + + import "core:sync/chan" + + recv_example :: proc() { + c, err := chan.create(chan.Chan(int), 1, context.allocator) + assert(err == .None) + defer chan.destroy(c) + + assert(chan.send(c, 2)) + + value, ok := chan.recv(c) + assert(ok, "the value was received") + + // this would block since the channel is now empty + // value, ok = chan.recv(c) + + // reading from a closed channel returns false + chan.close(c) + value, ok = chan.recv(c) + assert(!ok, "the channel is closed") + } +*/ @(require_results) recv :: proc "contextless" (c: $C/Chan($T)) -> (data: T, ok: bool) where C.D >= .Both { ok = recv_raw(c, &data) @@ -148,6 +535,29 @@ recv :: proc "contextless" (c: $C/Chan($T)) -> (data: T, ok: bool) where C.D >= } +/* +Tries reading a message from the channel in a non-blocking fashion. + +**Inputs** +- `c`: The channel + +**Returns** +- The message +- `true` if a message was received, `false` when the channel was already closed or no message was available + +Example: + + import "core:sync/chan" + + try_recv_example :: proc() { + c, err := chan.create(chan.Chan(int), context.allocator) + assert(err == .None) + defer chan.destroy(c) + + _, ok := chan.try_recv(c) + assert(!ok, "there is not value to read") + } +*/ @(require_results) try_recv :: proc "contextless" (c: $C/Chan($T)) -> (data: T, ok: bool) where C.D >= .Both { ok = try_recv_raw(c, &data) @@ -155,6 +565,43 @@ try_recv :: proc "contextless" (c: $C/Chan($T)) -> (data: T, ok: bool) where C.D } +/* +Sends the specified message, blocking the current thread if: +- the channel is unbuffered +- the channel's buffer is full +until the channel is being read from or the channel is closed. `send_raw` will +return `false` when attempting to send on an already closed channel. + +Note: The message referenced by `msg_out` must match the size +and alignment used when the `Raw_Chan` was created. + +**Inputs** +- `c`: The channel +- `msg_out`: Pointer to the data to send + +**Returns** +- `true` if the message was sent, `false` when the channel was already closed + +Example: + + import "core:sync/chan" + + send_raw_example :: proc() { + c, err := chan.create_raw(size_of(int), align_of(int), 1, context.allocator) + assert(err == .None) + defer chan.destroy(c) + + value := 2 + assert(chan.send_raw(c, &value)) + + // this would block since the channel has a buffersize of 1 + // assert(chan.send_raw(c, &value)) + + // sending on a closed channel returns false + chan.close(c) + assert(! chan.send_raw(c, &value)) + } +*/ @(require_results) send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool) { if c == nil { @@ -183,17 +630,68 @@ send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool) { return false } + c.did_read = false + defer c.did_read = false + mem.copy(c.unbuffered_data, msg_in, int(c.msg_size)) + c.w_waiting += 1 + if c.r_waiting > 0 { sync.signal(&c.r_cond) } + sync.wait(&c.w_cond, &c.mutex) + + if c.closed && !c.did_read { + return false + } + ok = true } return } +/* +Reads a message from the channel, blocking the current thread if: +- the channel is unbuffered +- the channel's buffer is empty +until the channel is being written to or the channel is closed. `recv_raw` +will return `false` when attempting to receive a message on an already closed +channel. + +Note: The location pointed to by `msg_out` must match the size +and alignment used when the `Raw_Chan` was created. + +**Inputs** +- `c`: The channel +- `msg_out`: Pointer to where the message should be stored + +**Returns** +- `true` if a message was received, `false` when the channel was already closed + +Example: + + import "core:sync/chan" + + recv_raw_example :: proc() { + c, err := chan.create_raw(size_of(int), align_of(int), 1, context.allocator) + assert(err == .None) + defer chan.destroy(c) + + value := 2 + assert(chan.send_raw(c, &value)) + + assert(chan.recv_raw(c, &value)) + + // this would block since the channel is now empty + // assert(chan.recv_raw(c, &value)) + + // reading from a closed channel returns false + chan.close(c) + assert(! chan.recv_raw(c, &value)) + } +*/ @(require_results) recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> (ok: bool) { if c == nil { @@ -223,8 +721,7 @@ recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> (ok: bool) { } else if c.unbuffered_data != nil { // unbuffered sync.guard(&c.mutex) - for !c.closed && - c.w_waiting == 0 { + for !c.closed && c.w_waiting == 0 { c.r_waiting += 1 sync.wait(&c.r_cond, &c.mutex) c.r_waiting -= 1 @@ -237,6 +734,7 @@ recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> (ok: bool) { mem.copy(msg_out, c.unbuffered_data, int(c.msg_size)) c.w_waiting -= 1 + c.did_read = true sync.signal(&c.w_cond) ok = true } @@ -244,6 +742,36 @@ recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> (ok: bool) { } +/* +Tries sending the specified message which is: +- blocking: given the channel is unbuffered +- non-blocking: given the channel is buffered + +Note: The message referenced by `msg_out` must match the size +and alignment used when the `Raw_Chan` was created. + +**Inputs** +- `c`: the channel +- `msg_out`: pointer to the data to send + +**Returns** +- `true` if the message was sent, `false` when the channel was +already closed or the channel's buffer was full + +Example: + + import "core:sync/chan" + + try_send_raw_example :: proc() { + c, err := chan.create_raw(size_of(int), align_of(int), 1, context.allocator) + assert(err == .None) + defer chan.destroy(c) + + value := 2 + assert(chan.try_send_raw(c, &value), "there is enough space") + assert(!chan.try_send_raw(c, &value), "the buffer is already full") + } +*/ @(require_results) try_send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool) { if c == nil { @@ -266,7 +794,7 @@ try_send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool) } else if c.unbuffered_data != nil { // unbuffered sync.guard(&c.mutex) - if c.closed { + if c.closed || c.r_waiting - c.w_waiting <= 0 { return false } @@ -281,6 +809,32 @@ try_send_raw :: proc "contextless" (c: ^Raw_Chan, msg_in: rawptr) -> (ok: bool) return } +/* +Reads a message from the channel if one is available. + +Note: The location pointed to by `msg_out` must match the size +and alignment used when the `Raw_Chan` was created. + +**Inputs** +- `c`: The channel +- `msg_out`: Pointer to where the message should be stored + +**Returns** +- `true` if a message was received, `false` when the channel was already closed or no message was available + +Example: + + import "core:sync/chan" + + try_recv_raw_example :: proc() { + c, err := chan.create_raw(size_of(int), align_of(int), context.allocator) + assert(err == .None) + defer chan.destroy(c) + + value: int + assert(!chan.try_recv_raw(c, &value)) + } +*/ @(require_results) try_recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> bool { if c == nil { @@ -304,7 +858,7 @@ try_recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> bool { } else if c.unbuffered_data != nil { // unbuffered sync.guard(&c.mutex) - if c.closed || c.w_waiting == 0 { + if c.closed || c.w_waiting - c.r_waiting <= 0 { return false } @@ -319,16 +873,85 @@ try_recv_raw :: proc "contextless" (c: ^Raw_Chan, msg_out: rawptr) -> bool { +/* +Checks if the given channel is buffered. + +**Inputs** +- `c`: The channel + +**Returns**: +- `true` if the channel is buffered, `false` otherwise + +Example: + + import "core:sync/chan" + + is_buffered_example :: proc() { + c, _ := chan.create(chan.Chan(int), 1, context.allocator) + defer chan.destroy(c) + assert(chan.is_buffered(c)) + } +*/ @(require_results) is_buffered :: proc "contextless" (c: ^Raw_Chan) -> bool { return c != nil && c.queue != nil } +/* +Checks if the given channel is unbuffered. + +**Inputs** +- `c`: The channel + +**Returns**: +- `true` if the channel is unbuffered, `false` otherwise + +Example: + + import "core:sync/chan" + + is_buffered_example :: proc() { + c, _ := chan.create(chan.Chan(int), context.allocator) + defer chan.destroy(c) + assert(chan.is_unbuffered(c)) + } +*/ @(require_results) is_unbuffered :: proc "contextless" (c: ^Raw_Chan) -> bool { return c != nil && c.unbuffered_data != nil } +/* +Returns the number of elements currently in the channel. + +Note: Unbuffered channels will always return `0` +because they cannot hold elements. + +**Inputs** +- `c`: The channel + +**Returns**: +- Number of elements + +Example: + + import "core:sync/chan" + import "core:fmt" + + len_example :: proc() { + c, _ := chan.create(chan.Chan(int), 2, context.allocator) + defer chan.destroy(c) + + fmt.println(chan.len(c)) + assert(chan.send(c, 1)) // add an element + fmt.println(chan.len(c)) + } + +Output: + + 0 + 1 +*/ @(require_results) len :: proc "contextless" (c: ^Raw_Chan) -> int { if c != nil && c.queue != nil { @@ -338,6 +961,34 @@ len :: proc "contextless" (c: ^Raw_Chan) -> int { return 0 } +/* +Returns the number of elements the channel could hold. + +Note: Unbuffered channels will always return `0` +because they cannot hold elements. + +**Inputs** +- `c`: The channel + +**Returns**: +- Number of elements + +Example: + + import "core:sync/chan" + import "core:fmt" + + cap_example :: proc() { + c, _ := chan.create(chan.Chan(int), 2, context.allocator) + defer chan.destroy(c) + + fmt.println(chan.cap(c)) + } + +Output: + + 2 +*/ @(require_results) cap :: proc "contextless" (c: ^Raw_Chan) -> int { if c != nil && c.queue != nil { @@ -347,6 +998,36 @@ cap :: proc "contextless" (c: ^Raw_Chan) -> int { return 0 } +/* +Closes the channel, preventing new messages from being added. + +**Inputs** +- `c`: The channel + +**Returns**: +- `true` if the channel was closed by this operation, `false` if it was already closed + +Example: + + import "core:sync/chan" + + close_example :: proc() { + c, _ := chan.create(chan.Chan(int), 2, context.allocator) + defer chan.destroy(c) + + // Sending a message to an open channel + assert(chan.send(c, 1), "allowed to send") + + // Closing the channel successfully + assert(chan.close(c), "successfully closed") + + // Trying to send a message after the channel is closed (should fail) + assert(!chan.send(c, 1), "not allowed to send after close") + + // Trying to close the channel again (should fail since it's already closed) + assert(!chan.close(c), "was already closed") + } +*/ close :: proc "contextless" (c: ^Raw_Chan) -> bool { if c == nil { return false @@ -361,6 +1042,15 @@ close :: proc "contextless" (c: ^Raw_Chan) -> bool { return true } +/* +Returns if the channel is closed or not + +**Inputs** +- `c`: The channel + +**Returns**: +- `true` if the channel is closed, `false` otherwise +*/ @(require_results) is_closed :: proc "contextless" (c: ^Raw_Chan) -> bool { if c == nil { @@ -370,9 +1060,230 @@ is_closed :: proc "contextless" (c: ^Raw_Chan) -> bool { return bool(c.closed) } +/* +Returns whether a message can be read without blocking the current +thread. Specifically, it checks if the channel is buffered and not full, +or if there is already a writer attempting to send a message. + +**Inputs** +- `c`: The channel + +**Returns** +- `true` if a message can be read, `false` otherwise + +Example: + + import "core:sync/chan" + + can_recv_example :: proc() { + c, err := chan.create(chan.Chan(int), 1, context.allocator) + assert(err == .None) + defer chan.destroy(c) + + assert(!chan.can_recv(c), "the cannel is empty") + assert(chan.send(c, 2)) + assert(chan.can_recv(c), "there is message to read") + } +*/ +@(require_results) +can_recv :: proc "contextless" (c: ^Raw_Chan) -> bool { + sync.guard(&c.mutex) + if is_buffered(c) { + return c.queue.len > 0 + } + return c.w_waiting - c.r_waiting > 0 +} + + +/* +Returns whether a message can be sent without blocking the current +thread. Specifically, it checks if the channel is buffered and not full, +or if there is already a reader waiting for a message. + +**Inputs** +- `c`: The channel + +**Returns** +- `true` if a message can be sent, `false` otherwise + +Example: + + import "core:sync/chan" + + can_send_example :: proc() { + c, err := chan.create(chan.Chan(int), 1, context.allocator) + assert(err == .None) + defer chan.destroy(c) + + assert(chan.can_send(c), "the channel's buffer is not full") + assert(chan.send(c, 2)) + assert(!chan.can_send(c), "the channel's buffer is full") + } +*/ +@(require_results) +can_send :: proc "contextless" (c: ^Raw_Chan) -> bool { + sync.guard(&c.mutex) + if is_buffered(c) { + return c.queue.len < c.queue.cap + } + return c.r_waiting - c.w_waiting > 0 +} + +/* +Specifies the direction of the selected channel. +*/ +Select_Status :: enum { + None, + Recv, + Send, +} + + +/* +Attempts to either send or receive messages on the specified channels without blocking. + +`try_select_raw` first identifies which channels have messages ready to be received +and which are available for sending. It then randomly selects one operation +(either a send or receive) to perform. + +If no channels have messages ready, the procedure is a noop. + +Note: Each message in `send_msgs` corresponds to the send channel at the same index in `sends`. +If the message is nil, corresponding send channel will be skipped. + +**Inputs** +- `recv`: A slice of channels to read from +- `sends`: A slice of channels to send messages on +- `send_msgs`: A slice of messages to send +- `recv_out`: A pointer to the location where, when receiving, the message should be stored + +**Returns** +- Position of the available channel which was used for receiving or sending +- `true` if sending/receiving was successfull, `false` if the channel was closed or no channel was available + +Example: + + import "core:sync/chan" + import "core:fmt" + + select_raw_example :: proc() { + c, err := chan.create(chan.Chan(int), 1, context.allocator) + assert(err == .None) + defer chan.destroy(c) + + // sending value '1' on the channel + value1 := 1 + msgs := [?]rawptr{&value1} + send_chans := [?]^chan.Raw_Chan{c} + + // for simplicity the same channel used for sending is also used for receiving + receive_chans := [?]^chan.Raw_Chan{c} + // where the value from the read should be stored + received_value: int + + idx, ok := chan.try_select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value) + fmt.println("SELECT: ", idx, ok) + fmt.println("RECEIVED VALUE ", received_value) + + idx, ok = chan.try_select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value) + fmt.println("SELECT: ", idx, ok) + fmt.println("RECEIVED VALUE ", received_value) + + // closing of a channel also affects the select operation + chan.close(c) + + idx, ok = chan.try_select_raw(receive_chans[:], send_chans[:], msgs[:], &received_value) + fmt.println("SELECT: ", idx, ok) + } + +Output: + + SELECT: 0 true + RECEIVED VALUE 0 + SELECT: 0 true + RECEIVED VALUE 1 + SELECT: 0 false + +*/ +@(require_results) +try_select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: []rawptr, recv_out: rawptr) -> (select_idx: int, status: Select_Status) #no_bounds_check { + Select_Op :: struct { + idx: int, // local to the slice that was given + is_recv: bool, + } + + candidate_count := builtin.len(recvs)+builtin.len(sends) + candidates := ([^]Select_Op)(intrinsics.alloca(candidate_count*size_of(Select_Op), align_of(Select_Op))) + + try_loop: for { + count := 0 + + for c, i in recvs { + if can_recv(c) { + candidates[count] = { + is_recv = true, + idx = i, + } + count += 1 + } + } + + for c, i in sends { + if i > builtin.len(send_msgs)-1 || send_msgs[i] == nil { + continue + } + if can_send(c) { + candidates[count] = { + is_recv = false, + idx = i, + } + count += 1 + } + } + + if count == 0 { + return -1, .None + } + + when ODIN_TEST { + if __try_select_raw_pause != nil { + __try_select_raw_pause() + } + } + + candidate_idx := rand.int_max(count) if count > 0 else 0 + + sel := candidates[candidate_idx] + if sel.is_recv { + status = .Recv + if !try_recv_raw(recvs[sel.idx], recv_out) { + continue try_loop + } + } else { + status = .Send + if !try_send_raw(sends[sel.idx], send_msgs[sel.idx]) { + continue try_loop + } + } + + return sel.idx, status + } +} +@(require_results, deprecated = "use try_select_raw") +select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: []rawptr, recv_out: rawptr) -> (select_idx: int, status: Select_Status) #no_bounds_check { + return try_select_raw(recvs, sends, send_msgs, recv_out) +} +/* +`Raw_Queue` is a non-thread-safe queue implementation designed to store messages +of fixed size and alignment. +Note: For most use cases, it is recommended to use `core:container/queue` instead, +as `Raw_Queue` is used internally by `Raw_Chan` and may not provide the desired +level of convenience for typical applications. +*/ +@(private) Raw_Queue :: struct { data: [^]byte, len: int, @@ -381,6 +1292,28 @@ Raw_Queue :: struct { size: int, // element size } +/* +Initializes a `Raw_Queue` + +**Inputs** +- `q`: A pointert to the `Raw_Queue` to initialize +- `data`: The pointer to backing slice storing the messages +- `cap`: The capacity of the queue +- `size`: The size of a message + +Example: + + import "core:sync/chan" + + raw_queue_init_example :: proc() { + // use a stack allocated array as backing storage + storage: [100]int + + rq: chan.Raw_Queue + chan.raw_queue_init(&rq, &storage, cap(storage), size_of(int)) + } +*/ +@(private) raw_queue_init :: proc "contextless" (q: ^Raw_Queue, data: rawptr, cap: int, size: int) { q.data = ([^]byte)(data) q.len = 0 @@ -389,8 +1322,33 @@ raw_queue_init :: proc "contextless" (q: ^Raw_Queue, data: rawptr, cap: int, siz q.size = size } +/* +Add an element to the queue. -@(require_results) +Note: The message referenced by `data` must match the size +and alignment used when the `Raw_Queue` was initialized. + +**Inputs** +- `q`: A pointert to the `Raw_Queue` +- `data`: The pointer to message to add + +**Returns** +- `true` if the element was added, `false` when the queue is already full + +Example: + + import "core:sync/chan" + + raw_queue_push_example :: proc() { + storage: [100]int + rq: chan.Raw_Queue + chan.raw_queue_init(&rq, &storage, cap(storage), size_of(int)) + + value := 2 + assert(chan.raw_queue_push(&rq, &value), "there was enough space") + } +*/ +@(private, require_results) raw_queue_push :: proc "contextless" (q: ^Raw_Queue, data: rawptr) -> bool { if q.len == q.cap { return false @@ -406,83 +1364,46 @@ raw_queue_push :: proc "contextless" (q: ^Raw_Queue, data: rawptr) -> bool { return true } -@(require_results) -raw_queue_pop :: proc "contextless" (q: ^Raw_Queue) -> (data: rawptr) { - if q.len > 0 { - data = q.data[q.next*q.size:] - q.next += 1 - q.len -= 1 - if q.next >= q.cap { - q.next -= q.cap - } - } - return -} +/* +Removes and returns the first element of the queue. +Note: The returned element is only guaranteed to be valid until the next +`raw_queue_push` operation. Accessing it after that point may result in +undefined behavior. -@(require_results) -can_recv :: proc "contextless" (c: ^Raw_Chan) -> bool { - sync.guard(&c.mutex) - if is_buffered(c) { - return c.queue.len > 0 - } - return c.w_waiting > 0 -} +**Inputs** +- `c`: A pointer to the `Raw_Queue`. +**Returns** +- A pointer to the first element in the queue, or `nil` if the queue is empty. -@(require_results) -can_send :: proc "contextless" (c: ^Raw_Chan) -> bool { - sync.guard(&c.mutex) - if is_buffered(c) { - return c.queue.len < c.queue.cap - } - return c.w_waiting == 0 -} +Example: + import "core:sync/chan" + raw_queue_pop_example :: proc() { + storage: [100]int + rq: chan.Raw_Queue + chan.raw_queue_init(&rq, &storage, cap(storage), size_of(int)) -@(require_results) -select_raw :: proc "odin" (recvs: []^Raw_Chan, sends: []^Raw_Chan, send_msgs: []rawptr, recv_out: rawptr) -> (select_idx: int, ok: bool) #no_bounds_check { - Select_Op :: struct { - idx: int, // local to the slice that was given - is_recv: bool, - } + assert(chan.raw_queue_pop(&rq) == nil, "queue was empty") - candidate_count := builtin.len(recvs)+builtin.len(sends) - candidates := ([^]Select_Op)(intrinsics.alloca(candidate_count*size_of(Select_Op), align_of(Select_Op))) - count := 0 + // add an element to the queue + value := 2 + assert(chan.raw_queue_push(&rq, &value), "there was enough space") - for c, i in recvs { - if can_recv(c) { - candidates[count] = { - is_recv = true, - idx = i, - } - count += 1 - } + assert((cast(^int)chan.raw_queue_pop(&rq))^ == 2, "retrieved the element") } - - for c, i in sends { - if can_send(c) { - candidates[count] = { - is_recv = false, - idx = i, - } - count += 1 +*/ +@(private, require_results) +raw_queue_pop :: proc "contextless" (q: ^Raw_Queue) -> (data: rawptr) { + if q.len > 0 { + data = q.data[q.next*q.size:] + q.next += 1 + q.len -= 1 + if q.next >= q.cap { + q.next -= q.cap } } - - if count == 0 { - return - } - - select_idx = rand.int_max(count) if count > 0 else 0 - - sel := candidates[select_idx] - if sel.is_recv { - ok = recv_raw(recvs[sel.idx], recv_out) - } else { - ok = send_raw(sends[sel.idx], send_msgs[sel.idx]) - } return } diff --git a/core/sync/chan/doc.odin b/core/sync/chan/doc.odin new file mode 100644 index 000000000..5d65d7410 --- /dev/null +++ b/core/sync/chan/doc.odin @@ -0,0 +1,69 @@ +/* +This package provides both high-level and low-level channel types +for thread-safe communication. + +While channels are essentially thread-safe queues under the hood, +their primary purpose is to facilitate safe communication between +multiple readers and multiple writers. +Although they can be used like queues, channels are designed with +synchronization and concurrent messaging patterns in mind. + +Provided types: +- `Chan` a high level channel +- `Raw_Chan` a low level channel +- `Raw_Queue` a low level non-threadsafe queue implementation used internally + +Example: + + import "core:sync/chan" + import "core:fmt" + import "core:thread" + + // The consumer reads from the channel until it's closed. + // Closing the channel acts as a signal to stop. + consumer :: proc(recv_chan: chan.Chan(int, .Recv)) { + for { + value, ok := chan.recv(recv_chan) + if !ok { + break // More idiomatic than return here + } + fmt.println("[CONSUMER] Received:", value) + } + fmt.println("[CONSUMER] Channel closed, stopping.") + } + + // The producer sends `count` number of messages. + producer :: proc(send_chan: chan.Chan(int, .Send), count: int) { + for i in 0..<count { + fmt.println("[PRODUCER] Sending:", i) + success := chan.send(send_chan, i) + if !success { + fmt.println("[PRODUCER] Failed to send, channel may be closed.") + return + } + } + + // Signal that production is complete by closing the channel. + chan.close(send_chan) + fmt.println("[PRODUCER] Done producing, channel closed.") + } + + chan_example :: proc() { + // Create an unbuffered channel for int messages + c, err := chan.create(chan.Chan(int), context.allocator) + assert(err == .None) + defer chan.destroy(c) + + // Start the consumer thread + consumer_thread := thread.create_and_start_with_poly_data(chan.as_recv(c), consumer) + defer thread.destroy(consumer_thread) + + // Start the producer thread with 5 messages (change count as needed) + producer_thread := thread.create_and_start_with_poly_data2(chan.as_send(c), 5, producer) + defer thread.destroy(producer_thread) + + // Wait for both threads to complete + thread.join_multiple(consumer_thread, producer_thread) + } +*/ +package sync_chan diff --git a/core/sync/extended.odin b/core/sync/extended.odin index 30b1b2770..82fc3d751 100644 --- a/core/sync/extended.odin +++ b/core/sync/extended.odin @@ -47,12 +47,12 @@ wait_group_add :: proc "contextless" (wg: ^Wait_Group, delta: int) { guard(&wg.mutex) atomic_add(&wg.counter, delta) - if wg.counter < 0 { + switch counter := atomic_load(&wg.counter); { + case counter < 0: panic_contextless("sync.Wait_Group negative counter") - } - if wg.counter == 0 { + case wg.counter == 0: cond_broadcast(&wg.cond) - if wg.counter != 0 { + if atomic_load(&wg.counter) != 0 { panic_contextless("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait") } } @@ -78,11 +78,8 @@ wait group's internal counter reaches zero. wait_group_wait :: proc "contextless" (wg: ^Wait_Group) { guard(&wg.mutex) - if wg.counter != 0 { + for atomic_load(&wg.counter) != 0 { cond_wait(&wg.cond, &wg.mutex) - if wg.counter != 0 { - panic_contextless("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait") - } } } @@ -100,13 +97,10 @@ wait_group_wait_with_timeout :: proc "contextless" (wg: ^Wait_Group, duration: t } guard(&wg.mutex) - if wg.counter != 0 { + for atomic_load(&wg.counter) != 0 { if !cond_wait_with_timeout(&wg.cond, &wg.mutex, duration) { return false } - if wg.counter != 0 { - panic_contextless("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait") - } } return true } @@ -581,9 +575,9 @@ once_do_without_data :: proc(o: ^Once, fn: proc()) { /* Call a contextless function with no data once. */ -once_do_without_data_contextless :: proc(o: ^Once, fn: proc "contextless" ()) { +once_do_without_data_contextless :: proc "contextless" (o: ^Once, fn: proc "contextless" ()) { @(cold) - do_slow :: proc(o: ^Once, fn: proc "contextless" ()) { + do_slow :: proc "contextless" (o: ^Once, fn: proc "contextless" ()) { guard(&o.m) if !o.done { fn() diff --git a/core/sync/futex_darwin.odin b/core/sync/futex_darwin.odin index 10ff7bfbb..2b104fed6 100644 --- a/core/sync/futex_darwin.odin +++ b/core/sync/futex_darwin.odin @@ -6,7 +6,7 @@ import "core:c" import "core:sys/darwin" import "core:time" -foreign import System "system:System.framework" +foreign import System "system:System" foreign System { // __ulock_wait is not available on 10.15 diff --git a/core/sync/primitives_atomic.odin b/core/sync/primitives_atomic.odin index a8a84b2bc..c694e5f96 100644 --- a/core/sync/primitives_atomic.odin +++ b/core/sync/primitives_atomic.odin @@ -95,20 +95,16 @@ atomic_mutex_guard :: proc "contextless" (m: ^Atomic_Mutex) -> bool { Atomic_RW_Mutex_State :: distinct uint -Atomic_RW_Mutex_State_Half_Width :: size_of(Atomic_RW_Mutex_State)*8/2 -Atomic_RW_Mutex_State_Is_Writing :: Atomic_RW_Mutex_State(1) -Atomic_RW_Mutex_State_Writer :: Atomic_RW_Mutex_State(1)<<1 -Atomic_RW_Mutex_State_Reader :: Atomic_RW_Mutex_State(1)<<Atomic_RW_Mutex_State_Half_Width +Atomic_RW_Mutex_State_Is_Writing :: Atomic_RW_Mutex_State(1) << (size_of(Atomic_RW_Mutex_State)*8-1) +Atomic_RW_Mutex_State_Reader :: Atomic_RW_Mutex_State(1) +Atomic_RW_Mutex_State_Reader_Mask :: ~Atomic_RW_Mutex_State_Is_Writing -Atomic_RW_Mutex_State_Writer_Mask :: Atomic_RW_Mutex_State(1<<(Atomic_RW_Mutex_State_Half_Width-1) - 1) << 1 -Atomic_RW_Mutex_State_Reader_Mask :: Atomic_RW_Mutex_State(1<<(Atomic_RW_Mutex_State_Half_Width-1) - 1) << Atomic_RW_Mutex_State_Half_Width - -// An Atomic_RW_Mutex is a reader/writer mutual exclusion lock -// The lock can be held by any arbitrary number of readers or a single writer -// The zero value for an Atomic_RW_Mutex is an unlocked mutex +// An Atomic_RW_Mutex is a reader/writer mutual exclusion lock. +// The lock can be held by any arbitrary number of readers or a single writer. +// The zero value for an Atomic_RW_Mutex is an unlocked mutex. // -// An Atomic_RW_Mutex must not be copied after first use +// An Atomic_RW_Mutex must not be copied after first use. Atomic_RW_Mutex :: struct #no_copy { state: Atomic_RW_Mutex_State, mutex: Atomic_Mutex, @@ -118,11 +114,17 @@ Atomic_RW_Mutex :: struct #no_copy { // atomic_rw_mutex_lock locks rw for writing (with a single writer) // If the mutex is already locked for reading or writing, the mutex blocks until the mutex is available. atomic_rw_mutex_lock :: proc "contextless" (rw: ^Atomic_RW_Mutex) { - _ = atomic_add(&rw.state, Atomic_RW_Mutex_State_Writer) atomic_mutex_lock(&rw.mutex) - state := atomic_or(&rw.state, Atomic_RW_Mutex_State_Writer) + state := atomic_or(&rw.state, Atomic_RW_Mutex_State_Is_Writing) if state & Atomic_RW_Mutex_State_Reader_Mask != 0 { + // There's at least one reader, so wait for the last one to post the semaphore. + // + // Because we hold the exclusive lock, no more readers can come in + // during this time, which will prevent any situations where the last + // reader is pre-empted around the count turning zero, which would + // result in the potential for another reader to run amok after the + // other posts. atomic_sema_wait(&rw.sema) } } @@ -138,10 +140,15 @@ atomic_rw_mutex_try_lock :: proc "contextless" (rw: ^Atomic_RW_Mutex) -> bool { if atomic_mutex_try_lock(&rw.mutex) { state := atomic_load(&rw.state) if state & Atomic_RW_Mutex_State_Reader_Mask == 0 { - _ = atomic_or(&rw.state, Atomic_RW_Mutex_State_Is_Writing) - return true + // Compare-and-exchange for absolute certainty that no one has come in to read. + _, ok := atomic_compare_exchange_strong(&rw.state, state, state | Atomic_RW_Mutex_State_Is_Writing) + if ok { + return true + } } + // A reader is active or came in while we have the lock, so we need to + // back out. atomic_mutex_unlock(&rw.mutex) } return false @@ -150,16 +157,22 @@ atomic_rw_mutex_try_lock :: proc "contextless" (rw: ^Atomic_RW_Mutex) -> bool { // atomic_rw_mutex_shared_lock locks rw for reading (with arbitrary number of readers) atomic_rw_mutex_shared_lock :: proc "contextless" (rw: ^Atomic_RW_Mutex) { state := atomic_load(&rw.state) - for state & (Atomic_RW_Mutex_State_Is_Writing|Atomic_RW_Mutex_State_Writer_Mask) == 0 { + for state & Atomic_RW_Mutex_State_Is_Writing == 0 { ok: bool state, ok = atomic_compare_exchange_weak(&rw.state, state, state + Atomic_RW_Mutex_State_Reader) if ok { + // We succesfully took the shared reader lock without any writers intervening. return } } + // A writer is active or came in while we were trying to get a shared + // reader lock, so now we must take the full lock in order to wait for the + // writer to give it up. atomic_mutex_lock(&rw.mutex) + // At this point, we have the lock, so we can add to the reader count. _ = atomic_add(&rw.state, Atomic_RW_Mutex_State_Reader) + // Then we give up the lock to let other readers (or writers) come through. atomic_mutex_unlock(&rw.mutex) } @@ -169,6 +182,8 @@ atomic_rw_mutex_shared_unlock :: proc "contextless" (rw: ^Atomic_RW_Mutex) { if (state & Atomic_RW_Mutex_State_Reader_Mask == Atomic_RW_Mutex_State_Reader) && (state & Atomic_RW_Mutex_State_Is_Writing != 0) { + // We were the last reader, so post to the writer with the lock who's + // waiting to continue. atomic_sema_post(&rw.sema) } } @@ -176,12 +191,21 @@ atomic_rw_mutex_shared_unlock :: proc "contextless" (rw: ^Atomic_RW_Mutex) { // atomic_rw_mutex_try_shared_lock tries to lock rw for reading (with arbitrary number of readers) atomic_rw_mutex_try_shared_lock :: proc "contextless" (rw: ^Atomic_RW_Mutex) -> bool { state := atomic_load(&rw.state) - if state & (Atomic_RW_Mutex_State_Is_Writing|Atomic_RW_Mutex_State_Writer_Mask) == 0 { - _, ok := atomic_compare_exchange_strong(&rw.state, state, state + Atomic_RW_Mutex_State_Reader) + // NOTE: We need to check this in a for loop, because it is possible for + // another reader to change the underlying state which would cause our + // compare-and-exchange to fail. + for state & (Atomic_RW_Mutex_State_Is_Writing) == 0 { + ok: bool + state, ok = atomic_compare_exchange_weak(&rw.state, state, state + Atomic_RW_Mutex_State_Reader) if ok { return true } } + // A writer is active or came in during our lock attempt. + + // We try to take the full lock, and if that succeeds (perhaps because the + // writer finished during the time since we failed our CAS), we increment + // the reader count and head on. if atomic_mutex_try_lock(&rw.mutex) { _ = atomic_add(&rw.state, Atomic_RW_Mutex_State_Reader) atomic_mutex_unlock(&rw.mutex) diff --git a/core/sync/primitives_darwin.odin b/core/sync/primitives_darwin.odin index 141cea744..4ae4b7789 100644 --- a/core/sync/primitives_darwin.odin +++ b/core/sync/primitives_darwin.odin @@ -5,7 +5,7 @@ package sync import "core:c" import "base:intrinsics" -foreign import pthread "system:System.framework" +foreign import pthread "system:System" _current_thread_id :: proc "contextless" () -> int { tid: u64 diff --git a/core/sys/darwin/CoreFoundation/CFString.odin b/core/sys/darwin/CoreFoundation/CFString.odin index 24485a494..d245ba793 100644 --- a/core/sys/darwin/CoreFoundation/CFString.odin +++ b/core/sys/darwin/CoreFoundation/CFString.odin @@ -1,10 +1,12 @@ package CoreFoundation +import "core:c" + foreign import CoreFoundation "system:CoreFoundation.framework" String :: distinct TypeRef // same as CFStringRef -StringEncoding :: distinct u32 +StringEncoding :: distinct c.long StringBuiltInEncodings :: enum StringEncoding { MacRoman = 0, @@ -171,7 +173,7 @@ foreign CoreFoundation { // Fetches a range of the characters from a string into a byte buffer after converting the characters to a specified encoding. StringGetBytes :: proc(thestring: String, range: Range, encoding: StringEncoding, lossByte: u8, isExternalRepresentation: b8, buffer: [^]byte, maxBufLen: Index, usedBufLen: ^Index) -> Index --- - StringIsEncodingAvailable :: proc(encoding: StringEncoding) -> bool --- + StringIsEncodingAvailable :: proc(encoding: StringEncoding) -> b8 --- @(link_name = "__CFStringMakeConstantString") StringMakeConstantString :: proc "c" (#const c: cstring) -> String --- diff --git a/core/sys/darwin/Foundation/NSApplication.odin b/core/sys/darwin/Foundation/NSApplication.odin index 254da75ad..a5e9f2e24 100644 --- a/core/sys/darwin/Foundation/NSApplication.odin +++ b/core/sys/darwin/Foundation/NSApplication.odin @@ -82,7 +82,6 @@ Application_setActivationPolicy :: proc "c" (self: ^Application, activationPolic // NOTE: this is technically deprecated but still actively used (Sokol, glfw, SDL, etc.) // and has no clear alternative although `activate` is what Apple tells you to use, // that does not work the same way. -// @(deprecated="Use NSApplication method activate instead.") @(objc_type=Application, objc_name="activateIgnoringOtherApps") Application_activateIgnoringOtherApps :: proc "c" (self: ^Application, ignoreOtherApps: BOOL) { msgSend(nil, self, "activateIgnoringOtherApps:", ignoreOtherApps) @@ -99,7 +98,7 @@ Application_setTitle :: proc "c" (self: ^Application, title: ^String) { } @(objc_type=Application, objc_name="mainMenu") -Window_mainMenu :: proc "c" (self: ^Application) -> ^Menu { +Application_mainMenu :: proc "c" (self: ^Application) -> ^Menu { return msgSend(^Menu, self, "mainMenu") } @@ -256,7 +255,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem return nil } if template.applicationWillFinishLaunching != nil { - applicationWillFinishLaunching :: proc "c" (self: id, notification: ^Notification) { + applicationWillFinishLaunching :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationWillFinishLaunching(notification) @@ -264,7 +263,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationWillFinishLaunching:"), auto_cast applicationWillFinishLaunching, "v@:@") } if template.applicationDidFinishLaunching != nil { - applicationDidFinishLaunching :: proc "c" (self: id, notification: ^Notification) { + applicationDidFinishLaunching :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationDidFinishLaunching(notification) @@ -272,7 +271,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationDidFinishLaunching:"), auto_cast applicationDidFinishLaunching, "v@:@") } if template.applicationWillBecomeActive != nil { - applicationWillBecomeActive :: proc "c" (self: id, notification: ^Notification) { + applicationWillBecomeActive :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationWillBecomeActive(notification) @@ -280,7 +279,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationWillBecomeActive:"), auto_cast applicationWillBecomeActive, "v@:@") } if template.applicationDidBecomeActive != nil { - applicationDidBecomeActive :: proc "c" (self: id, notification: ^Notification) { + applicationDidBecomeActive :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationDidBecomeActive(notification) @@ -288,7 +287,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationDidBecomeActive:"), auto_cast applicationDidBecomeActive, "v@:@") } if template.applicationWillResignActive != nil { - applicationWillResignActive :: proc "c" (self: id, notification: ^Notification) { + applicationWillResignActive :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationWillResignActive(notification) @@ -296,7 +295,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationWillResignActive:"), auto_cast applicationWillResignActive, "v@:@") } if template.applicationDidResignActive != nil { - applicationDidResignActive :: proc "c" (self: id, notification: ^Notification) { + applicationDidResignActive :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationDidResignActive(notification) @@ -304,7 +303,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationDidResignActive:"), auto_cast applicationDidResignActive, "v@:@") } if template.applicationShouldTerminate != nil { - applicationShouldTerminate :: proc "c" (self: id, sender: ^Application) -> ApplicationTerminateReply { + applicationShouldTerminate :: proc "c" (self: id, cmd: SEL, sender: ^Application) -> ApplicationTerminateReply { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context return del.applicationShouldTerminate(sender) @@ -312,7 +311,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationShouldTerminate:"), auto_cast applicationShouldTerminate, _UINTEGER_ENCODING+"@:@") } if template.applicationShouldTerminateAfterLastWindowClosed != nil { - applicationShouldTerminateAfterLastWindowClosed :: proc "c" (self: id, sender: ^Application) -> BOOL { + applicationShouldTerminateAfterLastWindowClosed :: proc "c" (self: id, cmd: SEL, sender: ^Application) -> BOOL { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context return del.applicationShouldTerminateAfterLastWindowClosed(sender) @@ -320,7 +319,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationShouldTerminateAfterLastWindowClosed:"), auto_cast applicationShouldTerminateAfterLastWindowClosed, "B@:@") } if template.applicationWillTerminate != nil { - applicationWillTerminate :: proc "c" (self: id, notification: ^Notification) { + applicationWillTerminate :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationWillTerminate(notification) @@ -328,7 +327,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationWillTerminate:"), auto_cast applicationWillTerminate, "v@:@") } if template.applicationWillHide != nil { - applicationWillHide :: proc "c" (self: id, notification: ^Notification) { + applicationWillHide :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationWillHide(notification) @@ -336,7 +335,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationWillHide:"), auto_cast applicationWillHide, "v@:@") } if template.applicationDidHide != nil { - applicationDidHide :: proc "c" (self: id, notification: ^Notification) { + applicationDidHide :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationDidHide(notification) @@ -344,7 +343,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationDidHide:"), auto_cast applicationDidHide, "v@:@") } if template.applicationWillUnhide != nil { - applicationWillUnhide :: proc "c" (self: id, notification: ^Notification) { + applicationWillUnhide :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationWillUnhide(notification) @@ -352,7 +351,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationWillUnhide:"), auto_cast applicationWillUnhide, "v@:@") } if template.applicationDidUnhide != nil { - applicationDidUnhide :: proc "c" (self: id, notification: ^Notification) { + applicationDidUnhide :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationDidUnhide(notification) @@ -360,7 +359,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationDidUnhide:"), auto_cast applicationDidUnhide, "v@:@") } if template.applicationWillUpdate != nil { - applicationWillUpdate :: proc "c" (self: id, notification: ^Notification) { + applicationWillUpdate :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationWillUpdate(notification) @@ -368,7 +367,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationWillUpdate:"), auto_cast applicationWillUpdate, "v@:@") } if template.applicationDidUpdate != nil { - applicationDidUpdate :: proc "c" (self: id, notification: ^Notification) { + applicationDidUpdate :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationDidUpdate(notification) @@ -376,7 +375,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationDidUpdate:"), auto_cast applicationDidUpdate, "v@:@") } if template.applicationShouldHandleReopenHasVisibleWindows != nil { - applicationShouldHandleReopenHasVisibleWindows :: proc "c" (self: id, sender: ^Application, flag: BOOL) -> BOOL { + applicationShouldHandleReopenHasVisibleWindows :: proc "c" (self: id, cmd: SEL, sender: ^Application, flag: BOOL) -> BOOL { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context return del.applicationShouldHandleReopenHasVisibleWindows(sender, flag) @@ -384,7 +383,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationShouldHandleReopen:hasVisibleWindows:"), auto_cast applicationShouldHandleReopenHasVisibleWindows, "B@:@B") } if template.applicationDockMenu != nil { - applicationDockMenu :: proc "c" (self: id, sender: ^Application) -> ^Menu { + applicationDockMenu :: proc "c" (self: id, cmd: SEL, sender: ^Application) -> ^Menu { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context return del.applicationDockMenu(sender) @@ -392,7 +391,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationDockMenu:"), auto_cast applicationDockMenu, "@@:@") } if template.applicationShouldAutomaticallyLocalizeKeyEquivalents != nil { - applicationShouldAutomaticallyLocalizeKeyEquivalents :: proc "c" (self: id, application: ^Application) -> BOOL { + applicationShouldAutomaticallyLocalizeKeyEquivalents :: proc "c" (self: id, cmd: SEL, application: ^Application) -> BOOL { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context return del.applicationShouldAutomaticallyLocalizeKeyEquivalents(application) @@ -400,7 +399,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationShouldAutomaticallyLocalizeKeyEquivalents:"), auto_cast applicationShouldAutomaticallyLocalizeKeyEquivalents, "B@:@") } if template.applicationWillPresentError != nil { - applicationWillPresentError :: proc "c" (self: id, application: ^Application, error: ^Error) -> ^Error { + applicationWillPresentError :: proc "c" (self: id, cmd: SEL, application: ^Application, error: ^Error) -> ^Error { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context return del.applicationWillPresentError(application, error) @@ -408,7 +407,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("application:willPresentError:"), auto_cast applicationWillPresentError, "@@:@@") } if template.applicationDidChangeScreenParameters != nil { - applicationDidChangeScreenParameters :: proc "c" (self: id, notification: ^Notification) { + applicationDidChangeScreenParameters :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationDidChangeScreenParameters(notification) @@ -416,7 +415,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationDidChangeScreenParameters:"), auto_cast applicationDidChangeScreenParameters, "v@:@") } if template.applicationWillContinueUserActivityWithType != nil { - applicationWillContinueUserActivityWithType :: proc "c" (self: id, application: ^Application, userActivityType: ^String) -> BOOL { + applicationWillContinueUserActivityWithType :: proc "c" (self: id, cmd: SEL, application: ^Application, userActivityType: ^String) -> BOOL { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context return del.applicationWillContinueUserActivityWithType(application, userActivityType) @@ -424,7 +423,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("application:willContinueUserActivityWithType:"), auto_cast applicationWillContinueUserActivityWithType, "B@:@@") } if template.applicationContinueUserActivityRestorationHandler != nil { - applicationContinueUserActivityRestorationHandler :: proc "c" (self: id, application: ^Application, userActivity: ^UserActivity, restorationHandler: ^Block) -> BOOL { + applicationContinueUserActivityRestorationHandler :: proc "c" (self: id, cmd: SEL, application: ^Application, userActivity: ^UserActivity, restorationHandler: ^Block) -> BOOL { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context return del.applicationContinueUserActivityRestorationHandler(application, userActivity, restorationHandler) @@ -432,7 +431,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("application:continueUserActivity:restorationHandler:"), auto_cast applicationContinueUserActivityRestorationHandler, "B@:@@?") } if template.applicationDidFailToContinueUserActivityWithTypeError != nil { - applicationDidFailToContinueUserActivityWithTypeError :: proc "c" (self: id, application: ^Application, userActivityType: ^String, error: ^Error) { + applicationDidFailToContinueUserActivityWithTypeError :: proc "c" (self: id, cmd: SEL, application: ^Application, userActivityType: ^String, error: ^Error) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationDidFailToContinueUserActivityWithTypeError(application, userActivityType, error) @@ -440,7 +439,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("application:didFailToContinueUserActivityWithType:error:"), auto_cast applicationDidFailToContinueUserActivityWithTypeError, "v@:@@@") } if template.applicationDidUpdateUserActivity != nil { - applicationDidUpdateUserActivity :: proc "c" (self: id, application: ^Application, userActivity: ^UserActivity) { + applicationDidUpdateUserActivity :: proc "c" (self: id, cmd: SEL, application: ^Application, userActivity: ^UserActivity) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationDidUpdateUserActivity(application, userActivity) @@ -448,7 +447,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("application:didUpdateUserActivity:"), auto_cast applicationDidUpdateUserActivity, "v@:@@") } if template.applicationDidRegisterForRemoteNotificationsWithDeviceToken != nil { - applicationDidRegisterForRemoteNotificationsWithDeviceToken :: proc "c" (self: id, application: ^Application, deviceToken: ^Data) { + applicationDidRegisterForRemoteNotificationsWithDeviceToken :: proc "c" (self: id, cmd: SEL, application: ^Application, deviceToken: ^Data) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationDidRegisterForRemoteNotificationsWithDeviceToken(application, deviceToken) @@ -456,7 +455,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("application:didRegisterForRemoteNotificationsWithDeviceToken:"), auto_cast applicationDidRegisterForRemoteNotificationsWithDeviceToken, "v@:@@") } if template.applicationDidFailToRegisterForRemoteNotificationsWithError != nil { - applicationDidFailToRegisterForRemoteNotificationsWithError :: proc "c" (self: id, application: ^Application, error: ^Error) { + applicationDidFailToRegisterForRemoteNotificationsWithError :: proc "c" (self: id, cmd: SEL, application: ^Application, error: ^Error) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationDidFailToRegisterForRemoteNotificationsWithError(application, error) @@ -464,7 +463,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("application:didFailToRegisterForRemoteNotificationsWithError:"), auto_cast applicationDidFailToRegisterForRemoteNotificationsWithError, "v@:@@") } if template.applicationDidReceiveRemoteNotification != nil { - applicationDidReceiveRemoteNotification :: proc "c" (self: id, application: ^Application, userInfo: ^Dictionary) { + applicationDidReceiveRemoteNotification :: proc "c" (self: id, cmd: SEL, application: ^Application, userInfo: ^Dictionary) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationDidReceiveRemoteNotification(application, userInfo) @@ -472,7 +471,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("application:didReceiveRemoteNotification:"), auto_cast applicationDidReceiveRemoteNotification, "v@:@@") } // if template.applicationUserDidAcceptCloudKitShareWithMetadata != nil { - // applicationUserDidAcceptCloudKitShareWithMetadata :: proc "c" (self: id, application: ^Application, metadata: ^CKShareMetadata) { + // applicationUserDidAcceptCloudKitShareWithMetadata :: proc "c" (self: id, cmd: SEL, application: ^Application, metadata: ^CKShareMetadata) { // del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) // context = del._context // del.applicationUserDidAcceptCloudKitShareWithMetadata(application, metadata) @@ -480,7 +479,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem // class_addMethod(class, intrinsics.objc_find_selector("application:userDidAcceptCloudKitShareWithMetadata:"), auto_cast applicationUserDidAcceptCloudKitShareWithMetadata, "v@:@@") // } // if template.applicationHandlerForIntent != nil { - // applicationHandlerForIntent :: proc "c" (self: id, application: ^Application, intent: ^INIntent) -> id { + // applicationHandlerForIntent :: proc "c" (self: id, cmd: SEL, application: ^Application, intent: ^INIntent) -> id { // del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) // context = del._context // return del.applicationHandlerForIntent(application, intent) @@ -488,7 +487,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem // class_addMethod(class, intrinsics.objc_find_selector("application:handlerForIntent:"), auto_cast applicationHandlerForIntent, "@@:@@") // } if template.applicationOpenURLs != nil { - applicationOpenURLs :: proc "c" (self: id, application: ^Application, urls: ^Array) { + applicationOpenURLs :: proc "c" (self: id, cmd: SEL, application: ^Application, urls: ^Array) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationOpenURLs(application, urls) @@ -496,7 +495,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("application:openURLs:"), auto_cast applicationOpenURLs, "v@:@@") } if template.applicationOpenFile != nil { - applicationOpenFile :: proc "c" (self: id, sender: ^Application, filename: ^String) -> BOOL { + applicationOpenFile :: proc "c" (self: id, cmd: SEL, sender: ^Application, filename: ^String) -> BOOL { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context return del.applicationOpenFile(sender, filename) @@ -504,7 +503,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("application:openFile:"), auto_cast applicationOpenFile, "B@:@@") } if template.applicationOpenFileWithoutUI != nil { - applicationOpenFileWithoutUI :: proc "c" (self: id, sender: id, filename: ^String) -> BOOL { + applicationOpenFileWithoutUI :: proc "c" (self: id, cmd: SEL, sender: id, filename: ^String) -> BOOL { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context return del.applicationOpenFileWithoutUI(sender, filename) @@ -512,7 +511,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("application:openFileWithoutUI:"), auto_cast applicationOpenFileWithoutUI, "B@:@@") } if template.applicationOpenTempFile != nil { - applicationOpenTempFile :: proc "c" (self: id, sender: ^Application, filename: ^String) -> BOOL { + applicationOpenTempFile :: proc "c" (self: id, cmd: SEL, sender: ^Application, filename: ^String) -> BOOL { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context return del.applicationOpenTempFile(sender, filename) @@ -520,7 +519,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("application:openTempFile:"), auto_cast applicationOpenTempFile, "B@:@@") } if template.applicationOpenFiles != nil { - applicationOpenFiles :: proc "c" (self: id, sender: ^Application, filenames: ^Array) { + applicationOpenFiles :: proc "c" (self: id, cmd: SEL, sender: ^Application, filenames: ^Array) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationOpenFiles(sender, filenames) @@ -528,7 +527,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("application:openFiles:"), auto_cast applicationOpenFiles, "v@:@@") } if template.applicationShouldOpenUntitledFile != nil { - applicationShouldOpenUntitledFile :: proc "c" (self: id, sender: ^Application) -> BOOL { + applicationShouldOpenUntitledFile :: proc "c" (self: id, cmd: SEL, sender: ^Application) -> BOOL { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context return del.applicationShouldOpenUntitledFile(sender) @@ -536,7 +535,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationShouldOpenUntitledFile:"), auto_cast applicationShouldOpenUntitledFile, "B@:@") } if template.applicationOpenUntitledFile != nil { - applicationOpenUntitledFile :: proc "c" (self: id, sender: ^Application) -> BOOL { + applicationOpenUntitledFile :: proc "c" (self: id, cmd: SEL, sender: ^Application) -> BOOL { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context return del.applicationOpenUntitledFile(sender) @@ -544,7 +543,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationOpenUntitledFile:"), auto_cast applicationOpenUntitledFile, "B@:@") } if template.applicationPrintFile != nil { - applicationPrintFile :: proc "c" (self: id, sender: ^Application, filename: ^String) -> BOOL { + applicationPrintFile :: proc "c" (self: id, cmd: SEL, sender: ^Application, filename: ^String) -> BOOL { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context return del.applicationPrintFile(sender, filename) @@ -552,7 +551,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("application:printFile:"), auto_cast applicationPrintFile, "B@:@@") } if template.applicationPrintFilesWithSettingsShowPrintPanels != nil { - applicationPrintFilesWithSettingsShowPrintPanels :: proc "c" (self: id, application: ^Application, fileNames: ^Array, printSettings: ^Dictionary, showPrintPanels: BOOL) -> ApplicationPrintReply { + applicationPrintFilesWithSettingsShowPrintPanels :: proc "c" (self: id, cmd: SEL, application: ^Application, fileNames: ^Array, printSettings: ^Dictionary, showPrintPanels: BOOL) -> ApplicationPrintReply { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context return del.applicationPrintFilesWithSettingsShowPrintPanels(application, fileNames, printSettings, showPrintPanels) @@ -560,7 +559,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("application:printFiles:withSettings:showPrintPanels:"), auto_cast applicationPrintFilesWithSettingsShowPrintPanels, _UINTEGER_ENCODING+"@:@@@B") } if template.applicationSupportsSecureRestorableState != nil { - applicationSupportsSecureRestorableState :: proc "c" (self: id, app: ^Application) -> BOOL { + applicationSupportsSecureRestorableState :: proc "c" (self: id, cmd: SEL, app: ^Application) -> BOOL { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context return del.applicationSupportsSecureRestorableState(app) @@ -568,7 +567,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationSupportsSecureRestorableState:"), auto_cast applicationSupportsSecureRestorableState, "B@:@") } if template.applicationProtectedDataDidBecomeAvailable != nil { - applicationProtectedDataDidBecomeAvailable :: proc "c" (self: id, notification: ^Notification) { + applicationProtectedDataDidBecomeAvailable :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationProtectedDataDidBecomeAvailable(notification) @@ -576,7 +575,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationProtectedDataDidBecomeAvailable:"), auto_cast applicationProtectedDataDidBecomeAvailable, "v@:@") } if template.applicationProtectedDataWillBecomeUnavailable != nil { - applicationProtectedDataWillBecomeUnavailable :: proc "c" (self: id, notification: ^Notification) { + applicationProtectedDataWillBecomeUnavailable :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationProtectedDataWillBecomeUnavailable(notification) @@ -584,7 +583,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationProtectedDataWillBecomeUnavailable:"), auto_cast applicationProtectedDataWillBecomeUnavailable, "v@:@") } if template.applicationWillEncodeRestorableState != nil { - applicationWillEncodeRestorableState :: proc "c" (self: id, app: ^Application, coder: ^Coder) { + applicationWillEncodeRestorableState :: proc "c" (self: id, cmd: SEL, app: ^Application, coder: ^Coder) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationWillEncodeRestorableState(app, coder) @@ -592,7 +591,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("application:willEncodeRestorableState:"), auto_cast applicationWillEncodeRestorableState, "v@:@@") } if template.applicationDidDecodeRestorableState != nil { - applicationDidDecodeRestorableState :: proc "c" (self: id, app: ^Application, coder: ^Coder) { + applicationDidDecodeRestorableState :: proc "c" (self: id, cmd: SEL, app: ^Application, coder: ^Coder) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationDidDecodeRestorableState(app, coder) @@ -600,7 +599,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("application:didDecodeRestorableState:"), auto_cast applicationDidDecodeRestorableState, "v@:@@") } if template.applicationDidChangeOcclusionState != nil { - applicationDidChangeOcclusionState :: proc "c" (self: id, notification: ^Notification) { + applicationDidChangeOcclusionState :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context del.applicationDidChangeOcclusionState(notification) @@ -608,7 +607,7 @@ application_delegate_register_and_alloc :: proc(template: ApplicationDelegateTem class_addMethod(class, intrinsics.objc_find_selector("applicationDidChangeOcclusionState:"), auto_cast applicationDidChangeOcclusionState, "v@:@") } if template.applicationDelegateHandlesKey != nil { - applicationDelegateHandlesKey :: proc "c" (self: id, sender: ^Application, key: ^String) -> BOOL { + applicationDelegateHandlesKey :: proc "c" (self: id, cmd: SEL, sender: ^Application, key: ^String) -> BOOL { del := cast(^_ApplicationDelegateInternal)object_getIndexedIvars(self) context = del._context return del.applicationDelegateHandlesKey(sender, key) diff --git a/core/sys/darwin/Foundation/NSArray.odin b/core/sys/darwin/Foundation/NSArray.odin index b238f63f8..0977c6469 100644 --- a/core/sys/darwin/Foundation/NSArray.odin +++ b/core/sys/darwin/Foundation/NSArray.odin @@ -40,3 +40,49 @@ Array_objectAs :: proc "c" (self: ^Array, index: UInteger, $T: typeid) -> T wher Array_count :: proc "c" (self: ^Array) -> UInteger { return msgSend(UInteger, self, "count") } + + +@(objc_class="NSMutableArray") +MutableArray :: struct { + using _: Copying(MutableArray), +} + +@(objc_type=MutableArray, objc_name="alloc", objc_is_class_method=true) +MutableArray_alloc :: proc "c" () -> ^MutableArray { + return msgSend(^MutableArray, MutableArray, "alloc") +} + +@(objc_type=MutableArray, objc_name="init") +MutableArray_init :: proc "c" (self: ^MutableArray) -> ^MutableArray { + return msgSend(^MutableArray, self, "init") +} + +@(objc_type=MutableArray, objc_name="initWithObjects") +MutableArray_initWithObjects :: proc "c" (self: ^MutableArray, objects: [^]^Object, count: UInteger) -> ^MutableArray { + return msgSend(^MutableArray, self, "initWithObjects:count:", objects, count) +} + +@(objc_type=MutableArray, objc_name="initWithCoder") +MutableArray_initWithCoder :: proc "c" (self: ^MutableArray, coder: ^Coder) -> ^MutableArray { + return msgSend(^MutableArray, self, "initWithCoder:", coder) +} + +@(objc_type=MutableArray, objc_name="object") +MutableArray_object :: proc "c" (self: ^MutableArray, index: UInteger) -> ^Object { + return msgSend(^Object, self, "objectAtIndex:", index) +} +@(objc_type=MutableArray, objc_name="objectAs") +MutableArray_objectAs :: proc "c" (self: ^MutableArray, index: UInteger, $T: typeid) -> T where intrinsics.type_is_pointer(T), intrinsics.type_is_subtype_of(T, ^Object) { + return (T)(MutableArray_object(self, index)) +} + +@(objc_type=MutableArray, objc_name="count") +MutableArray_count :: proc "c" (self: ^MutableArray) -> UInteger { + return msgSend(UInteger, self, "count") +} + + +@(objc_type=MutableArray, objc_name="exchangeObjectAtIndex") +MutableArray_exchangeObjectAtIndex :: proc "c" (self: ^MutableArray, idx1, idx2: UInteger) { + msgSend(nil, self, "exchangeObjectAtIndex:withObjectAtIndex:", idx1, idx2) +} diff --git a/core/sys/darwin/Foundation/NSBitmapImageRep.odin b/core/sys/darwin/Foundation/NSBitmapImageRep.odin new file mode 100644 index 000000000..059a75e43 --- /dev/null +++ b/core/sys/darwin/Foundation/NSBitmapImageRep.odin @@ -0,0 +1,50 @@ +package objc_Foundation + +import "base:intrinsics" + +@(objc_class="NSBitmapImageRep") +BitmapImageRep :: struct { using _: Object } + +@(objc_type=BitmapImageRep, objc_name="alloc", objc_is_class_method=true) +BitmapImageRep_alloc :: proc "c" () -> ^BitmapImageRep { + return msgSend(^BitmapImageRep, BitmapImageRep, "alloc") +} + +@(objc_type=BitmapImageRep, objc_name="initWithBitmapDataPlanes") +BitmapImageRep_initWithBitmapDataPlanes :: proc "c" ( + self: ^BitmapImageRep, + bitmapDataPlanes: ^^u8, + pixelsWide: Integer, + pixelsHigh: Integer, + bitsPerSample: Integer, + samplesPerPixel: Integer, + hasAlpha: bool, + isPlanar: bool, + colorSpaceName: ^String, + bytesPerRow: Integer, + bitsPerPixel: Integer) -> ^BitmapImageRep { + + return msgSend(^BitmapImageRep, + self, + "initWithBitmapDataPlanes:pixelsWide:pixelsHigh:bitsPerSample:samplesPerPixel:hasAlpha:isPlanar:colorSpaceName:bytesPerRow:bitsPerPixel:", + bitmapDataPlanes, + pixelsWide, + pixelsHigh, + bitsPerSample, + samplesPerPixel, + hasAlpha, + isPlanar, + colorSpaceName, + bytesPerRow, + bitsPerPixel) +} + +@(objc_type=BitmapImageRep, objc_name="bitmapData") +BitmapImageRep_bitmapData :: proc "c" (self: ^BitmapImageRep) -> rawptr { + return msgSend(rawptr, self, "bitmapData") +} + +@(objc_type=BitmapImageRep, objc_name="CGImage") +BitmapImageRep_CGImage :: proc "c" (self: ^BitmapImageRep) -> rawptr { + return msgSend(rawptr, self, "CGImage") +} diff --git a/core/sys/darwin/Foundation/NSBlock.odin b/core/sys/darwin/Foundation/NSBlock.odin index 1ef5e8a9b..8e65391f4 100644 --- a/core/sys/darwin/Foundation/NSBlock.odin +++ b/core/sys/darwin/Foundation/NSBlock.odin @@ -62,7 +62,7 @@ global_block_descriptor := Block_Descriptor{ size = size_of(Internal_Block_Literal), } -foreign import libSystem "system:System.framework" +foreign import libSystem "system:System" foreign libSystem { _NSConcreteGlobalBlock: intrinsics.objc_class _NSConcreteStackBlock: intrinsics.objc_class diff --git a/core/sys/darwin/Foundation/NSDictionary.odin b/core/sys/darwin/Foundation/NSDictionary.odin index 8af58cf62..ed98f3168 100644 --- a/core/sys/darwin/Foundation/NSDictionary.odin +++ b/core/sys/darwin/Foundation/NSDictionary.odin @@ -15,7 +15,7 @@ Dictionary_dictionaryWithObject :: proc "c" (object: ^Object, forKey: ^Object) - @(objc_type=Dictionary, objc_name="dictionaryWithObjects", objc_is_class_method=true) Dictionary_dictionaryWithObjects :: proc "c" (objects: [^]^Object, forKeys: [^]^Object, count: UInteger) -> ^Dictionary { - return msgSend(^Dictionary, Dictionary, "dictionaryWithObjects:forKeys:count", objects, forKeys, count) + return msgSend(^Dictionary, Dictionary, "dictionaryWithObjects:forKeys:count:", objects, forKeys, count) } @@ -31,7 +31,7 @@ Dictionary_init :: proc "c" (self: ^Dictionary) -> ^Dictionary { @(objc_type=Dictionary, objc_name="initWithObjects") Dictionary_initWithObjects :: proc "c" (self: ^Dictionary, objects: [^]^Object, forKeys: [^]^Object, count: UInteger) -> ^Dictionary { - return msgSend(^Dictionary, self, "initWithObjects:forKeys:count", objects, forKeys, count) + return msgSend(^Dictionary, self, "initWithObjects:forKeys:count:", objects, forKeys, count) } @(objc_type=Dictionary, objc_name="objectForKey") diff --git a/core/sys/darwin/Foundation/NSMenu.odin b/core/sys/darwin/Foundation/NSMenu.odin index 9a74151b0..747920ab7 100644 --- a/core/sys/darwin/Foundation/NSMenu.odin +++ b/core/sys/darwin/Foundation/NSMenu.odin @@ -2,127 +2,562 @@ package objc_Foundation import "base:builtin" import "base:intrinsics" +import "core:c" -KeyEquivalentModifierFlag :: enum UInteger { - CapsLock = 16, // Set if Caps Lock key is pressed. - Shift = 17, // Set if Shift key is pressed. - Control = 18, // Set if Control key is pressed. - Option = 19, // Set if Option or Alternate key is pressed. - Command = 20, // Set if Command key is pressed. - NumericPad = 21, // Set if any key in the numeric keypad is pressed. - Help = 22, // Set if the Help key is pressed. - Function = 23, // Set if any function key is pressed. + +MenuSelectionMode :: enum c.long { + Automatic = 0, + SelectOne = 1, + SelectAny = 2, } -KeyEquivalentModifierMask :: distinct bit_set[KeyEquivalentModifierFlag; UInteger] -// Used to retrieve only the device-independent modifier flags, allowing applications to mask off the device-dependent modifier flags, including event coalescing information. -KeyEventModifierFlagDeviceIndependentFlagsMask := transmute(KeyEquivalentModifierMask)_KeyEventModifierFlagDeviceIndependentFlagsMask -@(private) _KeyEventModifierFlagDeviceIndependentFlagsMask := UInteger(0xffff0000) +MenuPresentationStyle :: enum c.long { + Regular = 0, + Palette = 1, +} +UserInterfaceLayoutDirection :: enum c.long { + LeftToRight = 0, + RightToLeft = 1, +} -MenuItemCallback :: proc "c" (unused: rawptr, name: SEL, sender: ^Object) +MenuPropertyItem :: enum c.ulong { + Title = 0, + AttributedTitle = 1, + KeyEquivalent = 2, + Image = 3, + Enabled = 4, + AccessibilityDescription = 5, +} +MenuProperties :: distinct bit_set[MenuPropertyItem; c.ulong] -@(objc_class="NSMenuItem") -MenuItem :: struct {using _: Object} +@(objc_class="NSMenu") +Menu :: struct {using _: Object} -@(objc_type=MenuItem, objc_name="alloc", objc_is_class_method=true) -MenuItem_alloc :: proc "c" () -> ^MenuItem { - return msgSend(^MenuItem, MenuItem, "alloc") +@(objc_type=Menu, objc_name="init") +Menu_init :: proc "c" (self: ^Menu) -> ^Menu { + return msgSend(^Menu, self, "init") } -@(objc_type=MenuItem, objc_name="registerActionCallback", objc_is_class_method=true) -MenuItem_registerActionCallback :: proc "c" (name: cstring, callback: MenuItemCallback) -> SEL { - s := string(name) - n := len(s) - sel: SEL - if n > 0 && s[n-1] != ':' { - col_name := intrinsics.alloca(n+2, 1) - builtin.copy(col_name[:n], s) - col_name[n] = ':' - col_name[n+1] = 0 - sel = sel_registerName(cstring(col_name)) - } else { - sel = sel_registerName(name) - } - if callback != nil { - class_addMethod(intrinsics.objc_find_class("NSObject"), sel, auto_cast callback, "v@:@") - } - return sel -} -@(objc_type=MenuItem, objc_name="separatorItem", objc_is_class_method=true) -MenuItem_separatorItem :: proc "c" () -> ^MenuItem { - return msgSend(^MenuItem, MenuItem, "separatorItem") +@(objc_type=Menu, objc_name="initWithTitle") +Menu_initWithTitle :: #force_inline proc "c" (self: ^Menu, title: ^String) -> ^Menu { + return msgSend(^Menu, self, "initWithTitle:", title) } - -@(objc_type=MenuItem, objc_name="init") -MenuItem_init :: proc "c" (self: ^MenuItem) -> ^MenuItem { - return msgSend(^MenuItem, self, "init") +@(objc_type=Menu, objc_name="initWithCoder") +Menu_initWithCoder :: #force_inline proc "c" (self: ^Menu, coder: ^Coder) -> ^Menu { + return msgSend(^Menu, self, "initWithCoder:", coder) } - -@(objc_type=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=Menu, objc_name="popUpContextMenu_withEvent_forView", objc_is_class_method=true) +Menu_popUpContextMenu_withEvent_forView :: #force_inline proc "c" (menu: ^Menu, event: ^Event, view: ^View) { + msgSend(nil, Menu, "popUpContextMenu:withEvent:forView:", menu, event, view) } - -@(objc_type=MenuItem, objc_name="setKeyEquivalentModifierMask") -MenuItem_setKeyEquivalentModifierMask :: proc "c" (self: ^MenuItem, modifierMask: KeyEquivalentModifierMask) { - msgSend(nil, self, "setKeyEquivalentModifierMask:", modifierMask) +// @(objc_type=Menu, objc_name="popUpContextMenu_withEvent_forView_withFont", objc_is_class_method=true) +// Menu_popUpContextMenu_withEvent_forView_withFont :: #force_inline proc "c" (menu: ^Menu, event: ^Event, view: ^View, font: ^Font) { +// msgSend(nil, Menu, "popUpContextMenu:withEvent:forView:withFont:", menu, event, view, font) +// } +@(objc_type=Menu, objc_name="popUpMenuPositioningItem") +Menu_popUpMenuPositioningItem :: #force_inline proc "c" (self: ^Menu, item: ^MenuItem, location: Point, view: ^View) -> bool { + return msgSend(bool, self, "popUpMenuPositioningItem:atLocation:inView:", item, location, view) } - -@(objc_type=MenuItem, objc_name="keyEquivalentModifierMask") -MenuItem_keyEquivalentModifierMask :: proc "c" (self: ^MenuItem) -> KeyEquivalentModifierMask { - return msgSend(KeyEquivalentModifierMask, self, "keyEquivalentModifierMask") +@(objc_type=Menu, objc_name="setMenuBarVisible", objc_is_class_method=true) +Menu_setMenuBarVisible :: #force_inline proc "c" (visible: bool) { + msgSend(nil, Menu, "setMenuBarVisible:", visible) } - -@(objc_type=MenuItem, objc_name="setSubmenu") -MenuItem_setSubmenu :: proc "c" (self: ^MenuItem, submenu: ^Menu) { - msgSend(nil, self, "setSubmenu:", submenu) +@(objc_type=Menu, objc_name="menuBarVisible", objc_is_class_method=true) +Menu_menuBarVisible :: #force_inline proc "c" () -> bool { + return msgSend(bool, Menu, "menuBarVisible") } - -@(objc_type=MenuItem, objc_name="title") -MenuItem_title :: proc "c" (self: ^MenuItem) -> ^String { +@(objc_type=Menu, objc_name="insertItem") +Menu_insertItem :: #force_inline proc "c" (self: ^Menu, newItem: ^MenuItem, index: Integer) { + msgSend(nil, self, "insertItem:atIndex:", newItem, index) +} +@(objc_type=Menu, objc_name="addItem") +Menu_addItem :: #force_inline proc "c" (self: ^Menu, newItem: ^MenuItem) { + msgSend(nil, self, "addItem:", newItem) +} +@(objc_type=Menu, objc_name="insertItemWithTitle") +Menu_insertItemWithTitle :: #force_inline proc "c" (self: ^Menu, string: ^String, selector: SEL, charCode: ^String, index: Integer) -> ^MenuItem { + return msgSend(^MenuItem, self, "insertItemWithTitle:action:keyEquivalent:atIndex:", string, selector, charCode, index) +} +@(objc_type=Menu, objc_name="addItemWithTitle") +Menu_addItemWithTitle :: #force_inline proc "c" (self: ^Menu, string: ^String, selector: SEL, charCode: ^String) -> ^MenuItem { + return msgSend(^MenuItem, self, "addItemWithTitle:action:keyEquivalent:", string, selector, charCode) +} +@(objc_type=Menu, objc_name="removeItemAtIndex") +Menu_removeItemAtIndex :: #force_inline proc "c" (self: ^Menu, index: Integer) { + msgSend(nil, self, "removeItemAtIndex:", index) +} +@(objc_type=Menu, objc_name="removeItem") +Menu_removeItem :: #force_inline proc "c" (self: ^Menu, item: ^MenuItem) { + msgSend(nil, self, "removeItem:", item) +} +@(objc_type=Menu, objc_name="setSubmenu") +Menu_setSubmenu :: #force_inline proc "c" (self: ^Menu, menu: ^Menu, item: ^MenuItem) { + msgSend(nil, self, "setSubmenu:forItem:", menu, item) +} +@(objc_type=Menu, objc_name="removeAllItems") +Menu_removeAllItems :: #force_inline proc "c" (self: ^Menu) { + msgSend(nil, self, "removeAllItems") +} +@(objc_type=Menu, objc_name="itemAtIndex") +Menu_itemAtIndex :: #force_inline proc "c" (self: ^Menu, index: Integer) -> ^MenuItem { + return msgSend(^MenuItem, self, "itemAtIndex:", index) +} +@(objc_type=Menu, objc_name="indexOfItem") +Menu_indexOfItem :: #force_inline proc "c" (self: ^Menu, item: ^MenuItem) -> Integer { + return msgSend(Integer, self, "indexOfItem:", item) +} +@(objc_type=Menu, objc_name="indexOfItemWithTitle") +Menu_indexOfItemWithTitle :: #force_inline proc "c" (self: ^Menu, title: ^String) -> Integer { + return msgSend(Integer, self, "indexOfItemWithTitle:", title) +} +@(objc_type=Menu, objc_name="indexOfItemWithTag") +Menu_indexOfItemWithTag :: #force_inline proc "c" (self: ^Menu, tag: Integer) -> Integer { + return msgSend(Integer, self, "indexOfItemWithTag:", tag) +} +@(objc_type=Menu, objc_name="indexOfItemWithRepresentedObject") +Menu_indexOfItemWithRepresentedObject :: #force_inline proc "c" (self: ^Menu, object: id) -> Integer { + return msgSend(Integer, self, "indexOfItemWithRepresentedObject:", object) +} +@(objc_type=Menu, objc_name="indexOfItemWithSubmenu") +Menu_indexOfItemWithSubmenu :: #force_inline proc "c" (self: ^Menu, submenu: ^Menu) -> Integer { + return msgSend(Integer, self, "indexOfItemWithSubmenu:", submenu) +} +@(objc_type=Menu, objc_name="indexOfItemWithTarget") +Menu_indexOfItemWithTarget :: #force_inline proc "c" (self: ^Menu, target: id, actionSelector: SEL) -> Integer { + return msgSend(Integer, self, "indexOfItemWithTarget:andAction:", target, actionSelector) +} +@(objc_type=Menu, objc_name="itemWithTitle") +Menu_itemWithTitle :: #force_inline proc "c" (self: ^Menu, title: ^String) -> ^MenuItem { + return msgSend(^MenuItem, self, "itemWithTitle:", title) +} +@(objc_type=Menu, objc_name="itemWithTag") +Menu_itemWithTag :: #force_inline proc "c" (self: ^Menu, tag: Integer) -> ^MenuItem { + return msgSend(^MenuItem, self, "itemWithTag:", tag) +} +@(objc_type=Menu, objc_name="update") +Menu_update :: #force_inline proc "c" (self: ^Menu) { + msgSend(nil, self, "update") +} +@(objc_type=Menu, objc_name="performKeyEquivalent") +Menu_performKeyEquivalent :: #force_inline proc "c" (self: ^Menu, event: ^Event) -> bool { + return msgSend(bool, self, "performKeyEquivalent:", event) +} +@(objc_type=Menu, objc_name="itemChanged") +Menu_itemChanged :: #force_inline proc "c" (self: ^Menu, item: ^MenuItem) { + msgSend(nil, self, "itemChanged:", item) +} +@(objc_type=Menu, objc_name="performActionForItemAtIndex") +Menu_performActionForItemAtIndex :: #force_inline proc "c" (self: ^Menu, index: Integer) { + msgSend(nil, self, "performActionForItemAtIndex:", index) +} +@(objc_type=Menu, objc_name="cancelTracking") +Menu_cancelTracking :: #force_inline proc "c" (self: ^Menu) { + msgSend(nil, self, "cancelTracking") +} +@(objc_type=Menu, objc_name="cancelTrackingWithoutAnimation") +Menu_cancelTrackingWithoutAnimation :: #force_inline proc "c" (self: ^Menu) { + msgSend(nil, self, "cancelTrackingWithoutAnimation") +} +@(objc_type=Menu, objc_name="title") +Menu_title :: #force_inline proc "c" (self: ^Menu) -> ^String { return msgSend(^String, self, "title") } - -@(objc_type=MenuItem, objc_name="setTitle") -MenuItem_setTitle :: proc "c" (self: ^MenuItem, title: ^String) -> ^String { - return msgSend(^String, self, "title:", title) +@(objc_type=Menu, objc_name="setTitle") +Menu_setTitle :: #force_inline proc "c" (self: ^Menu, title: ^String) { + msgSend(nil, self, "setTitle:", title) +} +@(objc_type=Menu, objc_name="supermenu") +Menu_supermenu :: #force_inline proc "c" (self: ^Menu) -> ^Menu { + return msgSend(^Menu, self, "supermenu") +} +@(objc_type=Menu, objc_name="setSupermenu") +Menu_setSupermenu :: #force_inline proc "c" (self: ^Menu, supermenu: ^Menu) { + msgSend(nil, self, "setSupermenu:", supermenu) +} +@(objc_type=Menu, objc_name="itemArray") +Menu_itemArray :: #force_inline proc "c" (self: ^Menu) -> ^Array { + return msgSend(^Array, self, "itemArray") +} +@(objc_type=Menu, objc_name="setItemArray") +Menu_setItemArray :: #force_inline proc "c" (self: ^Menu, itemArray: ^Array) { + msgSend(nil, self, "setItemArray:", itemArray) +} +@(objc_type=Menu, objc_name="numberOfItems") +Menu_numberOfItems :: #force_inline proc "c" (self: ^Menu) -> Integer { + return msgSend(Integer, self, "numberOfItems") +} +@(objc_type=Menu, objc_name="autoenablesItems") +Menu_autoenablesItems :: #force_inline proc "c" (self: ^Menu) -> bool { + return msgSend(bool, self, "autoenablesItems") +} +@(objc_type=Menu, objc_name="setAutoenablesItems") +Menu_setAutoenablesItems :: #force_inline proc "c" (self: ^Menu, autoenablesItems: bool) { + msgSend(nil, self, "setAutoenablesItems:", autoenablesItems) +} +@(objc_type=Menu, objc_name="delegate") +Menu_delegate :: #force_inline proc "c" (self: ^Menu) -> ^MenuDelegate { + return msgSend(^MenuDelegate, self, "delegate") +} +@(objc_type=Menu, objc_name="setDelegate") +Menu_setDelegate :: #force_inline proc "c" (self: ^Menu, delegate: ^MenuDelegate) { + msgSend(nil, self, "setDelegate:", delegate) +} +@(objc_type=Menu, objc_name="menuBarHeight") +Menu_menuBarHeight :: #force_inline proc "c" (self: ^Menu) -> Float { + return msgSend(Float, self, "menuBarHeight") +} +@(objc_type=Menu, objc_name="highlightedItem") +Menu_highlightedItem :: #force_inline proc "c" (self: ^Menu) -> ^MenuItem { + return msgSend(^MenuItem, self, "highlightedItem") +} +@(objc_type=Menu, objc_name="minimumWidth") +Menu_minimumWidth :: #force_inline proc "c" (self: ^Menu) -> Float { + return msgSend(Float, self, "minimumWidth") +} +@(objc_type=Menu, objc_name="setMinimumWidth") +Menu_setMinimumWidth :: #force_inline proc "c" (self: ^Menu, minimumWidth: Float) { + msgSend(nil, self, "setMinimumWidth:", minimumWidth) +} +@(objc_type=Menu, objc_name="size") +Menu_size :: #force_inline proc "c" (self: ^Menu) -> Size { + return msgSend(Size, self, "size") +} +// @(objc_type=Menu, objc_name="font") +// Menu_font :: #force_inline proc "c" (self: ^Menu) -> ^Font { +// return msgSend(^Font, self, "font") +// } +// @(objc_type=Menu, objc_name="setFont") +// Menu_setFont :: #force_inline proc "c" (self: ^Menu, font: ^Font) { +// msgSend(nil, self, "setFont:", font) +// } +@(objc_type=Menu, objc_name="allowsContextMenuPlugIns") +Menu_allowsContextMenuPlugIns :: #force_inline proc "c" (self: ^Menu) -> bool { + return msgSend(bool, self, "allowsContextMenuPlugIns") +} +@(objc_type=Menu, objc_name="setAllowsContextMenuPlugIns") +Menu_setAllowsContextMenuPlugIns :: #force_inline proc "c" (self: ^Menu, allowsContextMenuPlugIns: bool) { + msgSend(nil, self, "setAllowsContextMenuPlugIns:", allowsContextMenuPlugIns) +} +@(objc_type=Menu, objc_name="showsStateColumn") +Menu_showsStateColumn :: #force_inline proc "c" (self: ^Menu) -> bool { + return msgSend(bool, self, "showsStateColumn") +} +@(objc_type=Menu, objc_name="setShowsStateColumn") +Menu_setShowsStateColumn :: #force_inline proc "c" (self: ^Menu, showsStateColumn: bool) { + msgSend(nil, self, "setShowsStateColumn:", showsStateColumn) +} +@(objc_type=Menu, objc_name="userInterfaceLayoutDirection") +Menu_userInterfaceLayoutDirection :: #force_inline proc "c" (self: ^Menu) -> UserInterfaceLayoutDirection { + return msgSend(UserInterfaceLayoutDirection, self, "userInterfaceLayoutDirection") +} +@(objc_type=Menu, objc_name="setUserInterfaceLayoutDirection") +Menu_setUserInterfaceLayoutDirection :: #force_inline proc "c" (self: ^Menu, userInterfaceLayoutDirection: UserInterfaceLayoutDirection) { + msgSend(nil, self, "setUserInterfaceLayoutDirection:", userInterfaceLayoutDirection) +} +@(objc_type=Menu, objc_name="paletteMenuWithColors_titles_selectionHandler", objc_is_class_method=true) +Menu_paletteMenuWithColors_titles_selectionHandler :: #force_inline proc "c" (colors: ^Array, itemTitles: ^Array, onSelectionChange: proc "c" (_arg_0: ^Menu)) -> ^Menu { + return msgSend(^Menu, Menu, "paletteMenuWithColors:titles:selectionHandler:", colors, itemTitles, onSelectionChange) +} +// @(objc_type=Menu, objc_name="paletteMenuWithColors_titles_templateImage_selectionHandler", objc_is_class_method=true) +// Menu_paletteMenuWithColors_titles_templateImage_selectionHandler :: #force_inline proc "c" (colors: ^Array, itemTitles: ^Array, image: ^Image, onSelectionChange: proc "c" (_arg_0: ^Menu)) -> ^Menu { +// return msgSend(^Menu, Menu, "paletteMenuWithColors:titles:templateImage:selectionHandler:", colors, itemTitles, image, onSelectionChange) +// } +@(objc_type=Menu, objc_name="presentationStyle") +Menu_presentationStyle :: #force_inline proc "c" (self: ^Menu) -> MenuPresentationStyle { + return msgSend(MenuPresentationStyle, self, "presentationStyle") +} +@(objc_type=Menu, objc_name="setPresentationStyle") +Menu_setPresentationStyle :: #force_inline proc "c" (self: ^Menu, presentationStyle: MenuPresentationStyle) { + msgSend(nil, self, "setPresentationStyle:", presentationStyle) +} +@(objc_type=Menu, objc_name="selectionMode") +Menu_selectionMode :: #force_inline proc "c" (self: ^Menu) -> MenuSelectionMode { + return msgSend(MenuSelectionMode, self, "selectionMode") +} +@(objc_type=Menu, objc_name="setSelectionMode") +Menu_setSelectionMode :: #force_inline proc "c" (self: ^Menu, selectionMode: MenuSelectionMode) { + msgSend(nil, self, "setSelectionMode:", selectionMode) +} +@(objc_type=Menu, objc_name="selectedItems") +Menu_selectedItems :: #force_inline proc "c" (self: ^Menu) -> ^Array { + return msgSend(^Array, self, "selectedItems") +} +@(objc_type=Menu, objc_name="setSelectedItems") +Menu_setSelectedItems :: #force_inline proc "c" (self: ^Menu, selectedItems: ^Array) { + msgSend(nil, self, "setSelectedItems:", selectedItems) +} +@(objc_type=Menu, objc_name="submenuAction") +Menu_submenuAction :: #force_inline proc "c" (self: ^Menu, sender: id) { + msgSend(nil, self, "submenuAction:", sender) +} +@(objc_type=Menu, objc_name="propertiesToUpdate") +Menu_propertiesToUpdate :: #force_inline proc "c" (self: ^Menu) -> MenuProperties { + return msgSend(MenuProperties, self, "propertiesToUpdate") +} +@(objc_type=Menu, objc_name="setMenuRepresentation") +Menu_setMenuRepresentation :: #force_inline proc "c" (self: ^Menu, menuRep: id) { + msgSend(nil, self, "setMenuRepresentation:", menuRep) +} +@(objc_type=Menu, objc_name="menuRepresentation") +Menu_menuRepresentation :: #force_inline proc "c" (self: ^Menu) -> id { + return msgSend(id, self, "menuRepresentation") +} +@(objc_type=Menu, objc_name="setContextMenuRepresentation") +Menu_setContextMenuRepresentation :: #force_inline proc "c" (self: ^Menu, menuRep: id) { + msgSend(nil, self, "setContextMenuRepresentation:", menuRep) +} +@(objc_type=Menu, objc_name="contextMenuRepresentation") +Menu_contextMenuRepresentation :: #force_inline proc "c" (self: ^Menu) -> id { + return msgSend(id, self, "contextMenuRepresentation") +} +@(objc_type=Menu, objc_name="setTearOffMenuRepresentation") +Menu_setTearOffMenuRepresentation :: #force_inline proc "c" (self: ^Menu, menuRep: id) { + msgSend(nil, self, "setTearOffMenuRepresentation:", menuRep) +} +@(objc_type=Menu, objc_name="tearOffMenuRepresentation") +Menu_tearOffMenuRepresentation :: #force_inline proc "c" (self: ^Menu) -> id { + return msgSend(id, self, "tearOffMenuRepresentation") +} +@(objc_type=Menu, objc_name="menuZone", objc_is_class_method=true) +Menu_menuZone :: #force_inline proc "c" () -> ^Zone { + return msgSend(^Zone, Menu, "menuZone") +} +@(objc_type=Menu, objc_name="setMenuZone", objc_is_class_method=true) +Menu_setMenuZone :: #force_inline proc "c" (zone: ^Zone) { + msgSend(nil, Menu, "setMenuZone:", zone) +} +@(objc_type=Menu, objc_name="attachedMenu") +Menu_attachedMenu :: #force_inline proc "c" (self: ^Menu) -> ^Menu { + return msgSend(^Menu, self, "attachedMenu") +} +@(objc_type=Menu, objc_name="isAttached") +Menu_isAttached :: #force_inline proc "c" (self: ^Menu) -> bool { + return msgSend(bool, self, "isAttached") +} +@(objc_type=Menu, objc_name="sizeToFit") +Menu_sizeToFit :: #force_inline proc "c" (self: ^Menu) { + msgSend(nil, self, "sizeToFit") +} +@(objc_type=Menu, objc_name="locationForSubmenu") +Menu_locationForSubmenu :: #force_inline proc "c" (self: ^Menu, submenu: ^Menu) -> Point { + return msgSend(Point, self, "locationForSubmenu:", submenu) +} +@(objc_type=Menu, objc_name="helpRequested") +Menu_helpRequested :: #force_inline proc "c" (self: ^Menu, eventPtr: ^Event) { + msgSend(nil, self, "helpRequested:", eventPtr) +} +@(objc_type=Menu, objc_name="menuChangedMessagesEnabled") +Menu_menuChangedMessagesEnabled :: #force_inline proc "c" (self: ^Menu) -> bool { + return msgSend(bool, self, "menuChangedMessagesEnabled") +} +@(objc_type=Menu, objc_name="setMenuChangedMessagesEnabled") +Menu_setMenuChangedMessagesEnabled :: #force_inline proc "c" (self: ^Menu, menuChangedMessagesEnabled: bool) { + msgSend(nil, self, "setMenuChangedMessagesEnabled:", menuChangedMessagesEnabled) +} +@(objc_type=Menu, objc_name="isTornOff") +Menu_isTornOff :: #force_inline proc "c" (self: ^Menu) -> bool { + return msgSend(bool, self, "isTornOff") +} +@(objc_type=Menu, objc_name="load", objc_is_class_method=true) +Menu_load :: #force_inline proc "c" () { + msgSend(nil, Menu, "load") +} +@(objc_type=Menu, objc_name="initialize", objc_is_class_method=true) +Menu_initialize :: #force_inline proc "c" () { + msgSend(nil, Menu, "initialize") +} +@(objc_type=Menu, objc_name="new", objc_is_class_method=true) +Menu_new :: #force_inline proc "c" () -> ^Menu { + return msgSend(^Menu, Menu, "new") +} +@(objc_type=Menu, objc_name="allocWithZone", objc_is_class_method=true) +Menu_allocWithZone :: #force_inline proc "c" (zone: ^Zone) -> ^Menu { + return msgSend(^Menu, Menu, "allocWithZone:", zone) } - - - -@(objc_class="NSMenu") -Menu :: struct {using _: Object} - @(objc_type=Menu, objc_name="alloc", objc_is_class_method=true) -Menu_alloc :: proc "c" () -> ^Menu { +Menu_alloc :: #force_inline proc "c" () -> ^Menu { return msgSend(^Menu, Menu, "alloc") } +@(objc_type=Menu, objc_name="copyWithZone", objc_is_class_method=true) +Menu_copyWithZone :: #force_inline proc "c" (zone: ^Zone) -> id { + return msgSend(id, Menu, "copyWithZone:", zone) +} +@(objc_type=Menu, objc_name="mutableCopyWithZone", objc_is_class_method=true) +Menu_mutableCopyWithZone :: #force_inline proc "c" (zone: ^Zone) -> id { + return msgSend(id, Menu, "mutableCopyWithZone:", zone) +} +@(objc_type=Menu, objc_name="instancesRespondToSelector", objc_is_class_method=true) +Menu_instancesRespondToSelector :: #force_inline proc "c" (aSelector: SEL) -> bool { + return msgSend(bool, Menu, "instancesRespondToSelector:", aSelector) +} +@(objc_type=Menu, objc_name="conformsToProtocol", objc_is_class_method=true) +Menu_conformsToProtocol :: #force_inline proc "c" (protocol: ^Protocol) -> bool { + return msgSend(bool, Menu, "conformsToProtocol:", protocol) +} +@(objc_type=Menu, objc_name="instanceMethodForSelector", objc_is_class_method=true) +Menu_instanceMethodForSelector :: #force_inline proc "c" (aSelector: SEL) -> IMP { + return msgSend(IMP, Menu, "instanceMethodForSelector:", aSelector) +} +// @(objc_type=Menu, objc_name="instanceMethodSignatureForSelector", objc_is_class_method=true) +// Menu_instanceMethodSignatureForSelector :: #force_inline proc "c" (aSelector: SEL) -> ^MethodSignature { +// return msgSend(^MethodSignature, Menu, "instanceMethodSignatureForSelector:", aSelector) +// } +@(objc_type=Menu, objc_name="isSubclassOfClass", objc_is_class_method=true) +Menu_isSubclassOfClass :: #force_inline proc "c" (aClass: Class) -> bool { + return msgSend(bool, Menu, "isSubclassOfClass:", aClass) +} +@(objc_type=Menu, objc_name="resolveClassMethod", objc_is_class_method=true) +Menu_resolveClassMethod :: #force_inline proc "c" (sel: SEL) -> bool { + return msgSend(bool, Menu, "resolveClassMethod:", sel) +} +@(objc_type=Menu, objc_name="resolveInstanceMethod", objc_is_class_method=true) +Menu_resolveInstanceMethod :: #force_inline proc "c" (sel: SEL) -> bool { + return msgSend(bool, Menu, "resolveInstanceMethod:", sel) +} +@(objc_type=Menu, objc_name="hash", objc_is_class_method=true) +Menu_hash :: #force_inline proc "c" () -> UInteger { + return msgSend(UInteger, Menu, "hash") +} +@(objc_type=Menu, objc_name="superclass", objc_is_class_method=true) +Menu_superclass :: #force_inline proc "c" () -> Class { + return msgSend(Class, Menu, "superclass") +} +@(objc_type=Menu, objc_name="class", objc_is_class_method=true) +Menu_class :: #force_inline proc "c" () -> Class { + return msgSend(Class, Menu, "class") +} +@(objc_type=Menu, objc_name="description", objc_is_class_method=true) +Menu_description :: #force_inline proc "c" () -> ^String { + return msgSend(^String, Menu, "description") +} +@(objc_type=Menu, objc_name="debugDescription", objc_is_class_method=true) +Menu_debugDescription :: #force_inline proc "c" () -> ^String { + return msgSend(^String, Menu, "debugDescription") +} +@(objc_type=Menu, objc_name="version", objc_is_class_method=true) +Menu_version :: #force_inline proc "c" () -> Integer { + return msgSend(Integer, Menu, "version") +} +@(objc_type=Menu, objc_name="setVersion", objc_is_class_method=true) +Menu_setVersion :: #force_inline proc "c" (aVersion: Integer) { + msgSend(nil, Menu, "setVersion:", aVersion) +} +@(objc_type=Menu, objc_name="poseAsClass", objc_is_class_method=true) +Menu_poseAsClass :: #force_inline proc "c" (aClass: Class) { + msgSend(nil, Menu, "poseAsClass:", aClass) +} +@(objc_type=Menu, objc_name="cancelPreviousPerformRequestsWithTarget_selector_object", objc_is_class_method=true) +Menu_cancelPreviousPerformRequestsWithTarget_selector_object :: #force_inline proc "c" (aTarget: id, aSelector: SEL, anArgument: id) { + msgSend(nil, Menu, "cancelPreviousPerformRequestsWithTarget:selector:object:", aTarget, aSelector, anArgument) +} +@(objc_type=Menu, objc_name="cancelPreviousPerformRequestsWithTarget_", objc_is_class_method=true) +Menu_cancelPreviousPerformRequestsWithTarget_ :: #force_inline proc "c" (aTarget: id) { + msgSend(nil, Menu, "cancelPreviousPerformRequestsWithTarget:", aTarget) +} +@(objc_type=Menu, objc_name="accessInstanceVariablesDirectly", objc_is_class_method=true) +Menu_accessInstanceVariablesDirectly :: #force_inline proc "c" () -> bool { + return msgSend(bool, Menu, "accessInstanceVariablesDirectly") +} +@(objc_type=Menu, objc_name="useStoredAccessor", objc_is_class_method=true) +Menu_useStoredAccessor :: #force_inline proc "c" () -> bool { + return msgSend(bool, Menu, "useStoredAccessor") +} +@(objc_type=Menu, objc_name="keyPathsForValuesAffectingValueForKey", objc_is_class_method=true) +Menu_keyPathsForValuesAffectingValueForKey :: #force_inline proc "c" (key: ^String) -> ^Set { + return msgSend(^Set, Menu, "keyPathsForValuesAffectingValueForKey:", key) +} +@(objc_type=Menu, objc_name="automaticallyNotifiesObserversForKey", objc_is_class_method=true) +Menu_automaticallyNotifiesObserversForKey :: #force_inline proc "c" (key: ^String) -> bool { + return msgSend(bool, Menu, "automaticallyNotifiesObserversForKey:", key) +} +@(objc_type=Menu, objc_name="setKeys", objc_is_class_method=true) +Menu_setKeys :: #force_inline proc "c" (keys: ^Array, dependentKey: ^String) { + msgSend(nil, Menu, "setKeys:triggerChangeNotificationsForDependentKey:", keys, dependentKey) +} +@(objc_type=Menu, objc_name="classFallbacksForKeyedArchiver", objc_is_class_method=true) +Menu_classFallbacksForKeyedArchiver :: #force_inline proc "c" () -> ^Array { + return msgSend(^Array, Menu, "classFallbacksForKeyedArchiver") +} +@(objc_type=Menu, objc_name="classForKeyedUnarchiver", objc_is_class_method=true) +Menu_classForKeyedUnarchiver :: #force_inline proc "c" () -> Class { + return msgSend(Class, Menu, "classForKeyedUnarchiver") +} +@(objc_type=Menu, objc_name="exposeBinding", objc_is_class_method=true) +Menu_exposeBinding :: #force_inline proc "c" (binding: ^String) { + msgSend(nil, Menu, "exposeBinding:", binding) +} +@(objc_type=Menu, objc_name="setDefaultPlaceholder", objc_is_class_method=true) +Menu_setDefaultPlaceholder :: #force_inline proc "c" (placeholder: id, marker: id, binding: ^String) { + msgSend(nil, Menu, "setDefaultPlaceholder:forMarker:withBinding:", placeholder, marker, binding) +} +@(objc_type=Menu, objc_name="defaultPlaceholderForMarker", objc_is_class_method=true) +Menu_defaultPlaceholderForMarker :: #force_inline proc "c" (marker: id, binding: ^String) -> id { + return msgSend(id, Menu, "defaultPlaceholderForMarker:withBinding:", marker, binding) +} +@(objc_type=Menu, objc_name="popUpContextMenu") +Menu_popUpContextMenu :: proc { + Menu_popUpContextMenu_withEvent_forView, + // Menu_popUpContextMenu_withEvent_forView_withFont, +} -@(objc_type=Menu, objc_name="init") -Menu_init :: proc "c" (self: ^Menu) -> ^Menu { - return msgSend(^Menu, self, "init") +@(objc_type=Menu, objc_name="paletteMenuWithColors") +Menu_paletteMenuWithColors :: proc { + Menu_paletteMenuWithColors_titles_selectionHandler, + // Menu_paletteMenuWithColors_titles_templateImage_selectionHandler, } -@(objc_type=Menu, objc_name="initWithTitle") -Menu_initWithTitle :: proc "c" (self: ^Menu, title: ^String) -> ^Menu { - return msgSend(^Menu, self, "initWithTitle:", title) +@(objc_type=Menu, objc_name="cancelPreviousPerformRequestsWithTarget") +Menu_cancelPreviousPerformRequestsWithTarget :: proc { + Menu_cancelPreviousPerformRequestsWithTarget_selector_object, + Menu_cancelPreviousPerformRequestsWithTarget_, } -@(objc_type=Menu, objc_name="addItem") -Menu_addItem :: proc "c" (self: ^Menu, item: ^MenuItem) { - msgSend(nil, self, "addItem:", item) -} -@(objc_type=Menu, objc_name="addItemWithTitle") -Menu_addItemWithTitle :: proc "c" (self: ^Menu, title: ^String, selector: SEL, keyEquivalent: ^String) -> ^MenuItem { - return msgSend(^MenuItem, self, "addItemWithTitle:action:keyEquivalent:", title, selector, keyEquivalent) -} -@(objc_type=Menu, objc_name="itemArray") -Menu_itemArray :: proc "c" (self: ^Menu) -> ^Array { - return msgSend(^Array, self, "itemArray") -}
\ No newline at end of file + + + +@(objc_class="NSMenuDelegate") +MenuDelegate :: struct {using _: Object, using _: ObjectProtocol} + +@(objc_type=MenuDelegate, objc_name="menuNeedsUpdate") +MenuDelegate_menuNeedsUpdate :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu) { + msgSend(nil, self, "menuNeedsUpdate:", menu) +} +@(objc_type=MenuDelegate, objc_name="numberOfItemsInMenu") +MenuDelegate_numberOfItemsInMenu :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu) -> Integer { + return msgSend(Integer, self, "numberOfItemsInMenu:", menu) +} +@(objc_type=MenuDelegate, objc_name="menu_updateItem_atIndex_shouldCancel") +MenuDelegate_menu_updateItem_atIndex_shouldCancel :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu, item: ^MenuItem, index: Integer, shouldCancel: bool) -> bool { + return msgSend(bool, self, "menu:updateItem:atIndex:shouldCancel:", menu, item, index, shouldCancel) +} +@(objc_type=MenuDelegate, objc_name="menuHasKeyEquivalent") +MenuDelegate_menuHasKeyEquivalent :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu, event: ^Event, target: ^id, action: ^SEL) -> bool { + return msgSend(bool, self, "menuHasKeyEquivalent:forEvent:target:action:", menu, event, target, action) +} +@(objc_type=MenuDelegate, objc_name="menuWillOpen") +MenuDelegate_menuWillOpen :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu) { + msgSend(nil, self, "menuWillOpen:", menu) +} +@(objc_type=MenuDelegate, objc_name="menuDidClose") +MenuDelegate_menuDidClose :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu) { + msgSend(nil, self, "menuDidClose:", menu) +} +@(objc_type=MenuDelegate, objc_name="menu_willHighlightItem") +MenuDelegate_menu_willHighlightItem :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu, item: ^MenuItem) { + msgSend(nil, self, "menu:willHighlightItem:", menu, item) +} +@(objc_type=MenuDelegate, objc_name="confinementRectForMenu") +MenuDelegate_confinementRectForMenu :: #force_inline proc "c" (self: ^MenuDelegate, menu: ^Menu, screen: ^Screen) -> Rect { + return msgSend(Rect, self, "confinementRectForMenu:onScreen:", menu, screen) +} +@(objc_type=MenuDelegate, objc_name="menu") +MenuDelegate_menu :: proc { + MenuDelegate_menu_updateItem_atIndex_shouldCancel, + MenuDelegate_menu_willHighlightItem, +} diff --git a/core/sys/darwin/Foundation/NSMenuItem.odin b/core/sys/darwin/Foundation/NSMenuItem.odin new file mode 100644 index 000000000..248a0cf4f --- /dev/null +++ b/core/sys/darwin/Foundation/NSMenuItem.odin @@ -0,0 +1,460 @@ +package objc_Foundation + +import "base:builtin" +import "base:intrinsics" + +KeyEquivalentModifierFlag :: EventModifierFlag +KeyEquivalentModifierMask :: EventModifierFlags + +// Used to retrieve only the device-independent modifier flags, allowing applications to mask off the device-dependent modifier flags, including event coalescing information. +KeyEventModifierFlagDeviceIndependentFlagsMask := transmute(KeyEquivalentModifierMask)_KeyEventModifierFlagDeviceIndependentFlagsMask +@(private) _KeyEventModifierFlagDeviceIndependentFlagsMask := UInteger(0xffff0000) + +MenuItemCallback :: proc "c" (unused: rawptr, name: SEL, sender: ^Object) + +@(objc_class="NSMenuItem") +MenuItem :: struct {using _: Object} + +@(objc_type=MenuItem, objc_name="registerActionCallback", objc_is_class_method=true) +MenuItem_registerActionCallback :: proc "c" (name: cstring, callback: MenuItemCallback) -> SEL { + s := string(name) + n := len(s) + sel: SEL + if n > 0 && s[n-1] != ':' { + col_name := intrinsics.alloca(n+2, 1) + builtin.copy(col_name[:n], s) + col_name[n] = ':' + col_name[n+1] = 0 + sel = sel_registerName(cstring(col_name)) + } else { + sel = sel_registerName(name) + } + if callback != nil { + class_addMethod(intrinsics.objc_find_class("NSObject"), sel, auto_cast callback, "v@:@") + } + return sel +} + +@(objc_type=MenuItem, objc_name="init") +MenuItem_init :: proc "c" (self: ^MenuItem) -> ^MenuItem { + return msgSend(^MenuItem, self, "init") +} + + +@(objc_type=MenuItem, objc_name="separatorItem", objc_is_class_method=true) +MenuItem_separatorItem :: #force_inline proc "c" () -> ^MenuItem { + return msgSend(^MenuItem, MenuItem, "separatorItem") +} +@(objc_type=MenuItem, objc_name="sectionHeaderWithTitle", objc_is_class_method=true) +MenuItem_sectionHeaderWithTitle :: #force_inline proc "c" (title: ^String) -> ^MenuItem { + return msgSend(^MenuItem, MenuItem, "sectionHeaderWithTitle:", title) +} +@(objc_type=MenuItem, objc_name="initWithTitle") +MenuItem_initWithTitle :: #force_inline proc "c" (self: ^MenuItem, string: ^String, selector: SEL, charCode: ^String) -> ^MenuItem { + return msgSend(^MenuItem, self, "initWithTitle:action:keyEquivalent:", string, selector, charCode) +} +@(objc_type=MenuItem, objc_name="initWithCoder") +MenuItem_initWithCoder :: #force_inline proc "c" (self: ^MenuItem, coder: ^Coder) -> ^MenuItem { + return msgSend(^MenuItem, self, "initWithCoder:", coder) +} +@(objc_type=MenuItem, objc_name="usesUserKeyEquivalents", objc_is_class_method=true) +MenuItem_usesUserKeyEquivalents :: #force_inline proc "c" () -> bool { + return msgSend(bool, MenuItem, "usesUserKeyEquivalents") +} +@(objc_type=MenuItem, objc_name="setUsesUserKeyEquivalents", objc_is_class_method=true) +MenuItem_setUsesUserKeyEquivalents :: #force_inline proc "c" (usesUserKeyEquivalents: bool) { + msgSend(nil, MenuItem, "setUsesUserKeyEquivalents:", usesUserKeyEquivalents) +} +@(objc_type=MenuItem, objc_name="menu") +MenuItem_menu :: #force_inline proc "c" (self: ^MenuItem) -> ^Menu { + return msgSend(^Menu, self, "menu") +} +@(objc_type=MenuItem, objc_name="setMenu") +MenuItem_setMenu :: #force_inline proc "c" (self: ^MenuItem, menu: ^Menu) { + msgSend(nil, self, "setMenu:", menu) +} +@(objc_type=MenuItem, objc_name="hasSubmenu") +MenuItem_hasSubmenu :: #force_inline proc "c" (self: ^MenuItem) -> bool { + return msgSend(bool, self, "hasSubmenu") +} +@(objc_type=MenuItem, objc_name="submenu") +MenuItem_submenu :: #force_inline proc "c" (self: ^MenuItem) -> ^Menu { + return msgSend(^Menu, self, "submenu") +} +@(objc_type=MenuItem, objc_name="setSubmenu") +MenuItem_setSubmenu :: #force_inline proc "c" (self: ^MenuItem, submenu: ^Menu) { + msgSend(nil, self, "setSubmenu:", submenu) +} +@(objc_type=MenuItem, objc_name="parentItem") +MenuItem_parentItem :: #force_inline proc "c" (self: ^MenuItem) -> ^MenuItem { + return msgSend(^MenuItem, self, "parentItem") +} +@(objc_type=MenuItem, objc_name="title") +MenuItem_title :: #force_inline proc "c" (self: ^MenuItem) -> ^String { + return msgSend(^String, self, "title") +} +@(objc_type=MenuItem, objc_name="setTitle") +MenuItem_setTitle :: #force_inline proc "c" (self: ^MenuItem, title: ^String) { + msgSend(nil, self, "setTitle:", title) +} +// @(objc_type=MenuItem, objc_name="attributedTitle") +// MenuItem_attributedTitle :: #force_inline proc "c" (self: ^MenuItem) -> ^AttributedString { +// return msgSend(^AttributedString, self, "attributedTitle") +// } +// @(objc_type=MenuItem, objc_name="setAttributedTitle") +// MenuItem_setAttributedTitle :: #force_inline proc "c" (self: ^MenuItem, attributedTitle: ^AttributedString) { +// msgSend(nil, self, "setAttributedTitle:", attributedTitle) +// } +@(objc_type=MenuItem, objc_name="subtitle") +MenuItem_subtitle :: #force_inline proc "c" (self: ^MenuItem) -> ^String { + return msgSend(^String, self, "subtitle") +} +@(objc_type=MenuItem, objc_name="setSubtitle") +MenuItem_setSubtitle :: #force_inline proc "c" (self: ^MenuItem, subtitle: ^String) { + msgSend(nil, self, "setSubtitle:", subtitle) +} +@(objc_type=MenuItem, objc_name="isSeparatorItem") +MenuItem_isSeparatorItem :: #force_inline proc "c" (self: ^MenuItem) -> bool { + return msgSend(bool, self, "isSeparatorItem") +} +@(objc_type=MenuItem, objc_name="isSectionHeader") +MenuItem_isSectionHeader :: #force_inline proc "c" (self: ^MenuItem) -> bool { + return msgSend(bool, self, "isSectionHeader") +} +@(objc_type=MenuItem, objc_name="keyEquivalent") +MenuItem_keyEquivalent :: #force_inline proc "c" (self: ^MenuItem) -> ^String { + return msgSend(^String, self, "keyEquivalent") +} +@(objc_type=MenuItem, objc_name="setKeyEquivalent") +MenuItem_setKeyEquivalent :: #force_inline proc "c" (self: ^MenuItem, keyEquivalent: ^String) { + msgSend(nil, self, "setKeyEquivalent:", keyEquivalent) +} +@(objc_type=MenuItem, objc_name="keyEquivalentModifierMask") +MenuItem_keyEquivalentModifierMask :: #force_inline proc "c" (self: ^MenuItem) -> EventModifierFlags { + return msgSend(EventModifierFlags, self, "keyEquivalentModifierMask") +} +@(objc_type=MenuItem, objc_name="setKeyEquivalentModifierMask") +MenuItem_setKeyEquivalentModifierMask :: #force_inline proc "c" (self: ^MenuItem, keyEquivalentModifierMask: EventModifierFlags) { + msgSend(nil, self, "setKeyEquivalentModifierMask:", keyEquivalentModifierMask) +} +@(objc_type=MenuItem, objc_name="userKeyEquivalent") +MenuItem_userKeyEquivalent :: #force_inline proc "c" (self: ^MenuItem) -> ^String { + return msgSend(^String, self, "userKeyEquivalent") +} +@(objc_type=MenuItem, objc_name="allowsKeyEquivalentWhenHidden") +MenuItem_allowsKeyEquivalentWhenHidden :: #force_inline proc "c" (self: ^MenuItem) -> bool { + return msgSend(bool, self, "allowsKeyEquivalentWhenHidden") +} +@(objc_type=MenuItem, objc_name="setAllowsKeyEquivalentWhenHidden") +MenuItem_setAllowsKeyEquivalentWhenHidden :: #force_inline proc "c" (self: ^MenuItem, allowsKeyEquivalentWhenHidden: bool) { + msgSend(nil, self, "setAllowsKeyEquivalentWhenHidden:", allowsKeyEquivalentWhenHidden) +} +@(objc_type=MenuItem, objc_name="allowsAutomaticKeyEquivalentLocalization") +MenuItem_allowsAutomaticKeyEquivalentLocalization :: #force_inline proc "c" (self: ^MenuItem) -> bool { + return msgSend(bool, self, "allowsAutomaticKeyEquivalentLocalization") +} +@(objc_type=MenuItem, objc_name="setAllowsAutomaticKeyEquivalentLocalization") +MenuItem_setAllowsAutomaticKeyEquivalentLocalization :: #force_inline proc "c" (self: ^MenuItem, allowsAutomaticKeyEquivalentLocalization: bool) { + msgSend(nil, self, "setAllowsAutomaticKeyEquivalentLocalization:", allowsAutomaticKeyEquivalentLocalization) +} +@(objc_type=MenuItem, objc_name="allowsAutomaticKeyEquivalentMirroring") +MenuItem_allowsAutomaticKeyEquivalentMirroring :: #force_inline proc "c" (self: ^MenuItem) -> bool { + return msgSend(bool, self, "allowsAutomaticKeyEquivalentMirroring") +} +@(objc_type=MenuItem, objc_name="setAllowsAutomaticKeyEquivalentMirroring") +MenuItem_setAllowsAutomaticKeyEquivalentMirroring :: #force_inline proc "c" (self: ^MenuItem, allowsAutomaticKeyEquivalentMirroring: bool) { + msgSend(nil, self, "setAllowsAutomaticKeyEquivalentMirroring:", allowsAutomaticKeyEquivalentMirroring) +} +// @(objc_type=MenuItem, objc_name="image") +// MenuItem_image :: #force_inline proc "c" (self: ^MenuItem) -> ^Image { +// return msgSend(^Image, self, "image") +// } +// @(objc_type=MenuItem, objc_name="setImage") +// MenuItem_setImage :: #force_inline proc "c" (self: ^MenuItem, image: ^Image) { +// msgSend(nil, self, "setImage:", image) +// } +// @(objc_type=MenuItem, objc_name="state") +// MenuItem_state :: #force_inline proc "c" (self: ^MenuItem) -> ControlStateValue { +// return msgSend(ControlStateValue, self, "state") +// } +// @(objc_type=MenuItem, objc_name="setState") +// MenuItem_setState :: #force_inline proc "c" (self: ^MenuItem, state: ControlStateValue) { +// msgSend(nil, self, "setState:", state) +// } +// @(objc_type=MenuItem, objc_name="onStateImage") +// MenuItem_onStateImage :: #force_inline proc "c" (self: ^MenuItem) -> ^Image { +// return msgSend(^Image, self, "onStateImage") +// } +// @(objc_type=MenuItem, objc_name="setOnStateImage") +// MenuItem_setOnStateImage :: #force_inline proc "c" (self: ^MenuItem, onStateImage: ^Image) { +// msgSend(nil, self, "setOnStateImage:", onStateImage) +// } +// @(objc_type=MenuItem, objc_name="offStateImage") +// MenuItem_offStateImage :: #force_inline proc "c" (self: ^MenuItem) -> ^Image { +// return msgSend(^Image, self, "offStateImage") +// } +// @(objc_type=MenuItem, objc_name="setOffStateImage") +// MenuItem_setOffStateImage :: #force_inline proc "c" (self: ^MenuItem, offStateImage: ^Image) { +// msgSend(nil, self, "setOffStateImage:", offStateImage) +// } +// @(objc_type=MenuItem, objc_name="mixedStateImage") +// MenuItem_mixedStateImage :: #force_inline proc "c" (self: ^MenuItem) -> ^Image { +// return msgSend(^Image, self, "mixedStateImage") +// } +// @(objc_type=MenuItem, objc_name="setMixedStateImage") +// MenuItem_setMixedStateImage :: #force_inline proc "c" (self: ^MenuItem, mixedStateImage: ^Image) { +// msgSend(nil, self, "setMixedStateImage:", mixedStateImage) +// } +@(objc_type=MenuItem, objc_name="isEnabled") +MenuItem_isEnabled :: #force_inline proc "c" (self: ^MenuItem) -> bool { + return msgSend(bool, self, "isEnabled") +} +@(objc_type=MenuItem, objc_name="setEnabled") +MenuItem_setEnabled :: #force_inline proc "c" (self: ^MenuItem, enabled: bool) { + msgSend(nil, self, "setEnabled:", enabled) +} +@(objc_type=MenuItem, objc_name="isAlternate") +MenuItem_isAlternate :: #force_inline proc "c" (self: ^MenuItem) -> bool { + return msgSend(bool, self, "isAlternate") +} +@(objc_type=MenuItem, objc_name="setAlternate") +MenuItem_setAlternate :: #force_inline proc "c" (self: ^MenuItem, alternate: bool) { + msgSend(nil, self, "setAlternate:", alternate) +} +@(objc_type=MenuItem, objc_name="indentationLevel") +MenuItem_indentationLevel :: #force_inline proc "c" (self: ^MenuItem) -> Integer { + return msgSend(Integer, self, "indentationLevel") +} +@(objc_type=MenuItem, objc_name="setIndentationLevel") +MenuItem_setIndentationLevel :: #force_inline proc "c" (self: ^MenuItem, indentationLevel: Integer) { + msgSend(nil, self, "setIndentationLevel:", indentationLevel) +} +@(objc_type=MenuItem, objc_name="target") +MenuItem_target :: #force_inline proc "c" (self: ^MenuItem) -> id { + return msgSend(id, self, "target") +} +@(objc_type=MenuItem, objc_name="setTarget") +MenuItem_setTarget :: #force_inline proc "c" (self: ^MenuItem, target: id) { + msgSend(nil, self, "setTarget:", target) +} +@(objc_type=MenuItem, objc_name="action") +MenuItem_action :: #force_inline proc "c" (self: ^MenuItem) -> SEL { + return msgSend(SEL, self, "action") +} +@(objc_type=MenuItem, objc_name="setAction") +MenuItem_setAction :: #force_inline proc "c" (self: ^MenuItem, action: SEL) { + msgSend(nil, self, "setAction:", action) +} +@(objc_type=MenuItem, objc_name="tag") +MenuItem_tag :: #force_inline proc "c" (self: ^MenuItem) -> Integer { + return msgSend(Integer, self, "tag") +} +@(objc_type=MenuItem, objc_name="setTag") +MenuItem_setTag :: #force_inline proc "c" (self: ^MenuItem, tag: Integer) { + msgSend(nil, self, "setTag:", tag) +} +@(objc_type=MenuItem, objc_name="representedObject") +MenuItem_representedObject :: #force_inline proc "c" (self: ^MenuItem) -> id { + return msgSend(id, self, "representedObject") +} +@(objc_type=MenuItem, objc_name="setRepresentedObject") +MenuItem_setRepresentedObject :: #force_inline proc "c" (self: ^MenuItem, representedObject: id) { + msgSend(nil, self, "setRepresentedObject:", representedObject) +} +@(objc_type=MenuItem, objc_name="view") +MenuItem_view :: #force_inline proc "c" (self: ^MenuItem) -> ^View { + return msgSend(^View, self, "view") +} +@(objc_type=MenuItem, objc_name="setView") +MenuItem_setView :: #force_inline proc "c" (self: ^MenuItem, view: ^View) { + msgSend(nil, self, "setView:", view) +} +@(objc_type=MenuItem, objc_name="isHighlighted") +MenuItem_isHighlighted :: #force_inline proc "c" (self: ^MenuItem) -> bool { + return msgSend(bool, self, "isHighlighted") +} +@(objc_type=MenuItem, objc_name="isHidden") +MenuItem_isHidden :: #force_inline proc "c" (self: ^MenuItem) -> bool { + return msgSend(bool, self, "isHidden") +} +@(objc_type=MenuItem, objc_name="setHidden") +MenuItem_setHidden :: #force_inline proc "c" (self: ^MenuItem, hidden: bool) { + msgSend(nil, self, "setHidden:", hidden) +} +@(objc_type=MenuItem, objc_name="isHiddenOrHasHiddenAncestor") +MenuItem_isHiddenOrHasHiddenAncestor :: #force_inline proc "c" (self: ^MenuItem) -> bool { + return msgSend(bool, self, "isHiddenOrHasHiddenAncestor") +} +@(objc_type=MenuItem, objc_name="toolTip") +MenuItem_toolTip :: #force_inline proc "c" (self: ^MenuItem) -> ^String { + return msgSend(^String, self, "toolTip") +} +@(objc_type=MenuItem, objc_name="setToolTip") +MenuItem_setToolTip :: #force_inline proc "c" (self: ^MenuItem, toolTip: ^String) { + msgSend(nil, self, "setToolTip:", toolTip) +} +// @(objc_type=MenuItem, objc_name="badge") +// MenuItem_badge :: #force_inline proc "c" (self: ^MenuItem) -> ^MenuItemBadge { +// return msgSend(^MenuItemBadge, self, "badge") +// } +// @(objc_type=MenuItem, objc_name="setBadge") +// MenuItem_setBadge :: #force_inline proc "c" (self: ^MenuItem, badge: ^MenuItemBadge) { +// msgSend(nil, self, "setBadge:", badge) +// } +@(objc_type=MenuItem, objc_name="setMnemonicLocation") +MenuItem_setMnemonicLocation :: #force_inline proc "c" (self: ^MenuItem, location: UInteger) { + msgSend(nil, self, "setMnemonicLocation:", location) +} +@(objc_type=MenuItem, objc_name="mnemonicLocation") +MenuItem_mnemonicLocation :: #force_inline proc "c" (self: ^MenuItem) -> UInteger { + return msgSend(UInteger, self, "mnemonicLocation") +} +@(objc_type=MenuItem, objc_name="mnemonic") +MenuItem_mnemonic :: #force_inline proc "c" (self: ^MenuItem) -> ^String { + return msgSend(^String, self, "mnemonic") +} +@(objc_type=MenuItem, objc_name="setTitleWithMnemonic") +MenuItem_setTitleWithMnemonic :: #force_inline proc "c" (self: ^MenuItem, stringWithAmpersand: ^String) { + msgSend(nil, self, "setTitleWithMnemonic:", stringWithAmpersand) +} +@(objc_type=MenuItem, objc_name="load", objc_is_class_method=true) +MenuItem_load :: #force_inline proc "c" () { + msgSend(nil, MenuItem, "load") +} +@(objc_type=MenuItem, objc_name="initialize", objc_is_class_method=true) +MenuItem_initialize :: #force_inline proc "c" () { + msgSend(nil, MenuItem, "initialize") +} +@(objc_type=MenuItem, objc_name="new", objc_is_class_method=true) +MenuItem_new :: #force_inline proc "c" () -> ^MenuItem { + return msgSend(^MenuItem, MenuItem, "new") +} +@(objc_type=MenuItem, objc_name="allocWithZone", objc_is_class_method=true) +MenuItem_allocWithZone :: #force_inline proc "c" (zone: ^Zone) -> ^MenuItem { + return msgSend(^MenuItem, MenuItem, "allocWithZone:", zone) +} +@(objc_type=MenuItem, objc_name="alloc", objc_is_class_method=true) +MenuItem_alloc :: #force_inline proc "c" () -> ^MenuItem { + return msgSend(^MenuItem, MenuItem, "alloc") +} +@(objc_type=MenuItem, objc_name="copyWithZone", objc_is_class_method=true) +MenuItem_copyWithZone :: #force_inline proc "c" (zone: ^Zone) -> id { + return msgSend(id, MenuItem, "copyWithZone:", zone) +} +@(objc_type=MenuItem, objc_name="mutableCopyWithZone", objc_is_class_method=true) +MenuItem_mutableCopyWithZone :: #force_inline proc "c" (zone: ^Zone) -> id { + return msgSend(id, MenuItem, "mutableCopyWithZone:", zone) +} +@(objc_type=MenuItem, objc_name="instancesRespondToSelector", objc_is_class_method=true) +MenuItem_instancesRespondToSelector :: #force_inline proc "c" (aSelector: SEL) -> bool { + return msgSend(bool, MenuItem, "instancesRespondToSelector:", aSelector) +} +@(objc_type=MenuItem, objc_name="conformsToProtocol", objc_is_class_method=true) +MenuItem_conformsToProtocol :: #force_inline proc "c" (protocol: ^Protocol) -> bool { + return msgSend(bool, MenuItem, "conformsToProtocol:", protocol) +} +@(objc_type=MenuItem, objc_name="instanceMethodForSelector", objc_is_class_method=true) +MenuItem_instanceMethodForSelector :: #force_inline proc "c" (aSelector: SEL) -> IMP { + return msgSend(IMP, MenuItem, "instanceMethodForSelector:", aSelector) +} +// @(objc_type=MenuItem, objc_name="instanceMethodSignatureForSelector", objc_is_class_method=true) +// MenuItem_instanceMethodSignatureForSelector :: #force_inline proc "c" (aSelector: SEL) -> ^MethodSignature { +// return msgSend(^MethodSignature, MenuItem, "instanceMethodSignatureForSelector:", aSelector) +// } +@(objc_type=MenuItem, objc_name="isSubclassOfClass", objc_is_class_method=true) +MenuItem_isSubclassOfClass :: #force_inline proc "c" (aClass: Class) -> bool { + return msgSend(bool, MenuItem, "isSubclassOfClass:", aClass) +} +@(objc_type=MenuItem, objc_name="resolveClassMethod", objc_is_class_method=true) +MenuItem_resolveClassMethod :: #force_inline proc "c" (sel: SEL) -> bool { + return msgSend(bool, MenuItem, "resolveClassMethod:", sel) +} +@(objc_type=MenuItem, objc_name="resolveInstanceMethod", objc_is_class_method=true) +MenuItem_resolveInstanceMethod :: #force_inline proc "c" (sel: SEL) -> bool { + return msgSend(bool, MenuItem, "resolveInstanceMethod:", sel) +} +@(objc_type=MenuItem, objc_name="hash", objc_is_class_method=true) +MenuItem_hash :: #force_inline proc "c" () -> UInteger { + return msgSend(UInteger, MenuItem, "hash") +} +@(objc_type=MenuItem, objc_name="superclass", objc_is_class_method=true) +MenuItem_superclass :: #force_inline proc "c" () -> Class { + return msgSend(Class, MenuItem, "superclass") +} +@(objc_type=MenuItem, objc_name="class", objc_is_class_method=true) +MenuItem_class :: #force_inline proc "c" () -> Class { + return msgSend(Class, MenuItem, "class") +} +@(objc_type=MenuItem, objc_name="description", objc_is_class_method=true) +MenuItem_description :: #force_inline proc "c" () -> ^String { + return msgSend(^String, MenuItem, "description") +} +@(objc_type=MenuItem, objc_name="debugDescription", objc_is_class_method=true) +MenuItem_debugDescription :: #force_inline proc "c" () -> ^String { + return msgSend(^String, MenuItem, "debugDescription") +} +@(objc_type=MenuItem, objc_name="version", objc_is_class_method=true) +MenuItem_version :: #force_inline proc "c" () -> Integer { + return msgSend(Integer, MenuItem, "version") +} +@(objc_type=MenuItem, objc_name="setVersion", objc_is_class_method=true) +MenuItem_setVersion :: #force_inline proc "c" (aVersion: Integer) { + msgSend(nil, MenuItem, "setVersion:", aVersion) +} +@(objc_type=MenuItem, objc_name="poseAsClass", objc_is_class_method=true) +MenuItem_poseAsClass :: #force_inline proc "c" (aClass: Class) { + msgSend(nil, MenuItem, "poseAsClass:", aClass) +} +@(objc_type=MenuItem, objc_name="cancelPreviousPerformRequestsWithTarget_selector_object", objc_is_class_method=true) +MenuItem_cancelPreviousPerformRequestsWithTarget_selector_object :: #force_inline proc "c" (aTarget: id, aSelector: SEL, anArgument: id) { + msgSend(nil, MenuItem, "cancelPreviousPerformRequestsWithTarget:selector:object:", aTarget, aSelector, anArgument) +} +@(objc_type=MenuItem, objc_name="cancelPreviousPerformRequestsWithTarget_", objc_is_class_method=true) +MenuItem_cancelPreviousPerformRequestsWithTarget_ :: #force_inline proc "c" (aTarget: id) { + msgSend(nil, MenuItem, "cancelPreviousPerformRequestsWithTarget:", aTarget) +} +@(objc_type=MenuItem, objc_name="accessInstanceVariablesDirectly", objc_is_class_method=true) +MenuItem_accessInstanceVariablesDirectly :: #force_inline proc "c" () -> bool { + return msgSend(bool, MenuItem, "accessInstanceVariablesDirectly") +} +@(objc_type=MenuItem, objc_name="useStoredAccessor", objc_is_class_method=true) +MenuItem_useStoredAccessor :: #force_inline proc "c" () -> bool { + return msgSend(bool, MenuItem, "useStoredAccessor") +} +@(objc_type=MenuItem, objc_name="keyPathsForValuesAffectingValueForKey", objc_is_class_method=true) +MenuItem_keyPathsForValuesAffectingValueForKey :: #force_inline proc "c" (key: ^String) -> ^Set { + return msgSend(^Set, MenuItem, "keyPathsForValuesAffectingValueForKey:", key) +} +@(objc_type=MenuItem, objc_name="automaticallyNotifiesObserversForKey", objc_is_class_method=true) +MenuItem_automaticallyNotifiesObserversForKey :: #force_inline proc "c" (key: ^String) -> bool { + return msgSend(bool, MenuItem, "automaticallyNotifiesObserversForKey:", key) +} +@(objc_type=MenuItem, objc_name="setKeys", objc_is_class_method=true) +MenuItem_setKeys :: #force_inline proc "c" (keys: ^Array, dependentKey: ^String) { + msgSend(nil, MenuItem, "setKeys:triggerChangeNotificationsForDependentKey:", keys, dependentKey) +} +@(objc_type=MenuItem, objc_name="classFallbacksForKeyedArchiver", objc_is_class_method=true) +MenuItem_classFallbacksForKeyedArchiver :: #force_inline proc "c" () -> ^Array { + return msgSend(^Array, MenuItem, "classFallbacksForKeyedArchiver") +} +@(objc_type=MenuItem, objc_name="classForKeyedUnarchiver", objc_is_class_method=true) +MenuItem_classForKeyedUnarchiver :: #force_inline proc "c" () -> Class { + return msgSend(Class, MenuItem, "classForKeyedUnarchiver") +} +@(objc_type=MenuItem, objc_name="exposeBinding", objc_is_class_method=true) +MenuItem_exposeBinding :: #force_inline proc "c" (binding: ^String) { + msgSend(nil, MenuItem, "exposeBinding:", binding) +} +@(objc_type=MenuItem, objc_name="setDefaultPlaceholder", objc_is_class_method=true) +MenuItem_setDefaultPlaceholder :: #force_inline proc "c" (placeholder: id, marker: id, binding: ^String) { + msgSend(nil, MenuItem, "setDefaultPlaceholder:forMarker:withBinding:", placeholder, marker, binding) +} +@(objc_type=MenuItem, objc_name="defaultPlaceholderForMarker", objc_is_class_method=true) +MenuItem_defaultPlaceholderForMarker :: #force_inline proc "c" (marker: id, binding: ^String) -> id { + return msgSend(id, MenuItem, "defaultPlaceholderForMarker:withBinding:", marker, binding) +} +@(objc_type=MenuItem, objc_name="cancelPreviousPerformRequestsWithTarget") +MenuItem_cancelPreviousPerformRequestsWithTarget :: proc { + MenuItem_cancelPreviousPerformRequestsWithTarget_selector_object, + MenuItem_cancelPreviousPerformRequestsWithTarget_, +}
\ No newline at end of file diff --git a/core/sys/darwin/Foundation/NSNotification.odin b/core/sys/darwin/Foundation/NSNotification.odin index f766d0cab..f2f4e819b 100644 --- a/core/sys/darwin/Foundation/NSNotification.odin +++ b/core/sys/darwin/Foundation/NSNotification.odin @@ -50,10 +50,22 @@ NotificationCenter_defaultCenter :: proc "c" () -> ^NotificationCenter { return msgSend(^NotificationCenter, NotificationCenter, "defaultCenter") } +@(objc_type=NotificationCenter, objc_name="addObserverForName") +NotificationCenter_addObserverForName :: proc{NotificationCenter_addObserverForName_old, NotificationCenter_addObserverForName_new} + +NotificationCenter_addObserverForName_old :: proc "c" (self: ^NotificationCenter, name: NotificationName, pObj: ^Object, pQueue: rawptr, block: ^Block) -> ^Object { + return msgSend(^Object, self, "addObserverForName:object:queue:usingBlock:", name, pObj, pQueue, block) +} + +NotificationCenter_addObserverForName_new :: proc "c" (self: ^NotificationCenter, name: NotificationName, pObj: ^Object, pQueue: rawptr, block: ^Objc_Block) -> ^Object { + return msgSend(^Object, self, "addObserverForName:object:queue:usingBlock:", name, pObj, pQueue, block) +} + @(objc_type=NotificationCenter, objc_name="addObserver") -NotificationCenter_addObserverName :: proc "c" (self: ^NotificationCenter, name: NotificationName, pObj: ^Object, pQueue: rawptr, block: ^Block) -> ^Object { - return msgSend(^Object, self, "addObserverName:object:queue:block:", name, pObj, pQueue, block) +NotificationCenter_addObserver :: proc "c" (self: ^NotificationCenter, observer: ^Object, selector: SEL, name: NotificationName, object: ^Object) { + msgSend(nil, self, "addObserver:selector:name:object:", observer, selector, name, object) } + @(objc_type=NotificationCenter, objc_name="removeObserver") NotificationCenter_removeObserver :: proc "c" (self: ^NotificationCenter, pObserver: ^Object) { msgSend(nil, self, "removeObserver:", pObserver) diff --git a/core/sys/darwin/Foundation/NSWindow.odin b/core/sys/darwin/Foundation/NSWindow.odin index 57ac2b6f6..e1b027a89 100644 --- a/core/sys/darwin/Foundation/NSWindow.odin +++ b/core/sys/darwin/Foundation/NSWindow.odin @@ -146,7 +146,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla return nil } if template.windowWillPositionSheetUsingRect != nil { - windowWillPositionSheetUsingRect :: proc "c" (self: id, window: ^Window, sheet: ^Window, rect: Rect) -> Rect { + windowWillPositionSheetUsingRect :: proc "c" (self: id, cmd: SEL, window: ^Window, sheet: ^Window, rect: Rect) -> Rect { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context return del.windowWillPositionSheetUsingRect(window, sheet, rect) @@ -154,7 +154,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("window:willPositionSheet:usingRect:"), auto_cast windowWillPositionSheetUsingRect, _RECT_ENCODING+"@:@@"+_RECT_ENCODING) } if template.windowWillBeginSheet != nil { - windowWillBeginSheet :: proc "c" (self: id, notification: ^Notification) { + windowWillBeginSheet :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowWillBeginSheet(notification) @@ -162,7 +162,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowWillBeginSheet:"), auto_cast windowWillBeginSheet, "v@:@") } if template.windowDidEndSheet != nil { - windowDidEndSheet :: proc "c" (self: id, notification: ^Notification) { + windowDidEndSheet :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowDidEndSheet(notification) @@ -170,7 +170,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowDidEndSheet:"), auto_cast windowDidEndSheet, "v@:@") } if template.windowWillResizeToSize != nil { - windowWillResizeToSize :: proc "c" (self: id, sender: ^Window, frameSize: Size) -> Size { + windowWillResizeToSize :: proc "c" (self: id, cmd: SEL, sender: ^Window, frameSize: Size) -> Size { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context return del.windowWillResizeToSize(sender, frameSize) @@ -178,7 +178,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowWillResize:toSize:"), auto_cast windowWillResizeToSize, _SIZE_ENCODING+"@:@"+_SIZE_ENCODING) } if template.windowDidResize != nil { - windowDidResize :: proc "c" (self: id, notification: ^Notification) { + windowDidResize :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowDidResize(notification) @@ -186,7 +186,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowDidResize:"), auto_cast windowDidResize, "v@:@") } if template.windowWillStartLiveResize != nil { - windowWillStartLiveResize :: proc "c" (self: id, notification: ^Notification) { + windowWillStartLiveResize :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowWillStartLiveResize(notification) @@ -194,7 +194,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowWillStartLiveResize:"), auto_cast windowWillStartLiveResize, "v@:@") } if template.windowDidEndLiveResize != nil { - windowDidEndLiveResize :: proc "c" (self: id, notification: ^Notification) { + windowDidEndLiveResize :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowDidEndLiveResize(notification) @@ -202,7 +202,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowDidEndLiveResize:"), auto_cast windowDidEndLiveResize, "v@:@") } if template.windowWillMiniaturize != nil { - windowWillMiniaturize :: proc "c" (self: id, notification: ^Notification) { + windowWillMiniaturize :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowWillMiniaturize(notification) @@ -210,7 +210,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowWillMiniaturize:"), auto_cast windowWillMiniaturize, "v@:@") } if template.windowDidMiniaturize != nil { - windowDidMiniaturize :: proc "c" (self: id, notification: ^Notification) { + windowDidMiniaturize :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowDidMiniaturize(notification) @@ -218,7 +218,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowDidMiniaturize:"), auto_cast windowDidMiniaturize, "v@:@") } if template.windowDidDeminiaturize != nil { - windowDidDeminiaturize :: proc "c" (self: id, notification: ^Notification) { + windowDidDeminiaturize :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowDidDeminiaturize(notification) @@ -226,7 +226,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowDidDeminiaturize:"), auto_cast windowDidDeminiaturize, "v@:@") } if template.windowWillUseStandardFrameDefaultFrame != nil { - windowWillUseStandardFrameDefaultFrame :: proc(self: id, window: ^Window, newFrame: Rect) -> Rect { + windowWillUseStandardFrameDefaultFrame :: proc(self: id, cmd: SEL, window: ^Window, newFrame: Rect) -> Rect { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context return del.windowWillUseStandardFrameDefaultFrame(window, newFrame) @@ -234,7 +234,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowWillUseStandardFrame:defaultFrame:"), auto_cast windowWillUseStandardFrameDefaultFrame, _RECT_ENCODING+"@:@"+_RECT_ENCODING) } if template.windowShouldZoomToFrame != nil { - windowShouldZoomToFrame :: proc "c" (self: id, window: ^Window, newFrame: Rect) -> BOOL { + windowShouldZoomToFrame :: proc "c" (self: id, cmd: SEL, window: ^Window, newFrame: Rect) -> BOOL { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context return del.windowShouldZoomToFrame(window, newFrame) @@ -242,7 +242,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowShouldZoom:toFrame:"), auto_cast windowShouldZoomToFrame, "B@:@"+_RECT_ENCODING) } if template.windowWillUseFullScreenContentSize != nil { - windowWillUseFullScreenContentSize :: proc "c" (self: id, window: ^Window, proposedSize: Size) -> Size { + windowWillUseFullScreenContentSize :: proc "c" (self: id, cmd: SEL, window: ^Window, proposedSize: Size) -> Size { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context return del.windowWillUseFullScreenContentSize(window, proposedSize) @@ -250,7 +250,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("window:willUseFullScreenContentSize:"), auto_cast windowWillUseFullScreenContentSize, _SIZE_ENCODING+"@:@"+_SIZE_ENCODING) } if template.windowWillUseFullScreenPresentationOptions != nil { - windowWillUseFullScreenPresentationOptions :: proc(self: id, window: ^Window, proposedOptions: ApplicationPresentationOptions) -> ApplicationPresentationOptions { + windowWillUseFullScreenPresentationOptions :: proc(self: id, cmd: SEL, window: ^Window, proposedOptions: ApplicationPresentationOptions) -> ApplicationPresentationOptions { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context return del.windowWillUseFullScreenPresentationOptions(window, proposedOptions) @@ -258,7 +258,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("window:willUseFullScreenPresentationOptions:"), auto_cast windowWillUseFullScreenPresentationOptions, _UINTEGER_ENCODING+"@:@"+_UINTEGER_ENCODING) } if template.windowWillEnterFullScreen != nil { - windowWillEnterFullScreen :: proc "c" (self: id, notification: ^Notification) { + windowWillEnterFullScreen :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowWillEnterFullScreen(notification) @@ -266,7 +266,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowWillEnterFullScreen:"), auto_cast windowWillEnterFullScreen, "v@:@") } if template.windowDidEnterFullScreen != nil { - windowDidEnterFullScreen :: proc "c" (self: id, notification: ^Notification) { + windowDidEnterFullScreen :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowDidEnterFullScreen(notification) @@ -274,7 +274,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowDidEnterFullScreen:"), auto_cast windowDidEnterFullScreen, "v@:@") } if template.windowWillExitFullScreen != nil { - windowWillExitFullScreen :: proc "c" (self: id, notification: ^Notification) { + windowWillExitFullScreen :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowWillExitFullScreen(notification) @@ -282,7 +282,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowWillExitFullScreen:"), auto_cast windowWillExitFullScreen, "v@:@") } if template.windowDidExitFullScreen != nil { - windowDidExitFullScreen :: proc "c" (self: id, notification: ^Notification) { + windowDidExitFullScreen :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowDidExitFullScreen(notification) @@ -290,7 +290,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowDidExitFullScreen:"), auto_cast windowDidExitFullScreen, "v@:@") } if template.customWindowsToEnterFullScreenForWindow != nil { - customWindowsToEnterFullScreenForWindow :: proc "c" (self: id, window: ^Window) -> ^Array { + customWindowsToEnterFullScreenForWindow :: proc "c" (self: id, cmd: SEL, window: ^Window) -> ^Array { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context return del.customWindowsToEnterFullScreenForWindow(window) @@ -298,7 +298,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("customWindowsToEnterFullScreenForWindow:"), auto_cast customWindowsToEnterFullScreenForWindow, "@@:@") } if template.customWindowsToEnterFullScreenForWindowOnScreen != nil { - customWindowsToEnterFullScreenForWindowOnScreen :: proc(self: id, window: ^Window, screen: ^Screen) -> ^Array { + customWindowsToEnterFullScreenForWindowOnScreen :: proc(self: id, cmd: SEL, window: ^Window, screen: ^Screen) -> ^Array { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context return del.customWindowsToEnterFullScreenForWindowOnScreen(window, screen) @@ -306,7 +306,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("customWindowsToEnterFullScreenForWindow:onScreen:"), auto_cast customWindowsToEnterFullScreenForWindowOnScreen, "@@:@@") } if template.windowStartCustomAnimationToEnterFullScreenWithDuration != nil { - windowStartCustomAnimationToEnterFullScreenWithDuration :: proc "c" (self: id, window: ^Window, duration: TimeInterval) { + windowStartCustomAnimationToEnterFullScreenWithDuration :: proc "c" (self: id, cmd: SEL, window: ^Window, duration: TimeInterval) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowStartCustomAnimationToEnterFullScreenWithDuration(window, duration) @@ -314,7 +314,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("window:startCustomAnimationToEnterFullScreenWithDuration:"), auto_cast windowStartCustomAnimationToEnterFullScreenWithDuration, "v@:@@") } if template.windowStartCustomAnimationToEnterFullScreenOnScreenWithDuration != nil { - windowStartCustomAnimationToEnterFullScreenOnScreenWithDuration :: proc(self: id, window: ^Window, screen: ^Screen, duration: TimeInterval) { + windowStartCustomAnimationToEnterFullScreenOnScreenWithDuration :: proc(self: id, cmd: SEL, window: ^Window, screen: ^Screen, duration: TimeInterval) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowStartCustomAnimationToEnterFullScreenOnScreenWithDuration(window, screen, duration) @@ -322,7 +322,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("window:startCustomAnimationToEnterFullScreenOnScreen:withDuration:"), auto_cast windowStartCustomAnimationToEnterFullScreenOnScreenWithDuration, "v@:@@d") } if template.windowDidFailToEnterFullScreen != nil { - windowDidFailToEnterFullScreen :: proc "c" (self: id, window: ^Window) { + windowDidFailToEnterFullScreen :: proc "c" (self: id, cmd: SEL, window: ^Window) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowDidFailToEnterFullScreen(window) @@ -330,7 +330,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowDidFailToEnterFullScreen:"), auto_cast windowDidFailToEnterFullScreen, "v@:@") } if template.customWindowsToExitFullScreenForWindow != nil { - customWindowsToExitFullScreenForWindow :: proc "c" (self: id, window: ^Window) -> ^Array { + customWindowsToExitFullScreenForWindow :: proc "c" (self: id, cmd: SEL, window: ^Window) -> ^Array { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context return del.customWindowsToExitFullScreenForWindow(window) @@ -338,7 +338,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("customWindowsToExitFullScreenForWindow:"), auto_cast customWindowsToExitFullScreenForWindow, "@@:@") } if template.windowStartCustomAnimationToExitFullScreenWithDuration != nil { - windowStartCustomAnimationToExitFullScreenWithDuration :: proc "c" (self: id, window: ^Window, duration: TimeInterval) { + windowStartCustomAnimationToExitFullScreenWithDuration :: proc "c" (self: id, cmd: SEL, window: ^Window, duration: TimeInterval) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowStartCustomAnimationToExitFullScreenWithDuration(window, duration) @@ -346,7 +346,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("window:startCustomAnimationToExitFullScreenWithDuration:"), auto_cast windowStartCustomAnimationToExitFullScreenWithDuration, "v@:@d") } if template.windowDidFailToExitFullScreen != nil { - windowDidFailToExitFullScreen :: proc "c" (self: id, window: ^Window) { + windowDidFailToExitFullScreen :: proc "c" (self: id, cmd: SEL, window: ^Window) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowDidFailToExitFullScreen(window) @@ -354,7 +354,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowDidFailToExitFullScreen:"), auto_cast windowDidFailToExitFullScreen, "v@:@") } if template.windowWillMove != nil { - windowWillMove :: proc "c" (self: id, notification: ^Notification) { + windowWillMove :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowWillMove(notification) @@ -362,7 +362,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowWillMove:"), auto_cast windowWillMove, "v@:@") } if template.windowDidMove != nil { - windowDidMove :: proc "c" (self: id, notification: ^Notification) { + windowDidMove :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowDidMove(notification) @@ -370,7 +370,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowDidMove:"), auto_cast windowDidMove, "v@:@") } if template.windowDidChangeScreen != nil { - windowDidChangeScreen :: proc "c" (self: id, notification: ^Notification) { + windowDidChangeScreen :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowDidChangeScreen(notification) @@ -378,7 +378,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowDidChangeScreen:"), auto_cast windowDidChangeScreen, "v@:@") } if template.windowDidChangeScreenProfile != nil { - windowDidChangeScreenProfile :: proc "c" (self: id, notification: ^Notification) { + windowDidChangeScreenProfile :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowDidChangeScreenProfile(notification) @@ -386,7 +386,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowDidChangeScreenProfile:"), auto_cast windowDidChangeScreenProfile, "v@:@") } if template.windowDidChangeBackingProperties != nil { - windowDidChangeBackingProperties :: proc "c" (self: id, notification: ^Notification) { + windowDidChangeBackingProperties :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowDidChangeBackingProperties(notification) @@ -394,7 +394,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowDidChangeBackingProperties:"), auto_cast windowDidChangeBackingProperties, "v@:@") } if template.windowShouldClose != nil { - windowShouldClose :: proc "c" (self:id, sender: ^Window) -> BOOL { + windowShouldClose :: proc "c" (self:id, cmd: SEL, sender: ^Window) -> BOOL { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context return del.windowShouldClose(sender) @@ -402,7 +402,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowShouldClose:"), auto_cast windowShouldClose, "B@:@") } if template.windowWillClose != nil { - windowWillClose :: proc "c" (self:id, notification: ^Notification) { + windowWillClose :: proc "c" (self:id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowWillClose(notification) @@ -410,7 +410,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowWillClose:"), auto_cast windowWillClose, "v@:@") } if template.windowDidBecomeKey != nil { - windowDidBecomeKey :: proc "c" (self: id, notification: ^Notification) { + windowDidBecomeKey :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowDidBecomeKey(notification) @@ -418,7 +418,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowDidBecomeKey:"), auto_cast windowDidBecomeKey, "v@:@") } if template.windowDidResignKey != nil { - windowDidResignKey :: proc "c" (self: id, notification: ^Notification) { + windowDidResignKey :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowDidResignKey(notification) @@ -426,7 +426,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowDidResignKey:"), auto_cast windowDidResignKey, "v@:@") } if template.windowDidBecomeMain != nil { - windowDidBecomeMain :: proc "c" (self: id, notification: ^Notification) { + windowDidBecomeMain :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowDidBecomeMain(notification) @@ -434,7 +434,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowDidBecomeMain:"), auto_cast windowDidBecomeMain, "v@:@") } if template.windowDidResignMain != nil { - windowDidResignMain :: proc "c" (self: id, notification: ^Notification) { + windowDidResignMain :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowDidResignMain(notification) @@ -442,7 +442,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowDidResignMain:"), auto_cast windowDidResignMain, "v@:@") } if template.windowWillReturnFieldEditorToObject != nil { - windowWillReturnFieldEditorToObject :: proc "c" (self:id, sender: ^Window, client: id) -> id { + windowWillReturnFieldEditorToObject :: proc "c" (self:id, cmd: SEL, sender: ^Window, client: id) -> id { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context return del.windowWillReturnFieldEditorToObject(sender, client) @@ -450,7 +450,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowWillReturnFieldEditor:toObject:"), auto_cast windowWillReturnFieldEditorToObject, "@@:@@") } if template.windowDidUpdate != nil { - windowDidUpdate :: proc "c" (self: id, notification: ^Notification) { + windowDidUpdate :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowDidUpdate(notification) @@ -458,7 +458,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowDidUpdate:"), auto_cast windowDidUpdate, "v@:@") } if template.windowDidExpose != nil { - windowDidExpose :: proc "c" (self: id, notification: ^Notification) { + windowDidExpose :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowDidExpose(notification) @@ -466,7 +466,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowDidExpose:"), auto_cast windowDidExpose, "v@:@") } if template.windowDidChangeOcclusionState != nil { - windowDidChangeOcclusionState :: proc "c" (self: id, notification: ^Notification) { + windowDidChangeOcclusionState :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowDidChangeOcclusionState(notification) @@ -474,7 +474,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowDidChangeOcclusionState:"), auto_cast windowDidChangeOcclusionState, "v@:@") } if template.windowShouldDragDocumentWithEventFromWithPasteboard != nil { - windowShouldDragDocumentWithEventFromWithPasteboard :: proc "c" (self: id, window: ^Window, event: ^Event, dragImageLocation: Point, pasteboard: ^Pasteboard) -> BOOL { + windowShouldDragDocumentWithEventFromWithPasteboard :: proc "c" (self: id, cmd: SEL, window: ^Window, event: ^Event, dragImageLocation: Point, pasteboard: ^Pasteboard) -> BOOL { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context return del.windowShouldDragDocumentWithEventFromWithPasteboard(window, event, dragImageLocation, pasteboard) @@ -482,7 +482,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("window:shouldDragDocumentWithEvent:from:withPasteboard:"), auto_cast windowShouldDragDocumentWithEventFromWithPasteboard, "B@:@@"+_POINT_ENCODING+"@") } if template.windowWillReturnUndoManager != nil { - windowWillReturnUndoManager :: proc "c" (self: id, window: ^Window) -> ^UndoManager { + windowWillReturnUndoManager :: proc "c" (self: id, cmd: SEL, window: ^Window) -> ^UndoManager { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context return del.windowWillReturnUndoManager(window) @@ -490,7 +490,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowWillReturnUndoManager:"), auto_cast windowWillReturnUndoManager, "@@:@") } if template.windowShouldPopUpDocumentPathMenu != nil { - windowShouldPopUpDocumentPathMenu :: proc "c" (self: id, window: ^Window, menu: ^Menu) -> BOOL { + windowShouldPopUpDocumentPathMenu :: proc "c" (self: id, cmd: SEL, window: ^Window, menu: ^Menu) -> BOOL { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context return del.windowShouldPopUpDocumentPathMenu(window, menu) @@ -498,7 +498,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("window:shouldPopUpDocumentPathMenu:"), auto_cast windowShouldPopUpDocumentPathMenu, "B@:@@") } if template.windowWillEncodeRestorableState != nil { - windowWillEncodeRestorableState :: proc "c" (self: id, window: ^Window, state: ^Coder) { + windowWillEncodeRestorableState :: proc "c" (self: id, cmd: SEL, window: ^Window, state: ^Coder) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowWillEncodeRestorableState(window, state) @@ -506,7 +506,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("window:willEncodeRestorableState:"), auto_cast windowWillEncodeRestorableState, "v@:@@") } if template.windowDidEncodeRestorableState != nil { - windowDidEncodeRestorableState :: proc "c" (self: id, window: ^Window, state: ^Coder) { + windowDidEncodeRestorableState :: proc "c" (self: id, cmd: SEL, window: ^Window, state: ^Coder) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowDidEncodeRestorableState(window, state) @@ -514,7 +514,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("window:didDecodeRestorableState:"), auto_cast windowDidEncodeRestorableState, "v@:@@") } if template.windowWillResizeForVersionBrowserWithMaxPreferredSizeMaxAllowedSize != nil { - windowWillResizeForVersionBrowserWithMaxPreferredSizeMaxAllowedSize :: proc "c" (self: id, window: ^Window, maxPreferredFrameSize: Size, maxAllowedFrameSize: Size) -> Size { + windowWillResizeForVersionBrowserWithMaxPreferredSizeMaxAllowedSize :: proc "c" (self: id, cmd: SEL, window: ^Window, maxPreferredFrameSize: Size, maxAllowedFrameSize: Size) -> Size { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context return del.windowWillResizeForVersionBrowserWithMaxPreferredSizeMaxAllowedSize(window, maxPreferredFrameSize, maxPreferredFrameSize) @@ -522,7 +522,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("window:willResizeForVersionBrowserWithMaxPreferredSize:maxAllowedSize:"), auto_cast windowWillResizeForVersionBrowserWithMaxPreferredSizeMaxAllowedSize, _SIZE_ENCODING+"@:@"+_SIZE_ENCODING+_SIZE_ENCODING) } if template.windowWillEnterVersionBrowser != nil { - windowWillEnterVersionBrowser :: proc "c" (self: id, notification: ^Notification) { + windowWillEnterVersionBrowser :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowWillEnterVersionBrowser(notification) @@ -530,7 +530,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowWillEnterVersionBrowser:"), auto_cast windowWillEnterVersionBrowser, "v@:@") } if template.windowDidEnterVersionBrowser != nil { - windowDidEnterVersionBrowser :: proc "c" (self: id, notification: ^Notification) { + windowDidEnterVersionBrowser :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowDidEnterVersionBrowser(notification) @@ -538,7 +538,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowDidEnterVersionBrowser:"), auto_cast windowDidEnterVersionBrowser, "v@:@") } if template.windowWillExitVersionBrowser != nil { - windowWillExitVersionBrowser :: proc "c" (self: id, notification: ^Notification) { + windowWillExitVersionBrowser :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowWillExitVersionBrowser(notification) @@ -546,7 +546,7 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla class_addMethod(class, intrinsics.objc_find_selector("windowWillExitVersionBrowser:"), auto_cast windowWillExitVersionBrowser, "v@:@") } if template.windowDidExitVersionBrowser != nil { - windowDidExitVersionBrowser :: proc "c" (self: id, notification: ^Notification) { + windowDidExitVersionBrowser :: proc "c" (self: id, cmd: SEL, notification: ^Notification) { del := cast(^_WindowDelegateInternal)object_getIndexedIvars(self) context = del._context del.windowDidExitVersionBrowser(notification) @@ -568,6 +568,15 @@ window_delegate_register_and_alloc :: proc(template: WindowDelegateTemplate, cla @(objc_class="CALayer") Layer :: struct { using _: Object } + +@(objc_type=Layer, objc_name="contents") +Layer_contents :: proc "c" (self: ^Layer) -> rawptr { + return msgSend(rawptr, self, "contents") +} +@(objc_type=Layer, objc_name="setContents") +Layer_setContents :: proc "c" (self: ^Layer, contents: rawptr) { + msgSend(nil, self, "setContents:", contents) +} @(objc_type=Layer, objc_name="contentsScale") Layer_contentsScale :: proc "c" (self: ^Layer) -> Float { return msgSend(Float, self, "contentsScale") @@ -654,8 +663,12 @@ Window_frame :: proc "c" (self: ^Window) -> Rect { return msgSend(Rect, self, "frame") } @(objc_type=Window, objc_name="setFrame") -Window_setFrame :: proc "c" (self: ^Window, frame: Rect) { - msgSend(nil, self, "setFrame:", frame) +Window_setFrame :: proc "c" (self: ^Window, frame: Rect, display: BOOL) { + msgSend(nil, self, "setFrame:display:", frame, display) +} +@(objc_type=Window, objc_name="setFrameOrigin") +Window_setFrameOrigin :: proc "c" (self: ^Window, origin: Point) { + msgSend(nil, self, "setFrameOrigin:", origin) } @(objc_type=Window, objc_name="opaque") Window_opaque :: proc "c" (self: ^Window) -> BOOL { @@ -693,6 +706,10 @@ Window_setMovable :: proc "c" (self: ^Window, ok: BOOL) { Window_setMovableByWindowBackground :: proc "c" (self: ^Window, ok: BOOL) { msgSend(nil, self, "setMovableByWindowBackground:", ok) } +@(objc_type=Window, objc_name="setAcceptsMouseMovedEvents") +Window_setAcceptsMouseMovedEvents :: proc "c" (self: ^Window, ok: BOOL) { + msgSend(nil, self, "setAcceptsMouseMovedEvents:", ok) +} @(objc_type=Window, objc_name="setStyleMask") Window_setStyleMask :: proc "c" (self: ^Window, style_mask: WindowStyleMask) { msgSend(nil, self, "setStyleMask:", style_mask) @@ -780,4 +797,4 @@ Window_performWindowDragWithEvent :: proc "c" (self: ^Window, event: ^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/darwin/Foundation/objc_helper.odin b/core/sys/darwin/Foundation/objc_helper.odin new file mode 100644 index 000000000..0748d700b --- /dev/null +++ b/core/sys/darwin/Foundation/objc_helper.odin @@ -0,0 +1,136 @@ +package objc_Foundation + +import "base:runtime" +import "base:intrinsics" + +Subclasser_Proc :: proc(cls: Class, vtable: rawptr) + +Object_VTable_Info :: struct { + vtable: rawptr, + size: uint, + impl: Subclasser_Proc, +} + +Class_VTable_Info :: struct { + _context: runtime.Context, + super_vtable: rawptr, + protocol_vtable: rawptr, +} + +@(require_results) +class_get_metaclass :: #force_inline proc "contextless" (cls: Class) -> Class { + return (^Class)(cls)^ +} + +@(require_results) +object_get_vtable_info :: proc "contextless" (obj: id) -> ^Class_VTable_Info { + return (^Class_VTable_Info)(object_getIndexedIvars(obj)) +} + +@(require_results) +make_subclasser :: #force_inline proc(vtable: ^$T, impl: proc(cls: Class, vt: ^T)) -> Object_VTable_Info { + return Object_VTable_Info{ + vtable = vtable, + size = size_of(T), + impl = (Subclasser_Proc)(impl), + } +} + +@(require_results) +register_subclass :: proc( + class_name: cstring, + superclass: Class, + superclass_overrides: Maybe(Object_VTable_Info) = nil, + protocol: Maybe(Object_VTable_Info) = nil, + _context: Maybe(runtime.Context) = nil, +) -> Class { + assert(superclass != nil) + + super_size: uint + proto_size: uint + + if superclass_overrides != nil { + // Align to 8-byte boundary + super_size = (superclass_overrides.?.size + 7)/8 * 8 + } + + if protocol != nil { + // Align to 8-byte boundary + proto_size = (protocol.?.size + 7)/8 * 8 + } + + cls := objc_lookUpClass(class_name) + if cls != nil { + return cls + } + + extra_size := uint(size_of(Class_VTable_Info)) + 8 + super_size + proto_size + + cls = objc_allocateClassPair(superclass, class_name, extra_size) + assert(cls != nil) + + if s, ok := superclass_overrides.?; ok { + s.impl(cls, s.vtable) + } + + if p, ok := protocol.?; ok { + p.impl(cls, p.vtable) + } + + objc_registerClassPair(cls) + meta_cls := class_get_metaclass(cls) + meta_size := uint(class_getInstanceSize(meta_cls)) + + // Offsets are always aligned to 8-byte boundary + info_offset := (meta_size + 7) / 8 * 8 + super_vtable_offset := (info_offset + size_of(Class_VTable_Info) + 7) / 8 * 8 + ptoto_vtable_offset := super_vtable_offset + super_size + + + p_info := (^Class_VTable_Info)(([^]u8)(cls)[info_offset:]) + p_super_vtable := ([^]u8)(cls)[super_vtable_offset:] + p_proto_vtable := ([^]u8)(cls)[ptoto_vtable_offset:] + + intrinsics.mem_zero(p_info, size_of(Class_VTable_Info)) + + // Assign the context + p_info._context = _context.? or_else context + + if s, ok := superclass_overrides.?; ok { + p_info.super_vtable = p_super_vtable + intrinsics.mem_copy(p_super_vtable, s.vtable, super_size) + } + if p, ok := protocol.?; ok { + p_info.protocol_vtable = p_proto_vtable + intrinsics.mem_copy(p_proto_vtable, p.vtable, p.size) + } + + return cls +} + +@(require_results) +class_get_vtable_info :: proc "contextless" (cls: Class) -> ^Class_VTable_Info { + meta_cls := class_get_metaclass(cls) + meta_size := uint(class_getInstanceSize(meta_cls)) + + // Align to 8-byte boundary + info_offset := (meta_size+7) / 8 * 8 + + p_cls := ([^]u8)(cls)[info_offset:] + ctx := (^Class_VTable_Info)(p_cls) + return ctx +} + +@(require_results) +alloc_user_object :: proc "contextless" (cls: Class, _context: Maybe(runtime.Context) = nil) -> id { + info := class_get_vtable_info(cls) + + obj := class_createInstance(cls, size_of(Class_VTable_Info)) + obj_info := (^Class_VTable_Info)(object_getIndexedIvars(obj)) + obj_info^ = info^ + + if _context != nil { + obj_info._context = _context.? + } + return obj +}
\ No newline at end of file diff --git a/core/sys/darwin/copyfile.odin b/core/sys/darwin/copyfile.odin new file mode 100644 index 000000000..6c58b8067 --- /dev/null +++ b/core/sys/darwin/copyfile.odin @@ -0,0 +1,67 @@ +package darwin + +import "core:sys/posix" + +copyfile_state_t :: distinct rawptr + +copyfile_flags :: bit_set[enum { + ACL, + STAT, + XATTR, + DATA, + + RECURSIVE = 15, + + CHECK, + EXCL, + NOFOLLOW_SRC, + NOFOLLOW_DST, + MOVE, + UNLINK, + PACK, + UNPACK, + + CLONE, + CLONE_FORCE, + RUN_IN_PLACE, + DATA_SPARSE, + PRESERVE_DST_TRACKED, + VERBOSE = 30, +}; u32] + +COPYFILE_SECURITY :: copyfile_flags{.STAT, .ACL} +COPYFILE_METADATA :: COPYFILE_SECURITY + copyfile_flags{.XATTR} +COPYFILE_ALL :: COPYFILE_METADATA + copyfile_flags{.DATA} + +COPYFILE_NOFOLLOW :: copyfile_flags{.NOFOLLOW_SRC, .NOFOLLOW_DST} + +copyfile_state_flag :: enum u32 { + SRC_FD = 1, + SRC_FILENAME, + DST_FD, + DST_FILENAME, + QUARANTINE, + STATUS_CB, + STATUS_CTX, + COPIED, + XATTRNAME, + WAS_CLONED, + SRC_BSIZE, + DST_BSIZE, + BSIZE, + FORBID_CROSS_MOUNT, + NOCPROTECT, + PRESERVE_SUID, + RECURSIVE_SRC_FTSENT, + FORBID_DST_EXISTING_SYMLINKS, +} + +foreign system { + copyfile :: proc(from, to: cstring, state: copyfile_state_t, flags: copyfile_flags) -> i32 --- + fcopyfile :: proc(from, to: posix.FD, state: copyfile_state_t, flags: copyfile_flags) -> i32 --- + + copyfile_state_alloc :: proc() -> copyfile_state_t --- + copyfile_state_free :: proc(state: copyfile_state_t) -> posix.result --- + copyfile_state_get :: proc(state: copyfile_state_t, flag: copyfile_state_flag, dst: rawptr) -> posix.result --- + copyfile_state_set :: proc(state: copyfile_state_t, flag: copyfile_state_flag, src: rawptr) -> posix.result --- +} diff --git a/core/sys/darwin/darwin.odin b/core/sys/darwin/darwin.odin index d109f5544..52297505a 100644 --- a/core/sys/darwin/darwin.odin +++ b/core/sys/darwin/darwin.odin @@ -3,7 +3,8 @@ package darwin import "core:c" -foreign import system "system:System.framework" +@(export) +foreign import system "system:System" Bool :: b8 diff --git a/core/sys/darwin/mach_darwin.odin b/core/sys/darwin/mach_darwin.odin index 2cc823e69..19515fe87 100644 --- a/core/sys/darwin/mach_darwin.odin +++ b/core/sys/darwin/mach_darwin.odin @@ -1,19 +1,38 @@ package darwin -foreign import mach "system:System.framework" +foreign import mach "system:System" import "core:c" import "base:intrinsics" +mach_port_t :: distinct c.uint +task_t :: mach_port_t + +semaphore_t :: distinct u64 + kern_return_t :: distinct c.int +thread_act_t :: distinct u64 +thread_state_t :: distinct ^u32 +thread_list_t :: [^]thread_act_t +vm_region_recurse_info_t :: distinct ^i32 +task_info_t :: distinct ^i32 + +MACH_PORT_NULL :: 0 +MACH_PORT_DEAD :: ~mach_port_t(0) + +MACH_MSG_PORT_DESCRIPTOR :: 0 + +X86_THREAD_STATE32 :: 1 +X86_THREAD_STATE64 :: 4 +ARM_THREAD_STATE64 :: 6 + +mach_msg_option_t :: distinct i32 +name_t :: distinct cstring -mach_port_t :: distinct c.uint vm_map_t :: mach_port_t mem_entry_name_port_t :: mach_port_t ipc_space_t :: mach_port_t thread_t :: mach_port_t -task_t :: mach_port_t -semaphore_t :: mach_port_t vm_size_t :: distinct c.uintptr_t @@ -29,11 +48,279 @@ vm_inherit_t :: distinct c.uint mach_port_name_t :: distinct c.uint +mach_port_right_t :: distinct c.uint + sync_policy_t :: distinct c.int +mach_msg_port_descriptor_t :: struct { + name: mach_port_t, + _: u32, + using _: bit_field u32 { + _: u32 | 16, + disposition: u32 | 8, + type: u32 | 8, + }, +} + +Task_Port_Type :: enum u32 { + Kernel = 1, + Host, + Name, + Bootstrap, + Seatbelt = 7, + Access = 9, +} + +Bootstrap_Error :: enum u32 { + Success, + Not_Privileged = 1100, + Name_In_Use = 1101, + Unknown_Service = 1102, + Service_Active = 1103, + Bad_Count = 1104, + No_Memory = 1105, + No_Children = 1106, +} + +Msg_Type :: enum u32 { + Unstructured = 0, + Bit = 0, + Boolean = 0, + Integer_16 = 1, + Integer_32 = 2, + Char = 8, + Byte = 9, + Integer_8 = 9, + Real = 10, + Integer_64 = 11, + String = 12, + String_C = 12, + + Port_Name = 15, + + Move_Receive = 16, + Port_Receive = 16, + Move_Send = 17, + Port_Send = 17, + Move_Send_Once = 18, + Port_Send_Once = 18, + Copy_Send = 19, + Make_Send = 20, + Make_Send_Once = 21, +} + +Msg_Header_Bits :: enum u32 { + Zero = 0, + Remote_Mask = 0xff, + Local_Mask = 0xff00, + Migrated = 0x08000000, + Unused = 0x07ff0000, + Complex_Data = 0x10000000, + Complex_Ports = 0x20000000, + Circular = 0x40000000, + Complex = 0x80000000, +} + +mach_msg_type_t :: struct { + using _: bit_field u32 { + name: u32 | 8, + size: u32 | 8, + number: u32 | 12, + inline: u32 | 1, + longform: u32 | 1, + deallocate: u32 | 1, + unused: u32 | 1, + }, +} + +mach_msg_header_t :: struct { + msgh_bits: u32, + msgh_size: u32, + msgh_remote_port: mach_port_t, + msgh_local_port: mach_port_t, + msgh_voucher_port: u32, + msgh_id: i32, +} + +mach_msg_body_t :: struct { + msgh_descriptor_count: u32, +} + +mach_msg_trailer_t :: struct { + msgh_trailer_type: u32, + msgh_trailer_size: u32, +} + +x86_thread_state32_t :: struct { + eax: u32, + ebx: u32, + ecx: u32, + edx: u32, + edi: u32, + esi: u32, + ebp: u32, + esp: u32, + ss: u32, + eflags: u32, + eip: u32, + cs: u32, + ds: u32, + es: u32, + fs: u32, + gs: u32, +} +X86_THREAD_STATE32_COUNT :: size_of(x86_thread_state32_t) / size_of(u32) + +x86_thread_state64_t :: struct #packed { + rax: u64, + rbx: u64, + rcx: u64, + rdx: u64, + rdi: u64, + rsi: u64, + rbp: u64, + rsp: u64, + r8: u64, + r9: u64, + r10: u64, + r11: u64, + r12: u64, + r13: u64, + r14: u64, + r15: u64, + rip: u64, + rflags: u64, + cs: u64, + fs: u64, + gs: u64, +} +X86_THREAD_STATE64_COUNT :: size_of(x86_thread_state64_t) / size_of(u32) + +arm_thread_state64_t :: struct #packed { + x: [29]u64, + fp: u64, + lr: u64, + sp: u64, + pc: u64, + cpsr: u32, + pad: u32, +} +ARM_THREAD_STATE64_COUNT :: size_of(arm_thread_state64_t) / size_of(u32) + +THREAD_IDENTIFIER_INFO :: 4 +thread_identifier_info :: struct { + thread_id: u64, + thread_handler: u64, + dispatch_qaddr: u64, +} +THREAD_IDENTIFIER_INFO_COUNT :: size_of(thread_identifier_info) / size_of(u32) + +vm_region_submap_info_64 :: struct { + protection: u32, + max_protection: u32, + inheritance: u32, + offset: u64, + user_tag: u32, + pages_residept: u32, + pages_shared_now_private: u32, + pages_swapped_out: u32, + pages_dirtied: u32, + ref_count: u32, + shadow_depth: u16, + external_pager: u8, + share_mode: u8, + is_submap: b32, + behavior: i32, + object_id: u32, + user_wired_count: u16, + pages_reusable: u32, +} +VM_REGION_SUBMAP_INFO_COUNT_64 :: size_of(vm_region_submap_info_64) / size_of(u32) + +TASK_DYLD_INFO :: 17 +task_dyld_info :: struct { + all_image_info_addr: u64, + all_image_info_size: u64, + all_image_info_format: i32, +} +TASK_DYLD_INFO_COUNT :: size_of(task_dyld_info) / size_of(u32) + +dyld_image_info :: struct { + image_load_addr: u64, + image_file_path: cstring, + image_file_mod_date: u64, +} + +dyld_uuid_info :: struct { + image_load_addr: u64, + image_uuid: [16]u8, +} + +dyld_all_image_infos :: struct { + version: u32, + info_array_count: u32, + info_array: rawptr, + notification: rawptr, + process_detached_from_shared_region: b32, + libSystem_initialized: b32, + dyld_image_load_addr: u64, + jit_info: rawptr, + dyld_version: cstring, + error_message: cstring, + termination_flags: u64, + core_symbolication_shm_page: rawptr, + system_order_flag: u64, + uuid_array_count: u64, + uuid_array: rawptr, + dyld_all_image_infos_addr: u64, + initial_image_count: u64, + error_kind: u64, + error_client_of_dylib_path: cstring, + error_target_dylib_path: cstring, + error_symbol: cstring, + shared_cache_slide: u64, + shared_cache_uuid: [16]u8, + shared_cache_base_addr: u64, + info_array_change_timestamp: u64, + dyld_path: cstring, + notify_ports: [8]mach_port_t, + reserved: [7]u64, + shared_cache_fsid: u64, + shared_cache_fsobjid: u64, + compact_dyld_image_info_addr: u64, + compact_dyld_image_info_size: u64, + platform: u32, + aot_info_count: u32, + aot_info_array: rawptr, + aot_info_array_change_timestamp: u64, + aot_shared_cache_base_address: u64, + aot_shared_cache_uuid: [16]u8, +} + + @(default_calling_convention="c") foreign mach { - mach_task_self :: proc() -> mach_port_t --- + mach_task_self :: proc() -> mach_port_t --- + mach_msg :: proc(header: rawptr, option: Msg_Option_Flags, send_size: u32, receive_limit: u32, receive_name: mach_port_t, timeout: u32, notify: mach_port_t) -> Kern_Return --- + mach_msg_send :: proc(header: rawptr) -> Kern_Return --- + mach_vm_allocate :: proc(target_task: task_t, adddress: u64, size: u64, flags: i32) -> Kern_Return --- + mach_vm_deallocate :: proc(target_task: task_t, adddress: ^u64, size: u64) -> Kern_Return --- + mach_vm_remap :: proc(target_task: task_t, page: rawptr, size: u64, mask: u64, flags: i32, src_task: task_t, src_address: u64, copy: b32, cur_protection: ^i32, max_protection: ^i32, inheritance: VM_Inherit) -> Kern_Return --- + mach_vm_region_recurse :: proc(target_task: task_t, address: ^u64, size: ^u64, depth: ^u32, info: vm_region_recurse_info_t, count: ^u32) -> Kern_Return --- + vm_page_size: u64 + vm_page_mask: u64 + vm_page_shift: i32 + + mach_port_allocate :: proc(task: task_t, right: Port_Right, name: rawptr) -> Kern_Return --- + mach_port_deallocate :: proc(task: task_t, name: u32) -> Kern_Return --- + mach_port_extract_right :: proc(task: task_t, name: u32, msgt_name: u32, poly: ^mach_port_t, poly_poly: ^mach_port_t) -> Kern_Return --- + + task_get_special_port :: proc(task: task_t, port: i32, special_port: ^mach_port_t) -> Kern_Return --- + task_suspend :: proc(task: task_t) -> Kern_Return --- + task_resume :: proc(task: task_t) -> Kern_Return --- + task_threads :: proc(task: task_t, thread_list: ^thread_list_t, list_count: ^u32) -> Kern_Return --- + task_info :: proc(task: task_t, flavor: i32, info: task_info_t, count: ^u32) -> Kern_Return --- + task_terminate :: proc(task: task_t) -> Kern_Return --- semaphore_create :: proc(task: task_t, semaphore: ^semaphore_t, policy: Sync_Policy, value: c.int) -> Kern_Return --- semaphore_destroy :: proc(task: task_t, semaphore: semaphore_t) -> Kern_Return --- @@ -44,9 +331,11 @@ foreign mach { semaphore_wait :: proc(semaphore: semaphore_t) -> Kern_Return --- - vm_allocate :: proc (target_task : vm_map_t, address: ^vm_address_t, size: vm_size_t, flags: VM_Flags) -> Kern_Return --- + thread_get_state :: proc(thread: thread_act_t, flavor: i32, thread_state: thread_state_t, old_state_count: ^u32) -> Kern_Return --- + thread_info :: proc(thread: thread_act_t, flavor: u32, thread_info: ^thread_identifier_info, info_count: ^u32) -> Kern_Return --- - vm_deallocate :: proc(target_task: vm_map_t, address: vm_address_t, size: vm_size_t) -> Kern_Return --- + bootstrap_register2 :: proc(bp: mach_port_t, service_name: name_t, sp: mach_port_t, flags: u64) -> Kern_Return --- + bootstrap_look_up :: proc(bp: mach_port_t, service_name: name_t, sp: ^mach_port_t) -> Kern_Return --- vm_map :: proc( target_task: vm_map_t, @@ -70,14 +359,9 @@ foreign mach { object_handle: ^mem_entry_name_port_t, parent_entry: mem_entry_name_port_t, ) -> Kern_Return --- +} - mach_port_deallocate :: proc( - task: ipc_space_t, - name: mach_port_name_t, - ) -> Kern_Return --- - vm_page_size: vm_size_t -} Kern_Return :: enum kern_return_t { Success, @@ -501,6 +785,39 @@ VM_PROT_DEFAULT :: VM_Prot_Flags{.Read, .Write} VM_PROT_ALL :: VM_Prot_Flags{.Read, .Write, .Execute} /* + * Mach msg options, defined as bits within the mach_msg_option_t type + */ + +Msg_Option :: enum mach_msg_option_t { + Send_Msg, + Receive_Msg, + + Send_Timeout = LOG2(0x10), + Send_Notify = LOG2(0x20), + Send_Interrupt = LOG2(0x40), + Send_Cancel = LOG2(0x80), + Receive_Timeout = LOG2(0x100), + Receive_Notify = LOG2(0x200), + Receive_Interrupt = LOG2(0x400), + Receive_Large = LOG2(0x800), + Send_Always = LOG2(0x10000), +} + +Msg_Option_Flags :: distinct bit_set[Msg_Option; mach_msg_option_t] + +/* + * Enumeration of valid values for mach_port_right_t + */ + +Port_Right :: enum mach_port_right_t { + Send, + Receive, + Send_Once, + Port_Set, + Dead_Name, +} + +/* * Enumeration of valid values for vm_inherit_t. */ @@ -522,3 +839,7 @@ Sync_Policy :: enum sync_policy_t { Lifo = Fifo | Reversed, } + +mach_vm_trunc_page :: proc(v: u64) -> u64 { + return v & ~vm_page_mask +} diff --git a/core/sys/darwin/proc.odin b/core/sys/darwin/proc.odin index fa5391f6f..ccd05f478 100644 --- a/core/sys/darwin/proc.odin +++ b/core/sys/darwin/proc.odin @@ -4,7 +4,7 @@ import "base:intrinsics" import "core:sys/posix" -foreign import lib "system:System.framework" +foreign import lib "system:System" // Incomplete bindings to the proc API on MacOS, add to when needed. diff --git a/core/sys/darwin/sync.odin b/core/sys/darwin/sync.odin index 58fc7c9e4..5f4f16fc3 100644 --- a/core/sys/darwin/sync.odin +++ b/core/sys/darwin/sync.odin @@ -1,27 +1,15 @@ package darwin -foreign import system "system:System.framework" - // #define OS_WAIT_ON_ADDR_AVAILABILITY \ // __API_AVAILABLE(macos(14.4), ios(17.4), tvos(17.4), watchos(10.4)) when ODIN_OS == .Darwin { - - when ODIN_PLATFORM_SUBTARGET == .iOS && ODIN_MINIMUM_OS_VERSION >= 17_04_00 { - WAIT_ON_ADDRESS_AVAILABLE :: true - } else when ODIN_MINIMUM_OS_VERSION >= 14_04_00 { - WAIT_ON_ADDRESS_AVAILABLE :: true - } else { - WAIT_ON_ADDRESS_AVAILABLE :: false - } - - when ODIN_PLATFORM_SUBTARGET == .iOS && ODIN_MINIMUM_OS_VERSION >= 14_00_00 { - ULOCK_WAIT_2_AVAILABLE :: true - } else when ODIN_MINIMUM_OS_VERSION >= 11_00_00 { - ULOCK_WAIT_2_AVAILABLE :: true + when ODIN_PLATFORM_SUBTARGET_IOS { + WAIT_ON_ADDRESS_AVAILABLE :: ODIN_MINIMUM_OS_VERSION >= 17_04_00 + ULOCK_WAIT_2_AVAILABLE :: ODIN_MINIMUM_OS_VERSION >= 14_00_00 } else { - ULOCK_WAIT_2_AVAILABLE :: false + WAIT_ON_ADDRESS_AVAILABLE :: ODIN_MINIMUM_OS_VERSION >= 14_04_00 + ULOCK_WAIT_2_AVAILABLE :: ODIN_MINIMUM_OS_VERSION >= 11_00_00 } - } else { WAIT_ON_ADDRESS_AVAILABLE :: false ULOCK_WAIT_2_AVAILABLE :: false diff --git a/core/sys/darwin/xnu_system_call_wrappers.odin b/core/sys/darwin/xnu_system_call_wrappers.odin index 1188091a9..43bcb543b 100644 --- a/core/sys/darwin/xnu_system_call_wrappers.odin +++ b/core/sys/darwin/xnu_system_call_wrappers.odin @@ -19,16 +19,6 @@ X_OK :: c.int((1 << 0)) /* test for execute or search permission */ W_OK :: c.int((1 << 1)) /* test for write permission */ R_OK :: c.int((1 << 2)) /* test for read permission */ -/* copyfile flags */ -COPYFILE_ACL :: (1 << 0) -COPYFILE_STAT :: (1 << 1) -COPYFILE_XATTR :: (1 << 2) -COPYFILE_DATA :: (1 << 3) - -COPYFILE_SECURITY :: (COPYFILE_STAT | COPYFILE_ACL) -COPYFILE_METADATA :: (COPYFILE_SECURITY | COPYFILE_XATTR) -COPYFILE_ALL :: (COPYFILE_METADATA | COPYFILE_DATA) - /* syslimits.h */ PATH_MAX :: 1024 /* max bytes in pathname */ @@ -233,6 +223,11 @@ _Proc_Bsdinfo :: struct { /*--==========================================================================--*/ +/* Get window size */ +TIOCGWINSZ :: 0x40087468 + +/*--==========================================================================--*/ + syscall_fsync :: #force_inline proc "contextless" (fildes: c.int) -> bool { return !(cast(bool)intrinsics.syscall(unix_offset_syscall(.fsync), uintptr(fildes))) } @@ -285,6 +280,10 @@ syscall_lseek :: #force_inline proc "contextless" (fd: c.int, offset: i64, whenc return cast(i64)intrinsics.syscall(unix_offset_syscall(.lseek), uintptr(fd), uintptr(offset), uintptr(whence)) } +syscall_ioctl :: #force_inline proc "contextless" (fd: c.int, request: u32, arg: rawptr) -> c.int { + return (cast(c.int)intrinsics.syscall(unix_offset_syscall(.ioctl), uintptr(fd), uintptr(request), uintptr(arg))) +} + syscall_gettid :: #force_inline proc "contextless" () -> u64 { return cast(u64)intrinsics.syscall(unix_offset_syscall(.gettid)) } diff --git a/core/sys/es/api.odin b/core/sys/es/api.odin index 4cf64b748..ae1a1a8bb 100644 --- a/core/sys/es/api.odin +++ b/core/sys/es/api.odin @@ -8,564 +8,564 @@ ElementPublic :: struct { instance : ^INSTANCE_TYPE, flags : u64 , } -Generic :: rawptr; -INSTANCE_TYPE :: Instance; -Element :: ElementPublic; -Panel :: Element; -Window :: Element; -Scrollbar :: Element; -Button :: Element; -TextDisplay :: Element; -IconDisplay :: Element; -Textbox :: Element; -ListView :: Element; -Menu :: Element; -Choice :: Element; -ColorWell :: Element; -Splitter :: Element; -ImageDisplay :: Element; -TextPlan :: rawptr; -Store :: rawptr; -PaintTarget :: rawptr; -DirectoryMonitor :: rawptr; -NodeType :: u8 ; -Error :: int ; -Handle :: uint ; -Response :: i32; -FileOffset :: u64 ; -FileOffsetDifference :: i64 ; -AudioDeviceID :: u64 ; -SCANCODE_A :: (0x04); -SCANCODE_B :: (0x05); -SCANCODE_C :: (0x06); -SCANCODE_D :: (0x07); -SCANCODE_E :: (0x08); -SCANCODE_F :: (0x09); -SCANCODE_G :: (0x0A); -SCANCODE_H :: (0x0B); -SCANCODE_I :: (0x0C); -SCANCODE_J :: (0x0D); -SCANCODE_K :: (0x0E); -SCANCODE_L :: (0x0F); -SCANCODE_M :: (0x10); -SCANCODE_N :: (0x11); -SCANCODE_O :: (0x12); -SCANCODE_P :: (0x13); -SCANCODE_Q :: (0x14); -SCANCODE_R :: (0x15); -SCANCODE_S :: (0x16); -SCANCODE_T :: (0x17); -SCANCODE_U :: (0x18); -SCANCODE_V :: (0x19); -SCANCODE_W :: (0x1A); -SCANCODE_X :: (0x1B); -SCANCODE_Y :: (0x1C); -SCANCODE_Z :: (0x1D); -SCANCODE_1 :: (0x1E); -SCANCODE_2 :: (0x1F); -SCANCODE_3 :: (0x20); -SCANCODE_4 :: (0x21); -SCANCODE_5 :: (0x22); -SCANCODE_6 :: (0x23); -SCANCODE_7 :: (0x24); -SCANCODE_8 :: (0x25); -SCANCODE_9 :: (0x26); -SCANCODE_0 :: (0x27); -SCANCODE_ENTER :: (0x28); -SCANCODE_ESCAPE :: (0x29); -SCANCODE_BACKSPACE :: (0x2A); -SCANCODE_TAB :: (0x2B); -SCANCODE_SPACE :: (0x2C); -SCANCODE_HYPHEN :: (0x2D); -SCANCODE_EQUALS :: (0x2E); -SCANCODE_LEFT_BRACE :: (0x2F); -SCANCODE_RIGHT_BRACE :: (0x30); -SCANCODE_COMMA :: (0x36); -SCANCODE_PERIOD :: (0x37); -SCANCODE_SLASH :: (0x38); -SCANCODE_PUNCTUATION_1 :: (0x31) ; -SCANCODE_PUNCTUATION_2 :: (0x32) ; -SCANCODE_PUNCTUATION_3 :: (0x33) ; -SCANCODE_PUNCTUATION_4 :: (0x34) ; -SCANCODE_PUNCTUATION_5 :: (0x35) ; -SCANCODE_PUNCTUATION_6 :: (0x64) ; -SCANCODE_F1 :: (0x3A); -SCANCODE_F2 :: (0x3B); -SCANCODE_F3 :: (0x3C); -SCANCODE_F4 :: (0x3D); -SCANCODE_F5 :: (0x3E); -SCANCODE_F6 :: (0x3F); -SCANCODE_F7 :: (0x40); -SCANCODE_F8 :: (0x41); -SCANCODE_F9 :: (0x42); -SCANCODE_F10 :: (0x43); -SCANCODE_F11 :: (0x44); -SCANCODE_F12 :: (0x45); -SCANCODE_F13 :: (0x68); -SCANCODE_F14 :: (0x69); -SCANCODE_F15 :: (0x6A); -SCANCODE_F16 :: (0x6B); -SCANCODE_F17 :: (0x6C); -SCANCODE_F18 :: (0x6D); -SCANCODE_F19 :: (0x6E); -SCANCODE_F20 :: (0x6F); -SCANCODE_F21 :: (0x70); -SCANCODE_F22 :: (0x71); -SCANCODE_F23 :: (0x72); -SCANCODE_F24 :: (0x73); -SCANCODE_CAPS_LOCK :: (0x39); -SCANCODE_PRINT_SCREEN :: (0x46); -SCANCODE_SCROLL_LOCK :: (0x47); -SCANCODE_PAUSE :: (0x48); -SCANCODE_INSERT :: (0x49); -SCANCODE_HOME :: (0x4A); -SCANCODE_PAGE_UP :: (0x4B); -SCANCODE_DELETE :: (0x4C); -SCANCODE_END :: (0x4D); -SCANCODE_PAGE_DOWN :: (0x4E); -SCANCODE_RIGHT_ARROW :: (0x4F); -SCANCODE_LEFT_ARROW :: (0x50); -SCANCODE_DOWN_ARROW :: (0x51); -SCANCODE_UP_ARROW :: (0x52); -SCANCODE_NUM_LOCK :: (0x53); -SCANCODE_CONTEXT_MENU :: (0x65); -SCANCODE_SYSTEM_REQUEST :: (0x9A); -SCANCODE_ACTION_EXECUTE :: (0x74); -SCANCODE_ACTION_HELP :: (0x75); -SCANCODE_ACTION_MENU :: (0x76); -SCANCODE_ACTION_SELECT :: (0x77); -SCANCODE_ACTION_STOP :: (0x78); -SCANCODE_ACTION_AGAIN :: (0x79); -SCANCODE_ACTION_UNDO :: (0x7A); -SCANCODE_ACTION_CUT :: (0x7B); -SCANCODE_ACTION_COPY :: (0x7C); -SCANCODE_ACTION_PASTE :: (0x7D); -SCANCODE_ACTION_FIND :: (0x7E); -SCANCODE_ACTION_CANCEL :: (0x9B); -SCANCODE_ACTION_CLEAR :: (0x9C); -SCANCODE_ACTION_PRIOR :: (0x9D); -SCANCODE_ACTION_RETURN :: (0x9E); -SCANCODE_ACTION_SEPARATOR :: (0x9F); -SCANCODE_MM_MUTE :: (0x7F); -SCANCODE_MM_LOUDER :: (0x80); -SCANCODE_MM_QUIETER :: (0x81); -SCANCODE_MM_NEXT :: (0x103); -SCANCODE_MM_PREVIOUS :: (0x104); -SCANCODE_MM_STOP :: (0x105); -SCANCODE_MM_PAUSE :: (0x106); -SCANCODE_MM_SELECT :: (0x107); -SCANCODE_MM_EMAIL :: (0x108); -SCANCODE_MM_CALC :: (0x109); -SCANCODE_MM_FILES :: (0x10A); -SCANCODE_INTERNATIONAL_1 :: (0x87); -SCANCODE_INTERNATIONAL_2 :: (0x88); -SCANCODE_INTERNATIONAL_3 :: (0x89); -SCANCODE_INTERNATIONAL_4 :: (0x8A); -SCANCODE_INTERNATIONAL_5 :: (0x8B); -SCANCODE_INTERNATIONAL_6 :: (0x8C); -SCANCODE_INTERNATIONAL_7 :: (0x8D); -SCANCODE_INTERNATIONAL_8 :: (0x8E); -SCANCODE_INTERNATIONAL_9 :: (0x8F); -SCANCODE_HANGUL_ENGLISH_TOGGLE :: (0x90); -SCANCODE_HANJA_CONVERSION :: (0x91); -SCANCODE_KATAKANA :: (0x92); -SCANCODE_HIRAGANA :: (0x93); -SCANCODE_HANKAKU_ZENKAKU_TOGGLE :: (0x94); -SCANCODE_ALTERNATE_ERASE :: (0x99); -SCANCODE_THOUSANDS_SEPARATOR :: (0xB2); -SCANCODE_DECIMAL_SEPARATOR :: (0xB3); -SCANCODE_CURRENCY_UNIT :: (0xB4); -SCANCODE_CURRENCY_SUBUNIT :: (0xB5); -SCANCODE_NUM_DIVIDE :: (0x54); -SCANCODE_NUM_MULTIPLY :: (0x55); -SCANCODE_NUM_SUBTRACT :: (0x56); -SCANCODE_NUM_ADD :: (0x57); -SCANCODE_NUM_ENTER :: (0x58); -SCANCODE_NUM_1 :: (0x59); -SCANCODE_NUM_2 :: (0x5A); -SCANCODE_NUM_3 :: (0x5B); -SCANCODE_NUM_4 :: (0x5C); -SCANCODE_NUM_5 :: (0x5D); -SCANCODE_NUM_6 :: (0x5E); -SCANCODE_NUM_7 :: (0x5F); -SCANCODE_NUM_8 :: (0x60); -SCANCODE_NUM_9 :: (0x61); -SCANCODE_NUM_0 :: (0x62); -SCANCODE_NUM_POINT :: (0x63); -SCANCODE_NUM_EQUALS :: (0x67); -SCANCODE_NUM_COMMA :: (0x82); -SCANCODE_NUM_00 :: (0xB0); -SCANCODE_NUM_000 :: (0xB1); -SCANCODE_NUM_LEFT_PAREN :: (0xB6); -SCANCODE_NUM_RIGHT_PAREN :: (0xB7); -SCANCODE_NUM_LEFT_BRACE :: (0xB8); -SCANCODE_NUM_RIGHT_BRACE :: (0xB9); -SCANCODE_NUM_TAB :: (0xBA); -SCANCODE_NUM_BACKSPACE :: (0xBB); -SCANCODE_NUM_A :: (0xBC); -SCANCODE_NUM_B :: (0xBD); -SCANCODE_NUM_C :: (0xBE); -SCANCODE_NUM_D :: (0xBF); -SCANCODE_NUM_E :: (0xC0); -SCANCODE_NUM_F :: (0xC1); -SCANCODE_NUM_XOR :: (0xC2); -SCANCODE_NUM_CARET :: (0xC3); -SCANCODE_NUM_PERCENT :: (0xC4); -SCANCODE_NUM_LESS_THAN :: (0xC5); -SCANCODE_NUM_GREATER_THAN :: (0xC6); -SCANCODE_NUM_AMPERSAND :: (0xC7); -SCANCODE_NUM_DOUBLE_AMPERSAND :: (0xC8); -SCANCODE_NUM_BAR :: (0xC9); -SCANCODE_NUM_DOUBLE_BAR :: (0xCA); -SCANCODE_NUM_COLON :: (0xCB); -SCANCODE_NUM_HASH :: (0xCC); -SCANCODE_NUM_SPACE :: (0xCD); -SCANCODE_NUM_AT :: (0xCE); -SCANCODE_NUM_EXCLAMATION_MARK :: (0xCF); -SCANCODE_NUM_MEMORY_STORE :: (0xD0); -SCANCODE_NUM_MEMORY_RECALL :: (0xD1); -SCANCODE_NUM_MEMORY_CLEAR :: (0xD2); -SCANCODE_NUM_MEMORY_ADD :: (0xD3); -SCANCODE_NUM_MEMORY_SUBTRACT :: (0xD4); -SCANCODE_NUM_MEMORY_MULTIPLY :: (0xD5); -SCANCODE_NUM_MEMORY_DIVIDE :: (0xD6); -SCANCODE_NUM_NEGATE :: (0xD7); -SCANCODE_NUM_CLEAR_ALL :: (0xD8); -SCANCODE_NUM_CLEAR :: (0xD9); -SCANCODE_NUM_BINARY :: (0xDA); -SCANCODE_NUM_OCTAL :: (0xDB); -SCANCODE_NUM_DECIMAL :: (0xDC); -SCANCODE_NUM_HEXADECIMAL :: (0xDD); -SCANCODE_LEFT_CTRL :: (0xE0); -SCANCODE_LEFT_SHIFT :: (0xE1); -SCANCODE_LEFT_ALT :: (0xE2); -SCANCODE_LEFT_FLAG :: (0xE3); -SCANCODE_RIGHT_CTRL :: (0xE4); -SCANCODE_RIGHT_SHIFT :: (0xE5); -SCANCODE_RIGHT_ALT :: (0xE6); -SCANCODE_RIGHT_FLAG :: (0xE7); -SCANCODE_ACPI_POWER :: (0x100); -SCANCODE_ACPI_SLEEP :: (0x101); -SCANCODE_ACPI_WAKE :: (0x102); -SCANCODE_WWW_SEARCH :: (0x10B); -SCANCODE_WWW_HOME :: (0x10C); -SCANCODE_WWW_BACK :: (0x10D); -SCANCODE_WWW_FORWARD :: (0x10E); -SCANCODE_WWW_STOP :: (0x10F); -SCANCODE_WWW_REFRESH :: (0x110); -SCANCODE_WWW_STARRED :: (0x111); -PROCESS_STATE_ALL_THREADS_TERMINATED :: (1); -PROCESS_STATE_TERMINATING :: (2); -PROCESS_STATE_CRASHED :: (4); -PROCESS_STATE_PINGED :: (8); -FLAGS_DEFAULT :: (0); -SUCCESS :: (-1); -ERROR_BUFFER_TOO_SMALL :: (-2); -ERROR_UNKNOWN :: (-7); -ERROR_NO_MESSAGES_AVAILABLE :: (-9); -ERROR_MESSAGE_QUEUE_FULL :: (-10); -ERROR_PATH_NOT_WITHIN_MOUNTED_VOLUME :: (-14); -ERROR_PATH_NOT_TRAVERSABLE :: (-15); -ERROR_FILE_ALREADY_EXISTS :: (-19); -ERROR_FILE_DOES_NOT_EXIST :: (-20); -ERROR_DRIVE_ERROR_FILE_DAMAGED :: (-21) ; -ERROR_ACCESS_NOT_WITHIN_FILE_BOUNDS :: (-22) ; -ERROR_FILE_PERMISSION_NOT_GRANTED :: (-23); -ERROR_FILE_IN_EXCLUSIVE_USE :: (-24); -ERROR_FILE_CANNOT_GET_EXCLUSIVE_USE :: (-25); -ERROR_INCORRECT_NODE_TYPE :: (-26); -ERROR_EVENT_NOT_SET :: (-27); -ERROR_FILE_HAS_WRITERS :: (-28); -ERROR_TIMEOUT_REACHED :: (-29); -ERROR_FILE_ON_READ_ONLY_VOLUME :: (-32); -ERROR_INVALID_DIMENSIONS :: (-34); -ERROR_DRIVE_CONTROLLER_REPORTED :: (-35); -ERROR_COULD_NOT_ISSUE_PACKET :: (-36); -ERROR_HANDLE_TABLE_FULL :: (-37); -ERROR_COULD_NOT_RESIZE_FILE :: (-38); -ERROR_DIRECTORY_NOT_EMPTY :: (-39); -ERROR_NODE_DELETED :: (-41); -ERROR_VOLUME_MISMATCH :: (-43); -ERROR_TARGET_WITHIN_SOURCE :: (-44); -ERROR_TARGET_INVALID_TYPE :: (-45); -ERROR_MALFORMED_NODE_PATH :: (-47); -ERROR_OUT_OF_CACHE_RESOURCES :: (-48); -ERROR_TARGET_IS_SOURCE :: (-49); -ERROR_INVALID_NAME :: (-50); -ERROR_CORRUPT_DATA :: (-51); -ERROR_INSUFFICIENT_RESOURCES :: (-52); -ERROR_UNSUPPORTED_FEATURE :: (-53); -ERROR_FILE_TOO_FRAGMENTED :: (-54); -ERROR_DRIVE_FULL :: (-55); -ERROR_COULD_NOT_RESOLVE_SYMBOL :: (-56); -ERROR_ALREADY_EMBEDDED :: (-57); -ERROR_EVENT_SINK_OVERFLOW :: (-58); -ERROR_EVENT_SINK_DUPLICATE :: (-59); -ERROR_UNSUPPORTED_CONVERSION :: (-60); -ERROR_SOURCE_EMPTY :: (-61); -ERROR_UNSUPPORTED_EXECUTABLE :: (-62); -ERROR_NO_ADDRESS_FOR_DOMAIN_NAME :: (-63); -ERROR_NO_CONNECTED_NETWORK_INTERFACES :: (-64); -ERROR_BAD_DOMAIN_NAME :: (-65); -ERROR_LOST_IP_ADDRESS :: (-66); -ERROR_CONNECTION_RESET :: (-67); -ERROR_CONNECTION_REFUSED :: (-68); -SYSTEM_CONSTANT_TIME_STAMP_UNITS_PER_MICROSECOND :: (0); -SYSTEM_CONSTANT_NO_FANCY_GRAPHICS :: (1); -SYSTEM_CONSTANT_REPORTED_PROBLEMS :: (2); -SYSTEM_CONSTANT_RIGHT_TO_LEFT :: (3); -SYSTEM_CONSTANT_WINDOW_INSET :: (4); -SYSTEM_CONSTANT_CONTAINER_TAB_BAND_HEIGHT :: (5); -INVALID_HANDLE :: (( Handle) (0)); -CURRENT_THREAD :: (( Handle) (0x10)); -CURRENT_PROCESS :: (( Handle) (0x11)); -DRAW_ALPHA_OVERWRITE :: (0x100); -DRAW_ALPHA_FULL :: (0x200) ; -WAIT_NO_TIMEOUT :: (-1); -MAX_WAIT_COUNT :: (8); -MAX_EVENT_FORWARD_COUNT :: (4) ; -MAX_EVENT_SINK_BUFFER_SIZE :: (256) ; -MAX_DIRECTORY_CHILD_NAME_LENGTH :: (256); -PROCESS_EXECUTABLE_NOT_LOADED :: (0); -PROCESS_EXECUTABLE_FAILED_TO_LOAD :: (1); -PROCESS_EXECUTABLE_LOADED :: (2); -SNAPSHOT_MAX_PROCESS_NAME_LENGTH :: (80); -SYSTEM_SNAPSHOT_PROCESSES :: (1); -SYSTEM_SNAPSHOT_DRIVES :: (2); -HANDLED :: (0); -NOT_HANDLED :: (-1); -REJECTED :: (-2); -SHARED_MEMORY_NAME_MAX_LENGTH :: (32); -MAP_OBJECT_ALL :: (0); -TEXT_H_LEFT :: (1); -TEXT_H_RIGHT :: (2); -TEXT_H_CENTER :: (3); -TEXT_V_TOP :: (4); -TEXT_V_BOTTOM :: (8); -TEXT_V_CENTER :: (12); -TEXT_WRAP :: (16); -TEXT_ELLIPSIS :: (32); -NODE_ACCESS_READ_SHARED :: (0x1) ; -NODE_ACCESS_READ :: (0x2) ; -NODE_ACCESS_WRITE :: (0x4) ; -NODE_ACCESS_WRITE_EXCLUSIVE :: (0x8) ; -NODE_FILE :: (0); -NODE_DIRECTORY :: (0x10); -NODE_INVALID :: (0x20); -NODE_FAIL_IF_FOUND :: (0x1000); -NODE_FAIL_IF_NOT_FOUND :: (0x2000); -NODE_PREVENT_RESIZE :: (0x4000); -NODE_CREATE_DIRECTORIES :: (0x8000) ; -NODE_POSIX_NAMESPACE :: (0x10000) ; -_ES_NODE_FROM_WRITE_EXCLUSIVE :: (0x20000); -DIRECTORY_CHILDREN_UNKNOWN :: (( FileOffsetDifference) (-1)); -MEMORY_OPEN_FAIL_IF_FOUND :: (0x1000); -MEMORY_OPEN_FAIL_IF_NOT_FOUND :: (0x2000); -MAP_OBJECT_READ_WRITE :: (0); -MAP_OBJECT_READ_ONLY :: (1); -MAP_OBJECT_COPY_ON_WRITE :: (2); -STRING_FORMAT_ENOUGH_SPACE :: ( (-1)); -STRING_FORMAT_SIMPLE :: (1 << 0); -POSIX_SYSCALL_GET_POSIX_FD_PATH :: (0x10000); -PERMISSION_ACCESS_SYSTEM_FILES :: (1 << 0); -PERMISSION_ACCESS_USER_FILES :: (1 << 1); -PERMISSION_PROCESS_CREATE :: (1 << 2); -PERMISSION_PROCESS_OPEN :: (1 << 3); -PERMISSION_SCREEN_MODIFY :: (1 << 4) ; -PERMISSION_SHUTDOWN :: (1 << 5); -PERMISSION_TAKE_SYSTEM_SNAPSHOT :: (1 << 6); -PERMISSION_WINDOW_OPEN :: (1 << 7); -PERMISSION_ALL :: ( (-1)); -PERMISSION_INHERIT :: ( (1) << 63); -PANEL_STYLE_DEFAULT :: "Panel.Default"; -PANEL_STYLE_TRANSPARENT :: "Panel.Transparent"; -PANEL_STYLE_WINDOW_BACKGROUND :: "Panel.WindowBackground"; -PANEL_STYLE_WINDOW_DIVIDER :: "Panel.WindowDivider"; -PANEL_STYLE_SHEET :: "Panel.Sheet"; -PANEL_STYLE_GROUP_BOX :: "Panel.GroupBox"; -PANEL_STYLE_INDENT :: "Panel.Indent"; -PANEL_BAND_SIZE_DEFAULT :: (-1); -ELEMENT_FOCUSABLE :: ( (1) << 32); -ELEMENT_HIDDEN :: ( (1) << 33) ; -ELEMENT_DISABLED :: ( (1) << 34) ; -ELEMENT_DEBUG :: ( (1) << 35) ; -ELEMENT_SCROLL_X :: ( (1) << 36); -ELEMENT_SCROLL_Y :: ( (1) << 37); -ELEMENT_NO_HOVER :: ( (1) << 38) ; -ELEMENT_BLOCK_FOCUS :: ( (1) << 39) ; -ELEMENT_NOT_TAB_TRAVERSABLE :: ( (1) << 40) ; -ELEMENT_NO_INFORM_PARENT :: ( (1) << 41) ; -ELEMENT_CENTER_ACCESS_KEY_HINT :: ( (1) << 42) ; -ELEMENT_LAYOUT_HINT_HORIZONTAL :: ( (1) << 43) ; -ELEMENT_STICKY_ACCESS_KEY :: ( (1) << 44) ; -CELL_NEW_BAND :: ( (1) << 51); -CELL_COLLAPSABLE :: ( (1) << 51); -CELL_H_PUSH :: ( (1) << 54); -CELL_H_EXPAND :: ( (1) << 55); -CELL_H_SHRINK :: ( (1) << 56); -CELL_H_LEFT :: ( (1) << 57); -CELL_H_RIGHT :: ( (1) << 58); -CELL_V_PUSH :: ( (1) << 59); -CELL_V_EXPAND :: ( (1) << 60); -CELL_V_SHRINK :: ( (1) << 61); -CELL_V_TOP :: ( (1) << 62); -CELL_V_BOTTOM :: ( (1) << 63); -PANEL_VERTICAL :: (0x0001); -PANEL_WRAP :: (0x0002); -PANEL_TABLE :: (0x0004); -PANEL_Z_STACK :: (0x0008); -PANEL_H_LEFT :: (0x0010); -PANEL_H_RIGHT :: (0x0020); -PANEL_H_CENTER :: (0x0040); -PANEL_H_JUSTIFY :: (0x0080); -PANEL_V_TOP :: (0x0100); -PANEL_V_BOTTOM :: (0x0200); -PANEL_V_CENTER :: (0x0400); -PANEL_V_JUSTIFY :: (0x0800); -PANEL_H_SCROLL :: (0x1000); -PANEL_V_SCROLL :: (0x2000); -PANEL_SWITCHER :: (0x4000); -TEXTBOX_MULTILINE :: (1 << 0); -TEXTBOX_EDIT_BASED :: (1 << 1); -TEXTBOX_COMPACT :: (1 << 2); -TEXTBOX_NO_SMART_CONTEXT_MENUS :: (1 << 3); -TEXTBOX_FIND_BACKWARDS :: (1 << 0); -BUTTON_DEFAULT :: (1 << 0); -BUTTON_DANGEROUS :: (1 << 1); -BUTTON_MENU_ITEM :: (1 << 2); -BUTTON_NOT_FOCUSABLE :: (1 << 3); -BUTTON_TOOLBAR :: (1 << 4); -BUTTON_DROPDOWN :: (1 << 5); -BUTTON_COMPACT :: (1 << 6); -MENU_ITEM_HEADER :: (1 << 7); -BUTTON_CHECKBOX :: (1 << 8); -BUTTON_RADIOBOX :: (1 << 9); -BUTTON_CANCEL :: (1 << 10); -BUTTON_PUSH :: (1 << 11); -COLOR_WELL_HAS_OPACITY :: (1 << 0); -SCROLLBAR_VERTICAL :: (0 << 0); -SCROLLBAR_HORIZONTAL :: (1 << 0); -SPLITTER_VERTICAL :: (0 << 0); -SPLITTER_HORIZONTAL :: (1 << 0); -IMAGE_DISPLAY_LOAD_ASYNCHRONOUSLY :: (1 << 0); -IMAGE_DISPLAY_UNLOAD_WHEN_HIDDEN :: (1 << 1); -IMAGE_DISPLAY_UPSCALE_NEAREST :: (1 << 2); -IMAGE_DISPLAY_DOWNSCALE_NEAREST :: (1 << 3); -IMAGE_DISPLAY_OPAQUE :: (1 << 4); -LIST_VIEW_HORIZONTAL :: (1 << 0) ; -LIST_VIEW_VARIABLE_SIZE :: (1 << 1) ; -LIST_VIEW_TILED :: (1 << 2) ; -LIST_VIEW_NON_LINEAR :: (1 << 3) ; -LIST_VIEW_SINGLE_SELECT :: (1 << 4) ; -LIST_VIEW_MULTI_SELECT :: (1 << 5) ; -LIST_VIEW_COLUMNS :: (1 << 6) ; -LIST_VIEW_GROUP_HAS_HEADER :: (1 << 0) ; -LIST_VIEW_GROUP_HAS_FOOTER :: (1 << 1) ; -LIST_VIEW_GROUP_INDENT :: (1 << 2) ; -LIST_VIEW_GROUP_COLLAPSABLE :: (1 << 3) ; -LIST_VIEW_COLUMN_RIGHT_ALIGNED :: (1 << 0) ; -LIST_VIEW_COLUMN_ASCENDING :: (1 << 1) ; -LIST_VIEW_COLUMN_DESCENDING :: (1 << 2) ; -LIST_VIEW_COLUMN_HAS_MENU :: (1 << 3) ; -MENU_AT_CURSOR :: (1 << 0); -MENU_MAXIMUM_HEIGHT :: (1 << 1); -FONT_SANS :: (0xFFFF); -FONT_SERIF :: (0xFFFE); -FONT_MONOSPACED :: (0xFFFD); -FONT_REGULAR :: (4); -FONT_BOLD :: (7); -TEXT_FIGURE_DEFAULT :: (0); -TEXT_FIGURE_OLD :: (1); -TEXT_FIGURE_TABULAR :: (2); -TEXT_DISPLAY_CONTENT_MAX_VALUES :: (8); -DIRECTORY_MONITOR_SUBTREE :: (1 << 0) ; -DIRECTORY_MONITOR_CONTENTS :: (1 << 1) ; -DIRECTORY_MONITOR_MODIFY :: (1 << 2) ; -COMMAND_SYSTEM_START :: (0xF0000000); -COMMAND_DELETE :: (0xF0000001); -COMMAND_SELECT_ALL :: (0xF0000002); -COMMAND_CUT :: (0xF0000003); -COMMAND_COPY :: (0xF0000004); -COMMAND_PASTE :: (0xF0000005); -AUDIO_STREAM_OVERRUN :: (1 << 0); -AUDIO_STREAM_UNDERRUN :: (1 << 0); -AUDIO_STREAM_FORMAT_CHANGED :: (1 << 1); -AUDIO_STREAM_TERMINATED :: (1 << 2); -AUDIO_STREAM_RUNNING :: (1 << 0); -AUDIO_STREAM_MUTED :: (1 << 1); -AUDIO_STREAM_ONESHOT :: (1 << 2); -AUDIO_DEFAULT_OUTPUT :: (1); -SAMPLE_FORMAT_U8 :: (1); -SAMPLE_FORMAT_S16LE :: (2); -SAMPLE_FORMAT_S32LE :: (3); -SAMPLE_FORMAT_F32LE :: (4) ; -CELL_FILL :: ( CELL_H_FILL | CELL_V_FILL); -CELL_H_FILL :: ( CELL_H_PUSH | CELL_H_EXPAND | CELL_H_SHRINK); -CELL_V_FILL :: ( CELL_V_PUSH | CELL_V_EXPAND | CELL_V_SHRINK); -CELL_CENTER :: ( CELL_H_CENTER | CELL_V_CENTER); -CELL_PUSH :: ( CELL_H_PUSH | CELL_V_PUSH); -CELL_EXPAND :: ( CELL_H_EXPAND | CELL_V_EXPAND); -CELL_CORNER :: ( CELL_H_LEFT | CELL_V_TOP); -CELL_SHRINK :: ( CELL_H_SHRINK | CELL_V_SHRINK); -CELL_H_CENTER :: ( CELL_H_LEFT | CELL_H_RIGHT); -CELL_V_CENTER :: ( CELL_V_TOP | CELL_V_BOTTOM); -THEME_METRICS_INSETS :: (1 << 0); -THEME_METRICS_CLIP_INSETS :: (1 << 1); -THEME_METRICS_GLOBAL_OFFSET :: (1 << 2) ; -THEME_METRICS_CLIP_ENABLED :: (1 << 3); -THEME_METRICS_CURSOR :: (1 << 4); -THEME_METRICS_ENTRANCE_TRANSITION :: (1 << 5); -THEME_METRICS_EXIT_TRANSITION :: (1 << 6); -THEME_METRICS_ENTRANCE_DURATION :: (1 << 7); -THEME_METRICS_EXIT_DURATION :: (1 << 8); -THEME_METRICS_PREFERRED_WIDTH :: (1 << 9); -THEME_METRICS_PREFERRED_HEIGHT :: (1 << 10); -THEME_METRICS_MINIMUM_WIDTH :: (1 << 11); -THEME_METRICS_MINIMUM_HEIGHT :: (1 << 12); -THEME_METRICS_MAXIMUM_WIDTH :: (1 << 13); -THEME_METRICS_MAXIMUM_HEIGHT :: (1 << 14); -THEME_METRICS_GAP_MAJOR :: (1 << 15); -THEME_METRICS_GAP_MINOR :: (1 << 16); -THEME_METRICS_GAP_WRAP :: (1 << 17); -THEME_METRICS_GAP_ALL :: ( THEME_METRICS_GAP_MAJOR | THEME_METRICS_GAP_MINOR | THEME_METRICS_GAP_WRAP); -THEME_METRICS_TEXT_COLOR :: (1 << 18); -THEME_METRICS_SELECTED_BACKGROUND :: (1 << 19); -THEME_METRICS_SELECTED_TEXT :: (1 << 20); -THEME_METRICS_ICON_COLOR :: (1 << 21); -THEME_METRICS_TEXT_ALIGN :: (1 << 22); -THEME_METRICS_TEXT_SIZE :: (1 << 23); -THEME_METRICS_FONT_FAMILY :: (1 << 24); -THEME_METRICS_FONT_WEIGHT :: (1 << 25); -THEME_METRICS_ICON_SIZE :: (1 << 26); -THEME_METRICS_IS_ITALIC :: (1 << 27); -THEME_METRICS_ELLIPSIS :: (1 << 28); -THEME_METRICS_WRAP_TEXT :: (1 << 29); -MOVE_WINDOW_MAXIMISED :: (1 << 0); -MOVE_WINDOW_ADJUST_TO_FIT_SCREEN :: (1 << 1); -MOVE_WINDOW_HIDDEN :: (1 << 2); -MOVE_WINDOW_ALWAYS_ON_TOP :: (1 << 3); -MOVE_WINDOW_AT_BOTTOM :: (1 << 4); -MOVE_WINDOW_UPDATE_SCREEN :: (1 << 5); -WINDOW_SOLID_TRUE :: (1 << 0); -WINDOW_SOLID_NO_ACTIVATE :: (1 << 1); -THEME_BITMAP_WIDTH :: (400); -THEME_BITMAP_HEIGHT :: (200); -THEME_BITMAP_NAME :: "Desktop.ThemeBitmap"; -TEXTBOX_MOVE_CARET_SINGLE :: (2); -TEXTBOX_MOVE_CARET_WORD :: (3); -TEXTBOX_MOVE_CARET_LINE :: (4); -TEXTBOX_MOVE_CARET_VERTICAL :: (5); -TEXTBOX_MOVE_CARET_ALL :: (6); -TEXTBOX_MOVE_CARET_FIRST_ONLY :: (1 << 8); -TEXTBOX_MOVE_CARET_SECOND_ONLY :: (1 << 9); -TEXTBOX_MOVE_CARET_BACKWARDS :: (1 << 10); -TEXTBOX_MOVE_CARET_STRONG_WHITESPACE :: (1 << 11); -GAME_CONTROLLER_MAX_COUNT :: (16); -DOMAIN_NAME_MAX_LENGTH :: (255); -ECHO_REQUEST_MAX_LENGTH :: (48); -CONNECTION_OPEN_WAIT :: (1 << 0); -FILE_CONTROL_NOTIFY_MONITORS :: (1 << 0); -FILE_CONTROL_FLUSH :: (1 << 1); +Generic :: rawptr +INSTANCE_TYPE :: Instance +Element :: ElementPublic +Panel :: Element +Window :: Element +Scrollbar :: Element +Button :: Element +TextDisplay :: Element +IconDisplay :: Element +Textbox :: Element +ListView :: Element +Menu :: Element +Choice :: Element +ColorWell :: Element +Splitter :: Element +ImageDisplay :: Element +TextPlan :: rawptr +Store :: rawptr +PaintTarget :: rawptr +DirectoryMonitor :: rawptr +NodeType :: u8 +Error :: int +Handle :: uint +Response :: i32 +FileOffset :: u64 +FileOffsetDifference :: i64 +AudioDeviceID :: u64 +SCANCODE_A :: (0x04) +SCANCODE_B :: (0x05) +SCANCODE_C :: (0x06) +SCANCODE_D :: (0x07) +SCANCODE_E :: (0x08) +SCANCODE_F :: (0x09) +SCANCODE_G :: (0x0A) +SCANCODE_H :: (0x0B) +SCANCODE_I :: (0x0C) +SCANCODE_J :: (0x0D) +SCANCODE_K :: (0x0E) +SCANCODE_L :: (0x0F) +SCANCODE_M :: (0x10) +SCANCODE_N :: (0x11) +SCANCODE_O :: (0x12) +SCANCODE_P :: (0x13) +SCANCODE_Q :: (0x14) +SCANCODE_R :: (0x15) +SCANCODE_S :: (0x16) +SCANCODE_T :: (0x17) +SCANCODE_U :: (0x18) +SCANCODE_V :: (0x19) +SCANCODE_W :: (0x1A) +SCANCODE_X :: (0x1B) +SCANCODE_Y :: (0x1C) +SCANCODE_Z :: (0x1D) +SCANCODE_1 :: (0x1E) +SCANCODE_2 :: (0x1F) +SCANCODE_3 :: (0x20) +SCANCODE_4 :: (0x21) +SCANCODE_5 :: (0x22) +SCANCODE_6 :: (0x23) +SCANCODE_7 :: (0x24) +SCANCODE_8 :: (0x25) +SCANCODE_9 :: (0x26) +SCANCODE_0 :: (0x27) +SCANCODE_ENTER :: (0x28) +SCANCODE_ESCAPE :: (0x29) +SCANCODE_BACKSPACE :: (0x2A) +SCANCODE_TAB :: (0x2B) +SCANCODE_SPACE :: (0x2C) +SCANCODE_HYPHEN :: (0x2D) +SCANCODE_EQUALS :: (0x2E) +SCANCODE_LEFT_BRACE :: (0x2F) +SCANCODE_RIGHT_BRACE :: (0x30) +SCANCODE_COMMA :: (0x36) +SCANCODE_PERIOD :: (0x37) +SCANCODE_SLASH :: (0x38) +SCANCODE_PUNCTUATION_1 :: (0x31) +SCANCODE_PUNCTUATION_2 :: (0x32) +SCANCODE_PUNCTUATION_3 :: (0x33) +SCANCODE_PUNCTUATION_4 :: (0x34) +SCANCODE_PUNCTUATION_5 :: (0x35) +SCANCODE_PUNCTUATION_6 :: (0x64) +SCANCODE_F1 :: (0x3A) +SCANCODE_F2 :: (0x3B) +SCANCODE_F3 :: (0x3C) +SCANCODE_F4 :: (0x3D) +SCANCODE_F5 :: (0x3E) +SCANCODE_F6 :: (0x3F) +SCANCODE_F7 :: (0x40) +SCANCODE_F8 :: (0x41) +SCANCODE_F9 :: (0x42) +SCANCODE_F10 :: (0x43) +SCANCODE_F11 :: (0x44) +SCANCODE_F12 :: (0x45) +SCANCODE_F13 :: (0x68) +SCANCODE_F14 :: (0x69) +SCANCODE_F15 :: (0x6A) +SCANCODE_F16 :: (0x6B) +SCANCODE_F17 :: (0x6C) +SCANCODE_F18 :: (0x6D) +SCANCODE_F19 :: (0x6E) +SCANCODE_F20 :: (0x6F) +SCANCODE_F21 :: (0x70) +SCANCODE_F22 :: (0x71) +SCANCODE_F23 :: (0x72) +SCANCODE_F24 :: (0x73) +SCANCODE_CAPS_LOCK :: (0x39) +SCANCODE_PRINT_SCREEN :: (0x46) +SCANCODE_SCROLL_LOCK :: (0x47) +SCANCODE_PAUSE :: (0x48) +SCANCODE_INSERT :: (0x49) +SCANCODE_HOME :: (0x4A) +SCANCODE_PAGE_UP :: (0x4B) +SCANCODE_DELETE :: (0x4C) +SCANCODE_END :: (0x4D) +SCANCODE_PAGE_DOWN :: (0x4E) +SCANCODE_RIGHT_ARROW :: (0x4F) +SCANCODE_LEFT_ARROW :: (0x50) +SCANCODE_DOWN_ARROW :: (0x51) +SCANCODE_UP_ARROW :: (0x52) +SCANCODE_NUM_LOCK :: (0x53) +SCANCODE_CONTEXT_MENU :: (0x65) +SCANCODE_SYSTEM_REQUEST :: (0x9A) +SCANCODE_ACTION_EXECUTE :: (0x74) +SCANCODE_ACTION_HELP :: (0x75) +SCANCODE_ACTION_MENU :: (0x76) +SCANCODE_ACTION_SELECT :: (0x77) +SCANCODE_ACTION_STOP :: (0x78) +SCANCODE_ACTION_AGAIN :: (0x79) +SCANCODE_ACTION_UNDO :: (0x7A) +SCANCODE_ACTION_CUT :: (0x7B) +SCANCODE_ACTION_COPY :: (0x7C) +SCANCODE_ACTION_PASTE :: (0x7D) +SCANCODE_ACTION_FIND :: (0x7E) +SCANCODE_ACTION_CANCEL :: (0x9B) +SCANCODE_ACTION_CLEAR :: (0x9C) +SCANCODE_ACTION_PRIOR :: (0x9D) +SCANCODE_ACTION_RETURN :: (0x9E) +SCANCODE_ACTION_SEPARATOR :: (0x9F) +SCANCODE_MM_MUTE :: (0x7F) +SCANCODE_MM_LOUDER :: (0x80) +SCANCODE_MM_QUIETER :: (0x81) +SCANCODE_MM_NEXT :: (0x103) +SCANCODE_MM_PREVIOUS :: (0x104) +SCANCODE_MM_STOP :: (0x105) +SCANCODE_MM_PAUSE :: (0x106) +SCANCODE_MM_SELECT :: (0x107) +SCANCODE_MM_EMAIL :: (0x108) +SCANCODE_MM_CALC :: (0x109) +SCANCODE_MM_FILES :: (0x10A) +SCANCODE_INTERNATIONAL_1 :: (0x87) +SCANCODE_INTERNATIONAL_2 :: (0x88) +SCANCODE_INTERNATIONAL_3 :: (0x89) +SCANCODE_INTERNATIONAL_4 :: (0x8A) +SCANCODE_INTERNATIONAL_5 :: (0x8B) +SCANCODE_INTERNATIONAL_6 :: (0x8C) +SCANCODE_INTERNATIONAL_7 :: (0x8D) +SCANCODE_INTERNATIONAL_8 :: (0x8E) +SCANCODE_INTERNATIONAL_9 :: (0x8F) +SCANCODE_HANGUL_ENGLISH_TOGGLE :: (0x90) +SCANCODE_HANJA_CONVERSION :: (0x91) +SCANCODE_KATAKANA :: (0x92) +SCANCODE_HIRAGANA :: (0x93) +SCANCODE_HANKAKU_ZENKAKU_TOGGLE :: (0x94) +SCANCODE_ALTERNATE_ERASE :: (0x99) +SCANCODE_THOUSANDS_SEPARATOR :: (0xB2) +SCANCODE_DECIMAL_SEPARATOR :: (0xB3) +SCANCODE_CURRENCY_UNIT :: (0xB4) +SCANCODE_CURRENCY_SUBUNIT :: (0xB5) +SCANCODE_NUM_DIVIDE :: (0x54) +SCANCODE_NUM_MULTIPLY :: (0x55) +SCANCODE_NUM_SUBTRACT :: (0x56) +SCANCODE_NUM_ADD :: (0x57) +SCANCODE_NUM_ENTER :: (0x58) +SCANCODE_NUM_1 :: (0x59) +SCANCODE_NUM_2 :: (0x5A) +SCANCODE_NUM_3 :: (0x5B) +SCANCODE_NUM_4 :: (0x5C) +SCANCODE_NUM_5 :: (0x5D) +SCANCODE_NUM_6 :: (0x5E) +SCANCODE_NUM_7 :: (0x5F) +SCANCODE_NUM_8 :: (0x60) +SCANCODE_NUM_9 :: (0x61) +SCANCODE_NUM_0 :: (0x62) +SCANCODE_NUM_POINT :: (0x63) +SCANCODE_NUM_EQUALS :: (0x67) +SCANCODE_NUM_COMMA :: (0x82) +SCANCODE_NUM_00 :: (0xB0) +SCANCODE_NUM_000 :: (0xB1) +SCANCODE_NUM_LEFT_PAREN :: (0xB6) +SCANCODE_NUM_RIGHT_PAREN :: (0xB7) +SCANCODE_NUM_LEFT_BRACE :: (0xB8) +SCANCODE_NUM_RIGHT_BRACE :: (0xB9) +SCANCODE_NUM_TAB :: (0xBA) +SCANCODE_NUM_BACKSPACE :: (0xBB) +SCANCODE_NUM_A :: (0xBC) +SCANCODE_NUM_B :: (0xBD) +SCANCODE_NUM_C :: (0xBE) +SCANCODE_NUM_D :: (0xBF) +SCANCODE_NUM_E :: (0xC0) +SCANCODE_NUM_F :: (0xC1) +SCANCODE_NUM_XOR :: (0xC2) +SCANCODE_NUM_CARET :: (0xC3) +SCANCODE_NUM_PERCENT :: (0xC4) +SCANCODE_NUM_LESS_THAN :: (0xC5) +SCANCODE_NUM_GREATER_THAN :: (0xC6) +SCANCODE_NUM_AMPERSAND :: (0xC7) +SCANCODE_NUM_DOUBLE_AMPERSAND :: (0xC8) +SCANCODE_NUM_BAR :: (0xC9) +SCANCODE_NUM_DOUBLE_BAR :: (0xCA) +SCANCODE_NUM_COLON :: (0xCB) +SCANCODE_NUM_HASH :: (0xCC) +SCANCODE_NUM_SPACE :: (0xCD) +SCANCODE_NUM_AT :: (0xCE) +SCANCODE_NUM_EXCLAMATION_MARK :: (0xCF) +SCANCODE_NUM_MEMORY_STORE :: (0xD0) +SCANCODE_NUM_MEMORY_RECALL :: (0xD1) +SCANCODE_NUM_MEMORY_CLEAR :: (0xD2) +SCANCODE_NUM_MEMORY_ADD :: (0xD3) +SCANCODE_NUM_MEMORY_SUBTRACT :: (0xD4) +SCANCODE_NUM_MEMORY_MULTIPLY :: (0xD5) +SCANCODE_NUM_MEMORY_DIVIDE :: (0xD6) +SCANCODE_NUM_NEGATE :: (0xD7) +SCANCODE_NUM_CLEAR_ALL :: (0xD8) +SCANCODE_NUM_CLEAR :: (0xD9) +SCANCODE_NUM_BINARY :: (0xDA) +SCANCODE_NUM_OCTAL :: (0xDB) +SCANCODE_NUM_DECIMAL :: (0xDC) +SCANCODE_NUM_HEXADECIMAL :: (0xDD) +SCANCODE_LEFT_CTRL :: (0xE0) +SCANCODE_LEFT_SHIFT :: (0xE1) +SCANCODE_LEFT_ALT :: (0xE2) +SCANCODE_LEFT_FLAG :: (0xE3) +SCANCODE_RIGHT_CTRL :: (0xE4) +SCANCODE_RIGHT_SHIFT :: (0xE5) +SCANCODE_RIGHT_ALT :: (0xE6) +SCANCODE_RIGHT_FLAG :: (0xE7) +SCANCODE_ACPI_POWER :: (0x100) +SCANCODE_ACPI_SLEEP :: (0x101) +SCANCODE_ACPI_WAKE :: (0x102) +SCANCODE_WWW_SEARCH :: (0x10B) +SCANCODE_WWW_HOME :: (0x10C) +SCANCODE_WWW_BACK :: (0x10D) +SCANCODE_WWW_FORWARD :: (0x10E) +SCANCODE_WWW_STOP :: (0x10F) +SCANCODE_WWW_REFRESH :: (0x110) +SCANCODE_WWW_STARRED :: (0x111) +PROCESS_STATE_ALL_THREADS_TERMINATED :: (1) +PROCESS_STATE_TERMINATING :: (2) +PROCESS_STATE_CRASHED :: (4) +PROCESS_STATE_PINGED :: (8) +FLAGS_DEFAULT :: (0) +SUCCESS :: (-1) +ERROR_BUFFER_TOO_SMALL :: (-2) +ERROR_UNKNOWN :: (-7) +ERROR_NO_MESSAGES_AVAILABLE :: (-9) +ERROR_MESSAGE_QUEUE_FULL :: (-10) +ERROR_PATH_NOT_WITHIN_MOUNTED_VOLUME :: (-14) +ERROR_PATH_NOT_TRAVERSABLE :: (-15) +ERROR_FILE_ALREADY_EXISTS :: (-19) +ERROR_FILE_DOES_NOT_EXIST :: (-20) +ERROR_DRIVE_ERROR_FILE_DAMAGED :: (-21) +ERROR_ACCESS_NOT_WITHIN_FILE_BOUNDS :: (-22) +ERROR_FILE_PERMISSION_NOT_GRANTED :: (-23) +ERROR_FILE_IN_EXCLUSIVE_USE :: (-24) +ERROR_FILE_CANNOT_GET_EXCLUSIVE_USE :: (-25) +ERROR_INCORRECT_NODE_TYPE :: (-26) +ERROR_EVENT_NOT_SET :: (-27) +ERROR_FILE_HAS_WRITERS :: (-28) +ERROR_TIMEOUT_REACHED :: (-29) +ERROR_FILE_ON_READ_ONLY_VOLUME :: (-32) +ERROR_INVALID_DIMENSIONS :: (-34) +ERROR_DRIVE_CONTROLLER_REPORTED :: (-35) +ERROR_COULD_NOT_ISSUE_PACKET :: (-36) +ERROR_HANDLE_TABLE_FULL :: (-37) +ERROR_COULD_NOT_RESIZE_FILE :: (-38) +ERROR_DIRECTORY_NOT_EMPTY :: (-39) +ERROR_NODE_DELETED :: (-41) +ERROR_VOLUME_MISMATCH :: (-43) +ERROR_TARGET_WITHIN_SOURCE :: (-44) +ERROR_TARGET_INVALID_TYPE :: (-45) +ERROR_MALFORMED_NODE_PATH :: (-47) +ERROR_OUT_OF_CACHE_RESOURCES :: (-48) +ERROR_TARGET_IS_SOURCE :: (-49) +ERROR_INVALID_NAME :: (-50) +ERROR_CORRUPT_DATA :: (-51) +ERROR_INSUFFICIENT_RESOURCES :: (-52) +ERROR_UNSUPPORTED_FEATURE :: (-53) +ERROR_FILE_TOO_FRAGMENTED :: (-54) +ERROR_DRIVE_FULL :: (-55) +ERROR_COULD_NOT_RESOLVE_SYMBOL :: (-56) +ERROR_ALREADY_EMBEDDED :: (-57) +ERROR_EVENT_SINK_OVERFLOW :: (-58) +ERROR_EVENT_SINK_DUPLICATE :: (-59) +ERROR_UNSUPPORTED_CONVERSION :: (-60) +ERROR_SOURCE_EMPTY :: (-61) +ERROR_UNSUPPORTED_EXECUTABLE :: (-62) +ERROR_NO_ADDRESS_FOR_DOMAIN_NAME :: (-63) +ERROR_NO_CONNECTED_NETWORK_INTERFACES :: (-64) +ERROR_BAD_DOMAIN_NAME :: (-65) +ERROR_LOST_IP_ADDRESS :: (-66) +ERROR_CONNECTION_RESET :: (-67) +ERROR_CONNECTION_REFUSED :: (-68) +SYSTEM_CONSTANT_TIME_STAMP_UNITS_PER_MICROSECOND :: (0) +SYSTEM_CONSTANT_NO_FANCY_GRAPHICS :: (1) +SYSTEM_CONSTANT_REPORTED_PROBLEMS :: (2) +SYSTEM_CONSTANT_RIGHT_TO_LEFT :: (3) +SYSTEM_CONSTANT_WINDOW_INSET :: (4) +SYSTEM_CONSTANT_CONTAINER_TAB_BAND_HEIGHT :: (5) +INVALID_HANDLE :: (( Handle) (0)) +CURRENT_THREAD :: (( Handle) (0x10)) +CURRENT_PROCESS :: (( Handle) (0x11)) +DRAW_ALPHA_OVERWRITE :: (0x100) +DRAW_ALPHA_FULL :: (0x200) +WAIT_NO_TIMEOUT :: (-1) +MAX_WAIT_COUNT :: (8) +MAX_EVENT_FORWARD_COUNT :: (4) +MAX_EVENT_SINK_BUFFER_SIZE :: (256) +MAX_DIRECTORY_CHILD_NAME_LENGTH :: (256) +PROCESS_EXECUTABLE_NOT_LOADED :: (0) +PROCESS_EXECUTABLE_FAILED_TO_LOAD :: (1) +PROCESS_EXECUTABLE_LOADED :: (2) +SNAPSHOT_MAX_PROCESS_NAME_LENGTH :: (80) +SYSTEM_SNAPSHOT_PROCESSES :: (1) +SYSTEM_SNAPSHOT_DRIVES :: (2) +HANDLED :: (0) +NOT_HANDLED :: (-1) +REJECTED :: (-2) +SHARED_MEMORY_NAME_MAX_LENGTH :: (32) +MAP_OBJECT_ALL :: (0) +TEXT_H_LEFT :: (1) +TEXT_H_RIGHT :: (2) +TEXT_H_CENTER :: (3) +TEXT_V_TOP :: (4) +TEXT_V_BOTTOM :: (8) +TEXT_V_CENTER :: (12) +TEXT_WRAP :: (16) +TEXT_ELLIPSIS :: (32) +NODE_ACCESS_READ_SHARED :: (0x1) +NODE_ACCESS_READ :: (0x2) +NODE_ACCESS_WRITE :: (0x4) +NODE_ACCESS_WRITE_EXCLUSIVE :: (0x8) +NODE_FILE :: (0) +NODE_DIRECTORY :: (0x10) +NODE_INVALID :: (0x20) +NODE_FAIL_IF_FOUND :: (0x1000) +NODE_FAIL_IF_NOT_FOUND :: (0x2000) +NODE_PREVENT_RESIZE :: (0x4000) +NODE_CREATE_DIRECTORIES :: (0x8000) +NODE_POSIX_NAMESPACE :: (0x10000) +_ES_NODE_FROM_WRITE_EXCLUSIVE :: (0x20000) +DIRECTORY_CHILDREN_UNKNOWN :: (( FileOffsetDifference) (-1)) +MEMORY_OPEN_FAIL_IF_FOUND :: (0x1000) +MEMORY_OPEN_FAIL_IF_NOT_FOUND :: (0x2000) +MAP_OBJECT_READ_WRITE :: (0) +MAP_OBJECT_READ_ONLY :: (1) +MAP_OBJECT_COPY_ON_WRITE :: (2) +STRING_FORMAT_ENOUGH_SPACE :: ( (-1)) +STRING_FORMAT_SIMPLE :: (1 << 0) +POSIX_SYSCALL_GET_POSIX_FD_PATH :: (0x10000) +PERMISSION_ACCESS_SYSTEM_FILES :: (1 << 0) +PERMISSION_ACCESS_USER_FILES :: (1 << 1) +PERMISSION_PROCESS_CREATE :: (1 << 2) +PERMISSION_PROCESS_OPEN :: (1 << 3) +PERMISSION_SCREEN_MODIFY :: (1 << 4) +PERMISSION_SHUTDOWN :: (1 << 5) +PERMISSION_TAKE_SYSTEM_SNAPSHOT :: (1 << 6) +PERMISSION_WINDOW_OPEN :: (1 << 7) +PERMISSION_ALL :: ( (-1)) +PERMISSION_INHERIT :: ( (1) << 63) +PANEL_STYLE_DEFAULT :: "Panel.Default" +PANEL_STYLE_TRANSPARENT :: "Panel.Transparent" +PANEL_STYLE_WINDOW_BACKGROUND :: "Panel.WindowBackground" +PANEL_STYLE_WINDOW_DIVIDER :: "Panel.WindowDivider" +PANEL_STYLE_SHEET :: "Panel.Sheet" +PANEL_STYLE_GROUP_BOX :: "Panel.GroupBox" +PANEL_STYLE_INDENT :: "Panel.Indent" +PANEL_BAND_SIZE_DEFAULT :: (-1) +ELEMENT_FOCUSABLE :: ( (1) << 32) +ELEMENT_HIDDEN :: ( (1) << 33) +ELEMENT_DISABLED :: ( (1) << 34) +ELEMENT_DEBUG :: ( (1) << 35) +ELEMENT_SCROLL_X :: ( (1) << 36) +ELEMENT_SCROLL_Y :: ( (1) << 37) +ELEMENT_NO_HOVER :: ( (1) << 38) +ELEMENT_BLOCK_FOCUS :: ( (1) << 39) +ELEMENT_NOT_TAB_TRAVERSABLE :: ( (1) << 40) +ELEMENT_NO_INFORM_PARENT :: ( (1) << 41) +ELEMENT_CENTER_ACCESS_KEY_HINT :: ( (1) << 42) +ELEMENT_LAYOUT_HINT_HORIZONTAL :: ( (1) << 43) +ELEMENT_STICKY_ACCESS_KEY :: ( (1) << 44) +CELL_NEW_BAND :: ( (1) << 51) +CELL_COLLAPSABLE :: ( (1) << 51) +CELL_H_PUSH :: ( (1) << 54) +CELL_H_EXPAND :: ( (1) << 55) +CELL_H_SHRINK :: ( (1) << 56) +CELL_H_LEFT :: ( (1) << 57) +CELL_H_RIGHT :: ( (1) << 58) +CELL_V_PUSH :: ( (1) << 59) +CELL_V_EXPAND :: ( (1) << 60) +CELL_V_SHRINK :: ( (1) << 61) +CELL_V_TOP :: ( (1) << 62) +CELL_V_BOTTOM :: ( (1) << 63) +PANEL_VERTICAL :: (0x0001) +PANEL_WRAP :: (0x0002) +PANEL_TABLE :: (0x0004) +PANEL_Z_STACK :: (0x0008) +PANEL_H_LEFT :: (0x0010) +PANEL_H_RIGHT :: (0x0020) +PANEL_H_CENTER :: (0x0040) +PANEL_H_JUSTIFY :: (0x0080) +PANEL_V_TOP :: (0x0100) +PANEL_V_BOTTOM :: (0x0200) +PANEL_V_CENTER :: (0x0400) +PANEL_V_JUSTIFY :: (0x0800) +PANEL_H_SCROLL :: (0x1000) +PANEL_V_SCROLL :: (0x2000) +PANEL_SWITCHER :: (0x4000) +TEXTBOX_MULTILINE :: (1 << 0) +TEXTBOX_EDIT_BASED :: (1 << 1) +TEXTBOX_COMPACT :: (1 << 2) +TEXTBOX_NO_SMART_CONTEXT_MENUS :: (1 << 3) +TEXTBOX_FIND_BACKWARDS :: (1 << 0) +BUTTON_DEFAULT :: (1 << 0) +BUTTON_DANGEROUS :: (1 << 1) +BUTTON_MENU_ITEM :: (1 << 2) +BUTTON_NOT_FOCUSABLE :: (1 << 3) +BUTTON_TOOLBAR :: (1 << 4) +BUTTON_DROPDOWN :: (1 << 5) +BUTTON_COMPACT :: (1 << 6) +MENU_ITEM_HEADER :: (1 << 7) +BUTTON_CHECKBOX :: (1 << 8) +BUTTON_RADIOBOX :: (1 << 9) +BUTTON_CANCEL :: (1 << 10) +BUTTON_PUSH :: (1 << 11) +COLOR_WELL_HAS_OPACITY :: (1 << 0) +SCROLLBAR_VERTICAL :: (0 << 0) +SCROLLBAR_HORIZONTAL :: (1 << 0) +SPLITTER_VERTICAL :: (0 << 0) +SPLITTER_HORIZONTAL :: (1 << 0) +IMAGE_DISPLAY_LOAD_ASYNCHRONOUSLY :: (1 << 0) +IMAGE_DISPLAY_UNLOAD_WHEN_HIDDEN :: (1 << 1) +IMAGE_DISPLAY_UPSCALE_NEAREST :: (1 << 2) +IMAGE_DISPLAY_DOWNSCALE_NEAREST :: (1 << 3) +IMAGE_DISPLAY_OPAQUE :: (1 << 4) +LIST_VIEW_HORIZONTAL :: (1 << 0) +LIST_VIEW_VARIABLE_SIZE :: (1 << 1) +LIST_VIEW_TILED :: (1 << 2) +LIST_VIEW_NON_LINEAR :: (1 << 3) +LIST_VIEW_SINGLE_SELECT :: (1 << 4) +LIST_VIEW_MULTI_SELECT :: (1 << 5) +LIST_VIEW_COLUMNS :: (1 << 6) +LIST_VIEW_GROUP_HAS_HEADER :: (1 << 0) +LIST_VIEW_GROUP_HAS_FOOTER :: (1 << 1) +LIST_VIEW_GROUP_INDENT :: (1 << 2) +LIST_VIEW_GROUP_COLLAPSABLE :: (1 << 3) +LIST_VIEW_COLUMN_RIGHT_ALIGNED :: (1 << 0) +LIST_VIEW_COLUMN_ASCENDING :: (1 << 1) +LIST_VIEW_COLUMN_DESCENDING :: (1 << 2) +LIST_VIEW_COLUMN_HAS_MENU :: (1 << 3) +MENU_AT_CURSOR :: (1 << 0) +MENU_MAXIMUM_HEIGHT :: (1 << 1) +FONT_SANS :: (0xFFFF) +FONT_SERIF :: (0xFFFE) +FONT_MONOSPACED :: (0xFFFD) +FONT_REGULAR :: (4) +FONT_BOLD :: (7) +TEXT_FIGURE_DEFAULT :: (0) +TEXT_FIGURE_OLD :: (1) +TEXT_FIGURE_TABULAR :: (2) +TEXT_DISPLAY_CONTENT_MAX_VALUES :: (8) +DIRECTORY_MONITOR_SUBTREE :: (1 << 0) +DIRECTORY_MONITOR_CONTENTS :: (1 << 1) +DIRECTORY_MONITOR_MODIFY :: (1 << 2) +COMMAND_SYSTEM_START :: (0xF0000000) +COMMAND_DELETE :: (0xF0000001) +COMMAND_SELECT_ALL :: (0xF0000002) +COMMAND_CUT :: (0xF0000003) +COMMAND_COPY :: (0xF0000004) +COMMAND_PASTE :: (0xF0000005) +AUDIO_STREAM_OVERRUN :: (1 << 0) +AUDIO_STREAM_UNDERRUN :: (1 << 0) +AUDIO_STREAM_FORMAT_CHANGED :: (1 << 1) +AUDIO_STREAM_TERMINATED :: (1 << 2) +AUDIO_STREAM_RUNNING :: (1 << 0) +AUDIO_STREAM_MUTED :: (1 << 1) +AUDIO_STREAM_ONESHOT :: (1 << 2) +AUDIO_DEFAULT_OUTPUT :: (1) +SAMPLE_FORMAT_U8 :: (1) +SAMPLE_FORMAT_S16LE :: (2) +SAMPLE_FORMAT_S32LE :: (3) +SAMPLE_FORMAT_F32LE :: (4) +CELL_FILL :: ( CELL_H_FILL | CELL_V_FILL) +CELL_H_FILL :: ( CELL_H_PUSH | CELL_H_EXPAND | CELL_H_SHRINK) +CELL_V_FILL :: ( CELL_V_PUSH | CELL_V_EXPAND | CELL_V_SHRINK) +CELL_CENTER :: ( CELL_H_CENTER | CELL_V_CENTER) +CELL_PUSH :: ( CELL_H_PUSH | CELL_V_PUSH) +CELL_EXPAND :: ( CELL_H_EXPAND | CELL_V_EXPAND) +CELL_CORNER :: ( CELL_H_LEFT | CELL_V_TOP) +CELL_SHRINK :: ( CELL_H_SHRINK | CELL_V_SHRINK) +CELL_H_CENTER :: ( CELL_H_LEFT | CELL_H_RIGHT) +CELL_V_CENTER :: ( CELL_V_TOP | CELL_V_BOTTOM) +THEME_METRICS_INSETS :: (1 << 0) +THEME_METRICS_CLIP_INSETS :: (1 << 1) +THEME_METRICS_GLOBAL_OFFSET :: (1 << 2) +THEME_METRICS_CLIP_ENABLED :: (1 << 3) +THEME_METRICS_CURSOR :: (1 << 4) +THEME_METRICS_ENTRANCE_TRANSITION :: (1 << 5) +THEME_METRICS_EXIT_TRANSITION :: (1 << 6) +THEME_METRICS_ENTRANCE_DURATION :: (1 << 7) +THEME_METRICS_EXIT_DURATION :: (1 << 8) +THEME_METRICS_PREFERRED_WIDTH :: (1 << 9) +THEME_METRICS_PREFERRED_HEIGHT :: (1 << 10) +THEME_METRICS_MINIMUM_WIDTH :: (1 << 11) +THEME_METRICS_MINIMUM_HEIGHT :: (1 << 12) +THEME_METRICS_MAXIMUM_WIDTH :: (1 << 13) +THEME_METRICS_MAXIMUM_HEIGHT :: (1 << 14) +THEME_METRICS_GAP_MAJOR :: (1 << 15) +THEME_METRICS_GAP_MINOR :: (1 << 16) +THEME_METRICS_GAP_WRAP :: (1 << 17) +THEME_METRICS_GAP_ALL :: ( THEME_METRICS_GAP_MAJOR | THEME_METRICS_GAP_MINOR | THEME_METRICS_GAP_WRAP) +THEME_METRICS_TEXT_COLOR :: (1 << 18) +THEME_METRICS_SELECTED_BACKGROUND :: (1 << 19) +THEME_METRICS_SELECTED_TEXT :: (1 << 20) +THEME_METRICS_ICON_COLOR :: (1 << 21) +THEME_METRICS_TEXT_ALIGN :: (1 << 22) +THEME_METRICS_TEXT_SIZE :: (1 << 23) +THEME_METRICS_FONT_FAMILY :: (1 << 24) +THEME_METRICS_FONT_WEIGHT :: (1 << 25) +THEME_METRICS_ICON_SIZE :: (1 << 26) +THEME_METRICS_IS_ITALIC :: (1 << 27) +THEME_METRICS_ELLIPSIS :: (1 << 28) +THEME_METRICS_WRAP_TEXT :: (1 << 29) +MOVE_WINDOW_MAXIMISED :: (1 << 0) +MOVE_WINDOW_ADJUST_TO_FIT_SCREEN :: (1 << 1) +MOVE_WINDOW_HIDDEN :: (1 << 2) +MOVE_WINDOW_ALWAYS_ON_TOP :: (1 << 3) +MOVE_WINDOW_AT_BOTTOM :: (1 << 4) +MOVE_WINDOW_UPDATE_SCREEN :: (1 << 5) +WINDOW_SOLID_TRUE :: (1 << 0) +WINDOW_SOLID_NO_ACTIVATE :: (1 << 1) +THEME_BITMAP_WIDTH :: (400) +THEME_BITMAP_HEIGHT :: (200) +THEME_BITMAP_NAME :: "Desktop.ThemeBitmap" +TEXTBOX_MOVE_CARET_SINGLE :: (2) +TEXTBOX_MOVE_CARET_WORD :: (3) +TEXTBOX_MOVE_CARET_LINE :: (4) +TEXTBOX_MOVE_CARET_VERTICAL :: (5) +TEXTBOX_MOVE_CARET_ALL :: (6) +TEXTBOX_MOVE_CARET_FIRST_ONLY :: (1 << 8) +TEXTBOX_MOVE_CARET_SECOND_ONLY :: (1 << 9) +TEXTBOX_MOVE_CARET_BACKWARDS :: (1 << 10) +TEXTBOX_MOVE_CARET_STRONG_WHITESPACE :: (1 << 11) +GAME_CONTROLLER_MAX_COUNT :: (16) +DOMAIN_NAME_MAX_LENGTH :: (255) +ECHO_REQUEST_MAX_LENGTH :: (48) +CONNECTION_OPEN_WAIT :: (1 << 0) +FILE_CONTROL_NOTIFY_MONITORS :: (1 << 0) +FILE_CONTROL_FLUSH :: (1 << 1) using StandardIcon :: enum i32 { ICON_NONE, ICON_ACTION_UNAVAILABLE_SYMBOLIC, @@ -1968,7 +1968,7 @@ using MemoryProtection :: enum i32 { MEMORY_PROTECTION_READ_WRITE, MEMORY_PROTECTION_EXECUTABLE, } -UICallbackFunction :: distinct #type proc "c" ( ^Element, ^Message, ^Response); +UICallbackFunction :: distinct #type proc "c" ( ^Element, ^Message, ^Response) BatchCall :: struct { index : SyscallType, stopBatchIfError : bool, @@ -2417,7 +2417,7 @@ FormatValue :: struct { time : u64 , }, } -FragmentShaderCallbackFunction :: distinct #type proc "c" ( i32, i32, ^StyledBox) -> u32 ; +FragmentShaderCallbackFunction :: distinct #type proc "c" ( i32, i32, ^StyledBox) -> u32 StyledBox :: struct { bounds : Rectangle, clip : Rectangle, @@ -2444,7 +2444,7 @@ CalculationValue :: struct { error : bool, number : f64 , } -CommandCallbackFunction :: distinct #type proc "c" ( ^INSTANCE_TYPE, ^Element, ^Command); +CommandCallbackFunction :: distinct #type proc "c" ( ^INSTANCE_TYPE, ^Element, ^Command) Command :: struct { elements : ^^Element, callback : CommandCallbackFunction, @@ -2569,332 +2569,332 @@ Connection :: struct { sendWritePointer : uint , handle : Handle, } -ThreadEntryFunction :: distinct #type proc "c" ( Generic); -ComparisonCallbackFunction :: distinct #type proc "c" ( rawptr, rawptr, Generic) -> i32; -SwapCallbackFunction :: distinct #type proc "c" ( rawptr, rawptr, Generic); -CRTComparisonCallback :: distinct #type proc "c" ( rawptr, rawptr) -> i32; -TimerCallbackFunction :: distinct #type proc "c" ( Generic); -MenuCallbackFunction :: distinct #type proc "c" ( ^Menu, Generic); -StorePullCallbackFunction :: distinct #type proc "c" ( ^Store, ^INSTANCE_TYPE, Generic, i32, Generic, ^FormatValue); -StoreCallbackFunction :: distinct #type proc "c" ( ^Store, ^INSTANCE_TYPE, Generic, Generic); -DirectoryMonitorCallbackFunction :: distinct #type proc "c" ( ^DirectoryMonitor, i32, ^i8 , int , ^i8 , int , Generic); -Batch :: #force_inline proc "c" (calls_ : ^BatchCall, count_ : int ){ addr := 0x1000 + 0 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^BatchCall, int )) (fp))(calls_, count_); } -GetCreationArgument :: #force_inline proc "c" (object_ : Handle) -> Generic{ addr := 0x1000 + 1 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle) -> Generic) (fp))(object_); } -GetSystemInformation :: #force_inline proc "c" (systemInformation_ : ^SystemInformation){ addr := 0x1000 + 2 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^SystemInformation)) (fp))(systemInformation_); } -HandleClose :: #force_inline proc "c" (handle_ : Handle) -> Error{ addr := 0x1000 + 3 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle) -> Error) (fp))(handle_); } -InitialiseCStandardLibrary :: #force_inline proc "c" (argc_ : ^i32, argv_ : ^^^i8 ){ addr := 0x1000 + 4 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^i32, ^^^i8 )) (fp))(argc_, argv_); } -MailslotSendData :: #force_inline proc "c" (mailslot_ : Handle, data_ : rawptr, bytes_ : int ) -> bool{ addr := 0x1000 + 5 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, rawptr, int ) -> bool) (fp))(mailslot_, data_, bytes_); } -MakeLinuxSystemCall2 :: #force_inline proc "c" (n_ : int , a1_ : int , a2_ : int , a3_ : int , a4_ : int , a5_ : int , a6_ : int ) -> int { addr := 0x1000 + 6 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( int , int , int , int , int , int , int ) -> int ) (fp))(n_, a1_, a2_, a3_, a4_, a5_, a6_); } -SystemGetConstant :: #force_inline proc "c" (index_ : uint ) -> u64 { addr := 0x1000 + 8 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( uint ) -> u64 ) (fp))(index_); } -TakeSystemSnapshot :: #force_inline proc "c" (type_ : i32, bufferSize_ : ^int ) -> Handle{ addr := 0x1000 + 9 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( i32, ^int ) -> Handle) (fp))(type_, bufferSize_); } -UserGetHomeFolder :: #force_inline proc "c" (buffer_ : ^i8 , bufferBytes_ : int ) -> int { addr := 0x1000 + 10 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , int ) -> int ) (fp))(buffer_, bufferBytes_); } -_EsInstanceCreate :: #force_inline proc "c" (bytes_ : int , message_ : ^Message, cName_ : cstring ) -> ^Instance{ addr := 0x1000 + 11 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( int , ^Message, cstring ) -> ^Instance) (fp))(bytes_, message_, cName_); } -_EsSyscall :: #force_inline proc "c" (a_ : uint , b_ : uint , c_ : uint , d_ : uint , e_ : uint , f_ : uint ) -> uint { addr := 0x1000 + 12 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( uint , uint , uint , uint , uint , uint ) -> uint ) (fp))(a_, b_, c_, d_, e_, f_); } -ApplicationStart :: #force_inline proc "c" (information_ : ^ApplicationStartupInformation){ addr := 0x1000 + 124 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^ApplicationStartupInformation)) (fp))(information_); } -SystemConfigurationReadAll :: #force_inline proc "c" (groupCount_ : ^int ) -> ^SystemConfigurationGroup{ addr := 0x1000 + 164 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^int ) -> ^SystemConfigurationGroup) (fp))(groupCount_); } -SystemConfigurationReadInteger :: #force_inline proc "c" (section_ : string, key_ : string, defaultValue_ : i64 = 0) -> i64 { addr := 0x1000 + 295 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, ^u8, int, i64 ) -> i64 ) (fp))(raw_data(section_), len(section_), raw_data(key_), len(key_), defaultValue_); } -SystemConfigurationGroupReadInteger :: #force_inline proc "c" (group_ : ^SystemConfigurationGroup, key_ : string, defaultValue_ : i64 = 0) -> i64 { addr := 0x1000 + 296 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^SystemConfigurationGroup, ^u8, int, i64 ) -> i64 ) (fp))(group_, raw_data(key_), len(key_), defaultValue_); } -SystemConfigurationReadString :: #force_inline proc "c" (section_ : string, key_ : string, valueBytes_ : ^int = nil) -> ^i8 { addr := 0x1000 + 297 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, ^u8, int, ^int ) -> ^i8 ) (fp))(raw_data(section_), len(section_), raw_data(key_), len(key_), valueBytes_); } -SystemConfigurationGroupReadString :: #force_inline proc "c" (group_ : ^SystemConfigurationGroup, key_ : string, valueBytes_ : ^int = nil) -> ^i8 { addr := 0x1000 + 298 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^SystemConfigurationGroup, ^u8, int, ^int ) -> ^i8 ) (fp))(group_, raw_data(key_), len(key_), valueBytes_); } -INIParse :: #force_inline proc "c" (s_ : ^INIState) -> bool{ addr := 0x1000 + 7 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^INIState) -> bool) (fp))(s_); } -INIPeek :: #force_inline proc "c" (s_ : ^INIState) -> bool{ addr := 0x1000 + 87 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^INIState) -> bool) (fp))(s_); } -INIFormat :: #force_inline proc "c" (s_ : ^INIState, buffer_ : ^i8 , bytes_ : int ) -> int { addr := 0x1000 + 125 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^INIState, ^i8 , int ) -> int ) (fp))(s_, buffer_, bytes_); } -INIZeroTerminate :: #force_inline proc "c" (s_ : ^INIState){ addr := 0x1000 + 126 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^INIState)) (fp))(s_); } -CommandAddButton :: #force_inline proc "c" (command_ : ^Command, button_ : ^Button){ addr := 0x1000 + 13 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Command, ^Button)) (fp))(command_, button_); } -CommandByID :: #force_inline proc "c" (instance_ : ^Instance, stableID_ : u32 ) -> ^Command{ addr := 0x1000 + 14 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Instance, u32 ) -> ^Command) (fp))(instance_, stableID_); } -CommandRegister :: #force_inline proc "c" (command_ : ^Command, instance_ : ^Instance, callback_ : CommandCallbackFunction, stableID_ : u32 , cDefaultKeyboardShortcut_ : cstring = nil, enabled_ : bool = false) -> ^Command{ addr := 0x1000 + 15 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Command, ^Instance, CommandCallbackFunction, u32 , cstring , bool) -> ^Command) (fp))(command_, instance_, callback_, stableID_, cDefaultKeyboardShortcut_, enabled_); } -CommandSetCallback :: #force_inline proc "c" (command_ : ^Command, callback_ : CommandCallbackFunction){ addr := 0x1000 + 16 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Command, CommandCallbackFunction)) (fp))(command_, callback_); } -CommandSetDisabled :: #force_inline proc "c" (command_ : ^Command, disabled_ : bool){ addr := 0x1000 + 17 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Command, bool)) (fp))(command_, disabled_); } -DialogClose :: #force_inline proc "c" (window_ : ^Window){ addr := 0x1000 + 18 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Window)) (fp))(window_); } -DialogShow :: #force_inline proc "c" (window_ : ^Window) -> ^Element{ addr := 0x1000 + 19 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Window) -> ^Element) (fp))(window_); } -DialogShowAlert :: #force_inline proc "c" (window_ : ^Window, title_ : string, content_ : string, iconID_ : u32 , addOKButton_ : bool = false) -> ^Element{ addr := 0x1000 + 20 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Window, ^u8, int, ^u8, int, u32 , bool) -> ^Element) (fp))(window_, raw_data(title_), len(title_), raw_data(content_), len(content_), iconID_, addOKButton_); } -InstanceDestroy :: #force_inline proc "c" (instance_ : ^INSTANCE_TYPE){ addr := 0x1000 + 300 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^INSTANCE_TYPE)) (fp))(instance_); } -KeyboardIsAltHeld :: #force_inline proc "c" () -> bool{ addr := 0x1000 + 21 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" () -> bool) (fp))(); } -KeyboardIsCtrlHeld :: #force_inline proc "c" () -> bool{ addr := 0x1000 + 22 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" () -> bool) (fp))(); } -KeyboardIsShiftHeld :: #force_inline proc "c" () -> bool{ addr := 0x1000 + 23 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" () -> bool) (fp))(); } -MessageGetInputText :: #force_inline proc "c" (message_ : ^Message, buffer_ : ^i8 ) -> int { addr := 0x1000 + 24 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Message, ^i8 ) -> int ) (fp))(message_, buffer_); } -MessageMutexAcquire :: #force_inline proc "c" (){ addr := 0x1000 + 26 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ()) (fp))(); } -MessageMutexCheck :: #force_inline proc "c" (){ addr := 0x1000 + 27 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ()) (fp))(); } -MessageMutexRelease :: #force_inline proc "c" (){ addr := 0x1000 + 28 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ()) (fp))(); } -MessagePost :: #force_inline proc "c" (target_ : ^Element, message_ : ^Message) -> Error{ addr := 0x1000 + 29 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, ^Message) -> Error) (fp))(target_, message_); } -MessagePostRemote :: #force_inline proc "c" (process_ : Handle, message_ : ^Message) -> Error{ addr := 0x1000 + 30 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, ^Message) -> Error) (fp))(process_, message_); } -MessageSend :: #force_inline proc "c" (object_ : ^Element, message_ : ^Message) -> Response{ addr := 0x1000 + 31 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, ^Message) -> Response) (fp))(object_, message_); } -MessageReceive :: #force_inline proc "c" () -> ^Message{ addr := 0x1000 + 318 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" () -> ^Message) (fp))(); } -MouseGetPosition :: #force_inline proc "c" (relativeElement_ : ^Element = nil) -> Point{ addr := 0x1000 + 32 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element) -> Point) (fp))(relativeElement_); } -MouseSetPosition :: #force_inline proc "c" (relativeWindow_ : ^Window, x_ : i32, y_ : i32){ addr := 0x1000 + 33 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Window, i32, i32)) (fp))(relativeWindow_, x_, y_); } -StyleInheritRegister :: #force_inline proc "c" (cStyle_ : cstring , cParent_ : cstring , customMetrics_ : ^ThemeMetrics){ addr := 0x1000 + 239 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( cstring , cstring , ^ThemeMetrics)) (fp))(cStyle_, cParent_, customMetrics_); } -StyleRefreshAll :: #force_inline proc "c" (window_ : ^Element){ addr := 0x1000 + 37 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element)) (fp))(window_); } -UISetDPI :: #force_inline proc "c" (dpiScale_ : i32){ addr := 0x1000 + 38 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( i32)) (fp))(dpiScale_); } -DirectoryEnumerateChildren :: #force_inline proc "c" (path_ : string, buffer_ : ^^DirectoryChild) -> int { addr := 0x1000 + 39 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, ^^DirectoryChild) -> int ) (fp))(raw_data(path_), len(path_), buffer_); } -DirectoryEnumerateChildrenFromHandle :: #force_inline proc "c" (directory_ : Handle, buffer_ : ^DirectoryChild, bufferCount_ : int ) -> int { addr := 0x1000 + 40 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, ^DirectoryChild, int ) -> int ) (fp))(directory_, buffer_, bufferCount_); } -DirectoryMonitorCreate :: #force_inline proc "c" (path_ : string, flags_ : u32 , callback_ : DirectoryMonitorCallbackFunction, _context_ : Generic) -> ^DirectoryMonitor{ addr := 0x1000 + 41 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, u32 , DirectoryMonitorCallbackFunction, Generic) -> ^DirectoryMonitor) (fp))(raw_data(path_), len(path_), flags_, callback_, _context_); } -DirectoryMonitorCreateFromHandle :: #force_inline proc "c" (directory_ : Handle, flags_ : u32 , callback_ : DirectoryMonitorCallbackFunction, _context_ : Generic) -> ^DirectoryMonitor{ addr := 0x1000 + 42 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, u32 , DirectoryMonitorCallbackFunction, Generic) -> ^DirectoryMonitor) (fp))(directory_, flags_, callback_, _context_); } -DirectoryMonitorDestroy :: #force_inline proc "c" (monitor_ : ^DirectoryMonitor){ addr := 0x1000 + 43 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^DirectoryMonitor)) (fp))(monitor_); } -FileControl :: #force_inline proc "c" (file_ : Handle, flags_ : u32 ) -> Error{ addr := 0x1000 + 96 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, u32 ) -> Error) (fp))(file_, flags_); } -FileReadAll :: #force_inline proc "c" (filePath_ : string, fileSize_ : ^int ) -> rawptr{ addr := 0x1000 + 44 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, ^int ) -> rawptr) (fp))(raw_data(filePath_), len(filePath_), fileSize_); } -FileReadSync :: #force_inline proc "c" (file_ : Handle, offset_ : FileOffset, size_ : int , buffer_ : rawptr) -> int { addr := 0x1000 + 45 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, FileOffset, int , rawptr) -> int ) (fp))(file_, offset_, size_, buffer_); } -FileResize :: #force_inline proc "c" (file_ : Handle, newSize_ : FileOffset) -> Error{ addr := 0x1000 + 46 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, FileOffset) -> Error) (fp))(file_, newSize_); } -FileWriteAll :: #force_inline proc "c" (filePath_ : string, data_ : rawptr, fileSize_ : int ) -> Error{ addr := 0x1000 + 47 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, rawptr, int ) -> Error) (fp))(raw_data(filePath_), len(filePath_), data_, fileSize_); } -FileWriteAllGather :: #force_inline proc "c" (filePath_ : string, data_ : ^rawptr, fileSize_ : ^int , gatherCount_ : int ) -> Error{ addr := 0x1000 + 48 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, ^rawptr, ^int , int ) -> Error) (fp))(raw_data(filePath_), len(filePath_), data_, fileSize_, gatherCount_); } -FileWriteSync :: #force_inline proc "c" (file_ : Handle, offset_ : FileOffset, size_ : int , buffer_ : rawptr) -> int { addr := 0x1000 + 50 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, FileOffset, int , rawptr) -> int ) (fp))(file_, offset_, size_, buffer_); } -NodeDelete :: #force_inline proc "c" (node_ : Handle) -> Error{ addr := 0x1000 + 51 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle) -> Error) (fp))(node_); } -NodeDeleteByPath :: #force_inline proc "c" (filePath_ : string) -> Error{ addr := 0x1000 + 52 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int) -> Error) (fp))(raw_data(filePath_), len(filePath_)); } -NodeFindUniqueName :: #force_inline proc "c" (buffer_ : ^i8 , originalBytes_ : int , bufferBytes_ : int ) -> int { addr := 0x1000 + 53 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , int , int ) -> int ) (fp))(buffer_, originalBytes_, bufferBytes_); } -NodeMove :: #force_inline proc "c" (node_ : Handle, newDirectory_ : Handle, newName_ : string) -> Error{ addr := 0x1000 + 54 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, Handle, ^u8, int) -> Error) (fp))(node_, newDirectory_, raw_data(newName_), len(newName_)); } -NodeOpen :: #force_inline proc "c" (path_ : string, flags_ : u32 , information_ : ^NodeInformation) -> Error{ addr := 0x1000 + 55 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, u32 , ^NodeInformation) -> Error) (fp))(raw_data(path_), len(path_), flags_, information_); } -NodeOpenRelative :: #force_inline proc "c" (directory_ : Handle, path_ : string, flags_ : u32 , information_ : ^NodeInformation) -> Error{ addr := 0x1000 + 56 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, ^u8, int, u32 , ^NodeInformation) -> Error) (fp))(directory_, raw_data(path_), len(path_), flags_, information_); } -NodeRefreshInformation :: #force_inline proc "c" (information_ : ^NodeInformation){ addr := 0x1000 + 57 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^NodeInformation)) (fp))(information_); } -ProcessCrash :: #force_inline proc "c" (error_ : Error, message_ : string){ addr := 0x1000 + 58 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( Error, ^u8, int)) (fp))(error_, raw_data(message_), len(message_)); } -ProcessCreate :: #force_inline proc "c" (executablePath_ : string, information_ : ^ProcessInformation, argument_ : Generic) -> Error{ addr := 0x1000 + 59 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, ^ProcessInformation, Generic) -> Error) (fp))(raw_data(executablePath_), len(executablePath_), information_, argument_); } -ProcessCreate2 :: #force_inline proc "c" (arguments_ : ^ProcessCreationArguments, information_ : ^ProcessInformation) -> Error{ addr := 0x1000 + 60 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^ProcessCreationArguments, ^ProcessInformation) -> Error) (fp))(arguments_, information_); } -ProcessGetExitStatus :: #force_inline proc "c" (process_ : Handle) -> i32{ addr := 0x1000 + 61 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle) -> i32) (fp))(process_); } -ProcessGetID :: #force_inline proc "c" (process_ : Handle) -> uint { addr := 0x1000 + 62 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle) -> uint ) (fp))(process_); } -ProcessGetState :: #force_inline proc "c" (process_ : Handle, state_ : ^ProcessState){ addr := 0x1000 + 63 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( Handle, ^ProcessState)) (fp))(process_, state_); } -ProcessOpen :: #force_inline proc "c" (pid_ : u64 ) -> Handle{ addr := 0x1000 + 64 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( u64 ) -> Handle) (fp))(pid_); } -ProcessPause :: #force_inline proc "c" (process_ : Handle, resume_ : bool){ addr := 0x1000 + 65 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( Handle, bool)) (fp))(process_, resume_); } -ProcessTerminate :: #force_inline proc "c" (process_ : Handle, status_ : i32){ addr := 0x1000 + 66 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( Handle, i32)) (fp))(process_, status_); } -ProcessTerminateCurrent :: #force_inline proc "c" (){ addr := 0x1000 + 67 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ()) (fp))(); } -ThreadCreate :: #force_inline proc "c" (entryFunction_ : ThreadEntryFunction, information_ : ^ThreadInformation, argument_ : Generic) -> Error{ addr := 0x1000 + 68 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ThreadEntryFunction, ^ThreadInformation, Generic) -> Error) (fp))(entryFunction_, information_, argument_); } -ThreadGetID :: #force_inline proc "c" (thread_ : Handle) -> uint { addr := 0x1000 + 69 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle) -> uint ) (fp))(thread_); } -ThreadTerminate :: #force_inline proc "c" (thread_ : Handle){ addr := 0x1000 + 71 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( Handle)) (fp))(thread_); } -ArenaAllocate :: #force_inline proc "c" (arena_ : ^Arena, zero_ : bool) -> rawptr{ addr := 0x1000 + 72 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Arena, bool) -> rawptr) (fp))(arena_, zero_); } -ArenaFree :: #force_inline proc "c" (arena_ : ^Arena, pointer_ : rawptr){ addr := 0x1000 + 73 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Arena, rawptr)) (fp))(arena_, pointer_); } -ArenaInitialise :: #force_inline proc "c" (arena_ : ^Arena, blockSize_ : int , itemSize_ : int ){ addr := 0x1000 + 74 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Arena, int , int )) (fp))(arena_, blockSize_, itemSize_); } -ConstantBufferCreate :: #force_inline proc "c" (data_ : rawptr, dataBytes_ : int , targetProcess_ : Handle) -> Handle{ addr := 0x1000 + 75 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( rawptr, int , Handle) -> Handle) (fp))(data_, dataBytes_, targetProcess_); } -ConstantBufferRead :: #force_inline proc "c" (constantBuffer_ : Handle, output_ : rawptr){ addr := 0x1000 + 76 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( Handle, rawptr)) (fp))(constantBuffer_, output_); } -ConstantBufferShare :: #force_inline proc "c" (constantBuffer_ : Handle, targetProcess_ : Handle) -> Handle{ addr := 0x1000 + 77 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, Handle) -> Handle) (fp))(constantBuffer_, targetProcess_); } -HeapAllocate :: #force_inline proc "c" (size_ : int , zeroMemory_ : bool) -> rawptr{ addr := 0x1000 + 78 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( int , bool) -> rawptr) (fp))(size_, zeroMemory_); } -HeapFree :: #force_inline proc "c" (address_ : rawptr){ addr := 0x1000 + 79 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( rawptr)) (fp))(address_); } -HeapReallocate :: #force_inline proc "c" (oldAddress_ : rawptr, newAllocationSize_ : int , zeroNewSpace_ : bool) -> rawptr{ addr := 0x1000 + 80 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( rawptr, int , bool) -> rawptr) (fp))(oldAddress_, newAllocationSize_, zeroNewSpace_); } -HeapValidate :: #force_inline proc "c" (){ addr := 0x1000 + 81 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ()) (fp))(); } -MemoryCommit :: #force_inline proc "c" (pointer_ : rawptr, bytes_ : int ) -> bool{ addr := 0x1000 + 82 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( rawptr, int ) -> bool) (fp))(pointer_, bytes_); } -MemoryCompare :: #force_inline proc "c" (a_ : rawptr, b_ : rawptr, bytes_ : int ) -> i32{ addr := 0x1000 + 83 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( rawptr, rawptr, int ) -> i32) (fp))(a_, b_, bytes_); } -MemoryCopy :: #force_inline proc "c" (destination_ : rawptr, source_ : rawptr, bytes_ : int ){ addr := 0x1000 + 84 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( rawptr, rawptr, int )) (fp))(destination_, source_, bytes_); } -MemoryCopyReverse :: #force_inline proc "c" (_destination_ : rawptr, _source_ : rawptr, bytes_ : int ){ addr := 0x1000 + 85 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( rawptr, rawptr, int )) (fp))(_destination_, _source_, bytes_); } -MemoryDecommit :: #force_inline proc "c" (pointer_ : rawptr, bytes_ : int ) -> bool{ addr := 0x1000 + 320 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( rawptr, int ) -> bool) (fp))(pointer_, bytes_); } -MemoryFill :: #force_inline proc "c" (from_ : rawptr, to_ : rawptr, byte_ : u8 ){ addr := 0x1000 + 86 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( rawptr, rawptr, u8 )) (fp))(from_, to_, byte_); } -MemoryMove :: #force_inline proc "c" (_start_ : rawptr, _end_ : rawptr, amount_ : int , zeroEmptySpace_ : bool){ addr := 0x1000 + 88 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( rawptr, rawptr, int , bool)) (fp))(_start_, _end_, amount_, zeroEmptySpace_); } -MemoryOpen :: #force_inline proc "c" (size_ : int , name_ : string, flags_ : u32 ) -> Handle{ addr := 0x1000 + 89 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( int , ^u8, int, u32 ) -> Handle) (fp))(size_, raw_data(name_), len(name_), flags_); } -MemoryReserve :: #force_inline proc "c" (size_ : int , protection_ : MemoryProtection = MEMORY_PROTECTION_READ_WRITE, commitAll_ : bool = true) -> rawptr{ addr := 0x1000 + 25 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( int , MemoryProtection, bool) -> rawptr) (fp))(size_, protection_, commitAll_); } -MemoryShare :: #force_inline proc "c" (sharedMemoryRegion_ : Handle, targetProcess_ : Handle, readOnly_ : bool) -> Handle{ addr := 0x1000 + 90 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, Handle, bool) -> Handle) (fp))(sharedMemoryRegion_, targetProcess_, readOnly_); } -MemorySumBytes :: #force_inline proc "c" (data_ : ^u8 , bytes_ : int ) -> u8 { addr := 0x1000 + 91 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^u8 , int ) -> u8 ) (fp))(data_, bytes_); } -MemoryUnreserve :: #force_inline proc "c" (pointer_ : rawptr, size_ : int = 0){ addr := 0x1000 + 319 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( rawptr, int )) (fp))(pointer_, size_); } -MemoryZero :: #force_inline proc "c" (destination_ : rawptr, bytes_ : int ){ addr := 0x1000 + 92 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( rawptr, int )) (fp))(destination_, bytes_); } -ObjectMap :: #force_inline proc "c" (object_ : Handle, offset_ : uint , size_ : int , flags_ : u32 ) -> rawptr{ addr := 0x1000 + 93 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, uint , int , u32 ) -> rawptr) (fp))(object_, offset_, size_, flags_); } -AssertionFailure :: #force_inline proc "c" (cFile_ : cstring , line_ : i32){ addr := 0x1000 + 94 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( cstring , i32)) (fp))(cFile_, line_); } -DoubleParse :: #force_inline proc "c" (string_ : string, endptr_ : ^^i8 ) -> f64 { addr := 0x1000 + 97 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, ^^i8 ) -> f64 ) (fp))(raw_data(string_), len(string_), endptr_); } -ExtractArguments :: #force_inline proc "c" (string_ : ^i8 , bytes_ : int , delimiterByte_ : u8 , replacementDelimiter_ : u8 , argvAllocated_ : int , argv_ : ^^i8 , argc_ : ^int ) -> bool{ addr := 0x1000 + 98 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , int , u8 , u8 , int , ^^i8 , ^int ) -> bool) (fp))(string_, bytes_, delimiterByte_, replacementDelimiter_, argvAllocated_, argv_, argc_); } -RandomU8 :: #force_inline proc "c" () -> u8 { addr := 0x1000 + 301 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" () -> u8 ) (fp))(); } -RandomU64 :: #force_inline proc "c" () -> u64 { addr := 0x1000 + 302 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" () -> u64 ) (fp))(); } -IntegerParse :: #force_inline proc "c" (text_ : string) -> i64 { addr := 0x1000 + 101 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int) -> i64 ) (fp))(raw_data(text_), len(text_)); } -Print :: #force_inline proc "c" (format_ : cstring , _varargs_ : ..any){ addr := 0x1000 + 102 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( cstring , ..any)) (fp))(format_, _varargs_); } -PrintDirect :: #force_inline proc "c" (string_ : string){ addr := 0x1000 + 103 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" (^u8, int)) (fp))(raw_data(string_), len(string_)); } -PrintHelloWorld :: #force_inline proc "c" (){ addr := 0x1000 + 104 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ()) (fp))(); } -RandomAddEntropy :: #force_inline proc "c" (x_ : u64 ){ addr := 0x1000 + 106 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( u64 )) (fp))(x_); } -RandomSeed :: #force_inline proc "c" (x_ : u64 ){ addr := 0x1000 + 107 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( u64 )) (fp))(x_); } -RectangleClip :: #force_inline proc "c" (parent_ : Rectangle, rectangle_ : Rectangle, output_ : ^Rectangle) -> bool{ addr := 0x1000 + 108 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Rectangle, Rectangle, ^Rectangle) -> bool) (fp))(parent_, rectangle_, output_); } -Sort :: #force_inline proc "c" (_base_ : rawptr, nmemb_ : int , size_ : int , compar_ : ComparisonCallbackFunction, argument_ : Generic){ addr := 0x1000 + 109 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( rawptr, int , int , ComparisonCallbackFunction, Generic)) (fp))(_base_, nmemb_, size_, compar_, argument_); } -SortWithSwapCallback :: #force_inline proc "c" (_base_ : rawptr, nmemb_ : int , size_ : int , compar_ : ComparisonCallbackFunction, argument_ : Generic, swap_ : SwapCallbackFunction){ addr := 0x1000 + 110 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( rawptr, int , int , ComparisonCallbackFunction, Generic, SwapCallbackFunction)) (fp))(_base_, nmemb_, size_, compar_, argument_, swap_); } -TimeStamp :: #force_inline proc "c" () -> u64 { addr := 0x1000 + 252 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" () -> u64 ) (fp))(); } -TimeStampMs :: #force_inline proc "c" () -> f64 { addr := 0x1000 + 261 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" () -> f64 ) (fp))(); } -ColorBlend :: #force_inline proc "c" (under_ : u32 , over_ : u32 , fullAlpha_ : bool) -> u32 { addr := 0x1000 + 111 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( u32 , u32 , bool) -> u32 ) (fp))(under_, over_, fullAlpha_); } -ColorConvertToRGB :: #force_inline proc "c" (h_ : f32 , s_ : f32 , v_ : f32 ) -> u32 { addr := 0x1000 + 112 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 , f32 , f32 ) -> u32 ) (fp))(h_, s_, v_); } -ColorConvertToHSV :: #force_inline proc "c" (color_ : u32 , h_ : ^f32 , s_ : ^f32 , v_ : ^f32 ) -> bool{ addr := 0x1000 + 113 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( u32 , ^f32 , ^f32 , ^f32 ) -> bool) (fp))(color_, h_, s_, v_); } -ColorParse :: #force_inline proc "c" (string_ : string) -> u32 { addr := 0x1000 + 114 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int) -> u32 ) (fp))(raw_data(string_), len(string_)); } -DrawBitmap :: #force_inline proc "c" (painter_ : ^Painter, region_ : Rectangle, bits_ : ^u32 , stride_ : uint ){ addr := 0x1000 + 324 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Painter, Rectangle, ^u32 , uint )) (fp))(painter_, region_, bits_, stride_); } -DrawInvert :: #force_inline proc "c" (painter_ : ^Painter, bounds_ : Rectangle){ addr := 0x1000 + 95 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Painter, Rectangle)) (fp))(painter_, bounds_); } -DrawStandardIcon :: #force_inline proc "c" (painter_ : ^Painter, id_ : u32 , size_ : i32, region_ : Rectangle, color_ : u32 ) -> bool{ addr := 0x1000 + 115 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Painter, u32 , i32, Rectangle, u32 ) -> bool) (fp))(painter_, id_, size_, region_, color_); } -DrawStyledBox :: #force_inline proc "c" (painter_ : ^Painter, box_ : StyledBox){ addr := 0x1000 + 116 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Painter, StyledBox)) (fp))(painter_, box_); } -DrawPaintTarget :: #force_inline proc "c" (painter_ : ^Painter, source_ : ^PaintTarget, destinationRegion_ : Rectangle, sourceRegion_ : Rectangle, borderRegion_ : Rectangle, mode_ : DrawMode, alpha_ : u16 , clipRegion_ : Rectangle){ addr := 0x1000 + 263 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Painter, ^PaintTarget, Rectangle, Rectangle, Rectangle, DrawMode, u16 , Rectangle)) (fp))(painter_, source_, destinationRegion_, sourceRegion_, borderRegion_, mode_, alpha_, clipRegion_); } -DrawText :: #force_inline proc "c" (painter_ : ^Painter, plan_ : ^TextPlan, bounds_ : Rectangle, clip_ : ^Rectangle = nil, selectionProperties_ : ^TextSelection = nil){ addr := 0x1000 + 120 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Painter, ^TextPlan, Rectangle, ^Rectangle, ^TextSelection)) (fp))(painter_, plan_, bounds_, clip_, selectionProperties_); } -FontGetName :: #force_inline proc "c" (family_ : u16 , buffer_ : ^i8 , bufferBytes_ : int ) -> int { addr := 0x1000 + 121 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( u16 , ^i8 , int ) -> int ) (fp))(family_, buffer_, bufferBytes_); } -IconIDFromString :: #force_inline proc "c" (string_ : string = "") -> u32 { addr := 0x1000 + 127 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int) -> u32 ) (fp))(raw_data(string_), len(string_)); } -ImageLoad :: #force_inline proc "c" (file_ : ^u8 , fileSize_ : int , width_ : ^u32 , height_ : ^u32 , imageChannels_ : i32) -> ^u8 { addr := 0x1000 + 165 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^u8 , int , ^u32 , ^u32 , i32) -> ^u8 ) (fp))(file_, fileSize_, width_, height_, imageChannels_); } -PainterBoundsClient :: #force_inline proc "c" (painter_ : ^Painter) -> Rectangle{ addr := 0x1000 + 122 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Painter) -> Rectangle) (fp))(painter_); } -PainterBoundsInset :: #force_inline proc "c" (painter_ : ^Painter) -> Rectangle{ addr := 0x1000 + 123 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Painter) -> Rectangle) (fp))(painter_); } -PainterDrawStandardContent :: #force_inline proc "c" (painter_ : ^Painter, text_ : string, iconID_ : u32 ){ addr := 0x1000 + 117 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Painter, ^u8, int, u32 )) (fp))(painter_, raw_data(text_), len(text_), iconID_); } -PaintTargetClear :: #force_inline proc "c" (target_ : ^PaintTarget){ addr := 0x1000 + 34 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^PaintTarget)) (fp))(target_); } -PaintTargetEndDirectAccess :: #force_inline proc "c" (target_ : ^PaintTarget){ addr := 0x1000 + 264 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^PaintTarget)) (fp))(target_); } -PaintTargetStartDirectAccess :: #force_inline proc "c" (target_ : ^PaintTarget, bits_ : ^^u32 , width_ : ^int , height_ : ^int , stride_ : ^int ){ addr := 0x1000 + 276 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^PaintTarget, ^^u32 , ^int , ^int , ^int )) (fp))(target_, bits_, width_, height_, stride_); } -PaintTargetTake :: #force_inline proc "c" (target_ : ^PaintTarget, width_ : int , height_ : int ) -> bool{ addr := 0x1000 + 35 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^PaintTarget, int , int ) -> bool) (fp))(target_, width_, height_); } -PaintTargetReturn :: #force_inline proc "c" (target_ : ^PaintTarget){ addr := 0x1000 + 36 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^PaintTarget)) (fp))(target_); } -TextGetLineHeight :: #force_inline proc "c" (font_ : Font, size_ : u16 ) -> i32{ addr := 0x1000 + 157 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Font, u16 ) -> i32) (fp))(font_, size_); } -TextGetPartialStringWidth :: #force_inline proc "c" (font_ : Font, size_ : u16 , fullString_ : ^i8 , fullStringBytes_ : int , measureOffset_ : uint , measureBytes_ : int ) -> i32{ addr := 0x1000 + 171 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Font, u16 , ^i8 , int , uint , int ) -> i32) (fp))(font_, size_, fullString_, fullStringBytes_, measureOffset_, measureBytes_); } -TextGetCharacterAtPoint :: #force_inline proc "c" (font_ : Font, size_ : u16 , fullString_ : ^i8 , fullStringBytes_ : int , measureOffset_ : uint , measureBytes_ : int , pointX_ : ^i32, reverse_ : bool, middle_ : bool) -> int { addr := 0x1000 + 237 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Font, u16 , ^i8 , int , uint , int , ^i32, bool, bool) -> int ) (fp))(font_, size_, fullString_, fullStringBytes_, measureOffset_, measureBytes_, pointX_, reverse_, middle_); } -AudioStreamClose :: #force_inline proc "c" (stream_ : ^AudioStream){ addr := 0x1000 + 128 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^AudioStream)) (fp))(stream_); } -AudioStreamOpen :: #force_inline proc "c" (device_ : AudioDeviceID, bufferLengthUs_ : int ) -> ^AudioStream{ addr := 0x1000 + 129 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( AudioDeviceID, int ) -> ^AudioStream) (fp))(device_, bufferLengthUs_); } -AudioStreamNotify :: #force_inline proc "c" (stream_ : ^AudioStream){ addr := 0x1000 + 130 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^AudioStream)) (fp))(stream_); } -AudioStreamSend :: #force_inline proc "c" (destination_ : ^AudioStream, source_ : ^AudioStream, time_ : ^f64 ) -> Error{ addr := 0x1000 + 131 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^AudioStream, ^AudioStream, ^f64 ) -> Error) (fp))(destination_, source_, time_); } -AddressResolve :: #force_inline proc "c" (domain_ : string, flags_ : u32 , address_ : ^Address) -> Error{ addr := 0x1000 + 99 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, u32 , ^Address) -> Error) (fp))(raw_data(domain_), len(domain_), flags_, address_); } -ConnectionClose :: #force_inline proc "c" (connection_ : ^Connection){ addr := 0x1000 + 321 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Connection)) (fp))(connection_); } -ConnectionNotify :: #force_inline proc "c" (connection_ : ^Connection){ addr := 0x1000 + 322 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Connection)) (fp))(connection_); } -ConnectionOpen :: #force_inline proc "c" (connection_ : ^Connection, flags_ : u32 ) -> Error{ addr := 0x1000 + 100 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Connection, u32 ) -> Error) (fp))(connection_, flags_); } -ConnectionPoll :: #force_inline proc "c" (connection_ : ^Connection){ addr := 0x1000 + 303 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Connection)) (fp))(connection_); } -ConnectionRead :: #force_inline proc "c" (connection_ : ^Connection, buffer_ : rawptr, bufferBytes_ : int , bytesRead_ : ^int ) -> Error{ addr := 0x1000 + 325 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Connection, rawptr, int , ^int ) -> Error) (fp))(connection_, buffer_, bufferBytes_, bytesRead_); } -ConnectionWriteSync :: #force_inline proc "c" (connection_ : ^Connection, data_ : rawptr, dataBytes_ : int ) -> Error{ addr := 0x1000 + 323 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Connection, rawptr, int ) -> Error) (fp))(connection_, data_, dataBytes_); } -GameControllerStatePoll :: #force_inline proc "c" (buffer_ : ^GameControllerState) -> int { addr := 0x1000 + 299 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^GameControllerState) -> int ) (fp))(buffer_); } -EventCreate :: #force_inline proc "c" (autoReset_ : bool) -> Handle{ addr := 0x1000 + 132 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( bool) -> Handle) (fp))(autoReset_); } -EventForward :: #force_inline proc "c" (event_ : Handle, eventSink_ : Handle, data_ : Generic){ addr := 0x1000 + 133 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( Handle, Handle, Generic)) (fp))(event_, eventSink_, data_); } -EventPoll :: #force_inline proc "c" (event_ : Handle) -> Error{ addr := 0x1000 + 134 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle) -> Error) (fp))(event_); } -EventReset :: #force_inline proc "c" (event_ : Handle){ addr := 0x1000 + 135 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( Handle)) (fp))(event_); } -EventSet :: #force_inline proc "c" (event_ : Handle){ addr := 0x1000 + 136 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( Handle)) (fp))(event_); } -EventSinkCreate :: #force_inline proc "c" (ignoreDuplicates_ : bool) -> Handle{ addr := 0x1000 + 137 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( bool) -> Handle) (fp))(ignoreDuplicates_); } -EventSinkPop :: #force_inline proc "c" (eventSink_ : Handle, data_ : ^Generic) -> Error{ addr := 0x1000 + 138 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, ^Generic) -> Error) (fp))(eventSink_, data_); } -EventSinkPush :: #force_inline proc "c" (eventSink_ : Handle, data_ : Generic) -> Error{ addr := 0x1000 + 139 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, Generic) -> Error) (fp))(eventSink_, data_); } -MutexAcquire :: #force_inline proc "c" (mutex_ : ^Mutex){ addr := 0x1000 + 140 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Mutex)) (fp))(mutex_); } -MutexDestroy :: #force_inline proc "c" (mutex_ : ^Mutex){ addr := 0x1000 + 141 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Mutex)) (fp))(mutex_); } -MutexRelease :: #force_inline proc "c" (mutex_ : ^Mutex){ addr := 0x1000 + 142 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Mutex)) (fp))(mutex_); } -PerformanceTimerPush :: #force_inline proc "c" (){ addr := 0x1000 + 143 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ()) (fp))(); } -PerformanceTimerPop :: #force_inline proc "c" () -> f64 { addr := 0x1000 + 144 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" () -> f64 ) (fp))(); } -SchedulerYield :: #force_inline proc "c" (){ addr := 0x1000 + 145 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ()) (fp))(); } -Sleep :: #force_inline proc "c" (milliseconds_ : u64 ){ addr := 0x1000 + 146 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( u64 )) (fp))(milliseconds_); } -SpinlockAcquire :: #force_inline proc "c" (spinlock_ : ^Spinlock){ addr := 0x1000 + 147 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Spinlock)) (fp))(spinlock_); } -SpinlockRelease :: #force_inline proc "c" (spinlock_ : ^Spinlock){ addr := 0x1000 + 148 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Spinlock)) (fp))(spinlock_); } -TimerCreate :: #force_inline proc "c" () -> Handle{ addr := 0x1000 + 149 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" () -> Handle) (fp))(); } -TimerSet :: #force_inline proc "c" (handle_ : Handle, afterMs_ : u64 , callback_ : TimerCallbackFunction, argument_ : Generic){ addr := 0x1000 + 150 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( Handle, u64 , TimerCallbackFunction, Generic)) (fp))(handle_, afterMs_, callback_, argument_); } -Wait :: #force_inline proc "c" (objects_ : ^Handle, objectCount_ : int , timeoutMs_ : uint ) -> uint { addr := 0x1000 + 151 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Handle, int , uint ) -> uint ) (fp))(objects_, objectCount_, timeoutMs_); } -CStringLength :: #force_inline proc "c" (string_ : cstring ) -> int { addr := 0x1000 + 152 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( cstring ) -> int ) (fp))(string_); } -StringAllocateAndFormat :: #force_inline proc "c" (bytes_ : ^int , format_ : cstring , _varargs_ : ..any) -> ^i8 { addr := 0x1000 + 153 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^int , cstring , ..any) -> ^i8 ) (fp))(bytes_, format_, _varargs_); } -StringCompare :: #force_inline proc "c" (s1_ : string, s2_ : string) -> i32{ addr := 0x1000 + 155 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, ^u8, int) -> i32) (fp))(raw_data(s1_), len(s1_), raw_data(s2_), len(s2_)); } -StringCompareRaw :: #force_inline proc "c" (s1_ : string, s2_ : string) -> i32{ addr := 0x1000 + 156 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, ^u8, int) -> i32) (fp))(raw_data(s1_), len(s1_), raw_data(s2_), len(s2_)); } -StringFormat :: #force_inline proc "c" (buffer_ : ^i8 , bufferLength_ : int , format_ : cstring , _varargs_ : ..any) -> int { addr := 0x1000 + 158 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , int , cstring , ..any) -> int ) (fp))(buffer_, bufferLength_, format_, _varargs_); } -StringFormatTemporary :: #force_inline proc "c" (format_ : cstring , _varargs_ : ..any) -> ^i8 { addr := 0x1000 + 159 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( cstring , ..any) -> ^i8 ) (fp))(format_, _varargs_); } -StringFormatAppend :: #force_inline proc "c" (buffer_ : ^i8 , bufferLength_ : int , bufferPosition_ : ^int , format_ : cstring , _varargs_ : ..any) -> bool{ addr := 0x1000 + 161 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , int , ^int , cstring , ..any) -> bool) (fp))(buffer_, bufferLength_, bufferPosition_, format_, _varargs_); } -StringLength :: #force_inline proc "c" (string_ : ^i8 , end_ : u8 ) -> int { addr := 0x1000 + 163 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , u8 ) -> int ) (fp))(string_, end_); } -StringStartsWith :: #force_inline proc "c" (string_ : string, prefix_ : string, caseInsensitive_ : bool) -> bool{ addr := 0x1000 + 166 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, ^u8, int, bool) -> bool) (fp))(raw_data(string_), len(string_), raw_data(prefix_), len(prefix_), caseInsensitive_); } -StringZeroTerminate :: #force_inline proc "c" (string_ : string) -> ^i8 { addr := 0x1000 + 167 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int) -> ^i8 ) (fp))(raw_data(string_), len(string_)); } -CRTabs :: #force_inline proc "c" (n_ : i32) -> i32{ addr := 0x1000 + 168 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( i32) -> i32) (fp))(n_); } -CRTacosf :: #force_inline proc "c" (x_ : f32 ) -> f32 { addr := 0x1000 + 169 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 ) -> f32 ) (fp))(x_); } -CRTasinf :: #force_inline proc "c" (x_ : f32 ) -> f32 { addr := 0x1000 + 170 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 ) -> f32 ) (fp))(x_); } -CRTatan2f :: #force_inline proc "c" (y_ : f32 , x_ : f32 ) -> f32 { addr := 0x1000 + 172 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 , f32 ) -> f32 ) (fp))(y_, x_); } -CRTatanf :: #force_inline proc "c" (x_ : f32 ) -> f32 { addr := 0x1000 + 173 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 ) -> f32 ) (fp))(x_); } -CRTatoi :: #force_inline proc "c" (string_ : ^i8 ) -> i32{ addr := 0x1000 + 174 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 ) -> i32) (fp))(string_); } -CRTcalloc :: #force_inline proc "c" (num_ : int , size_ : int ) -> rawptr{ addr := 0x1000 + 175 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( int , int ) -> rawptr) (fp))(num_, size_); } -CRTceil :: #force_inline proc "c" (x_ : f64 ) -> f64 { addr := 0x1000 + 176 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f64 ) -> f64 ) (fp))(x_); } -CRTceilf :: #force_inline proc "c" (x_ : f32 ) -> f32 { addr := 0x1000 + 177 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 ) -> f32 ) (fp))(x_); } -CRTcosf :: #force_inline proc "c" (x_ : f32 ) -> f32 { addr := 0x1000 + 178 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 ) -> f32 ) (fp))(x_); } -CRTexp :: #force_inline proc "c" (x_ : f64 ) -> f64 { addr := 0x1000 + 326 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f64 ) -> f64 ) (fp))(x_); } -CRTexp2f :: #force_inline proc "c" (x_ : f32 ) -> f32 { addr := 0x1000 + 327 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 ) -> f32 ) (fp))(x_); } -CRTfabs :: #force_inline proc "c" (x_ : f64 ) -> f64 { addr := 0x1000 + 179 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f64 ) -> f64 ) (fp))(x_); } -CRTfabsf :: #force_inline proc "c" (x_ : f32 ) -> f32 { addr := 0x1000 + 344 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 ) -> f32 ) (fp))(x_); } -CRTfloor :: #force_inline proc "c" (x_ : f64 ) -> f64 { addr := 0x1000 + 181 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f64 ) -> f64 ) (fp))(x_); } -CRTfloorf :: #force_inline proc "c" (x_ : f32 ) -> f32 { addr := 0x1000 + 182 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 ) -> f32 ) (fp))(x_); } -CRTfmodf :: #force_inline proc "c" (x_ : f32 , y_ : f32 ) -> f32 { addr := 0x1000 + 183 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 , f32 ) -> f32 ) (fp))(x_, y_); } -CRTfree :: #force_inline proc "c" (ptr_ : rawptr){ addr := 0x1000 + 184 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( rawptr)) (fp))(ptr_); } -CRTgetenv :: #force_inline proc "c" (name_ : ^i8 ) -> ^i8 { addr := 0x1000 + 185 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 ) -> ^i8 ) (fp))(name_); } -CRTisalpha :: #force_inline proc "c" (c_ : i32) -> i32{ addr := 0x1000 + 186 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( i32) -> i32) (fp))(c_); } -CRTisdigit :: #force_inline proc "c" (c_ : i32) -> i32{ addr := 0x1000 + 187 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( i32) -> i32) (fp))(c_); } -CRTisnanf :: #force_inline proc "c" (f_ : f32 ) -> bool{ addr := 0x1000 + 345 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 ) -> bool) (fp))(f_); } -CRTisspace :: #force_inline proc "c" (c_ : i32) -> i32{ addr := 0x1000 + 188 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( i32) -> i32) (fp))(c_); } -CRTisupper :: #force_inline proc "c" (c_ : i32) -> i32{ addr := 0x1000 + 189 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( i32) -> i32) (fp))(c_); } -CRTisxdigit :: #force_inline proc "c" (c_ : i32) -> i32{ addr := 0x1000 + 190 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( i32) -> i32) (fp))(c_); } -CRTmalloc :: #force_inline proc "c" (size_ : int ) -> rawptr{ addr := 0x1000 + 191 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( int ) -> rawptr) (fp))(size_); } -CRTmemchr :: #force_inline proc "c" (_s_ : rawptr, _c_ : i32, n_ : int ) -> rawptr{ addr := 0x1000 + 192 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( rawptr, i32, int ) -> rawptr) (fp))(_s_, _c_, n_); } -CRTmemcmp :: #force_inline proc "c" (s1_ : rawptr, s2_ : rawptr, n_ : int ) -> i32{ addr := 0x1000 + 193 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( rawptr, rawptr, int ) -> i32) (fp))(s1_, s2_, n_); } -CRTmemcpy :: #force_inline proc "c" (dest_ : rawptr, src_ : rawptr, n_ : int ) -> rawptr{ addr := 0x1000 + 194 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( rawptr, rawptr, int ) -> rawptr) (fp))(dest_, src_, n_); } -CRTmemmove :: #force_inline proc "c" (dest_ : rawptr, src_ : rawptr, n_ : int ) -> rawptr{ addr := 0x1000 + 195 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( rawptr, rawptr, int ) -> rawptr) (fp))(dest_, src_, n_); } -CRTmemset :: #force_inline proc "c" (s_ : rawptr, c_ : i32, n_ : int ) -> rawptr{ addr := 0x1000 + 196 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( rawptr, i32, int ) -> rawptr) (fp))(s_, c_, n_); } -CRTpowf :: #force_inline proc "c" (x_ : f32 , y_ : f32 ) -> f32 { addr := 0x1000 + 328 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 , f32 ) -> f32 ) (fp))(x_, y_); } -CRTqsort :: #force_inline proc "c" (_base_ : rawptr, nmemb_ : int , size_ : int , compar_ : CRTComparisonCallback){ addr := 0x1000 + 197 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( rawptr, int , int , CRTComparisonCallback)) (fp))(_base_, nmemb_, size_, compar_); } -CRTrealloc :: #force_inline proc "c" (ptr_ : rawptr, size_ : int ) -> rawptr{ addr := 0x1000 + 198 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( rawptr, int ) -> rawptr) (fp))(ptr_, size_); } -CRTsinf :: #force_inline proc "c" (x_ : f32 ) -> f32 { addr := 0x1000 + 199 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 ) -> f32 ) (fp))(x_); } -CRTsnprintf :: #force_inline proc "c" (buffer_ : ^i8 , bufferSize_ : int , format_ : ^i8 , _varargs_ : ..any) -> i32{ addr := 0x1000 + 200 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , int , ^i8 , ..any) -> i32) (fp))(buffer_, bufferSize_, format_, _varargs_); } -CRTsprintf :: #force_inline proc "c" (buffer_ : ^i8 , format_ : ^i8 , _varargs_ : ..any) -> i32{ addr := 0x1000 + 201 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , ^i8 , ..any) -> i32) (fp))(buffer_, format_, _varargs_); } -CRTsqrt :: #force_inline proc "c" (x_ : f64 ) -> f64 { addr := 0x1000 + 202 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f64 ) -> f64 ) (fp))(x_); } -CRTsqrtf :: #force_inline proc "c" (x_ : f32 ) -> f32 { addr := 0x1000 + 203 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 ) -> f32 ) (fp))(x_); } -CRTstrcat :: #force_inline proc "c" (dest_ : ^i8 , src_ : ^i8 ) -> ^i8 { addr := 0x1000 + 205 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , ^i8 ) -> ^i8 ) (fp))(dest_, src_); } -CRTstrchr :: #force_inline proc "c" (s_ : ^i8 , c_ : i32) -> ^i8 { addr := 0x1000 + 206 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , i32) -> ^i8 ) (fp))(s_, c_); } -CRTstrcmp :: #force_inline proc "c" (s1_ : ^i8 , s2_ : ^i8 ) -> i32{ addr := 0x1000 + 207 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , ^i8 ) -> i32) (fp))(s1_, s2_); } -CRTstrcpy :: #force_inline proc "c" (dest_ : ^i8 , src_ : ^i8 ) -> ^i8 { addr := 0x1000 + 208 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , ^i8 ) -> ^i8 ) (fp))(dest_, src_); } -CRTstrdup :: #force_inline proc "c" (string_ : ^i8 ) -> ^i8 { addr := 0x1000 + 70 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 ) -> ^i8 ) (fp))(string_); } -CRTstrerror :: #force_inline proc "c" (errnum_ : i32) -> ^i8 { addr := 0x1000 + 209 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( i32) -> ^i8 ) (fp))(errnum_); } -CRTstrlen :: #force_inline proc "c" (s_ : ^i8 ) -> int { addr := 0x1000 + 210 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 ) -> int ) (fp))(s_); } -CRTstrncmp :: #force_inline proc "c" (s1_ : ^i8 , s2_ : ^i8 , n_ : int ) -> i32{ addr := 0x1000 + 211 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , ^i8 , int ) -> i32) (fp))(s1_, s2_, n_); } -CRTstrncpy :: #force_inline proc "c" (dest_ : ^i8 , src_ : ^i8 , n_ : int ) -> ^i8 { addr := 0x1000 + 212 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , ^i8 , int ) -> ^i8 ) (fp))(dest_, src_, n_); } -CRTstrnlen :: #force_inline proc "c" (s_ : ^i8 , maxlen_ : int ) -> int { addr := 0x1000 + 213 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , int ) -> int ) (fp))(s_, maxlen_); } -CRTstrstr :: #force_inline proc "c" (haystack_ : ^i8 , needle_ : ^i8 ) -> ^i8 { addr := 0x1000 + 214 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , ^i8 ) -> ^i8 ) (fp))(haystack_, needle_); } -CRTstrtol :: #force_inline proc "c" (nptr_ : ^i8 , endptr_ : ^^i8 , base_ : i32) -> i64 { addr := 0x1000 + 215 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , ^^i8 , i32) -> i64 ) (fp))(nptr_, endptr_, base_); } -CRTstrtoul :: #force_inline proc "c" (nptr_ : ^i8 , endptr_ : ^^i8 , base_ : i32) -> u64 { addr := 0x1000 + 216 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , ^^i8 , i32) -> u64 ) (fp))(nptr_, endptr_, base_); } -CRTtolower :: #force_inline proc "c" (c_ : i32) -> i32{ addr := 0x1000 + 217 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( i32) -> i32) (fp))(c_); } -NewPanel :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, cStyle_ : cstring = nil) -> ^Panel{ addr := 0x1000 + 219 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , cstring ) -> ^Panel) (fp))(parent_, flags_, cStyle_); } -NewSplitter :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, cStyle_ : cstring = nil) -> ^Splitter{ addr := 0x1000 + 220 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , cstring ) -> ^Splitter) (fp))(parent_, flags_, cStyle_); } -NewButton :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, cStyle_ : cstring = nil, label_ : string = "") -> ^Button{ addr := 0x1000 + 221 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , cstring , ^u8, int) -> ^Button) (fp))(parent_, flags_, cStyle_, raw_data(label_), len(label_)); } -NewChoice :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, cStyle_ : cstring = nil) -> ^Choice{ addr := 0x1000 + 222 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , cstring ) -> ^Choice) (fp))(parent_, flags_, cStyle_); } -NewColorWell :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, cStyle_ : cstring = nil, colorRGB_ : u32 = 0) -> ^ColorWell{ addr := 0x1000 + 223 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , cstring , u32 ) -> ^ColorWell) (fp))(parent_, flags_, cStyle_, colorRGB_); } -NewTextDisplay :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, cStyle_ : cstring = nil, label_ : string = "") -> ^TextDisplay{ addr := 0x1000 + 224 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , cstring , ^u8, int) -> ^TextDisplay) (fp))(parent_, flags_, cStyle_, raw_data(label_), len(label_)); } -NewIconDisplay :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, cStyle_ : cstring = nil, iconID_ : u32 = 0) -> ^IconDisplay{ addr := 0x1000 + 225 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , cstring , u32 ) -> ^IconDisplay) (fp))(parent_, flags_, cStyle_, iconID_); } -NewImageDisplay :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, cStyle_ : cstring = nil) -> ^ImageDisplay{ addr := 0x1000 + 226 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , cstring ) -> ^ImageDisplay) (fp))(parent_, flags_, cStyle_); } -NewListView :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, cStyle_ : cstring = nil, cItemStyle_ : cstring = nil, cHeaderItemStyle_ : cstring = nil, cFooterItemStyle_ : cstring = nil) -> ^ListView{ addr := 0x1000 + 227 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , cstring , cstring , cstring , cstring ) -> ^ListView) (fp))(parent_, flags_, cStyle_, cItemStyle_, cHeaderItemStyle_, cFooterItemStyle_); } -NewTextbox :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, cStyle_ : cstring = nil) -> ^Textbox{ addr := 0x1000 + 228 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , cstring ) -> ^Textbox) (fp))(parent_, flags_, cStyle_); } -NewCustomElement :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, cStyle_ : cstring = nil) -> ^Element{ addr := 0x1000 + 229 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , cstring ) -> ^Element) (fp))(parent_, flags_, cStyle_); } -NewWindow :: #force_inline proc "c" (instance_ : ^INSTANCE_TYPE, style_ : WindowStyle) -> ^Window{ addr := 0x1000 + 230 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^INSTANCE_TYPE, WindowStyle) -> ^Window) (fp))(instance_, style_); } -NewMenu :: #force_inline proc "c" (source_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, userCallback_ : MenuCallbackFunction = nil, _context_ : Generic = nil, fixedWidth_ : i32 = 0, fixedHeight_ : i32 = 0) -> ^Menu{ addr := 0x1000 + 231 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , MenuCallbackFunction, Generic, i32, i32) -> ^Menu) (fp))(source_, flags_, userCallback_, _context_, fixedWidth_, fixedHeight_); } -NewMenuItem :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 , label_ : string = "", callback_ : MenuCallbackFunction = nil, _context_ : Generic = nil){ addr := 0x1000 + 232 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element, u64 , ^u8, int, MenuCallbackFunction, Generic)) (fp))(parent_, flags_, raw_data(label_), len(label_), callback_, _context_); } -NewMenuCommand :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 , label_ : string, command_ : ^Command){ addr := 0x1000 + 233 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element, u64 , ^u8, int, ^Command)) (fp))(parent_, flags_, raw_data(label_), len(label_), command_); } -NewMenuColumn :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT) -> ^Element{ addr := 0x1000 + 234 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 ) -> ^Element) (fp))(parent_, flags_); } -NewMenuSeparator :: #force_inline proc "c" (parent_ : ^Element){ addr := 0x1000 + 235 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element)) (fp))(parent_); } -NewSpacer :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 ) -> ^Element{ addr := 0x1000 + 236 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 ) -> ^Element) (fp))(parent_, flags_); } -NewTextPlan :: #force_inline proc "c" (properties_ : ^TextDisplayProperties, bounds_ : Rectangle, string_ : ^i8 , textRuns_ : ^TextRun, textRunCount_ : int , singleUse_ : bool) -> ^TextPlan{ addr := 0x1000 + 238 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^TextDisplayProperties, Rectangle, ^i8 , ^TextRun, int , bool) -> ^TextPlan) (fp))(properties_, bounds_, string_, textRuns_, textRunCount_, singleUse_); } -ElementDraw :: #force_inline proc "c" (element_ : ^Element, painter_ : ^Painter){ addr := 0x1000 + 240 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element, ^Painter)) (fp))(element_, painter_); } -ElementFocus :: #force_inline proc "c" (element_ : ^Element, ensureVisible_ : bool = false){ addr := 0x1000 + 243 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element, bool)) (fp))(element_, ensureVisible_); } -ElementSetDisabled :: #force_inline proc "c" (element_ : ^Element, disabled_ : bool = true){ addr := 0x1000 + 244 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element, bool)) (fp))(element_, disabled_); } -ElementSetHidden :: #force_inline proc "c" (element_ : ^Element, hidden_ : bool = true){ addr := 0x1000 + 245 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element, bool)) (fp))(element_, hidden_); } -ElementSetCallback :: #force_inline proc "c" (element_ : ^Element, callback_ : UICallbackFunction){ addr := 0x1000 + 246 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element, UICallbackFunction)) (fp))(element_, callback_); } -ElementGetSize :: #force_inline proc "c" (element_ : ^Element, width_ : ^i32, height_ : ^i32){ addr := 0x1000 + 247 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element, ^i32, ^i32)) (fp))(element_, width_, height_); } -ElementRepaint :: #force_inline proc "c" (element_ : ^Element, all_ : bool, region_ : Rectangle){ addr := 0x1000 + 248 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element, bool, Rectangle)) (fp))(element_, all_, region_); } -ElementSetCellRange :: #force_inline proc "c" (element_ : ^Element, xFrom_ : i32, yFrom_ : i32, xTo_ : i32 = -1, yTo_ : i32 = -1){ addr := 0x1000 + 251 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element, i32, i32, i32, i32)) (fp))(element_, xFrom_, yFrom_, xTo_, yTo_); } -ElementGetInsets :: #force_inline proc "c" (element_ : ^Element) -> Rectangle{ addr := 0x1000 + 253 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element) -> Rectangle) (fp))(element_); } -ElementGetInsetSize :: #force_inline proc "c" (element_ : ^Element) -> Rectangle{ addr := 0x1000 + 254 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element) -> Rectangle) (fp))(element_); } -ElementGetMetrics :: #force_inline proc "c" (element_ : ^Element) -> ThemeMetrics{ addr := 0x1000 + 105 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element) -> ThemeMetrics) (fp))(element_); } -ElementGetPreferredSize :: #force_inline proc "c" (element_ : ^Element) -> Rectangle{ addr := 0x1000 + 255 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element) -> Rectangle) (fp))(element_); } -ElementMove :: #force_inline proc "c" (element_ : ^Element, x_ : i32, y_ : i32, width_ : i32, height_ : i32){ addr := 0x1000 + 256 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element, i32, i32, i32, i32)) (fp))(element_, x_, y_, width_, height_); } -ElementGetLayoutParent :: #force_inline proc "c" (element_ : ^Element) -> ^Element{ addr := 0x1000 + 257 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element) -> ^Element) (fp))(element_); } -ElementDestroy :: #force_inline proc "c" (element_ : ^Element){ addr := 0x1000 + 258 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element)) (fp))(element_); } -ElementDestroyContents :: #force_inline proc "c" (element_ : ^Element){ addr := 0x1000 + 259 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element)) (fp))(element_); } -ElementStartAnimating :: #force_inline proc "c" (element_ : ^Element) -> bool{ addr := 0x1000 + 260 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element) -> bool) (fp))(element_); } -WindowGetBounds :: #force_inline proc "c" (window_ : ^Window) -> Rectangle{ addr := 0x1000 + 265 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Window) -> Rectangle) (fp))(window_); } -WindowGetToolbar :: #force_inline proc "c" (window_ : ^Window) -> ^Element{ addr := 0x1000 + 266 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Window) -> ^Element) (fp))(window_); } -WindowSetIcon :: #force_inline proc "c" (window_ : ^Window, iconID_ : u32 ){ addr := 0x1000 + 267 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Window, u32 )) (fp))(window_, iconID_); } -WindowSetTitle :: #force_inline proc "c" (window_ : ^Window, title_ : string = ""){ addr := 0x1000 + 268 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Window, ^u8, int)) (fp))(window_, raw_data(title_), len(title_)); } -MenuGetSource :: #force_inline proc "c" (menu_ : ^Menu) -> ^Element{ addr := 0x1000 + 269 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Menu) -> ^Element) (fp))(menu_); } -ButtonSetIcon :: #force_inline proc "c" (button_ : ^Button, iconID_ : u32 ){ addr := 0x1000 + 270 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Button, u32 )) (fp))(button_, iconID_); } -ButtonSetCheck :: #force_inline proc "c" (button_ : ^Button, checkState_ : CheckState = CHECK_CHECKED, sendUpdatedMessage_ : bool = true){ addr := 0x1000 + 271 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Button, CheckState, bool)) (fp))(button_, checkState_, sendUpdatedMessage_); } -ButtonGetCheck :: #force_inline proc "c" (button_ : ^Button) -> CheckState{ addr := 0x1000 + 272 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Button) -> CheckState) (fp))(button_); } -ButtonOnCommand :: #force_inline proc "c" (button_ : ^Button, callback_ : CommandCallbackFunction, command_ : ^Command = nil){ addr := 0x1000 + 273 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Button, CommandCallbackFunction, ^Command)) (fp))(button_, callback_, command_); } -ButtonSetCheckBuddy :: #force_inline proc "c" (button_ : ^Button, checkBuddy_ : ^Element){ addr := 0x1000 + 274 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Button, ^Element)) (fp))(button_, checkBuddy_); } -ButtonGetCheckBuddy :: #force_inline proc "c" (button_ : ^Button) -> ^Element{ addr := 0x1000 + 275 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Button) -> ^Element) (fp))(button_); } -TextboxFind :: #force_inline proc "c" (textbox_ : ^Textbox, string_ : string, line_ : ^i32 , byte_ : ^i32 , flags_ : u32 ) -> bool{ addr := 0x1000 + 277 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Textbox, ^u8, int, ^i32 , ^i32 , u32 ) -> bool) (fp))(textbox_, raw_data(string_), len(string_), line_, byte_, flags_); } -TextboxInsert :: #force_inline proc "c" (textbox_ : ^Textbox, string_ : string = "", sendUpdatedMessage_ : bool = true){ addr := 0x1000 + 278 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Textbox, ^u8, int, bool)) (fp))(textbox_, raw_data(string_), len(string_), sendUpdatedMessage_); } -TextboxGetContents :: #force_inline proc "c" (textbox_ : ^Textbox, bytes_ : ^int = nil) -> ^i8 { addr := 0x1000 + 279 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Textbox, ^int ) -> ^i8 ) (fp))(textbox_, bytes_); } -TextboxGetLineLength :: #force_inline proc "c" (textbox_ : ^Textbox, line_ : uint = 0) -> int { addr := 0x1000 + 280 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Textbox, uint ) -> int ) (fp))(textbox_, line_); } -TextboxGetSelection :: #force_inline proc "c" (textbox_ : ^Textbox, fromLine_ : ^i32 , fromByte_ : ^i32 , toLine_ : ^i32 , toByte_ : ^i32 ){ addr := 0x1000 + 281 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Textbox, ^i32 , ^i32 , ^i32 , ^i32 )) (fp))(textbox_, fromLine_, fromByte_, toLine_, toByte_); } -TextboxMoveCaret :: #force_inline proc "c" (textbox_ : ^Textbox, line_ : i32 , byte_ : i32 ){ addr := 0x1000 + 282 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Textbox, i32 , i32 )) (fp))(textbox_, line_, byte_); } -TextboxSetSelection :: #force_inline proc "c" (textbox_ : ^Textbox, fromLine_ : i32 , fromByte_ : i32 , toLine_ : i32 , toByte_ : i32 ){ addr := 0x1000 + 283 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Textbox, i32 , i32 , i32 , i32 )) (fp))(textbox_, fromLine_, fromByte_, toLine_, toByte_); } -TextboxSelectAll :: #force_inline proc "c" (textbox_ : ^Textbox){ addr := 0x1000 + 284 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Textbox)) (fp))(textbox_); } -TextboxClear :: #force_inline proc "c" (textbox_ : ^Textbox, sendUpdatedMessage_ : bool){ addr := 0x1000 + 285 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Textbox, bool)) (fp))(textbox_, sendUpdatedMessage_); } -TextboxUseNumberOverlay :: #force_inline proc "c" (textbox_ : ^Textbox, defaultBehaviour_ : bool){ addr := 0x1000 + 286 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Textbox, bool)) (fp))(textbox_, defaultBehaviour_); } -TextboxUseBreadcrumbOverlay :: #force_inline proc "c" (textbox_ : ^Textbox){ addr := 0x1000 + 287 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Textbox)) (fp))(textbox_); } -TextboxMoveCaretRelative :: #force_inline proc "c" (textbox_ : ^Textbox, flags_ : u32 ){ addr := 0x1000 + 118 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Textbox, u32 )) (fp))(textbox_, flags_); } -TextboxEnsureCaretVisible :: #force_inline proc "c" (textbox_ : ^Textbox){ addr := 0x1000 + 119 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Textbox)) (fp))(textbox_); } -PanelSetBands :: #force_inline proc "c" (panel_ : ^Panel, columnCount_ : int , rowCount_ : int = 0, columns_ : ^PanelBand = nil, rows_ : ^PanelBand = nil){ addr := 0x1000 + 288 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Panel, int , int , ^PanelBand, ^PanelBand)) (fp))(panel_, columnCount_, rowCount_, columns_, rows_); } -PanelSwitchTo :: #force_inline proc "c" (panel_ : ^Panel, targetChild_ : ^Element, transitionType_ : TransitionType, destroyPreviousAfterTransitionCompletes_ : bool = false){ addr := 0x1000 + 289 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Panel, ^Element, TransitionType, bool)) (fp))(panel_, targetChild_, transitionType_, destroyPreviousAfterTransitionCompletes_); } -TextPlanGetWidth :: #force_inline proc "c" (plan_ : ^TextPlan) -> i32{ addr := 0x1000 + 290 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^TextPlan) -> i32) (fp))(plan_); } -TextPlanGetHeight :: #force_inline proc "c" (plan_ : ^TextPlan) -> i32{ addr := 0x1000 + 291 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^TextPlan) -> i32) (fp))(plan_); } -TextPlanGetLineCount :: #force_inline proc "c" (plan_ : ^TextPlan) -> int { addr := 0x1000 + 292 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^TextPlan) -> int ) (fp))(plan_); } -TextPlanDestroy :: #force_inline proc "c" (plan_ : ^TextPlan){ addr := 0x1000 + 293 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^TextPlan)) (fp))(plan_); } -TextDisplaySetContents :: #force_inline proc "c" (display_ : ^TextDisplay, contents_ : string = ""){ addr := 0x1000 + 294 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^TextDisplay, ^u8, int)) (fp))(display_, raw_data(contents_), len(contents_)); } -ColorWellGetRGB :: #force_inline proc "c" (well_ : ^ColorWell) -> u32 { addr := 0x1000 + 304 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^ColorWell) -> u32 ) (fp))(well_); } -ColorWellSetRGB :: #force_inline proc "c" (well_ : ^ColorWell, colorRGB_ : u32 , sendChangedMessage_ : bool){ addr := 0x1000 + 305 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^ColorWell, u32 , bool)) (fp))(well_, colorRGB_, sendChangedMessage_); } -ColorWellSetIndeterminate :: #force_inline proc "c" (well_ : ^ColorWell){ addr := 0x1000 + 306 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^ColorWell)) (fp))(well_); } -ChoiceAddMenuItem :: #force_inline proc "c" (choice_ : ^Choice, item_ : Generic){ addr := 0x1000 + 307 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Choice, Generic)) (fp))(choice_, item_); } -ChoiceSetItem :: #force_inline proc "c" (choice_ : ^Choice, item_ : Generic, sendUpdatedMessage_ : bool = true){ addr := 0x1000 + 308 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Choice, Generic, bool)) (fp))(choice_, item_, sendUpdatedMessage_); } -ChoiceGetItem :: #force_inline proc "c" (choice_ : ^Choice) -> Generic{ addr := 0x1000 + 309 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Choice) -> Generic) (fp))(choice_); } -ListViewInsertGroup :: #force_inline proc "c" (view_ : ^ListView, group_ : i32 , flags_ : u32 = FLAGS_DEFAULT){ addr := 0x1000 + 310 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^ListView, i32 , u32 )) (fp))(view_, group_, flags_); } -ListViewInsert :: #force_inline proc "c" (view_ : ^ListView, group_ : i32 , firstIndex_ : Generic, lastIndex_ : Generic, count_ : i64 = -1){ addr := 0x1000 + 311 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^ListView, i32 , Generic, Generic, i64 )) (fp))(view_, group_, firstIndex_, lastIndex_, count_); } -ListViewRemove :: #force_inline proc "c" (view_ : ^ListView, group_ : i32 , firstIndex_ : Generic, lastIndex_ : Generic, count_ : i64 = -1){ addr := 0x1000 + 312 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^ListView, i32 , Generic, Generic, i64 )) (fp))(view_, group_, firstIndex_, lastIndex_, count_); } -ListViewRemoveAll :: #force_inline proc "c" (view_ : ^ListView, group_ : i32 ){ addr := 0x1000 + 313 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^ListView, i32 )) (fp))(view_, group_); } -ListViewGetIndex :: #force_inline proc "c" (view_ : ^ListView, item_ : ^Element) -> Generic{ addr := 0x1000 + 314 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^ListView, ^Element) -> Generic) (fp))(view_, item_); } -ListViewSetColumns :: #force_inline proc "c" (view_ : ^ListView, columns_ : ^ListViewColumn, columnCount_ : int ){ addr := 0x1000 + 315 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^ListView, ^ListViewColumn, int )) (fp))(view_, columns_, columnCount_); } -ListViewSetEmptyMessage :: #force_inline proc "c" (view_ : ^ListView, message_ : string = ""){ addr := 0x1000 + 262 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^ListView, ^u8, int)) (fp))(view_, raw_data(message_), len(message_)); } -ListViewSelect :: #force_inline proc "c" (view_ : ^ListView, group_ : i32 , index_ : Generic){ addr := 0x1000 + 316 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^ListView, i32 , Generic)) (fp))(view_, group_, index_); } -ListViewResetSearchBuffer :: #force_inline proc "c" (view_ : ^ListView){ addr := 0x1000 + 317 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^ListView)) (fp))(view_); } +ThreadEntryFunction :: distinct #type proc "c" ( Generic) +ComparisonCallbackFunction :: distinct #type proc "c" ( rawptr, rawptr, Generic) -> i32 +SwapCallbackFunction :: distinct #type proc "c" ( rawptr, rawptr, Generic) +CRTComparisonCallback :: distinct #type proc "c" ( rawptr, rawptr) -> i32 +TimerCallbackFunction :: distinct #type proc "c" ( Generic) +MenuCallbackFunction :: distinct #type proc "c" ( ^Menu, Generic) +StorePullCallbackFunction :: distinct #type proc "c" ( ^Store, ^INSTANCE_TYPE, Generic, i32, Generic, ^FormatValue) +StoreCallbackFunction :: distinct #type proc "c" ( ^Store, ^INSTANCE_TYPE, Generic, Generic) +DirectoryMonitorCallbackFunction :: distinct #type proc "c" ( ^DirectoryMonitor, i32, ^i8 , int , ^i8 , int , Generic) +Batch :: #force_inline proc "c" (calls_ : ^BatchCall, count_ : int ){ addr := 0x1000 + 0 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^BatchCall, int )) (fp))(calls_, count_)} +GetCreationArgument :: #force_inline proc "c" (object_ : Handle) -> Generic{ addr := 0x1000 + 1 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle) -> Generic) (fp))(object_)} +GetSystemInformation :: #force_inline proc "c" (systemInformation_ : ^SystemInformation){ addr := 0x1000 + 2 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^SystemInformation)) (fp))(systemInformation_)} +HandleClose :: #force_inline proc "c" (handle_ : Handle) -> Error{ addr := 0x1000 + 3 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle) -> Error) (fp))(handle_)} +InitialiseCStandardLibrary :: #force_inline proc "c" (argc_ : ^i32, argv_ : ^^^i8 ){ addr := 0x1000 + 4 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^i32, ^^^i8 )) (fp))(argc_, argv_)} +MailslotSendData :: #force_inline proc "c" (mailslot_ : Handle, data_ : rawptr, bytes_ : int ) -> bool{ addr := 0x1000 + 5 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, rawptr, int ) -> bool) (fp))(mailslot_, data_, bytes_)} +MakeLinuxSystemCall2 :: #force_inline proc "c" (n_ : int , a1_ : int , a2_ : int , a3_ : int , a4_ : int , a5_ : int , a6_ : int ) -> int { addr := 0x1000 + 6 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( int , int , int , int , int , int , int ) -> int ) (fp))(n_, a1_, a2_, a3_, a4_, a5_, a6_)} +SystemGetConstant :: #force_inline proc "c" (index_ : uint ) -> u64 { addr := 0x1000 + 8 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( uint ) -> u64 ) (fp))(index_)} +TakeSystemSnapshot :: #force_inline proc "c" (type_ : i32, bufferSize_ : ^int ) -> Handle{ addr := 0x1000 + 9 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( i32, ^int ) -> Handle) (fp))(type_, bufferSize_)} +UserGetHomeFolder :: #force_inline proc "c" (buffer_ : ^i8 , bufferBytes_ : int ) -> int { addr := 0x1000 + 10 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , int ) -> int ) (fp))(buffer_, bufferBytes_)} +_EsInstanceCreate :: #force_inline proc "c" (bytes_ : int , message_ : ^Message, cName_ : cstring ) -> ^Instance{ addr := 0x1000 + 11 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( int , ^Message, cstring ) -> ^Instance) (fp))(bytes_, message_, cName_)} +_EsSyscall :: #force_inline proc "c" (a_ : uint , b_ : uint , c_ : uint , d_ : uint , e_ : uint , f_ : uint ) -> uint { addr := 0x1000 + 12 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( uint , uint , uint , uint , uint , uint ) -> uint ) (fp))(a_, b_, c_, d_, e_, f_)} +ApplicationStart :: #force_inline proc "c" (information_ : ^ApplicationStartupInformation){ addr := 0x1000 + 124 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^ApplicationStartupInformation)) (fp))(information_)} +SystemConfigurationReadAll :: #force_inline proc "c" (groupCount_ : ^int ) -> ^SystemConfigurationGroup{ addr := 0x1000 + 164 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^int ) -> ^SystemConfigurationGroup) (fp))(groupCount_)} +SystemConfigurationReadInteger :: #force_inline proc "c" (section_ : string, key_ : string, defaultValue_ : i64 = 0) -> i64 { addr := 0x1000 + 295 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, ^u8, int, i64 ) -> i64 ) (fp))(raw_data(section_), len(section_), raw_data(key_), len(key_), defaultValue_)} +SystemConfigurationGroupReadInteger :: #force_inline proc "c" (group_ : ^SystemConfigurationGroup, key_ : string, defaultValue_ : i64 = 0) -> i64 { addr := 0x1000 + 296 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^SystemConfigurationGroup, ^u8, int, i64 ) -> i64 ) (fp))(group_, raw_data(key_), len(key_), defaultValue_)} +SystemConfigurationReadString :: #force_inline proc "c" (section_ : string, key_ : string, valueBytes_ : ^int = nil) -> ^i8 { addr := 0x1000 + 297 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, ^u8, int, ^int ) -> ^i8 ) (fp))(raw_data(section_), len(section_), raw_data(key_), len(key_), valueBytes_)} +SystemConfigurationGroupReadString :: #force_inline proc "c" (group_ : ^SystemConfigurationGroup, key_ : string, valueBytes_ : ^int = nil) -> ^i8 { addr := 0x1000 + 298 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^SystemConfigurationGroup, ^u8, int, ^int ) -> ^i8 ) (fp))(group_, raw_data(key_), len(key_), valueBytes_)} +INIParse :: #force_inline proc "c" (s_ : ^INIState) -> bool{ addr := 0x1000 + 7 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^INIState) -> bool) (fp))(s_)} +INIPeek :: #force_inline proc "c" (s_ : ^INIState) -> bool{ addr := 0x1000 + 87 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^INIState) -> bool) (fp))(s_)} +INIFormat :: #force_inline proc "c" (s_ : ^INIState, buffer_ : ^i8 , bytes_ : int ) -> int { addr := 0x1000 + 125 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^INIState, ^i8 , int ) -> int ) (fp))(s_, buffer_, bytes_)} +INIZeroTerminate :: #force_inline proc "c" (s_ : ^INIState){ addr := 0x1000 + 126 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^INIState)) (fp))(s_)} +CommandAddButton :: #force_inline proc "c" (command_ : ^Command, button_ : ^Button){ addr := 0x1000 + 13 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Command, ^Button)) (fp))(command_, button_)} +CommandByID :: #force_inline proc "c" (instance_ : ^Instance, stableID_ : u32 ) -> ^Command{ addr := 0x1000 + 14 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Instance, u32 ) -> ^Command) (fp))(instance_, stableID_)} +CommandRegister :: #force_inline proc "c" (command_ : ^Command, instance_ : ^Instance, callback_ : CommandCallbackFunction, stableID_ : u32 , cDefaultKeyboardShortcut_ : cstring = nil, enabled_ : bool = false) -> ^Command{ addr := 0x1000 + 15 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Command, ^Instance, CommandCallbackFunction, u32 , cstring , bool) -> ^Command) (fp))(command_, instance_, callback_, stableID_, cDefaultKeyboardShortcut_, enabled_)} +CommandSetCallback :: #force_inline proc "c" (command_ : ^Command, callback_ : CommandCallbackFunction){ addr := 0x1000 + 16 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Command, CommandCallbackFunction)) (fp))(command_, callback_)} +CommandSetDisabled :: #force_inline proc "c" (command_ : ^Command, disabled_ : bool){ addr := 0x1000 + 17 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Command, bool)) (fp))(command_, disabled_)} +DialogClose :: #force_inline proc "c" (window_ : ^Window){ addr := 0x1000 + 18 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Window)) (fp))(window_)} +DialogShow :: #force_inline proc "c" (window_ : ^Window) -> ^Element{ addr := 0x1000 + 19 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Window) -> ^Element) (fp))(window_)} +DialogShowAlert :: #force_inline proc "c" (window_ : ^Window, title_ : string, content_ : string, iconID_ : u32 , addOKButton_ : bool = false) -> ^Element{ addr := 0x1000 + 20 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Window, ^u8, int, ^u8, int, u32 , bool) -> ^Element) (fp))(window_, raw_data(title_), len(title_), raw_data(content_), len(content_), iconID_, addOKButton_)} +InstanceDestroy :: #force_inline proc "c" (instance_ : ^INSTANCE_TYPE){ addr := 0x1000 + 300 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^INSTANCE_TYPE)) (fp))(instance_)} +KeyboardIsAltHeld :: #force_inline proc "c" () -> bool{ addr := 0x1000 + 21 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" () -> bool) (fp))()} +KeyboardIsCtrlHeld :: #force_inline proc "c" () -> bool{ addr := 0x1000 + 22 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" () -> bool) (fp))()} +KeyboardIsShiftHeld :: #force_inline proc "c" () -> bool{ addr := 0x1000 + 23 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" () -> bool) (fp))()} +MessageGetInputText :: #force_inline proc "c" (message_ : ^Message, buffer_ : ^i8 ) -> int { addr := 0x1000 + 24 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Message, ^i8 ) -> int ) (fp))(message_, buffer_)} +MessageMutexAcquire :: #force_inline proc "c" (){ addr := 0x1000 + 26 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ()) (fp))()} +MessageMutexCheck :: #force_inline proc "c" (){ addr := 0x1000 + 27 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ()) (fp))()} +MessageMutexRelease :: #force_inline proc "c" (){ addr := 0x1000 + 28 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ()) (fp))()} +MessagePost :: #force_inline proc "c" (target_ : ^Element, message_ : ^Message) -> Error{ addr := 0x1000 + 29 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, ^Message) -> Error) (fp))(target_, message_)} +MessagePostRemote :: #force_inline proc "c" (process_ : Handle, message_ : ^Message) -> Error{ addr := 0x1000 + 30 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, ^Message) -> Error) (fp))(process_, message_)} +MessageSend :: #force_inline proc "c" (object_ : ^Element, message_ : ^Message) -> Response{ addr := 0x1000 + 31 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, ^Message) -> Response) (fp))(object_, message_)} +MessageReceive :: #force_inline proc "c" () -> ^Message{ addr := 0x1000 + 318 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" () -> ^Message) (fp))()} +MouseGetPosition :: #force_inline proc "c" (relativeElement_ : ^Element = nil) -> Point{ addr := 0x1000 + 32 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element) -> Point) (fp))(relativeElement_)} +MouseSetPosition :: #force_inline proc "c" (relativeWindow_ : ^Window, x_ : i32, y_ : i32){ addr := 0x1000 + 33 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Window, i32, i32)) (fp))(relativeWindow_, x_, y_)} +StyleInheritRegister :: #force_inline proc "c" (cStyle_ : cstring , cParent_ : cstring , customMetrics_ : ^ThemeMetrics){ addr := 0x1000 + 239 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( cstring , cstring , ^ThemeMetrics)) (fp))(cStyle_, cParent_, customMetrics_)} +StyleRefreshAll :: #force_inline proc "c" (window_ : ^Element){ addr := 0x1000 + 37 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element)) (fp))(window_)} +UISetDPI :: #force_inline proc "c" (dpiScale_ : i32){ addr := 0x1000 + 38 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( i32)) (fp))(dpiScale_)} +DirectoryEnumerateChildren :: #force_inline proc "c" (path_ : string, buffer_ : ^^DirectoryChild) -> int { addr := 0x1000 + 39 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, ^^DirectoryChild) -> int ) (fp))(raw_data(path_), len(path_), buffer_)} +DirectoryEnumerateChildrenFromHandle :: #force_inline proc "c" (directory_ : Handle, buffer_ : ^DirectoryChild, bufferCount_ : int ) -> int { addr := 0x1000 + 40 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, ^DirectoryChild, int ) -> int ) (fp))(directory_, buffer_, bufferCount_)} +DirectoryMonitorCreate :: #force_inline proc "c" (path_ : string, flags_ : u32 , callback_ : DirectoryMonitorCallbackFunction, _context_ : Generic) -> ^DirectoryMonitor{ addr := 0x1000 + 41 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, u32 , DirectoryMonitorCallbackFunction, Generic) -> ^DirectoryMonitor) (fp))(raw_data(path_), len(path_), flags_, callback_, _context_)} +DirectoryMonitorCreateFromHandle :: #force_inline proc "c" (directory_ : Handle, flags_ : u32 , callback_ : DirectoryMonitorCallbackFunction, _context_ : Generic) -> ^DirectoryMonitor{ addr := 0x1000 + 42 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, u32 , DirectoryMonitorCallbackFunction, Generic) -> ^DirectoryMonitor) (fp))(directory_, flags_, callback_, _context_)} +DirectoryMonitorDestroy :: #force_inline proc "c" (monitor_ : ^DirectoryMonitor){ addr := 0x1000 + 43 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^DirectoryMonitor)) (fp))(monitor_)} +FileControl :: #force_inline proc "c" (file_ : Handle, flags_ : u32 ) -> Error{ addr := 0x1000 + 96 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, u32 ) -> Error) (fp))(file_, flags_)} +FileReadAll :: #force_inline proc "c" (filePath_ : string, fileSize_ : ^int ) -> rawptr{ addr := 0x1000 + 44 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, ^int ) -> rawptr) (fp))(raw_data(filePath_), len(filePath_), fileSize_)} +FileReadSync :: #force_inline proc "c" (file_ : Handle, offset_ : FileOffset, size_ : int , buffer_ : rawptr) -> int { addr := 0x1000 + 45 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, FileOffset, int , rawptr) -> int ) (fp))(file_, offset_, size_, buffer_)} +FileResize :: #force_inline proc "c" (file_ : Handle, newSize_ : FileOffset) -> Error{ addr := 0x1000 + 46 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, FileOffset) -> Error) (fp))(file_, newSize_)} +FileWriteAll :: #force_inline proc "c" (filePath_ : string, data_ : rawptr, fileSize_ : int ) -> Error{ addr := 0x1000 + 47 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, rawptr, int ) -> Error) (fp))(raw_data(filePath_), len(filePath_), data_, fileSize_)} +FileWriteAllGather :: #force_inline proc "c" (filePath_ : string, data_ : ^rawptr, fileSize_ : ^int , gatherCount_ : int ) -> Error{ addr := 0x1000 + 48 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, ^rawptr, ^int , int ) -> Error) (fp))(raw_data(filePath_), len(filePath_), data_, fileSize_, gatherCount_)} +FileWriteSync :: #force_inline proc "c" (file_ : Handle, offset_ : FileOffset, size_ : int , buffer_ : rawptr) -> int { addr := 0x1000 + 50 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, FileOffset, int , rawptr) -> int ) (fp))(file_, offset_, size_, buffer_)} +NodeDelete :: #force_inline proc "c" (node_ : Handle) -> Error{ addr := 0x1000 + 51 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle) -> Error) (fp))(node_)} +NodeDeleteByPath :: #force_inline proc "c" (filePath_ : string) -> Error{ addr := 0x1000 + 52 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int) -> Error) (fp))(raw_data(filePath_), len(filePath_))} +NodeFindUniqueName :: #force_inline proc "c" (buffer_ : ^i8 , originalBytes_ : int , bufferBytes_ : int ) -> int { addr := 0x1000 + 53 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , int , int ) -> int ) (fp))(buffer_, originalBytes_, bufferBytes_)} +NodeMove :: #force_inline proc "c" (node_ : Handle, newDirectory_ : Handle, newName_ : string) -> Error{ addr := 0x1000 + 54 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, Handle, ^u8, int) -> Error) (fp))(node_, newDirectory_, raw_data(newName_), len(newName_))} +NodeOpen :: #force_inline proc "c" (path_ : string, flags_ : u32 , information_ : ^NodeInformation) -> Error{ addr := 0x1000 + 55 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, u32 , ^NodeInformation) -> Error) (fp))(raw_data(path_), len(path_), flags_, information_)} +NodeOpenRelative :: #force_inline proc "c" (directory_ : Handle, path_ : string, flags_ : u32 , information_ : ^NodeInformation) -> Error{ addr := 0x1000 + 56 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, ^u8, int, u32 , ^NodeInformation) -> Error) (fp))(directory_, raw_data(path_), len(path_), flags_, information_)} +NodeRefreshInformation :: #force_inline proc "c" (information_ : ^NodeInformation){ addr := 0x1000 + 57 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^NodeInformation)) (fp))(information_)} +ProcessCrash :: #force_inline proc "c" (error_ : Error, message_ : string){ addr := 0x1000 + 58 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( Error, ^u8, int)) (fp))(error_, raw_data(message_), len(message_))} +ProcessCreate :: #force_inline proc "c" (executablePath_ : string, information_ : ^ProcessInformation, argument_ : Generic) -> Error{ addr := 0x1000 + 59 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, ^ProcessInformation, Generic) -> Error) (fp))(raw_data(executablePath_), len(executablePath_), information_, argument_)} +ProcessCreate2 :: #force_inline proc "c" (arguments_ : ^ProcessCreationArguments, information_ : ^ProcessInformation) -> Error{ addr := 0x1000 + 60 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^ProcessCreationArguments, ^ProcessInformation) -> Error) (fp))(arguments_, information_)} +ProcessGetExitStatus :: #force_inline proc "c" (process_ : Handle) -> i32{ addr := 0x1000 + 61 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle) -> i32) (fp))(process_)} +ProcessGetID :: #force_inline proc "c" (process_ : Handle) -> uint { addr := 0x1000 + 62 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle) -> uint ) (fp))(process_)} +ProcessGetState :: #force_inline proc "c" (process_ : Handle, state_ : ^ProcessState){ addr := 0x1000 + 63 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( Handle, ^ProcessState)) (fp))(process_, state_)} +ProcessOpen :: #force_inline proc "c" (pid_ : u64 ) -> Handle{ addr := 0x1000 + 64 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( u64 ) -> Handle) (fp))(pid_)} +ProcessPause :: #force_inline proc "c" (process_ : Handle, resume_ : bool){ addr := 0x1000 + 65 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( Handle, bool)) (fp))(process_, resume_)} +ProcessTerminate :: #force_inline proc "c" (process_ : Handle, status_ : i32){ addr := 0x1000 + 66 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( Handle, i32)) (fp))(process_, status_)} +ProcessTerminateCurrent :: #force_inline proc "c" (){ addr := 0x1000 + 67 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ()) (fp))()} +ThreadCreate :: #force_inline proc "c" (entryFunction_ : ThreadEntryFunction, information_ : ^ThreadInformation, argument_ : Generic) -> Error{ addr := 0x1000 + 68 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ThreadEntryFunction, ^ThreadInformation, Generic) -> Error) (fp))(entryFunction_, information_, argument_)} +ThreadGetID :: #force_inline proc "c" (thread_ : Handle) -> uint { addr := 0x1000 + 69 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle) -> uint ) (fp))(thread_)} +ThreadTerminate :: #force_inline proc "c" (thread_ : Handle){ addr := 0x1000 + 71 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( Handle)) (fp))(thread_)} +ArenaAllocate :: #force_inline proc "c" (arena_ : ^Arena, zero_ : bool) -> rawptr{ addr := 0x1000 + 72 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Arena, bool) -> rawptr) (fp))(arena_, zero_)} +ArenaFree :: #force_inline proc "c" (arena_ : ^Arena, pointer_ : rawptr){ addr := 0x1000 + 73 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Arena, rawptr)) (fp))(arena_, pointer_)} +ArenaInitialise :: #force_inline proc "c" (arena_ : ^Arena, blockSize_ : int , itemSize_ : int ){ addr := 0x1000 + 74 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Arena, int , int )) (fp))(arena_, blockSize_, itemSize_)} +ConstantBufferCreate :: #force_inline proc "c" (data_ : rawptr, dataBytes_ : int , targetProcess_ : Handle) -> Handle{ addr := 0x1000 + 75 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( rawptr, int , Handle) -> Handle) (fp))(data_, dataBytes_, targetProcess_)} +ConstantBufferRead :: #force_inline proc "c" (constantBuffer_ : Handle, output_ : rawptr){ addr := 0x1000 + 76 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( Handle, rawptr)) (fp))(constantBuffer_, output_)} +ConstantBufferShare :: #force_inline proc "c" (constantBuffer_ : Handle, targetProcess_ : Handle) -> Handle{ addr := 0x1000 + 77 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, Handle) -> Handle) (fp))(constantBuffer_, targetProcess_)} +HeapAllocate :: #force_inline proc "c" (size_ : int , zeroMemory_ : bool) -> rawptr{ addr := 0x1000 + 78 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( int , bool) -> rawptr) (fp))(size_, zeroMemory_)} +HeapFree :: #force_inline proc "c" (address_ : rawptr){ addr := 0x1000 + 79 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( rawptr)) (fp))(address_)} +HeapReallocate :: #force_inline proc "c" (oldAddress_ : rawptr, newAllocationSize_ : int , zeroNewSpace_ : bool) -> rawptr{ addr := 0x1000 + 80 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( rawptr, int , bool) -> rawptr) (fp))(oldAddress_, newAllocationSize_, zeroNewSpace_)} +HeapValidate :: #force_inline proc "c" (){ addr := 0x1000 + 81 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ()) (fp))()} +MemoryCommit :: #force_inline proc "c" (pointer_ : rawptr, bytes_ : int ) -> bool{ addr := 0x1000 + 82 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( rawptr, int ) -> bool) (fp))(pointer_, bytes_)} +MemoryCompare :: #force_inline proc "c" (a_ : rawptr, b_ : rawptr, bytes_ : int ) -> i32{ addr := 0x1000 + 83 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( rawptr, rawptr, int ) -> i32) (fp))(a_, b_, bytes_)} +MemoryCopy :: #force_inline proc "c" (destination_ : rawptr, source_ : rawptr, bytes_ : int ){ addr := 0x1000 + 84 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( rawptr, rawptr, int )) (fp))(destination_, source_, bytes_)} +MemoryCopyReverse :: #force_inline proc "c" (_destination_ : rawptr, _source_ : rawptr, bytes_ : int ){ addr := 0x1000 + 85 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( rawptr, rawptr, int )) (fp))(_destination_, _source_, bytes_)} +MemoryDecommit :: #force_inline proc "c" (pointer_ : rawptr, bytes_ : int ) -> bool{ addr := 0x1000 + 320 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( rawptr, int ) -> bool) (fp))(pointer_, bytes_)} +MemoryFill :: #force_inline proc "c" (from_ : rawptr, to_ : rawptr, byte_ : u8 ){ addr := 0x1000 + 86 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( rawptr, rawptr, u8 )) (fp))(from_, to_, byte_)} +MemoryMove :: #force_inline proc "c" (_start_ : rawptr, _end_ : rawptr, amount_ : int , zeroEmptySpace_ : bool){ addr := 0x1000 + 88 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( rawptr, rawptr, int , bool)) (fp))(_start_, _end_, amount_, zeroEmptySpace_)} +MemoryOpen :: #force_inline proc "c" (size_ : int , name_ : string, flags_ : u32 ) -> Handle{ addr := 0x1000 + 89 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( int , ^u8, int, u32 ) -> Handle) (fp))(size_, raw_data(name_), len(name_), flags_)} +MemoryReserve :: #force_inline proc "c" (size_ : int , protection_ : MemoryProtection = MEMORY_PROTECTION_READ_WRITE, commitAll_ : bool = true) -> rawptr{ addr := 0x1000 + 25 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( int , MemoryProtection, bool) -> rawptr) (fp))(size_, protection_, commitAll_)} +MemoryShare :: #force_inline proc "c" (sharedMemoryRegion_ : Handle, targetProcess_ : Handle, readOnly_ : bool) -> Handle{ addr := 0x1000 + 90 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, Handle, bool) -> Handle) (fp))(sharedMemoryRegion_, targetProcess_, readOnly_)} +MemorySumBytes :: #force_inline proc "c" (data_ : ^u8 , bytes_ : int ) -> u8 { addr := 0x1000 + 91 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^u8 , int ) -> u8 ) (fp))(data_, bytes_)} +MemoryUnreserve :: #force_inline proc "c" (pointer_ : rawptr, size_ : int = 0){ addr := 0x1000 + 319 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( rawptr, int )) (fp))(pointer_, size_)} +MemoryZero :: #force_inline proc "c" (destination_ : rawptr, bytes_ : int ){ addr := 0x1000 + 92 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( rawptr, int )) (fp))(destination_, bytes_)} +ObjectMap :: #force_inline proc "c" (object_ : Handle, offset_ : uint , size_ : int , flags_ : u32 ) -> rawptr{ addr := 0x1000 + 93 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, uint , int , u32 ) -> rawptr) (fp))(object_, offset_, size_, flags_)} +AssertionFailure :: #force_inline proc "c" (cFile_ : cstring , line_ : i32){ addr := 0x1000 + 94 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( cstring , i32)) (fp))(cFile_, line_)} +DoubleParse :: #force_inline proc "c" (string_ : string, endptr_ : ^^i8 ) -> f64 { addr := 0x1000 + 97 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, ^^i8 ) -> f64 ) (fp))(raw_data(string_), len(string_), endptr_)} +ExtractArguments :: #force_inline proc "c" (string_ : ^i8 , bytes_ : int , delimiterByte_ : u8 , replacementDelimiter_ : u8 , argvAllocated_ : int , argv_ : ^^i8 , argc_ : ^int ) -> bool{ addr := 0x1000 + 98 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , int , u8 , u8 , int , ^^i8 , ^int ) -> bool) (fp))(string_, bytes_, delimiterByte_, replacementDelimiter_, argvAllocated_, argv_, argc_)} +RandomU8 :: #force_inline proc "c" () -> u8 { addr := 0x1000 + 301 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" () -> u8 ) (fp))()} +RandomU64 :: #force_inline proc "c" () -> u64 { addr := 0x1000 + 302 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" () -> u64 ) (fp))()} +IntegerParse :: #force_inline proc "c" (text_ : string) -> i64 { addr := 0x1000 + 101 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int) -> i64 ) (fp))(raw_data(text_), len(text_))} +Print :: #force_inline proc "c" (format_ : cstring , _varargs_ : ..any){ addr := 0x1000 + 102 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( cstring , ..any)) (fp))(format_, _varargs_)} +PrintDirect :: #force_inline proc "c" (string_ : string){ addr := 0x1000 + 103 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" (^u8, int)) (fp))(raw_data(string_), len(string_))} +PrintHelloWorld :: #force_inline proc "c" (){ addr := 0x1000 + 104 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ()) (fp))()} +RandomAddEntropy :: #force_inline proc "c" (x_ : u64 ){ addr := 0x1000 + 106 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( u64 )) (fp))(x_)} +RandomSeed :: #force_inline proc "c" (x_ : u64 ){ addr := 0x1000 + 107 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( u64 )) (fp))(x_)} +RectangleClip :: #force_inline proc "c" (parent_ : Rectangle, rectangle_ : Rectangle, output_ : ^Rectangle) -> bool{ addr := 0x1000 + 108 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Rectangle, Rectangle, ^Rectangle) -> bool) (fp))(parent_, rectangle_, output_)} +Sort :: #force_inline proc "c" (_base_ : rawptr, nmemb_ : int , size_ : int , compar_ : ComparisonCallbackFunction, argument_ : Generic){ addr := 0x1000 + 109 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( rawptr, int , int , ComparisonCallbackFunction, Generic)) (fp))(_base_, nmemb_, size_, compar_, argument_)} +SortWithSwapCallback :: #force_inline proc "c" (_base_ : rawptr, nmemb_ : int , size_ : int , compar_ : ComparisonCallbackFunction, argument_ : Generic, swap_ : SwapCallbackFunction){ addr := 0x1000 + 110 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( rawptr, int , int , ComparisonCallbackFunction, Generic, SwapCallbackFunction)) (fp))(_base_, nmemb_, size_, compar_, argument_, swap_)} +TimeStamp :: #force_inline proc "c" () -> u64 { addr := 0x1000 + 252 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" () -> u64 ) (fp))()} +TimeStampMs :: #force_inline proc "c" () -> f64 { addr := 0x1000 + 261 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" () -> f64 ) (fp))()} +ColorBlend :: #force_inline proc "c" (under_ : u32 , over_ : u32 , fullAlpha_ : bool) -> u32 { addr := 0x1000 + 111 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( u32 , u32 , bool) -> u32 ) (fp))(under_, over_, fullAlpha_)} +ColorConvertToRGB :: #force_inline proc "c" (h_ : f32 , s_ : f32 , v_ : f32 ) -> u32 { addr := 0x1000 + 112 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 , f32 , f32 ) -> u32 ) (fp))(h_, s_, v_)} +ColorConvertToHSV :: #force_inline proc "c" (color_ : u32 , h_ : ^f32 , s_ : ^f32 , v_ : ^f32 ) -> bool{ addr := 0x1000 + 113 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( u32 , ^f32 , ^f32 , ^f32 ) -> bool) (fp))(color_, h_, s_, v_)} +ColorParse :: #force_inline proc "c" (string_ : string) -> u32 { addr := 0x1000 + 114 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int) -> u32 ) (fp))(raw_data(string_), len(string_))} +DrawBitmap :: #force_inline proc "c" (painter_ : ^Painter, region_ : Rectangle, bits_ : ^u32 , stride_ : uint ){ addr := 0x1000 + 324 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Painter, Rectangle, ^u32 , uint )) (fp))(painter_, region_, bits_, stride_)} +DrawInvert :: #force_inline proc "c" (painter_ : ^Painter, bounds_ : Rectangle){ addr := 0x1000 + 95 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Painter, Rectangle)) (fp))(painter_, bounds_)} +DrawStandardIcon :: #force_inline proc "c" (painter_ : ^Painter, id_ : u32 , size_ : i32, region_ : Rectangle, color_ : u32 ) -> bool{ addr := 0x1000 + 115 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Painter, u32 , i32, Rectangle, u32 ) -> bool) (fp))(painter_, id_, size_, region_, color_)} +DrawStyledBox :: #force_inline proc "c" (painter_ : ^Painter, box_ : StyledBox){ addr := 0x1000 + 116 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Painter, StyledBox)) (fp))(painter_, box_)} +DrawPaintTarget :: #force_inline proc "c" (painter_ : ^Painter, source_ : ^PaintTarget, destinationRegion_ : Rectangle, sourceRegion_ : Rectangle, borderRegion_ : Rectangle, mode_ : DrawMode, alpha_ : u16 , clipRegion_ : Rectangle){ addr := 0x1000 + 263 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Painter, ^PaintTarget, Rectangle, Rectangle, Rectangle, DrawMode, u16 , Rectangle)) (fp))(painter_, source_, destinationRegion_, sourceRegion_, borderRegion_, mode_, alpha_, clipRegion_)} +DrawText :: #force_inline proc "c" (painter_ : ^Painter, plan_ : ^TextPlan, bounds_ : Rectangle, clip_ : ^Rectangle = nil, selectionProperties_ : ^TextSelection = nil){ addr := 0x1000 + 120 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Painter, ^TextPlan, Rectangle, ^Rectangle, ^TextSelection)) (fp))(painter_, plan_, bounds_, clip_, selectionProperties_)} +FontGetName :: #force_inline proc "c" (family_ : u16 , buffer_ : ^i8 , bufferBytes_ : int ) -> int { addr := 0x1000 + 121 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( u16 , ^i8 , int ) -> int ) (fp))(family_, buffer_, bufferBytes_)} +IconIDFromString :: #force_inline proc "c" (string_ : string = "") -> u32 { addr := 0x1000 + 127 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int) -> u32 ) (fp))(raw_data(string_), len(string_))} +ImageLoad :: #force_inline proc "c" (file_ : ^u8 , fileSize_ : int , width_ : ^u32 , height_ : ^u32 , imageChannels_ : i32) -> ^u8 { addr := 0x1000 + 165 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^u8 , int , ^u32 , ^u32 , i32) -> ^u8 ) (fp))(file_, fileSize_, width_, height_, imageChannels_)} +PainterBoundsClient :: #force_inline proc "c" (painter_ : ^Painter) -> Rectangle{ addr := 0x1000 + 122 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Painter) -> Rectangle) (fp))(painter_)} +PainterBoundsInset :: #force_inline proc "c" (painter_ : ^Painter) -> Rectangle{ addr := 0x1000 + 123 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Painter) -> Rectangle) (fp))(painter_)} +PainterDrawStandardContent :: #force_inline proc "c" (painter_ : ^Painter, text_ : string, iconID_ : u32 ){ addr := 0x1000 + 117 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Painter, ^u8, int, u32 )) (fp))(painter_, raw_data(text_), len(text_), iconID_)} +PaintTargetClear :: #force_inline proc "c" (target_ : ^PaintTarget){ addr := 0x1000 + 34 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^PaintTarget)) (fp))(target_)} +PaintTargetEndDirectAccess :: #force_inline proc "c" (target_ : ^PaintTarget){ addr := 0x1000 + 264 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^PaintTarget)) (fp))(target_)} +PaintTargetStartDirectAccess :: #force_inline proc "c" (target_ : ^PaintTarget, bits_ : ^^u32 , width_ : ^int , height_ : ^int , stride_ : ^int ){ addr := 0x1000 + 276 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^PaintTarget, ^^u32 , ^int , ^int , ^int )) (fp))(target_, bits_, width_, height_, stride_)} +PaintTargetTake :: #force_inline proc "c" (target_ : ^PaintTarget, width_ : int , height_ : int ) -> bool{ addr := 0x1000 + 35 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^PaintTarget, int , int ) -> bool) (fp))(target_, width_, height_)} +PaintTargetReturn :: #force_inline proc "c" (target_ : ^PaintTarget){ addr := 0x1000 + 36 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^PaintTarget)) (fp))(target_)} +TextGetLineHeight :: #force_inline proc "c" (font_ : Font, size_ : u16 ) -> i32{ addr := 0x1000 + 157 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Font, u16 ) -> i32) (fp))(font_, size_)} +TextGetPartialStringWidth :: #force_inline proc "c" (font_ : Font, size_ : u16 , fullString_ : ^i8 , fullStringBytes_ : int , measureOffset_ : uint , measureBytes_ : int ) -> i32{ addr := 0x1000 + 171 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Font, u16 , ^i8 , int , uint , int ) -> i32) (fp))(font_, size_, fullString_, fullStringBytes_, measureOffset_, measureBytes_)} +TextGetCharacterAtPoint :: #force_inline proc "c" (font_ : Font, size_ : u16 , fullString_ : ^i8 , fullStringBytes_ : int , measureOffset_ : uint , measureBytes_ : int , pointX_ : ^i32, reverse_ : bool, middle_ : bool) -> int { addr := 0x1000 + 237 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Font, u16 , ^i8 , int , uint , int , ^i32, bool, bool) -> int ) (fp))(font_, size_, fullString_, fullStringBytes_, measureOffset_, measureBytes_, pointX_, reverse_, middle_)} +AudioStreamClose :: #force_inline proc "c" (stream_ : ^AudioStream){ addr := 0x1000 + 128 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^AudioStream)) (fp))(stream_)} +AudioStreamOpen :: #force_inline proc "c" (device_ : AudioDeviceID, bufferLengthUs_ : int ) -> ^AudioStream{ addr := 0x1000 + 129 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( AudioDeviceID, int ) -> ^AudioStream) (fp))(device_, bufferLengthUs_)} +AudioStreamNotify :: #force_inline proc "c" (stream_ : ^AudioStream){ addr := 0x1000 + 130 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^AudioStream)) (fp))(stream_)} +AudioStreamSend :: #force_inline proc "c" (destination_ : ^AudioStream, source_ : ^AudioStream, time_ : ^f64 ) -> Error{ addr := 0x1000 + 131 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^AudioStream, ^AudioStream, ^f64 ) -> Error) (fp))(destination_, source_, time_)} +AddressResolve :: #force_inline proc "c" (domain_ : string, flags_ : u32 , address_ : ^Address) -> Error{ addr := 0x1000 + 99 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, u32 , ^Address) -> Error) (fp))(raw_data(domain_), len(domain_), flags_, address_)} +ConnectionClose :: #force_inline proc "c" (connection_ : ^Connection){ addr := 0x1000 + 321 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Connection)) (fp))(connection_)} +ConnectionNotify :: #force_inline proc "c" (connection_ : ^Connection){ addr := 0x1000 + 322 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Connection)) (fp))(connection_)} +ConnectionOpen :: #force_inline proc "c" (connection_ : ^Connection, flags_ : u32 ) -> Error{ addr := 0x1000 + 100 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Connection, u32 ) -> Error) (fp))(connection_, flags_)} +ConnectionPoll :: #force_inline proc "c" (connection_ : ^Connection){ addr := 0x1000 + 303 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Connection)) (fp))(connection_)} +ConnectionRead :: #force_inline proc "c" (connection_ : ^Connection, buffer_ : rawptr, bufferBytes_ : int , bytesRead_ : ^int ) -> Error{ addr := 0x1000 + 325 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Connection, rawptr, int , ^int ) -> Error) (fp))(connection_, buffer_, bufferBytes_, bytesRead_)} +ConnectionWriteSync :: #force_inline proc "c" (connection_ : ^Connection, data_ : rawptr, dataBytes_ : int ) -> Error{ addr := 0x1000 + 323 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Connection, rawptr, int ) -> Error) (fp))(connection_, data_, dataBytes_)} +GameControllerStatePoll :: #force_inline proc "c" (buffer_ : ^GameControllerState) -> int { addr := 0x1000 + 299 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^GameControllerState) -> int ) (fp))(buffer_)} +EventCreate :: #force_inline proc "c" (autoReset_ : bool) -> Handle{ addr := 0x1000 + 132 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( bool) -> Handle) (fp))(autoReset_)} +EventForward :: #force_inline proc "c" (event_ : Handle, eventSink_ : Handle, data_ : Generic){ addr := 0x1000 + 133 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( Handle, Handle, Generic)) (fp))(event_, eventSink_, data_)} +EventPoll :: #force_inline proc "c" (event_ : Handle) -> Error{ addr := 0x1000 + 134 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle) -> Error) (fp))(event_)} +EventReset :: #force_inline proc "c" (event_ : Handle){ addr := 0x1000 + 135 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( Handle)) (fp))(event_)} +EventSet :: #force_inline proc "c" (event_ : Handle){ addr := 0x1000 + 136 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( Handle)) (fp))(event_)} +EventSinkCreate :: #force_inline proc "c" (ignoreDuplicates_ : bool) -> Handle{ addr := 0x1000 + 137 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( bool) -> Handle) (fp))(ignoreDuplicates_)} +EventSinkPop :: #force_inline proc "c" (eventSink_ : Handle, data_ : ^Generic) -> Error{ addr := 0x1000 + 138 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, ^Generic) -> Error) (fp))(eventSink_, data_)} +EventSinkPush :: #force_inline proc "c" (eventSink_ : Handle, data_ : Generic) -> Error{ addr := 0x1000 + 139 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( Handle, Generic) -> Error) (fp))(eventSink_, data_)} +MutexAcquire :: #force_inline proc "c" (mutex_ : ^Mutex){ addr := 0x1000 + 140 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Mutex)) (fp))(mutex_)} +MutexDestroy :: #force_inline proc "c" (mutex_ : ^Mutex){ addr := 0x1000 + 141 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Mutex)) (fp))(mutex_)} +MutexRelease :: #force_inline proc "c" (mutex_ : ^Mutex){ addr := 0x1000 + 142 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Mutex)) (fp))(mutex_)} +PerformanceTimerPush :: #force_inline proc "c" (){ addr := 0x1000 + 143 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ()) (fp))()} +PerformanceTimerPop :: #force_inline proc "c" () -> f64 { addr := 0x1000 + 144 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" () -> f64 ) (fp))()} +SchedulerYield :: #force_inline proc "c" (){ addr := 0x1000 + 145 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ()) (fp))()} +Sleep :: #force_inline proc "c" (milliseconds_ : u64 ){ addr := 0x1000 + 146 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( u64 )) (fp))(milliseconds_)} +SpinlockAcquire :: #force_inline proc "c" (spinlock_ : ^Spinlock){ addr := 0x1000 + 147 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Spinlock)) (fp))(spinlock_)} +SpinlockRelease :: #force_inline proc "c" (spinlock_ : ^Spinlock){ addr := 0x1000 + 148 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Spinlock)) (fp))(spinlock_)} +TimerCreate :: #force_inline proc "c" () -> Handle{ addr := 0x1000 + 149 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" () -> Handle) (fp))()} +TimerSet :: #force_inline proc "c" (handle_ : Handle, afterMs_ : u64 , callback_ : TimerCallbackFunction, argument_ : Generic){ addr := 0x1000 + 150 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( Handle, u64 , TimerCallbackFunction, Generic)) (fp))(handle_, afterMs_, callback_, argument_)} +Wait :: #force_inline proc "c" (objects_ : ^Handle, objectCount_ : int , timeoutMs_ : uint ) -> uint { addr := 0x1000 + 151 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Handle, int , uint ) -> uint ) (fp))(objects_, objectCount_, timeoutMs_)} +CStringLength :: #force_inline proc "c" (string_ : cstring ) -> int { addr := 0x1000 + 152 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( cstring ) -> int ) (fp))(string_)} +StringAllocateAndFormat :: #force_inline proc "c" (bytes_ : ^int , format_ : cstring , _varargs_ : ..any) -> ^i8 { addr := 0x1000 + 153 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^int , cstring , ..any) -> ^i8 ) (fp))(bytes_, format_, _varargs_)} +StringCompare :: #force_inline proc "c" (s1_ : string, s2_ : string) -> i32{ addr := 0x1000 + 155 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, ^u8, int) -> i32) (fp))(raw_data(s1_), len(s1_), raw_data(s2_), len(s2_))} +StringCompareRaw :: #force_inline proc "c" (s1_ : string, s2_ : string) -> i32{ addr := 0x1000 + 156 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, ^u8, int) -> i32) (fp))(raw_data(s1_), len(s1_), raw_data(s2_), len(s2_))} +StringFormat :: #force_inline proc "c" (buffer_ : ^i8 , bufferLength_ : int , format_ : cstring , _varargs_ : ..any) -> int { addr := 0x1000 + 158 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , int , cstring , ..any) -> int ) (fp))(buffer_, bufferLength_, format_, _varargs_)} +StringFormatTemporary :: #force_inline proc "c" (format_ : cstring , _varargs_ : ..any) -> ^i8 { addr := 0x1000 + 159 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( cstring , ..any) -> ^i8 ) (fp))(format_, _varargs_)} +StringFormatAppend :: #force_inline proc "c" (buffer_ : ^i8 , bufferLength_ : int , bufferPosition_ : ^int , format_ : cstring , _varargs_ : ..any) -> bool{ addr := 0x1000 + 161 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , int , ^int , cstring , ..any) -> bool) (fp))(buffer_, bufferLength_, bufferPosition_, format_, _varargs_)} +StringLength :: #force_inline proc "c" (string_ : ^i8 , end_ : u8 ) -> int { addr := 0x1000 + 163 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , u8 ) -> int ) (fp))(string_, end_)} +StringStartsWith :: #force_inline proc "c" (string_ : string, prefix_ : string, caseInsensitive_ : bool) -> bool{ addr := 0x1000 + 166 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int, ^u8, int, bool) -> bool) (fp))(raw_data(string_), len(string_), raw_data(prefix_), len(prefix_), caseInsensitive_)} +StringZeroTerminate :: #force_inline proc "c" (string_ : string) -> ^i8 { addr := 0x1000 + 167 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" (^u8, int) -> ^i8 ) (fp))(raw_data(string_), len(string_))} +CRTabs :: #force_inline proc "c" (n_ : i32) -> i32{ addr := 0x1000 + 168 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( i32) -> i32) (fp))(n_)} +CRTacosf :: #force_inline proc "c" (x_ : f32 ) -> f32 { addr := 0x1000 + 169 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 ) -> f32 ) (fp))(x_)} +CRTasinf :: #force_inline proc "c" (x_ : f32 ) -> f32 { addr := 0x1000 + 170 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 ) -> f32 ) (fp))(x_)} +CRTatan2f :: #force_inline proc "c" (y_ : f32 , x_ : f32 ) -> f32 { addr := 0x1000 + 172 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 , f32 ) -> f32 ) (fp))(y_, x_)} +CRTatanf :: #force_inline proc "c" (x_ : f32 ) -> f32 { addr := 0x1000 + 173 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 ) -> f32 ) (fp))(x_)} +CRTatoi :: #force_inline proc "c" (string_ : ^i8 ) -> i32{ addr := 0x1000 + 174 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 ) -> i32) (fp))(string_)} +CRTcalloc :: #force_inline proc "c" (num_ : int , size_ : int ) -> rawptr{ addr := 0x1000 + 175 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( int , int ) -> rawptr) (fp))(num_, size_)} +CRTceil :: #force_inline proc "c" (x_ : f64 ) -> f64 { addr := 0x1000 + 176 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f64 ) -> f64 ) (fp))(x_)} +CRTceilf :: #force_inline proc "c" (x_ : f32 ) -> f32 { addr := 0x1000 + 177 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 ) -> f32 ) (fp))(x_)} +CRTcosf :: #force_inline proc "c" (x_ : f32 ) -> f32 { addr := 0x1000 + 178 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 ) -> f32 ) (fp))(x_)} +CRTexp :: #force_inline proc "c" (x_ : f64 ) -> f64 { addr := 0x1000 + 326 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f64 ) -> f64 ) (fp))(x_)} +CRTexp2f :: #force_inline proc "c" (x_ : f32 ) -> f32 { addr := 0x1000 + 327 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 ) -> f32 ) (fp))(x_)} +CRTfabs :: #force_inline proc "c" (x_ : f64 ) -> f64 { addr := 0x1000 + 179 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f64 ) -> f64 ) (fp))(x_)} +CRTfabsf :: #force_inline proc "c" (x_ : f32 ) -> f32 { addr := 0x1000 + 344 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 ) -> f32 ) (fp))(x_)} +CRTfloor :: #force_inline proc "c" (x_ : f64 ) -> f64 { addr := 0x1000 + 181 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f64 ) -> f64 ) (fp))(x_)} +CRTfloorf :: #force_inline proc "c" (x_ : f32 ) -> f32 { addr := 0x1000 + 182 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 ) -> f32 ) (fp))(x_)} +CRTfmodf :: #force_inline proc "c" (x_ : f32 , y_ : f32 ) -> f32 { addr := 0x1000 + 183 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 , f32 ) -> f32 ) (fp))(x_, y_)} +CRTfree :: #force_inline proc "c" (ptr_ : rawptr){ addr := 0x1000 + 184 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( rawptr)) (fp))(ptr_)} +CRTgetenv :: #force_inline proc "c" (name_ : ^i8 ) -> ^i8 { addr := 0x1000 + 185 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 ) -> ^i8 ) (fp))(name_)} +CRTisalpha :: #force_inline proc "c" (c_ : i32) -> i32{ addr := 0x1000 + 186 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( i32) -> i32) (fp))(c_)} +CRTisdigit :: #force_inline proc "c" (c_ : i32) -> i32{ addr := 0x1000 + 187 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( i32) -> i32) (fp))(c_)} +CRTisnanf :: #force_inline proc "c" (f_ : f32 ) -> bool{ addr := 0x1000 + 345 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 ) -> bool) (fp))(f_)} +CRTisspace :: #force_inline proc "c" (c_ : i32) -> i32{ addr := 0x1000 + 188 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( i32) -> i32) (fp))(c_)} +CRTisupper :: #force_inline proc "c" (c_ : i32) -> i32{ addr := 0x1000 + 189 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( i32) -> i32) (fp))(c_)} +CRTisxdigit :: #force_inline proc "c" (c_ : i32) -> i32{ addr := 0x1000 + 190 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( i32) -> i32) (fp))(c_)} +CRTmalloc :: #force_inline proc "c" (size_ : int ) -> rawptr{ addr := 0x1000 + 191 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( int ) -> rawptr) (fp))(size_)} +CRTmemchr :: #force_inline proc "c" (_s_ : rawptr, _c_ : i32, n_ : int ) -> rawptr{ addr := 0x1000 + 192 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( rawptr, i32, int ) -> rawptr) (fp))(_s_, _c_, n_)} +CRTmemcmp :: #force_inline proc "c" (s1_ : rawptr, s2_ : rawptr, n_ : int ) -> i32{ addr := 0x1000 + 193 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( rawptr, rawptr, int ) -> i32) (fp))(s1_, s2_, n_)} +CRTmemcpy :: #force_inline proc "c" (dest_ : rawptr, src_ : rawptr, n_ : int ) -> rawptr{ addr := 0x1000 + 194 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( rawptr, rawptr, int ) -> rawptr) (fp))(dest_, src_, n_)} +CRTmemmove :: #force_inline proc "c" (dest_ : rawptr, src_ : rawptr, n_ : int ) -> rawptr{ addr := 0x1000 + 195 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( rawptr, rawptr, int ) -> rawptr) (fp))(dest_, src_, n_)} +CRTmemset :: #force_inline proc "c" (s_ : rawptr, c_ : i32, n_ : int ) -> rawptr{ addr := 0x1000 + 196 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( rawptr, i32, int ) -> rawptr) (fp))(s_, c_, n_)} +CRTpowf :: #force_inline proc "c" (x_ : f32 , y_ : f32 ) -> f32 { addr := 0x1000 + 328 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 , f32 ) -> f32 ) (fp))(x_, y_)} +CRTqsort :: #force_inline proc "c" (_base_ : rawptr, nmemb_ : int , size_ : int , compar_ : CRTComparisonCallback){ addr := 0x1000 + 197 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( rawptr, int , int , CRTComparisonCallback)) (fp))(_base_, nmemb_, size_, compar_)} +CRTrealloc :: #force_inline proc "c" (ptr_ : rawptr, size_ : int ) -> rawptr{ addr := 0x1000 + 198 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( rawptr, int ) -> rawptr) (fp))(ptr_, size_)} +CRTsinf :: #force_inline proc "c" (x_ : f32 ) -> f32 { addr := 0x1000 + 199 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 ) -> f32 ) (fp))(x_)} +CRTsnprintf :: #force_inline proc "c" (buffer_ : ^i8 , bufferSize_ : int , format_ : ^i8 , _varargs_ : ..any) -> i32{ addr := 0x1000 + 200 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , int , ^i8 , ..any) -> i32) (fp))(buffer_, bufferSize_, format_, _varargs_)} +CRTsprintf :: #force_inline proc "c" (buffer_ : ^i8 , format_ : ^i8 , _varargs_ : ..any) -> i32{ addr := 0x1000 + 201 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , ^i8 , ..any) -> i32) (fp))(buffer_, format_, _varargs_)} +CRTsqrt :: #force_inline proc "c" (x_ : f64 ) -> f64 { addr := 0x1000 + 202 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f64 ) -> f64 ) (fp))(x_)} +CRTsqrtf :: #force_inline proc "c" (x_ : f32 ) -> f32 { addr := 0x1000 + 203 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( f32 ) -> f32 ) (fp))(x_)} +CRTstrcat :: #force_inline proc "c" (dest_ : ^i8 , src_ : ^i8 ) -> ^i8 { addr := 0x1000 + 205 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , ^i8 ) -> ^i8 ) (fp))(dest_, src_)} +CRTstrchr :: #force_inline proc "c" (s_ : ^i8 , c_ : i32) -> ^i8 { addr := 0x1000 + 206 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , i32) -> ^i8 ) (fp))(s_, c_)} +CRTstrcmp :: #force_inline proc "c" (s1_ : ^i8 , s2_ : ^i8 ) -> i32{ addr := 0x1000 + 207 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , ^i8 ) -> i32) (fp))(s1_, s2_)} +CRTstrcpy :: #force_inline proc "c" (dest_ : ^i8 , src_ : ^i8 ) -> ^i8 { addr := 0x1000 + 208 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , ^i8 ) -> ^i8 ) (fp))(dest_, src_)} +CRTstrdup :: #force_inline proc "c" (string_ : ^i8 ) -> ^i8 { addr := 0x1000 + 70 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 ) -> ^i8 ) (fp))(string_)} +CRTstrerror :: #force_inline proc "c" (errnum_ : i32) -> ^i8 { addr := 0x1000 + 209 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( i32) -> ^i8 ) (fp))(errnum_)} +CRTstrlen :: #force_inline proc "c" (s_ : ^i8 ) -> int { addr := 0x1000 + 210 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 ) -> int ) (fp))(s_)} +CRTstrncmp :: #force_inline proc "c" (s1_ : ^i8 , s2_ : ^i8 , n_ : int ) -> i32{ addr := 0x1000 + 211 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , ^i8 , int ) -> i32) (fp))(s1_, s2_, n_)} +CRTstrncpy :: #force_inline proc "c" (dest_ : ^i8 , src_ : ^i8 , n_ : int ) -> ^i8 { addr := 0x1000 + 212 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , ^i8 , int ) -> ^i8 ) (fp))(dest_, src_, n_)} +CRTstrnlen :: #force_inline proc "c" (s_ : ^i8 , maxlen_ : int ) -> int { addr := 0x1000 + 213 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , int ) -> int ) (fp))(s_, maxlen_)} +CRTstrstr :: #force_inline proc "c" (haystack_ : ^i8 , needle_ : ^i8 ) -> ^i8 { addr := 0x1000 + 214 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , ^i8 ) -> ^i8 ) (fp))(haystack_, needle_)} +CRTstrtol :: #force_inline proc "c" (nptr_ : ^i8 , endptr_ : ^^i8 , base_ : i32) -> i64 { addr := 0x1000 + 215 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , ^^i8 , i32) -> i64 ) (fp))(nptr_, endptr_, base_)} +CRTstrtoul :: #force_inline proc "c" (nptr_ : ^i8 , endptr_ : ^^i8 , base_ : i32) -> u64 { addr := 0x1000 + 216 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^i8 , ^^i8 , i32) -> u64 ) (fp))(nptr_, endptr_, base_)} +CRTtolower :: #force_inline proc "c" (c_ : i32) -> i32{ addr := 0x1000 + 217 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( i32) -> i32) (fp))(c_)} +NewPanel :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, cStyle_ : cstring = nil) -> ^Panel{ addr := 0x1000 + 219 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , cstring ) -> ^Panel) (fp))(parent_, flags_, cStyle_)} +NewSplitter :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, cStyle_ : cstring = nil) -> ^Splitter{ addr := 0x1000 + 220 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , cstring ) -> ^Splitter) (fp))(parent_, flags_, cStyle_)} +NewButton :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, cStyle_ : cstring = nil, label_ : string = "") -> ^Button{ addr := 0x1000 + 221 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , cstring , ^u8, int) -> ^Button) (fp))(parent_, flags_, cStyle_, raw_data(label_), len(label_))} +NewChoice :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, cStyle_ : cstring = nil) -> ^Choice{ addr := 0x1000 + 222 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , cstring ) -> ^Choice) (fp))(parent_, flags_, cStyle_)} +NewColorWell :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, cStyle_ : cstring = nil, colorRGB_ : u32 = 0) -> ^ColorWell{ addr := 0x1000 + 223 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , cstring , u32 ) -> ^ColorWell) (fp))(parent_, flags_, cStyle_, colorRGB_)} +NewTextDisplay :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, cStyle_ : cstring = nil, label_ : string = "") -> ^TextDisplay{ addr := 0x1000 + 224 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , cstring , ^u8, int) -> ^TextDisplay) (fp))(parent_, flags_, cStyle_, raw_data(label_), len(label_))} +NewIconDisplay :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, cStyle_ : cstring = nil, iconID_ : u32 = 0) -> ^IconDisplay{ addr := 0x1000 + 225 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , cstring , u32 ) -> ^IconDisplay) (fp))(parent_, flags_, cStyle_, iconID_)} +NewImageDisplay :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, cStyle_ : cstring = nil) -> ^ImageDisplay{ addr := 0x1000 + 226 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , cstring ) -> ^ImageDisplay) (fp))(parent_, flags_, cStyle_)} +NewListView :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, cStyle_ : cstring = nil, cItemStyle_ : cstring = nil, cHeaderItemStyle_ : cstring = nil, cFooterItemStyle_ : cstring = nil) -> ^ListView{ addr := 0x1000 + 227 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , cstring , cstring , cstring , cstring ) -> ^ListView) (fp))(parent_, flags_, cStyle_, cItemStyle_, cHeaderItemStyle_, cFooterItemStyle_)} +NewTextbox :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, cStyle_ : cstring = nil) -> ^Textbox{ addr := 0x1000 + 228 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , cstring ) -> ^Textbox) (fp))(parent_, flags_, cStyle_)} +NewCustomElement :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, cStyle_ : cstring = nil) -> ^Element{ addr := 0x1000 + 229 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , cstring ) -> ^Element) (fp))(parent_, flags_, cStyle_)} +NewWindow :: #force_inline proc "c" (instance_ : ^INSTANCE_TYPE, style_ : WindowStyle) -> ^Window{ addr := 0x1000 + 230 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^INSTANCE_TYPE, WindowStyle) -> ^Window) (fp))(instance_, style_)} +NewMenu :: #force_inline proc "c" (source_ : ^Element, flags_ : u64 = FLAGS_DEFAULT, userCallback_ : MenuCallbackFunction = nil, _context_ : Generic = nil, fixedWidth_ : i32 = 0, fixedHeight_ : i32 = 0) -> ^Menu{ addr := 0x1000 + 231 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 , MenuCallbackFunction, Generic, i32, i32) -> ^Menu) (fp))(source_, flags_, userCallback_, _context_, fixedWidth_, fixedHeight_)} +NewMenuItem :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 , label_ : string = "", callback_ : MenuCallbackFunction = nil, _context_ : Generic = nil){ addr := 0x1000 + 232 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element, u64 , ^u8, int, MenuCallbackFunction, Generic)) (fp))(parent_, flags_, raw_data(label_), len(label_), callback_, _context_)} +NewMenuCommand :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 , label_ : string, command_ : ^Command){ addr := 0x1000 + 233 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element, u64 , ^u8, int, ^Command)) (fp))(parent_, flags_, raw_data(label_), len(label_), command_)} +NewMenuColumn :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 = FLAGS_DEFAULT) -> ^Element{ addr := 0x1000 + 234 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 ) -> ^Element) (fp))(parent_, flags_)} +NewMenuSeparator :: #force_inline proc "c" (parent_ : ^Element){ addr := 0x1000 + 235 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element)) (fp))(parent_)} +NewSpacer :: #force_inline proc "c" (parent_ : ^Element, flags_ : u64 ) -> ^Element{ addr := 0x1000 + 236 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element, u64 ) -> ^Element) (fp))(parent_, flags_)} +NewTextPlan :: #force_inline proc "c" (properties_ : ^TextDisplayProperties, bounds_ : Rectangle, string_ : ^i8 , textRuns_ : ^TextRun, textRunCount_ : int , singleUse_ : bool) -> ^TextPlan{ addr := 0x1000 + 238 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^TextDisplayProperties, Rectangle, ^i8 , ^TextRun, int , bool) -> ^TextPlan) (fp))(properties_, bounds_, string_, textRuns_, textRunCount_, singleUse_)} +ElementDraw :: #force_inline proc "c" (element_ : ^Element, painter_ : ^Painter){ addr := 0x1000 + 240 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element, ^Painter)) (fp))(element_, painter_)} +ElementFocus :: #force_inline proc "c" (element_ : ^Element, ensureVisible_ : bool = false){ addr := 0x1000 + 243 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element, bool)) (fp))(element_, ensureVisible_)} +ElementSetDisabled :: #force_inline proc "c" (element_ : ^Element, disabled_ : bool = true){ addr := 0x1000 + 244 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element, bool)) (fp))(element_, disabled_)} +ElementSetHidden :: #force_inline proc "c" (element_ : ^Element, hidden_ : bool = true){ addr := 0x1000 + 245 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element, bool)) (fp))(element_, hidden_)} +ElementSetCallback :: #force_inline proc "c" (element_ : ^Element, callback_ : UICallbackFunction){ addr := 0x1000 + 246 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element, UICallbackFunction)) (fp))(element_, callback_)} +ElementGetSize :: #force_inline proc "c" (element_ : ^Element, width_ : ^i32, height_ : ^i32){ addr := 0x1000 + 247 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element, ^i32, ^i32)) (fp))(element_, width_, height_)} +ElementRepaint :: #force_inline proc "c" (element_ : ^Element, all_ : bool, region_ : Rectangle){ addr := 0x1000 + 248 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element, bool, Rectangle)) (fp))(element_, all_, region_)} +ElementSetCellRange :: #force_inline proc "c" (element_ : ^Element, xFrom_ : i32, yFrom_ : i32, xTo_ : i32 = -1, yTo_ : i32 = -1){ addr := 0x1000 + 251 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element, i32, i32, i32, i32)) (fp))(element_, xFrom_, yFrom_, xTo_, yTo_)} +ElementGetInsets :: #force_inline proc "c" (element_ : ^Element) -> Rectangle{ addr := 0x1000 + 253 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element) -> Rectangle) (fp))(element_)} +ElementGetInsetSize :: #force_inline proc "c" (element_ : ^Element) -> Rectangle{ addr := 0x1000 + 254 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element) -> Rectangle) (fp))(element_)} +ElementGetMetrics :: #force_inline proc "c" (element_ : ^Element) -> ThemeMetrics{ addr := 0x1000 + 105 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element) -> ThemeMetrics) (fp))(element_)} +ElementGetPreferredSize :: #force_inline proc "c" (element_ : ^Element) -> Rectangle{ addr := 0x1000 + 255 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element) -> Rectangle) (fp))(element_)} +ElementMove :: #force_inline proc "c" (element_ : ^Element, x_ : i32, y_ : i32, width_ : i32, height_ : i32){ addr := 0x1000 + 256 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element, i32, i32, i32, i32)) (fp))(element_, x_, y_, width_, height_)} +ElementGetLayoutParent :: #force_inline proc "c" (element_ : ^Element) -> ^Element{ addr := 0x1000 + 257 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element) -> ^Element) (fp))(element_)} +ElementDestroy :: #force_inline proc "c" (element_ : ^Element){ addr := 0x1000 + 258 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element)) (fp))(element_)} +ElementDestroyContents :: #force_inline proc "c" (element_ : ^Element){ addr := 0x1000 + 259 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Element)) (fp))(element_)} +ElementStartAnimating :: #force_inline proc "c" (element_ : ^Element) -> bool{ addr := 0x1000 + 260 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Element) -> bool) (fp))(element_)} +WindowGetBounds :: #force_inline proc "c" (window_ : ^Window) -> Rectangle{ addr := 0x1000 + 265 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Window) -> Rectangle) (fp))(window_)} +WindowGetToolbar :: #force_inline proc "c" (window_ : ^Window) -> ^Element{ addr := 0x1000 + 266 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Window) -> ^Element) (fp))(window_)} +WindowSetIcon :: #force_inline proc "c" (window_ : ^Window, iconID_ : u32 ){ addr := 0x1000 + 267 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Window, u32 )) (fp))(window_, iconID_)} +WindowSetTitle :: #force_inline proc "c" (window_ : ^Window, title_ : string = ""){ addr := 0x1000 + 268 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Window, ^u8, int)) (fp))(window_, raw_data(title_), len(title_))} +MenuGetSource :: #force_inline proc "c" (menu_ : ^Menu) -> ^Element{ addr := 0x1000 + 269 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Menu) -> ^Element) (fp))(menu_)} +ButtonSetIcon :: #force_inline proc "c" (button_ : ^Button, iconID_ : u32 ){ addr := 0x1000 + 270 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Button, u32 )) (fp))(button_, iconID_)} +ButtonSetCheck :: #force_inline proc "c" (button_ : ^Button, checkState_ : CheckState = CHECK_CHECKED, sendUpdatedMessage_ : bool = true){ addr := 0x1000 + 271 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Button, CheckState, bool)) (fp))(button_, checkState_, sendUpdatedMessage_)} +ButtonGetCheck :: #force_inline proc "c" (button_ : ^Button) -> CheckState{ addr := 0x1000 + 272 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Button) -> CheckState) (fp))(button_)} +ButtonOnCommand :: #force_inline proc "c" (button_ : ^Button, callback_ : CommandCallbackFunction, command_ : ^Command = nil){ addr := 0x1000 + 273 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Button, CommandCallbackFunction, ^Command)) (fp))(button_, callback_, command_)} +ButtonSetCheckBuddy :: #force_inline proc "c" (button_ : ^Button, checkBuddy_ : ^Element){ addr := 0x1000 + 274 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Button, ^Element)) (fp))(button_, checkBuddy_)} +ButtonGetCheckBuddy :: #force_inline proc "c" (button_ : ^Button) -> ^Element{ addr := 0x1000 + 275 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Button) -> ^Element) (fp))(button_)} +TextboxFind :: #force_inline proc "c" (textbox_ : ^Textbox, string_ : string, line_ : ^i32 , byte_ : ^i32 , flags_ : u32 ) -> bool{ addr := 0x1000 + 277 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Textbox, ^u8, int, ^i32 , ^i32 , u32 ) -> bool) (fp))(textbox_, raw_data(string_), len(string_), line_, byte_, flags_)} +TextboxInsert :: #force_inline proc "c" (textbox_ : ^Textbox, string_ : string = "", sendUpdatedMessage_ : bool = true){ addr := 0x1000 + 278 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Textbox, ^u8, int, bool)) (fp))(textbox_, raw_data(string_), len(string_), sendUpdatedMessage_)} +TextboxGetContents :: #force_inline proc "c" (textbox_ : ^Textbox, bytes_ : ^int = nil) -> ^i8 { addr := 0x1000 + 279 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Textbox, ^int ) -> ^i8 ) (fp))(textbox_, bytes_)} +TextboxGetLineLength :: #force_inline proc "c" (textbox_ : ^Textbox, line_ : uint = 0) -> int { addr := 0x1000 + 280 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Textbox, uint ) -> int ) (fp))(textbox_, line_)} +TextboxGetSelection :: #force_inline proc "c" (textbox_ : ^Textbox, fromLine_ : ^i32 , fromByte_ : ^i32 , toLine_ : ^i32 , toByte_ : ^i32 ){ addr := 0x1000 + 281 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Textbox, ^i32 , ^i32 , ^i32 , ^i32 )) (fp))(textbox_, fromLine_, fromByte_, toLine_, toByte_)} +TextboxMoveCaret :: #force_inline proc "c" (textbox_ : ^Textbox, line_ : i32 , byte_ : i32 ){ addr := 0x1000 + 282 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Textbox, i32 , i32 )) (fp))(textbox_, line_, byte_)} +TextboxSetSelection :: #force_inline proc "c" (textbox_ : ^Textbox, fromLine_ : i32 , fromByte_ : i32 , toLine_ : i32 , toByte_ : i32 ){ addr := 0x1000 + 283 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Textbox, i32 , i32 , i32 , i32 )) (fp))(textbox_, fromLine_, fromByte_, toLine_, toByte_)} +TextboxSelectAll :: #force_inline proc "c" (textbox_ : ^Textbox){ addr := 0x1000 + 284 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Textbox)) (fp))(textbox_)} +TextboxClear :: #force_inline proc "c" (textbox_ : ^Textbox, sendUpdatedMessage_ : bool){ addr := 0x1000 + 285 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Textbox, bool)) (fp))(textbox_, sendUpdatedMessage_)} +TextboxUseNumberOverlay :: #force_inline proc "c" (textbox_ : ^Textbox, defaultBehaviour_ : bool){ addr := 0x1000 + 286 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Textbox, bool)) (fp))(textbox_, defaultBehaviour_)} +TextboxUseBreadcrumbOverlay :: #force_inline proc "c" (textbox_ : ^Textbox){ addr := 0x1000 + 287 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Textbox)) (fp))(textbox_)} +TextboxMoveCaretRelative :: #force_inline proc "c" (textbox_ : ^Textbox, flags_ : u32 ){ addr := 0x1000 + 118 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Textbox, u32 )) (fp))(textbox_, flags_)} +TextboxEnsureCaretVisible :: #force_inline proc "c" (textbox_ : ^Textbox){ addr := 0x1000 + 119 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Textbox)) (fp))(textbox_)} +PanelSetBands :: #force_inline proc "c" (panel_ : ^Panel, columnCount_ : int , rowCount_ : int = 0, columns_ : ^PanelBand = nil, rows_ : ^PanelBand = nil){ addr := 0x1000 + 288 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Panel, int , int , ^PanelBand, ^PanelBand)) (fp))(panel_, columnCount_, rowCount_, columns_, rows_)} +PanelSwitchTo :: #force_inline proc "c" (panel_ : ^Panel, targetChild_ : ^Element, transitionType_ : TransitionType, destroyPreviousAfterTransitionCompletes_ : bool = false){ addr := 0x1000 + 289 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Panel, ^Element, TransitionType, bool)) (fp))(panel_, targetChild_, transitionType_, destroyPreviousAfterTransitionCompletes_)} +TextPlanGetWidth :: #force_inline proc "c" (plan_ : ^TextPlan) -> i32{ addr := 0x1000 + 290 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^TextPlan) -> i32) (fp))(plan_)} +TextPlanGetHeight :: #force_inline proc "c" (plan_ : ^TextPlan) -> i32{ addr := 0x1000 + 291 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^TextPlan) -> i32) (fp))(plan_)} +TextPlanGetLineCount :: #force_inline proc "c" (plan_ : ^TextPlan) -> int { addr := 0x1000 + 292 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^TextPlan) -> int ) (fp))(plan_)} +TextPlanDestroy :: #force_inline proc "c" (plan_ : ^TextPlan){ addr := 0x1000 + 293 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^TextPlan)) (fp))(plan_)} +TextDisplaySetContents :: #force_inline proc "c" (display_ : ^TextDisplay, contents_ : string = ""){ addr := 0x1000 + 294 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^TextDisplay, ^u8, int)) (fp))(display_, raw_data(contents_), len(contents_))} +ColorWellGetRGB :: #force_inline proc "c" (well_ : ^ColorWell) -> u32 { addr := 0x1000 + 304 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^ColorWell) -> u32 ) (fp))(well_)} +ColorWellSetRGB :: #force_inline proc "c" (well_ : ^ColorWell, colorRGB_ : u32 , sendChangedMessage_ : bool){ addr := 0x1000 + 305 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^ColorWell, u32 , bool)) (fp))(well_, colorRGB_, sendChangedMessage_)} +ColorWellSetIndeterminate :: #force_inline proc "c" (well_ : ^ColorWell){ addr := 0x1000 + 306 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^ColorWell)) (fp))(well_)} +ChoiceAddMenuItem :: #force_inline proc "c" (choice_ : ^Choice, item_ : Generic){ addr := 0x1000 + 307 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Choice, Generic)) (fp))(choice_, item_)} +ChoiceSetItem :: #force_inline proc "c" (choice_ : ^Choice, item_ : Generic, sendUpdatedMessage_ : bool = true){ addr := 0x1000 + 308 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^Choice, Generic, bool)) (fp))(choice_, item_, sendUpdatedMessage_)} +ChoiceGetItem :: #force_inline proc "c" (choice_ : ^Choice) -> Generic{ addr := 0x1000 + 309 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^Choice) -> Generic) (fp))(choice_)} +ListViewInsertGroup :: #force_inline proc "c" (view_ : ^ListView, group_ : i32 , flags_ : u32 = FLAGS_DEFAULT){ addr := 0x1000 + 310 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^ListView, i32 , u32 )) (fp))(view_, group_, flags_)} +ListViewInsert :: #force_inline proc "c" (view_ : ^ListView, group_ : i32 , firstIndex_ : Generic, lastIndex_ : Generic, count_ : i64 = -1){ addr := 0x1000 + 311 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^ListView, i32 , Generic, Generic, i64 )) (fp))(view_, group_, firstIndex_, lastIndex_, count_)} +ListViewRemove :: #force_inline proc "c" (view_ : ^ListView, group_ : i32 , firstIndex_ : Generic, lastIndex_ : Generic, count_ : i64 = -1){ addr := 0x1000 + 312 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^ListView, i32 , Generic, Generic, i64 )) (fp))(view_, group_, firstIndex_, lastIndex_, count_)} +ListViewRemoveAll :: #force_inline proc "c" (view_ : ^ListView, group_ : i32 ){ addr := 0x1000 + 313 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^ListView, i32 )) (fp))(view_, group_)} +ListViewGetIndex :: #force_inline proc "c" (view_ : ^ListView, item_ : ^Element) -> Generic{ addr := 0x1000 + 314 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); return ((proc "c" ( ^ListView, ^Element) -> Generic) (fp))(view_, item_)} +ListViewSetColumns :: #force_inline proc "c" (view_ : ^ListView, columns_ : ^ListViewColumn, columnCount_ : int ){ addr := 0x1000 + 315 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^ListView, ^ListViewColumn, int )) (fp))(view_, columns_, columnCount_)} +ListViewSetEmptyMessage :: #force_inline proc "c" (view_ : ^ListView, message_ : string = ""){ addr := 0x1000 + 262 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^ListView, ^u8, int)) (fp))(view_, raw_data(message_), len(message_))} +ListViewSelect :: #force_inline proc "c" (view_ : ^ListView, group_ : i32 , index_ : Generic){ addr := 0x1000 + 316 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^ListView, i32 , Generic)) (fp))(view_, group_, index_)} +ListViewResetSearchBuffer :: #force_inline proc "c" (view_ : ^ListView){ addr := 0x1000 + 317 * size_of(int); fp := (rawptr(((^uintptr)(uintptr(addr)))^)); ((proc "c" ( ^ListView)) (fp))(view_)} diff --git a/core/sys/freebsd/constants.odin b/core/sys/freebsd/constants.odin new file mode 100644 index 000000000..3188b32d6 --- /dev/null +++ b/core/sys/freebsd/constants.odin @@ -0,0 +1,5 @@ +package sys_freebsd + +/* Get window size */ +TIOCGWINSZ :: 0x40087468 + diff --git a/core/sys/freebsd/syscalls.odin b/core/sys/freebsd/syscalls.odin index 83b51138a..96fd9ac3f 100644 --- a/core/sys/freebsd/syscalls.odin +++ b/core/sys/freebsd/syscalls.odin @@ -21,6 +21,7 @@ SYS_close : uintptr : 6 SYS_getpid : uintptr : 20 SYS_recvfrom : uintptr : 29 SYS_accept : uintptr : 30 +SYS_getpeername: uintptr : 31 SYS_getsockname: uintptr : 32 SYS_fcntl : uintptr : 92 SYS_fsync : uintptr : 95 @@ -202,26 +203,38 @@ accept_nil :: proc "contextless" (s: Fd) -> (Fd, Errno) { accept :: proc { accept_T, accept_nil } -// Get socket name. -// -// The getsockname() system call appeared in 4.2BSD. -getsockname :: proc "contextless" (s: Fd, sockaddr: ^$T) -> Errno { +getsockname_or_peername :: proc "contextless" (s: Fd, sockaddr: ^$T, is_peer: bool) -> Errno { // sockaddr must contain a valid pointer, or this will segfault because // we're telling the syscall that there's memory available to write to. addrlen: socklen_t = size_of(T) - result, ok := intrinsics.syscall_bsd(SYS_getsockname, + result, ok := intrinsics.syscall_bsd( + is_peer ? SYS_getpeername : SYS_getsockname, cast(uintptr)s, cast(uintptr)sockaddr, cast(uintptr)&addrlen) if !ok { - return cast(Errno)result + return cast(Errno)result } return nil } +// Get name of connected peer +// +// The getpeername() system call appeared in 4.2BSD. +getpeername :: proc "contextless" (s: Fd, sockaddr: ^$T) -> Errno { + return getsockname_or_peername(s, sockaddr, true) +} + +// Get socket name. +// +// The getsockname() system call appeared in 4.2BSD. +getsockname :: proc "contextless" (s: Fd, sockaddr: ^$T) -> Errno { + return getsockname_or_peername(s, sockaddr, false) +} + // Synchronize changes to a file. // // The fsync() system call appeared in 4.2BSD. diff --git a/core/sys/info/cpu_arm.odin b/core/sys/info/cpu_arm.odin index 960e55a56..8f5143394 100644 --- a/core/sys/info/cpu_arm.odin +++ b/core/sys/info/cpu_arm.odin @@ -40,9 +40,13 @@ CPU_Feature :: enum u64 { } CPU_Features :: distinct bit_set[CPU_Feature; u64] - -cpu_features: Maybe(CPU_Features) -cpu_name: Maybe(string) +CPU :: struct { + name: Maybe(string), + features: Maybe(CPU_Features), + physical_cores: int, + logical_cores: int, +} +cpu: CPU @(private) cpu_name_buf: [128]byte @@ -53,7 +57,7 @@ init_cpu_name :: proc "contextless" () { when ODIN_OS == .Darwin { if unix.sysctlbyname("machdep.cpu.brand_string", &cpu_name_buf) { - cpu_name = string(cstring(rawptr(&cpu_name_buf))) + cpu.name = string(cstring(rawptr(&cpu_name_buf))) generic = false } } @@ -61,10 +65,10 @@ init_cpu_name :: proc "contextless" () { if generic { when ODIN_ARCH == .arm64 { copy(cpu_name_buf[:], "ARM64") - cpu_name = string(cpu_name_buf[:len("ARM64")]) + cpu.name = string(cpu_name_buf[:len("ARM64")]) } else { copy(cpu_name_buf[:], "ARM") - cpu_name = string(cpu_name_buf[:len("ARM")]) + cpu.name = string(cpu_name_buf[:len("ARM")]) } } } diff --git a/core/sys/info/cpu_darwin.odin b/core/sys/info/cpu_darwin.odin new file mode 100644 index 000000000..c4a209f6d --- /dev/null +++ b/core/sys/info/cpu_darwin.odin @@ -0,0 +1,12 @@ +package sysinfo + +import "core:sys/unix" + +@(init, private) +init_cpu_core_count :: proc "contextless" () { + physical, logical: i64 + unix.sysctlbyname("hw.physicalcpu", &physical) + unix.sysctlbyname("hw.logicalcpu", &logical) + cpu.physical_cores = int(physical) + cpu.logical_cores = int(logical) +} diff --git a/core/sys/info/cpu_darwin_arm64.odin b/core/sys/info/cpu_darwin_arm64.odin index ffa60d1cb..aaeef9ad9 100644 --- a/core/sys/info/cpu_darwin_arm64.odin +++ b/core/sys/info/cpu_darwin_arm64.odin @@ -5,7 +5,7 @@ import "core:sys/unix" @(init, private) init_cpu_features :: proc "contextless" () { @(static) features: CPU_Features - defer cpu_features = features + defer cpu.features = features try_set :: proc "contextless" (name: cstring, feature: CPU_Feature) -> (ok: bool) { support: b32 diff --git a/core/sys/info/cpu_intel.odin b/core/sys/info/cpu_intel.odin index 95b53dda0..e8f07c732 100644 --- a/core/sys/info/cpu_intel.odin +++ b/core/sys/info/cpu_intel.odin @@ -3,12 +3,6 @@ package sysinfo import "base:intrinsics" -// cpuid :: proc(ax, cx: u32) -> (eax, ebc, ecx, edx: u32) --- -cpuid :: intrinsics.x86_cpuid - -// xgetbv :: proc(cx: u32) -> (eax, edx: u32) --- -xgetbv :: intrinsics.x86_xgetbv - CPU_Feature :: enum u64 { aes, // AES hardware implementation (AES NI) adx, // Multi-precision add-carry instruction extensions @@ -23,6 +17,7 @@ CPU_Feature :: enum u64 { popcnt, // Hamming weight instruction POPCNT. rdrand, // RDRAND instruction (on-chip random number generator) rdseed, // RDSEED instruction (on-chip random number generator) + sha, // SHA Extensions (SHA-1, SHA-224, SHA-256) sse2, // Streaming SIMD extension 2 (always available on amd64) sse3, // Streaming SIMD extension 3 ssse3, // Supplemental streaming SIMD extension 3 @@ -48,12 +43,16 @@ CPU_Feature :: enum u64 { } CPU_Features :: distinct bit_set[CPU_Feature; u64] - -cpu_features: Maybe(CPU_Features) -cpu_name: Maybe(string) +CPU :: struct { + name: Maybe(string), + features: Maybe(CPU_Features), + physical_cores: int, // Initialized by cpu_<os>.odin + logical_cores: int, // Initialized by cpu_<os>.odin +} +cpu: CPU @(init, private) -init_cpu_features :: proc "c" () { +init_cpu_features :: proc "contextless" () { is_set :: #force_inline proc "c" (bit: u32, value: u32) -> bool { return (value>>bit) & 0x1 != 0 } @@ -87,7 +86,7 @@ init_cpu_features :: proc "c" () { when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { // xgetbv is an illegal instruction under FreeBSD 13, OpenBSD 7.1 and NetBSD 10 // return before probing further - cpu_features = set + cpu.features = set return } @@ -115,6 +114,7 @@ init_cpu_features :: proc "c" () { _, ebx7, ecx7, edx7 := cpuid(7, 0) try_set(&set, .bmi1, 3, ebx7) + try_set(&set, .sha, 29, ebx7) if os_supports_avx { try_set(&set, .avx2, 5, ebx7) } @@ -149,14 +149,14 @@ init_cpu_features :: proc "c" () { try_set(&set, .rdseed, 18, ebx7) try_set(&set, .adx, 19, ebx7) - cpu_features = set + cpu.features = set } @(private) _cpu_name_buf: [72]u8 @(init, private) -init_cpu_name :: proc "c" () { +init_cpu_name :: proc "contextless" () { number_of_extended_ids, _, _, _ := cpuid(0x8000_0000, 0) if number_of_extended_ids < 0x8000_0004 { return @@ -177,5 +177,11 @@ init_cpu_name :: proc "c" () { for len(brand) > 0 && brand[len(brand) - 1] == 0 || brand[len(brand) - 1] == ' ' { brand = brand[:len(brand) - 1] } - cpu_name = brand + cpu.name = brand } + +// cpuid :: proc(ax, cx: u32) -> (eax, ebc, ecx, edx: u32) --- +cpuid :: intrinsics.x86_cpuid + +// xgetbv :: proc(cx: u32) -> (eax, edx: u32) --- +xgetbv :: intrinsics.x86_xgetbv
\ No newline at end of file diff --git a/core/sys/info/cpu_linux_arm.odin b/core/sys/info/cpu_linux_arm.odin index 6408decb7..6e8b1a634 100644 --- a/core/sys/info/cpu_linux_arm.odin +++ b/core/sys/info/cpu_linux_arm.odin @@ -2,11 +2,13 @@ #+build linux package sysinfo +import "base:runtime" import "core:sys/linux" import "core:strings" @(init, private) -init_cpu_features :: proc() { +init_cpu_features :: proc "contextless" () { + context = runtime.default_context() fd, err := linux.open("/proc/cpuinfo", {}) if err != .NONE { return } defer linux.close(fd) @@ -17,7 +19,7 @@ init_cpu_features :: proc() { if rerr != .NONE || n == 0 { return } features: CPU_Features - defer cpu_features = features + defer cpu.features = features str := string(buf[:n]) for line in strings.split_lines_iterator(&str) { diff --git a/core/sys/info/cpu_linux_intel.odin b/core/sys/info/cpu_linux_intel.odin new file mode 100644 index 000000000..af76a75e4 --- /dev/null +++ b/core/sys/info/cpu_linux_intel.odin @@ -0,0 +1,41 @@ +#+build i386, amd64 +#+build linux +package sysinfo + +import "base:runtime" +import "core:sys/linux" +import "core:strings" +import "core:strconv" + +@(init, private) +init_cpu_core_count :: proc "contextless" () { + context = runtime.default_context() + + fd, err := linux.open("/proc/cpuinfo", {}) + if err != .NONE { return } + defer linux.close(fd) + + // This is probably enough right? + buf: [4096]byte + n, rerr := linux.read(fd, buf[:]) + if rerr != .NONE || n == 0 { return } + + str := string(buf[:n]) + for line in strings.split_lines_iterator(&str) { + key, _, value := strings.partition(line, ":") + key = strings.trim_space(key) + value = strings.trim_space(value) + + if key == "cpu cores" { + if num_physical_cores, ok := strconv.parse_int(value); ok { + cpu.physical_cores = num_physical_cores + } + } + + if key == "siblings" { + if num_logical_cores, ok := strconv.parse_int(value); ok { + cpu.logical_cores = num_logical_cores + } + } + } +}
\ No newline at end of file diff --git a/core/sys/info/cpu_linux_riscv64.odin b/core/sys/info/cpu_linux_riscv64.odin index 84f6134d4..e65e8a3d2 100644 --- a/core/sys/info/cpu_linux_riscv64.odin +++ b/core/sys/info/cpu_linux_riscv64.odin @@ -7,9 +7,9 @@ import "base:intrinsics" import "core:sys/linux" @(init, private) -init_cpu_features :: proc() { +init_cpu_features :: proc "contextless" () { _features: CPU_Features - defer cpu_features = _features + defer cpu.features = _features HWCAP_Bits :: enum u64 { I = 'I' - 'A', @@ -81,11 +81,11 @@ init_cpu_features :: proc() { } err := linux.riscv_hwprobe(raw_data(pairs), len(pairs), 0, nil, {}) if err != nil { - assert(err == .ENOSYS, "unexpected error from riscv_hwprobe()") + assert_contextless(err == .ENOSYS, "unexpected error from riscv_hwprobe()") return } - assert(pairs[0].key == .IMA_EXT_0) + assert_contextless(pairs[0].key == .IMA_EXT_0) exts := pairs[0].value.ima_ext_0 exts -= { .FD, .C, .V } _features += transmute(CPU_Features)exts @@ -97,7 +97,7 @@ init_cpu_features :: proc() { _features += { .Misaligned_Supported } } } else { - assert(pairs[1].key == .CPUPERF_0) + assert_contextless(pairs[1].key == .CPUPERF_0) if .FAST in pairs[1].value.cpu_perf_0 { _features += { .Misaligned_Supported, .Misaligned_Fast } } else if .UNSUPPORTED not_in pairs[1].value.cpu_perf_0 { @@ -108,6 +108,6 @@ init_cpu_features :: proc() { } @(init, private) -init_cpu_name :: proc() { - cpu_name = "RISCV64" +init_cpu_name :: proc "contextless" () { + cpu.name = "RISCV64" } diff --git a/core/sys/info/cpu_riscv64.odin b/core/sys/info/cpu_riscv64.odin index c3319c48c..64f2edfd3 100644 --- a/core/sys/info/cpu_riscv64.odin +++ b/core/sys/info/cpu_riscv64.odin @@ -95,6 +95,10 @@ CPU_Feature :: enum u64 { } CPU_Features :: distinct bit_set[CPU_Feature; u64] - -cpu_features: Maybe(CPU_Features) -cpu_name: Maybe(string) +CPU :: struct { + name: Maybe(string), + features: Maybe(CPU_Features), + physical_cores: int, + logical_cores: int, +} +cpu: CPU
\ No newline at end of file diff --git a/core/sys/info/cpu_windows.odin b/core/sys/info/cpu_windows.odin new file mode 100644 index 000000000..72d79f9a7 --- /dev/null +++ b/core/sys/info/cpu_windows.odin @@ -0,0 +1,31 @@ +package sysinfo + +import sys "core:sys/windows" +import "base:intrinsics" +import "base:runtime" + +@(init, private) +init_cpu_core_count :: proc "contextless" () { + context = runtime.default_context() + + infos: []sys.SYSTEM_LOGICAL_PROCESSOR_INFORMATION + defer delete(infos) + + returned_length: sys.DWORD + // Query for the required buffer size. + if ok := sys.GetLogicalProcessorInformation(raw_data(infos), &returned_length); !ok { + infos = make([]sys.SYSTEM_LOGICAL_PROCESSOR_INFORMATION, returned_length / size_of(sys.SYSTEM_LOGICAL_PROCESSOR_INFORMATION)) + } + + // If it still doesn't work, return + if ok := sys.GetLogicalProcessorInformation(raw_data(infos), &returned_length); !ok { + return + } + + for info in infos { + #partial switch info.Relationship { + case .RelationProcessorCore: cpu.physical_cores += 1 + case .RelationNumaNode: cpu.logical_cores += int(intrinsics.count_ones(info.ProcessorMask)) + } + } +}
\ No newline at end of file diff --git a/core/sys/info/doc.odin b/core/sys/info/doc.odin index 2fd34b864..2a4f71642 100644 --- a/core/sys/info/doc.odin +++ b/core/sys/info/doc.odin @@ -26,13 +26,14 @@ Example: import si "core:sys/info" main :: proc() { - fmt.printfln("Odin: %v", ODIN_VERSION) - fmt.printfln("OS: %v", si.os_version.as_string) - fmt.printfln("OS: %#v", si.os_version) - fmt.printfln("CPU: %v", si.cpu_name) - fmt.printfln("RAM: %#.1M", si.ram.total_ram) + fmt.printfln("Odin: %v", ODIN_VERSION) + fmt.printfln("OS: %v", si.os_version.as_string) + fmt.printfln("OS: %#v", si.os_version) + fmt.printfln("CPU: %v", si.cpu.name) + fmt.printfln("CPU cores: %vc/%vt", si.cpu.physical_cores, si.cpu.logical_cores) + fmt.printfln("RAM: %#.1M", si.ram.total_ram) - // fmt.printfln("Features: %v", si.cpu_features) + // fmt.printfln("Features: %v", si.cpu.features) // fmt.printfln("MacOS version: %v", si.macos_version) fmt.println() diff --git a/core/sys/info/platform_bsd.odin b/core/sys/info/platform_bsd.odin index 6bb32cd3d..2f8d7f5bb 100644 --- a/core/sys/info/platform_bsd.odin +++ b/core/sys/info/platform_bsd.odin @@ -10,7 +10,9 @@ import "base:runtime" version_string_buf: [1024]u8 @(init, private) -init_os_version :: proc () { +init_os_version :: proc "contextless" () { + context = runtime.default_context() + when ODIN_OS == .NetBSD { os_version.platform = .NetBSD } else { @@ -66,7 +68,7 @@ init_os_version :: proc () { } @(init, private) -init_ram :: proc() { +init_ram :: proc "contextless" () { // Retrieve RAM info using `sysctl` mib := []i32{sys.CTL_HW, sys.HW_PHYSMEM64} mem_size: u64 diff --git a/core/sys/info/platform_darwin.odin b/core/sys/info/platform_darwin.odin index 7dc49bcd1..07c26ec28 100644 --- a/core/sys/info/platform_darwin.odin +++ b/core/sys/info/platform_darwin.odin @@ -1,5 +1,7 @@ package sysinfo +import "base:runtime" + import "core:strconv" import "core:strings" import "core:sys/unix" @@ -9,7 +11,8 @@ import NS "core:sys/darwin/Foundation" version_string_buf: [1024]u8 @(init, private) -init_platform :: proc() { +init_platform :: proc "contextless" () { + context = runtime.default_context() ws :: strings.write_string wi :: strings.write_int @@ -28,12 +31,13 @@ init_platform :: proc() { macos_version = {int(version.majorVersion), int(version.minorVersion), int(version.patchVersion)} - when ODIN_PLATFORM_SUBTARGET == .iOS { + when ODIN_PLATFORM_SUBTARGET_IOS { os_version.platform = .iOS ws(&b, "iOS") } else { os_version.platform = .MacOS switch version.majorVersion { + case 26: ws(&b, "macOS Tahoe") case 15: ws(&b, "macOS Sequoia") case 14: ws(&b, "macOS Sonoma") case 13: ws(&b, "macOS Ventura") diff --git a/core/sys/info/platform_freebsd.odin b/core/sys/info/platform_freebsd.odin index b26fb7875..eb39769de 100644 --- a/core/sys/info/platform_freebsd.odin +++ b/core/sys/info/platform_freebsd.odin @@ -9,7 +9,9 @@ import "base:runtime" version_string_buf: [1024]u8 @(init, private) -init_os_version :: proc () { +init_os_version :: proc "contextless" () { + context = runtime.default_context() + os_version.platform = .FreeBSD kernel_version_buf: [1024]u8 @@ -68,7 +70,7 @@ init_os_version :: proc () { } @(init, private) -init_ram :: proc() { +init_ram :: proc "contextless" () { // Retrieve RAM info using `sysctl` mib := []i32{sys.CTL_HW, sys.HW_PHYSMEM} mem_size: u64 diff --git a/core/sys/info/platform_linux.odin b/core/sys/info/platform_linux.odin index 45efc3329..43cd580c1 100644 --- a/core/sys/info/platform_linux.odin +++ b/core/sys/info/platform_linux.odin @@ -1,6 +1,7 @@ package sysinfo import "base:intrinsics" +import "base:runtime" import "core:strconv" import "core:strings" @@ -10,15 +11,21 @@ import "core:sys/linux" version_string_buf: [1024]u8 @(init, private) -init_os_version :: proc () { +init_os_version :: proc "contextless" () { + context = runtime.default_context() + os_version.platform = .Linux b := strings.builder_from_bytes(version_string_buf[:]) // Try to parse `/etc/os-release` for `PRETTY_NAME="Ubuntu 20.04.3 LTS` - { + pretty_parse: { fd, errno := linux.open("/etc/os-release", {}) - assert(errno == .NONE, "Failed to read /etc/os-release") + if errno != .NONE { + strings.write_string(&b, "Unknown Linux Distro") + break pretty_parse + } + defer { cerrno := linux.close(fd) assert(cerrno == .NONE, "Failed to close the file descriptor") @@ -26,7 +33,10 @@ init_os_version :: proc () { os_release_buf: [2048]u8 n, read_errno := linux.read(fd, os_release_buf[:]) - assert(read_errno == .NONE, "Failed to read data from /etc/os-release") + if read_errno != .NONE { + strings.write_string(&b, "Unknown Linux Distro") + break pretty_parse + } release := string(os_release_buf[:n]) // Search the line in the file until we find "PRETTY_NAME=" @@ -59,7 +69,7 @@ init_os_version :: proc () { os_version.as_string = strings.to_string(b) // Parse the Linux version out of the release string - { + version_loop: { version_num, _, version_suffix := strings.partition(release_str, "-") os_version.version = version_suffix @@ -72,11 +82,11 @@ init_os_version :: proc () { case 0: dst = &os_version.major case 1: dst = &os_version.minor case 2: dst = &os_version.patch - case: break + case: break version_loop } num, ok := strconv.parse_int(part) - if !ok { break } + if !ok { break version_loop } dst^ = num } @@ -84,11 +94,11 @@ init_os_version :: proc () { } @(init, private) -init_ram :: proc() { +init_ram :: proc "contextless" () { // Retrieve RAM info using `sysinfo` sys_info: linux.Sys_Info errno := linux.sysinfo(&sys_info) - assert(errno == .NONE, "Good luck to whoever's debugging this, something's seriously cucked up!") + assert_contextless(errno == .NONE, "Good luck to whoever's debugging this, something's seriously cucked up!") ram = RAM{ total_ram = int(sys_info.totalram) * int(sys_info.mem_unit), free_ram = int(sys_info.freeram) * int(sys_info.mem_unit), diff --git a/core/sys/info/platform_windows.odin b/core/sys/info/platform_windows.odin index 4c00ddadf..ff8ebe2ee 100644 --- a/core/sys/info/platform_windows.odin +++ b/core/sys/info/platform_windows.odin @@ -12,7 +12,9 @@ import "base:runtime" version_string_buf: [1024]u8 @(init, private) -init_os_version :: proc () { +init_os_version :: proc "contextless" () { + context = runtime.default_context() + /* NOTE(Jeroen): `GetVersionEx` will return 6.2 for Windows 10 unless the program is manifested for Windows 10. @@ -43,6 +45,7 @@ init_os_version :: proc () { os_version.minor = int(osvi.dwMinorVersion) os_version.build[0] = int(osvi.dwBuildNumber) + b := strings.builder_from_bytes(version_string_buf[:]) strings.write_string(&b, "Windows ") @@ -259,7 +262,7 @@ init_os_version :: proc () { } @(init, private) -init_ram :: proc() { +init_ram :: proc "contextless" () { state: sys.MEMORYSTATUSEX state.dwLength = size_of(state) @@ -276,10 +279,11 @@ init_ram :: proc() { } @(init, private) -init_gpu_info :: proc() { - +init_gpu_info :: proc "contextless" () { GPU_INFO_BASE :: "SYSTEM\\ControlSet001\\Control\\Class\\{4d36e968-e325-11ce-bfc1-08002be10318}\\" + context = runtime.default_context() + gpu_list: [dynamic]GPU gpu_index: int @@ -324,8 +328,8 @@ read_reg_string :: proc(hkey: sys.HKEY, subkey, val: string) -> (res: string, ok status := sys.RegGetValueW( hkey, - &key_name_wide[0], - &val_name_wide[0], + cstring16(&key_name_wide[0]), + cstring16(&val_name_wide[0]), sys.RRF_RT_REG_SZ, nil, raw_data(result_wide[:]), @@ -359,8 +363,8 @@ read_reg_i32 :: proc(hkey: sys.HKEY, subkey, val: string) -> (res: i32, ok: bool result_size := sys.DWORD(size_of(i32)) status := sys.RegGetValueW( hkey, - &key_name_wide[0], - &val_name_wide[0], + cstring16(&key_name_wide[0]), + cstring16(&val_name_wide[0]), sys.RRF_RT_REG_DWORD, nil, &res, @@ -386,8 +390,8 @@ read_reg_i64 :: proc(hkey: sys.HKEY, subkey, val: string) -> (res: i64, ok: bool result_size := sys.DWORD(size_of(i64)) status := sys.RegGetValueW( hkey, - &key_name_wide[0], - &val_name_wide[0], + cstring16(&key_name_wide[0]), + cstring16(&val_name_wide[0]), sys.RRF_RT_REG_QWORD, nil, &res, diff --git a/core/sys/info/sysinfo.odin b/core/sys/info/sysinfo.odin index f624a1718..75cc237c6 100644 --- a/core/sys/info/sysinfo.odin +++ b/core/sys/info/sysinfo.odin @@ -1,6 +1,6 @@ package sysinfo -when !(ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 || ODIN_ARCH == .arm32 || ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64) { +when !(ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 || ODIN_ARCH == .arm32 || ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 || ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32) { #assert(false, "This package is unsupported on this architecture.") } diff --git a/core/sys/kqueue/kqueue.odin b/core/sys/kqueue/kqueue.odin index 56be1cf7a..25ee9bdce 100644 --- a/core/sys/kqueue/kqueue.odin +++ b/core/sys/kqueue/kqueue.odin @@ -2,7 +2,7 @@ package kqueue when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/linux/bits.odin b/core/sys/linux/bits.odin index 4493ea767..64cdd2208 100644 --- a/core/sys/linux/bits.odin +++ b/core/sys/linux/bits.odin @@ -1,5 +1,10 @@ package linux +import "base:intrinsics" + +@(private) +log2 :: intrinsics.constant_log2 + /* Represents an error returned by most of syscalls @@ -574,7 +579,7 @@ Inotify_Event_Bits :: enum u32 { /* Bits for Mem_Protection bitfield */ -Mem_Protection_Bits :: enum{ +Mem_Protection_Bits :: enum { READ = 0, WRITE = 1, EXEC = 2, @@ -589,11 +594,13 @@ Mem_Protection_Bits :: enum{ /* Bits for Map_Flags + + See `constants.odin` for `MAP_SHARED_VALIDATE` and `MAP_HUGE_16KB`, et al. */ Map_Flags_Bits :: enum { SHARED = 0, PRIVATE = 1, - SHARED_VALIDATE = 2, + DROPPABLE = 3, FIXED = 4, ANONYMOUS = 5, // platform-dependent section start @@ -1611,36 +1618,39 @@ PER_HPUX :: 0x0010 PER_MASK :: 0x00ff /* - Bits for access modes for shared memory + Bits for SystemV IPC flags. + + In this enum, access modes are common for any shared memory. Prefixed + entries (i.e. `IPC_` or `SHM_`) denote flags, where `IPC_` are common flags + for all SystemV IPC primitives, and `SHM_`, `SEM_` and `MSG_` are specific + to shared memory segments, semaphores and message queues respectively. + + These bits overlap, because they are meant to be used within the + context of specific procedures. Creation flags, used for `*get` procedures, + and usage flags used by all other IPC procedures. Do not mix creation and + usage flags, as well as flags prefixed differently (excluding `IPC_` + prefix). */ -IPC_Mode_Bits :: enum { +IPC_Flags_Bits :: enum { + // Access modes for shared memory. WROTH = 1, RDOTH = 2, WRGRP = 4, RDGRP = 5, WRUSR = 7, RDUSR = 8, - DEST = 9, - LOCKED = 10, -} - -/* - Shared memory flags bits -*/ -IPC_Flags_Bits :: enum { + // Creation flags for shared memory. IPC_CREAT = 9, IPC_EXCL = 10, - IPC_NOWAIT = 11, - // Semaphore - SEM_UNDO = 9, - // Shared memory SHM_HUGETLB = 11, SHM_NORESERVE = 12, + // Usage flags for shared memory. + IPC_NOWAIT = 11, + SEM_UNDO = 9, SHM_RDONLY = 12, SHM_RND = 13, SHM_REMAP = 14, SHM_EXEC = 15, - // Message queue MSG_NOERROR = 12, MSG_EXCEPT = 13, MSG_COPY = 14, @@ -1828,7 +1838,7 @@ Clock_Id :: enum { Bits for POSIX interval timer flags. */ ITimer_Flags_Bits :: enum { - ABSTIME = 1, + ABSTIME = 0, } /* @@ -1839,22 +1849,23 @@ EPoll_Flags_Bits :: enum { } EPoll_Event_Kind :: enum u32 { - IN = 0x001, - PRI = 0x002, - OUT = 0x004, - RDNORM = 0x040, - RDBAND = 0x080, - WRNORM = 0x100, - WRBAND = 0x200, - MSG = 0x400, - ERR = 0x008, - HUP = 0x010, - RDHUP = 0x2000, - EXCLUSIVE = 1<<28, - WAKEUP = 1<<29, - ONESHOT = 1<<30, - ET = 1<<31, -} + IN = log2(0x001), + PRI = log2(0x002), + OUT = log2(0x004), + RDNORM = log2(0x040), + RDBAND = log2(0x080), + WRNORM = log2(0x100), + WRBAND = log2(0x200), + MSG = log2(0x400), + ERR = log2(0x008), + HUP = log2(0x010), + RDHUP = log2(0x2000), + EXCLUSIVE = log2(1<<28), + WAKEUP = log2(1<<29), + ONESHOT = log2(1<<30), + ET = log2(1<<31), +} +EPoll_Event_Set :: bit_set[EPoll_Event_Kind; u32] EPoll_Ctl_Opcode :: enum i32 { ADD = 1, diff --git a/core/sys/linux/constants.odin b/core/sys/linux/constants.odin index b3bbcafb3..ceab17f6d 100644 --- a/core/sys/linux/constants.odin +++ b/core/sys/linux/constants.odin @@ -373,3 +373,25 @@ PTRACE_SECCOMP_GET_FILTER :: PTrace_Seccomp_Get_Filter_Type(.SECCOMP_GET_FIL PTRACE_SECCOMP_GET_METADATA :: PTrace_Seccomp_Get_Metadata_Type(.SECCOMP_GET_METADATA) PTRACE_GET_SYSCALL_INFO :: PTrace_Get_Syscall_Info_Type(.GET_SYSCALL_INFO) PTRACE_GET_RSEQ_CONFIGURATION :: PTrace_Get_RSeq_Configuration_Type(.GET_RSEQ_CONFIGURATION) + +MAP_SHARED_VALIDATE :: Map_Flags{.SHARED, .PRIVATE} + +MAP_HUGE_SHIFT :: 26 +MAP_HUGE_MASK :: 63 + +MAP_HUGE_16KB :: transmute(Map_Flags)(u32(14) << MAP_HUGE_SHIFT) +MAP_HUGE_64KB :: transmute(Map_Flags)(u32(16) << MAP_HUGE_SHIFT) +MAP_HUGE_512KB :: transmute(Map_Flags)(u32(19) << MAP_HUGE_SHIFT) +MAP_HUGE_1MB :: transmute(Map_Flags)(u32(20) << MAP_HUGE_SHIFT) +MAP_HUGE_2MB :: transmute(Map_Flags)(u32(21) << MAP_HUGE_SHIFT) +MAP_HUGE_8MB :: transmute(Map_Flags)(u32(23) << MAP_HUGE_SHIFT) +MAP_HUGE_16MB :: transmute(Map_Flags)(u32(24) << MAP_HUGE_SHIFT) +MAP_HUGE_32MB :: transmute(Map_Flags)(u32(25) << MAP_HUGE_SHIFT) +MAP_HUGE_256MB :: transmute(Map_Flags)(u32(28) << MAP_HUGE_SHIFT) +MAP_HUGE_512MB :: transmute(Map_Flags)(u32(29) << MAP_HUGE_SHIFT) +MAP_HUGE_1GB :: transmute(Map_Flags)(u32(30) << MAP_HUGE_SHIFT) +MAP_HUGE_2GB :: transmute(Map_Flags)(u32(31) << MAP_HUGE_SHIFT) +MAP_HUGE_16GB :: transmute(Map_Flags)(u32(34) << MAP_HUGE_SHIFT) + +/* Get window size */ +TIOCGWINSZ :: 0x5413 diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index 532c1ff5f..deb22726f 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -1,3 +1,4 @@ +#+build linux #+no-instrumentation package linux @@ -212,7 +213,7 @@ rt_sigreturn :: proc "c" () -> ! { /* Alter an action taken by a process. */ -rt_sigaction :: proc "contextless" (sig: Signal, sigaction: ^Sig_Action($T), old_sigaction: ^Sig_Action) -> Errno { +rt_sigaction :: proc "contextless" (sig: Signal, sigaction: ^Sig_Action($T), old_sigaction: ^Sig_Action($U)) -> Errno { // NOTE(jason): It appears that the restorer is required for i386 and amd64 when ODIN_ARCH == .i386 || ODIN_ARCH == .amd64 { sigaction.flags += {.RESTORER} @@ -1412,7 +1413,7 @@ umask :: proc "contextless" (mask: Mode) -> Mode { Available since Linux 1.0. */ gettimeofday :: proc "contextless" (tv: ^Time_Val) -> (Errno) { - ret := syscall(SYS_gettimeofday, tv) + ret := syscall(SYS_gettimeofday, tv, rawptr(nil)) return Errno(-ret) } diff --git a/core/sys/linux/syscall_arm64.odin b/core/sys/linux/syscall_arm64.odin index da8eb45da..6f1c93f83 100644 --- a/core/sys/linux/syscall_arm64.odin +++ b/core/sys/linux/syscall_arm64.odin @@ -317,5 +317,18 @@ SYS_futex_waitv :: uintptr(449) SYS_set_mempolicy_home_node :: uintptr(450) SYS_cachestat :: uintptr(451) SYS_fchmodat2 :: uintptr(452) - +SYS_map_shadow_stack :: uintptr(453) +SYS_futex_wake :: uintptr(454) +SYS_futex_wait :: uintptr(455) +SYS_futex_requeue :: uintptr(456) +SYS_statmount :: uintptr(457) +SYS_listmount :: uintptr(458) +SYS_lsm_get_self_attr :: uintptr(459) +SYS_lsm_set_self_attr :: uintptr(460) +SYS_lsm_list_modules :: uintptr(461) +SYS_mseal :: uintptr(462) +SYS_setxattrat :: uintptr(463) +SYS_getxattrat :: uintptr(464) +SYS_listxattrat :: uintptr(465) +SYS_removexattrat :: uintptr(466) diff --git a/core/sys/linux/types.odin b/core/sys/linux/types.odin index dcc72f72b..c2948c36e 100644 --- a/core/sys/linux/types.odin +++ b/core/sys/linux/types.odin @@ -288,7 +288,7 @@ Rename_Flags :: bit_set[Rename_Flags_Bits; u32] /* Directory entry record. - Recommended iterate these with `dirent_iterator()`, + Recommended to iterate these with `dirent_iterate_buf()`, and obtain the name via `dirent_name()`. */ Dirent :: struct { @@ -368,6 +368,8 @@ Mem_Protection :: bit_set[Mem_Protection_Bits; i32] /* Flags for mmap. + + See `constants.odin` for `MAP_SHARED_VALIDATE` and `MAP_HUGE_16KB`, et al. */ Map_Flags :: bit_set[Map_Flags_Bits; i32] @@ -935,17 +937,12 @@ IO_Vec :: struct { } /* - Access mode for shared memory -*/ -IPC_Mode :: bit_set[IPC_Mode_Bits; u32] - -/* - Flags used by IPC objects + Access modes and flags used by SystemV IPC procedures. */ IPC_Flags :: bit_set[IPC_Flags_Bits; i16] /* - Permissions for IPC objects + Permissions for SystemV IPC primitives. */ IPC_Perm :: struct { key: Key, @@ -953,7 +950,7 @@ IPC_Perm :: struct { gid: u32, cuid: u32, cgid: u32, - mode: IPC_Mode, + mode: IPC_Flags, // Only contains mode flags. seq: u16, _: [2 + 2*size_of(int)]u8, } @@ -1450,7 +1447,7 @@ EPoll_Data :: struct #raw_union { } EPoll_Event :: struct #packed { - events: EPoll_Event_Kind, + events: EPoll_Event_Set, data: EPoll_Data, } diff --git a/core/sys/linux/wrappers.odin b/core/sys/linux/wrappers.odin index ab1992a57..53eb80f86 100644 --- a/core/sys/linux/wrappers.odin +++ b/core/sys/linux/wrappers.odin @@ -54,22 +54,45 @@ WCOREDUMP :: #force_inline proc "contextless" (s: u32) -> bool { // TODO: sigaddset etc -/// Iterate the results of getdents -/// Only iterates as much data as loaded in the buffer -/// In case you need to iterate *all* files in a directory -/// consider using dirent_get_iterate -/// -/// Example of using dirent_iterate_buf -/// // Get dirents into a buffer -/// buf: [128]u8 -/// sys.getdents(dirfd, buf[:]) -/// // Print the names of the files -/// for dir in sys.dirent_iterate_buf(buf[:], &offs) { -/// name := sys.dirent_name(dir) -/// fmt.println(name) -/// } -/// This function doesn't automatically make a request -/// for the buffer to be refilled +/* +Iterate the results of `getdents()`. + +This procedure extracts a directory entry from `buf` at the offset `offs`. +`offs` will be modified to store an offset to the possible next directory entry +in `buf`. The procedure only iterates as much data as loaded in the buffer and +does not automatically make a request for the buffer to be refilled. + +Inputs: +- buf: A byte buffer with data from `getdents()` +- offs: An offset to the next possible directory entry in `buf` + +Returns: +- A pointer to a directory entry in `buf`, or `nil` +- A bool value denoting if a valid directory entry is returned + +Example: + + import "core:fmt" + import "core:sys/linux" + + print_names :: proc(dirfd: linux.Fd) { + // Get dirents into a buffer. + buf: [128]u8 + // Loop until there are no more entries. + for { + written, err := linux.getdents(dirfd, buf[:]) + if err != .NONE || written == 0 { + break + } + // Print the names of the files. + offset : int + for dir in linux.dirent_iterate_buf(buf[:written], &offset) { + name := linux.dirent_name(dir) + fmt.println(name) + } + } + } +*/ dirent_iterate_buf :: proc "contextless" (buf: []u8, offs: ^int) -> (d: ^Dirent, cont: bool) { // Stopped iterating when there's no space left if offs^ >= len(buf) { @@ -82,8 +105,17 @@ dirent_iterate_buf :: proc "contextless" (buf: []u8, offs: ^int) -> (d: ^Dirent, return dirent, true } -/// Obtain the name of dirent as a string -/// The lifetime of the string is bound to the lifetime of the provided dirent structure +/* +Obtain the name of dirent as a string. + +The lifetime of the returned string is bound to the lifetime of the provided dirent structure. + +Inputs: +- dirent: A directory entry + +Returns: +- A name of the entry +*/ dirent_name :: proc "contextless" (dirent: ^Dirent) -> string #no_bounds_check { str := ([^]u8)(&dirent.name) // Dirents are aligned to 8 bytes, so there is guaranteed to be a null @@ -93,10 +125,10 @@ dirent_name :: proc "contextless" (dirent: ^Dirent) -> string #no_bounds_check { trunc := min(str_size, 8) str_size -= trunc for _ in 0..<trunc { - str_size += 1 if str[str_size] == 0 { break } + str_size += 1 } return string(str[:str_size]) } diff --git a/core/sys/posix/arpa_inet.odin b/core/sys/posix/arpa_inet.odin index ac850ed49..70b12678c 100644 --- a/core/sys/posix/arpa_inet.odin +++ b/core/sys/posix/arpa_inet.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else when ODIN_OS == .Haiku { foreign import lib "system:network" } else { @@ -50,7 +50,6 @@ foreign lib { af: AF, // INET or INET6 src: cstring, dst: rawptr, // either ^in_addr or ^in_addr6 - size: socklen_t, // size_of(dst^) ) -> pton_result --- } diff --git a/core/sys/posix/dirent.odin b/core/sys/posix/dirent.odin index 1394f6b9e..cf15dada4 100644 --- a/core/sys/posix/dirent.odin +++ b/core/sys/posix/dirent.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/dlfcn.odin b/core/sys/posix/dlfcn.odin index e84b29d79..6c96b0079 100644 --- a/core/sys/posix/dlfcn.odin +++ b/core/sys/posix/dlfcn.odin @@ -4,11 +4,14 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { foreign import lib "system:dl" } else { - foreign import lib "system:c" + foreign import lib { + "system:c", + "system:dl", + } } // dlfcn.h - dynamic linking diff --git a/core/sys/posix/fcntl.odin b/core/sys/posix/fcntl.odin index bc0b5b5ba..db095c418 100644 --- a/core/sys/posix/fcntl.odin +++ b/core/sys/posix/fcntl.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/fnmatch.odin b/core/sys/posix/fnmatch.odin index 04c3d2888..efe179324 100644 --- a/core/sys/posix/fnmatch.odin +++ b/core/sys/posix/fnmatch.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/glob.odin b/core/sys/posix/glob.odin index fb90b7546..530481587 100644 --- a/core/sys/posix/glob.odin +++ b/core/sys/posix/glob.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/grp.odin b/core/sys/posix/grp.odin index 3694308a0..8e8e69fc2 100644 --- a/core/sys/posix/grp.odin +++ b/core/sys/posix/grp.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/langinfo.odin b/core/sys/posix/langinfo.odin index 1fddfe280..195de650d 100644 --- a/core/sys/posix/langinfo.odin +++ b/core/sys/posix/langinfo.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/libgen.odin b/core/sys/posix/libgen.odin index 2354bf70d..aa2effd72 100644 --- a/core/sys/posix/libgen.odin +++ b/core/sys/posix/libgen.odin @@ -2,7 +2,7 @@ package posix when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/monetary.odin b/core/sys/posix/monetary.odin index a444bff09..2e4105881 100644 --- a/core/sys/posix/monetary.odin +++ b/core/sys/posix/monetary.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/net_if.odin b/core/sys/posix/net_if.odin index 774d11b72..182a049d1 100644 --- a/core/sys/posix/net_if.odin +++ b/core/sys/posix/net_if.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/netdb.odin b/core/sys/posix/netdb.odin index ff1cb9d4c..f2f83875f 100644 --- a/core/sys/posix/netdb.odin +++ b/core/sys/posix/netdb.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/netinet_in.odin b/core/sys/posix/netinet_in.odin index ec05915de..4b74b87f0 100644 --- a/core/sys/posix/netinet_in.odin +++ b/core/sys/posix/netinet_in.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/poll.odin b/core/sys/posix/poll.odin index 44ec767a6..a9e582b51 100644 --- a/core/sys/posix/poll.odin +++ b/core/sys/posix/poll.odin @@ -6,7 +6,7 @@ import "base:intrinsics" import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/posix.odin b/core/sys/posix/posix.odin index d56217407..3dcf36a5e 100644 --- a/core/sys/posix/posix.odin +++ b/core/sys/posix/posix.odin @@ -31,7 +31,7 @@ Unimplemented headers: - iso646.h | Impossible - math.h | See `core:c/libc` - mqueue.h | Targets don't seem to have implemented it -- regex.h | See `core:regex` +- regex.h | See `core:text/regex` - search.h | Not useful in Odin - spawn.h | Use `fork`, `execve`, etc. - stdarg.h | See `core:c/libc` @@ -53,6 +53,8 @@ import "base:intrinsics" import "core:c" +IS_SUPPORTED :: _IS_SUPPORTED + result :: enum c.int { // Use `errno` and `strerror` for more information. FAIL = -1, diff --git a/core/sys/posix/posix_other.odin b/core/sys/posix/posix_other.odin new file mode 100644 index 000000000..88542c56b --- /dev/null +++ b/core/sys/posix/posix_other.odin @@ -0,0 +1,10 @@ +#+build !linux +#+build !darwin +#+build !netbsd +#+build !openbsd +#+build !freebsd +#+build !haiku +package posix + +_IS_SUPPORTED :: false + diff --git a/core/sys/posix/posix_unix.odin b/core/sys/posix/posix_unix.odin new file mode 100644 index 000000000..51580a655 --- /dev/null +++ b/core/sys/posix/posix_unix.odin @@ -0,0 +1,5 @@ +#+build linux, darwin, netbsd, openbsd, freebsd, haiku +package posix + +_IS_SUPPORTED :: true + diff --git a/core/sys/posix/pthread.odin b/core/sys/posix/pthread.odin index 36a3cd7b3..733725e2c 100644 --- a/core/sys/posix/pthread.odin +++ b/core/sys/posix/pthread.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .Linux { foreign import lib "system:pthread" } else { @@ -124,7 +124,7 @@ foreign lib { [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getscope.html ]] */ - pthread_attr_setscope :: proc(attr: ^pthread_attr_t, contentionscope: ^Thread_Scope) -> Errno --- + pthread_attr_setscope :: proc(attr: ^pthread_attr_t, contentionscope: Thread_Scope) -> Errno --- /* Get the area of storage to be used for the created thread's stack. @@ -400,7 +400,7 @@ when ODIN_OS == .Darwin { PTHREAD_SCOPE_PROCESS :: 2 PTHREAD_SCOPE_SYSTEM :: 1 - pthread_t :: distinct u64 + pthread_t :: distinct rawptr pthread_attr_t :: struct { __sig: c.long, diff --git a/core/sys/posix/pwd.odin b/core/sys/posix/pwd.odin index 75d15c899..c3ee3c0f6 100644 --- a/core/sys/posix/pwd.odin +++ b/core/sys/posix/pwd.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/sched.odin b/core/sys/posix/sched.odin index 82b335653..cc509ba8e 100644 --- a/core/sys/posix/sched.odin +++ b/core/sys/posix/sched.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/setjmp.odin b/core/sys/posix/setjmp.odin index 926dbd3ad..a26bdb93e 100644 --- a/core/sys/posix/setjmp.odin +++ b/core/sys/posix/setjmp.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/signal.odin b/core/sys/posix/signal.odin index c7e2cc09b..69b405c5d 100644 --- a/core/sys/posix/signal.odin +++ b/core/sys/posix/signal.odin @@ -6,7 +6,7 @@ import "base:intrinsics" import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } @@ -50,7 +50,7 @@ foreign lib { /* Send a signal to a thread. - + As with kill, if sig is 0, only validation (of the pthread_t given) is done and no signal is sent. [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_kill.html ]] @@ -124,7 +124,7 @@ foreign lib { sigignore :: proc(sig: Signal) -> result --- /* - Removes sig from the signal mask of the calling process and suspend the calling process until + Removes sig from the signal mask of the calling process and suspend the calling process until a signal is received. [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sighold.html ]] @@ -166,7 +166,7 @@ foreign lib { [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/sigpending.html ]] */ @(link_name=LSIGPENDING) - sigpending :: proc(set: ^sigset_t) -> result --- + sigpending :: proc(set: ^sigset_t) -> result --- /* Wait for one of the given signals. @@ -333,7 +333,7 @@ SS_Flag_Bits :: enum c.int { SS_Flags :: bit_set[SS_Flag_Bits; c.int] Sig :: enum c.int { - // Resulting set is the union of the current set and the signal set and the complement of + // Resulting set is the union of the current set and the signal set and the complement of // the signal set pointed to by the argument. BLOCK = SIG_BLOCK, // Resulting set is the intersection of the current set and the complement of the signal set @@ -395,6 +395,7 @@ when ODIN_OS == .Darwin { SIGXFSZ :: 25 SIGVTALRM :: 26 SIGPROF :: 27 + SIGWINCH :: 28 SIGUSR1 :: 30 SIGUSR2 :: 31 @@ -535,6 +536,7 @@ when ODIN_OS == .Darwin { SIGXFSZ :: 25 SIGVTALRM :: 26 SIGPROF :: 27 + SIGWINCH :: 28 SIGUSR1 :: 30 SIGUSR2 :: 31 @@ -565,7 +567,7 @@ when ODIN_OS == .Darwin { SS_ONSTACK :: 0x0001 SS_DISABLE :: 0x0004 - when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm32 { + when ODIN_ARCH == .arm64 || ODIN_ARCH == .arm32 { MINSIGSTKSZ :: 1024 * 4 } else when ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 { MINSIGSTKSZ :: 512 * 4 @@ -699,6 +701,7 @@ when ODIN_OS == .Darwin { SIGXFSZ :: 25 SIGVTALRM :: 26 SIGPROF :: 27 + SIGWINCH :: 28 SIGUSR1 :: 30 SIGUSR2 :: 31 @@ -876,6 +879,7 @@ when ODIN_OS == .Darwin { SIGXFSZ :: 25 SIGVTALRM :: 26 SIGPROF :: 27 + SIGWINCH :: 28 SIGUSR1 :: 30 SIGUSR2 :: 31 @@ -1036,6 +1040,7 @@ when ODIN_OS == .Darwin { SIGXFSZ :: 25 SIGVTALRM :: 26 SIGPROF :: 27 + SIGWINCH :: 28 SIGPOLL :: 29 SIGSYS :: 31 @@ -1084,7 +1089,7 @@ when ODIN_OS == .Darwin { @(private) __SI_MAX_SIZE :: 128 - when size_of(int) == 8 { + when size_of(int) == 8 { @(private) _pad0 :: struct { _pad0: c.int, diff --git a/core/sys/posix/signal_libc.odin b/core/sys/posix/signal_libc.odin index 7a054ddd7..ba0fbf084 100644 --- a/core/sys/posix/signal_libc.odin +++ b/core/sys/posix/signal_libc.odin @@ -9,7 +9,7 @@ import "core:c/libc" when ODIN_OS == .Windows { foreign import lib "system:libucrt.lib" } else when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/spawn.odin b/core/sys/posix/spawn.odin new file mode 100644 index 000000000..4eacb3b4b --- /dev/null +++ b/core/sys/posix/spawn.odin @@ -0,0 +1,21 @@ +#+build linux, darwin, openbsd, freebsd, netbsd, haiku +package posix + +when ODIN_OS == .Darwin { + foreign import lib "system:System.framework" +} else { + foreign import lib "system:c" +} + +foreign lib { + /* + Creates a child process from a provided filepath + spawnp searches directories on the path for the file + + Returns: 0 on success, with the child pid returned in the pid argument, or error values on failure. + + [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_spawn.html ]] + */ + posix_spawn :: proc(pid: ^pid_t, path: cstring, file_actions: rawptr, attrp: rawptr, argv: [^]cstring, envp: [^]cstring) -> Errno --- + posix_spawnp :: proc(pid: ^pid_t, file: cstring, file_actions: rawptr, attrp: rawptr, argv: [^]cstring, envp: [^]cstring) -> Errno --- +} diff --git a/core/sys/posix/stdio.odin b/core/sys/posix/stdio.odin index 24464dfd8..69c8ad3cb 100644 --- a/core/sys/posix/stdio.odin +++ b/core/sys/posix/stdio.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/stdio_libc.odin b/core/sys/posix/stdio_libc.odin index 12706970d..8ccdcc37a 100644 --- a/core/sys/posix/stdio_libc.odin +++ b/core/sys/posix/stdio_libc.odin @@ -10,7 +10,7 @@ when ODIN_OS == .Windows { "system:legacy_stdio_definitions.lib", } } else when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/stdlib.odin b/core/sys/posix/stdlib.odin index 5f1ae1908..0a6e5403c 100644 --- a/core/sys/posix/stdlib.odin +++ b/core/sys/posix/stdlib.odin @@ -6,7 +6,7 @@ import "base:intrinsics" import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/stdlib_libc.odin b/core/sys/posix/stdlib_libc.odin index 6574026f4..966dc1d32 100644 --- a/core/sys/posix/stdlib_libc.odin +++ b/core/sys/posix/stdlib_libc.odin @@ -9,7 +9,7 @@ import "core:c/libc" when ODIN_OS == .Windows { foreign import lib "system:libucrt.lib" } else when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } @@ -60,7 +60,7 @@ wctomb :: libc.wctomb mbstowcs :: libc.mbstowcs wcstombs :: libc.wcstombs -free :: #force_inline proc(ptr: $T) where intrinsics.type_is_pointer(T) || intrinsics.type_is_multi_pointer(T) || T == cstring { +free :: #force_inline proc "c" (ptr: $T) where intrinsics.type_is_pointer(T) || intrinsics.type_is_multi_pointer(T) || T == cstring { libc.free(rawptr(ptr)) } diff --git a/core/sys/posix/string.odin b/core/sys/posix/string.odin index 3f9dbb43e..3d0c5b7a2 100644 --- a/core/sys/posix/string.odin +++ b/core/sys/posix/string.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/string_libc.odin b/core/sys/posix/string_libc.odin index 72164cc4c..d689847ee 100644 --- a/core/sys/posix/string_libc.odin +++ b/core/sys/posix/string_libc.odin @@ -4,7 +4,7 @@ package posix when ODIN_OS == .Windows { foreign import lib "system:libucrt.lib" } else when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/sys_ipc.odin b/core/sys/posix/sys_ipc.odin index bf5938ce1..5814c7211 100644 --- a/core/sys/posix/sys_ipc.odin +++ b/core/sys/posix/sys_ipc.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/sys_mman.odin b/core/sys/posix/sys_mman.odin index 0594672ae..1bf3615dc 100644 --- a/core/sys/posix/sys_mman.odin +++ b/core/sys/posix/sys_mman.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } @@ -92,7 +92,12 @@ foreign lib { [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_open.html ]] */ - shm_open :: proc(name: cstring, oflag: O_Flags, mode: mode_t) -> FD --- + when ODIN_OS == .Darwin { + // https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/shm_open.2.html + shm_open :: proc(name: cstring, oflag: O_Flags, #c_vararg args: ..any) -> FD --- + } else { + shm_open :: proc(name: cstring, oflag: O_Flags, mode: mode_t) -> FD --- + } /* Removes a shared memory object. diff --git a/core/sys/posix/sys_msg.odin b/core/sys/posix/sys_msg.odin index c578b1fc6..87d5089ea 100644 --- a/core/sys/posix/sys_msg.odin +++ b/core/sys/posix/sys_msg.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/sys_resource.odin b/core/sys/posix/sys_resource.odin index ae478382a..a748c2bba 100644 --- a/core/sys/posix/sys_resource.odin +++ b/core/sys/posix/sys_resource.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/sys_select.odin b/core/sys/posix/sys_select.odin index a75e58de6..117dee625 100644 --- a/core/sys/posix/sys_select.odin +++ b/core/sys/posix/sys_select.odin @@ -6,7 +6,7 @@ import "base:intrinsics" import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/sys_sem.odin b/core/sys/posix/sys_sem.odin index 069315f87..e876cf74a 100644 --- a/core/sys/posix/sys_sem.odin +++ b/core/sys/posix/sys_sem.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/sys_shm.odin b/core/sys/posix/sys_shm.odin index 8f3c56b9c..8ee16e5a3 100644 --- a/core/sys/posix/sys_shm.odin +++ b/core/sys/posix/sys_shm.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/sys_socket.odin b/core/sys/posix/sys_socket.odin index 0645893d0..812451219 100644 --- a/core/sys/posix/sys_socket.odin +++ b/core/sys/posix/sys_socket.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import libc "system:System.framework" + foreign import libc "system:System" } else { foreign import libc "system:c" } diff --git a/core/sys/posix/sys_stat.odin b/core/sys/posix/sys_stat.odin index 265356e54..df0bf2b49 100644 --- a/core/sys/posix/sys_stat.odin +++ b/core/sys/posix/sys_stat.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/sys_statvfs.odin b/core/sys/posix/sys_statvfs.odin index 47c810135..1a332c5ce 100644 --- a/core/sys/posix/sys_statvfs.odin +++ b/core/sys/posix/sys_statvfs.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/sys_time.odin b/core/sys/posix/sys_time.odin index 94eafec85..058166759 100644 --- a/core/sys/posix/sys_time.odin +++ b/core/sys/posix/sys_time.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/sys_times.odin b/core/sys/posix/sys_times.odin index 73db489a7..636d3e153 100644 --- a/core/sys/posix/sys_times.odin +++ b/core/sys/posix/sys_times.odin @@ -2,7 +2,7 @@ package posix when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/sys_uio.odin b/core/sys/posix/sys_uio.odin index 5770f8058..b4411851b 100644 --- a/core/sys/posix/sys_uio.odin +++ b/core/sys/posix/sys_uio.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import libc "system:System.framework" + foreign import libc "system:System" } else { foreign import libc "system:c" } diff --git a/core/sys/posix/sys_utsname.odin b/core/sys/posix/sys_utsname.odin index 5ea8807a7..61f88b584 100644 --- a/core/sys/posix/sys_utsname.odin +++ b/core/sys/posix/sys_utsname.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/sys_wait.odin b/core/sys/posix/sys_wait.odin index d3bcdfddd..e12fcd212 100644 --- a/core/sys/posix/sys_wait.odin +++ b/core/sys/posix/sys_wait.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/termios.odin b/core/sys/posix/termios.odin index 4ca884e87..b385b7097 100644 --- a/core/sys/posix/termios.odin +++ b/core/sys/posix/termios.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/time.odin b/core/sys/posix/time.odin index 88f0153f4..7d55cf15b 100644 --- a/core/sys/posix/time.odin +++ b/core/sys/posix/time.odin @@ -5,7 +5,7 @@ import "core:c" import "core:c/libc" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/ulimit.odin b/core/sys/posix/ulimit.odin index 0f87641fa..892f6dee4 100644 --- a/core/sys/posix/ulimit.odin +++ b/core/sys/posix/ulimit.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/unistd.odin b/core/sys/posix/unistd.odin index b8020317c..b05f1e4fa 100644 --- a/core/sys/posix/unistd.odin +++ b/core/sys/posix/unistd.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/unistd_libc.odin b/core/sys/posix/unistd_libc.odin index 74edb6862..85d019f21 100644 --- a/core/sys/posix/unistd_libc.odin +++ b/core/sys/posix/unistd_libc.odin @@ -6,7 +6,7 @@ import "core:c" when ODIN_OS == .Windows { foreign import lib "system:libucrt.lib" } else when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/utime.odin b/core/sys/posix/utime.odin index 98c8166d6..fca0dee59 100644 --- a/core/sys/posix/utime.odin +++ b/core/sys/posix/utime.odin @@ -2,7 +2,7 @@ package posix when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/posix/wordexp.odin b/core/sys/posix/wordexp.odin index a9e6f39a7..92d6aba40 100644 --- a/core/sys/posix/wordexp.odin +++ b/core/sys/posix/wordexp.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else { foreign import lib "system:c" } diff --git a/core/sys/unix/sysctl_freebsd.odin b/core/sys/unix/sysctl_freebsd.odin index f5fee6c6c..cdd591a5b 100644 --- a/core/sys/unix/sysctl_freebsd.odin +++ b/core/sys/unix/sysctl_freebsd.odin @@ -3,7 +3,7 @@ package unix import "base:intrinsics" -sysctl :: proc(mib: []i32, val: ^$T) -> (ok: bool) { +sysctl :: proc "contextless" (mib: []i32, val: ^$T) -> (ok: bool) { mib := mib result_size := u64(size_of(T)) diff --git a/core/sys/unix/sysctl_netbsd.odin b/core/sys/unix/sysctl_netbsd.odin index ad89b9ad4..b70740721 100644 --- a/core/sys/unix/sysctl_netbsd.odin +++ b/core/sys/unix/sysctl_netbsd.odin @@ -8,7 +8,7 @@ foreign libc { @(link_name="sysctl") _unix_sysctl :: proc(name: [^]i32, namelen: u32, oldp: rawptr, oldlenp: ^c.size_t, newp: rawptr, newlen: c.size_t) -> i32 --- } -sysctl :: proc(mib: []i32, val: ^$T) -> (ok: bool) { +sysctl :: proc "contextless" (mib: []i32, val: ^$T) -> (ok: bool) { mib := mib result_size := c.size_t(size_of(T)) res := _unix_sysctl(raw_data(mib), u32(len(mib)), val, &result_size, nil, 0) diff --git a/core/sys/unix/sysctl_openbsd.odin b/core/sys/unix/sysctl_openbsd.odin index 49c9b6336..e71b743f8 100644 --- a/core/sys/unix/sysctl_openbsd.odin +++ b/core/sys/unix/sysctl_openbsd.odin @@ -9,7 +9,7 @@ foreign libc { @(link_name="sysctl") _unix_sysctl :: proc(name: [^]i32, namelen: u32, oldp: rawptr, oldlenp: ^c.size_t, newp: rawptr, newlen: c.size_t) -> i32 --- } -sysctl :: proc(mib: []i32, val: ^$T) -> (ok: bool) { +sysctl :: proc "contextless" (mib: []i32, val: ^$T) -> (ok: bool) { mib := mib result_size := c.size_t(size_of(T)) res := _unix_sysctl(raw_data(mib), u32(len(mib)), val, &result_size, nil, 0) diff --git a/core/sys/wasm/js/events.odin b/core/sys/wasm/js/events.odin index ffa3a1202..f5a47c06b 100644 --- a/core/sys/wasm/js/events.odin +++ b/core/sys/wasm/js/events.odin @@ -189,7 +189,7 @@ Key_Location :: enum u8 { KEYBOARD_MAX_KEY_SIZE :: 32 KEYBOARD_MAX_CODE_SIZE :: 32 -GAMEPAD_MAX_ID_SIZE :: 64 +GAMEPAD_MAX_ID_SIZE :: 96 GAMEPAD_MAX_MAPPING_SIZE :: 64 GAMEPAD_MAX_BUTTONS :: 64 @@ -239,6 +239,12 @@ Gamepad_State :: struct { _mapping_buf: [GAMEPAD_MAX_MAPPING_SIZE]byte `fmt:"-"`, } +Pointer_Type :: enum u8 { + Mouse, + Pen, + Touch, +} + Event :: struct { kind: Event_Kind, target_kind: Event_Target_Kind, @@ -275,6 +281,8 @@ Event :: struct { repeat: bool, + char: rune, + _key_len: int `fmt:"-"`, _code_len: int `fmt:"-"`, _key_buf: [KEYBOARD_MAX_KEY_SIZE]byte `fmt:"-"`, @@ -295,6 +303,21 @@ Event :: struct { button: i16, buttons: bit_set[0..<16; u16], + + pointer: struct { + altitude_angle: f64, + azimuth_angle: f64, + persistent_device_id: int, + pointer_id: int, + width: int, + height: int, + pressure: f64, + tangential_pressure: f64, + tilt: [2]f64, + twist: f64, + pointer_type: Pointer_Type, + is_primary: bool, + }, }, gamepad: Gamepad_State, @@ -323,13 +346,13 @@ add_event_listener :: proc(id: string, kind: Event_Kind, user_data: rawptr, call return _add_event_listener(id, event_kind_string[kind], kind, user_data, callback, use_capture) } -remove_event_listener :: proc(id: string, kind: Event_Kind, user_data: rawptr, callback: proc(e: Event)) -> bool { +remove_event_listener :: proc(id: string, kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool { @(default_calling_convention="contextless") foreign dom_lib { @(link_name="remove_event_listener") - _remove_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc "odin" (Event)) -> bool --- + _remove_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc "odin" (Event), use_capture: bool) -> bool --- } - return _remove_event_listener(id, event_kind_string[kind], user_data, callback) + return _remove_event_listener(id, event_kind_string[kind], user_data, callback, use_capture) } add_window_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool { @@ -341,20 +364,46 @@ add_window_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: return _add_window_event_listener(event_kind_string[kind], kind, user_data, callback, use_capture) } -remove_window_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: proc(e: Event)) -> bool { +remove_window_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool { @(default_calling_convention="contextless") foreign dom_lib { @(link_name="remove_window_event_listener") - _remove_window_event_listener :: proc(name: string, user_data: rawptr, callback: proc "odin" (Event)) -> bool --- + _remove_window_event_listener :: proc(name: string, user_data: rawptr, callback: proc "odin" (Event), use_capture: bool) -> bool --- + } + return _remove_window_event_listener(event_kind_string[kind], user_data, callback, use_capture) +} + +add_document_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="add_document_event_listener") + _add_document_event_listener :: proc(name: string, name_code: Event_Kind, user_data: rawptr, callback: proc "odin" (Event), use_capture: bool) -> bool --- } - return _remove_window_event_listener(event_kind_string[kind], user_data, callback) + return _add_document_event_listener(event_kind_string[kind], kind, user_data, callback, use_capture) +} + +remove_document_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool { + @(default_calling_convention="contextless") + foreign dom_lib { + @(link_name="remove_document_event_listener") + _remove_document_event_listener :: proc(name: string, user_data: rawptr, callback: proc "odin" (Event), use_capture: bool) -> bool --- + } + return _remove_document_event_listener(event_kind_string[kind], user_data, callback, use_capture) } remove_event_listener_from_event :: proc(e: Event) -> bool { + from_use_capture_false: bool + from_use_capture_true: bool if e.id == "" { - return remove_window_event_listener(e.kind, e.user_data, e.callback) + from_use_capture_false = remove_window_event_listener(e.kind, e.user_data, e.callback, false) + from_use_capture_true = remove_window_event_listener(e.kind, e.user_data, e.callback, true) + from_use_capture_false |= remove_document_event_listener(e.kind, e.user_data, e.callback, false) + from_use_capture_true |= remove_document_event_listener(e.kind, e.user_data, e.callback, true) + } else { + from_use_capture_false = remove_event_listener(e.id, e.kind, e.user_data, e.callback, false) + from_use_capture_true = remove_event_listener(e.id, e.kind, e.user_data, e.callback, true) } - return remove_event_listener(e.id, e.kind, e.user_data, e.callback) + return from_use_capture_false || from_use_capture_true } add_custom_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool { @@ -365,13 +414,13 @@ add_custom_event_listener :: proc(id: string, name: string, user_data: rawptr, c } return _add_event_listener(id, name, .Custom, user_data, callback, use_capture) } -remove_custom_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc(e: Event)) -> bool { +remove_custom_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool { @(default_calling_convention="contextless") foreign dom_lib { @(link_name="remove_event_listener") - _remove_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc "odin" (Event)) -> bool --- + _remove_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc "odin" (Event), use_capture: bool) -> bool --- } - return _remove_event_listener(id, name, user_data, callback) + return _remove_event_listener(id, name, user_data, callback, use_capture) } get_gamepad_state :: proc "contextless" (index: int, s: ^Gamepad_State) -> bool { @@ -384,7 +433,14 @@ get_gamepad_state :: proc "contextless" (index: int, s: ^Gamepad_State) -> bool if s == nil { return false } - return _get_gamepad_state(index, s) + + if !_get_gamepad_state(index, s) { + return false + } + + s.id = string(s._id_buf[:s._id_len]) + s.mapping = string(s._mapping_buf[:s._mapping_len]) + return true } @@ -415,4 +471,4 @@ do_event_callback :: proc(user_data: rawptr, callback: proc(e: Event)) { callback(event) } -}
\ No newline at end of file +} diff --git a/core/sys/wasm/js/events_all_targets.odin b/core/sys/wasm/js/events_all_targets.odin index b7e01ca10..903252c7a 100644 --- a/core/sys/wasm/js/events_all_targets.odin +++ b/core/sys/wasm/js/events_all_targets.odin @@ -263,7 +263,7 @@ add_event_listener :: proc(id: string, kind: Event_Kind, user_data: rawptr, call panic("vendor:wasm/js not supported on non JS targets") } -remove_event_listener :: proc(id: string, kind: Event_Kind, user_data: rawptr, callback: proc(e: Event)) -> bool { +remove_event_listener :: proc(id: string, kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool { panic("vendor:wasm/js not supported on non JS targets") } @@ -271,7 +271,15 @@ add_window_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: panic("vendor:wasm/js not supported on non JS targets") } -remove_window_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: proc(e: Event)) -> bool { +remove_window_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool { + panic("vendor:wasm/js not supported on non JS targets") +} + +add_document_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool { + panic("vendor:wasm/js not supported on non JS targets") +} + +remove_document_event_listener :: proc(kind: Event_Kind, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool { panic("vendor:wasm/js not supported on non JS targets") } @@ -282,6 +290,6 @@ remove_event_listener_from_event :: proc(e: Event) -> bool { add_custom_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool { panic("vendor:wasm/js not supported on non JS targets") } -remove_custom_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc(e: Event)) -> bool { +remove_custom_event_listener :: proc(id: string, name: string, user_data: rawptr, callback: proc(e: Event), use_capture := false) -> bool { panic("vendor:wasm/js not supported on non JS targets") -}
\ No newline at end of file +} diff --git a/core/sys/wasm/js/general.odin b/core/sys/wasm/js/general.odin index 4ed2ae298..22bb08e2b 100644 --- a/core/sys/wasm/js/general.odin +++ b/core/sys/wasm/js/general.odin @@ -9,4 +9,5 @@ foreign odin_env { abort :: proc() -> ! --- alert :: proc(msg: string) --- evaluate :: proc(str: string) --- -}
\ No newline at end of file + open :: proc(url: string, name := "", specs := "") --- +} diff --git a/core/sys/wasm/js/odin.js b/core/sys/wasm/js/odin.js index 29227c526..f4f73a42a 100644 --- a/core/sys/wasm/js/odin.js +++ b/core/sys/wasm/js/odin.js @@ -17,7 +17,7 @@ class WasmMemoryInterface { constructor() { this.memory = null; this.exports = null; - this.listenerMap = {}; + this.listenerMap = new Map(); // Size (in bytes) of the integer type, should be 4 on `js_wasm32` and 8 on `js_wasm64p32` this.intSize = 4; @@ -110,16 +110,12 @@ class WasmMemoryInterface { } loadCstring(ptr) { - return this.loadCstringDirect(this.loadPtr(ptr)); - } - - loadCstringDirect(start) { - if (start == 0) { + if (ptr == 0) { return null; } let len = 0; - for (; this.mem.getUint8(start+len) != 0; len += 1) {} - return this.loadString(start, len); + for (; this.mem.getUint8(ptr+len) != 0; len += 1) {} + return this.loadString(ptr, len); } storeU8(addr, value) { this.mem.setUint8 (addr, value); } @@ -396,12 +392,18 @@ class WebGLInterface { BindTexture: (target, texture) => { this.ctx.bindTexture(target, texture ? this.textures[texture] : null) }, + BindRenderbuffer: (target, renderbuffer) => { + this.ctx.bindRenderbuffer(target, renderbuffer ? this.renderbuffers[renderbuffer] : null) + }, BlendColor: (red, green, blue, alpha) => { this.ctx.blendColor(red, green, blue, alpha); }, BlendEquation: (mode) => { this.ctx.blendEquation(mode); }, + BlendEquationSeparate: (modeRGB, modeAlpha) => { + this.ctx.blendEquationSeparate(modeRGB, modeAlpha); + }, BlendFunc: (sfactor, dfactor) => { this.ctx.blendFunc(sfactor, dfactor); }, @@ -633,6 +635,13 @@ class WebGLInterface { GetParameter: (pname) => { return this.ctx.getParameter(pname); }, + GetParameter4i: (pname, v0, v1, v2, v3) => { + const i4 = this.ctx.getParameter(pname); + this.mem.storeI32(v0, i4[0]); + this.mem.storeI32(v1, i4[1]); + this.mem.storeI32(v2, i4[2]); + this.mem.storeI32(v3, i4[3]); + }, GetProgramParameter: (program, pname) => { return this.ctx.getProgramParameter(this.programs[program], pname) }, @@ -803,6 +812,40 @@ class WebGLInterface { Uniform3i: (location, v0, v1, v2) => { this.ctx.uniform3i(this.uniforms[location], v0, v1, v2); }, Uniform4i: (location, v0, v1, v2, v3) => { this.ctx.uniform4i(this.uniforms[location], v0, v1, v2, v3); }, + Uniform1fv: (location, count, addr) => { + let array = this.mem.loadF32Array(addr, 1*count); + this.ctx.uniform1fv(this.uniforms[location], array); + }, + Uniform2fv: (location, count, addr) => { + let array = this.mem.loadF32Array(addr, 2*count); + this.ctx.uniform2fv(this.uniforms[location], array); + }, + Uniform3fv: (location, count, addr) => { + let array = this.mem.loadF32Array(addr, 3*count); + this.ctx.uniform3fv(this.uniforms[location], array); + }, + Uniform4fv: (location, count, addr) => { + let array = this.mem.loadF32Array(addr, 4*count); + this.ctx.uniform4fv(this.uniforms[location], array); + }, + + Uniform1iv: (location, count, addr) => { + let array = this.mem.loadI32Array(addr, 1*count); + this.ctx.uniform1iv(this.uniforms[location], array); + }, + Uniform2iv: (location, count, addr) => { + let array = this.mem.loadI32Array(addr, 2*count); + this.ctx.uniform2iv(this.uniforms[location], array); + }, + Uniform3iv: (location, count, addr) => { + let array = this.mem.loadI32Array(addr, 3*count); + this.ctx.uniform3iv(this.uniforms[location], array); + }, + Uniform4iv: (location, count, addr) => { + let array = this.mem.loadI32Array(addr, 4*count); + this.ctx.uniform4iv(this.uniforms[location], array); + }, + UniformMatrix2fv: (location, addr) => { let array = this.mem.loadF32Array(addr, 2*2); this.ctx.uniformMatrix2fv(this.uniforms[location], false, array); @@ -1315,18 +1358,20 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { } else if (!line.includes("\n")) { currentLine[isError] = currentLine[isError].concat(line); } else { - let lines = line.split("\n"); + let lines = line.trimEnd().split("\n"); let printLast = lines.length > 1 && line.endsWith("\n"); println(currentLine[isError].concat(lines[0])); currentLine[isError] = ""; for (let i = 1; i < lines.length-1; i++) { println(lines[i]); } - let last = lines[lines.length-1]; - if (printLast) { - println(last); - } else { - currentLine[isError] = last; + if (lines.length > 1) { + let last = lines[lines.length-1]; + if (printLast) { + println(last); + } else { + currentLine[isError] = last; + } } } @@ -1393,6 +1438,10 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { info.scrollTop = info.scrollHeight; }; + const listener_key = (id, name, data, callback, useCapture) => { + return `${id}-${name}-data:${data}-callback:${callback}-useCapture:${useCapture}`; + }; + let webglContext = new WebGLInterface(wasmMemoryInterface); const env = {}; @@ -1421,6 +1470,13 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { abort: () => { Module.abort() }, evaluate: (str_ptr, str_len) => { eval.call(null, wasmMemoryInterface.loadString(str_ptr, str_len)); }, + open: (url_ptr, url_len, name_ptr, name_len, specs_ptr, specs_len) => { + const url = wasmMemoryInterface.loadString(url_ptr, url_len); + const name = wasmMemoryInterface.loadString(name_ptr, name_len); + const specs = wasmMemoryInterface.loadString(specs_ptr, specs_len); + window.open(url, name, specs); + }, + // return a bigint to be converted to i64 time_now: () => BigInt(Date.now()), tick_now: () => performance.now(), @@ -1533,6 +1589,29 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { wmi.storeI16(off(2), e.button); wmi.storeU16(off(2), e.buttons); + + if (e instanceof PointerEvent) { + wmi.storeF64(off(8), e.altitudeAngle); + wmi.storeF64(off(8), e.azimuthAngle); + wmi.storeInt(off(W), e.persistentDeviceId); + wmi.storeInt(off(W), e.pointerId); + wmi.storeInt(off(W), e.width); + wmi.storeInt(off(W), e.height); + wmi.storeF64(off(8), e.pressure); + wmi.storeF64(off(8), e.tangentialPressure); + wmi.storeF64(off(8), e.tiltX); + wmi.storeF64(off(8), e.tiltY); + wmi.storeF64(off(8), e.twist); + if (e.pointerType == "pen") { + wmi.storeU8(off(1), 1); + } else if (e.pointerType == "touch") { + wmi.storeU8(off(1), 2); + } else { + wmi.storeU8(off(1), 0); + } + wmi.storeU8(off(1), !!e.isPrimary); + } + } else if (e instanceof KeyboardEvent) { // Note: those strings are constructed // on the native side from buffers that @@ -1549,6 +1628,8 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { wmi.storeU8(off(1), !!e.repeat); + wmi.storeI32(off(4), e.charCode); + wmi.storeInt(off(W, W), e.key.length) wmi.storeInt(off(W, W), e.code.length) wmi.storeString(off(32, 1), e.key); @@ -1588,10 +1669,24 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { } } - wmi.storeInt(off(W, W), e.gamepad.id.length) - wmi.storeInt(off(W, W), e.gamepad.mapping.length) - wmi.storeString(off(64, 1), e.gamepad.id); - wmi.storeString(off(64, 1), e.gamepad.mapping); + let idLength = e.gamepad.id.length; + let id = e.gamepad.id; + if (idLength > 96) { + idLength = 96; + id = id.slice(0, 93) + '...'; + } + + let mappingLength = e.gamepad.mapping.length; + let mapping = e.gamepad.mapping; + if (mappingLength > 64) { + mappingLength = 61; + mapping = mapping.slice(0, 61) + '...'; + } + + wmi.storeInt(off(W, W), idLength); + wmi.storeInt(off(W, W), mappingLength); + wmi.storeString(off(96, 1), id); + wmi.storeString(off(64, 1), mapping); } }, @@ -1602,6 +1697,10 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { if (element == undefined) { return false; } + let key = listener_key(id, name, data, callback, !!use_capture); + if (wasmMemoryInterface.listenerMap.has(key)) { + return false; + } let listener = (e) => { let event_data = {}; @@ -1612,7 +1711,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { onEventReceived(event_data, data, callback); }; - wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener; + wasmMemoryInterface.listenerMap.set(key, listener); element.addEventListener(name, listener, !!use_capture); return true; }, @@ -1620,6 +1719,33 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { add_window_event_listener: (name_ptr, name_len, name_code, data, callback, use_capture) => { let name = wasmMemoryInterface.loadString(name_ptr, name_len); let element = window; + let key = listener_key('window', name, data, callback, !!use_capture); + if (wasmMemoryInterface.listenerMap.has(key)) { + return false; + } + + let listener = (e) => { + let event_data = {}; + event_data.id_ptr = 0; + event_data.id_len = 0; + event_data.event = e; + event_data.name_code = name_code; + + onEventReceived(event_data, data, callback); + }; + wasmMemoryInterface.listenerMap.set(key, listener); + element.addEventListener(name, listener, !!use_capture); + return true; + }, + + add_document_event_listener: (name_ptr, name_len, name_code, data, callback, use_capture) => { + let name = wasmMemoryInterface.loadString(name_ptr, name_len); + let element = document; + let key = listener_key('document', name, data, callback, !!use_capture); + if (wasmMemoryInterface.listenerMap.has(key)) { + return false; + } + let listener = (e) => { let event_data = {}; event_data.id_ptr = 0; @@ -1629,12 +1755,12 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { onEventReceived(event_data, data, callback); }; - wasmMemoryInterface.listenerMap[{data: data, callback: callback}] = listener; + wasmMemoryInterface.listenerMap.set(key, listener); element.addEventListener(name, listener, !!use_capture); return true; }, - remove_event_listener: (id_ptr, id_len, name_ptr, name_len, data, callback) => { + remove_event_listener: (id_ptr, id_len, name_ptr, name_len, data, callback, use_capture) => { let id = wasmMemoryInterface.loadString(id_ptr, id_len); let name = wasmMemoryInterface.loadString(name_ptr, name_len); let element = getElement(id); @@ -1642,24 +1768,42 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { return false; } - let listener = wasmMemoryInterface.listenerMap[{data: data, callback: callback}]; - if (listener == undefined) { + let key = listener_key(id, name, data, callback, !!use_capture); + let listener = wasmMemoryInterface.listenerMap.get(key); + if (listener === undefined) { return false; } - element.removeEventListener(name, listener); + wasmMemoryInterface.listenerMap.delete(key); + + element.removeEventListener(name, listener, !!use_capture); return true; }, - remove_window_event_listener: (name_ptr, name_len, data, callback) => { + remove_window_event_listener: (name_ptr, name_len, data, callback, use_capture) => { let name = wasmMemoryInterface.loadString(name_ptr, name_len); let element = window; - let key = {data: data, callback: callback}; - let listener = wasmMemoryInterface.listenerMap[key]; - if (!listener) { + + let key = listener_key('window', name, data, callback, !!use_capture); + let listener = wasmMemoryInterface.listenerMap.get(key); + if (listener === undefined) { return false; } - wasmMemoryInterface.listenerMap[key] = undefined; + wasmMemoryInterface.listenerMap.delete(key); - element.removeEventListener(name, listener); + element.removeEventListener(name, listener, !!use_capture); + return true; + }, + remove_document_event_listener: (name_ptr, name_len, data, callback, use_capture) => { + let name = wasmMemoryInterface.loadString(name_ptr, name_len); + let element = document; + + let key = listener_key('document', name, data, callback, !!use_capture); + let listener = wasmMemoryInterface.listenerMap.get(key); + if (listener === undefined) { + return false; + } + wasmMemoryInterface.listenerMap.delete(key); + + element.removeEventListener(name, listener, !!use_capture); return true; }, @@ -1756,10 +1900,24 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { } } - wmi.storeInt(off(W, W), gamepad.id.length) - wmi.storeInt(off(W, W), gamepad.mapping.length) - wmi.storeString(off(64, 1), gamepad.id); - wmi.storeString(off(64, 1), gamepad.mapping); + let idLength = gamepad.id.length; + let id = gamepad.id; + if (idLength > 96) { + idLength = 96; + id = id.slice(0, 93) + '...'; + } + + let mappingLength = gamepad.mapping.length; + let mapping = gamepad.mapping; + if (mappingLength > 64) { + mappingLength = 61; + mapping = mapping.slice(0, 61) + '...'; + } + + wmi.storeInt(off(W, W), idLength); + wmi.storeInt(off(W, W), mappingLength); + wmi.storeString(off(96, 1), id); + wmi.storeString(off(64, 1), mapping); return true; } @@ -1779,7 +1937,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { if (buf_len > 0 && buf_ptr) { let n = Math.min(buf_len, str.length); str = str.substring(0, n); - this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(str)) + wasmMemoryInterface.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(str)) return n; } } @@ -1843,7 +2001,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement, memory) { if (buf_len > 0 && buf_ptr) { let n = Math.min(buf_len, str.length); str = str.substring(0, n); - this.mem.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(str)) + wasmMemoryInterface.loadBytes(buf_ptr, buf_len).set(new TextEncoder().encode(str)) return n; } } diff --git a/core/sys/windows/comctl32.odin b/core/sys/windows/comctl32.odin index d954f952c..c7a166634 100644 --- a/core/sys/windows/comctl32.odin +++ b/core/sys/windows/comctl32.odin @@ -573,10 +573,10 @@ Button_GetTextMargin :: #force_inline proc "system" (hwnd: HWND, pmargin: ^RECT) return cast(BOOL)SendMessageW(hwnd, BCM_GETTEXTMARGIN, 0, cast(LPARAM)uintptr(pmargin)) } Button_SetNote :: #force_inline proc "system" (hwnd: HWND, psz: LPCWSTR) -> BOOL { - return cast(BOOL)SendMessageW(hwnd, BCM_SETNOTE, 0, cast(LPARAM)uintptr(psz)) + return cast(BOOL)SendMessageW(hwnd, BCM_SETNOTE, 0, cast(LPARAM)uintptr(rawptr(psz))) } Button_GetNote :: #force_inline proc "system" (hwnd: HWND, psz: LPCWSTR, pcc: ^c_int) -> BOOL { - return cast(BOOL)SendMessageW(hwnd, BCM_GETNOTE, uintptr(pcc), cast(LPARAM)uintptr(psz)) + return cast(BOOL)SendMessageW(hwnd, BCM_GETNOTE, uintptr(pcc), cast(LPARAM)uintptr(rawptr(psz))) } Button_GetNoteLength :: #force_inline proc "system" (hwnd: HWND) -> LRESULT { return SendMessageW(hwnd, BCM_GETNOTELENGTH, 0, 0) @@ -604,10 +604,10 @@ EDITBALLOONTIP :: struct { PEDITBALLOONTIP :: ^EDITBALLOONTIP Edit_SetCueBannerText :: #force_inline proc "system" (hwnd: HWND, lpcwText: LPCWSTR) -> BOOL { - return cast(BOOL)SendMessageW(hwnd, EM_SETCUEBANNER, 0, cast(LPARAM)uintptr(lpcwText)) + return cast(BOOL)SendMessageW(hwnd, EM_SETCUEBANNER, 0, cast(LPARAM)uintptr(rawptr(lpcwText))) } Edit_SetCueBannerTextFocused :: #force_inline proc "system" (hwnd: HWND, lpcwText: LPCWSTR, fDrawFocused: BOOL) -> BOOL { - return cast(BOOL)SendMessageW(hwnd, EM_SETCUEBANNER, cast(WPARAM)fDrawFocused, cast(LPARAM)uintptr(lpcwText)) + return cast(BOOL)SendMessageW(hwnd, EM_SETCUEBANNER, cast(WPARAM)fDrawFocused, cast(LPARAM)uintptr(rawptr(lpcwText))) } Edit_GetCueBannerText :: #force_inline proc "system" (hwnd: HWND, lpwText: LPWSTR, cchText: LONG) -> BOOL { return cast(BOOL)SendMessageW(hwnd, EM_GETCUEBANNER, uintptr(lpwText), cast(LPARAM)cchText) @@ -1197,7 +1197,7 @@ ListView_GetItemPosition :: #force_inline proc "system" (hwnd: HWND, i: c_int, p return cast(BOOL)SendMessageW(hwnd, LVM_GETITEMPOSITION, cast(WPARAM)i, cast(LPARAM)uintptr(ppt)) } ListView_GetStringWidth :: #force_inline proc "system" (hwndLV: HWND, psz: LPCWSTR) -> c_int { - return cast(c_int)SendMessageW(hwndLV, LVM_GETSTRINGWIDTHW, 0, cast(LPARAM)uintptr(psz)) + return cast(c_int)SendMessageW(hwndLV, LVM_GETSTRINGWIDTHW, 0, cast(LPARAM)uintptr(rawptr(psz))) } ListView_HitTest :: #force_inline proc "system" (hwndLV: HWND, pinfo: ^LV_HITTESTINFO) -> c_int { return cast(c_int)SendMessageW(hwndLV, LVM_HITTEST, 0, cast(LPARAM)uintptr(pinfo)) diff --git a/core/sys/windows/dwmapi.odin b/core/sys/windows/dwmapi.odin index 11e91c704..e86730660 100644 --- a/core/sys/windows/dwmapi.odin +++ b/core/sys/windows/dwmapi.odin @@ -39,10 +39,10 @@ DWMNCRENDERINGPOLICY :: enum { } DWM_WINDOW_CORNER_PREFERENCE :: enum c_int { - DEFAULT, - DONOTROUND, - ROUND, - ROUNDSMALL, + DEFAULT, + DONOTROUND, + ROUND, + ROUNDSMALL, } @(default_calling_convention="system") diff --git a/core/sys/windows/gdi32.odin b/core/sys/windows/gdi32.odin index 1d7a93d85..141429483 100644 --- a/core/sys/windows/gdi32.odin +++ b/core/sys/windows/gdi32.odin @@ -22,7 +22,7 @@ foreign gdi32 { CreateDIBPatternBrush :: proc(h: HGLOBAL, iUsage: UINT) -> HBRUSH --- CreateDIBitmap :: proc(hdc: HDC, pbmih: ^BITMAPINFOHEADER, flInit: DWORD, pjBits: VOID, pbmi: ^BITMAPINFO, iUsage: UINT) -> HBITMAP --- - CreateDIBSection :: proc(hdc: HDC, pbmi: ^BITMAPINFO, usage: UINT, ppvBits: VOID, hSection: HANDLE, offset: DWORD) -> HBITMAP --- + CreateDIBSection :: proc(hdc: HDC, pbmi: ^BITMAPINFO, usage: UINT, ppvBits: ^^VOID, hSection: HANDLE, offset: DWORD) -> HBITMAP --- StretchDIBits :: proc(hdc: HDC, xDest, yDest, DestWidth, DestHeight, xSrc, ySrc, SrcWidth, SrcHeight: INT, lpBits: VOID, lpbmi: ^BITMAPINFO, iUsage: UINT, rop: DWORD) -> INT --- StretchBlt :: proc(hdcDest: HDC, xDest, yDest, wDest, hDest: INT, hdcSrc: HDC, xSrc, ySrc, wSrc, hSrc: INT, rop: DWORD) -> BOOL --- @@ -350,4 +350,4 @@ NEWTEXTMETRICW :: struct { ntmAvgWidth: UINT, } -FONTENUMPROCW :: #type proc(lpelf: ^ENUMLOGFONTW, lpntm: ^NEWTEXTMETRICW, FontType: DWORD, lParam: LPARAM) -> INT +FONTENUMPROCW :: #type proc "system" (lpelf: ^ENUMLOGFONTW, lpntm: ^NEWTEXTMETRICW, FontType: DWORD, lParam: LPARAM) -> INT diff --git a/core/sys/windows/ip_helper.odin b/core/sys/windows/ip_helper.odin index 7a6e545ac..d2e75d531 100644 --- a/core/sys/windows/ip_helper.odin +++ b/core/sys/windows/ip_helper.odin @@ -38,9 +38,9 @@ IP_Adapter_Addresses :: struct { FirstAnycastAddress: ^IP_ADAPTER_ANYCAST_ADDRESS_XP, FirstMulticastAddress: ^IP_ADAPTER_MULTICAST_ADDRESS_XP, FirstDnsServerAddress: ^IP_ADAPTER_DNS_SERVER_ADDRESS_XP, - DnsSuffix: ^u16, - Description: ^u16, - FriendlyName: ^u16, + DnsSuffix: cstring16, + Description: cstring16, + FriendlyName: cstring16, PhysicalAddress: [8]u8, PhysicalAddressLength: u32, Anonymous2: struct #raw_union { diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index 266dcdbf4..ff27cf795 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -168,6 +168,7 @@ foreign kernel32 { ResumeThread :: proc(thread: HANDLE) -> DWORD --- GetThreadPriority :: proc(thread: HANDLE) -> c_int --- SetThreadPriority :: proc(thread: HANDLE, priority: c_int) -> BOOL --- + GetThreadDescription :: proc(hThread: HANDLE, ppszThreadDescription: ^PCWSTR) -> HRESULT --- SetThreadDescription :: proc(hThread: HANDLE, lpThreadDescription: PCWSTR) -> HRESULT --- GetExitCodeThread :: proc(thread: HANDLE, exit_code: ^DWORD) -> BOOL --- TerminateThread :: proc(thread: HANDLE, exit_code: DWORD) -> BOOL --- @@ -257,7 +258,7 @@ foreign kernel32 { ) -> BOOL --- CreateProcessW :: proc( lpApplicationName: LPCWSTR, - lpCommandLine: LPWSTR, + lpCommandLine: LPCWSTR, lpProcessAttributes: LPSECURITY_ATTRIBUTES, lpThreadAttributes: LPSECURITY_ATTRIBUTES, bInheritHandles: BOOL, @@ -372,6 +373,12 @@ foreign kernel32 { bInitialState: BOOL, lpName: LPCWSTR, ) -> HANDLE --- + CreateEventExW :: proc( + lpEventAttributes: LPSECURITY_ATTRIBUTES, + lpName: LPCWSTR, + dwFlags: DWORD, + dwDesiredAccess: DWORD, + ) -> HANDLE --- ResetEvent :: proc(hEvent: HANDLE) -> BOOL --- SetEvent :: proc(hEvent: HANDLE) -> BOOL --- WaitForMultipleObjects :: proc( @@ -406,6 +413,7 @@ foreign kernel32 { lpBytesLeftThisMessage: ^u32, ) -> BOOL --- CancelIo :: proc(handle: HANDLE) -> BOOL --- + CancelIoEx :: proc(hFile: HANDLE, lpOverlapped: LPOVERLAPPED) -> BOOL --- GetOverlappedResult :: proc( hFile: HANDLE, lpOverlapped: LPOVERLAPPED, @@ -547,6 +555,7 @@ foreign kernel32 { GetHandleInformation :: proc(hObject: HANDLE, lpdwFlags: ^DWORD) -> BOOL --- RtlCaptureStackBackTrace :: proc(FramesToSkip: ULONG, FramesToCapture: ULONG, BackTrace: [^]PVOID, BackTraceHash: PULONG) -> USHORT --- + RtlNtStatusToDosError :: proc(status: NTSTATUS) -> ULONG --- GetSystemPowerStatus :: proc(lpSystemPowerStatus: ^SYSTEM_POWER_STATUS) -> BOOL --- } @@ -857,7 +866,6 @@ MEMORY_RESOURCE_NOTIFICATION_TYPE :: enum c_int { LowMemoryResourceNotification :: MEMORY_RESOURCE_NOTIFICATION_TYPE.LowMemoryResourceNotification HighMemoryResourceNotification :: MEMORY_RESOURCE_NOTIFICATION_TYPE.HighMemoryResourceNotification - @(default_calling_convention="system") foreign kernel32 { CreateMemoryResourceNotification :: proc( @@ -1194,7 +1202,7 @@ DUMMYUNIONNAME_u :: struct #raw_union { SYSTEM_LOGICAL_PROCESSOR_INFORMATION :: struct { ProcessorMask: ULONG_PTR, Relationship: LOGICAL_PROCESSOR_RELATIONSHIP, - DummyUnion: DUMMYUNIONNAME_u, + using DummyUnion: DUMMYUNIONNAME_u, } SYSTEM_POWER_STATUS :: struct { diff --git a/core/sys/windows/ole32.odin b/core/sys/windows/ole32.odin index 8535a6f87..32cb6fd60 100644 --- a/core/sys/windows/ole32.odin +++ b/core/sys/windows/ole32.odin @@ -25,11 +25,12 @@ COINIT :: enum DWORD { SPEED_OVER_MEMORY = 0x8, } +IUnknown_UUID_STRING :: "00000000-0000-0000-C000-000000000046" +IUnknown_UUID := &IID{0x00000000, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}} +IUnknownVtbl :: IUnknown_VTable IUnknown :: struct { using _iunknown_vtable: ^IUnknown_VTable, } - -IUnknownVtbl :: IUnknown_VTable IUnknown_VTable :: struct { QueryInterface: proc "system" (This: ^IUnknown, riid: REFIID, ppvObject: ^rawptr) -> HRESULT, AddRef: proc "system" (This: ^IUnknown) -> ULONG, @@ -52,10 +53,12 @@ foreign Ole32 { ppv: ^LPVOID, ) -> HRESULT --- + CoTaskMemAlloc :: proc(cb: SIZE_T) -> rawptr --- + CoTaskMemRealloc :: proc(pv: rawptr, cb: SIZE_T) -> rawptr --- CoTaskMemFree :: proc(pv: rawptr) --- CLSIDFromProgID :: proc(lpszProgID: LPCOLESTR, lpclsid: LPCLSID) -> HRESULT --- - CLSIDFromProgIDEx :: proc(lpszProgID, LPCOLESTR, lpclsid: LPCLSID) -> HRESULT --- + CLSIDFromProgIDEx :: proc(lpszProgID: LPCOLESTR, lpclsid: LPCLSID) -> HRESULT --- CLSIDFromString :: proc(lpsz: LPOLESTR, pclsid: LPCLSID) -> HRESULT --- IIDFromString :: proc(lpsz: LPOLESTR, lpiid: LPIID) -> HRESULT --- ProgIDFromCLSID :: proc(clsid: REFCLSID, lplpszProgID: ^LPOLESTR) -> HRESULT --- diff --git a/core/sys/windows/scan_codes.odin b/core/sys/windows/scan_codes.odin new file mode 100644 index 000000000..54949c2f6 --- /dev/null +++ b/core/sys/windows/scan_codes.odin @@ -0,0 +1,172 @@ +#+build windows +package sys_windows + +// Win32 scan codes for QWERTY layout +// https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#scan-codes + +KB_SYS_POWERDOWN :: 0xE05E +KB_SYS_SLEEP :: 0xE05F +KB_SYS_WAKEUP :: 0xE063 +KB_ERR_ROLLOVER :: 0x00FF + +KB_A :: 0x001E +KB_B :: 0x0030 +KB_C :: 0x002E +KB_D :: 0x0020 +KB_E :: 0x0012 +KB_F :: 0x0021 +KB_G :: 0x0022 +KB_H :: 0x0023 +KB_I :: 0x0017 +KB_J :: 0x0024 +KB_K :: 0x0025 +KB_L :: 0x0026 +KB_M :: 0x0032 +KB_N :: 0x0031 +KB_O :: 0x0018 +KB_P :: 0x0019 +KB_Q :: 0x0010 +KB_R :: 0x0013 +KB_S :: 0x001F +KB_T :: 0x0014 +KB_U :: 0x0016 +KB_V :: 0x002F +KB_W :: 0x0011 +KB_X :: 0x002D +KB_Y :: 0x0015 +KB_Z :: 0x002C + +KB_1_BANG :: 0x0002 +KB_2_AT :: 0x0003 +KB_3_HASH :: 0x0004 +KB_4_DOLLAR :: 0x0005 +KB_5_PERCENT :: 0x0006 +KB_6_CARET :: 0x0007 +KB_7_AMPERSAND :: 0x0008 +KB_8_STAR :: 0x0009 +KB_9_LEFTBRACKET :: 0x000A +KB_0_RIGHTBRACKET :: 0x000B + +KB_RETURN_ENTER :: 0x001C +KB_ESCAPE :: 0x0001 +KB_DELETE :: 0x000E +KB_TAB :: 0x000F +KB_SPACEBAR :: 0x0039 +KB_DASH_UNDERSCORE :: 0x000C +KB_EQUALS_PLUS :: 0x000D +KB_LEFTBRACE :: 0x001A +KB_RIGHTBRACE :: 0x001B +KB_PIPE_SLASH :: 0x002B +KB_NONUS :: 0x002B +KB_SEMICOLON_COLON :: 0x0027 +KB_APOSTR_DOUBLEQUOT :: 0x0028 +KB_GRAVEACC_TILDE :: 0x0029 +KB_COMMA :: 0x0033 +KB_PERIOD :: 0x0034 +KB_QUESTIONMARK :: 0x0035 +KB_CAPSLOCK :: 0x003A + +KB_F1 :: 0x003B +KB_F2 :: 0x003C +KB_F3 :: 0x003D +KB_F4 :: 0x003E +KB_F5 :: 0x003F +KB_F6 :: 0x0040 +KB_F7 :: 0x0041 +KB_F8 :: 0x0042 +KB_F9 :: 0x0043 +KB_F10 :: 0x0044 +KB_F11 :: 0x0057 +KB_F12 :: 0x0058 + +KB_PRINTSCREEN :: 0xE037 +KB_SCROLLLOCK :: 0x0046 +KB_PAUSE :: 0xE11D45 +KB_INSERT :: 0xE052 +KB_HOME :: 0xE047 +KB_PAGEUP :: 0xE049 +KB_DELETEFORWARD :: 0xE053 +KB_END :: 0xE04F +KB_PAGEDOWN :: 0xE051 +KB_RIGHTARROW :: 0xE04D +KB_LEFTARROW :: 0xE04B +KB_DOWNARROW :: 0xE050 +KB_UPARROW :: 0xE048 + +KP_NUMLOCK_CLEAR :: 0x0045 +KP_FORWARDSLASH :: 0xE035 +KP_STAR :: 0x0037 +KP_DASH :: 0x004A +KP_PLUS :: 0x004E +KP_ENTER :: 0xE01C +KP_1_END :: 0x004F +KP_2_DOWNARROW :: 0x0050 +KP_3_PAGEDN :: 0x0051 +KP_4_LEFTARROW :: 0x004B +KP_5 :: 0x004C +KP_6_RIGHTARROW :: 0x004D +KP_7_HOME :: 0x0047 +KP_8_UPARROW :: 0x0048 +KP_9_PAGEUP :: 0x0049 +KP_0_INSERT :: 0x0052 +KP_PERIOD :: 0x0053 + +KB_NONUS_SLASHBAR :: 0x0056 +KB_APPLICATION :: 0xE05D +KB_POWER :: 0xE05E +KB_EQUALS :: 0x0059 +KB_F13 :: 0x0064 +KB_F14 :: 0x0065 +KB_F15 :: 0x0066 +KB_F16 :: 0x0067 +KB_F17 :: 0x0068 +KB_F18 :: 0x0069 +KB_F19 :: 0x006A +KB_F20 :: 0x006B +KB_F21 :: 0x006C +KB_F22 :: 0x006D +KB_F23 :: 0x006E +KB_F24 :: 0x0076 + +KP_COMMA :: 0x007E + +KB_INTERNATIONAL1 :: 0x0073 +KB_INTERNATIONAL2 :: 0x0070 +KB_INTERNATIONAL3 :: 0x007D +KB_INTERNATIONAL4 :: 0x0079 +KB_INTERNATIONAL5 :: 0x007B +KB_INTERNATIONAL6 :: 0x005C + +KB_LANG1 :: 0x0072 +KB_LANG2 :: 0x0071 +KB_LANG3 :: 0x0078 +KB_LANG4 :: 0x0077 +KB_LANG5 :: 0x0076 + +KB_LEFTCONTROL :: 0x001D +KB_LEFTSHIFT :: 0x002A +KB_LEFTALT :: 0x0038 +KB_LEFTGUI :: 0xE05B +KB_RIGHTCONTROL :: 0xE01D +KB_RIGHTSHIFT :: 0x0036 +KB_RIGHTALT :: 0xE038 +KB_RIGHTGUI :: 0xE05C + +FN_SCANNEXTTRACK :: 0xE019 +FN_SCANPREVTRACK :: 0xE010 +FN_STOP :: 0xE024 +FN_PLAY_PAUSE :: 0xE022 +FN_MUTE :: 0xE020 +FN_VOLUMEINC :: 0xE030 +FN_VOLUMEDEC :: 0xE02E +FN_AL_CONSUMERCTRLCONFIG :: 0xE06D +FN_AL_EMAILREADER :: 0xE06C +FN_AL_CALCULATOR :: 0xE021 +FN_AL_LOCALMACHINEBROWSER :: 0xE06B +FN_AC_SEARCH :: 0xE065 +FN_AC_HOME :: 0xE032 +FN_AC_BACK :: 0xE06A +FN_AC_FORWARD :: 0xE069 +FN_AC_STOP :: 0xE068 +FN_AC_REFRESH :: 0xE067 +FN_AC_BOOKMARKS :: 0xE066 diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index f698b63d4..904970589 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -107,8 +107,8 @@ PDWORD64 :: ^DWORD64 PDWORD_PTR :: ^DWORD_PTR ATOM :: distinct WORD -wstring :: [^]WCHAR -PWSTR :: [^]WCHAR +wstring :: cstring16 +PWSTR :: cstring16 PBYTE :: ^BYTE LPBYTE :: ^BYTE @@ -145,7 +145,7 @@ LPSTR :: ^CHAR LPWSTR :: ^WCHAR OLECHAR :: WCHAR BSTR :: ^OLECHAR -LPOLESTR :: ^OLECHAR +LPOLESTR :: cstring16 LPCOLESTR :: LPCSTR LPFILETIME :: ^FILETIME LPWSABUF :: ^WSABUF @@ -1698,7 +1698,7 @@ NM_FONTCHANGED :: NM_OUTOFMEMORY-22 NM_CUSTOMTEXT :: NM_OUTOFMEMORY-23 // uses NMCUSTOMTEXT struct NM_TVSTATEIMAGECHANGING :: NM_OUTOFMEMORY-23 // uses NMTVSTATEIMAGECHANGING struct, defined after HTREEITEM -PCZZWSTR :: ^WCHAR +PCZZWSTR :: cstring16 SHFILEOPSTRUCTW :: struct { hwnd: HWND, @@ -3385,6 +3385,19 @@ FILE_ATTRIBUTE_TAG_INFO :: struct { ReparseTag: DWORD, } +// getaddrinfo flags https://learn.microsoft.com/en-us/windows/win32/api/ws2def/ns-ws2def-addrinfoa +AI_PASSIVE :: 0x01 +AI_CANONNAME :: 0x02 +AI_NUMERICHOST :: 0x04 +AI_ALL :: 0x0100 +AI_ADDRCONFIG :: 0x0400 +AI_V4MAPPED :: 0x0800 +AI_NON_AUTHORITATIVE :: 0x04000 +AI_SECURE :: 0x08000 +AI_RETURN_PREFERRED_NAMES :: 0x010000 +AI_FQDN :: 0x00020000 +AI_FILESERVER :: 0x00040000 + PADDRINFOEXW :: ^ADDRINFOEXW LPADDRINFOEXW :: ^ADDRINFOEXW ADDRINFOEXW :: struct { @@ -4845,6 +4858,12 @@ SOMAXCONN :: 128 // The number of messages that can be queued in memory after SOCKET_ERROR :: -1 // Networking errors +WSA_INVALID_HANDLE :: 6 // Specified event object handle is invalid. +WSA_NOT_ENOUGH_MEMORY :: 8 // Insufficient memory available. +WSA_INVALID_PARAMETER :: 87 // One or more parameters are invalid. +WSA_OPERATION_ABORTED :: 995 // Overlapped operation aborted. +WSA_IO_INCOMPLETE :: 996 // Overlapped I/O event object not in signaled state. +WSA_IO_PENDING :: 997 // Overlapped operations will complete later. WSAEINTR :: 10004 // Call interrupted. CancelBlockingCall was called. (This is different on Linux.) WSAEACCES :: 10013 // If you try to bind a Udp socket to the broadcast address without the socket option set. WSAEFAULT :: 10014 // A pointer that was passed to a WSA function is invalid, such as a buffer size is smaller than you said it was diff --git a/core/sys/windows/user32.odin b/core/sys/windows/user32.odin index 94cd57811..49ebb49cb 100644 --- a/core/sys/windows/user32.odin +++ b/core/sys/windows/user32.odin @@ -47,6 +47,8 @@ foreign user32 { lpParam: LPVOID, ) -> HWND --- + GetWindowThreadProcessId :: proc(hwnd: HWND, lpdwProcessId: LPDWORD) -> DWORD --- + DestroyWindow :: proc(hWnd: HWND) -> BOOL --- ShowWindow :: proc(hWnd: HWND, nCmdShow: INT) -> BOOL --- diff --git a/core/sys/windows/util.odin b/core/sys/windows/util.odin index b3eb800bc..125038ac4 100644 --- a/core/sys/windows/util.odin +++ b/core/sys/windows/util.odin @@ -75,15 +75,7 @@ LANGIDFROMLCID :: #force_inline proc "contextless" (lcid: LCID) -> LANGID { return LANGID(lcid) } -// this one gave me trouble as it do not mask the values. -// the _ in the name is also off comparing to the c code -// i can't find any usage in the odin repo -@(deprecated = "use MAKEWORD") -MAKE_WORD :: #force_inline proc "contextless" (x, y: WORD) -> WORD { - return x << 8 | y -} - -utf8_to_utf16 :: proc(s: string, allocator := context.temp_allocator) -> []u16 { +utf8_to_utf16_alloc :: proc(s: string, allocator := context.temp_allocator) -> []u16 { if len(s) < 1 { return nil } @@ -109,14 +101,42 @@ utf8_to_utf16 :: proc(s: string, allocator := context.temp_allocator) -> []u16 { } return text[:n] } -utf8_to_wstring :: proc(s: string, allocator := context.temp_allocator) -> wstring { + +utf8_to_utf16_buf :: proc(buf: []u16, s: string) -> []u16 { + n1 := MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, raw_data(s), i32(len(s)), nil, 0) + if n1 == 0 { + return nil + } else if int(n1) > len(buf) { + return nil + } + + n1 = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, raw_data(s), i32(len(s)), raw_data(buf[:]), n1) + if n1 == 0 { + return nil + } else if int(n1) > len(buf) { + return nil + } + return buf[:n1] +} +utf8_to_utf16 :: proc{utf8_to_utf16_alloc, utf8_to_utf16_buf} + +utf8_to_wstring_alloc :: proc(s: string, allocator := context.temp_allocator) -> wstring { if res := utf8_to_utf16(s, allocator); len(res) > 0 { - return raw_data(res) + return wstring(raw_data(res)) } return nil } -wstring_to_utf8 :: proc(s: wstring, N: int, allocator := context.temp_allocator) -> (res: string, err: runtime.Allocator_Error) { +utf8_to_wstring_buf :: proc(buf: []u16, s: string) -> wstring { + if res := utf8_to_utf16(buf, s); len(res) > 0 { + return wstring(raw_data(res)) + } + return nil +} + +utf8_to_wstring :: proc{utf8_to_wstring_alloc, utf8_to_wstring_buf} + +wstring_to_utf8_alloc :: proc(s: wstring, N: int, allocator := context.temp_allocator) -> (res: string, err: runtime.Allocator_Error) { context.allocator = allocator if N == 0 { @@ -150,13 +170,78 @@ wstring_to_utf8 :: proc(s: wstring, N: int, allocator := context.temp_allocator) return string(text[:n]), nil } -utf16_to_utf8 :: proc(s: []u16, allocator := context.temp_allocator) -> (res: string, err: runtime.Allocator_Error) { +wstring_to_utf8_buf :: proc(buf: []u8, s: wstring, N := -1) -> (res: string) { + n := WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, s, i32(N), nil, 0, nil, nil) + if n == 0 { + return + } else if int(n) > len(buf) { + return + } + + n2 := WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, s, i32(N), raw_data(buf), n, nil, nil) + if n2 == 0 { + return + } else if int(n2) > len(buf) { + return + } + + for i in 0..<n2 { + if buf[i] == 0 { + n2 = i + break + } + } + return string(buf[:n2]) +} + +wstring_to_utf8 :: proc{wstring_to_utf8_alloc, wstring_to_utf8_buf} + +/* +Converts a UTF-16 string into a regular UTF-8 `string` and allocates the result. +If the input is null-terminated, only the part of the input string leading up +to it will be converted. + +*Allocates Using Provided Allocator* + +Inputs: +- s: The string to be converted +- allocator: (default: context.allocator) + +Returns: +- res: A cloned and converted string +- err: An optional allocator error if one occured, `nil` otherwise +*/ +utf16_to_utf8_alloc :: proc(s: []u16, allocator := context.temp_allocator) -> (res: string, err: runtime.Allocator_Error) { if len(s) == 0 { return "", nil } - return wstring_to_utf8(raw_data(s), len(s), allocator) + return wstring_to_utf8(wstring(raw_data(s)), len(s), allocator) } +/* +Converts a UTF-16 string into a regular UTF-8 `string`, using `buf` as its backing. +If the input is null-terminated, only the part of the input string leading up +to it will be converted. + +*Uses `buf` for backing* + +Inputs: +- s: The string to be converted +- buf: Backing buffer for result string + +Returns: +- res: A converted string, backed byu `buf` +*/ +utf16_to_utf8_buf :: proc(buf: []u8, s: []u16) -> (res: string) { + if len(s) == 0 { + return + } + return wstring_to_utf8(buf, wstring(raw_data(s)), len(s)) +} + +utf16_to_utf8 :: proc{utf16_to_utf8_alloc, utf16_to_utf8_buf} + + // AdvAPI32, NetAPI32 and UserENV helpers. allowed_username :: proc(username: string) -> bool { @@ -213,7 +298,7 @@ _add_user :: proc(servername: string, username: string, password: string) -> (ok servername_w = nil } else { server := utf8_to_utf16(servername, context.temp_allocator) - servername_w = &server[0] + servername_w = wstring(&server[0]) } if len(username) == 0 || len(username) > LM20_UNLEN { @@ -263,7 +348,7 @@ get_computer_name_and_account_sid :: proc(username: string) -> (computer_name: s res := LookupAccountNameW( nil, // Look on this computer first - &username_w[0], + wstring(&username_w[0]), &sid, &cbsid, nil, @@ -279,10 +364,10 @@ get_computer_name_and_account_sid :: proc(username: string) -> (computer_name: s res = LookupAccountNameW( nil, - &username_w[0], + wstring(&username_w[0]), &sid, &cbsid, - &cname_w[0], + wstring(&cname_w[0]), &computer_name_size, &pe_use, ) @@ -305,7 +390,7 @@ get_sid :: proc(username: string, sid: ^SID) -> (ok: bool) { res := LookupAccountNameW( nil, // Look on this computer first - &username_w[0], + wstring(&username_w[0]), sid, &cbsid, nil, @@ -321,10 +406,10 @@ get_sid :: proc(username: string, sid: ^SID) -> (ok: bool) { res = LookupAccountNameW( nil, - &username_w[0], + wstring(&username_w[0]), sid, &cbsid, - &cname_w[0], + wstring(&cname_w[0]), &computer_name_size, &pe_use, ) @@ -343,7 +428,7 @@ add_user_to_group :: proc(sid: ^SID, group: string) -> (ok: NET_API_STATUS) { group_name := utf8_to_utf16(group, context.temp_allocator) ok = NetLocalGroupAddMembers( nil, - &group_name[0], + wstring(&group_name[0]), 0, &group_member, 1, @@ -358,7 +443,7 @@ add_del_from_group :: proc(sid: ^SID, group: string) -> (ok: NET_API_STATUS) { group_name := utf8_to_utf16(group, context.temp_allocator) ok = NetLocalGroupDelMembers( nil, - &group_name[0], + cstring16(&group_name[0]), 0, &group_member, 1, @@ -380,19 +465,19 @@ add_user_profile :: proc(username: string) -> (ok: bool, profile_path: string) { if res == false { return false, "" } - defer LocalFree(sb) + defer LocalFree(rawptr(sb)) pszProfilePath := make([]u16, 257, context.temp_allocator) res2 := CreateProfile( sb, - &username_w[0], - &pszProfilePath[0], + cstring16(&username_w[0]), + cstring16(&pszProfilePath[0]), 257, ) if res2 != 0 { return false, "" } - profile_path = wstring_to_utf8(&pszProfilePath[0], 257) or_else "" + profile_path = wstring_to_utf8(wstring(&pszProfilePath[0]), 257) or_else "" return true, profile_path } @@ -410,7 +495,7 @@ delete_user_profile :: proc(username: string) -> (ok: bool) { if res == false { return false } - defer LocalFree(sb) + defer LocalFree(rawptr(sb)) res2 := DeleteProfileW( sb, @@ -463,13 +548,13 @@ delete_user :: proc(servername: string, username: string) -> (ok: bool) { servername_w = nil } else { server := utf8_to_utf16(servername, context.temp_allocator) - servername_w = &server[0] + servername_w = wstring(&server[0]) } username_w := utf8_to_utf16(username) res := NetUserDel( servername_w, - &username_w[0], + wstring(&username_w[0]), ) if res != .Success { return false @@ -501,9 +586,9 @@ run_as_user :: proc(username, password, application, commandline: string, pi: ^P user_token: HANDLE ok = bool(LogonUserW( - lpszUsername = &username_w[0], - lpszDomain = &domain_w[0], - lpszPassword = &password_w[0], + lpszUsername = wstring(&username_w[0]), + lpszDomain = wstring(&domain_w[0]), + lpszPassword = wstring(&password_w[0]), dwLogonType = .NEW_CREDENTIALS, dwLogonProvider = .WINNT50, phToken = &user_token, @@ -520,8 +605,8 @@ run_as_user :: proc(username, password, application, commandline: string, pi: ^P ok = bool(CreateProcessAsUserW( user_token, - &app_w[0], - &commandline_w[0], + wstring(&app_w[0]), + wstring(&commandline_w[0]), nil, // lpProcessAttributes, nil, // lpThreadAttributes, false, // bInheritHandles, @@ -543,7 +628,7 @@ run_as_user :: proc(username, password, application, commandline: string, pi: ^P } } -ensure_winsock_initialized :: proc() { +ensure_winsock_initialized :: proc "contextless" () { @static gate := false @static initted := false @@ -559,7 +644,7 @@ ensure_winsock_initialized :: proc() { unused_info: WSADATA version_requested := WORD(2) << 8 | 2 res := WSAStartup(version_requested, &unused_info) - assert(res == 0, "unable to initialized Winsock2") + assert_contextless(res == 0, "unable to initialized Winsock2") initted = true } diff --git a/core/sys/windows/wgl.odin b/core/sys/windows/wgl.odin index 8fea55c3d..f50f06939 100644 --- a/core/sys/windows/wgl.odin +++ b/core/sys/windows/wgl.odin @@ -82,7 +82,8 @@ foreign Opengl32 { wglSetLayerPaletteEntries :: proc(hdc: HDC, layer_plane, start, entries: c.int, cr: ^COLORREF) -> c.int --- wglShareLists :: proc(HGLRC1, HGLRC2: HGLRC) -> BOOL --- wglSwapLayerBuffers :: proc(hdc: HDC, planes: DWORD) -> BOOL --- - wglUseFontBitmaps :: proc(hdc: HDC, first, count, list_base: DWORD) -> BOOL --- + wglUseFontBitmapsA :: proc(hdc: HDC, first, count, list_base: DWORD) -> BOOL --- + wglUseFontBitmapsW :: proc(hdc: HDC, first, count, list_base: DWORD) -> BOOL --- wglUseFontOutlines :: proc(hdc: HDC, first, count, list_base: DWORD, deviation, extrusion: f32, format: c.int, gmf: LPGLYPHMETRICSFLOAT) -> BOOL --- } diff --git a/core/sys/windows/winerror.odin b/core/sys/windows/winerror.odin index 61a7d9d86..23467761d 100644 --- a/core/sys/windows/winerror.odin +++ b/core/sys/windows/winerror.odin @@ -173,6 +173,9 @@ FACILITY :: enum DWORD { EAS = 85, WEB = 885, WEB_SOCKET = 886, + XAUDIO2 = 896, + XAPO = 897, + GAMEINPUT = 906, MOBILE = 1793, SQLITE = 1967, SERVICE_FABRIC = 1968, @@ -219,9 +222,11 @@ ERROR_LOCK_FAILED : DWORD : 167 ERROR_ALREADY_EXISTS : DWORD : 183 ERROR_NO_DATA : DWORD : 232 ERROR_ENVVAR_NOT_FOUND : DWORD : 203 +ERROR_MR_MID_NOT_FOUND : DWORD : 317 ERROR_OPERATION_ABORTED : DWORD : 995 ERROR_IO_PENDING : DWORD : 997 ERROR_NO_UNICODE_TRANSLATION : DWORD : 1113 +ERROR_NOT_FOUND : DWORD : 1168 ERROR_TIMEOUT : DWORD : 1460 ERROR_DATATYPE_MISMATCH : DWORD : 1629 ERROR_UNSUPPORTED_TYPE : DWORD : 1630 @@ -231,6 +236,7 @@ ERROR_PIPE_BUSY : DWORD : 231 // https://learn.microsoft.com/en-us/windows/win32/seccrypto/common-hresult-values S_OK :: 0x00000000 // Operation successful +S_FALSE :: 0x00000001 E_NOTIMPL :: 0x80004001 // Not implemented E_NOINTERFACE :: 0x80004002 // No such interface supported E_POINTER :: 0x80004003 // Pointer that is not valid @@ -270,6 +276,10 @@ MAKE_HRESULT :: #force_inline proc "contextless" (#any_int sev: int, #any_int fa return HRESULT((uint(sev)<<31) | (uint(fac)<<16) | (uint(code))) } +HRESULT_FROM_WIN32 :: #force_inline proc "contextless" (#any_int code: int) -> HRESULT { + return HRESULT(code) <= 0 ? HRESULT(code) : HRESULT(uint(code & 0x0000FFFF) | (uint(FACILITY.WIN32) << 16) | 0x80000000) +} + DECODE_HRESULT :: #force_inline proc "contextless" (#any_int hr: int) -> (SEVERITY, FACILITY, int) { return HRESULT_SEVERITY(hr), HRESULT_FACILITY(hr), HRESULT_CODE(hr) } diff --git a/core/sys/windows/winmm.odin b/core/sys/windows/winmm.odin index 3c7ec80e7..b85548b85 100644 --- a/core/sys/windows/winmm.odin +++ b/core/sys/windows/winmm.odin @@ -265,7 +265,7 @@ HWAVE :: distinct HANDLE HWAVEIN :: distinct HANDLE HWAVEOUT :: distinct HANDLE -LPHWAVEIN :: ^HWAVEIN +LPHWAVEIN :: ^HWAVEIN LPHWAVEOUT :: ^HWAVEOUT // https://learn.microsoft.com/en-us/windows/win32/multimedia/multimedia-timer-structures @@ -311,10 +311,285 @@ MAXPNAMELEN :: 32 MAXERRORLENGTH :: 256 MMVERSION :: UINT +// Input is four characters string +// Output is little-endian u32 representation +MAKEFOURCC :: #force_inline proc "contextless" (s: [4]byte) -> DWORD { + return (DWORD(s[0])) | (DWORD(s[1]) << 8) | (DWORD(s[2]) << 16) | (DWORD(s[3]) << 24 ) +} + /* flags for wFormatTag field of WAVEFORMAT */ WAVE_FORMAT_PCM :: 1 -WAVEFORMATEX :: struct { +WAVE_FORMAT_UNKNOWN :: 0x0000 /* Microsoft Corporation */ +WAVE_FORMAT_ADPCM :: 0x0002 /* Microsoft Corporation */ +WAVE_FORMAT_IEEE_FLOAT :: 0x0003 /* Microsoft Corporation */ +WAVE_FORMAT_VSELP :: 0x0004 /* Compaq Computer Corp. */ +WAVE_FORMAT_IBM_CVSD :: 0x0005 /* IBM Corporation */ +WAVE_FORMAT_ALAW :: 0x0006 /* Microsoft Corporation */ +WAVE_FORMAT_MULAW :: 0x0007 /* Microsoft Corporation */ +WAVE_FORMAT_DTS :: 0x0008 /* Microsoft Corporation */ +WAVE_FORMAT_DRM :: 0x0009 /* Microsoft Corporation */ +WAVE_FORMAT_WMAVOICE9 :: 0x000A /* Microsoft Corporation */ +WAVE_FORMAT_WMAVOICE10 :: 0x000B /* Microsoft Corporation */ +WAVE_FORMAT_OKI_ADPCM :: 0x0010 /* OKI */ +WAVE_FORMAT_DVI_ADPCM :: 0x0011 /* Intel Corporation */ +WAVE_FORMAT_IMA_ADPCM :: WAVE_FORMAT_DVI_ADPCM /* Intel Corporation */ +WAVE_FORMAT_MEDIASPACE_ADPCM :: 0x0012 /* Videologic */ +WAVE_FORMAT_SIERRA_ADPCM :: 0x0013 /* Sierra Semiconductor Corp */ +WAVE_FORMAT_G723_ADPCM :: 0x0014 /* Antex Electronics Corporation */ +WAVE_FORMAT_DIGISTD :: 0x0015 /* DSP Solutions, Inc. */ +WAVE_FORMAT_DIGIFIX :: 0x0016 /* DSP Solutions, Inc. */ +WAVE_FORMAT_DIALOGIC_OKI_ADPCM :: 0x0017 /* Dialogic Corporation */ +WAVE_FORMAT_MEDIAVISION_ADPCM :: 0x0018 /* Media Vision, Inc. */ +WAVE_FORMAT_CU_CODEC :: 0x0019 /* Hewlett-Packard Company */ +WAVE_FORMAT_HP_DYN_VOICE :: 0x001A /* Hewlett-Packard Company */ +WAVE_FORMAT_YAMAHA_ADPCM :: 0x0020 /* Yamaha Corporation of America */ +WAVE_FORMAT_SONARC :: 0x0021 /* Speech Compression */ +WAVE_FORMAT_DSPGROUP_TRUESPEECH :: 0x0022 /* DSP Group, Inc */ +WAVE_FORMAT_ECHOSC1 :: 0x0023 /* Echo Speech Corporation */ +WAVE_FORMAT_AUDIOFILE_AF36 :: 0x0024 /* Virtual Music, Inc. */ +WAVE_FORMAT_APTX :: 0x0025 /* Audio Processing Technology */ +WAVE_FORMAT_AUDIOFILE_AF10 :: 0x0026 /* Virtual Music, Inc. */ +WAVE_FORMAT_PROSODY_1612 :: 0x0027 /* Aculab plc */ +WAVE_FORMAT_LRC :: 0x0028 /* Merging Technologies S.A. */ +WAVE_FORMAT_DOLBY_AC2 :: 0x0030 /* Dolby Laboratories */ +WAVE_FORMAT_GSM610 :: 0x0031 /* Microsoft Corporation */ +WAVE_FORMAT_MSNAUDIO :: 0x0032 /* Microsoft Corporation */ +WAVE_FORMAT_ANTEX_ADPCME :: 0x0033 /* Antex Electronics Corporation */ +WAVE_FORMAT_CONTROL_RES_VQLPC :: 0x0034 /* Control Resources Limited */ +WAVE_FORMAT_DIGIREAL :: 0x0035 /* DSP Solutions, Inc. */ +WAVE_FORMAT_DIGIADPCM :: 0x0036 /* DSP Solutions, Inc. */ +WAVE_FORMAT_CONTROL_RES_CR10 :: 0x0037 /* Control Resources Limited */ +WAVE_FORMAT_NMS_VBXADPCM :: 0x0038 /* Natural MicroSystems */ +WAVE_FORMAT_CS_IMAADPCM :: 0x0039 /* Crystal Semiconductor IMA ADPCM */ +WAVE_FORMAT_ECHOSC3 :: 0x003A /* Echo Speech Corporation */ +WAVE_FORMAT_ROCKWELL_ADPCM :: 0x003B /* Rockwell International */ +WAVE_FORMAT_ROCKWELL_DIGITALK :: 0x003C /* Rockwell International */ +WAVE_FORMAT_XEBEC :: 0x003D /* Xebec Multimedia Solutions Limited */ +WAVE_FORMAT_G721_ADPCM :: 0x0040 /* Antex Electronics Corporation */ +WAVE_FORMAT_G728_CELP :: 0x0041 /* Antex Electronics Corporation */ +WAVE_FORMAT_MSG723 :: 0x0042 /* Microsoft Corporation */ +WAVE_FORMAT_INTEL_G723_1 :: 0x0043 /* Intel Corp. */ +WAVE_FORMAT_INTEL_G729 :: 0x0044 /* Intel Corp. */ +WAVE_FORMAT_SHARP_G726 :: 0x0045 /* Sharp */ +WAVE_FORMAT_MPEG :: 0x0050 /* Microsoft Corporation */ +WAVE_FORMAT_RT24 :: 0x0052 /* InSoft, Inc. */ +WAVE_FORMAT_PAC :: 0x0053 /* InSoft, Inc. */ +WAVE_FORMAT_MPEGLAYER3 :: 0x0055 /* ISO/MPEG Layer3 Format Tag */ +WAVE_FORMAT_LUCENT_G723 :: 0x0059 /* Lucent Technologies */ +WAVE_FORMAT_CIRRUS :: 0x0060 /* Cirrus Logic */ +WAVE_FORMAT_ESPCM :: 0x0061 /* ESS Technology */ +WAVE_FORMAT_VOXWARE :: 0x0062 /* Voxware Inc */ +WAVE_FORMAT_CANOPUS_ATRAC :: 0x0063 /* Canopus, co., Ltd. */ +WAVE_FORMAT_G726_ADPCM :: 0x0064 /* APICOM */ +WAVE_FORMAT_G722_ADPCM :: 0x0065 /* APICOM */ +WAVE_FORMAT_DSAT :: 0x0066 /* Microsoft Corporation */ +WAVE_FORMAT_DSAT_DISPLAY :: 0x0067 /* Microsoft Corporation */ +WAVE_FORMAT_VOXWARE_BYTE_ALIGNED :: 0x0069 /* Voxware Inc */ +WAVE_FORMAT_VOXWARE_AC8 :: 0x0070 /* Voxware Inc */ +WAVE_FORMAT_VOXWARE_AC10 :: 0x0071 /* Voxware Inc */ +WAVE_FORMAT_VOXWARE_AC16 :: 0x0072 /* Voxware Inc */ +WAVE_FORMAT_VOXWARE_AC20 :: 0x0073 /* Voxware Inc */ +WAVE_FORMAT_VOXWARE_RT24 :: 0x0074 /* Voxware Inc */ +WAVE_FORMAT_VOXWARE_RT29 :: 0x0075 /* Voxware Inc */ +WAVE_FORMAT_VOXWARE_RT29HW :: 0x0076 /* Voxware Inc */ +WAVE_FORMAT_VOXWARE_VR12 :: 0x0077 /* Voxware Inc */ +WAVE_FORMAT_VOXWARE_VR18 :: 0x0078 /* Voxware Inc */ +WAVE_FORMAT_VOXWARE_TQ40 :: 0x0079 /* Voxware Inc */ +WAVE_FORMAT_VOXWARE_SC3 :: 0x007A /* Voxware Inc */ +WAVE_FORMAT_VOXWARE_SC3_1 :: 0x007B /* Voxware Inc */ +WAVE_FORMAT_SOFTSOUND :: 0x0080 /* Softsound, Ltd. */ +WAVE_FORMAT_VOXWARE_TQ60 :: 0x0081 /* Voxware Inc */ +WAVE_FORMAT_MSRT24 :: 0x0082 /* Microsoft Corporation */ +WAVE_FORMAT_G729A :: 0x0083 /* AT&T Labs, Inc. */ +WAVE_FORMAT_MVI_MVI2 :: 0x0084 /* Motion Pixels */ +WAVE_FORMAT_DF_G726 :: 0x0085 /* DataFusion Systems (Pty) (Ltd) */ +WAVE_FORMAT_DF_GSM610 :: 0x0086 /* DataFusion Systems (Pty) (Ltd) */ +WAVE_FORMAT_ISIAUDIO :: 0x0088 /* Iterated Systems, Inc. */ +WAVE_FORMAT_ONLIVE :: 0x0089 /* OnLive! Technologies, Inc. */ +WAVE_FORMAT_MULTITUDE_FT_SX20 :: 0x008A /* Multitude Inc. */ +WAVE_FORMAT_INFOCOM_ITS_G721_ADPCM :: 0x008B /* Infocom */ +WAVE_FORMAT_CONVEDIA_G729 :: 0x008C /* Convedia Corp. */ +WAVE_FORMAT_CONGRUENCY :: 0x008D /* Congruency Inc. */ +WAVE_FORMAT_SBC24 :: 0x0091 /* Siemens Business Communications Sys */ +WAVE_FORMAT_DOLBY_AC3_SPDIF :: 0x0092 /* Sonic Foundry */ +WAVE_FORMAT_MEDIASONIC_G723 :: 0x0093 /* MediaSonic */ +WAVE_FORMAT_PROSODY_8KBPS :: 0x0094 /* Aculab plc */ +WAVE_FORMAT_ZYXEL_ADPCM :: 0x0097 /* ZyXEL Communications, Inc. */ +WAVE_FORMAT_PHILIPS_LPCBB :: 0x0098 /* Philips Speech Processing */ +WAVE_FORMAT_PACKED :: 0x0099 /* Studer Professional Audio AG */ +WAVE_FORMAT_MALDEN_PHONYTALK :: 0x00A0 /* Malden Electronics Ltd. */ +WAVE_FORMAT_RACAL_RECORDER_GSM :: 0x00A1 /* Racal recorders */ +WAVE_FORMAT_RACAL_RECORDER_G720_A :: 0x00A2 /* Racal recorders */ +WAVE_FORMAT_RACAL_RECORDER_G723_1 :: 0x00A3 /* Racal recorders */ +WAVE_FORMAT_RACAL_RECORDER_TETRA_ACELP :: 0x00A4 /* Racal recorders */ +WAVE_FORMAT_NEC_AAC :: 0x00B0 /* NEC Corp. */ +WAVE_FORMAT_RAW_AAC1 :: 0x00FF /* For Raw AAC, with format block AudioSpecificConfig() (as defined by MPEG-4), that follows WAVEFORMATEX */ +WAVE_FORMAT_RHETOREX_ADPCM :: 0x0100 /* Rhetorex Inc. */ +WAVE_FORMAT_IRAT :: 0x0101 /* BeCubed Software Inc. */ +WAVE_FORMAT_VIVO_G723 :: 0x0111 /* Vivo Software */ +WAVE_FORMAT_VIVO_SIREN :: 0x0112 /* Vivo Software */ +WAVE_FORMAT_PHILIPS_CELP :: 0x0120 /* Philips Speech Processing */ +WAVE_FORMAT_PHILIPS_GRUNDIG :: 0x0121 /* Philips Speech Processing */ +WAVE_FORMAT_DIGITAL_G723 :: 0x0123 /* Digital Equipment Corporation */ +WAVE_FORMAT_SANYO_LD_ADPCM :: 0x0125 /* Sanyo Electric Co., Ltd. */ +WAVE_FORMAT_SIPROLAB_ACEPLNET :: 0x0130 /* Sipro Lab Telecom Inc. */ +WAVE_FORMAT_SIPROLAB_ACELP4800 :: 0x0131 /* Sipro Lab Telecom Inc. */ +WAVE_FORMAT_SIPROLAB_ACELP8V3 :: 0x0132 /* Sipro Lab Telecom Inc. */ +WAVE_FORMAT_SIPROLAB_G729 :: 0x0133 /* Sipro Lab Telecom Inc. */ +WAVE_FORMAT_SIPROLAB_G729A :: 0x0134 /* Sipro Lab Telecom Inc. */ +WAVE_FORMAT_SIPROLAB_KELVIN :: 0x0135 /* Sipro Lab Telecom Inc. */ +WAVE_FORMAT_VOICEAGE_AMR :: 0x0136 /* VoiceAge Corp. */ +WAVE_FORMAT_G726ADPCM :: 0x0140 /* Dictaphone Corporation */ +WAVE_FORMAT_DICTAPHONE_CELP68 :: 0x0141 /* Dictaphone Corporation */ +WAVE_FORMAT_DICTAPHONE_CELP54 :: 0x0142 /* Dictaphone Corporation */ +WAVE_FORMAT_QUALCOMM_PUREVOICE :: 0x0150 /* Qualcomm, Inc. */ +WAVE_FORMAT_QUALCOMM_HALFRATE :: 0x0151 /* Qualcomm, Inc. */ +WAVE_FORMAT_TUBGSM :: 0x0155 /* Ring Zero Systems, Inc. */ +WAVE_FORMAT_MSAUDIO1 :: 0x0160 /* Microsoft Corporation */ +WAVE_FORMAT_WMAUDIO2 :: 0x0161 /* Microsoft Corporation */ +WAVE_FORMAT_WMAUDIO3 :: 0x0162 /* Microsoft Corporation */ +WAVE_FORMAT_WMAUDIO_LOSSLESS :: 0x0163 /* Microsoft Corporation */ +WAVE_FORMAT_WMASPDIF :: 0x0164 /* Microsoft Corporation */ +WAVE_FORMAT_UNISYS_NAP_ADPCM :: 0x0170 /* Unisys Corp. */ +WAVE_FORMAT_UNISYS_NAP_ULAW :: 0x0171 /* Unisys Corp. */ +WAVE_FORMAT_UNISYS_NAP_ALAW :: 0x0172 /* Unisys Corp. */ +WAVE_FORMAT_UNISYS_NAP_16K :: 0x0173 /* Unisys Corp. */ +WAVE_FORMAT_SYCOM_ACM_SYC008 :: 0x0174 /* SyCom Technologies */ +WAVE_FORMAT_SYCOM_ACM_SYC701_G726L :: 0x0175 /* SyCom Technologies */ +WAVE_FORMAT_SYCOM_ACM_SYC701_CELP54 :: 0x0176 /* SyCom Technologies */ +WAVE_FORMAT_SYCOM_ACM_SYC701_CELP68 :: 0x0177 /* SyCom Technologies */ +WAVE_FORMAT_KNOWLEDGE_ADVENTURE_ADPCM :: 0x0178 /* Knowledge Adventure, Inc. */ +WAVE_FORMAT_FRAUNHOFER_IIS_MPEG2_AAC :: 0x0180 /* Fraunhofer IIS */ +WAVE_FORMAT_DTS_DS :: 0x0190 /* Digital Theatre Systems, Inc. */ +WAVE_FORMAT_CREATIVE_ADPCM :: 0x0200 /* Creative Labs, Inc */ +WAVE_FORMAT_CREATIVE_FASTSPEECH8 :: 0x0202 /* Creative Labs, Inc */ +WAVE_FORMAT_CREATIVE_FASTSPEECH10 :: 0x0203 /* Creative Labs, Inc */ +WAVE_FORMAT_UHER_ADPCM :: 0x0210 /* UHER informatic GmbH */ +WAVE_FORMAT_ULEAD_DV_AUDIO :: 0x0215 /* Ulead Systems, Inc. */ +WAVE_FORMAT_ULEAD_DV_AUDIO_1 :: 0x0216 /* Ulead Systems, Inc. */ +WAVE_FORMAT_QUARTERDECK :: 0x0220 /* Quarterdeck Corporation */ +WAVE_FORMAT_ILINK_VC :: 0x0230 /* I-link Worldwide */ +WAVE_FORMAT_RAW_SPORT :: 0x0240 /* Aureal Semiconductor */ +WAVE_FORMAT_ESST_AC3 :: 0x0241 /* ESS Technology, Inc. */ +WAVE_FORMAT_GENERIC_PASSTHRU :: 0x0249 +WAVE_FORMAT_IPI_HSX :: 0x0250 /* Interactive Products, Inc. */ +WAVE_FORMAT_IPI_RPELP :: 0x0251 /* Interactive Products, Inc. */ +WAVE_FORMAT_CS2 :: 0x0260 /* Consistent Software */ +WAVE_FORMAT_SONY_SCX :: 0x0270 /* Sony Corp. */ +WAVE_FORMAT_SONY_SCY :: 0x0271 /* Sony Corp. */ +WAVE_FORMAT_SONY_ATRAC3 :: 0x0272 /* Sony Corp. */ +WAVE_FORMAT_SONY_SPC :: 0x0273 /* Sony Corp. */ +WAVE_FORMAT_TELUM_AUDIO :: 0x0280 /* Telum Inc. */ +WAVE_FORMAT_TELUM_IA_AUDIO :: 0x0281 /* Telum Inc. */ +WAVE_FORMAT_NORCOM_VOICE_SYSTEMS_ADPCM :: 0x0285 /* Norcom Electronics Corp. */ +WAVE_FORMAT_FM_TOWNS_SND :: 0x0300 /* Fujitsu Corp. */ +WAVE_FORMAT_MICRONAS :: 0x0350 /* Micronas Semiconductors, Inc. */ +WAVE_FORMAT_MICRONAS_CELP833 :: 0x0351 /* Micronas Semiconductors, Inc. */ +WAVE_FORMAT_BTV_DIGITAL :: 0x0400 /* Brooktree Corporation */ +WAVE_FORMAT_INTEL_MUSIC_CODER :: 0x0401 /* Intel Corp. */ +WAVE_FORMAT_INDEO_AUDIO :: 0x0402 /* Ligos */ +WAVE_FORMAT_QDESIGN_MUSIC :: 0x0450 /* QDesign Corporation */ +WAVE_FORMAT_ON2_VP7_AUDIO :: 0x0500 /* On2 Technologies */ +WAVE_FORMAT_ON2_VP6_AUDIO :: 0x0501 /* On2 Technologies */ +WAVE_FORMAT_VME_VMPCM :: 0x0680 /* AT&T Labs, Inc. */ +WAVE_FORMAT_TPC :: 0x0681 /* AT&T Labs, Inc. */ +WAVE_FORMAT_LIGHTWAVE_LOSSLESS :: 0x08AE /* Clearjump */ +WAVE_FORMAT_OLIGSM :: 0x1000 /* Ing C. Olivetti & C., S.p.A. */ +WAVE_FORMAT_OLIADPCM :: 0x1001 /* Ing C. Olivetti & C., S.p.A. */ +WAVE_FORMAT_OLICELP :: 0x1002 /* Ing C. Olivetti & C., S.p.A. */ +WAVE_FORMAT_OLISBC :: 0x1003 /* Ing C. Olivetti & C., S.p.A. */ +WAVE_FORMAT_OLIOPR :: 0x1004 /* Ing C. Olivetti & C., S.p.A. */ +WAVE_FORMAT_LH_CODEC :: 0x1100 /* Lernout & Hauspie */ +WAVE_FORMAT_LH_CODEC_CELP :: 0x1101 /* Lernout & Hauspie */ +WAVE_FORMAT_LH_CODEC_SBC8 :: 0x1102 /* Lernout & Hauspie */ +WAVE_FORMAT_LH_CODEC_SBC12 :: 0x1103 /* Lernout & Hauspie */ +WAVE_FORMAT_LH_CODEC_SBC16 :: 0x1104 /* Lernout & Hauspie */ +WAVE_FORMAT_NORRIS :: 0x1400 /* Norris Communications, Inc. */ +WAVE_FORMAT_ISIAUDIO_2 :: 0x1401 /* ISIAudio */ +WAVE_FORMAT_SOUNDSPACE_MUSICOMPRESS :: 0x1500 /* AT&T Labs, Inc. */ +WAVE_FORMAT_MPEG_ADTS_AAC :: 0x1600 /* Microsoft Corporation */ +WAVE_FORMAT_MPEG_RAW_AAC :: 0x1601 /* Microsoft Corporation */ +WAVE_FORMAT_MPEG_LOAS :: 0x1602 /* Microsoft Corporation (MPEG-4 Audio Transport Streams (LOAS/LATM) */ +WAVE_FORMAT_NOKIA_MPEG_ADTS_AAC :: 0x1608 /* Microsoft Corporation */ +WAVE_FORMAT_NOKIA_MPEG_RAW_AAC :: 0x1609 /* Microsoft Corporation */ +WAVE_FORMAT_VODAFONE_MPEG_ADTS_AAC :: 0x160A /* Microsoft Corporation */ +WAVE_FORMAT_VODAFONE_MPEG_RAW_AAC :: 0x160B /* Microsoft Corporation */ +WAVE_FORMAT_MPEG_HEAAC :: 0x1610 /* Microsoft Corporation (MPEG-2 AAC or MPEG-4 HE-AAC v1/v2 streams with any payload (ADTS, ADIF, LOAS/LATM, RAW). Format block includes MP4 AudioSpecificConfig() -- see HEAACWAVEFORMAT below */ +WAVE_FORMAT_VOXWARE_RT24_SPEECH :: 0x181C /* Voxware Inc. */ +WAVE_FORMAT_SONICFOUNDRY_LOSSLESS :: 0x1971 /* Sonic Foundry */ +WAVE_FORMAT_INNINGS_TELECOM_ADPCM :: 0x1979 /* Innings Telecom Inc. */ +WAVE_FORMAT_LUCENT_SX8300P :: 0x1C07 /* Lucent Technologies */ +WAVE_FORMAT_LUCENT_SX5363S :: 0x1C0C /* Lucent Technologies */ +WAVE_FORMAT_CUSEEME :: 0x1F03 /* CUSeeMe */ +WAVE_FORMAT_NTCSOFT_ALF2CM_ACM :: 0x1FC4 /* NTCSoft */ +WAVE_FORMAT_DVM :: 0x2000 /* FAST Multimedia AG */ +WAVE_FORMAT_DTS2 :: 0x2001 +WAVE_FORMAT_MAKEAVIS :: 0x3313 +WAVE_FORMAT_DIVIO_MPEG4_AAC :: 0x4143 /* Divio, Inc. */ +WAVE_FORMAT_NOKIA_ADAPTIVE_MULTIRATE :: 0x4201 /* Nokia */ +WAVE_FORMAT_DIVIO_G726 :: 0x4243 /* Divio, Inc. */ +WAVE_FORMAT_LEAD_SPEECH :: 0x434C /* LEAD Technologies */ +WAVE_FORMAT_LEAD_VORBIS :: 0x564C /* LEAD Technologies */ +WAVE_FORMAT_WAVPACK_AUDIO :: 0x5756 /* xiph.org */ +WAVE_FORMAT_ALAC :: 0x6C61 /* Apple Lossless */ +WAVE_FORMAT_OGG_VORBIS_MODE_1 :: 0x674F /* Ogg Vorbis */ +WAVE_FORMAT_OGG_VORBIS_MODE_2 :: 0x6750 /* Ogg Vorbis */ +WAVE_FORMAT_OGG_VORBIS_MODE_3 :: 0x6751 /* Ogg Vorbis */ +WAVE_FORMAT_OGG_VORBIS_MODE_1_PLUS :: 0x676F /* Ogg Vorbis */ +WAVE_FORMAT_OGG_VORBIS_MODE_2_PLUS :: 0x6770 /* Ogg Vorbis */ +WAVE_FORMAT_OGG_VORBIS_MODE_3_PLUS :: 0x6771 /* Ogg Vorbis */ +WAVE_FORMAT_3COM_NBX :: 0x7000 /* 3COM Corp. */ +WAVE_FORMAT_OPUS :: 0x704F /* Opus */ +WAVE_FORMAT_FAAD_AAC :: 0x706D +WAVE_FORMAT_AMR_NB :: 0x7361 /* AMR Narrowband */ +WAVE_FORMAT_AMR_WB :: 0x7362 /* AMR Wideband */ +WAVE_FORMAT_AMR_WP :: 0x7363 /* AMR Wideband Plus */ +WAVE_FORMAT_GSM_AMR_CBR :: 0x7A21 /* GSMA/3GPP */ +WAVE_FORMAT_GSM_AMR_VBR_SID :: 0x7A22 /* GSMA/3GPP */ +WAVE_FORMAT_COMVERSE_INFOSYS_G723_1 :: 0xA100 /* Comverse Infosys */ +WAVE_FORMAT_COMVERSE_INFOSYS_AVQSBC :: 0xA101 /* Comverse Infosys */ +WAVE_FORMAT_COMVERSE_INFOSYS_SBC :: 0xA102 /* Comverse Infosys */ +WAVE_FORMAT_SYMBOL_G729_A :: 0xA103 /* Symbol Technologies */ +WAVE_FORMAT_VOICEAGE_AMR_WB :: 0xA104 /* VoiceAge Corp. */ +WAVE_FORMAT_INGENIENT_G726 :: 0xA105 /* Ingenient Technologies, Inc. */ +WAVE_FORMAT_MPEG4_AAC :: 0xA106 /* ISO/MPEG-4 */ +WAVE_FORMAT_ENCORE_G726 :: 0xA107 /* Encore Software */ +WAVE_FORMAT_ZOLL_ASAO :: 0xA108 /* ZOLL Medical Corp. */ +WAVE_FORMAT_SPEEX_VOICE :: 0xA109 /* xiph.org */ +WAVE_FORMAT_VIANIX_MASC :: 0xA10A /* Vianix LLC */ +WAVE_FORMAT_WM9_SPECTRUM_ANALYZER :: 0xA10B /* Microsoft */ +WAVE_FORMAT_WMF_SPECTRUM_ANAYZER :: 0xA10C /* Microsoft */ +WAVE_FORMAT_GSM_610 :: 0xA10D +WAVE_FORMAT_GSM_620 :: 0xA10E +WAVE_FORMAT_GSM_660 :: 0xA10F +WAVE_FORMAT_GSM_690 :: 0xA110 +WAVE_FORMAT_GSM_ADAPTIVE_MULTIRATE_WB :: 0xA111 +WAVE_FORMAT_POLYCOM_G722 :: 0xA112 /* Polycom */ +WAVE_FORMAT_POLYCOM_G728 :: 0xA113 /* Polycom */ +WAVE_FORMAT_POLYCOM_G729_A :: 0xA114 /* Polycom */ +WAVE_FORMAT_POLYCOM_SIREN :: 0xA115 /* Polycom */ +WAVE_FORMAT_GLOBAL_IP_ILBC :: 0xA116 /* Global IP */ +WAVE_FORMAT_RADIOTIME_TIME_SHIFT_RADIO :: 0xA117 /* RadioTime */ +WAVE_FORMAT_NICE_ACA :: 0xA118 /* Nice Systems */ +WAVE_FORMAT_NICE_ADPCM :: 0xA119 /* Nice Systems */ +WAVE_FORMAT_VOCORD_G721 :: 0xA11A /* Vocord Telecom */ +WAVE_FORMAT_VOCORD_G726 :: 0xA11B /* Vocord Telecom */ +WAVE_FORMAT_VOCORD_G722_1 :: 0xA11C /* Vocord Telecom */ +WAVE_FORMAT_VOCORD_G728 :: 0xA11D /* Vocord Telecom */ +WAVE_FORMAT_VOCORD_G729 :: 0xA11E /* Vocord Telecom */ +WAVE_FORMAT_VOCORD_G729_A :: 0xA11F /* Vocord Telecom */ +WAVE_FORMAT_VOCORD_G723_1 :: 0xA120 /* Vocord Telecom */ +WAVE_FORMAT_VOCORD_LBC :: 0xA121 /* Vocord Telecom */ +WAVE_FORMAT_NICE_G728 :: 0xA122 /* Nice Systems */ +WAVE_FORMAT_FRACE_TELECOM_G729 :: 0xA123 /* France Telecom */ +WAVE_FORMAT_CODIAN :: 0xA124 /* CODIAN */ +WAVE_FORMAT_DOLBY_AC4 :: 0xAC40 /* Dolby AC-4 */ +WAVE_FORMAT_FLAC :: 0xF1AC /* flac.sourceforge.net */ +WAVE_FORMAT_EXTENSIBLE :: 0xFFFE /* Microsoft */ + + +WAVEFORMATEX :: struct #packed { wFormatTag: WORD, nChannels: WORD, nSamplesPerSec: DWORD, @@ -325,6 +600,20 @@ WAVEFORMATEX :: struct { } LPCWAVEFORMATEX :: ^WAVEFORMATEX +// New wave format development should be based on the WAVEFORMATEXTENSIBLE structure. +// WAVEFORMATEXTENSIBLE allows you to avoid having to register a new format tag with Microsoft. +// Simply define a new GUID value for the WAVEFORMATEXTENSIBLE.SubFormat field and use WAVE_FORMAT_EXTENSIBLE in the WAVEFORMATEXTENSIBLE.Format.wFormatTag field. +WAVEFORMATEXTENSIBLE :: struct #packed { + using Format: WAVEFORMATEX, + Samples: struct #raw_union { + wValidBitsPerSample: WORD, /* bits of precision */ + wSamplesPerBlock: WORD, /* valid if wBitsPerSample==0 */ + wReserved: WORD, /* If neither applies, set to zero. */ + }, + dwChannelMask: SPEAKER_FLAGS, /* which channels are present in stream */ + SubFormat: GUID, +} + WAVEHDR :: struct { lpData: LPSTR, /* pointer to locked data buffer */ dwBufferLength: DWORD, /* length of data buffer */ @@ -360,26 +649,50 @@ WAVEOUTCAPSW :: struct { } LPWAVEOUTCAPSW :: ^WAVEOUTCAPSW +SPEAKER_FLAGS :: distinct bit_set[SPEAKER_FLAG; DWORD] +SPEAKER_FLAG :: enum DWORD { + FRONT_LEFT = 0, + FRONT_RIGHT = 1, + FRONT_CENTER = 2, + LOW_FREQUENCY = 3, + BACK_LEFT = 4, + BACK_RIGHT = 5, + FRONT_LEFT_OF_CENTER = 6, + FRONT_RIGHT_OF_CENTER = 7, + BACK_CENTER = 8, + SIDE_LEFT = 9, + SIDE_RIGHT = 10, + TOP_CENTER = 11, + TOP_FRONT_LEFT = 12, + TOP_FRONT_CENTER = 13, + TOP_FRONT_RIGHT = 14, + TOP_BACK_LEFT = 15, + TOP_BACK_CENTER = 16, + TOP_BACK_RIGHT = 17, + //RESERVED = 0x7FFC0000, // bit mask locations reserved for future use + ALL = 31, // used to specify that any possible permutation of speaker configurations +} + // flag values for PlaySound -SND_SYNC :: 0x0000 /* play synchronously (default) */ -SND_ASYNC :: 0x0001 /* play asynchronously */ -SND_NODEFAULT :: 0x0002 /* silence (!default) if sound not found */ -SND_MEMORY :: 0x0004 /* pszSound points to a memory file */ -SND_LOOP :: 0x0008 /* loop the sound until next sndPlaySound */ -SND_NOSTOP :: 0x0010 /* don't stop any currently playing sound */ - -SND_NOWAIT :: 0x00002000 /* don't wait if the driver is busy */ -SND_ALIAS :: 0x00010000 /* name is a registry alias */ -SND_ALIAS_ID :: 0x00110000 /* alias is a predefined ID */ -SND_FILENAME :: 0x00020000 /* name is file name */ -SND_RESOURCE :: 0x00040004 /* name is resource name or atom */ - -SND_PURGE :: 0x0040 /* purge non-static events for task */ -SND_APPLICATION :: 0x0080 /* look for application specific association */ - -SND_SENTRY :: 0x00080000 /* Generate a SoundSentry event with this sound */ -SND_RING :: 0x00100000 /* Treat this as a "ring" from a communications app - don't duck me */ -SND_SYSTEM :: 0x00200000 /* Treat this as a system sound */ +SND_SYNC :: 0x0000 /* play synchronously (default) */ +SND_ASYNC :: 0x0001 /* play asynchronously */ +SND_NODEFAULT :: 0x0002 /* silence (!default) if sound not found */ +SND_MEMORY :: 0x0004 /* pszSound points to a memory file */ +SND_LOOP :: 0x0008 /* loop the sound until next sndPlaySound */ +SND_NOSTOP :: 0x0010 /* don't stop any currently playing sound */ + +SND_NOWAIT :: 0x00002000 /* don't wait if the driver is busy */ +SND_ALIAS :: 0x00010000 /* name is a registry alias */ +SND_ALIAS_ID :: 0x00110000 /* alias is a predefined ID */ +SND_FILENAME :: 0x00020000 /* name is file name */ +SND_RESOURCE :: 0x00040004 /* name is resource name or atom */ + +SND_PURGE :: 0x0040 /* purge non-static events for task */ +SND_APPLICATION :: 0x0080 /* look for application specific association */ + +SND_SENTRY :: 0x00080000 /* Generate a SoundSentry event with this sound */ +SND_RING :: 0x00100000 /* Treat this as a "ring" from a communications app - don't duck me */ +SND_SYSTEM :: 0x00200000 /* Treat this as a system sound */ CALLBACK_TYPEMASK :: 0x00070000 /* callback type mask */ diff --git a/core/sys/windows/ws2_32.odin b/core/sys/windows/ws2_32.odin index 5b2952495..0d1f477c3 100644 --- a/core/sys/windows/ws2_32.odin +++ b/core/sys/windows/ws2_32.odin @@ -82,6 +82,17 @@ Example Load: } */ +LPFN_GETACCEPTEXSOCKADDRS :: #type proc "system" ( + lpOutputBuffer: PVOID, + dwReceiveDataLength: DWORD, + dwLocalAddressLength: DWORD, + dwRemoteAddressLength: DWORD, + LocalSockaddr: ^^sockaddr, + LocalSockaddrLength: LPINT, + RemoteSockaddr: ^^sockaddr, + RemoteSockaddrLength: LPINT, +) + foreign import ws2_32 "system:Ws2_32.lib" @(default_calling_convention="system") foreign ws2_32 { @@ -91,6 +102,7 @@ foreign ws2_32 { WSACleanup :: proc() -> c_int --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsagetlasterror) WSAGetLastError :: proc() -> c_int --- + WSASetLastError :: proc(err: c_int) --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsapoll) WSAPoll :: proc(fdArray: ^WSA_POLLFD, fds: c_ulong, timeout: c_int) -> c_int --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsaduplicatesocketw) @@ -130,6 +142,10 @@ foreign ws2_32 { ) -> SOCKET --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsaioctl) WSAIoctl :: proc(s: SOCKET, dwIoControlCode: DWORD, lpvInBuffer: rawptr, cbInBuffer: DWORD, lpvOutBuffer: rawptr, cbOutBuffer: DWORD, lpcbBytesReturned: ^DWORD, lpOverlapped: ^OVERLAPPED, lpCompletionRoutine: LPWSAOVERLAPPED_COMPLETION_ROUTINE) -> c_int --- + // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsacreateevent) + WSACreateEvent :: proc() -> WSAEVENT --- + // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsacloseevent) + WSACloseEvent :: proc(hEvent: WSAEVENT) -> bool --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsaeventselect) WSAEventSelect :: proc(s: SOCKET, hEventObject: WSAEVENT, lNetworkEvents: i32) -> c_int --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsawaitformultipleevents) @@ -238,9 +254,9 @@ foreign ws2_32 { // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-ntohs) ntohs :: proc(netshort: c_ushort) -> c_ushort --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-htonl) - @(deprecated="Use endian specific integers instead, https://odin-lang.org/docs/overview/#basic-types") + // Prefer using endian-specific integers instead, https://odin-lang.org/docs/overview/#basic-types htonl :: proc(hostlong: c_ulong) -> c_ulong --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-htons) - @(deprecated="Use endian specific integers instead, https://odin-lang.org/docs/overview/#basic-types") + // Prefer using endian-specific integers instead, https://odin-lang.org/docs/overview/#basic-types htons :: proc(hostshort: c_ushort) -> c_ushort --- } diff --git a/core/encoding/ansi/ansi.odin b/core/terminal/ansi/ansi.odin index 5550a1671..5550a1671 100644 --- a/core/encoding/ansi/ansi.odin +++ b/core/terminal/ansi/ansi.odin diff --git a/core/encoding/ansi/doc.odin b/core/terminal/ansi/doc.odin index 966e6be00..966e6be00 100644 --- a/core/encoding/ansi/doc.odin +++ b/core/terminal/ansi/doc.odin diff --git a/core/terminal/doc.odin b/core/terminal/doc.odin new file mode 100644 index 000000000..490e9d398 --- /dev/null +++ b/core/terminal/doc.odin @@ -0,0 +1,4 @@ +/* +This package is for interacting with the command line interface of the system. +*/ +package terminal diff --git a/core/terminal/internal.odin b/core/terminal/internal.odin new file mode 100644 index 000000000..9404ff833 --- /dev/null +++ b/core/terminal/internal.odin @@ -0,0 +1,89 @@ +#+private +package terminal + +import "base:runtime" +import "core:os" +import "core:strings" + +// Reference documentation: +// +// - [[ https://no-color.org/ ]] +// - [[ https://github.com/termstandard/colors ]] +// - [[ https://invisible-island.net/ncurses/terminfo.src.html ]] + +get_no_color :: proc() -> bool { + buf: [128]u8 + if no_color, err := os.lookup_env(buf[:], "NO_COLOR"); err == nil { + return no_color != "" + } + return false +} + +get_environment_color :: proc() -> Color_Depth { + buf: [128]u8 + // `COLORTERM` is non-standard but widespread and unambiguous. + if colorterm, err := os.lookup_env(buf[:], "COLORTERM"); err == nil { + // These are the only values that are typically advertised that have + // anything to do with color depth. + if colorterm == "truecolor" || colorterm == "24bit" { + return .True_Color + } + } + + if term, err := os.lookup_env(buf[:], "TERM"); err == nil { + if strings.contains(term, "-truecolor") { + return .True_Color + } + if strings.contains(term, "-256color") { + return .Eight_Bit + } + if strings.contains(term, "-16color") { + return .Four_Bit + } + + // The `terminfo` database, which is stored in binary on *nix + // platforms, has an undocumented format that is not guaranteed to be + // portable, so beyond this point, we can only make safe assumptions. + // + // This section should only be necessary for terminals that do not + // define any of the previous environment values. + // + // Only a small sampling of some common values are checked here. + switch term { + case "ansi": fallthrough + case "konsole": fallthrough + case "putty": fallthrough + case "rxvt": fallthrough + case "rxvt-color": fallthrough + case "screen": fallthrough + case "st": fallthrough + case "tmux": fallthrough + case "vte": fallthrough + case "xterm": fallthrough + case "xterm-color": + return .Three_Bit + } + } + + return .None +} + +@(init) +init_terminal :: proc "contextless" () { + _init_terminal() + + context = runtime.default_context() + + // We respect `NO_COLOR` specifically as a color-disabler but not as a + // blanket ban on any terminal manipulation codes, hence why this comes + // after `_init_terminal` which will allow Windows to enable Virtual + // Terminal Processing for non-color control sequences. + if !get_no_color() { + color_enabled = color_depth > .None + } +} + +@(fini) +fini_terminal :: proc "contextless" () { + _fini_terminal() +} diff --git a/core/terminal/terminal.odin b/core/terminal/terminal.odin new file mode 100644 index 000000000..1e5566295 --- /dev/null +++ b/core/terminal/terminal.odin @@ -0,0 +1,36 @@ +package terminal + +import "core:os" + +/* +This describes the range of colors that a terminal is capable of supporting. +*/ +Color_Depth :: enum { + None, // No color support + Three_Bit, // 8 colors + Four_Bit, // 16 colors + Eight_Bit, // 256 colors + True_Color, // 24-bit true color +} + +/* +Returns true if the file `handle` is attached to a terminal. + +This is normally true for `os.stdout` and `os.stderr` unless they are +redirected to a file. +*/ +@(require_results) +is_terminal :: proc(handle: os.Handle) -> bool { + return _is_terminal(handle) +} + +/* +This is true if the terminal is accepting any form of colored text output. +*/ +color_enabled: bool + +/* +This value reports the color depth support as reported by the terminal at the +start of the program. +*/ +color_depth: Color_Depth diff --git a/core/terminal/terminal_js.odin b/core/terminal/terminal_js.odin new file mode 100644 index 000000000..4dcd4465e --- /dev/null +++ b/core/terminal/terminal_js.odin @@ -0,0 +1,15 @@ +#+private +#+build js +package terminal + +import "core:os" + +_is_terminal :: proc "contextless" (handle: os.Handle) -> bool { + return true +} + +_init_terminal :: proc "contextless" () { + color_depth = .None +} + +_fini_terminal :: proc "contextless" () { }
\ No newline at end of file diff --git a/core/terminal/terminal_posix.odin b/core/terminal/terminal_posix.odin new file mode 100644 index 000000000..8d96dd256 --- /dev/null +++ b/core/terminal/terminal_posix.odin @@ -0,0 +1,18 @@ +#+private +#+build linux, darwin, netbsd, openbsd, freebsd, haiku +package terminal + +import "base:runtime" +import "core:os" +import "core:sys/posix" + +_is_terminal :: proc "contextless" (handle: os.Handle) -> bool { + return bool(posix.isatty(posix.FD(handle))) +} + +_init_terminal :: proc "contextless" () { + context = runtime.default_context() + color_depth = get_environment_color() +} + +_fini_terminal :: proc "contextless" () { } diff --git a/core/terminal/terminal_windows.odin b/core/terminal/terminal_windows.odin new file mode 100644 index 000000000..6d5f98a1f --- /dev/null +++ b/core/terminal/terminal_windows.odin @@ -0,0 +1,63 @@ +#+private +package terminal + +import "base:runtime" +import "core:os" +import "core:sys/windows" + +_is_terminal :: proc "contextless" (handle: os.Handle) -> bool { + is_tty := windows.GetFileType(windows.HANDLE(handle)) == windows.FILE_TYPE_CHAR + return is_tty +} + +old_modes: [2]struct{ + handle: windows.DWORD, + mode: windows.DWORD, +} = { + {windows.STD_OUTPUT_HANDLE, 0}, + {windows.STD_ERROR_HANDLE, 0}, +} + +@(init) +_init_terminal :: proc "contextless" () { + vtp_enabled: bool + + for &v in old_modes { + handle := windows.GetStdHandle(v.handle) + if handle == windows.INVALID_HANDLE || handle == nil { + return + } + if windows.GetConsoleMode(handle, &v.mode) { + windows.SetConsoleMode(handle, v.mode | windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) + + new_mode: windows.DWORD + windows.GetConsoleMode(handle, &new_mode) + + if new_mode & (windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0 { + vtp_enabled = true + } + } + } + + if vtp_enabled { + // This color depth is available on Windows 10 since build 10586. + color_depth = .Four_Bit + } else { + context = runtime.default_context() + + // The user may be on a non-default terminal emulator. + color_depth = get_environment_color() + } +} + +@(fini) +_fini_terminal :: proc "contextless" () { + for v in old_modes { + handle := windows.GetStdHandle(v.handle) + if handle == windows.INVALID_HANDLE || handle == nil { + return + } + + windows.SetConsoleMode(handle, v.mode) + } +} diff --git a/core/testing/reporting.odin b/core/testing/reporting.odin index 6752cd79b..7c7eb7b2d 100644 --- a/core/testing/reporting.odin +++ b/core/testing/reporting.odin @@ -10,12 +10,12 @@ package testing */ import "base:runtime" -import "core:encoding/ansi" import "core:fmt" import "core:io" import "core:mem" import "core:path/filepath" import "core:strings" +import "core:terminal/ansi" // Definitions of colors for use in the test runner. SGR_RESET :: ansi.CSI + ansi.RESET + ansi.SGR diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 83a5ac4e7..b53bd8722 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -13,7 +13,6 @@ package testing import "base:intrinsics" import "base:runtime" import "core:bytes" -import "core:encoding/ansi" @require import "core:encoding/base64" @require import "core:encoding/json" import "core:fmt" @@ -25,6 +24,8 @@ import "core:os" import "core:slice" @require import "core:strings" import "core:sync/chan" +import "core:terminal" +import "core:terminal/ansi" import "core:thread" import "core:time" @@ -44,6 +45,7 @@ PER_THREAD_MEMORY : int : #config(ODIN_TEST_THREAD_MEMORY, mem.ROLLBACK_S // The format is: `package.test_name,test_name_only,...` TEST_NAMES : string : #config(ODIN_TEST_NAMES, "") // Show the fancy animated progress report. +// This requires terminal color support, as well as STDOUT to not be redirected to a file. FANCY_OUTPUT : bool : #config(ODIN_TEST_FANCY, true) // Copy failed tests to the clipboard when done. USE_CLIPBOARD : bool : #config(ODIN_TEST_CLIPBOARD, false) @@ -55,6 +57,8 @@ SHARED_RANDOM_SEED : u64 : #config(ODIN_TEST_RANDOM_SEED, 0) // Set the lowest log level for this test run. LOG_LEVEL_DEFAULT : string : "debug" when ODIN_DEBUG else "info" LOG_LEVEL : string : #config(ODIN_TEST_LOG_LEVEL, LOG_LEVEL_DEFAULT) +// Report a message at the info level when a test has changed its state. +LOG_STATE_CHANGES : bool : #config(ODIN_TEST_LOG_STATE_CHANGES, false) // Show only the most necessary logging information. USING_SHORT_LOGS : bool : #config(ODIN_TEST_SHORT_LOGS, false) // Output a report of the tests to the given path. @@ -70,6 +74,9 @@ get_log_level :: #force_inline proc() -> runtime.Logger_Level { } } +@(private) global_log_colors_disabled: bool +@(private) global_ansi_disabled: bool + JSON :: struct { total: int, success: int, @@ -129,11 +136,16 @@ run_test_task :: proc(task: thread.Task) { context.assertion_failure_proc = test_assertion_failure_proc + logger_options := Default_Test_Logger_Opts + if global_log_colors_disabled { + logger_options -= {.Terminal_Color} + } + context.logger = { procedure = test_logger_proc, data = &data.t, lowest_level = get_log_level(), - options = Default_Test_Logger_Opts, + options = logger_options, } random_generator_state: runtime.Default_Random_State @@ -204,13 +216,16 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { } } - when ODIN_OS == .Windows { - console_ansi_init() - } - stdout := io.to_writer(os.stream_from_handle(os.stdout)) stderr := io.to_writer(os.stream_from_handle(os.stderr)) + // The animations are only ever shown through STDOUT; + // STDERR is used exclusively for logging regardless of error level. + global_log_colors_disabled = !terminal.color_enabled || !terminal.is_terminal(os.stderr) + global_ansi_disabled = !terminal.is_terminal(os.stdout) + + should_show_animations := FANCY_OUTPUT && terminal.color_enabled && !global_ansi_disabled + // -- Prepare test data. alloc_error: mem.Allocator_Error @@ -268,12 +283,12 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { total_done_count := 0 total_test_count := len(internal_tests) - when !FANCY_OUTPUT { - // This is strictly for updating the window title when the progress - // report is disabled. We're otherwise able to depend on the call to - // `needs_to_redraw`. - last_done_count := -1 - } + + // This is strictly for updating the window title when the progress + // report is disabled. We're otherwise able to depend on the call to + // `needs_to_redraw`. + last_done_count := -1 + if total_test_count == 0 { // Exit early. @@ -342,31 +357,31 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { fmt.assertf(alloc_error == nil, "Error allocating memory for test report: %v", alloc_error) defer destroy_report(&report) - when FANCY_OUTPUT { - // We cannot make use of the ANSI save/restore cursor codes, because they - // work by absolute screen coordinates. This will cause unnecessary - // scrollback if we print at the bottom of someone's terminal. - ansi_redraw_string := fmt.aprintf( - // ANSI for "go up N lines then erase the screen from the cursor forward." - ansi.CSI + "%i" + ansi.CPL + ansi.CSI + ansi.ED + - // We'll combine this with the window title format string, since it - // can be printed at the same time. - "%s", - // 1 extra line for the status bar. - 1 + len(report.packages), OSC_WINDOW_TITLE) - assert(len(ansi_redraw_string) > 0, "Error allocating ANSI redraw string.") - defer delete(ansi_redraw_string) - - thread_count_status_string: string = --- - { - PADDING :: PROGRESS_COLUMN_SPACING + PROGRESS_WIDTH - unpadded := fmt.tprintf("%i thread%s", thread_count, "" if thread_count == 1 else "s") - thread_count_status_string = fmt.aprintf("%- *[1]s", unpadded, report.pkg_column_len + PADDING) - assert(len(thread_count_status_string) > 0, "Error allocating thread count status string.") - } - defer delete(thread_count_status_string) + // We cannot make use of the ANSI save/restore cursor codes, because they + // work by absolute screen coordinates. This will cause unnecessary + // scrollback if we print at the bottom of someone's terminal. + ansi_redraw_string := fmt.aprintf( + // ANSI for "go up N lines then erase the screen from the cursor forward." + ansi.CSI + "%i" + ansi.CPL + ansi.CSI + ansi.ED + + // We'll combine this with the window title format string, since it + // can be printed at the same time. + "%s", + // 1 extra line for the status bar. + 1 + len(report.packages), OSC_WINDOW_TITLE) + assert(len(ansi_redraw_string) > 0, "Error allocating ANSI redraw string.") + defer delete(ansi_redraw_string) + + thread_count_status_string: string = --- + { + PADDING :: PROGRESS_COLUMN_SPACING + PROGRESS_WIDTH + + unpadded := fmt.tprintf("%i thread%s", thread_count, "" if thread_count == 1 else "s") + thread_count_status_string = fmt.aprintf("%- *[1]s", unpadded, report.pkg_column_len + PADDING) + assert(len(thread_count_status_string) > 0, "Error allocating thread count status string.") } + defer delete(thread_count_status_string) + task_data_slots: []Task_Data = --- task_data_slots, alloc_error = make([]Task_Data, thread_count) @@ -442,11 +457,16 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { // digging through the source to divine everywhere it is used for that. shared_log_allocator := context.allocator + logger_options := Default_Test_Logger_Opts - {.Short_File_Path, .Line, .Procedure} + if global_log_colors_disabled { + logger_options -= {.Terminal_Color} + } + context.logger = { procedure = runner_logger_proc, data = &log_messages, lowest_level = get_log_level(), - options = Default_Test_Logger_Opts - {.Short_File_Path, .Line, .Procedure}, + options = logger_options, } run_index: int @@ -481,11 +501,13 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { setup_signal_handler() - fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_HIDE) + if !global_ansi_disabled { + fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_HIDE) + } - when FANCY_OUTPUT { - signals_were_raised := false + signals_were_raised := false + if should_show_animations { redraw_report(stdout, report) draw_status_bar(stdout, thread_count_status_string, total_done_count, total_test_count) } @@ -611,8 +633,8 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { total_done_count += 1 } - when ODIN_DEBUG { - log.debugf("Test #%i %s.%s changed state to %v.", task_channel.test_index, it.pkg, it.name, event.new_state) + when LOG_STATE_CHANGES { + log.infof("Test #%i %s.%s changed state to %v.", task_channel.test_index, it.pkg, it.name, event.new_state) } pkg.last_change_state = event.new_state @@ -703,25 +725,26 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { break main_loop } - when FANCY_OUTPUT { - // Because the bounds checking procs send directly to STDERR with - // no way to redirect or handle them, we need to at least try to - // let the user see those messages when using the animated progress - // report. This flag may be set by the block of code below if a - // signal is raised. - // - // It'll be purely by luck if the output is interleaved properly, - // given the nature of non-thread-safe printing. - // - // At worst, if Odin did not print any error for this signal, we'll - // just re-display the progress report. The fatal log error message - // should be enough to clue the user in that something dire has - // occurred. - bypass_progress_overwrite := false - } + + // Because the bounds checking procs send directly to STDERR with + // no way to redirect or handle them, we need to at least try to + // let the user see those messages when using the animated progress + // report. This flag may be set by the block of code below if a + // signal is raised. + // + // It'll be purely by luck if the output is interleaved properly, + // given the nature of non-thread-safe printing. + // + // At worst, if Odin did not print any error for this signal, we'll + // just re-display the progress report. The fatal log error message + // should be enough to clue the user in that something dire has + // occurred. + bypass_progress_overwrite := false + if test_index, reason, ok := should_stop_test(); ok { - #no_bounds_check report.all_test_states[test_index] = .Failed + passed := reason == .Successful_Stop + #no_bounds_check report.all_test_states[test_index] = .Successful if passed else .Failed #no_bounds_check it := internal_tests[test_index] #no_bounds_check pkg := report.packages_by_name[it.pkg] pkg.frame_ready = false @@ -742,7 +765,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { fmt.assertf(task_data != nil, "A signal (%v) was raised to stop test #%i %s.%s, but its task data is missing.", reason, test_index, it.pkg, it.name) - if !task_data.t._fail_now_called { + if !passed && !task_data.t._fail_now_called { if test_index not_in failed_test_reason_map { // We only write a new error message here if there wasn't one // already, because the message we can provide based only on @@ -752,7 +775,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { log.fatalf("Caught signal to stop test #%i %s.%s for: %v.", test_index, it.pkg, it.name, reason) } - when FANCY_OUTPUT { + if should_show_animations { bypass_progress_overwrite = true signals_were_raised = true } @@ -760,13 +783,17 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { end_t(&task_data.t) - total_failure_count += 1 + if passed { + total_success_count += 1 + } else { + total_failure_count += 1 + } total_done_count += 1 } // -- Redraw. - when FANCY_OUTPUT { + if should_show_animations { if len(log_messages) == 0 && !needs_to_redraw(report) { continue main_loop } @@ -776,7 +803,9 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { } } else { if total_done_count != last_done_count { - fmt.wprintf(stdout, OSC_WINDOW_TITLE, total_done_count, total_test_count) + if !global_ansi_disabled { + fmt.wprintf(stdout, OSC_WINDOW_TITLE, total_done_count, total_test_count) + } last_done_count = total_done_count } @@ -801,7 +830,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { clear(&log_messages) bytes.buffer_reset(&batch_buffer) - when FANCY_OUTPUT { + if should_show_animations { redraw_report(batch_writer, report) draw_status_bar(batch_writer, thread_count_status_string, total_done_count, total_test_count) fmt.wprint(stdout, bytes.buffer_to_string(&batch_buffer)) @@ -822,7 +851,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { finished_in := time.since(start_time) - when !FANCY_OUTPUT { + if !should_show_animations || !terminal.is_terminal(os.stderr) { // One line to space out the results, since we don't have the status // bar in plain mode. fmt.wprintln(batch_writer) @@ -836,24 +865,28 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { if total_done_count != total_test_count { not_run_count := total_test_count - total_done_count + message := " %i %s left undone." if global_log_colors_disabled else " " + SGR_READY + "%i" + SGR_RESET + " %s left undone." fmt.wprintf(batch_writer, - " " + SGR_READY + "%i" + SGR_RESET + " %s left undone.", + message, not_run_count, "test was" if not_run_count == 1 else "tests were") } if total_success_count == total_test_count { + message := " %s successful." if global_log_colors_disabled else " %s " + SGR_SUCCESS + "successful." + SGR_RESET fmt.wprintfln(batch_writer, - " %s " + SGR_SUCCESS + "successful." + SGR_RESET, + message, "The test was" if total_test_count == 1 else "All tests were") } else if total_failure_count > 0 { if total_failure_count == total_test_count { + message := " %s failed." if global_log_colors_disabled else " %s " + SGR_FAILED + "failed." + SGR_RESET fmt.wprintfln(batch_writer, - " %s " + SGR_FAILED + "failed." + SGR_RESET, + message, "The test" if total_test_count == 1 else "All tests") } else { + message := " %i test%s failed." if global_log_colors_disabled else " " + SGR_FAILED + "%i" + SGR_RESET + " test%s failed." fmt.wprintfln(batch_writer, - " " + SGR_FAILED + "%i" + SGR_RESET + " test%s failed.", + message, total_failure_count, "" if total_failure_count == 1 else "s") } @@ -907,9 +940,11 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { } } - fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_SHOW) + if !global_ansi_disabled { + fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_SHOW) + } - when FANCY_OUTPUT { + if should_show_animations { if signals_were_raised { fmt.wprintln(batch_writer, ` Signals were raised during this test run. Log messages are likely to have collided with each other. diff --git a/core/testing/runner_windows.odin b/core/testing/runner_windows.odin deleted file mode 100644 index 401804c71..000000000 --- a/core/testing/runner_windows.odin +++ /dev/null @@ -1,22 +0,0 @@ -#+private -package testing - -import win32 "core:sys/windows" - -console_ansi_init :: proc() { - stdout := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE) - if stdout != win32.INVALID_HANDLE && stdout != nil { - old_console_mode: u32 - if win32.GetConsoleMode(stdout, &old_console_mode) { - win32.SetConsoleMode(stdout, old_console_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING) - } - } - - stderr := win32.GetStdHandle(win32.STD_ERROR_HANDLE) - if stderr != win32.INVALID_HANDLE && stderr != nil { - old_console_mode: u32 - if win32.GetConsoleMode(stderr, &old_console_mode) { - win32.SetConsoleMode(stderr, old_console_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING) - } - } -} diff --git a/core/testing/signal_handler.odin b/core/testing/signal_handler.odin index 2f1f7c89a..73ed362f0 100644 --- a/core/testing/signal_handler.odin +++ b/core/testing/signal_handler.odin @@ -12,8 +12,26 @@ package testing import "base:runtime" import "core:log" +@(private, thread_local) +local_test_expected_failures: struct { + signal: i32, + + message_count: int, + messages: [MAX_EXPECTED_ASSERTIONS_PER_TEST]string, + + location_count: int, + locations: [MAX_EXPECTED_ASSERTIONS_PER_TEST]runtime.Source_Code_Location, +} + +@(private, thread_local) +local_test_assertion_raised: struct { + message: string, + location: runtime.Source_Code_Location, +} + Stop_Reason :: enum { Unknown, + Successful_Stop, Illegal_Instruction, Arithmetic_Error, Segmentation_Fault, @@ -21,7 +39,12 @@ Stop_Reason :: enum { } test_assertion_failure_proc :: proc(prefix, message: string, loc: runtime.Source_Code_Location) -> ! { - log.fatalf("%s: %s", prefix, message, location = loc) + if local_test_expected_failures.message_count + local_test_expected_failures.location_count > 0 { + local_test_assertion_raised = { message, loc } + log.debugf("%s\n\tmessage: %q\n\tlocation: %w", prefix, message, loc) + } else { + log.fatalf("%s: %s", prefix, message, location = loc) + } runtime.trap() } diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin index 281fbde40..961f5c7ce 100644 --- a/core/testing/signal_handler_libc.odin +++ b/core/testing/signal_handler_libc.odin @@ -12,21 +12,30 @@ package testing import "base:intrinsics" import "core:c/libc" -import "core:encoding/ansi" -import "core:sync" import "core:os" +import "core:sync" +import "core:terminal/ansi" @(private="file") stop_runner_flag: libc.sig_atomic_t @(private="file") stop_test_gate: sync.Mutex @(private="file") stop_test_index: libc.sig_atomic_t -@(private="file") stop_test_reason: libc.sig_atomic_t +@(private="file") stop_test_signal: libc.sig_atomic_t +@(private="file") stop_test_passed: libc.sig_atomic_t @(private="file") stop_test_alert: libc.sig_atomic_t -@(private="file", thread_local) -local_test_index: libc.sig_atomic_t -@(private="file", thread_local) -local_test_index_set: bool +when ODIN_ARCH == .i386 && ODIN_OS == .Windows { + // Thread-local storage is problematic on Windows i386 + @(private="file") + local_test_index: libc.sig_atomic_t + @(private="file") + local_test_index_set: bool +} else { + @(private="file", thread_local) + local_test_index: libc.sig_atomic_t + @(private="file", thread_local) + local_test_index_set: bool +} // Windows does not appear to have a SIGTRAP, so this is defined here, instead // of in the libc package, just so there's no confusion about it being @@ -63,9 +72,11 @@ stop_test_callback :: proc "c" (sig: libc.int) { // NOTE(Feoramund): Using these write calls in a signal handler is // undefined behavior in C99 but possibly tolerated in POSIX 2008. // Either way, we may as well try to salvage what we can. - show_cursor := ansi.CSI + ansi.DECTCEM_SHOW - libc.fwrite(raw_data(show_cursor), size_of(byte), len(show_cursor), libc.stdout) - libc.fflush(libc.stdout) + if !global_ansi_disabled { + show_cursor := ansi.CSI + ansi.DECTCEM_SHOW + libc.fwrite(raw_data(show_cursor), size_of(byte), len(show_cursor), libc.stdout) + libc.fflush(libc.stdout) + } // This is an attempt at being compliant by avoiding printf. sigbuf: [8]byte @@ -97,7 +108,30 @@ This is a dire bug and should be reported to the Odin developers. if sync.mutex_guard(&stop_test_gate) { intrinsics.atomic_store(&stop_test_index, local_test_index) - intrinsics.atomic_store(&stop_test_reason, cast(libc.sig_atomic_t)sig) + intrinsics.atomic_store(&stop_test_signal, cast(libc.sig_atomic_t)sig) + passed: bool + check_passing: { + if location := local_test_assertion_raised.location; location != {} { + for i in 0..<local_test_expected_failures.location_count { + if local_test_expected_failures.locations[i] == location { + passed = true + break check_passing + } + } + } + if message := local_test_assertion_raised.message; message != "" { + for i in 0..<local_test_expected_failures.message_count { + if local_test_expected_failures.messages[i] == message { + passed = true + break check_passing + } + } + } + if signal := local_test_expected_failures.signal; signal == sig { + passed = true + } + } + intrinsics.atomic_store(&stop_test_passed, cast(libc.sig_atomic_t)passed) intrinsics.atomic_store(&stop_test_alert, 1) for { @@ -152,11 +186,15 @@ _should_stop_test :: proc() -> (test_index: int, reason: Stop_Reason, ok: bool) intrinsics.atomic_store(&stop_test_alert, 0) test_index = cast(int)intrinsics.atomic_load(&stop_test_index) - switch intrinsics.atomic_load(&stop_test_reason) { - case libc.SIGFPE: reason = .Arithmetic_Error - case libc.SIGILL: reason = .Illegal_Instruction - case libc.SIGSEGV: reason = .Segmentation_Fault - case SIGTRAP: reason = .Unhandled_Trap + if cast(bool)intrinsics.atomic_load(&stop_test_passed) { + reason = .Successful_Stop + } else { + switch intrinsics.atomic_load(&stop_test_signal) { + case libc.SIGFPE: reason = .Arithmetic_Error + case libc.SIGILL: reason = .Illegal_Instruction + case libc.SIGSEGV: reason = .Segmentation_Fault + case SIGTRAP: reason = .Unhandled_Trap + } } ok = true } diff --git a/core/testing/testing.odin b/core/testing/testing.odin index 09bf6dc0e..1357a4683 100644 --- a/core/testing/testing.odin +++ b/core/testing/testing.odin @@ -21,6 +21,8 @@ import "core:mem" _ :: reflect // alias reflect to nothing to force visibility for -vet _ :: mem // in case TRACKING_MEMORY is not enabled +MAX_EXPECTED_ASSERTIONS_PER_TEST :: 5 + // IMPORTANT NOTE: Compiler requires this layout Test_Signature :: proc(^T) @@ -155,3 +157,74 @@ set_fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location location = loc, }) } + +/* +Let the test runner know that it should expect an assertion failure from a +specific location in the source code for this test. + +In the event that an assertion fails, a debug message will be logged with its +exact message and location in a copyable format to make it convenient to write +tests which use this API. + +This procedure may be called up to 5 times with different locations. + +This is a limitation for the sake of simplicity in the implementation, and you +should consider breaking up your tests into smaller procedures if you need to +check for asserts in more than 2 places. +*/ +expect_assert_from :: proc(t: ^T, expected_place: runtime.Source_Code_Location, caller_loc := #caller_location) { + count := local_test_expected_failures.location_count + if count == MAX_EXPECTED_ASSERTIONS_PER_TEST { + panic("This test cannot handle that many expected assertions based on matching the location.", caller_loc) + } + local_test_expected_failures.locations[count] = expected_place + local_test_expected_failures.location_count += 1 +} + +/* +Let the test runner know that it should expect an assertion failure with a +specific message for this test. + +In the event that an assertion fails, a debug message will be logged with its +exact message and location in a copyable format to make it convenient to write +tests which use this API. + +This procedure may be called up to 5 times with different messages. + +This is a limitation for the sake of simplicity in the implementation, and you +should consider breaking up your tests into smaller procedures if you need to +check for more than a couple different assertion messages. +*/ +expect_assert_message :: proc(t: ^T, expected_message: string, caller_loc := #caller_location) { + count := local_test_expected_failures.message_count + if count == MAX_EXPECTED_ASSERTIONS_PER_TEST { + panic("This test cannot handle that many expected assertions based on matching the message.", caller_loc) + } + local_test_expected_failures.messages[count] = expected_message + local_test_expected_failures.message_count += 1 +} + +expect_assert :: proc { + expect_assert_from, + expect_assert_message, +} + +/* +Let the test runner know that it should expect a signal to be raised within +this test. + +This API is for advanced users, as arbitrary signals will not be caught; only +the ones already handled by the test runner, such as + +- SIGINT, (interrupt) +- SIGTERM, (polite termination) +- SIGILL, (illegal instruction) +- SIGFPE, (arithmetic error) +- SIGSEGV, and (segmentation fault) +- SIGTRAP (only on POSIX systems). (trap / debug trap) + +Note that only one signal can be expected per test. +*/ +expect_signal :: proc(t: ^T, #any_int sig: i32) { + local_test_expected_failures.signal = sig +} diff --git a/core/text/regex/common/common.odin b/core/text/regex/common/common.odin index 4a303e0a3..e60bef58f 100644 --- a/core/text/regex/common/common.odin +++ b/core/text/regex/common/common.odin @@ -15,8 +15,6 @@ MAX_PROGRAM_SIZE :: int(max(i16)) MAX_CLASSES :: int(max(u8)) Flag :: enum u8 { - // Global: try to match the pattern anywhere in the string. - Global, // Multiline: treat `^` and `$` as if they also match newlines. Multiline, // Case Insensitive: treat `a-z` as if it was also `A-Z`. @@ -36,7 +34,6 @@ Flags :: bit_set[Flag; u8] @(rodata) Flag_To_Letter := #sparse[Flag]u8 { - .Global = 'g', .Multiline = 'm', .Case_Insensitive = 'i', .Ignore_Whitespace = 'x', diff --git a/core/text/regex/compiler/compiler.odin b/core/text/regex/compiler/compiler.odin index b3ded0104..2f0f183e9 100644 --- a/core/text/regex/compiler/compiler.odin +++ b/core/text/regex/compiler/compiler.odin @@ -195,8 +195,12 @@ generate_code :: proc(c: ^Compiler, node: Node) -> (code: Program) { case ^Node_Anchor: if .Multiline in c.flags { - append(&code, Opcode.Multiline_Open) - append(&code, Opcode.Multiline_Close) + if specific.start { + append(&code, Opcode.Assert_Start_Multiline) + } else { + append(&code, Opcode.Multiline_Open) + append(&code, Opcode.Multiline_Close) + } } else { if specific.start { append(&code, Opcode.Assert_Start) @@ -401,7 +405,7 @@ compile :: proc(tree: Node, flags: common.Flags) -> (code: Program, class_data: pc_open := 0 - add_global: if .Global in flags { + optimize_opening: { // Check if the opening to the pattern is predictable. // If so, use one of the optimized Wait opcodes. iter := virtual_machine.Opcode_Iterator{ code[:], 0 } @@ -412,7 +416,7 @@ compile :: proc(tree: Node, flags: common.Flags) -> (code: Program, class_data: pc_open += size_of(Opcode) inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open])) pc_open += size_of(u8) - break add_global + break optimize_opening case .Rune: operand := intrinsics.unaligned_load(cast(^rune)&code[pc+1]) @@ -420,24 +424,28 @@ compile :: proc(tree: Node, flags: common.Flags) -> (code: Program, class_data: pc_open += size_of(Opcode) inject_raw(&code, pc_open, operand) pc_open += size_of(rune) - break add_global + break optimize_opening case .Rune_Class: inject_at(&code, pc_open, Opcode.Wait_For_Rune_Class) pc_open += size_of(Opcode) inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open])) pc_open += size_of(u8) - break add_global + break optimize_opening case .Rune_Class_Negated: inject_at(&code, pc_open, Opcode.Wait_For_Rune_Class_Negated) pc_open += size_of(Opcode) inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open])) pc_open += size_of(u8) - break add_global + break optimize_opening case .Save: continue + + case .Assert_Start, .Assert_Start_Multiline: + break optimize_opening + case: break seek_loop } diff --git a/core/text/regex/regex.odin b/core/text/regex/regex.odin index 8f8efe252..bff82c407 100644 --- a/core/text/regex/regex.odin +++ b/core/text/regex/regex.odin @@ -8,6 +8,7 @@ package regex Feoramund: Initial implementation. */ +import "base:runtime" import "core:text/regex/common" import "core:text/regex/compiler" import "core:text/regex/optimizer" @@ -51,7 +52,7 @@ This struct corresponds to a set of string captures from a RegEx match. such that `str[pos[0][0]:pos[0][1]] == groups[0]`. */ Capture :: struct { - pos: [][2]int, + pos: [][2]int, groups: []string, } @@ -59,11 +60,23 @@ Capture :: struct { A compiled Regular Expression value, to be used with the `match_*` procedures. */ Regular_Expression :: struct { - flags: Flags `fmt:"-"`, + flags: Flags `fmt:"-"`, class_data: []virtual_machine.Rune_Class_Data `fmt:"-"`, - program: []virtual_machine.Opcode `fmt:"-"`, + program: []virtual_machine.Opcode `fmt:"-"`, } +/* +An iterator to repeatedly match a pattern against a string, to be used with `*_iterator` procedures. +*/ +Match_Iterator :: struct { + regex: Regular_Expression, + capture: Capture, + vm: virtual_machine.Machine, + idx: int, + temp: runtime.Allocator, + threads: int, + done: bool, +} /* Create a regular expression from a string pattern and a set of flags. @@ -87,7 +100,6 @@ create :: proc( permanent_allocator := context.allocator, temporary_allocator := context.temp_allocator, ) -> (result: Regular_Expression, err: Error) { - // For the sake of speed and simplicity, we first run all the intermediate // processes such as parsing and compilation through the temporary // allocator. @@ -152,7 +164,6 @@ to escape the delimiter if found in the middle of the string. All runes after the closing delimiter will be parsed as flags: -- 'g': Global - 'm': Multiline - 'i': Case_Insensitive - 'x': Ignore_Whitespace @@ -229,7 +240,6 @@ create_by_user :: proc( // to `end` here. for r in pattern[start + end:] { switch r { - case 'g': flags += { .Global } case 'm': flags += { .Multiline } case 'i': flags += { .Case_Insensitive } case 'x': flags += { .Ignore_Whitespace } @@ -246,6 +256,40 @@ create_by_user :: proc( } /* +Create a `Match_Iterator` using a string to search, a regular expression to match against it, and a set of flags. + +*Allocates Using Provided Allocators* + +Inputs: +- str: The string to iterate over. +- pattern: The pattern to match. +- flags: A `bit_set` of RegEx flags. +- permanent_allocator: The allocator to use for the compiled regular expression. (default: context.allocator) +- temporary_allocator: The allocator to use for the intermediate compilation and iteration stages. (default: context.temp_allocator) + +Returns: +- result: The `Match_Iterator`. +- err: An error, if one occurred. +*/ +create_iterator :: proc( + str: string, + pattern: string, + flags: Flags = {}, + permanent_allocator := context.allocator, + temporary_allocator := context.temp_allocator, +) -> (result: Match_Iterator, err: Error) { + + result.regex = create(pattern, flags, permanent_allocator, temporary_allocator) or_return + result.capture = preallocate_capture() + result.temp = temporary_allocator + result.vm = virtual_machine.create(result.regex.program, str) + result.vm.class_data = result.regex.class_data + result.threads = max(1, virtual_machine.opcode_count(result.vm.code) - 1) + + return +} + +/* Match a regular expression against a string and allocate the results into the returned `capture` structure. @@ -387,9 +431,127 @@ match_with_preallocated_capture :: proc( return } +/* +Iterate over a `Match_Iterator` and return successive captures. + +Inputs: +- it: Pointer to the `Match_Iterator` to iterate over. + +Returns: +- result: `Capture` for this iteration. +- ok: A bool indicating if there was a match, stopping the iteration on `false`. +*/ +match_iterator :: proc(it: ^Match_Iterator) -> (result: Capture, index: int, ok: bool) { + assert(len(it.capture.groups) >= common.MAX_CAPTURE_GROUPS, + "Pre-allocated RegEx capture `groups` must be at least 10 elements long.") + assert(len(it.capture.pos) >= common.MAX_CAPTURE_GROUPS, + "Pre-allocated RegEx capture `pos` must be at least 10 elements long.") + + // Guard against situations in which the iterator should finish. + if it.done { + return + } + + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + if it.idx > 0 { + // Reset the state needed to `virtual_machine.run` again. + it.vm.top_thread = 0 + it.vm.current_rune = rune(0) + it.vm.current_rune_size = 0 + for i in 0..<it.threads { + it.vm.threads[i] = {} + it.vm.next_threads[i] = {} + } + } + + // Take note of where the string pointer is before we start. + sp_before := it.vm.string_pointer + + saved: ^[2 * common.MAX_CAPTURE_GROUPS]int + { + context.allocator = it.temp + if .Unicode in it.regex.flags { + saved, ok = virtual_machine.run(&it.vm, true) + } else { + saved, ok = virtual_machine.run(&it.vm, false) + } + } + + if !ok { + // Match failed, bail out. + return + } + + if it.vm.string_pointer == sp_before { + // The string pointer did not move, but there was a match. + // + // At this point, the pattern supplied to the iterator will infinitely + // loop if we do not intervene. + it.done = true + } + if it.vm.string_pointer == len(it.vm.memory) { + // The VM hit the end of the string. + // + // We do not check at the start, because a match of pattern `$` + // against string "" is valid and must return a match. + // + // This check prevents a double-match of `$` against a non-empty string. + it.done = true + } + + str := string(it.vm.memory) + num_groups: int + + if saved != nil { + n := 0 + + #no_bounds_check for i := 0; i < len(saved); i += 2 { + a, b := saved[i], saved[i + 1] + if a == -1 || b == -1 { + continue + } + + it.capture.groups[n] = str[a:b] + it.capture.pos[n] = {a, b} + n += 1 + } + num_groups = n + } + + defer it.idx += 1 + + if num_groups > 0 { + result = {it.capture.pos[:num_groups], it.capture.groups[:num_groups]} + } + return result, it.idx, ok +} + match :: proc { match_and_allocate_capture, match_with_preallocated_capture, + match_iterator, +} + +/* +Reset an iterator, allowing it to be run again as if new. + +Inputs: +- it: The iterator to reset. +*/ +reset :: proc(it: ^Match_Iterator) { + it.done = false + it.idx = 0 + it.vm.string_pointer = 0 + + it.vm.top_thread = 0 + it.vm.current_rune = rune(0) + it.vm.current_rune_size = 0 + it.vm.last_rune = rune(0) + for i in 0..<it.threads { + it.vm.threads[i] = {} + it.vm.next_threads[i] = {} + } } /* @@ -406,7 +568,7 @@ Returns: @require_results preallocate_capture :: proc(allocator := context.allocator) -> (result: Capture) { context.allocator = allocator - result.pos = make([][2]int, common.MAX_CAPTURE_GROUPS) + result.pos = make([][2]int, common.MAX_CAPTURE_GROUPS) result.groups = make([]string, common.MAX_CAPTURE_GROUPS) return } @@ -436,7 +598,7 @@ Free all data allocated by the `match_and_allocate_capture` procedure. *Frees Using Provided Allocator* Inputs: -- capture: A Capture. +- capture: A `Capture`. - allocator: (default: context.allocator) */ destroy_capture :: proc(capture: Capture, allocator := context.allocator) { @@ -445,7 +607,24 @@ destroy_capture :: proc(capture: Capture, allocator := context.allocator) { delete(capture.pos) } +/* +Free all data allocated by the `create_iterator` procedure. + +*Frees Using Provided Allocator* + +Inputs: +- it: A `Match_Iterator` +- allocator: (default: context.allocator) +*/ +destroy_iterator :: proc(it: Match_Iterator, allocator := context.allocator) { + context.allocator = allocator + destroy(it.regex) + destroy(it.capture) + virtual_machine.destroy(it.vm) +} + destroy :: proc { destroy_regex, destroy_capture, + destroy_iterator, } diff --git a/core/text/regex/virtual_machine/doc.odin b/core/text/regex/virtual_machine/doc.odin index 1b0694565..d599dbb1c 100644 --- a/core/text/regex/virtual_machine/doc.odin +++ b/core/text/regex/virtual_machine/doc.odin @@ -109,34 +109,42 @@ For more information, see: https://swtch.com/~rsc/regexp/regexp2.html (0x0A) Assert_Start - Asserts that the thread is at the beginning of a string. + Asserts that the thread is at the beginning of the string. - (0x0B) Assert_End + (0x0B) Assert_Start_Multiline - Asserts that the thread is at the end of a string. + This opcode is compiled in only when the `Multiline` flag is present as a + replacement for the `^` text anchor. - (0x0C) Assert_Word_Boundary + Asserts that the thread is at the beginning of the string or previously + parsed either a "\n" or "\r". + + (0x0C) Assert_End + + Asserts that the thread is at the end of the string. + + (0x0D) Assert_Word_Boundary Asserts that the thread is on a word boundary, which can be the start or end of the text. This examines both the current rune and the next rune. - (0x0D) Assert_Non_Word_Boundary + (0x0E) Assert_Non_Word_Boundary A modified version of Assert_Word_Boundary that returns the opposite value. - (0x0E) Multiline_Open + (0x0F) Multiline_Open - This opcode is compiled in only when the `Multiline` flag is present, and - it replaces both `^` and `$` text anchors. + This opcode is compiled in only when the `Multiline` flag is present as a + replacement for the `$` text anchor. - It asserts that either the current thread is on one of the string - boundaries, or it consumes a `\n` or `\r` character. + It asserts that either the current thread is at the end of the string, + or it consumes a `\n` or `\r` character. If a `\r` character is consumed, the PC will be advanced to the sibling `Multiline_Close` opcode to optionally consume a `\n` character on the next frame. - (0x0F) Multiline_Close + (0x10) Multiline_Close This opcode is always present after `Multiline_Open`. @@ -144,10 +152,10 @@ For more information, see: https://swtch.com/~rsc/regexp/regexp2.html For example, Windows newlines are represented by the characters `\r\n`, whereas UNIX newlines are `\n` and Macintosh newlines are `\r`. - (0x10) Wait_For_Byte - (0x11) Wait_For_Rune - (0x12) Wait_For_Rune_Class - (0x13) Wait_For_Rune_Class_Negated + (0x11) Wait_For_Byte + (0x12) Wait_For_Rune + (0x13) Wait_For_Rune_Class + (0x14) Wait_For_Rune_Class_Negated These opcodes are an optimization around restarting threads on failed matches when the beginning to a pattern is predictable and the Global flag @@ -156,7 +164,7 @@ For more information, see: https://swtch.com/~rsc/regexp/regexp2.html They will cause the VM to wait for the next rune to match before splitting, as would happen in the un-optimized version. - (0x14) Match_All_And_Escape + (0x15) Match_All_And_Escape This opcode is an optimized version of `.*$` or `.+$` that causes the active thread to immediately work on escaping the program by following all diff --git a/core/text/regex/virtual_machine/util.odin b/core/text/regex/virtual_machine/util.odin index fa94a139f..79c781e4a 100644 --- a/core/text/regex/virtual_machine/util.odin +++ b/core/text/regex/virtual_machine/util.odin @@ -34,6 +34,7 @@ iterate_opcodes :: proc(iter: ^Opcode_Iterator) -> (opcode: Opcode, pc: int, ok: case .Split: iter.pc += size_of(Opcode) + 2 * size_of(u16) case .Save: iter.pc += size_of(Opcode) + size_of(u8) case .Assert_Start: iter.pc += size_of(Opcode) + case .Assert_Start_Multiline: iter.pc += size_of(Opcode) case .Assert_End: iter.pc += size_of(Opcode) case .Assert_Word_Boundary: iter.pc += size_of(Opcode) case .Assert_Non_Word_Boundary: iter.pc += size_of(Opcode) @@ -64,6 +65,7 @@ opcode_to_name :: proc(opcode: Opcode) -> (str: string) { case .Split: str = "Split" case .Save: str = "Save" case .Assert_Start: str = "Assert_Start" + case .Assert_Start_Multiline: str = "Assert_Start_Multiline" case .Assert_End: str = "Assert_End" case .Assert_Word_Boundary: str = "Assert_Word_Boundary" case .Assert_Non_Word_Boundary: str = "Assert_Non_Word_Boundary" diff --git a/core/text/regex/virtual_machine/virtual_machine.odin b/core/text/regex/virtual_machine/virtual_machine.odin index a4fca6c4d..c292b0e99 100644 --- a/core/text/regex/virtual_machine/virtual_machine.odin +++ b/core/text/regex/virtual_machine/virtual_machine.odin @@ -37,16 +37,17 @@ Opcode :: enum u8 { Split = 0x08, // | u16, u16 Save = 0x09, // | u8 Assert_Start = 0x0A, // | - Assert_End = 0x0B, // | - Assert_Word_Boundary = 0x0C, // | - Assert_Non_Word_Boundary = 0x0D, // | - Multiline_Open = 0x0E, // | - Multiline_Close = 0x0F, // | - Wait_For_Byte = 0x10, // | u8 - Wait_For_Rune = 0x11, // | i32 - Wait_For_Rune_Class = 0x12, // | u8 - Wait_For_Rune_Class_Negated = 0x13, // | u8 - Match_All_And_Escape = 0x14, // | + Assert_Start_Multiline = 0x0B, // | + Assert_End = 0x0C, // | + Assert_Word_Boundary = 0x0D, // | + Assert_Non_Word_Boundary = 0x0E, // | + Multiline_Open = 0x0F, // | + Multiline_Close = 0x10, // | + Wait_For_Byte = 0x11, // | u8 + Wait_For_Rune = 0x12, // | i32 + Wait_For_Rune_Class = 0x13, // | u8 + Wait_For_Rune_Class_Negated = 0x14, // | u8 + Match_All_And_Escape = 0x15, // | } Thread :: struct { @@ -77,6 +78,8 @@ Machine :: struct { current_rune_size: int, next_rune: rune, next_rune_size: int, + + last_rune: rune, } @@ -169,6 +172,12 @@ add_thread :: proc(vm: ^Machine, saved: ^[2 * common.MAX_CAPTURE_GROUPS]int, pc: pc += size_of(Opcode) continue } + case .Assert_Start_Multiline: + sp := vm.string_pointer+vm.current_rune_size + if sp == 0 || vm.last_rune == '\n' || vm.last_rune == '\r' { + pc += size_of(Opcode) + continue + } case .Assert_End: sp := vm.string_pointer+vm.current_rune_size if sp == len(vm.memory) { @@ -177,24 +186,12 @@ add_thread :: proc(vm: ^Machine, saved: ^[2 * common.MAX_CAPTURE_GROUPS]int, pc: } case .Multiline_Open: sp := vm.string_pointer+vm.current_rune_size - if sp == 0 || sp == len(vm.memory) { - if vm.next_rune == '\r' || vm.next_rune == '\n' { - // The VM is currently on a newline at the string boundary, - // so consume the newline next frame. - when common.ODIN_DEBUG_REGEX { - io.write_string(common.debug_stream, "*** New thread added [PC:") - common.write_padded_hex(common.debug_stream, pc, 4) - io.write_string(common.debug_stream, "]\n") - } - vm.next_threads[vm.top_thread] = Thread{ pc = pc, saved = saved } - vm.top_thread += 1 - } else { - // Skip the `Multiline_Close` opcode. - pc += 2 * size_of(Opcode) - continue - } + if sp == len(vm.memory) { + // Skip the `Multiline_Close` opcode. + pc += 2 * size_of(Opcode) + continue } else { - // Not on a string boundary. + // Not at the end of the string. // Try to consume a newline next frame in the other opcode loop. when common.ODIN_DEBUG_REGEX { io.write_string(common.debug_stream, "*** New thread added [PC:") @@ -329,10 +326,10 @@ add_thread :: proc(vm: ^Machine, saved: ^[2 * common.MAX_CAPTURE_GROUPS]int, pc: run :: proc(vm: ^Machine, $UNICODE_MODE: bool) -> (saved: ^[2 * common.MAX_CAPTURE_GROUPS]int, ok: bool) #no_bounds_check { when UNICODE_MODE { - vm.next_rune, vm.next_rune_size = utf8.decode_rune_in_string(vm.memory) + vm.next_rune, vm.next_rune_size = utf8.decode_rune_in_string(vm.memory[vm.string_pointer:]) } else { if len(vm.memory) > 0 { - vm.next_rune = cast(rune)vm.memory[0] + vm.next_rune = cast(rune)vm.memory[vm.string_pointer] vm.next_rune_size = 1 } } @@ -613,6 +610,7 @@ run :: proc(vm: ^Machine, $UNICODE_MODE: bool) -> (saved: ^[2 * common.MAX_CAPTU break } + vm.last_rune = vm.current_rune vm.string_pointer += vm.current_rune_size } @@ -627,8 +625,9 @@ opcode_count :: proc(code: Program) -> (opcodes: int) { return } -create :: proc(code: Program, str: string) -> (vm: Machine) { +create :: proc(code: Program, str: string, allocator := context.allocator) -> (vm: Machine) { assert(len(code) > 0, "RegEx VM has no instructions.") + context.allocator = allocator vm.memory = str vm.code = code @@ -644,3 +643,11 @@ create :: proc(code: Program, str: string) -> (vm: Machine) { return } + +destroy :: proc(vm: Machine, allocator := context.allocator) { + context.allocator = allocator + + delete(vm.busy_map) + free(vm.threads) + free(vm.next_threads) +} diff --git a/core/text/scanner/scanner.odin b/core/text/scanner/scanner.odin index 24dbcc8a4..96109f614 100644 --- a/core/text/scanner/scanner.odin +++ b/core/text/scanner/scanner.odin @@ -285,6 +285,7 @@ scan_number :: proc(s: ^Scanner, ch: rune, seen_dot: bool) -> (rune, rune) { case 'o': return "octal literal" case 'z': return "dozenal literal" case 'x': return "hexadecimal literal" + case 'h': return "hexadecimal literal" } return "decimal literal" } @@ -360,7 +361,8 @@ scan_number :: proc(s: ^Scanner, ch: rune, seen_dot: bool) -> (rune, rune) { base, prefix = 12, 'z' case 'h': tok = Float - fallthrough + ch = advance(s) + base, prefix = 16, 'h' case 'x': ch = advance(s) base, prefix = 16, 'x' @@ -447,7 +449,7 @@ scan_string :: proc(s: ^Scanner, quote: rune) -> (n: int) { ch := advance(s) for ch != quote { if ch == '\n' || ch < 0 { - error(s, "literal no terminated") + error(s, "literal not terminated") return } if ch == '\\' { diff --git a/core/thread/thread.odin b/core/thread/thread.odin index c1cbceb42..194c7bfef 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -39,7 +39,8 @@ Type representing a thread handle and the associated with that thread data. Thread :: struct { using specific: Thread_Os_Specific, flags: bit_set[Thread_State; u8], - // Thread ID. + // Thread ID. Depending on the platform, may start out as 0 (zero) until the thread + // has had a chance to run. id: int, // The thread procedure. procedure: Thread_Proc, @@ -257,19 +258,23 @@ to execute. The thread will have priority specified by the `priority` parameter. If `self_cleanup` is specified, after the thread finishes the execution of the `fn` procedure, the resources associated with the thread are going to be -automatically freed. **Do not** dereference the `^Thread` pointer, if this -flag is specified. +automatically freed. + +**Do not** dereference the `^Thread` pointer, if this flag is specified. +That includes calling `join`, which needs to dereference ^Thread`. **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread { +create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread) { thread_proc :: proc(t: ^Thread) { fn := cast(proc())t.data fn() } - t := create(thread_proc, priority) + if t = create(thread_proc, priority); t == nil { + return + } t.data = rawptr(fn) if self_cleanup { intrinsics.atomic_or(&t.flags, {.Self_Cleanup}) @@ -288,21 +293,25 @@ to execute. The thread will have priority specified by the `priority` parameter. If `self_cleanup` is specified, after the thread finishes the execution of the `fn` procedure, the resources associated with the thread are going to be -automatically freed. **Do not** dereference the `^Thread` pointer, if this -flag is specified. +automatically freed. + +**Do not** dereference the `^Thread` pointer, if this flag is specified. +That includes calling `join`, which needs to dereference ^Thread`. **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread { +create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread) { thread_proc :: proc(t: ^Thread) { fn := cast(proc(rawptr))t.data assert(t.user_index >= 1) data := t.user_args[0] fn(data) } - t := create(thread_proc, priority) + if t = create(thread_proc, priority); t == nil { + return + } t.data = rawptr(fn) t.user_index = 1 t.user_args[0] = data @@ -323,14 +332,16 @@ to execute. The thread will have priority specified by the `priority` parameter. If `self_cleanup` is specified, after the thread finishes the execution of the `fn` procedure, the resources associated with the thread are going to be -automatically freed. **Do not** dereference the `^Thread` pointer, if this -flag is specified. +automatically freed. + +**Do not** dereference the `^Thread` pointer, if this flag is specified. +That includes calling `join`, which needs to dereference ^Thread`. **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread +create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread) where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { fn := cast(proc(T))t.data @@ -338,7 +349,9 @@ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_contex data := (^T)(&t.user_args[0])^ fn(data) } - t := create(thread_proc, priority) + if t = create(thread_proc, priority); t == nil { + return + } t.data = rawptr(fn) t.user_index = 1 @@ -364,14 +377,16 @@ to execute. The thread will have priority specified by the `priority` parameter. If `self_cleanup` is specified, after the thread finishes the execution of the `fn` procedure, the resources associated with the thread are going to be -automatically freed. **Do not** dereference the `^Thread` pointer, if this -flag is specified. +automatically freed. + +**Do not** dereference the `^Thread` pointer, if this flag is specified. +That includes calling `join`, which needs to dereference ^Thread`. **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread +create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread) where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { fn := cast(proc(T1, T2))t.data @@ -383,7 +398,9 @@ create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), fn(arg1, arg2) } - t := create(thread_proc, priority) + if t = create(thread_proc, priority); t == nil { + return + } t.data = rawptr(fn) t.user_index = 2 @@ -411,14 +428,16 @@ to execute. The thread will have priority specified by the `priority` parameter. If `self_cleanup` is specified, after the thread finishes the execution of the `fn` procedure, the resources associated with the thread are going to be -automatically freed. **Do not** dereference the `^Thread` pointer, if this -flag is specified. +automatically freed. + +**Do not** dereference the `^Thread` pointer, if this flag is specified. +That includes calling `join`, which needs to dereference ^Thread`. **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread +create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread) where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { fn := cast(proc(T1, T2, T3))t.data @@ -431,7 +450,9 @@ create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: pr fn(arg1, arg2, arg3) } - t := create(thread_proc, priority) + if t = create(thread_proc, priority); t == nil { + return + } t.data = rawptr(fn) t.user_index = 3 @@ -460,14 +481,16 @@ to execute. The thread will have priority specified by the `priority` parameter. If `self_cleanup` is specified, after the thread finishes the execution of the `fn` procedure, the resources associated with the thread are going to be -automatically freed. **Do not** dereference the `^Thread` pointer, if this -flag is specified. +automatically freed. + +**Do not** dereference the `^Thread` pointer, if this flag is specified. +That includes calling `join`, which needs to dereference ^Thread`. **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread +create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread) where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { fn := cast(proc(T1, T2, T3, T4))t.data @@ -481,7 +504,9 @@ create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: fn(arg1, arg2, arg3, arg4) } - t := create(thread_proc, priority) + if t = create(thread_proc, priority); t == nil { + return + } t.data = rawptr(fn) t.user_index = 4 @@ -513,8 +538,10 @@ _select_context_for_thread :: proc(init_context: Maybe(runtime.Context)) -> runt Ensure that the temp allocator is thread-safe when the user provides a specific initial context to use. Without this, the thread will use the same temp allocator state as the parent thread, and thus, bork it up. */ - if ctx.temp_allocator.procedure == runtime.default_temp_allocator_proc { - ctx.temp_allocator.data = &runtime.global_default_temp_allocator_data + when !ODIN_DEFAULT_TO_NIL_ALLOCATOR { + if ctx.temp_allocator.procedure == runtime.default_temp_allocator_proc { + ctx.temp_allocator.data = &runtime.global_default_temp_allocator_data + } } return ctx } @@ -529,4 +556,4 @@ _maybe_destroy_default_temp_allocator :: proc(init_context: Maybe(runtime.Contex if context.temp_allocator.procedure == runtime.default_temp_allocator_proc { runtime.default_temp_allocator_destroy(auto_cast context.temp_allocator.data) } -} +}
\ No newline at end of file diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index 59bf90620..15b3a28d2 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -120,6 +120,20 @@ pool_join :: proc(pool: ^Pool) { yield() + unstarted_count: int + for t in pool.threads { + flags := intrinsics.atomic_load(&t.flags) + if .Started not_in flags { + unstarted_count += 1 + } + } + + // most likely the user forgot to call `pool_start` + // exit here, so we don't hang forever + if len(pool.threads) == unstarted_count { + return + } + started_count: int for started_count < len(pool.threads) { started_count = 0 diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index ff79cfcbc..1db32657e 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -29,14 +29,10 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { t.id = sync.current_thread_id() - if .Started not_in sync.atomic_load(&t.flags) { + for (.Started not_in sync.atomic_load(&t.flags)) { sync.wait(&t.start_ok) } - if .Joined in sync.atomic_load(&t.flags) { - return nil - } - // Enable thread's cancelability. // NOTE(laytan): Darwin does not correctly/fully support all of this, not doing this does // actually make pthread_cancel work in the capacity of my tests, while executing this would @@ -85,8 +81,13 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { } defer posix.pthread_attr_destroy(&attrs) - // NOTE(tetra, 2019-11-01): These only fail if their argument is invalid. + stacksize: posix.rlimit + if res := posix.getrlimit(.STACK, &stacksize); res == .OK && stacksize.rlim_cur > 0 { + _ = posix.pthread_attr_setstacksize(&attrs, uint(stacksize.rlim_cur)) + } + res: posix.Errno + // NOTE(tetra, 2019-11-01): These only fail if their argument is invalid. res = posix.pthread_attr_setdetachstate(&attrs, .CREATE_JOINABLE) assert(res == nil) when ODIN_OS != .Haiku && ODIN_OS != .NetBSD { @@ -124,7 +125,6 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { free(thread, thread.creation_allocator) return nil } - return thread } @@ -149,10 +149,13 @@ _join :: proc(t: ^Thread) { // Prevent non-started threads from blocking main thread with initial wait // condition. - if .Started not_in sync.atomic_load(&t.flags) { + for (.Started not_in sync.atomic_load(&t.flags)) { _start(t) } + posix.pthread_join(t.unix_thread, nil) + + t.flags += {.Joined} } _join_multiple :: proc(threads: ..^Thread) { diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index cc73a2d6a..358e3e7f1 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -13,6 +13,7 @@ Thread_Os_Specific :: struct { win32_thread: win32.HANDLE, win32_thread_id: win32.DWORD, mutex: sync.Mutex, + start_ok: sync.Sema, } _thread_priority_map := [Thread_Priority]i32{ @@ -27,12 +28,10 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { __windows_thread_entry_proc :: proc "system" (t_: rawptr) -> win32.DWORD { t := (^Thread)(t_) - if .Joined in sync.atomic_load(&t.flags) { - return 0 + for (.Started not_in sync.atomic_load(&t.flags)) { + sync.wait(&t.start_ok) } - t.id = sync.current_thread_id() - { init_context := t.init_context @@ -76,6 +75,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { thread.procedure = procedure thread.win32_thread = win32_thread thread.win32_thread_id = win32_thread_id + thread.id = int(win32_thread_id) ok := win32.SetThreadPriority(win32_thread, _thread_priority_map[priority]) assert(ok == true) @@ -103,16 +103,15 @@ _join :: proc(t: ^Thread) { return } - t.flags += {.Joined} - - if .Started not_in t.flags { - t.flags += {.Started} - win32.ResumeThread(t.win32_thread) + for (.Started not_in sync.atomic_load(&t.flags)) { + _start(t) } win32.WaitForSingleObject(t.win32_thread, win32.INFINITE) win32.CloseHandle(t.win32_thread) t.win32_thread = win32.INVALID_HANDLE + + t.flags += {.Joined} } _join_multiple :: proc(threads: ..^Thread) { @@ -136,6 +135,7 @@ _join_multiple :: proc(threads: ..^Thread) { for t in threads { win32.CloseHandle(t.win32_thread) t.win32_thread = win32.INVALID_HANDLE + t.flags += {.Joined} } } diff --git a/core/time/datetime/constants.odin b/core/time/datetime/constants.odin index e24709e49..b4fb4a15f 100644 --- a/core/time/datetime/constants.odin +++ b/core/time/datetime/constants.odin @@ -5,12 +5,10 @@ Type representing a mononotic day number corresponding to a date. Ordinal 1 = Midnight Monday, January 1, 1 A.D. (Gregorian) | Midnight Monday, January 3, 1 A.D. (Julian) -*/ -Ordinal :: i64 -/* +Every other ordinal counts days forwards, starting from the above date. */ -EPOCH :: Ordinal(1) +Ordinal :: i64 /* Minimum valid value for date. diff --git a/core/time/datetime/datetime.odin b/core/time/datetime/datetime.odin index 2cd90b0e7..f52bff9b6 100644 --- a/core/time/datetime/datetime.odin +++ b/core/time/datetime/datetime.odin @@ -98,7 +98,7 @@ This procedure takes the value of an ordinal and returns the day of week for that ordinal. */ day_of_week :: proc "contextless" (ordinal: Ordinal) -> (day: Weekday) { - return Weekday((ordinal - EPOCH + 1) %% 7) + return Weekday(ordinal %% 7) } /* @@ -349,8 +349,7 @@ the result is unspecified. unsafe_date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal) { year_minus_one := date.year - 1 - // Day before epoch - ordinal = EPOCH - 1 + ordinal = 0 // Add non-leap days ordinal += 365 * year_minus_one @@ -382,17 +381,17 @@ This procedure returns the year and the day of the year of a given ordinal. Of the ordinal is outside of its valid range, the result is unspecified. */ unsafe_ordinal_to_year :: proc "contextless" (ordinal: Ordinal) -> (year: i64, day_ordinal: i64) { - // Days after epoch - d0 := ordinal - EPOCH + // Correct for leap year cycle starting at day 1. + d0 := ordinal - 1 // Number of 400-year cycles and remainder - n400, d1 := divmod(d0, 146097) + n400, d1 := divmod(d0, 365*400 + 100 - 3) // Number of 100-year cycles and remainder - n100, d2 := divmod(d1, 36524) + n100, d2 := divmod(d1, 365*100 + 25 - 1) // Number of 4-year cycles and remainder - n4, d3 := divmod(d2, 1461) + n4, d3 := divmod(d2, 365*4 + 1) // Number of remaining days n1, d4 := divmod(d3, 365) diff --git a/core/time/perf.odin b/core/time/perf.odin index 784d7acd6..f4e1b4aa8 100644 --- a/core/time/perf.odin +++ b/core/time/perf.odin @@ -18,6 +18,13 @@ tick_now :: proc "contextless" () -> Tick { } /* +Add duration to a tick. +*/ +tick_add :: proc "contextless" (t: Tick, d: Duration) -> Tick { + return Tick{t._nsec + i64(d)} +} + +/* Obtain the difference between ticks. */ tick_diff :: proc "contextless" (start, end: Tick) -> Duration { @@ -97,6 +104,8 @@ TSC at a fixed frequency, independent of ACPI state, and CPU frequency. has_invariant_tsc :: proc "contextless" () -> bool { when ODIN_ARCH == .amd64 { return x86_has_invariant_tsc() + } else when ODIN_ARCH == .arm64 { + return true } return false diff --git a/core/time/time_js.odin b/core/time/time_js.odin index 9175fbfe9..0e021dc88 100644 --- a/core/time/time_js.odin +++ b/core/time/time_js.odin @@ -24,7 +24,7 @@ _sleep :: proc "contextless" (d: Duration) { _tick_now :: proc "contextless" () -> Tick { foreign odin_env { - tick_now :: proc "contextless" () -> f32 --- + tick_now :: proc "contextless" () -> f64 --- } return Tick{i64(tick_now()*1e6)} } diff --git a/core/time/timezone/tz_js.odin b/core/time/timezone/tz_js.odin new file mode 100644 index 000000000..d63e40033 --- /dev/null +++ b/core/time/timezone/tz_js.odin @@ -0,0 +1,13 @@ +#+build js +#+private +package timezone + +import "core:time/datetime" + +local_tz_name :: proc(allocator := context.allocator) -> (name: string, success: bool) { + return +} + +_region_load :: proc(_reg_str: string, allocator := context.allocator) -> (out_reg: ^datetime.TZ_Region, success: bool) { + return nil, true +}
\ No newline at end of file diff --git a/core/time/timezone/tz_windows.odin b/core/time/timezone/tz_windows.odin index 8dc5f533c..fe00719a2 100644 --- a/core/time/timezone/tz_windows.odin +++ b/core/time/timezone/tz_windows.odin @@ -159,9 +159,9 @@ iana_to_windows_tz :: proc(iana_name: string, allocator := context.allocator) -> status: windows.UError iana_name_wstr := windows.utf8_to_wstring(iana_name, allocator) - defer free(iana_name_wstr, allocator) + defer free(rawptr(iana_name_wstr), allocator) - wintz_name_len := windows.ucal_getWindowsTimeZoneID(iana_name_wstr, -1, raw_data(wintz_name_buffer[:]), len(wintz_name_buffer), &status) + wintz_name_len := windows.ucal_getWindowsTimeZoneID(iana_name_wstr, -1, cstring16(raw_data(wintz_name_buffer[:])), len(wintz_name_buffer), &status) if status != .U_ZERO_ERROR { return } @@ -178,7 +178,7 @@ local_tz_name :: proc(allocator := context.allocator) -> (name: string, success: iana_name_buffer: [128]u16 status: windows.UError - zone_str_len := windows.ucal_getDefaultTimeZone(raw_data(iana_name_buffer[:]), len(iana_name_buffer), &status) + zone_str_len := windows.ucal_getDefaultTimeZone(cstring16(raw_data(iana_name_buffer[:])), len(iana_name_buffer), &status) if status != .U_ZERO_ERROR { return } @@ -291,7 +291,7 @@ _region_load :: proc(reg_str: string, allocator := context.allocator) -> (out_re defer delete(tz_key, allocator) tz_key_wstr := windows.utf8_to_wstring(tz_key, allocator) - defer free(tz_key_wstr, allocator) + defer free(rawptr(tz_key_wstr), allocator) key: windows.HKEY res := windows.RegOpenKeyExW(windows.HKEY_LOCAL_MACHINE, tz_key_wstr, 0, windows.KEY_READ, &key) diff --git a/core/time/timezone/tzdate.odin b/core/time/timezone/tzdate.odin index e62400889..8e900ec11 100644 --- a/core/time/timezone/tzdate.odin +++ b/core/time/timezone/tzdate.odin @@ -224,7 +224,7 @@ datetime_to_tz :: proc(dt: datetime.DateTime, tz: ^datetime.TZ_Region) -> (out: record := region_get_nearest(tz, tm) or_return secs := time.time_to_unix(tm) - adj_time := time.unix(secs + record.utc_offset, 0) + adj_time := time.unix(secs + record.utc_offset, i64(dt.nano)) adj_dt := time.time_to_datetime(adj_time) or_return adj_dt.tz = tz diff --git a/core/time/timezone/tzif.odin b/core/time/timezone/tzif.odin index 3fec7be53..804211ef4 100644 --- a/core/time/timezone/tzif.odin +++ b/core/time/timezone/tzif.odin @@ -577,12 +577,7 @@ parse_tzif :: proc(_buffer: []u8, region_name: string, allocator := context.allo footer_str := string(buffer[:end_idx]) // UTC is a special case, we don't need to alloc - if len(local_time_types) == 1 { - name := cstring(raw_data(timezone_string_table[local_time_types[0].idx:])) - if name != "UTC" { - return - } - + if len(local_time_types) == 1 && local_time_types[0].utoff == 0 { return nil, true } diff --git a/core/time/tsc_darwin.odin b/core/time/tsc_darwin.odin index 3726cff49..2efd35f20 100644 --- a/core/time/tsc_darwin.odin +++ b/core/time/tsc_darwin.odin @@ -1,10 +1,17 @@ #+private package time -import "core:sys/unix" +import "base:intrinsics" +@require import "core:sys/unix" _get_tsc_frequency :: proc "contextless" () -> (freq: u64, ok: bool) { - unix.sysctlbyname("machdep.tsc.frequency", &freq) or_return + when ODIN_ARCH == .amd64 { + unix.sysctlbyname("machdep.tsc.frequency", &freq) or_return + } else when ODIN_ARCH == .arm64 { + freq = u64(intrinsics.read_cycle_counter_frequency()) + } else { + return + } ok = true return } diff --git a/core/time/tsc_linux.odin b/core/time/tsc_linux.odin index a83634414..f59a0338b 100644 --- a/core/time/tsc_linux.odin +++ b/core/time/tsc_linux.odin @@ -2,32 +2,38 @@ #+build linux package time -import linux "core:sys/linux" +import "base:intrinsics" +@(require) import linux "core:sys/linux" _get_tsc_frequency :: proc "contextless" () -> (u64, bool) { - // Get the file descriptor for the perf mapping - perf_attr := linux.Perf_Event_Attr{} - perf_attr.size = size_of(perf_attr) - perf_attr.type = .HARDWARE - perf_attr.config.hw = .INSTRUCTIONS - perf_attr.flags = {.Disabled, .Exclude_Kernel, .Exclude_HV} - fd, perf_errno := linux.perf_event_open(&perf_attr, linux.Pid(0), -1, linux.Fd(-1), {}) - if perf_errno != nil { - return 0, false + when ODIN_ARCH == .arm64 { + frequency := u64(intrinsics.read_cycle_counter_frequency()) + return frequency, true + } else { + // Get the file descriptor for the perf mapping + perf_attr := linux.Perf_Event_Attr{} + perf_attr.size = size_of(perf_attr) + perf_attr.type = .HARDWARE + perf_attr.config.hw = .INSTRUCTIONS + perf_attr.flags = {.Disabled, .Exclude_Kernel, .Exclude_HV} + fd, perf_errno := linux.perf_event_open(&perf_attr, linux.Pid(0), -1, linux.Fd(-1), {}) + if perf_errno != nil { + return 0, false + } + defer linux.close(fd) + // Map it into the memory + page_size : uint = 4096 + addr, mmap_errno := linux.mmap(0, page_size, {.READ}, {.SHARED}, fd) + if mmap_errno != nil { + return 0, false + } + defer linux.munmap(addr, page_size) + // Get the frequency from the mapped page + event_page := cast(^linux.Perf_Event_Mmap_Page) addr + if .User_Time not_in event_page.cap.flags { + return 0, false + } + frequency := u64((u128(1_000_000_000) << u128(event_page.time_shift)) / u128(event_page.time_mult)) + return frequency, true } - defer linux.close(fd) - // Map it into the memory - page_size : uint = 4096 - addr, mmap_errno := linux.mmap(0, page_size, {.READ}, {.SHARED}, fd) - if mmap_errno != nil { - return 0, false - } - defer linux.munmap(addr, page_size) - // Get the frequency from the mapped page - event_page := cast(^linux.Perf_Event_Mmap_Page) addr - if .User_Time not_in event_page.cap.flags { - return 0, false - } - frequency := u64((u128(1_000_000_000) << u128(event_page.time_shift)) / u128(event_page.time_mult)) - return frequency, true } diff --git a/core/unicode/utf16/utf16.odin b/core/unicode/utf16/utf16.odin index e2bcf7f68..d3f98584b 100644 --- a/core/unicode/utf16/utf16.odin +++ b/core/unicode/utf16/utf16.odin @@ -106,7 +106,57 @@ decode :: proc(d: []rune, s: []u16) -> (n: int) { return } -rune_count :: proc(s: []u16) -> (n: int) { +decode_rune_in_string :: proc(s: string16) -> (r: rune, width: int) { + r = rune(REPLACEMENT_CHAR) + n := len(s) + if n < 1 { + return + } + width = 1 + + + switch c := s[0]; { + case c < _surr1, _surr3 <= c: + r = rune(c) + case _surr1 <= c && c < _surr2 && 1 < len(s) && + _surr2 <= s[1] && s[1] < _surr3: + r = decode_surrogate_pair(rune(c), rune(s[1])) + width += 1 + } + return +} + +string_to_runes :: proc "odin" (s: string16, allocator := context.allocator) -> (runes: []rune) { + n := rune_count(s) + + runes = make([]rune, n, allocator) + i := 0 + for r in s { + runes[i] = r + i += 1 + } + return +} + + +rune_count :: proc{ + rune_count_in_string, + rune_count_in_slice, +} +rune_count_in_string :: proc(s: string16) -> (n: int) { + for i := 0; i < len(s); i += 1 { + c := s[i] + if _surr1 <= c && c < _surr2 && i+1 < len(s) && + _surr2 <= s[i+1] && s[i+1] < _surr3 { + i += 1 + } + n += 1 + } + return +} + + +rune_count_in_slice :: proc(s: []u16) -> (n: int) { for i := 0; i < len(s); i += 1 { c := s[i] if _surr1 <= c && c < _surr2 && i+1 < len(s) && |