From 8169cb48534a8ea4332a434cee6ed8a6ad9203f7 Mon Sep 17 00:00:00 2001 From: vassvik Date: Fri, 9 Apr 2021 09:21:20 +0200 Subject: [PATCH 01/36] Fix missing newlines in core:math/linalg/specific.odin --- core/math/linalg/specific.odin | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/math/linalg/specific.odin b/core/math/linalg/specific.odin index 3aa3b67f7..e4a588c7d 100644 --- a/core/math/linalg/specific.odin +++ b/core/math/linalg/specific.odin @@ -711,7 +711,8 @@ quaternion_nlerp_f16 :: proc(a, b: Quaternionf16, t: f16) -> (c: Quaternionf16) c.z = a.z + (b.z-a.z)*t; c.w = a.w + (b.w-a.w)*t; return normalize(c); -}quaternion_nlerp_f32 :: proc(a, b: Quaternionf32, t: f32) -> (c: Quaternionf32) { +} +quaternion_nlerp_f32 :: proc(a, b: Quaternionf32, t: f32) -> (c: Quaternionf32) { c.x = a.x + (b.x-a.x)*t; c.y = a.y + (b.y-a.y)*t; c.z = a.z + (b.z-a.z)*t; @@ -758,7 +759,8 @@ quaternion_slerp_f16 :: proc(x, y: Quaternionf16, t: f16) -> (q: Quaternionf16) q.z = factor_a * a.z + factor_b * b.z; q.w = factor_a * a.w + factor_b * b.w; return; -}quaternion_slerp_f32 :: proc(x, y: Quaternionf32, t: f32) -> (q: Quaternionf32) { +} +quaternion_slerp_f32 :: proc(x, y: Quaternionf32, t: f32) -> (q: Quaternionf32) { a, b := x, y; cos_angle := dot(a, b); if cos_angle < 0 { From 011c8d5cda343ea93cbe6d2dc77bc1aeb6f5c1fb Mon Sep 17 00:00:00 2001 From: Gitea Date: Fri, 9 Apr 2021 13:03:01 +0300 Subject: [PATCH 02/36] fix Syntax Warning --- src/tokenizer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index 509bcb9cd..081ef6443 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -478,7 +478,7 @@ void syntax_warning_va(Token token, char const *fmt, va_list va) { // NOTE(bill): Duplicate error, skip it if (global_error_collector.prev != token.pos) { global_error_collector.prev = token.pos; - error_out("%S Syntax Warning: %s\n", + error_out("%s Syntax Warning: %s\n", token_pos_to_string(token.pos), gb_bprintf_va(fmt, va)); } else if (token.pos.line == 0) { From 2d99a348b8f4ae201b2b9d8271fb0d63a2dbc125 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 9 Apr 2021 11:27:44 +0100 Subject: [PATCH 03/36] Fix proc literal bug not finding the associated `DeclInfo` --- src/ir.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ir.cpp b/src/ir.cpp index 9fb06582d..0ad48ca27 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -132,6 +132,7 @@ struct irProcedure { irModule * module; String name; Type * type; + Ast * proc_lit; // only for actual anonymous procedure literals Ast * type_expr; Ast * body; u64 tags; @@ -6894,7 +6895,7 @@ irTargetList *ir_push_target_list(irProcedure *proc, Ast *label, irBlock *break_ } } - GB_PANIC("ir_set_label_blocks: Unreachable"); + GB_PANIC("ir_set_label_blocks: Unreachable; Unable to find label: %s", expr_to_string(label->Label.name)); } return tl; @@ -6927,6 +6928,7 @@ irValue *ir_gen_anonymous_proc_lit(irModule *m, String prefix_name, Ast *expr, i set_procedure_abi_types(type); irValue *value = ir_value_procedure(m, nullptr, type, pl->type, pl->body, name); + value->Proc.proc_lit = expr; value->Proc.tags = pl->tags; value->Proc.inlining = pl->inlining; value->Proc.parent = proc; @@ -11390,6 +11392,10 @@ void ir_begin_procedure_body(irProcedure *proc) { array_init(&proc->context_stack, heap_allocator()); DeclInfo *decl = decl_info_of_entity(proc->entity); + if (decl == nullptr && proc->proc_lit != nullptr) { + GB_ASSERT(proc->proc_lit->kind == Ast_ProcLit); + decl = proc->proc_lit->ProcLit.decl; + } if (decl != nullptr) { for_array(i, decl->labels) { BlockLabel bl = decl->labels[i]; From 2db1fe74299766c9a29a33c39299d07e12556bb2 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 11 Apr 2021 15:18:28 +0100 Subject: [PATCH 04/36] New redesign of core:sync (stored under core:sync/sync2 for the time being) --- core/sync/sync2/atomic.odin | 170 +++++ core/sync/sync2/channel.odin | 887 +++++++++++++++++++++++ core/sync/sync2/channel_unix.odin | 17 + core/sync/sync2/channel_windows.odin | 35 + core/sync/sync2/extended.odin | 215 ++++++ core/sync/sync2/primitives.odin | 185 +++++ core/sync/sync2/primitives_atomic.odin | 244 +++++++ core/sync/sync2/primitives_pthreads.odin | 155 ++++ core/sync/sync2/primitives_windows.odin | 73 ++ 9 files changed, 1981 insertions(+) create mode 100644 core/sync/sync2/atomic.odin create mode 100644 core/sync/sync2/channel.odin create mode 100644 core/sync/sync2/channel_unix.odin create mode 100644 core/sync/sync2/channel_windows.odin create mode 100644 core/sync/sync2/extended.odin create mode 100644 core/sync/sync2/primitives.odin create mode 100644 core/sync/sync2/primitives_atomic.odin create mode 100644 core/sync/sync2/primitives_pthreads.odin create mode 100644 core/sync/sync2/primitives_windows.odin diff --git a/core/sync/sync2/atomic.odin b/core/sync/sync2/atomic.odin new file mode 100644 index 000000000..8240c0fcd --- /dev/null +++ b/core/sync/sync2/atomic.odin @@ -0,0 +1,170 @@ +package sync2 + +import "intrinsics" + +// TODO(bill): Is this even a good design? The intrinsics seem to be more than good enough and just as clean + +Ordering :: enum { + Relaxed, // Monotonic + Release, + Acquire, + Acquire_Release, + Sequentially_Consistent, +} + +strongest_failure_ordering_table := [Ordering]Ordering{ + .Relaxed = .Relaxed, + .Release = .Relaxed, + .Acquire = .Acquire, + .Acquire_Release = .Acquire, + .Sequentially_Consistent = .Sequentially_Consistent, +}; + +strongest_failure_ordering :: #force_inline proc(order: Ordering) -> Ordering { + return strongest_failure_ordering_table[order]; +} + +fence :: #force_inline proc($order: Ordering) { + when order == .Relaxed { #panic("there is no such thing as a relaxed fence"); } + else when order == .Release { intrinsics.atomic_fence_rel(); } + else when order == .Acquire { intrinsics.atomic_fence_acq(); } + else when order == .Acquire_Release { intrinsics.atomic_fence_acqrel(); } + else when order == .Sequentially_Consistent { intrinsics.atomic_fence(); } + else { #panic("unknown order"); } +} + + +atomic_store :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) { + when order == .Relaxed { intrinsics.atomic_store_relaxed(dst, val); } + else when order == .Release { intrinsics.atomic_store_rel(dst, val); } + else when order == .Sequentially_Consistent { intrinsics.atomic_store(dst, val); } + else when order == .Acquire { #panic("there is not such thing as an acquire store"); } + else when order == .Acquire_Release { #panic("there is not such thing as an acquire/release store"); } + else { #panic("unknown order"); } +} + +atomic_load :: #force_inline proc(dst: ^$T, $order: Ordering) -> T { + when order == .Relaxed { return intrinsics.atomic_load_relaxed(dst); } + else when order == .Acquire { return intrinsics.atomic_load_acq(dst); } + else when order == .Sequentially_Consistent { return intrinsics.atomic_load(dst); } + else when order == .Release { #panic("there is no such thing as a release load"); } + else when order == .Acquire_Release { #panic("there is no such thing as an acquire/release load"); } + else { #panic("unknown order"); } +} + +atomic_exchange :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) -> T { + when order == .Relaxed { return intrinsics.atomic_xchg_relaxed(dst, val); } + else when order == .Release { return intrinsics.atomic_xchg_rel(dst, val); } + else when order == .Acquire { return intrinsics.atomic_xchg_acq(dst, val); } + else when order == .Acquire_Release { return intrinsics.atomic_xchg_acqrel(dst, val); } + else when order == .Sequentially_Consistent { return intrinsics.atomic_xchg(dst, val); } + else { #panic("unknown order"); } +} + +atomic_compare_exchange :: #force_inline proc(dst: ^$T, old, new: T, $success, $failure: Ordering) -> (val: T, ok: bool) { + when failure == .Relaxed { + when success == .Relaxed { return intrinsics.atomic_cxchg_relaxed(dst, old, new); } + else when success == .Acquire { return intrinsics.atomic_cxchg_acq_failrelaxed(dst, old, new); } + else when success == .Acquire_Release { return intrinsics.atomic_cxchg_acqrel_failrelaxed(dst, old, new); } + else when success == .Sequentially_Consistent { return intrinsics.atomic_cxchg_failrelaxed(dst, old, new); } + else when success == .Release { return intrinsics.atomic_cxchg_rel(dst, old, new); } + else { #panic("an unknown ordering combination"); } + } else when failure == .Acquire { + when success == .Release { return intrinsics.atomic_cxchg_acqrel(dst, old, new); } + else when success == .Acquire { return intrinsics.atomic_cxchg_acq(dst, old, new); } + else { #panic("an unknown ordering combination"); } + } else when failure == .Sequentially_Consistent { + when success == .Sequentially_Consistent { return intrinsics.atomic_cxchg(dst, old, new); } + else { #panic("an unknown ordering combination"); } + } else when failure == .Acquire_Release { + #panic("there is not such thing as an acquire/release failure ordering"); + } else when failure == .Release { + when success == .Acquire { return instrinsics.atomic_cxchg_failacq(dst, old, new); } + else { #panic("an unknown ordering combination"); } + } else { + return T{}, false; + } + +} + +atomic_compare_exchange_weak :: #force_inline proc(dst: ^$T, old, new: T, $success, $failure: Ordering) -> (val: T, ok: bool) { + when failure == .Relaxed { + when success == .Relaxed { return intrinsics.atomic_cxchgweak_relaxed(dst, old, new); } + else when success == .Acquire { return intrinsics.atomic_cxchgweak_acq_failrelaxed(dst, old, new); } + else when success == .Acquire_Release { return intrinsics.atomic_cxchgweak_acqrel_failrelaxed(dst, old, new); } + else when success == .Sequentially_Consistent { return intrinsics.atomic_cxchgweak_failrelaxed(dst, old, new); } + else when success == .Release { return intrinsics.atomic_cxchgweak_rel(dst, old, new); } + else { #panic("an unknown ordering combination"); } + } else when failure == .Acquire { + when success == .Release { return intrinsics.atomic_cxchgweak_acqrel(dst, old, new); } + else when success == .Acquire { return intrinsics.atomic_cxchgweak_acq(dst, old, new); } + else { #panic("an unknown ordering combination"); } + } else when failure == .Sequentially_Consistent { + when success == .Sequentially_Consistent { return intrinsics.atomic_cxchgweak(dst, old, new); } + else { #panic("an unknown ordering combination"); } + } else when failure == .Acquire_Release { + #panic("there is not such thing as an acquire/release failure ordering"); + } else when failure == .Release { + when success == .Acquire { return intrinsics.atomic_cxchgweak_failacq(dst, old, new); } + else { #panic("an unknown ordering combination"); } + } else { + return T{}, false; + } + +} + + +atomic_add :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) -> T { + when order == .Relaxed { return intrinsics.atomic_add_relaxed(dst, val); } + else when order == .Release { return intrinsics.atomic_add_rel(dst, val); } + else when order == .Acquire { return intrinsics.atomic_add_acq(dst, val); } + else when order == .Acquire_Release { return intrinsics.atomic_add_acqrel(dst, val); } + else when order == .Sequentially_Consistent { return intrinsics.atomic_add(dst, val); } + else { #panic("unknown order"); } +} + +atomic_sub :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) -> T { + when order == .Relaxed { return intrinsics.atomic_sub_relaxed(dst, val); } + else when order == .Release { return intrinsics.atomic_sub_rel(dst, val); } + else when order == .Acquire { return intrinsics.atomic_sub_acq(dst, val); } + else when order == .Acquire_Release { return intrinsics.atomic_sub_acqrel(dst, val); } + else when order == .Sequentially_Consistent { return intrinsics.atomic_sub(dst, val); } + else { #panic("unknown order"); } +} + +atomic_and :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) -> T { + when order == .Relaxed { return intrinsics.atomic_and_relaxed(dst, val); } + else when order == .Release { return intrinsics.atomic_and_rel(dst, val); } + else when order == .Acquire { return intrinsics.atomic_and_acq(dst, val); } + else when order == .Acquire_Release { return intrinsics.atomic_and_acqrel(dst, val); } + else when order == .Sequentially_Consistent { return intrinsics.atomic_and(dst, val); } + else { #panic("unknown order"); } +} + +atomic_nand :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) -> T { + when order == .Relaxed { return intrinsics.atomic_nand_relaxed(dst, val); } + else when order == .Release { return intrinsics.atomic_nand_rel(dst, val); } + else when order == .Acquire { return intrinsics.atomic_nand_acq(dst, val); } + else when order == .Acquire_Release { return intrinsics.atomic_nand_acqrel(dst, val); } + else when order == .Sequentially_Consistent { return intrinsics.atomic_nand(dst, val); } + else { #panic("unknown order"); } +} + +atomic_or :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) -> T { + when order == .Relaxed { return intrinsics.atomic_or_relaxed(dst, val); } + else when order == .Release { return intrinsics.atomic_or_rel(dst, val); } + else when order == .Acquire { return intrinsics.atomic_or_acq(dst, val); } + else when order == .Acquire_Release { return intrinsics.atomic_or_acqrel(dst, val); } + else when order == .Sequentially_Consistent { return intrinsics.atomic_or(dst, val); } + else { #panic("unknown order"); } +} + +atomic_xor :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) -> T { + when order == .Relaxed { return intrinsics.atomic_xor_relaxed(dst, val); } + else when order == .Release { return intrinsics.atomic_xor_rel(dst, val); } + else when order == .Acquire { return intrinsics.atomic_xor_acq(dst, val); } + else when order == .Acquire_Release { return intrinsics.atomic_xor_acqrel(dst, val); } + else when order == .Sequentially_Consistent { return intrinsics.atomic_xor(dst, val); } + else { #panic("unknown order"); } +} + diff --git a/core/sync/sync2/channel.odin b/core/sync/sync2/channel.odin new file mode 100644 index 000000000..782b1d86a --- /dev/null +++ b/core/sync/sync2/channel.odin @@ -0,0 +1,887 @@ +package sync2 + +// TODO(bill): The Channel implementation needs a complete rewrite for this new package sync design +// Especially how the `select` things work + +import "core:mem" +import "core:time" +import "intrinsics" +import "core:math/rand" + +_, _ :: time, rand; + +Channel_Direction :: enum i8 { + Both = 0, + Send = +1, + Recv = -1, +} + +Channel :: struct(T: typeid, Direction := Channel_Direction.Both) { + using _internal: ^Raw_Channel, +} + +channel_init :: proc(ch: ^$C/Channel($T, $D), cap := 0, allocator := context.allocator) { + context.allocator = allocator; + ch._internal = raw_channel_create(size_of(T), align_of(T), cap); + return; +} + +channel_make :: proc($T: typeid, cap := 0, allocator := context.allocator) -> (ch: Channel(T, .Both)) { + context.allocator = allocator; + ch._internal = raw_channel_create(size_of(T), align_of(T), cap); + return; +} + +channel_make_send :: proc($T: typeid, cap := 0, allocator := context.allocator) -> (ch: Channel(T, .Send)) { + context.allocator = allocator; + ch._internal = raw_channel_create(size_of(T), align_of(T), cap); + return; +} +channel_make_recv :: proc($T: typeid, cap := 0, allocator := context.allocator) -> (ch: Channel(T, .Recv)) { + context.allocator = allocator; + ch._internal = raw_channel_create(size_of(T), align_of(T), cap); + return; +} + +channel_destroy :: proc(ch: $C/Channel($T, $D)) { + raw_channel_destroy(ch._internal); +} + +channel_as_send :: proc(ch: $C/Channel($T, .Both)) -> (res: Channel(T, .Send)) { + res._internal = ch._internal; + return; +} + +channel_as_recv :: proc(ch: $C/Channel($T, .Both)) -> (res: Channel(T, .Recv)) { + res._internal = ch._internal; + return; +} + + +channel_len :: proc(ch: $C/Channel($T, $D)) -> int { + return ch._internal.len if ch._internal != nil else 0; +} +channel_cap :: proc(ch: $C/Channel($T, $D)) -> int { + return ch._internal.cap if ch._internal != nil else 0; +} + + +channel_send :: proc(ch: $C/Channel($T, $D), msg: T, loc := #caller_location) where D >= .Both { + msg := msg; + _ = raw_channel_send_impl(ch._internal, &msg, /*block*/true, loc); +} +channel_try_send :: proc(ch: $C/Channel($T, $D), msg: T, loc := #caller_location) -> bool where D >= .Both { + msg := msg; + return raw_channel_send_impl(ch._internal, &msg, /*block*/false, loc); +} + +channel_recv :: proc(ch: $C/Channel($T, $D), loc := #caller_location) -> (msg: T) where D <= .Both { + c := ch._internal; + if c == nil { + panic(message="cannot recv message; channel is nil", loc=loc); + } + mutex_lock(&c.mutex); + raw_channel_recv_impl(c, &msg, loc); + mutex_unlock(&c.mutex); + return; +} +channel_try_recv :: proc(ch: $C/Channel($T, $D), loc := #caller_location) -> (msg: T, ok: bool) where D <= .Both { + c := ch._internal; + if c != nil && mutex_try_lock(&c.mutex) { + if c.len > 0 { + raw_channel_recv_impl(c, &msg, loc); + ok = true; + } + mutex_unlock(&c.mutex); + } + return; +} +channel_try_recv_ptr :: proc(ch: $C/Channel($T, $D), msg: ^T, loc := #caller_location) -> (ok: bool) where D <= .Both { + res: T; + res, ok = channel_try_recv(ch, loc); + if ok && msg != nil { + msg^ = res; + } + return; +} + + +channel_is_nil :: proc(ch: $C/Channel($T, $D)) -> bool { + return ch._internal == nil; +} +channel_is_open :: proc(ch: $C/Channel($T, $D)) -> bool { + c := ch._internal; + return c != nil && !c.closed; +} + + +channel_eq :: proc(a, b: $C/Channel($T, $D)) -> bool { + return a._internal == b._internal; +} +channel_ne :: proc(a, b: $C/Channel($T, $D)) -> bool { + return a._internal != b._internal; +} + + +channel_can_send :: proc(ch: $C/Channel($T, $D)) -> (ok: bool) where D >= .Both { + return raw_channel_can_send(ch._internal); +} +channel_can_recv :: proc(ch: $C/Channel($T, $D)) -> (ok: bool) where D <= .Both { + return raw_channel_can_recv(ch._internal); +} + + +channel_peek :: proc(ch: $C/Channel($T, $D)) -> int { + c := ch._internal; + if c == nil { + return -1; + } + if intrinsics.atomic_load(&c.closed) { + return -1; + } + return intrinsics.atomic_load(&c.len); +} + + +channel_close :: proc(ch: $C/Channel($T, $D), loc := #caller_location) { + raw_channel_close(ch._internal, loc); +} + + +channel_iterator :: proc(ch: $C/Channel($T, $D)) -> (msg: T, ok: bool) where D <= .Both { + c := ch._internal; + if c == nil { + return; + } + + if !c.closed || c.len > 0 { + msg, ok = channel_recv(ch), true; + } + return; +} +channel_drain :: proc(ch: $C/Channel($T, $D)) where D >= .Both { + raw_channel_drain(ch._internal); +} + + +channel_move :: proc(dst: $C1/Channel($T, $D1) src: $C2/Channel(T, $D2)) where D1 <= .Both, D2 >= .Both { + for msg in channel_iterator(src) { + channel_send(dst, msg); + } +} + + +Raw_Channel_Wait_Queue :: struct { + next: ^Raw_Channel_Wait_Queue, + state: ^uintptr, +} + + +Raw_Channel :: struct { + closed: bool, + ready: bool, // ready to recv + data_offset: u16, // data is stored at the end of this data structure + elem_size: u32, + len, cap: int, + read, write: int, + mutex: Mutex, + cond: Cond, + allocator: mem.Allocator, + + sendq: ^Raw_Channel_Wait_Queue, + recvq: ^Raw_Channel_Wait_Queue, +} + +raw_channel_wait_queue_insert :: proc(head: ^^Raw_Channel_Wait_Queue, val: ^Raw_Channel_Wait_Queue) { + val.next = head^; + head^ = val; +} +raw_channel_wait_queue_remove :: proc(head: ^^Raw_Channel_Wait_Queue, val: ^Raw_Channel_Wait_Queue) { + p := head; + for p^ != nil && p^ != val { + p = &p^.next; + } + if p != nil { + p^ = p^.next; + } +} + + +raw_channel_create :: proc(elem_size, elem_align: int, cap := 0) -> ^Raw_Channel { + assert(int(u32(elem_size)) == elem_size); + + s := size_of(Raw_Channel); + s = mem.align_forward_int(s, elem_align); + data_offset := uintptr(s); + s += elem_size * max(cap, 1); + + a := max(elem_align, align_of(Raw_Channel)); + + c := (^Raw_Channel)(mem.alloc(s, a)); + if c == nil { + return nil; + } + + c.data_offset = u16(data_offset); + c.elem_size = u32(elem_size); + c.len, c.cap = 0, max(cap, 0); + c.read, c.write = 0, 0; + c.allocator = context.allocator; + c.closed = false; + + return c; +} + + +raw_channel_destroy :: proc(c: ^Raw_Channel) { + if c == nil { + return; + } + context.allocator = c.allocator; + intrinsics.atomic_store(&c.closed, true); + free(c); +} + +raw_channel_close :: proc(c: ^Raw_Channel, loc := #caller_location) { + if c == nil { + panic(message="cannot close nil channel", loc=loc); + } + mutex_lock(&c.mutex); + defer mutex_unlock(&c.mutex); + intrinsics.atomic_store(&c.closed, true); + + // Release readers and writers + raw_channel_wait_queue_broadcast(c.recvq); + raw_channel_wait_queue_broadcast(c.sendq); + cond_broadcast(&c.cond); +} + + + +raw_channel_send_impl :: proc(c: ^Raw_Channel, msg: rawptr, block: bool, loc := #caller_location) -> bool { + send :: proc(c: ^Raw_Channel, src: rawptr) { + data := uintptr(c) + uintptr(c.data_offset); + dst := data + uintptr(c.write * int(c.elem_size)); + mem.copy(rawptr(dst), src, int(c.elem_size)); + c.len += 1; + c.write = (c.write + 1) % max(c.cap, 1); + } + + switch { + case c == nil: + panic(message="cannot send message; channel is nil", loc=loc); + case c.closed: + panic(message="cannot send message; channel is closed", loc=loc); + } + + mutex_lock(&c.mutex); + defer mutex_unlock(&c.mutex); + + if c.cap > 0 { + if !block && c.len >= c.cap { + return false; + } + + for c.len >= c.cap { + cond_wait(&c.cond, &c.mutex); + } + } else if c.len > 0 { // TODO(bill): determine correct behaviour + if !block { + return false; + } + cond_wait(&c.cond, &c.mutex); + } else if c.len == 0 && !block { + return false; + } + + send(c, msg); + cond_signal(&c.cond); + raw_channel_wait_queue_signal(c.recvq); + + return true; +} + +raw_channel_recv_impl :: proc(c: ^Raw_Channel, res: rawptr, loc := #caller_location) { + recv :: proc(c: ^Raw_Channel, dst: rawptr, loc := #caller_location) { + if c.len < 1 { + panic(message="cannot recv message; channel is empty", loc=loc); + } + c.len -= 1; + + data := uintptr(c) + uintptr(c.data_offset); + src := data + uintptr(c.read * int(c.elem_size)); + mem.copy(dst, rawptr(src), int(c.elem_size)); + c.read = (c.read + 1) % max(c.cap, 1); + } + + if c == nil { + panic(message="cannot recv message; channel is nil", loc=loc); + } + intrinsics.atomic_store(&c.ready, true); + for c.len < 1 { + raw_channel_wait_queue_signal(c.sendq); + cond_wait(&c.cond, &c.mutex); + } + intrinsics.atomic_store(&c.ready, false); + recv(c, res, loc); + if c.cap > 0 { + if c.len == c.cap - 1 { + // NOTE(bill): Only signal on the last one + cond_signal(&c.cond); + } + } else { + cond_signal(&c.cond); + } +} + + +raw_channel_can_send :: proc(c: ^Raw_Channel) -> (ok: bool) { + if c == nil { + return false; + } + mutex_lock(&c.mutex); + switch { + case c.closed: + ok = false; + case c.cap > 0: + ok = c.ready && c.len < c.cap; + case: + ok = c.ready && c.len == 0; + } + mutex_unlock(&c.mutex); + return; +} +raw_channel_can_recv :: proc(c: ^Raw_Channel) -> (ok: bool) { + if c == nil { + return false; + } + mutex_lock(&c.mutex); + ok = c.len > 0; + mutex_unlock(&c.mutex); + return; +} + + +raw_channel_drain :: proc(c: ^Raw_Channel) { + if c == nil { + return; + } + mutex_lock(&c.mutex); + c.len = 0; + c.read = 0; + c.write = 0; + mutex_unlock(&c.mutex); +} + + + +MAX_SELECT_CHANNELS :: 64; +SELECT_MAX_TIMEOUT :: max(time.Duration); + +Select_Command :: enum { + Recv, + Send, +} + +Select_Channel :: struct { + channel: ^Raw_Channel, + command: Select_Command, +} + + + +select :: proc(channels: ..Select_Channel) -> (index: int) { + return select_timeout(SELECT_MAX_TIMEOUT, ..channels); +} +select_timeout :: proc(timeout: time.Duration, channels: ..Select_Channel) -> (index: int) { + switch len(channels) { + case 0: + panic("sync: select with no channels"); + } + + assert(len(channels) <= MAX_SELECT_CHANNELS); + + backing: [MAX_SELECT_CHANNELS]int; + queues: [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue; + candidates := backing[:]; + cap := len(channels); + candidates = candidates[:cap]; + + count := u32(0); + for c, i in channels { + if c.channel == nil { + continue; + } + switch c.command { + case .Recv: + if raw_channel_can_recv(c.channel) { + candidates[count] = i; + count += 1; + } + case .Send: + if raw_channel_can_send(c.channel) { + candidates[count] = i; + count += 1; + } + } + } + + if count == 0 { + wait_state: uintptr = 0; + for _, i in channels { + q := &queues[i]; + q.state = &wait_state; + } + + for c, i in channels { + if c.channel == nil { + continue; + } + q := &queues[i]; + switch c.command { + case .Recv: raw_channel_wait_queue_insert(&c.channel.recvq, q); + case .Send: raw_channel_wait_queue_insert(&c.channel.sendq, q); + } + } + raw_channel_wait_queue_wait_on(&wait_state, timeout); + for c, i in channels { + if c.channel == nil { + continue; + } + q := &queues[i]; + switch c.command { + case .Recv: raw_channel_wait_queue_remove(&c.channel.recvq, q); + case .Send: raw_channel_wait_queue_remove(&c.channel.sendq, q); + } + } + + for c, i in channels { + switch c.command { + case .Recv: + if raw_channel_can_recv(c.channel) { + candidates[count] = i; + count += 1; + } + case .Send: + if raw_channel_can_send(c.channel) { + candidates[count] = i; + count += 1; + } + } + } + if count == 0 && timeout == SELECT_MAX_TIMEOUT { + index = -1; + return; + } + + assert(count != 0); + } + + t := time.now(); + r := rand.create(transmute(u64)t); + i := rand.uint32(&r); + + index = candidates[i % count]; + return; +} + +select_recv :: proc(channels: ..^Raw_Channel) -> (index: int) { + switch len(channels) { + case 0: + panic("sync: select with no channels"); + } + + assert(len(channels) <= MAX_SELECT_CHANNELS); + + backing: [MAX_SELECT_CHANNELS]int; + queues: [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue; + candidates := backing[:]; + cap := len(channels); + candidates = candidates[:cap]; + + count := u32(0); + for c, i in channels { + if raw_channel_can_recv(c) { + candidates[count] = i; + count += 1; + } + } + + if count == 0 { + state: uintptr; + for c, i in channels { + q := &queues[i]; + q.state = &state; + raw_channel_wait_queue_insert(&c.recvq, q); + } + raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT); + for c, i in channels { + q := &queues[i]; + raw_channel_wait_queue_remove(&c.recvq, q); + } + + for c, i in channels { + if raw_channel_can_recv(c) { + candidates[count] = i; + count += 1; + } + } + assert(count != 0); + } + + t := time.now(); + r := rand.create(transmute(u64)t); + i := rand.uint32(&r); + + index = candidates[i % count]; + return; +} + +select_recv_msg :: proc(channels: ..$C/Channel($T, $D)) -> (msg: T, index: int) { + switch len(channels) { + case 0: + panic("sync: select with no channels"); + } + + assert(len(channels) <= MAX_SELECT_CHANNELS); + + queues: [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue; + candidates: [MAX_SELECT_CHANNELS]int; + + count := u32(0); + for c, i in channels { + if raw_channel_can_recv(c) { + candidates[count] = i; + count += 1; + } + } + + if count == 0 { + state: uintptr; + for c, i in channels { + q := &queues[i]; + q.state = &state; + raw_channel_wait_queue_insert(&c.recvq, q); + } + raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT); + for c, i in channels { + q := &queues[i]; + raw_channel_wait_queue_remove(&c.recvq, q); + } + + for c, i in channels { + if raw_channel_can_recv(c) { + candidates[count] = i; + count += 1; + } + } + assert(count != 0); + } + + t := time.now(); + r := rand.create(transmute(u64)t); + i := rand.uint32(&r); + + index = candidates[i % count]; + msg = channel_recv(channels[index]); + + return; +} + +select_send_msg :: proc(msg: $T, channels: ..$C/Channel(T, $D)) -> (index: int) { + switch len(channels) { + case 0: + panic("sync: select with no channels"); + } + + assert(len(channels) <= MAX_SELECT_CHANNELS); + + backing: [MAX_SELECT_CHANNELS]int; + queues: [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue; + candidates := backing[:]; + cap := len(channels); + candidates = candidates[:cap]; + + count := u32(0); + for c, i in channels { + if raw_channel_can_recv(c) { + candidates[count] = i; + count += 1; + } + } + + if count == 0 { + state: uintptr; + for c, i in channels { + q := &queues[i]; + q.state = &state; + raw_channel_wait_queue_insert(&c.recvq, q); + } + raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT); + for c, i in channels { + q := &queues[i]; + raw_channel_wait_queue_remove(&c.recvq, q); + } + + for c, i in channels { + if raw_channel_can_recv(c) { + candidates[count] = i; + count += 1; + } + } + assert(count != 0); + } + + t := time.now(); + r := rand.create(transmute(u64)t); + i := rand.uint32(&r); + + index = candidates[i % count]; + + if msg != nil { + channel_send(channels[index], msg); + } + + return; +} + +select_send :: proc(channels: ..^Raw_Channel) -> (index: int) { + switch len(channels) { + case 0: + panic("sync: select with no channels"); + } + + assert(len(channels) <= MAX_SELECT_CHANNELS); + candidates: [MAX_SELECT_CHANNELS]int; + queues: [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue; + + count := u32(0); + for c, i in channels { + if raw_channel_can_send(c) { + candidates[count] = i; + count += 1; + } + } + + if count == 0 { + state: uintptr; + for c, i in channels { + q := &queues[i]; + q.state = &state; + raw_channel_wait_queue_insert(&c.sendq, q); + } + raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT); + for c, i in channels { + q := &queues[i]; + raw_channel_wait_queue_remove(&c.sendq, q); + } + + for c, i in channels { + if raw_channel_can_send(c) { + candidates[count] = i; + count += 1; + } + } + assert(count != 0); + } + + t := time.now(); + r := rand.create(transmute(u64)t); + i := rand.uint32(&r); + + index = candidates[i % count]; + return; +} + +select_try :: proc(channels: ..Select_Channel) -> (index: int) { + switch len(channels) { + case 0: + panic("sync: select with no channels"); + } + + assert(len(channels) <= MAX_SELECT_CHANNELS); + + backing: [MAX_SELECT_CHANNELS]int; + candidates := backing[:]; + cap := len(channels); + candidates = candidates[:cap]; + + count := u32(0); + for c, i in channels { + switch c.command { + case .Recv: + if raw_channel_can_recv(c.channel) { + candidates[count] = i; + count += 1; + } + case .Send: + if raw_channel_can_send(c.channel) { + candidates[count] = i; + count += 1; + } + } + } + + if count == 0 { + index = -1; + return; + } + + t := time.now(); + r := rand.create(transmute(u64)t); + i := rand.uint32(&r); + + index = candidates[i % count]; + return; +} + + +select_try_recv :: proc(channels: ..^Raw_Channel) -> (index: int) { + switch len(channels) { + case 0: + index = -1; + return; + case 1: + index = -1; + if raw_channel_can_recv(channels[0]) { + index = 0; + } + return; + } + + assert(len(channels) <= MAX_SELECT_CHANNELS); + candidates: [MAX_SELECT_CHANNELS]int; + + count := u32(0); + for c, i in channels { + if raw_channel_can_recv(c) { + candidates[count] = i; + count += 1; + } + } + + if count == 0 { + index = -1; + return; + } + + t := time.now(); + r := rand.create(transmute(u64)t); + i := rand.uint32(&r); + + index = candidates[i % count]; + return; +} + + +select_try_send :: proc(channels: ..^Raw_Channel) -> (index: int) #no_bounds_check { + switch len(channels) { + case 0: + return -1; + case 1: + if raw_channel_can_send(channels[0]) { + return 0; + } + return -1; + } + + assert(len(channels) <= MAX_SELECT_CHANNELS); + candidates: [MAX_SELECT_CHANNELS]int; + + count := u32(0); + for c, i in channels { + if raw_channel_can_send(c) { + candidates[count] = i; + count += 1; + } + } + + if count == 0 { + index = -1; + return; + } + + t := time.now(); + r := rand.create(transmute(u64)t); + i := rand.uint32(&r); + + index = candidates[i % count]; + return; +} + +select_try_recv_msg :: proc(channels: ..$C/Channel($T, $D)) -> (msg: T, index: int) { + switch len(channels) { + case 0: + index = -1; + return; + case 1: + ok: bool; + if msg, ok = channel_try_recv(channels[0]); ok { + index = 0; + } + return; + } + + assert(len(channels) <= MAX_SELECT_CHANNELS); + candidates: [MAX_SELECT_CHANNELS]int; + + count := u32(0); + for c, i in channels { + if channel_can_recv(c) { + candidates[count] = i; + count += 1; + } + } + + if count == 0 { + index = -1; + return; + } + + t := time.now(); + r := rand.create(transmute(u64)t); + i := rand.uint32(&r); + + index = candidates[i % count]; + msg = channel_recv(channels[index]); + return; +} + +select_try_send_msg :: proc(msg: $T, channels: ..$C/Channel(T, $D)) -> (index: int) { + index = -1; + switch len(channels) { + case 0: + return; + case 1: + if channel_try_send(channels[0], msg) { + index = 0; + } + return; + } + + + assert(len(channels) <= MAX_SELECT_CHANNELS); + candidates: [MAX_SELECT_CHANNELS]int; + + count := u32(0); + for c, i in channels { + if raw_channel_can_send(c) { + candidates[count] = i; + count += 1; + } + } + + if count == 0 { + index = -1; + return; + } + + t := time.now(); + r := rand.create(transmute(u64)t); + i := rand.uint32(&r); + + index = candidates[i % count]; + channel_send(channels[index], msg); + return; +} + diff --git a/core/sync/sync2/channel_unix.odin b/core/sync/sync2/channel_unix.odin new file mode 100644 index 000000000..7429b67db --- /dev/null +++ b/core/sync/sync2/channel_unix.odin @@ -0,0 +1,17 @@ +//+build linux, darwin, freebsd +//+private +package sync2 + +import "core:time" + +raw_channel_wait_queue_wait_on :: proc(state: ^uintptr, timeout: time.Duration) { + // stub +} + +raw_channel_wait_queue_signal :: proc(q: ^Raw_Channel_Wait_Queue) { + // stub +} + +raw_channel_wait_queue_broadcast :: proc(q: ^Raw_Channel_Wait_Queue) { + // stub +} diff --git a/core/sync/sync2/channel_windows.odin b/core/sync/sync2/channel_windows.odin new file mode 100644 index 000000000..a38a9cc2c --- /dev/null +++ b/core/sync/sync2/channel_windows.odin @@ -0,0 +1,35 @@ +//+build windows +//+private +package sync2 + +import "intrinsics" +import win32 "core:sys/windows" +import "core:time" + +raw_channel_wait_queue_wait_on :: proc(state: ^uintptr, timeout: time.Duration) { + ms: win32.DWORD = win32.INFINITE; + if max(time.Duration) != SELECT_MAX_TIMEOUT { + ms = win32.DWORD((max(time.duration_nanoseconds(timeout), 0) + 999999)/1000000); + } + + v := intrinsics.atomic_load(state); + for v == 0 { + win32.WaitOnAddress(state, &v, size_of(state^), ms); + v = intrinsics.atomic_load(state); + } + intrinsics.atomic_store(state, 0); +} + +raw_channel_wait_queue_signal :: proc(q: ^Raw_Channel_Wait_Queue) { + for x := q; x != nil; x = x.next { + intrinsics.atomic_add(x.state, 1); + win32.WakeByAddressSingle(x.state); + } +} + +raw_channel_wait_queue_broadcast :: proc(q: ^Raw_Channel_Wait_Queue) { + for x := q; x != nil; x = x.next { + intrinsics.atomic_add(x.state, 1); + win32.WakeByAddressAll(x.state); + } +} diff --git a/core/sync/sync2/extended.odin b/core/sync/sync2/extended.odin new file mode 100644 index 000000000..3c439b225 --- /dev/null +++ b/core/sync/sync2/extended.odin @@ -0,0 +1,215 @@ +package sync2 + +import "core:runtime" +import "intrinsics" + +// A Wait_Group waits for a collection of threads to finish +// +// A Wait_Group must not be copied after first use +Wait_Group :: struct { + counter: int, + mutex: Mutex, + cond: Cond, +} + +wait_group_add :: proc(wg: ^Wait_Group, delta: int) { + if delta == 0 { + return; + } + + mutex_lock(&wg.mutex); + defer mutex_unlock(&wg.mutex); + + intrinsics.atomic_add(&wg.counter, delta); + if wg.counter < 0 { + panic("sync.Wait_Group negative counter"); + } + if wg.counter == 0 { + cond_broadcast(&wg.cond); + if wg.counter != 0 { + panic("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait"); + } + } +} + +wait_group_done :: proc(wg: ^Wait_Group) { + wait_group_add(wg, -1); +} + +wait_group_wait :: proc(wg: ^Wait_Group) { + mutex_lock(&wg.mutex); + defer mutex_unlock(&wg.mutex); + + if wg.counter != 0 { + cond_wait(&wg.cond, &wg.mutex); + if wg.counter != 0 { + panic("sync.Wait_Group misuse: sync.wait_group_add called concurrently with sync.wait_group_wait"); + } + } +} + + + +// A barrier enabling multiple threads to synchronize the beginning of some computation +/* + * Example: + * + * package example + * + * import "core:fmt" + * import "core:sync" + * import "core:thread" + * + * barrier := &sync.Barrier{}; + * + * main :: proc() { + * fmt.println("Start"); + * + * THREAD_COUNT :: 4; + * threads: [THREAD_COUNT]^thread.Thread; + * + * sync.barrier_init(barrier, THREAD_COUNT); + * defer sync.barrier_destroy(barrier); + * + * + * for _, i in threads { + * threads[i] = thread.create_and_start(proc(t: ^thread.Thread) { + * // Same messages will be printed together but without any interleaving + * fmt.println("Getting ready!"); + * sync.barrier_wait(barrier); + * fmt.println("Off their marks they go!"); + * }); + * } + * + * for t in threads { + * thread.destroy(t); // join and free thread + * } + * fmt.println("Finished"); + * } + * + */ +Barrier :: struct { + mutex: Mutex, + cond: Cond, + index: int, + generation_id: int, + thread_count: int, +} + +barrier_init :: proc(b: ^Barrier, thread_count: int) { + b.index = 0; + b.generation_id = 0; + b.thread_count = thread_count; +} + +// Block the current thread until all threads have rendezvoused +// Barrier can be reused after all threads rendezvoused once, and can be used continuously +barrier_wait :: proc(b: ^Barrier) -> (is_leader: bool) { + mutex_lock(&b.mutex); + defer mutex_unlock(&b.mutex); + local_gen := b.generation_id; + b.index += 1; + if b.index < b.thread_count { + for local_gen == b.generation_id && b.index < b.thread_count { + cond_wait(&b.cond, &b.mutex); + } + return false; + } + + b.index = 0; + b.generation_id += 1; + cond_broadcast(&b.cond); + return true; +} + + + +Ticket_Mutex :: struct { + ticket: uint, + serving: uint, +} + +ticket_mutex_lock :: #force_inline proc(m: ^Ticket_Mutex) { + ticket := intrinsics.atomic_add_relaxed(&m.ticket, 1); + for ticket != intrinsics.atomic_load_acq(&m.serving) { + intrinsics.cpu_relax(); + } +} + +ticket_mutex_unlock :: #force_inline proc(m: ^Ticket_Mutex) { + intrinsics.atomic_add_relaxed(&m.serving, 1); +} + + + +Benaphore :: struct { + counter: int, + sema: Sema, +} + +benaphore_lock :: proc(b: ^Benaphore) { + if intrinsics.atomic_add_acq(&b.counter, 1) > 1 { + sema_wait(&b.sema); + } +} + +benaphore_try_lock :: proc(b: ^Benaphore) -> bool { + v, _ := intrinsics.atomic_cxchg_acq(&b.counter, 1, 0); + return v == 0; +} + +benaphore_unlock :: proc(b: ^Benaphore) { + if intrinsics.atomic_sub_rel(&b.counter, 1) > 0 { + sema_post(&b.sema); + } +} + +Recursive_Benaphore :: struct { + counter: int, + owner: int, + recursion: int, + sema: Sema, +} + +recursive_benaphore_lock :: proc(b: ^Recursive_Benaphore) { + tid := runtime.current_thread_id(); + if intrinsics.atomic_add_acq(&b.counter, 1) > 1 { + if tid != b.owner { + sema_wait(&b.sema); + } + } + // inside the lock + b.owner = tid; + b.recursion += 1; +} + +recursive_benaphore_try_lock :: proc(b: ^Recursive_Benaphore) -> bool { + tid := runtime.current_thread_id(); + if b.owner == tid { + intrinsics.atomic_add_acq(&b.counter, 1); + } + + if v, _ := intrinsics.atomic_cxchg_acq(&b.counter, 1, 0); v != 0 { + return false; + } + // inside the lock + b.owner = tid; + b.recursion += 1; + return true; +} + +recursive_benaphore_unlock :: proc(b: ^Recursive_Benaphore) { + tid := runtime.current_thread_id(); + assert(tid == b.owner); + b.recursion -= 1; + recursion := b.recursion; + if recursion == 0 { + b.owner = 0; + } + if intrinsics.atomic_sub_rel(&b.counter, 1) > 0 { + if recursion == 0 { + sema_post(&b.sema); + } + } + // outside the lock +} diff --git a/core/sync/sync2/primitives.odin b/core/sync/sync2/primitives.odin new file mode 100644 index 000000000..dd6688a50 --- /dev/null +++ b/core/sync/sync2/primitives.odin @@ -0,0 +1,185 @@ +package sync2 + +import "core:time" +import "core:runtime" + +// A Mutex is a mutual exclusion lock +// The zero value for a Mutex is an unlocked mutex +// +// A Mutex must not be copied after first use +Mutex :: struct { + impl: _Mutex, +} + +// mutex_lock locks m +mutex_lock :: proc(m: ^Mutex) { + _mutex_lock(m); +} + +// mutex_lock unlocks m +mutex_unlock :: proc(m: ^Mutex) { + _mutex_unlock(m); +} + +// mutex_lock tries to lock m, will return true on success, and false on failure +mutex_try_lock :: proc(m: ^Mutex) -> bool { + return _mutex_try_lock(m); +} + +// A 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 a RW_Mutex is an unlocked mutex +// +// A RW_Mutex must not be copied after first use +RW_Mutex :: struct { + impl: _RW_Mutex, +} + +// 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. +rw_mutex_lock :: proc(rw: ^RW_Mutex) { + _rw_mutex_lock(rw); +} + +// rw_mutex_unlock unlocks rw for writing (with a single writer) +rw_mutex_unlock :: proc(rw: ^RW_Mutex) { + _rw_mutex_unlock(rw); +} + +// rw_mutex_try_lock tries to lock rw for writing (with a single writer) +rw_mutex_try_lock :: proc(rw: ^RW_Mutex) -> bool { + return _rw_mutex_try_lock(rw); +} + +// rw_mutex_shared_lock locks rw for reading (with arbitrary number of readers) +rw_mutex_shared_lock :: proc(rw: ^RW_Mutex) { + _rw_mutex_shared_lock(rw); +} + +// rw_mutex_shared_unlock unlocks rw for reading (with arbitrary number of readers) +rw_mutex_shared_unlock :: proc(rw: ^RW_Mutex) { + _rw_mutex_shared_unlock(rw); +} + +// rw_mutex_try_shared_lock tries to lock rw for reading (with arbitrary number of readers) +rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool { + return _rw_mutex_try_shared_lock(rw); +} + + +// A Recusrive_Mutex is a recursive mutual exclusion lock +// The zero value for a Recursive_Mutex is an unlocked mutex +// +// A Recursive_Mutex must not be copied after first use +Recursive_Mutex :: struct { + // TODO(bill): Is this implementation too lazy? + // Can this be made to work on all OSes without construction and destruction, i.e. Zero is Initialized + // CRITICAL_SECTION would be a perfect candidate for this on Windows but that cannot be "dumb" + + owner: int, + recursion: int, + mutex: Mutex, +} + +recursive_mutex_lock :: proc(m: ^Recursive_Mutex) { + tid := runtime.current_thread_id(); + if tid != m.owner { + mutex_lock(&m.mutex); + } + // inside the lock + m.owner = tid; + m.recursion += 1; +} + +recursive_mutex_unlock :: proc(m: ^Recursive_Mutex) { + tid := runtime.current_thread_id(); + assert(tid == m.owner); + m.recursion -= 1; + recursion := m.recursion; + if recursion == 0 { + m.owner = 0; + } + if recursion == 0 { + mutex_unlock(&m.mutex); + } + // outside the lock + +} + +recursive_mutex_try_lock :: proc(m: ^Recursive_Mutex) -> bool { + tid := runtime.current_thread_id(); + if m.owner == tid { + return mutex_try_lock(&m.mutex); + } + if !mutex_try_lock(&m.mutex) { + return false; + } + // inside the lock + m.owner = tid; + m.recursion += 1; + return true; +} + + + +// Cond implements a condition variable, a rendezvous point for threads +// waiting for signalling the occurence of an event +// +// A Cond must not be copied after first use +Cond :: struct { + impl: _Cond, +} + +cond_wait :: proc(c: ^Cond, m: ^Mutex) { + _cond_wait(c, m); +} + +cond_wait_with_timeout :: proc(c: ^Cond, m: ^Mutex, timeout: time.Duration) -> bool { + return _cond_wait_with_timeout(c, m, timeout); +} + +cond_signal :: proc(c: ^Cond) { + _cond_signal(c); +} + +cond_broadcast :: proc(c: ^Cond) { + _cond_broadcast(c); +} + + + +// When waited upon, blocks until the internal count is greater than zero, then subtracts one. +// Posting to the semaphore increases the count by one, or the provided amount. +// +// A Sema must not be copied after first use +Sema :: struct { + // TODO(bill): Is this implementation too lazy? + // Can this be made to work on all OSes without construction and destruction, i.e. Zero is Initialized + + mutex: Mutex, + cond: Cond, + count: int, +} + + +sema_wait :: proc(s: ^Sema) { + mutex_lock(&s.mutex); + defer mutex_unlock(&s.mutex); + + for s.count == 0 { + cond_wait(&s.cond, &s.mutex); + } + + s.count -= 1; + if s.count > 0 { + cond_signal(&s.cond); + } +} + +sema_post :: proc(s: ^Sema, count := 1) { + mutex_lock(&s.mutex); + defer mutex_unlock(&s.mutex); + + s.count += count; + cond_signal(&s.cond); +} diff --git a/core/sync/sync2/primitives_atomic.odin b/core/sync/sync2/primitives_atomic.odin new file mode 100644 index 000000000..6133ed77b --- /dev/null +++ b/core/sync/sync2/primitives_atomic.odin @@ -0,0 +1,244 @@ +//+build linux, darwin, freebsd +//+private +package sync2 + +when !#config(ODIN_SYNC_USE_PTHREADS, false) { + +import "intrinsics" +import "core:time" + +_Mutex_State :: enum i32 { + Unlocked = 0, + Locked = 1, + Waiting = 2, +} +_Mutex :: struct { + state: _Mutex_State, +} + +_mutex_lock :: proc(m: ^Mutex) { + if intrinsics.atomic_xchg_rel(&m.impl.state, .Unlocked) != .Unlocked { + _mutex_unlock_slow(m); + } +} + +_mutex_unlock :: proc(m: ^Mutex) { + switch intrinsics.atomic_xchg_rel(&m.impl.state, .Unlocked) { + case .Unlocked: + unreachable(); + case .Locked: + // Okay + case .Waiting: + _mutex_unlock_slow(m); + } +} + +_mutex_try_lock :: proc(m: ^Mutex) -> bool { + _, ok := intrinsics.atomic_cxchg_acq(&m.impl.state, .Unlocked, .Locked); + return ok; +} + + + +_mutex_lock_slow :: proc(m: ^Mutex, curr_state: _Mutex_State) { + new_state := curr_state; // Make a copy of it + + spin_lock: for spin in 0.. 0; i -= 1 { + intrinsics.cpu_relax(); + } + } + + for { + if intrinsics.atomic_xchg_acq(&m.impl.state, .Waiting) == .Unlocked { + return; + } + + // TODO(bill): Use a Futex here for Linux to improve performance and error handling + intrinsics.cpu_relax(); + } +} + + +_mutex_unlock_slow :: proc(m: ^Mutex) { + // TODO(bill): Use a Futex here for Linux to improve performance and error handling +} + + +RW_Mutex_State :: distinct uint; +RW_Mutex_State_Half_Width :: size_of(RW_Mutex_State)*8/2; +RW_Mutex_State_Is_Writing :: RW_Mutex_State(1); +RW_Mutex_State_Writer :: RW_Mutex_State(1)<<1; +RW_Mutex_State_Reader :: RW_Mutex_State(1)< bool { + if mutex_try_lock(&rw.impl.mutex) { + state := intrinsics.atomic_load(&rw.impl.state); + if state & RW_Mutex_State_Reader_Mask == 0 { + _ = intrinsics.atomic_or(&rw.impl.state, RW_Mutex_State_Is_Writing); + return true; + } + + mutex_unlock(&rw.impl.mutex); + } + return false; +} + +_rw_mutex_shared_lock :: proc(rw: ^RW_Mutex) { + state := intrinsics.atomic_load(&rw.impl.state); + for state & (RW_Mutex_State_Is_Writing|RW_Mutex_State_Writer_Mask) == 0 { + ok: bool; + state, ok = intrinsics.atomic_cxchgweak(&rw.impl.state, state, state + RW_Mutex_State_Reader); + if ok { + return; + } + } + + mutex_lock(&rw.impl.mutex); + _ = intrinsics.atomic_add(&rw.impl.state, RW_Mutex_State_Reader); + mutex_unlock(&rw.impl.mutex); +} + +_rw_mutex_shared_unlock :: proc(rw: ^RW_Mutex) { + state := intrinsics.atomic_sub(&rw.impl.state, RW_Mutex_State_Reader); + + if (state & RW_Mutex_State_Reader_Mask == RW_Mutex_State_Reader) && + (state & RW_Mutex_State_Is_Writing != 0) { + sema_post(&rw.impl.sema); + } +} + +_rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool { + state := intrinsics.atomic_load(&rw.impl.state); + if state & (RW_Mutex_State_Is_Writing|RW_Mutex_State_Writer_Mask) == 0 { + _, ok := intrinsics.atomic_cxchg(&rw.impl.state, state, state + RW_Mutex_State_Reader); + if ok { + return true; + } + } + if mutex_try_lock(&rw.impl.mutex) { + _ = intrinsics.atomic_add(&rw.impl.state, RW_Mutex_State_Reader); + mutex_unlock(&rw.impl.mutex); + return true; + } + + return false; +} + + + +Queue_Item :: struct { + next: ^Queue_Item, + futex: i32, +} + +queue_item_wait :: proc(item: ^Queue_Item) { + for intrinsics.atomic_load_acq(&item.futex) == 0 { + // TODO(bill): Use a Futex here for Linux to improve performance and error handling + intrinsics.cpu_relax(); + } +} +queue_item_signal :: proc(item: ^Queue_Item) { + intrinsics.atomic_store_rel(&item.futex, 1); + // TODO(bill): Use a Futex here for Linux to improve performance and error handling +} + + +_Cond :: struct { + queue_mutex: Mutex, + queue_head: ^Queue_Item, + pending: bool, +} + +_cond_wait :: proc(c: ^Cond, m: ^Mutex) { + waiter := &Queue_Item{}; + + mutex_lock(&c.impl.queue_mutex); + waiter.next = c.impl.queue_head; + c.impl.queue_head = waiter; + + intrinsics.atomic_store(&c.impl.pending, true); + mutex_unlock(&c.impl.queue_mutex); + + mutex_unlock(m); + queue_item_wait(waiter); + mutex_lock(m); +} + +_cond_wait_with_timeout :: proc(c: ^Cond, m: ^Mutex, timeout: time.Duration) -> bool { + // TODO(bill): _cond_wait_with_timeout for unix + return false; +} + +_cond_signal :: proc(c: ^Cond) { + if !intrinsics.atomic_load(&c.impl.pending) { + return; + } + + mutex_lock(&c.impl.queue_mutex); + waiter := c.impl.queue_head; + if c.impl.queue_head != nil { + c.impl.queue_head = c.impl.queue_head.next; + } + intrinsics.atomic_store(&c.impl.pending, c.impl.queue_head != nil); + mutex_unlock(&c.impl.queue_mutex); + + if waiter != nil { + queue_item_signal(waiter); + } +} + +_cond_broadcast :: proc(c: ^Cond) { + if !intrinsics.atomic_load(&c.impl.pending) { + return; + } + + intrinsics.atomic_store(&c.impl.pending, false); + + mutex_lock(&c.impl.queue_mutex); + waiters := c.impl.queue_head; + c.impl.queue_head = nil; + mutex_unlock(&c.impl.queue_mutex); + + for waiters != nil { + queue_item_signal(waiters); + waiters = waiters.next; + } +} + + +} // !ODIN_SYNC_USE_PTHREADS diff --git a/core/sync/sync2/primitives_pthreads.odin b/core/sync/sync2/primitives_pthreads.odin new file mode 100644 index 000000000..cb580f03f --- /dev/null +++ b/core/sync/sync2/primitives_pthreads.odin @@ -0,0 +1,155 @@ +//+build linux, darwin, freebsd +//+private +package sync2 + +when #config(ODIN_SYNC_USE_PTHREADS, false) { + +import "intrinsics" +import "core:time" +import "core:sys/unix" + +_Mutex_State :: enum i32 { + Unlocked = 0, + Locked = 1, + Waiting = 2, +} +_Mutex :: struct { + pthread_mutex: unix.pthread_mutex_t, +} + +_mutex_lock :: proc(m: ^Mutex) { + err := unix.pthread_mutex_lock(&m.impl.pthread_mutex); + assert(err == 0); +} + +_mutex_unlock :: proc(m: ^Mutex) { + err := unix.pthread_mutex_unlock(&m.impl.pthread_mutex); + assert(err == 0); +} + +_mutex_try_lock :: proc(m: ^Mutex) -> bool { + err := unix.pthread_mutex_trylock(&m.impl.pthread_mutex); + return err == 0; +} + + + +RW_Mutex_State :: distinct uint; +RW_Mutex_State_Half_Width :: size_of(RW_Mutex_State)*8/2; +RW_Mutex_State_Is_Writing :: RW_Mutex_State(1); +RW_Mutex_State_Writer :: RW_Mutex_State(1)<<1; +RW_Mutex_State_Reader :: RW_Mutex_State(1)< bool { + if mutex_try_lock(&rw.impl.mutex) { + state := intrinsics.atomic_load(&rw.impl.state); + if state & RW_Mutex_State_Reader_Mask == 0 { + _ = intrinsics.atomic_or(&rw.impl.state, RW_Mutex_State_Is_Writing); + return true; + } + + mutex_unlock(&rw.impl.mutex); + } + return false; +} + +_rw_mutex_shared_lock :: proc(rw: ^RW_Mutex) { + state := intrinsics.atomic_load(&rw.impl.state); + for state & (RW_Mutex_State_Is_Writing|RW_Mutex_State_Writer_Mask) == 0 { + ok: bool; + state, ok = intrinsics.atomic_cxchgweak(&rw.impl.state, state, state + RW_Mutex_State_Reader); + if ok { + return; + } + } + + mutex_lock(&rw.impl.mutex); + _ = intrinsics.atomic_add(&rw.impl.state, RW_Mutex_State_Reader); + mutex_unlock(&rw.impl.mutex); +} + +_rw_mutex_shared_unlock :: proc(rw: ^RW_Mutex) { + state := intrinsics.atomic_sub(&rw.impl.state, RW_Mutex_State_Reader); + + if (state & RW_Mutex_State_Reader_Mask == RW_Mutex_State_Reader) && + (state & RW_Mutex_State_Is_Writing != 0) { + sema_post(&rw.impl.sema); + } +} + +_rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool { + state := intrinsics.atomic_load(&rw.impl.state); + if state & (RW_Mutex_State_Is_Writing|RW_Mutex_State_Writer_Mask) == 0 { + _, ok := intrinsics.atomic_cxchg(&rw.impl.state, state, state + RW_Mutex_State_Reader); + if ok { + return true; + } + } + if mutex_try_lock(&rw.impl.mutex) { + _ = intrinsics.atomic_add(&rw.impl.state, RW_Mutex_State_Reader); + mutex_unlock(&rw.impl.mutex); + return true; + } + + return false; +} + +_Cond :: struct { + pthread_cond: unix.pthread_cond_t, +} + +_cond_wait :: proc(c: ^Cond, m: ^Mutex) { + err := unix.pthread_cond_wait(&c.impl.pthread_cond, &m.impl.pthread_mutex); + assert(err == 0); +} + +_cond_wait_with_timeout :: proc(c: ^Cond, m: ^Mutex, timeout: time.Duration) -> bool { + ns := time.duration_nanoseconds(timeout); + timeout_timespec := &time.TimeSpec{ + tv_sec = ns / 1e9, + tv_nsec = ns % 1e9, + }; + err := unix.pthread_cond_timedwait(&c.impl.pthread_cond, &m.impl.pthread_mutex, timeout_timespec); + // TODO(bill): + return err == 0; +} + +_cond_signal :: proc(c: ^Cond) { + err := unix.pthread_cond_signal(&c.impl.pthread_cond); + assert(err == 0); +} + +_cond_broadcast :: proc(c: ^Cond) { + err := unix.pthread_cond_broadcast(&c.impl.pthread_cond); + assert(err == 0); +} + + +} // ODIN_SYNC_USE_PTHREADS diff --git a/core/sync/sync2/primitives_windows.odin b/core/sync/sync2/primitives_windows.odin new file mode 100644 index 000000000..02b6cd733 --- /dev/null +++ b/core/sync/sync2/primitives_windows.odin @@ -0,0 +1,73 @@ +//+build windows +//+private +package sync2 + +import "core:time" +import win32 "core:sys/windows" + +_Mutex :: struct { + srwlock: win32.SRWLOCK, +} + +_mutex_lock :: proc(m: ^Mutex) { + win32.AcquireSRWLockExclusive(&m.impl.srwlock); +} + +_mutex_unlock :: proc(m: ^Mutex) { + win32.ReleaseSRWLockExclusive(&m.impl.srwlock); +} + +_mutex_try_lock :: proc(m: ^Mutex) -> bool { + return bool(win32.TryAcquireSRWLockExclusive(&m.impl.srwlock)); +} + +_RW_Mutex :: struct { + srwlock: win32.SRWLOCK, +} + +_rw_mutex_lock :: proc(rw: ^RW_Mutex) { + win32.AcquireSRWLockExclusive(&rw.impl.srwlock); +} + +_rw_mutex_unlock :: proc(rw: ^RW_Mutex) { + win32.ReleaseSRWLockExclusive(&rw.impl.srwlock); +} + +_rw_mutex_try_lock :: proc(rw: ^RW_Mutex) -> bool { + return bool(win32.TryAcquireSRWLockExclusive(&rw.impl.srwlock)); +} + +_rw_mutex_shared_lock :: proc(rw: ^RW_Mutex) { + win32.AcquireSRWLockShared(&rw.impl.srwlock); +} + +_rw_mutex_shared_unlock :: proc(rw: ^RW_Mutex) { + win32.ReleaseSRWLockShared(&rw.impl.srwlock); +} + +_rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool { + return bool(win32.TryAcquireSRWLockShared(&rw.impl.srwlock)); +} + + + +_Cond :: struct { + cond: win32.CONDITION_VARIABLE, +} + +_cond_wait :: proc(c: ^Cond, m: ^Mutex) { + _ = win32.SleepConditionVariableSRW(&c.impl.cond, &m.impl.srwlock, win32.INFINITE, 0); +} + +_cond_wait_with_timeout :: proc(c: ^Cond, m: ^Mutex, timeout: time.Duration) -> bool { + ms := win32.DWORD((max(time.duration_nanoseconds(timeout), 0) + 999999)/1000000); + return cast(bool)win32.SleepConditionVariableSRW(&c.impl.cond, &m.impl.srwlock, ms, 0); +} + +_cond_signal :: proc(c: ^Cond) { + win32.WakeConditionVariable(&c.impl.cond); +} + +_cond_broadcast :: proc(c: ^Cond) { + win32.WakeAllConditionVariable(&c.impl.cond); +} From 52c193316b3dec24ae78b6790134840536af406f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 11 Apr 2021 15:36:55 +0100 Subject: [PATCH 05/36] Add Thread stuff to new sync package --- core/sync/sync2/thread.odin | 193 ++++++++++++++++++++++++++++ core/sync/sync2/thread_unix.odin | 175 +++++++++++++++++++++++++ core/sync/sync2/thread_windows.odin | 123 ++++++++++++++++++ 3 files changed, 491 insertions(+) create mode 100644 core/sync/sync2/thread.odin create mode 100644 core/sync/sync2/thread_unix.odin create mode 100644 core/sync/sync2/thread_windows.odin diff --git a/core/sync/sync2/thread.odin b/core/sync/sync2/thread.odin new file mode 100644 index 000000000..a20f1bd7f --- /dev/null +++ b/core/sync/sync2/thread.odin @@ -0,0 +1,193 @@ +package sync2 + +import "core:runtime" +import "core:sync" +import "core:mem" +import "intrinsics" + +_ :: intrinsics; + +Thread_Proc :: #type proc(^Thread); + +MAX_USER_ARGUMENTS :: 8; + +Thread :: struct { + using specific: Thread_Os_Specific, + procedure: Thread_Proc, + data: rawptr, + user_index: int, + user_args: [MAX_USER_ARGUMENTS]rawptr, + + init_context: Maybe(runtime.Context), + + + creation_allocator: mem.Allocator, +} + +#assert(size_of(Thread{}.user_index) == size_of(uintptr)); + +Thread_Priority :: enum { + Normal, + Low, + High, +} + +thread_create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread { + return _thread_create(procedure, priority); +} +thread_destroy :: proc(thread: ^Thread) { + _thread_destroy(thread); +} + +thread_start :: proc(thread: ^Thread) { + _thread_start(thread); +} + +thread_is_done :: proc(thread: ^Thread) -> bool { + return _thread_is_done(thread); +} + + +thread_join :: proc(thread: ^Thread) { + _thread_join(thread); +} + + +thread_join_mulitple :: proc(threads: ..^Thread) { + _thread_join_multiple(..threads); +} + +thread_terminate :: proc(thread: ^Thread, exit_code: int) { + _thread_terminate(thread, exit_code); +} + +thread_yield :: proc() { + _thread_yield(); +} + + + +thread_run :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) { + thread_proc :: proc(t: ^Thread) { + fn := cast(proc())t.data; + fn(); + thread_destroy(t); + } + t := thread_create(thread_proc, priority); + t.data = rawptr(fn); + t.init_context = init_context; + thread_start(t); +} + +thread_run_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) { + thread_proc :: proc(t: ^Thread) { + fn := cast(proc(rawptr))t.data; + assert(t.user_index >= 1); + data := t.user_args[0]; + fn(data); + thread_destroy(t); + } + t := thread_create(thread_proc, priority); + t.data = rawptr(fn); + t.user_index = 1; + t.user_args = data; + t.init_context = init_context; + thread_start(t); +} + +thread_run_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) + where size_of(T) <= size_of(rawptr) { + thread_proc :: proc(t: ^Thread) { + fn := cast(proc(T))t.data; + assert(t.user_index >= 1); + data := (^T)(&t.user_args[0])^; + fn(data); + thread_destroy(t); + } + t := thread_create(thread_proc, priority); + t.data = rawptr(fn); + t.user_index = 1; + data := data; + mem.copy(&t.user_args[0], &data, size_of(data)); + t.init_context = init_context; + thread_start(t); +} + +thread_run_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) + where size_of(T1) <= size_of(rawptr), + size_of(T2) <= size_of(rawptr) { + thread_proc :: proc(t: ^Thread) { + fn := cast(proc(T1, T2))t.data; + assert(t.user_index >= 2); + arg1 := (^T1)(&t.user_args[0])^; + arg2 := (^T2)(&t.user_args[1])^; + fn(arg1, arg2); + thread_destroy(t); + } + t := thread_create(thread_proc, priority); + t.data = rawptr(fn); + t.user_index = 2; + arg1, arg2 := arg1, arg2; + mem.copy(&t.user_args[0], &arg1, size_of(arg1)); + mem.copy(&t.user_args[1], &arg2, size_of(arg2)); + t.init_context = init_context; + thread_start(t); +} + +thread_run_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) + where size_of(T1) <= size_of(rawptr), + size_of(T2) <= size_of(rawptr), + size_of(T3) <= size_of(rawptr) { + thread_proc :: proc(t: ^Thread) { + fn := cast(proc(T1, T2, T3))t.data; + assert(t.user_index >= 3); + arg1 := (^T1)(&t.user_args[0])^; + arg2 := (^T2)(&t.user_args[1])^; + arg3 := (^T3)(&t.user_args[2])^; + fn(arg1, arg2, arg3); + thread_destroy(t); + } + t := thread_create(thread_proc, priority); + t.data = rawptr(fn); + t.user_index = 3; + arg1, arg2, arg3 := arg1, arg2, arg3; + mem.copy(&t.user_args[0], &arg1, size_of(arg1)); + mem.copy(&t.user_args[1], &arg2, size_of(arg2)); + mem.copy(&t.user_args[2], &arg3, size_of(arg3)); + t.init_context = init_context; + thread_start(t); +} +thread_run_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) + where size_of(T1) <= size_of(rawptr), + size_of(T2) <= size_of(rawptr), + size_of(T3) <= size_of(rawptr) { + thread_proc :: proc(t: ^Thread) { + fn := cast(proc(T1, T2, T3, T4))t.data; + assert(t.user_index >= 4); + arg1 := (^T1)(&t.user_args[0])^; + arg2 := (^T2)(&t.user_args[1])^; + arg3 := (^T3)(&t.user_args[2])^; + arg4 := (^T4)(&t.user_args[3])^; + fn(arg1, arg2, arg3, arg4); + thread_destroy(t); + } + t := thread_create(thread_proc, priority); + t.data = rawptr(fn); + t.user_index = 4; + arg1, arg2, arg3, arg4 := arg1, arg2, arg3, arg4; + mem.copy(&t.user_args[0], &arg1, size_of(arg1)); + mem.copy(&t.user_args[1], &arg2, size_of(arg2)); + mem.copy(&t.user_args[2], &arg3, size_of(arg3)); + mem.copy(&t.user_args[3], &arg4, size_of(arg4)); + t.init_context = init_context; + thread_start(t); +} + + + +thread_create_and_start :: proc(fn: Thread_Proc, init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) -> ^Thread { + t := thread_create(fn, priority); + t.init_context = init_context; + thread_start(t); + return t; +} diff --git a/core/sync/sync2/thread_unix.odin b/core/sync/sync2/thread_unix.odin new file mode 100644 index 000000000..d56734ed9 --- /dev/null +++ b/core/sync/sync2/thread_unix.odin @@ -0,0 +1,175 @@ +// +build linux, darwin, freebsd +// +private +package sync2 + +import "core:runtime" +import "core:intrinsics" +import "core:sys/unix" + +// NOTE(tetra): Aligned here because of core/unix/pthread_linux.odin/pthread_t. +// Also see core/sys/darwin/mach_darwin.odin/semaphore_t. +Thread_Os_Specific :: struct #align 16 { + unix_thread: unix.pthread_t, // NOTE: very large on Darwin, small on Linux. + + // NOTE: pthread has a proc to query this, but it is marked + // as non-portable ("np") so we do this instead. + done: bool, + + // since libpthread doesn't seem to have a way to create a thread + // in a suspended state, we have it wait on this gate, which we + // signal to start it. + // destroyed after thread is started. + start_gate: Cond, + start_mutex: Mutex, + + // if true, the thread has been started and the start_gate has been destroyed. + started: bool, + + // NOTE: with pthreads, it is undefined behavior for multiple threads + // to call join on the same thread at the same time. + // this value is atomically updated to detect this. + // See the comment in `join`. + already_joined: bool, +} +// +// Creates a thread which will run the given procedure. +// It then waits for `start` to be called. +// +_thread_create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread { + __linux_thread_entry_proc :: proc "c" (t: rawptr) -> rawptr { + context = runtime.default_context(); + + t := (^Thread)(t); + cond_wait(&t.start_gate, &t.start_mutex); + t.start_gate = {}; + t.start_mutex = {}; + + c := context; + if ic, ok := t.init_context.?; ok { + c = ic; + } + context = c; + + t.procedure(t); + + if t.init_context == nil { + if context.temp_allocator.data == &runtime.global_default_temp_allocator_data { + runtime.default_temp_allocator_destroy(auto_cast context.temp_allocator.data); + } + } + + atomic_store(&t.done, true, .Sequentially_Consistent); + return nil; + } + + attrs: unix.pthread_attr_t; + if unix.pthread_attr_init(&attrs) != 0 { + return nil; // NOTE(tetra, 2019-11-01): POSIX OOM. + } + defer unix.pthread_attr_destroy(&attrs); + + // NOTE(tetra, 2019-11-01): These only fail if their argument is invalid. + assert(unix.pthread_attr_setdetachstate(&attrs, unix.PTHREAD_CREATE_JOINABLE) == 0); + assert(unix.pthread_attr_setinheritsched(&attrs, unix.PTHREAD_EXPLICIT_SCHED) == 0); + + thread := new(Thread); + if thread == nil { + return nil; + } + thread.creation_allocator = context.allocator; + + // Set thread priority. + policy: i32; + res := unix.pthread_attr_getschedpolicy(&attrs, &policy); + assert(res == 0); + params: unix.sched_param; + res = unix.pthread_attr_getschedparam(&attrs, ¶ms); + assert(res == 0); + low := unix.sched_get_priority_min(policy); + high := unix.sched_get_priority_max(policy); + switch priority { + case .Normal: // Okay + case .Low: params.sched_priority = low + 1; + case .High: params.sched_priority = high; + } + res = unix.pthread_attr_setschedparam(&attrs, ¶ms); + assert(res == 0); + + if unix.pthread_create(&thread.unix_thread, &attrs, __linux_thread_entry_proc, thread) != 0 { + free(thread, thread.creation_allocator); + return nil; + } + thread.procedure = procedure; + + return thread; +} + +_thread_start :: proc(t: ^Thread) { + if intrinsics.atomic_xchg(&t.started, true) { + return; + } + cond_signal(&t.start_gate); +} + +_thread_is_done :: proc(t: ^Thread) -> bool { + return atomic_load(&t.done, .Sequentially_Consistent); +} + +_thread_join :: proc(t: ^Thread) { + if unix.pthread_equal(unix.pthread_self(), t.unix_thread) { + return; + } + // if unix.pthread_self().x == t.unix_thread.x do return; + + // NOTE(tetra): It's apparently UB for multiple threads to join the same thread + // at the same time. + // If someone else already did, spin until the thread dies. + // See note on `already_joined` field. + // TODO(tetra): I'm not sure if we should do this, or panic, since I'm not + // sure it makes sense to need to join from multiple threads? + if intrinsics.atomic_xchg(&t.already_joined, true) { + for { + if intrinsics.atomic_load(&t.done) { + return; + } + intrinsics.cpu_relax(); + } + } + + // NOTE(tetra): If we're already dead, don't bother calling to pthread_join as that + // will just return 3 (ESRCH). + // We do this instead because I don't know if there is a danger + // that you may join a different thread from the one you called join on, + // if the thread handle is reused. + if intrinsics.atomic_load(&t.done) { + return; + } + + ret_val: rawptr; + _ = unix.pthread_join(t.unix_thread, &ret_val); + if !intrinsics.atomic_load(&t.done) { + panic("thread not done after join"); + } +} + +_thread_join_multiple :: proc(threads: ..^Thread) { + for t in threads { + _thread_join(t); + } +} + + +_thread_destroy :: proc(t: ^Thread) { + _thread_join(t); + t.unix_thread = {}; + free(t, t.creation_allocator); +} + + +_thread_terminate :: proc(t: ^Thread, exit_code: int) { + // TODO(bill) +} + +_thread_yield :: proc() { + unix.sched_yield(); +} diff --git a/core/sync/sync2/thread_windows.odin b/core/sync/sync2/thread_windows.odin new file mode 100644 index 000000000..6aa2fddd2 --- /dev/null +++ b/core/sync/sync2/thread_windows.odin @@ -0,0 +1,123 @@ +//+build windows +//+private +package sync2 + +import "core:runtime" +import "core:sync" +import win32 "core:sys/windows" + +Thread_Os_Specific :: struct { + win32_thread: win32.HANDLE, + win32_thread_id: win32.DWORD, + done: bool, // see note in `is_done` +} + +_thread_priority_map := [Thread_Priority]i32{ + .Normal = 0, + .Low = -2, + .High = +2, +}; + +_thread_create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread { + win32_thread_id: win32.DWORD; + + __windows_thread_entry_proc :: proc "stdcall" (t_: rawptr) -> win32.DWORD { + t := (^Thread)(t_); + context = runtime.default_context(); + c := context; + if ic, ok := t.init_context.?; ok { + c = ic; + } + context = c; + + t.procedure(t); + + if t.init_context == nil { + if context.temp_allocator.data == &runtime.global_default_temp_allocator_data { + runtime.default_temp_allocator_destroy(auto_cast context.temp_allocator.data); + } + } + + sync.atomic_store(&t.done, true, .Sequentially_Consistent); + return 0; + } + + + thread := new(Thread); + if thread == nil { + return nil; + } + thread.creation_allocator = context.allocator; + + win32_thread := win32.CreateThread(nil, 0, __windows_thread_entry_proc, thread, win32.CREATE_SUSPENDED, &win32_thread_id); + if win32_thread == nil { + free(thread, thread.creation_allocator); + return nil; + } + thread.procedure = procedure; + thread.win32_thread = win32_thread; + thread.win32_thread_id = win32_thread_id; + thread.init_context = context; + + ok := win32.SetThreadPriority(win32_thread, _thread_priority_map[priority]); + assert(ok == true); + + return thread; +} + +_thread_start :: proc(thread: ^Thread) { + win32.ResumeThread(thread.win32_thread); +} + +_thread_is_done :: proc(using thread: ^Thread) -> bool { + // NOTE(tetra, 2019-10-31): Apparently using wait_for_single_object and + // checking if it didn't time out immediately, is not good enough, + // so we do it this way instead. + return sync.atomic_load(&done, .Sequentially_Consistent); +} + +_thread_join :: proc(using thread: ^Thread) { + if win32_thread != win32.INVALID_HANDLE { + win32.WaitForSingleObject(win32_thread, win32.INFINITE); + win32.CloseHandle(win32_thread); + win32_thread = win32.INVALID_HANDLE; + } +} + +_thread_join_multiple :: proc(threads: ..^Thread) { + MAXIMUM_WAIT_OBJECTS :: 64; + + handles: [MAXIMUM_WAIT_OBJECTS]win32.HANDLE; + + for k := 0; k < len(threads); k += MAXIMUM_WAIT_OBJECTS { + count := min(len(threads) - k, MAXIMUM_WAIT_OBJECTS); + j := 0; + for i in 0.. Date: Sun, 11 Apr 2021 18:25:56 +0100 Subject: [PATCH 06/36] Remove thread stuff from sync2; Cleanup package thread --- core/sync/sync2/extended.odin | 24 ++++ core/sync/sync2/thread.odin | 193 ---------------------------- core/sync/sync2/thread_unix.odin | 175 ------------------------- core/sync/sync2/thread_windows.odin | 123 ------------------ core/thread/thread.odin | 69 +++++----- core/thread/thread_unix.odin | 58 ++++----- core/thread/thread_windows.odin | 31 ++--- 7 files changed, 103 insertions(+), 570 deletions(-) delete mode 100644 core/sync/sync2/thread.odin delete mode 100644 core/sync/sync2/thread_unix.odin delete mode 100644 core/sync/sync2/thread_windows.odin diff --git a/core/sync/sync2/extended.odin b/core/sync/sync2/extended.odin index 3c439b225..f9216d116 100644 --- a/core/sync/sync2/extended.odin +++ b/core/sync/sync2/extended.odin @@ -213,3 +213,27 @@ recursive_benaphore_unlock :: proc(b: ^Recursive_Benaphore) { } // outside the lock } + + + + + +Once :: struct { + m: Mutex, + done: bool, +} + +once_do :: proc(o: ^Once, fn: proc()) { + if intrinsics.atomic_load_acq(&o.done) == false { + _once_do_slow(o, fn); + } +} + +_once_do_slow :: proc(o: ^Once, fn: proc()) { + mutex_lock(&o.m); + defer mutex_unlock(&o.m); + if !o.done { + fn(); + intrinsics.atomic_store_rel(&o.done, true); + } +} diff --git a/core/sync/sync2/thread.odin b/core/sync/sync2/thread.odin deleted file mode 100644 index a20f1bd7f..000000000 --- a/core/sync/sync2/thread.odin +++ /dev/null @@ -1,193 +0,0 @@ -package sync2 - -import "core:runtime" -import "core:sync" -import "core:mem" -import "intrinsics" - -_ :: intrinsics; - -Thread_Proc :: #type proc(^Thread); - -MAX_USER_ARGUMENTS :: 8; - -Thread :: struct { - using specific: Thread_Os_Specific, - procedure: Thread_Proc, - data: rawptr, - user_index: int, - user_args: [MAX_USER_ARGUMENTS]rawptr, - - init_context: Maybe(runtime.Context), - - - creation_allocator: mem.Allocator, -} - -#assert(size_of(Thread{}.user_index) == size_of(uintptr)); - -Thread_Priority :: enum { - Normal, - Low, - High, -} - -thread_create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread { - return _thread_create(procedure, priority); -} -thread_destroy :: proc(thread: ^Thread) { - _thread_destroy(thread); -} - -thread_start :: proc(thread: ^Thread) { - _thread_start(thread); -} - -thread_is_done :: proc(thread: ^Thread) -> bool { - return _thread_is_done(thread); -} - - -thread_join :: proc(thread: ^Thread) { - _thread_join(thread); -} - - -thread_join_mulitple :: proc(threads: ..^Thread) { - _thread_join_multiple(..threads); -} - -thread_terminate :: proc(thread: ^Thread, exit_code: int) { - _thread_terminate(thread, exit_code); -} - -thread_yield :: proc() { - _thread_yield(); -} - - - -thread_run :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) { - thread_proc :: proc(t: ^Thread) { - fn := cast(proc())t.data; - fn(); - thread_destroy(t); - } - t := thread_create(thread_proc, priority); - t.data = rawptr(fn); - t.init_context = init_context; - thread_start(t); -} - -thread_run_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) { - thread_proc :: proc(t: ^Thread) { - fn := cast(proc(rawptr))t.data; - assert(t.user_index >= 1); - data := t.user_args[0]; - fn(data); - thread_destroy(t); - } - t := thread_create(thread_proc, priority); - t.data = rawptr(fn); - t.user_index = 1; - t.user_args = data; - t.init_context = init_context; - thread_start(t); -} - -thread_run_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) - where size_of(T) <= size_of(rawptr) { - thread_proc :: proc(t: ^Thread) { - fn := cast(proc(T))t.data; - assert(t.user_index >= 1); - data := (^T)(&t.user_args[0])^; - fn(data); - thread_destroy(t); - } - t := thread_create(thread_proc, priority); - t.data = rawptr(fn); - t.user_index = 1; - data := data; - mem.copy(&t.user_args[0], &data, size_of(data)); - t.init_context = init_context; - thread_start(t); -} - -thread_run_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) - where size_of(T1) <= size_of(rawptr), - size_of(T2) <= size_of(rawptr) { - thread_proc :: proc(t: ^Thread) { - fn := cast(proc(T1, T2))t.data; - assert(t.user_index >= 2); - arg1 := (^T1)(&t.user_args[0])^; - arg2 := (^T2)(&t.user_args[1])^; - fn(arg1, arg2); - thread_destroy(t); - } - t := thread_create(thread_proc, priority); - t.data = rawptr(fn); - t.user_index = 2; - arg1, arg2 := arg1, arg2; - mem.copy(&t.user_args[0], &arg1, size_of(arg1)); - mem.copy(&t.user_args[1], &arg2, size_of(arg2)); - t.init_context = init_context; - thread_start(t); -} - -thread_run_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) - where size_of(T1) <= size_of(rawptr), - size_of(T2) <= size_of(rawptr), - size_of(T3) <= size_of(rawptr) { - thread_proc :: proc(t: ^Thread) { - fn := cast(proc(T1, T2, T3))t.data; - assert(t.user_index >= 3); - arg1 := (^T1)(&t.user_args[0])^; - arg2 := (^T2)(&t.user_args[1])^; - arg3 := (^T3)(&t.user_args[2])^; - fn(arg1, arg2, arg3); - thread_destroy(t); - } - t := thread_create(thread_proc, priority); - t.data = rawptr(fn); - t.user_index = 3; - arg1, arg2, arg3 := arg1, arg2, arg3; - mem.copy(&t.user_args[0], &arg1, size_of(arg1)); - mem.copy(&t.user_args[1], &arg2, size_of(arg2)); - mem.copy(&t.user_args[2], &arg3, size_of(arg3)); - t.init_context = init_context; - thread_start(t); -} -thread_run_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) - where size_of(T1) <= size_of(rawptr), - size_of(T2) <= size_of(rawptr), - size_of(T3) <= size_of(rawptr) { - thread_proc :: proc(t: ^Thread) { - fn := cast(proc(T1, T2, T3, T4))t.data; - assert(t.user_index >= 4); - arg1 := (^T1)(&t.user_args[0])^; - arg2 := (^T2)(&t.user_args[1])^; - arg3 := (^T3)(&t.user_args[2])^; - arg4 := (^T4)(&t.user_args[3])^; - fn(arg1, arg2, arg3, arg4); - thread_destroy(t); - } - t := thread_create(thread_proc, priority); - t.data = rawptr(fn); - t.user_index = 4; - arg1, arg2, arg3, arg4 := arg1, arg2, arg3, arg4; - mem.copy(&t.user_args[0], &arg1, size_of(arg1)); - mem.copy(&t.user_args[1], &arg2, size_of(arg2)); - mem.copy(&t.user_args[2], &arg3, size_of(arg3)); - mem.copy(&t.user_args[3], &arg4, size_of(arg4)); - t.init_context = init_context; - thread_start(t); -} - - - -thread_create_and_start :: proc(fn: Thread_Proc, init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) -> ^Thread { - t := thread_create(fn, priority); - t.init_context = init_context; - thread_start(t); - return t; -} diff --git a/core/sync/sync2/thread_unix.odin b/core/sync/sync2/thread_unix.odin deleted file mode 100644 index d56734ed9..000000000 --- a/core/sync/sync2/thread_unix.odin +++ /dev/null @@ -1,175 +0,0 @@ -// +build linux, darwin, freebsd -// +private -package sync2 - -import "core:runtime" -import "core:intrinsics" -import "core:sys/unix" - -// NOTE(tetra): Aligned here because of core/unix/pthread_linux.odin/pthread_t. -// Also see core/sys/darwin/mach_darwin.odin/semaphore_t. -Thread_Os_Specific :: struct #align 16 { - unix_thread: unix.pthread_t, // NOTE: very large on Darwin, small on Linux. - - // NOTE: pthread has a proc to query this, but it is marked - // as non-portable ("np") so we do this instead. - done: bool, - - // since libpthread doesn't seem to have a way to create a thread - // in a suspended state, we have it wait on this gate, which we - // signal to start it. - // destroyed after thread is started. - start_gate: Cond, - start_mutex: Mutex, - - // if true, the thread has been started and the start_gate has been destroyed. - started: bool, - - // NOTE: with pthreads, it is undefined behavior for multiple threads - // to call join on the same thread at the same time. - // this value is atomically updated to detect this. - // See the comment in `join`. - already_joined: bool, -} -// -// Creates a thread which will run the given procedure. -// It then waits for `start` to be called. -// -_thread_create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread { - __linux_thread_entry_proc :: proc "c" (t: rawptr) -> rawptr { - context = runtime.default_context(); - - t := (^Thread)(t); - cond_wait(&t.start_gate, &t.start_mutex); - t.start_gate = {}; - t.start_mutex = {}; - - c := context; - if ic, ok := t.init_context.?; ok { - c = ic; - } - context = c; - - t.procedure(t); - - if t.init_context == nil { - if context.temp_allocator.data == &runtime.global_default_temp_allocator_data { - runtime.default_temp_allocator_destroy(auto_cast context.temp_allocator.data); - } - } - - atomic_store(&t.done, true, .Sequentially_Consistent); - return nil; - } - - attrs: unix.pthread_attr_t; - if unix.pthread_attr_init(&attrs) != 0 { - return nil; // NOTE(tetra, 2019-11-01): POSIX OOM. - } - defer unix.pthread_attr_destroy(&attrs); - - // NOTE(tetra, 2019-11-01): These only fail if their argument is invalid. - assert(unix.pthread_attr_setdetachstate(&attrs, unix.PTHREAD_CREATE_JOINABLE) == 0); - assert(unix.pthread_attr_setinheritsched(&attrs, unix.PTHREAD_EXPLICIT_SCHED) == 0); - - thread := new(Thread); - if thread == nil { - return nil; - } - thread.creation_allocator = context.allocator; - - // Set thread priority. - policy: i32; - res := unix.pthread_attr_getschedpolicy(&attrs, &policy); - assert(res == 0); - params: unix.sched_param; - res = unix.pthread_attr_getschedparam(&attrs, ¶ms); - assert(res == 0); - low := unix.sched_get_priority_min(policy); - high := unix.sched_get_priority_max(policy); - switch priority { - case .Normal: // Okay - case .Low: params.sched_priority = low + 1; - case .High: params.sched_priority = high; - } - res = unix.pthread_attr_setschedparam(&attrs, ¶ms); - assert(res == 0); - - if unix.pthread_create(&thread.unix_thread, &attrs, __linux_thread_entry_proc, thread) != 0 { - free(thread, thread.creation_allocator); - return nil; - } - thread.procedure = procedure; - - return thread; -} - -_thread_start :: proc(t: ^Thread) { - if intrinsics.atomic_xchg(&t.started, true) { - return; - } - cond_signal(&t.start_gate); -} - -_thread_is_done :: proc(t: ^Thread) -> bool { - return atomic_load(&t.done, .Sequentially_Consistent); -} - -_thread_join :: proc(t: ^Thread) { - if unix.pthread_equal(unix.pthread_self(), t.unix_thread) { - return; - } - // if unix.pthread_self().x == t.unix_thread.x do return; - - // NOTE(tetra): It's apparently UB for multiple threads to join the same thread - // at the same time. - // If someone else already did, spin until the thread dies. - // See note on `already_joined` field. - // TODO(tetra): I'm not sure if we should do this, or panic, since I'm not - // sure it makes sense to need to join from multiple threads? - if intrinsics.atomic_xchg(&t.already_joined, true) { - for { - if intrinsics.atomic_load(&t.done) { - return; - } - intrinsics.cpu_relax(); - } - } - - // NOTE(tetra): If we're already dead, don't bother calling to pthread_join as that - // will just return 3 (ESRCH). - // We do this instead because I don't know if there is a danger - // that you may join a different thread from the one you called join on, - // if the thread handle is reused. - if intrinsics.atomic_load(&t.done) { - return; - } - - ret_val: rawptr; - _ = unix.pthread_join(t.unix_thread, &ret_val); - if !intrinsics.atomic_load(&t.done) { - panic("thread not done after join"); - } -} - -_thread_join_multiple :: proc(threads: ..^Thread) { - for t in threads { - _thread_join(t); - } -} - - -_thread_destroy :: proc(t: ^Thread) { - _thread_join(t); - t.unix_thread = {}; - free(t, t.creation_allocator); -} - - -_thread_terminate :: proc(t: ^Thread, exit_code: int) { - // TODO(bill) -} - -_thread_yield :: proc() { - unix.sched_yield(); -} diff --git a/core/sync/sync2/thread_windows.odin b/core/sync/sync2/thread_windows.odin deleted file mode 100644 index 6aa2fddd2..000000000 --- a/core/sync/sync2/thread_windows.odin +++ /dev/null @@ -1,123 +0,0 @@ -//+build windows -//+private -package sync2 - -import "core:runtime" -import "core:sync" -import win32 "core:sys/windows" - -Thread_Os_Specific :: struct { - win32_thread: win32.HANDLE, - win32_thread_id: win32.DWORD, - done: bool, // see note in `is_done` -} - -_thread_priority_map := [Thread_Priority]i32{ - .Normal = 0, - .Low = -2, - .High = +2, -}; - -_thread_create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread { - win32_thread_id: win32.DWORD; - - __windows_thread_entry_proc :: proc "stdcall" (t_: rawptr) -> win32.DWORD { - t := (^Thread)(t_); - context = runtime.default_context(); - c := context; - if ic, ok := t.init_context.?; ok { - c = ic; - } - context = c; - - t.procedure(t); - - if t.init_context == nil { - if context.temp_allocator.data == &runtime.global_default_temp_allocator_data { - runtime.default_temp_allocator_destroy(auto_cast context.temp_allocator.data); - } - } - - sync.atomic_store(&t.done, true, .Sequentially_Consistent); - return 0; - } - - - thread := new(Thread); - if thread == nil { - return nil; - } - thread.creation_allocator = context.allocator; - - win32_thread := win32.CreateThread(nil, 0, __windows_thread_entry_proc, thread, win32.CREATE_SUSPENDED, &win32_thread_id); - if win32_thread == nil { - free(thread, thread.creation_allocator); - return nil; - } - thread.procedure = procedure; - thread.win32_thread = win32_thread; - thread.win32_thread_id = win32_thread_id; - thread.init_context = context; - - ok := win32.SetThreadPriority(win32_thread, _thread_priority_map[priority]); - assert(ok == true); - - return thread; -} - -_thread_start :: proc(thread: ^Thread) { - win32.ResumeThread(thread.win32_thread); -} - -_thread_is_done :: proc(using thread: ^Thread) -> bool { - // NOTE(tetra, 2019-10-31): Apparently using wait_for_single_object and - // checking if it didn't time out immediately, is not good enough, - // so we do it this way instead. - return sync.atomic_load(&done, .Sequentially_Consistent); -} - -_thread_join :: proc(using thread: ^Thread) { - if win32_thread != win32.INVALID_HANDLE { - win32.WaitForSingleObject(win32_thread, win32.INFINITE); - win32.CloseHandle(win32_thread); - win32_thread = win32.INVALID_HANDLE; - } -} - -_thread_join_multiple :: proc(threads: ..^Thread) { - MAXIMUM_WAIT_OBJECTS :: 64; - - handles: [MAXIMUM_WAIT_OBJECTS]win32.HANDLE; - - for k := 0; k < len(threads); k += MAXIMUM_WAIT_OBJECTS { - count := min(len(threads) - k, MAXIMUM_WAIT_OBJECTS); - j := 0; - for i in 0.. ^Thread { + return _create(procedure, priority); +} +destroy :: proc(thread: ^Thread) { + _destroy(thread); +} + +start :: proc(thread: ^Thread) { + _start(thread); +} + +is_done :: proc(thread: ^Thread) -> bool { + return _is_done(thread); +} + + +join :: proc(thread: ^Thread) { + _join(thread); +} + + +join_mulitple :: proc(threads: ..^Thread) { + _join_multiple(..threads); +} + +terminate :: proc(thread: ^Thread, exit_code: int) { + _terminate(thread, exit_code); +} + +yield :: proc() { + _yield(); +} + + run :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) { thread_proc :: proc(t: ^Thread) { @@ -39,7 +79,6 @@ run :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := start(t); } - run_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) { thread_proc :: proc(t: ^Thread) { fn := cast(proc(rawptr))t.data; @@ -152,31 +191,3 @@ create_and_start :: proc(fn: Thread_Proc, init_context: Maybe(runtime.Context) = start(t); return t; } - - -Once :: struct { - m: sync.Blocking_Mutex, - done: bool, -} -once_init :: proc(o: ^Once) { - sync.blocking_mutex_init(&o.m); - intrinsics.atomic_store_rel(&o.done, false); -} -once_destroy :: proc(o: ^Once) { - sync.blocking_mutex_destroy(&o.m); -} - -once_do :: proc(o: ^Once, fn: proc()) { - if intrinsics.atomic_load(&o.done) == false { - _once_do_slow(o, fn); - } -} - -_once_do_slow :: proc(o: ^Once, fn: proc()) { - sync.blocking_mutex_lock(&o.m); - defer sync.blocking_mutex_unlock(&o.m); - if !o.done { - fn(); - intrinsics.atomic_store_rel(&o.done, true); - } -} diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index d87291c0e..139c323bd 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -1,9 +1,10 @@ // +build linux, darwin, freebsd -package thread; +// +private +package thread import "core:runtime" import "core:intrinsics" -import "core:sync" +import sync "core:sync/sync2" import "core:sys/unix" // NOTE(tetra): Aligned here because of core/unix/pthread_linux.odin/pthread_t. @@ -19,7 +20,7 @@ Thread_Os_Specific :: struct #align 16 { // in a suspended state, we have it wait on this gate, which we // signal to start it. // destroyed after thread is started. - start_gate: sync.Condition, + start_gate: sync.Cond, start_mutex: sync.Mutex, // if true, the thread has been started and the start_gate has been destroyed. @@ -31,25 +32,16 @@ Thread_Os_Specific :: struct #align 16 { // See the comment in `join`. already_joined: bool, } - -Thread_Priority :: enum { - Normal, - Low, - High, -} - // // Creates a thread which will run the given procedure. // It then waits for `start` to be called. // -create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread { +_create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread { __linux_thread_entry_proc :: proc "c" (t: rawptr) -> rawptr { context = runtime.default_context(); t := (^Thread)(t); - sync.condition_wait_for(&t.start_gate); - sync.condition_destroy(&t.start_gate); - sync.mutex_destroy(&t.start_mutex); + sync.cond_wait(&t.start_gate, &t.start_mutex); t.start_gate = {}; t.start_mutex = {}; @@ -67,7 +59,7 @@ create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^T } } - sync.atomic_store(&t.done, true, .Sequentially_Consistent); + intrinsics.atomic_store(&t.done, true); return nil; } @@ -104,8 +96,6 @@ create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^T res = unix.pthread_attr_setschedparam(&attrs, ¶ms); assert(res == 0); - sync.mutex_init(&thread.start_mutex); - sync.condition_init(&thread.start_gate, &thread.start_mutex); if unix.pthread_create(&thread.unix_thread, &attrs, __linux_thread_entry_proc, thread) != 0 { free(thread, thread.creation_allocator); return nil; @@ -115,18 +105,18 @@ create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^T return thread; } -start :: proc(t: ^Thread) { - if sync.atomic_swap(&t.started, true, .Sequentially_Consistent) { +_start :: proc(t: ^Thread) { + if intrinsics.atomic_xchg(&t.started, true) { return; } - sync.condition_signal(&t.start_gate); + sync.cond_signal(&t.start_gate); } -is_done :: proc(t: ^Thread) -> bool { - return sync.atomic_load(&t.done, .Sequentially_Consistent); +_is_done :: proc(t: ^Thread) -> bool { + return intrinsics.atomic_load(&t.done); } -join :: proc(t: ^Thread) { +_join :: proc(t: ^Thread) { if unix.pthread_equal(unix.pthread_self(), t.unix_thread) { return; } @@ -138,9 +128,9 @@ join :: proc(t: ^Thread) { // See note on `already_joined` field. // TODO(tetra): I'm not sure if we should do this, or panic, since I'm not // sure it makes sense to need to join from multiple threads? - if sync.atomic_swap(&t.already_joined, true, .Sequentially_Consistent) { + if intrinsics.atomic_xchg(&t.already_joined, true) { for { - if sync.atomic_load(&t.done, .Sequentially_Consistent) { + if intrinsics.atomic_load(&t.done) { return; } intrinsics.cpu_relax(); @@ -152,31 +142,35 @@ join :: proc(t: ^Thread) { // We do this instead because I don't know if there is a danger // that you may join a different thread from the one you called join on, // if the thread handle is reused. - if sync.atomic_load(&t.done, .Sequentially_Consistent) { + if intrinsics.atomic_load(&t.done) { return; } ret_val: rawptr; _ = unix.pthread_join(t.unix_thread, &ret_val); - if !sync.atomic_load(&t.done, .Sequentially_Consistent) { + if !intrinsics.atomic_load(&t.done) { panic("thread not done after join"); } } -join_multiple :: proc(threads: ..^Thread) { +_join_multiple :: proc(threads: ..^Thread) { for t in threads { - join(t); + _join(t); } } -destroy :: proc(t: ^Thread) { - join(t); +_destroy :: proc(t: ^Thread) { + _join(t); t.unix_thread = {}; free(t, t.creation_allocator); } -yield :: proc() { +_terminate :: proc(t: ^Thread, exit_code: int) { + // TODO(bill) +} + +_yield :: proc() { unix.sched_yield(); } diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index 27a14c7f6..b44ec8f36 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -1,3 +1,5 @@ +//+build windows +//+private package thread import "core:runtime" @@ -10,20 +12,13 @@ Thread_Os_Specific :: struct { done: bool, // see note in `is_done` } - -Thread_Priority :: enum { - Normal, - Low, - High, -} - _thread_priority_map := [Thread_Priority]i32{ .Normal = 0, .Low = -2, .High = +2, }; -create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread { +_create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread { win32_thread_id: win32.DWORD; __windows_thread_entry_proc :: proc "stdcall" (t_: rawptr) -> win32.DWORD { @@ -70,18 +65,18 @@ create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^T return thread; } -start :: proc(using thread: ^Thread) { - win32.ResumeThread(win32_thread); +_start :: proc(thread: ^Thread) { + win32.ResumeThread(thread.win32_thread); } -is_done :: proc(using thread: ^Thread) -> bool { +_is_done :: proc(using thread: ^Thread) -> bool { // NOTE(tetra, 2019-10-31): Apparently using wait_for_single_object and // checking if it didn't time out immediately, is not good enough, // so we do it this way instead. return sync.atomic_load(&done, .Sequentially_Consistent); } -join :: proc(using thread: ^Thread) { +_join :: proc(using thread: ^Thread) { if win32_thread != win32.INVALID_HANDLE { win32.WaitForSingleObject(win32_thread, win32.INFINITE); win32.CloseHandle(win32_thread); @@ -89,7 +84,7 @@ join :: proc(using thread: ^Thread) { } } -join_multiple :: proc(threads: ..^Thread) { +_join_multiple :: proc(threads: ..^Thread) { MAXIMUM_WAIT_OBJECTS :: 64; handles: [MAXIMUM_WAIT_OBJECTS]win32.HANDLE; @@ -113,16 +108,16 @@ join_multiple :: proc(threads: ..^Thread) { } } -destroy :: proc(thread: ^Thread) { - join(thread); +_destroy :: proc(thread: ^Thread) { + _join(thread); free(thread, thread.creation_allocator); } -terminate :: proc(using thread : ^Thread, exit_code: u32) { - win32.TerminateThread(win32_thread, exit_code); +_terminate :: proc(using thread : ^Thread, exit_code: int) { + win32.TerminateThread(win32_thread, u32(exit_code)); } -yield :: proc() { +_yield :: proc() { win32.SwitchToThread(); } From e8bf1f2064da8e4c7811e4f8d94a8c05fd848efe Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 11 Apr 2021 18:59:54 +0100 Subject: [PATCH 07/36] Minor fixes to platform checking code --- src/build_settings.cpp | 1 + src/llvm_backend.cpp | 10 ++++++++++ src/main.cpp | 26 +++++++++++++------------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index f55bc631e..6d32d1c8d 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -127,6 +127,7 @@ char const *odin_command_strings[32] = { "query", "doc", "version", + "test", }; diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index b6f2b2d68..eb4f3c300 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -8988,6 +8988,16 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv, ); GB_ASSERT(the_asm != nullptr); LLVMBuildCall2(p->builder, func_type, the_asm, nullptr, 0, ""); + } else if (build_context.metrics.arch == TargetArch_arm64) { + LLVMTypeRef func_type = LLVMFunctionType(LLVMVoidTypeInContext(p->module->ctx), nullptr, 0, false); + LLVMValueRef the_asm = LLVMGetInlineAsm(func_type, + cast(char *)"yield", 5, + cast(char *)"", 0, + /*HasSideEffects*/true, /*IsAlignStack*/false, + LLVMInlineAsmDialectATT + ); + GB_ASSERT(the_asm != nullptr); + LLVMBuildCall2(p->builder, func_type, the_asm, nullptr, 0, ""); } return {}; diff --git a/src/main.cpp b/src/main.cpp index e6748f170..b52f970a2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -273,7 +273,7 @@ i32 linker_stage(lbGenerator *gen) { LIT(output_base), LIT(build_context.resource_filepath) ); - + if(result == 0) { result = system_exec_command_line_app("msvc-link", "\"%.*slink.exe\" %s \"%.*s.res\" -OUT:\"%.*s.%s\" %s " @@ -425,7 +425,7 @@ i32 linker_stage(lbGenerator *gen) { output_ext = substring(build_context.out_filepath, pos, build_context.out_filepath.len); } } - + result = system_exec_command_line_app("ld-link", "%s %s -o \"%.*s%.*s\" %s " " %s " @@ -467,7 +467,7 @@ i32 linker_stage(lbGenerator *gen) { #endif } - + return result; } #endif @@ -696,7 +696,7 @@ bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_Collection, str_lit("collection"), BuildFlagParam_String, Command__does_check); add_flag(&build_flags, BuildFlag_Define, str_lit("define"), BuildFlagParam_String, Command__does_check, true); add_flag(&build_flags, BuildFlag_BuildMode, str_lit("build-mode"), BuildFlagParam_String, Command__does_build); // Commands_build is not used to allow for a better error message - add_flag(&build_flags, BuildFlag_Target, str_lit("target"), BuildFlagParam_String, Command__does_build); + add_flag(&build_flags, BuildFlag_Target, str_lit("target"), BuildFlagParam_String, Command__does_check); add_flag(&build_flags, BuildFlag_Debug, str_lit("debug"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_DisableAssert, str_lit("disable-assert"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_NoBoundsCheck, str_lit("no-bounds-check"), BuildFlagParam_None, Command__does_check); @@ -2349,11 +2349,11 @@ int main(int arg_count, char const **arg_ptr) { LIT(output_base), LIT(build_context.resource_filepath) ); - + if(result != 0) { return 1; } - + result = system_exec_command_line_app("msvc-link", "\"%.*slink.exe\" \"%.*s.obj\" \"%.*s.res\" -OUT:\"%.*s.%s\" %s " "/nologo /incremental:no /opt:ref /subsystem:%s " @@ -2368,11 +2368,11 @@ int main(int arg_count, char const **arg_ptr) { LIT(build_context.extra_linker_flags), lib_str ); - + if(result != 0) { return 1; } - + } else { i32 result = system_exec_command_line_app("msvc-link", "\"%.*slink.exe\" \"%.*s.obj\" -OUT:\"%.*s.%s\" %s " @@ -2388,7 +2388,7 @@ int main(int arg_count, char const **arg_ptr) { LIT(build_context.extra_linker_flags), lib_str ); - + if(result != 0) { return 1; } @@ -2409,7 +2409,7 @@ int main(int arg_count, char const **arg_ptr) { LIT(build_context.extra_linker_flags), lib_str ); - + if(result != 0) { return 1; } @@ -2547,11 +2547,11 @@ int main(int arg_count, char const **arg_ptr) { LIT(build_context.link_flags), LIT(build_context.extra_linker_flags), link_settings); - + if(result != 0) { return 1; } - + #if defined(GB_SYSTEM_OSX) if (build_context.ODIN_DEBUG) { // NOTE: macOS links DWARF symbols dynamically. Dsymutil will map the stubs in the exe @@ -2578,6 +2578,6 @@ int main(int arg_count, char const **arg_ptr) { #endif } } - + return 0; } From e3ee005404ee9ab9308c3fe08b81610d59eec8de Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 11 Apr 2021 19:05:01 +0100 Subject: [PATCH 08/36] Clean up path_unix.odin to make it not depend on `package os` --- core/path/filepath/path_unix.odin | 44 ++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/core/path/filepath/path_unix.odin b/core/path/filepath/path_unix.odin index 8db5064d2..a1e16a3e9 100644 --- a/core/path/filepath/path_unix.odin +++ b/core/path/filepath/path_unix.odin @@ -1,8 +1,13 @@ //+build linux, darwin, freebsd package filepath +when ODIN_OS == "darwin" { + foreign import libc "System.framework" +} else { + foreign import libc "system:c" +} + import "core:strings" -import "core:os" SEPARATOR :: '/'; SEPARATOR_STRING :: `/`; @@ -17,11 +22,20 @@ is_abs :: proc(path: string) -> bool { } abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { - full_path, err := os.absolute_path_from_relative(path); - if err != os.ERROR_NONE { - return "", false; + rel := path; + if rel == "" { + rel = "."; } - return full_path, true; + rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator); + path_ptr := realpath(rel_cstr, nil); + if path_ptr == nil { + return "", __error()^ == 0; + } + defer _unix_free(path_ptr); + + path_cstr := cstring(path_ptr); + path = strings.clone(string(path_cstr), allocator); + return path, true; } join :: proc(elems: ..string, allocator := context.allocator) -> string { @@ -32,4 +46,22 @@ join :: proc(elems: ..string, allocator := context.allocator) -> string { } } return ""; -} \ No newline at end of file +} + +@(private) +foreign libc { + realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- + @(link_name="free") _unix_free :: proc(ptr: rawptr) --- + +} +when ODIN_OS == "darwin" { + @(private) + foreign libc { + @(link_name="__error") __error :: proc() -> ^i32 --- + } +} else { + @(private) + foreign libc { + @(link_name="__errno_location") __error :: proc() -> ^i32 --- + } +} From 4fb4ada2c72233de59dbf708bcd3a6ffcfec3953 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 12 Apr 2021 15:22:40 +0100 Subject: [PATCH 09/36] Update sync2 to just use atomic intrinsics rather than the parapoly wrappers --- core/os/stat_unix.odin | 37 +++- core/sync/sync2/atomic.odin | 223 +++++++---------------- core/sync/sync2/channel.odin | 13 +- core/sync/sync2/channel_windows.odin | 11 +- core/sync/sync2/extended.odin | 29 ++- core/sync/sync2/primitives_atomic.odin | 55 +++--- core/sync/sync2/primitives_pthreads.odin | 25 ++- 7 files changed, 163 insertions(+), 230 deletions(-) diff --git a/core/os/stat_unix.odin b/core/os/stat_unix.odin index fa013cefb..b1f427828 100644 --- a/core/os/stat_unix.odin +++ b/core/os/stat_unix.odin @@ -2,7 +2,6 @@ package os import "core:time" -import "core:path" /* For reference @@ -71,6 +70,36 @@ _fill_file_info_from_stat :: proc(fi: ^File_Info, s: OS_Stat) { fi.access_time = _make_time_from_unix_file_time(s.last_access); } + +@private +path_base :: proc(path: string) -> string { + is_separator :: proc(c: byte) -> bool { + return c == '/'; + } + + if path == "" { + return "."; + } + + path := path; + for len(path) > 0 && is_separator(path[len(path)-1]) { + path = path[:len(path)-1]; + } + + i := len(path)-1; + for i >= 0 && !is_separator(path[i]) { + i -= 1; + } + if i >= 0 { + path = path[i+1:]; + } + if path == "" { + return "/"; + } + return path; +} + + lstat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, err: Errno) { context.allocator = allocator; @@ -85,7 +114,7 @@ lstat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, e if err != ERROR_NONE { return; } - fi.name = path.base(fi.fullpath); + fi.name = path_base(fi.fullpath); return fi, ERROR_NONE; } @@ -103,7 +132,7 @@ stat :: proc(name: string, allocator := context.allocator) -> (fi: File_Info, er if err != ERROR_NONE { return; } - fi.name = path.base(fi.fullpath); + fi.name = path_base(fi.fullpath); return fi, ERROR_NONE; } @@ -121,6 +150,6 @@ fstat :: proc(fd: Handle, allocator := context.allocator) -> (fi: File_Info, err if err != ERROR_NONE { return; } - fi.name = path.base(fi.fullpath); + fi.name = path_base(fi.fullpath); return fi, ERROR_NONE; } diff --git a/core/sync/sync2/atomic.odin b/core/sync/sync2/atomic.odin index 8240c0fcd..1f8e2f3a8 100644 --- a/core/sync/sync2/atomic.odin +++ b/core/sync/sync2/atomic.odin @@ -4,167 +4,76 @@ import "intrinsics" // TODO(bill): Is this even a good design? The intrinsics seem to be more than good enough and just as clean -Ordering :: enum { - Relaxed, // Monotonic - Release, - Acquire, - Acquire_Release, - Sequentially_Consistent, -} +cpu_relax :: intrinsics.cpu_relax; -strongest_failure_ordering_table := [Ordering]Ordering{ - .Relaxed = .Relaxed, - .Release = .Relaxed, - .Acquire = .Acquire, - .Acquire_Release = .Acquire, - .Sequentially_Consistent = .Sequentially_Consistent, -}; +atomic_fence :: intrinsics.atomic_fence; +atomic_fence_acq :: intrinsics.atomic_fence_acq; +atomic_fence_rel :: intrinsics.atomic_fence_rel; +atomic_fence_acqrel :: intrinsics.atomic_fence_acqrel; -strongest_failure_ordering :: #force_inline proc(order: Ordering) -> Ordering { - return strongest_failure_ordering_table[order]; -} +atomic_store :: intrinsics.atomic_store; +atomic_store_rel :: intrinsics.atomic_store_rel; +atomic_store_relaxed :: intrinsics.atomic_store_relaxed; +atomic_store_unordered :: intrinsics.atomic_store_unordered; -fence :: #force_inline proc($order: Ordering) { - when order == .Relaxed { #panic("there is no such thing as a relaxed fence"); } - else when order == .Release { intrinsics.atomic_fence_rel(); } - else when order == .Acquire { intrinsics.atomic_fence_acq(); } - else when order == .Acquire_Release { intrinsics.atomic_fence_acqrel(); } - else when order == .Sequentially_Consistent { intrinsics.atomic_fence(); } - else { #panic("unknown order"); } -} +atomic_load :: intrinsics.atomic_load; +atomic_load_acq :: intrinsics.atomic_load_acq; +atomic_load_relaxed :: intrinsics.atomic_load_relaxed; +atomic_load_unordered :: intrinsics.atomic_load_unordered; +atomic_add :: intrinsics.atomic_add; +atomic_add_acq :: intrinsics.atomic_add_acq; +atomic_add_rel :: intrinsics.atomic_add_rel; +atomic_add_acqrel :: intrinsics.atomic_add_acqrel; +atomic_add_relaxed :: intrinsics.atomic_add_relaxed; +atomic_sub :: intrinsics.atomic_sub; +atomic_sub_acq :: intrinsics.atomic_sub_acq; +atomic_sub_rel :: intrinsics.atomic_sub_rel; +atomic_sub_acqrel :: intrinsics.atomic_sub_acqrel; +atomic_sub_relaxed :: intrinsics.atomic_sub_relaxed; +atomic_and :: intrinsics.atomic_and; +atomic_and_acq :: intrinsics.atomic_and_acq; +atomic_and_rel :: intrinsics.atomic_and_rel; +atomic_and_acqrel :: intrinsics.atomic_and_acqrel; +atomic_and_relaxed :: intrinsics.atomic_and_relaxed; +atomic_nand :: intrinsics.atomic_nand; +atomic_nand_acq :: intrinsics.atomic_nand_acq; +atomic_nand_rel :: intrinsics.atomic_nand_rel; +atomic_nand_acqrel :: intrinsics.atomic_nand_acqrel; +atomic_nand_relaxed :: intrinsics.atomic_nand_relaxed; +atomic_or :: intrinsics.atomic_or; +atomic_or_acq :: intrinsics.atomic_or_acq; +atomic_or_rel :: intrinsics.atomic_or_rel; +atomic_or_acqrel :: intrinsics.atomic_or_acqrel; +atomic_or_relaxed :: intrinsics.atomic_or_relaxed; +atomic_xor :: intrinsics.atomic_xor; +atomic_xor_acq :: intrinsics.atomic_xor_acq; +atomic_xor_rel :: intrinsics.atomic_xor_rel; +atomic_xor_acqrel :: intrinsics.atomic_xor_acqrel; +atomic_xor_relaxed :: intrinsics.atomic_xor_relaxed; -atomic_store :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) { - when order == .Relaxed { intrinsics.atomic_store_relaxed(dst, val); } - else when order == .Release { intrinsics.atomic_store_rel(dst, val); } - else when order == .Sequentially_Consistent { intrinsics.atomic_store(dst, val); } - else when order == .Acquire { #panic("there is not such thing as an acquire store"); } - else when order == .Acquire_Release { #panic("there is not such thing as an acquire/release store"); } - else { #panic("unknown order"); } -} +atomic_xchg :: intrinsics.atomic_xchg; +atomic_xchg_acq :: intrinsics.atomic_xchg_acq; +atomic_xchg_rel :: intrinsics.atomic_xchg_rel; +atomic_xchg_acqrel :: intrinsics.atomic_xchg_acqrel; +atomic_xchg_relaxed :: intrinsics.atomic_xchg_relaxed; -atomic_load :: #force_inline proc(dst: ^$T, $order: Ordering) -> T { - when order == .Relaxed { return intrinsics.atomic_load_relaxed(dst); } - else when order == .Acquire { return intrinsics.atomic_load_acq(dst); } - else when order == .Sequentially_Consistent { return intrinsics.atomic_load(dst); } - else when order == .Release { #panic("there is no such thing as a release load"); } - else when order == .Acquire_Release { #panic("there is no such thing as an acquire/release load"); } - else { #panic("unknown order"); } -} - -atomic_exchange :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) -> T { - when order == .Relaxed { return intrinsics.atomic_xchg_relaxed(dst, val); } - else when order == .Release { return intrinsics.atomic_xchg_rel(dst, val); } - else when order == .Acquire { return intrinsics.atomic_xchg_acq(dst, val); } - else when order == .Acquire_Release { return intrinsics.atomic_xchg_acqrel(dst, val); } - else when order == .Sequentially_Consistent { return intrinsics.atomic_xchg(dst, val); } - else { #panic("unknown order"); } -} - -atomic_compare_exchange :: #force_inline proc(dst: ^$T, old, new: T, $success, $failure: Ordering) -> (val: T, ok: bool) { - when failure == .Relaxed { - when success == .Relaxed { return intrinsics.atomic_cxchg_relaxed(dst, old, new); } - else when success == .Acquire { return intrinsics.atomic_cxchg_acq_failrelaxed(dst, old, new); } - else when success == .Acquire_Release { return intrinsics.atomic_cxchg_acqrel_failrelaxed(dst, old, new); } - else when success == .Sequentially_Consistent { return intrinsics.atomic_cxchg_failrelaxed(dst, old, new); } - else when success == .Release { return intrinsics.atomic_cxchg_rel(dst, old, new); } - else { #panic("an unknown ordering combination"); } - } else when failure == .Acquire { - when success == .Release { return intrinsics.atomic_cxchg_acqrel(dst, old, new); } - else when success == .Acquire { return intrinsics.atomic_cxchg_acq(dst, old, new); } - else { #panic("an unknown ordering combination"); } - } else when failure == .Sequentially_Consistent { - when success == .Sequentially_Consistent { return intrinsics.atomic_cxchg(dst, old, new); } - else { #panic("an unknown ordering combination"); } - } else when failure == .Acquire_Release { - #panic("there is not such thing as an acquire/release failure ordering"); - } else when failure == .Release { - when success == .Acquire { return instrinsics.atomic_cxchg_failacq(dst, old, new); } - else { #panic("an unknown ordering combination"); } - } else { - return T{}, false; - } - -} - -atomic_compare_exchange_weak :: #force_inline proc(dst: ^$T, old, new: T, $success, $failure: Ordering) -> (val: T, ok: bool) { - when failure == .Relaxed { - when success == .Relaxed { return intrinsics.atomic_cxchgweak_relaxed(dst, old, new); } - else when success == .Acquire { return intrinsics.atomic_cxchgweak_acq_failrelaxed(dst, old, new); } - else when success == .Acquire_Release { return intrinsics.atomic_cxchgweak_acqrel_failrelaxed(dst, old, new); } - else when success == .Sequentially_Consistent { return intrinsics.atomic_cxchgweak_failrelaxed(dst, old, new); } - else when success == .Release { return intrinsics.atomic_cxchgweak_rel(dst, old, new); } - else { #panic("an unknown ordering combination"); } - } else when failure == .Acquire { - when success == .Release { return intrinsics.atomic_cxchgweak_acqrel(dst, old, new); } - else when success == .Acquire { return intrinsics.atomic_cxchgweak_acq(dst, old, new); } - else { #panic("an unknown ordering combination"); } - } else when failure == .Sequentially_Consistent { - when success == .Sequentially_Consistent { return intrinsics.atomic_cxchgweak(dst, old, new); } - else { #panic("an unknown ordering combination"); } - } else when failure == .Acquire_Release { - #panic("there is not such thing as an acquire/release failure ordering"); - } else when failure == .Release { - when success == .Acquire { return intrinsics.atomic_cxchgweak_failacq(dst, old, new); } - else { #panic("an unknown ordering combination"); } - } else { - return T{}, false; - } - -} - - -atomic_add :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) -> T { - when order == .Relaxed { return intrinsics.atomic_add_relaxed(dst, val); } - else when order == .Release { return intrinsics.atomic_add_rel(dst, val); } - else when order == .Acquire { return intrinsics.atomic_add_acq(dst, val); } - else when order == .Acquire_Release { return intrinsics.atomic_add_acqrel(dst, val); } - else when order == .Sequentially_Consistent { return intrinsics.atomic_add(dst, val); } - else { #panic("unknown order"); } -} - -atomic_sub :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) -> T { - when order == .Relaxed { return intrinsics.atomic_sub_relaxed(dst, val); } - else when order == .Release { return intrinsics.atomic_sub_rel(dst, val); } - else when order == .Acquire { return intrinsics.atomic_sub_acq(dst, val); } - else when order == .Acquire_Release { return intrinsics.atomic_sub_acqrel(dst, val); } - else when order == .Sequentially_Consistent { return intrinsics.atomic_sub(dst, val); } - else { #panic("unknown order"); } -} - -atomic_and :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) -> T { - when order == .Relaxed { return intrinsics.atomic_and_relaxed(dst, val); } - else when order == .Release { return intrinsics.atomic_and_rel(dst, val); } - else when order == .Acquire { return intrinsics.atomic_and_acq(dst, val); } - else when order == .Acquire_Release { return intrinsics.atomic_and_acqrel(dst, val); } - else when order == .Sequentially_Consistent { return intrinsics.atomic_and(dst, val); } - else { #panic("unknown order"); } -} - -atomic_nand :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) -> T { - when order == .Relaxed { return intrinsics.atomic_nand_relaxed(dst, val); } - else when order == .Release { return intrinsics.atomic_nand_rel(dst, val); } - else when order == .Acquire { return intrinsics.atomic_nand_acq(dst, val); } - else when order == .Acquire_Release { return intrinsics.atomic_nand_acqrel(dst, val); } - else when order == .Sequentially_Consistent { return intrinsics.atomic_nand(dst, val); } - else { #panic("unknown order"); } -} - -atomic_or :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) -> T { - when order == .Relaxed { return intrinsics.atomic_or_relaxed(dst, val); } - else when order == .Release { return intrinsics.atomic_or_rel(dst, val); } - else when order == .Acquire { return intrinsics.atomic_or_acq(dst, val); } - else when order == .Acquire_Release { return intrinsics.atomic_or_acqrel(dst, val); } - else when order == .Sequentially_Consistent { return intrinsics.atomic_or(dst, val); } - else { #panic("unknown order"); } -} - -atomic_xor :: #force_inline proc(dst: ^$T, val: T, $order: Ordering) -> T { - when order == .Relaxed { return intrinsics.atomic_xor_relaxed(dst, val); } - else when order == .Release { return intrinsics.atomic_xor_rel(dst, val); } - else when order == .Acquire { return intrinsics.atomic_xor_acq(dst, val); } - else when order == .Acquire_Release { return intrinsics.atomic_xor_acqrel(dst, val); } - else when order == .Sequentially_Consistent { return intrinsics.atomic_xor(dst, val); } - else { #panic("unknown order"); } -} +atomic_cxchg :: intrinsics.atomic_cxchg; +atomic_cxchg_acq :: intrinsics.atomic_cxchg_acq; +atomic_cxchg_rel :: intrinsics.atomic_cxchg_rel; +atomic_cxchg_acqrel :: intrinsics.atomic_cxchg_acqrel; +atomic_cxchg_relaxed :: intrinsics.atomic_cxchg_relaxed; +atomic_cxchg_failrelaxed :: intrinsics.atomic_cxchg_failrelaxed; +atomic_cxchg_failacq :: intrinsics.atomic_cxchg_failacq; +atomic_cxchg_acq_failrelaxed :: intrinsics.atomic_cxchg_acq_failrelaxed; +atomic_cxchg_acqrel_failrelaxed :: intrinsics.atomic_cxchg_acqrel_failrelaxed; +atomic_cxchgweak :: intrinsics.atomic_cxchgweak; +atomic_cxchgweak_acq :: intrinsics.atomic_cxchgweak_acq; +atomic_cxchgweak_rel :: intrinsics.atomic_cxchgweak_rel; +atomic_cxchgweak_acqrel :: intrinsics.atomic_cxchgweak_acqrel; +atomic_cxchgweak_relaxed :: intrinsics.atomic_cxchgweak_relaxed; +atomic_cxchgweak_failrelaxed :: intrinsics.atomic_cxchgweak_failrelaxed; +atomic_cxchgweak_failacq :: intrinsics.atomic_cxchgweak_failacq; +atomic_cxchgweak_acq_failrelaxed :: intrinsics.atomic_cxchgweak_acq_failrelaxed; +atomic_cxchgweak_acqrel_failrelaxed :: intrinsics.atomic_cxchgweak_acqrel_failrelaxed; diff --git a/core/sync/sync2/channel.odin b/core/sync/sync2/channel.odin index 782b1d86a..fc30d8280 100644 --- a/core/sync/sync2/channel.odin +++ b/core/sync/sync2/channel.odin @@ -5,7 +5,6 @@ package sync2 import "core:mem" import "core:time" -import "intrinsics" import "core:math/rand" _, _ :: time, rand; @@ -136,10 +135,10 @@ channel_peek :: proc(ch: $C/Channel($T, $D)) -> int { if c == nil { return -1; } - if intrinsics.atomic_load(&c.closed) { + if atomic_load(&c.closed) { return -1; } - return intrinsics.atomic_load(&c.len); + return atomic_load(&c.len); } @@ -238,7 +237,7 @@ raw_channel_destroy :: proc(c: ^Raw_Channel) { return; } context.allocator = c.allocator; - intrinsics.atomic_store(&c.closed, true); + atomic_store(&c.closed, true); free(c); } @@ -248,7 +247,7 @@ raw_channel_close :: proc(c: ^Raw_Channel, loc := #caller_location) { } mutex_lock(&c.mutex); defer mutex_unlock(&c.mutex); - intrinsics.atomic_store(&c.closed, true); + atomic_store(&c.closed, true); // Release readers and writers raw_channel_wait_queue_broadcast(c.recvq); @@ -317,12 +316,12 @@ raw_channel_recv_impl :: proc(c: ^Raw_Channel, res: rawptr, loc := #caller_locat if c == nil { panic(message="cannot recv message; channel is nil", loc=loc); } - intrinsics.atomic_store(&c.ready, true); + atomic_store(&c.ready, true); for c.len < 1 { raw_channel_wait_queue_signal(c.sendq); cond_wait(&c.cond, &c.mutex); } - intrinsics.atomic_store(&c.ready, false); + atomic_store(&c.ready, false); recv(c, res, loc); if c.cap > 0 { if c.len == c.cap - 1 { diff --git a/core/sync/sync2/channel_windows.odin b/core/sync/sync2/channel_windows.odin index a38a9cc2c..e365506c8 100644 --- a/core/sync/sync2/channel_windows.odin +++ b/core/sync/sync2/channel_windows.odin @@ -2,7 +2,6 @@ //+private package sync2 -import "intrinsics" import win32 "core:sys/windows" import "core:time" @@ -12,24 +11,24 @@ raw_channel_wait_queue_wait_on :: proc(state: ^uintptr, timeout: time.Duration) ms = win32.DWORD((max(time.duration_nanoseconds(timeout), 0) + 999999)/1000000); } - v := intrinsics.atomic_load(state); + v := atomic_load(state); for v == 0 { win32.WaitOnAddress(state, &v, size_of(state^), ms); - v = intrinsics.atomic_load(state); + v = atomic_load(state); } - intrinsics.atomic_store(state, 0); + atomic_store(state, 0); } raw_channel_wait_queue_signal :: proc(q: ^Raw_Channel_Wait_Queue) { for x := q; x != nil; x = x.next { - intrinsics.atomic_add(x.state, 1); + atomic_add(x.state, 1); win32.WakeByAddressSingle(x.state); } } raw_channel_wait_queue_broadcast :: proc(q: ^Raw_Channel_Wait_Queue) { for x := q; x != nil; x = x.next { - intrinsics.atomic_add(x.state, 1); + atomic_add(x.state, 1); win32.WakeByAddressAll(x.state); } } diff --git a/core/sync/sync2/extended.odin b/core/sync/sync2/extended.odin index f9216d116..70a2e8011 100644 --- a/core/sync/sync2/extended.odin +++ b/core/sync/sync2/extended.odin @@ -1,7 +1,6 @@ package sync2 import "core:runtime" -import "intrinsics" // A Wait_Group waits for a collection of threads to finish // @@ -20,7 +19,7 @@ wait_group_add :: proc(wg: ^Wait_Group, delta: int) { mutex_lock(&wg.mutex); defer mutex_unlock(&wg.mutex); - intrinsics.atomic_add(&wg.counter, delta); + atomic_add(&wg.counter, delta); if wg.counter < 0 { panic("sync.Wait_Group negative counter"); } @@ -130,14 +129,14 @@ Ticket_Mutex :: struct { } ticket_mutex_lock :: #force_inline proc(m: ^Ticket_Mutex) { - ticket := intrinsics.atomic_add_relaxed(&m.ticket, 1); - for ticket != intrinsics.atomic_load_acq(&m.serving) { - intrinsics.cpu_relax(); + ticket := atomic_add_relaxed(&m.ticket, 1); + for ticket != atomic_load_acq(&m.serving) { + cpu_relax(); } } ticket_mutex_unlock :: #force_inline proc(m: ^Ticket_Mutex) { - intrinsics.atomic_add_relaxed(&m.serving, 1); + atomic_add_relaxed(&m.serving, 1); } @@ -148,18 +147,18 @@ Benaphore :: struct { } benaphore_lock :: proc(b: ^Benaphore) { - if intrinsics.atomic_add_acq(&b.counter, 1) > 1 { + if atomic_add_acq(&b.counter, 1) > 1 { sema_wait(&b.sema); } } benaphore_try_lock :: proc(b: ^Benaphore) -> bool { - v, _ := intrinsics.atomic_cxchg_acq(&b.counter, 1, 0); + v, _ := atomic_cxchg_acq(&b.counter, 1, 0); return v == 0; } benaphore_unlock :: proc(b: ^Benaphore) { - if intrinsics.atomic_sub_rel(&b.counter, 1) > 0 { + if atomic_sub_rel(&b.counter, 1) > 0 { sema_post(&b.sema); } } @@ -173,7 +172,7 @@ Recursive_Benaphore :: struct { recursive_benaphore_lock :: proc(b: ^Recursive_Benaphore) { tid := runtime.current_thread_id(); - if intrinsics.atomic_add_acq(&b.counter, 1) > 1 { + if atomic_add_acq(&b.counter, 1) > 1 { if tid != b.owner { sema_wait(&b.sema); } @@ -186,10 +185,10 @@ recursive_benaphore_lock :: proc(b: ^Recursive_Benaphore) { recursive_benaphore_try_lock :: proc(b: ^Recursive_Benaphore) -> bool { tid := runtime.current_thread_id(); if b.owner == tid { - intrinsics.atomic_add_acq(&b.counter, 1); + atomic_add_acq(&b.counter, 1); } - if v, _ := intrinsics.atomic_cxchg_acq(&b.counter, 1, 0); v != 0 { + if v, _ := atomic_cxchg_acq(&b.counter, 1, 0); v != 0 { return false; } // inside the lock @@ -206,7 +205,7 @@ recursive_benaphore_unlock :: proc(b: ^Recursive_Benaphore) { if recursion == 0 { b.owner = 0; } - if intrinsics.atomic_sub_rel(&b.counter, 1) > 0 { + if atomic_sub_rel(&b.counter, 1) > 0 { if recursion == 0 { sema_post(&b.sema); } @@ -224,7 +223,7 @@ Once :: struct { } once_do :: proc(o: ^Once, fn: proc()) { - if intrinsics.atomic_load_acq(&o.done) == false { + if atomic_load_acq(&o.done) == false { _once_do_slow(o, fn); } } @@ -234,6 +233,6 @@ _once_do_slow :: proc(o: ^Once, fn: proc()) { defer mutex_unlock(&o.m); if !o.done { fn(); - intrinsics.atomic_store_rel(&o.done, true); + atomic_store_rel(&o.done, true); } } diff --git a/core/sync/sync2/primitives_atomic.odin b/core/sync/sync2/primitives_atomic.odin index 6133ed77b..bf47e6190 100644 --- a/core/sync/sync2/primitives_atomic.odin +++ b/core/sync/sync2/primitives_atomic.odin @@ -4,7 +4,6 @@ package sync2 when !#config(ODIN_SYNC_USE_PTHREADS, false) { -import "intrinsics" import "core:time" _Mutex_State :: enum i32 { @@ -17,13 +16,13 @@ _Mutex :: struct { } _mutex_lock :: proc(m: ^Mutex) { - if intrinsics.atomic_xchg_rel(&m.impl.state, .Unlocked) != .Unlocked { + if atomic_xchg_rel(&m.impl.state, .Unlocked) != .Unlocked { _mutex_unlock_slow(m); } } _mutex_unlock :: proc(m: ^Mutex) { - switch intrinsics.atomic_xchg_rel(&m.impl.state, .Unlocked) { + switch atomic_xchg_rel(&m.impl.state, .Unlocked) { case .Unlocked: unreachable(); case .Locked: @@ -34,7 +33,7 @@ _mutex_unlock :: proc(m: ^Mutex) { } _mutex_try_lock :: proc(m: ^Mutex) -> bool { - _, ok := intrinsics.atomic_cxchg_acq(&m.impl.state, .Unlocked, .Locked); + _, ok := atomic_cxchg_acq(&m.impl.state, .Unlocked, .Locked); return ok; } @@ -44,7 +43,7 @@ _mutex_lock_slow :: proc(m: ^Mutex, curr_state: _Mutex_State) { new_state := curr_state; // Make a copy of it spin_lock: for spin in 0.. 0; i -= 1 { - intrinsics.cpu_relax(); + cpu_relax(); } } for { - if intrinsics.atomic_xchg_acq(&m.impl.state, .Waiting) == .Unlocked { + if atomic_xchg_acq(&m.impl.state, .Waiting) == .Unlocked { return; } // TODO(bill): Use a Futex here for Linux to improve performance and error handling - intrinsics.cpu_relax(); + cpu_relax(); } } @@ -91,25 +90,25 @@ _RW_Mutex :: struct { } _rw_mutex_lock :: proc(rw: ^RW_Mutex) { - _ = intrinsics.atomic_add(&rw.impl.state, RW_Mutex_State_Writer); + _ = atomic_add(&rw.impl.state, RW_Mutex_State_Writer); mutex_lock(&rw.impl.mutex); - state := intrinsics.atomic_or(&rw.impl.state, RW_Mutex_State_Writer); + state := atomic_or(&rw.impl.state, RW_Mutex_State_Writer); if state & RW_Mutex_State_Reader_Mask != 0 { sema_wait(&rw.impl.sema); } } _rw_mutex_unlock :: proc(rw: ^RW_Mutex) { - _ = intrinsics.atomic_and(&rw.impl.state, ~RW_Mutex_State_Is_Writing); + _ = atomic_and(&rw.impl.state, ~RW_Mutex_State_Is_Writing); mutex_unlock(&rw.impl.mutex); } _rw_mutex_try_lock :: proc(rw: ^RW_Mutex) -> bool { if mutex_try_lock(&rw.impl.mutex) { - state := intrinsics.atomic_load(&rw.impl.state); + state := atomic_load(&rw.impl.state); if state & RW_Mutex_State_Reader_Mask == 0 { - _ = intrinsics.atomic_or(&rw.impl.state, RW_Mutex_State_Is_Writing); + _ = atomic_or(&rw.impl.state, RW_Mutex_State_Is_Writing); return true; } @@ -119,22 +118,22 @@ _rw_mutex_try_lock :: proc(rw: ^RW_Mutex) -> bool { } _rw_mutex_shared_lock :: proc(rw: ^RW_Mutex) { - state := intrinsics.atomic_load(&rw.impl.state); + state := atomic_load(&rw.impl.state); for state & (RW_Mutex_State_Is_Writing|RW_Mutex_State_Writer_Mask) == 0 { ok: bool; - state, ok = intrinsics.atomic_cxchgweak(&rw.impl.state, state, state + RW_Mutex_State_Reader); + state, ok = atomic_cxchgweak(&rw.impl.state, state, state + RW_Mutex_State_Reader); if ok { return; } } mutex_lock(&rw.impl.mutex); - _ = intrinsics.atomic_add(&rw.impl.state, RW_Mutex_State_Reader); + _ = atomic_add(&rw.impl.state, RW_Mutex_State_Reader); mutex_unlock(&rw.impl.mutex); } _rw_mutex_shared_unlock :: proc(rw: ^RW_Mutex) { - state := intrinsics.atomic_sub(&rw.impl.state, RW_Mutex_State_Reader); + state := atomic_sub(&rw.impl.state, RW_Mutex_State_Reader); if (state & RW_Mutex_State_Reader_Mask == RW_Mutex_State_Reader) && (state & RW_Mutex_State_Is_Writing != 0) { @@ -143,15 +142,15 @@ _rw_mutex_shared_unlock :: proc(rw: ^RW_Mutex) { } _rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool { - state := intrinsics.atomic_load(&rw.impl.state); + state := atomic_load(&rw.impl.state); if state & (RW_Mutex_State_Is_Writing|RW_Mutex_State_Writer_Mask) == 0 { - _, ok := intrinsics.atomic_cxchg(&rw.impl.state, state, state + RW_Mutex_State_Reader); + _, ok := atomic_cxchg(&rw.impl.state, state, state + RW_Mutex_State_Reader); if ok { return true; } } if mutex_try_lock(&rw.impl.mutex) { - _ = intrinsics.atomic_add(&rw.impl.state, RW_Mutex_State_Reader); + _ = atomic_add(&rw.impl.state, RW_Mutex_State_Reader); mutex_unlock(&rw.impl.mutex); return true; } @@ -167,13 +166,13 @@ Queue_Item :: struct { } queue_item_wait :: proc(item: ^Queue_Item) { - for intrinsics.atomic_load_acq(&item.futex) == 0 { + for atomic_load_acq(&item.futex) == 0 { // TODO(bill): Use a Futex here for Linux to improve performance and error handling - intrinsics.cpu_relax(); + cpu_relax(); } } queue_item_signal :: proc(item: ^Queue_Item) { - intrinsics.atomic_store_rel(&item.futex, 1); + atomic_store_rel(&item.futex, 1); // TODO(bill): Use a Futex here for Linux to improve performance and error handling } @@ -191,7 +190,7 @@ _cond_wait :: proc(c: ^Cond, m: ^Mutex) { waiter.next = c.impl.queue_head; c.impl.queue_head = waiter; - intrinsics.atomic_store(&c.impl.pending, true); + atomic_store(&c.impl.pending, true); mutex_unlock(&c.impl.queue_mutex); mutex_unlock(m); @@ -205,7 +204,7 @@ _cond_wait_with_timeout :: proc(c: ^Cond, m: ^Mutex, timeout: time.Duration) -> } _cond_signal :: proc(c: ^Cond) { - if !intrinsics.atomic_load(&c.impl.pending) { + if !atomic_load(&c.impl.pending) { return; } @@ -214,7 +213,7 @@ _cond_signal :: proc(c: ^Cond) { if c.impl.queue_head != nil { c.impl.queue_head = c.impl.queue_head.next; } - intrinsics.atomic_store(&c.impl.pending, c.impl.queue_head != nil); + atomic_store(&c.impl.pending, c.impl.queue_head != nil); mutex_unlock(&c.impl.queue_mutex); if waiter != nil { @@ -223,11 +222,11 @@ _cond_signal :: proc(c: ^Cond) { } _cond_broadcast :: proc(c: ^Cond) { - if !intrinsics.atomic_load(&c.impl.pending) { + if !atomic_load(&c.impl.pending) { return; } - intrinsics.atomic_store(&c.impl.pending, false); + atomic_store(&c.impl.pending, false); mutex_lock(&c.impl.queue_mutex); waiters := c.impl.queue_head; diff --git a/core/sync/sync2/primitives_pthreads.odin b/core/sync/sync2/primitives_pthreads.odin index cb580f03f..c0cab91e4 100644 --- a/core/sync/sync2/primitives_pthreads.odin +++ b/core/sync/sync2/primitives_pthreads.odin @@ -4,7 +4,6 @@ package sync2 when #config(ODIN_SYNC_USE_PTHREADS, false) { -import "intrinsics" import "core:time" import "core:sys/unix" @@ -53,25 +52,25 @@ _RW_Mutex :: struct { } _rw_mutex_lock :: proc(rw: ^RW_Mutex) { - _ = intrinsics.atomic_add(&rw.impl.state, RW_Mutex_State_Writer); + _ = atomic_add(&rw.impl.state, RW_Mutex_State_Writer); mutex_lock(&rw.impl.mutex); - state := intrinsics.atomic_or(&rw.impl.state, RW_Mutex_State_Writer); + state := atomic_or(&rw.impl.state, RW_Mutex_State_Writer); if state & RW_Mutex_State_Reader_Mask != 0 { sema_wait(&rw.impl.sema); } } _rw_mutex_unlock :: proc(rw: ^RW_Mutex) { - _ = intrinsics.atomic_and(&rw.impl.state, ~RW_Mutex_State_Is_Writing); + _ = atomic_and(&rw.impl.state, ~RW_Mutex_State_Is_Writing); mutex_unlock(&rw.impl.mutex); } _rw_mutex_try_lock :: proc(rw: ^RW_Mutex) -> bool { if mutex_try_lock(&rw.impl.mutex) { - state := intrinsics.atomic_load(&rw.impl.state); + state := atomic_load(&rw.impl.state); if state & RW_Mutex_State_Reader_Mask == 0 { - _ = intrinsics.atomic_or(&rw.impl.state, RW_Mutex_State_Is_Writing); + _ = atomic_or(&rw.impl.state, RW_Mutex_State_Is_Writing); return true; } @@ -81,22 +80,22 @@ _rw_mutex_try_lock :: proc(rw: ^RW_Mutex) -> bool { } _rw_mutex_shared_lock :: proc(rw: ^RW_Mutex) { - state := intrinsics.atomic_load(&rw.impl.state); + state := atomic_load(&rw.impl.state); for state & (RW_Mutex_State_Is_Writing|RW_Mutex_State_Writer_Mask) == 0 { ok: bool; - state, ok = intrinsics.atomic_cxchgweak(&rw.impl.state, state, state + RW_Mutex_State_Reader); + state, ok = atomic_cxchgweak(&rw.impl.state, state, state + RW_Mutex_State_Reader); if ok { return; } } mutex_lock(&rw.impl.mutex); - _ = intrinsics.atomic_add(&rw.impl.state, RW_Mutex_State_Reader); + _ = atomic_add(&rw.impl.state, RW_Mutex_State_Reader); mutex_unlock(&rw.impl.mutex); } _rw_mutex_shared_unlock :: proc(rw: ^RW_Mutex) { - state := intrinsics.atomic_sub(&rw.impl.state, RW_Mutex_State_Reader); + state := atomic_sub(&rw.impl.state, RW_Mutex_State_Reader); if (state & RW_Mutex_State_Reader_Mask == RW_Mutex_State_Reader) && (state & RW_Mutex_State_Is_Writing != 0) { @@ -105,15 +104,15 @@ _rw_mutex_shared_unlock :: proc(rw: ^RW_Mutex) { } _rw_mutex_try_shared_lock :: proc(rw: ^RW_Mutex) -> bool { - state := intrinsics.atomic_load(&rw.impl.state); + state := atomic_load(&rw.impl.state); if state & (RW_Mutex_State_Is_Writing|RW_Mutex_State_Writer_Mask) == 0 { - _, ok := intrinsics.atomic_cxchg(&rw.impl.state, state, state + RW_Mutex_State_Reader); + _, ok := atomic_cxchg(&rw.impl.state, state, state + RW_Mutex_State_Reader); if ok { return true; } } if mutex_try_lock(&rw.impl.mutex) { - _ = intrinsics.atomic_add(&rw.impl.state, RW_Mutex_State_Reader); + _ = atomic_add(&rw.impl.state, RW_Mutex_State_Reader); mutex_unlock(&rw.impl.mutex); return true; } From 2b36069924c9fa21111fe8a8b5471e25847c52cf Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 12 Apr 2021 17:13:05 +0100 Subject: [PATCH 10/36] Fix typo --- core/slice/slice.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/slice/slice.odin b/core/slice/slice.odin index 75105a1c4..fdacb842f 100644 --- a/core/slice/slice.odin +++ b/core/slice/slice.odin @@ -281,7 +281,7 @@ reduce :: proc(s: $S/[]$U, initializer: $V, f: proc(V, U) -> V) -> V { } filter :: proc(s: $S/[]$U, f: proc(U) -> bool, allocator := context.allocator) -> S { - r := make([dynamic]S, 0, 0, allocator); + r := make([dynamic]U, 0, 0, allocator); for v in s { if f(v) { append(&r, v); From a1d871360cdfd0e2683bf17df5eaca094254282e Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 13 Apr 2021 02:09:44 +0200 Subject: [PATCH 11/36] Add support to core:windows to add/delete users. main :: proc() { using fmt; using windows; username := "testuser"; password := "testpass"; ok := add_user("", username, password); fmt.printf("add_user: %v\n", ok); pi := windows.PROCESS_INFORMATION{}; ok2, path := windows.add_user_profile(username); fmt.printf("add_user_profile: %v, %v\n", ok2, path); ok3 := windows.delete_user_profile(username); fmt.printf("delete_user_profile: %v\n", ok3); ok4 := windows.delete_user("", username); fmt.printf("delete_user: %v\n", ok4); // Has optional bool to not wait on the process before returning. b := run_as_user(username, password, "C:\\Repro\\repro.exe", "Hellope!", &pi); fmt.printf("run_as_user: %v %v\n", b, pi); } --- core/sys/windows/advapi32.odin | 54 ++++ core/sys/windows/netapi32.odin | 37 +++ core/sys/windows/types.odin | 450 ++++++++++++++++++++++++++++++++- core/sys/windows/userenv.odin | 28 ++ core/sys/windows/util.odin | 376 +++++++++++++++++++++++++++ 5 files changed, 943 insertions(+), 2 deletions(-) create mode 100644 core/sys/windows/netapi32.odin diff --git a/core/sys/windows/advapi32.odin b/core/sys/windows/advapi32.odin index b25ed6ef4..31e542e37 100644 --- a/core/sys/windows/advapi32.odin +++ b/core/sys/windows/advapi32.odin @@ -10,3 +10,57 @@ foreign advapi32 { DesiredAccess: DWORD, TokenHandle: ^HANDLE) -> BOOL --- } + +// Necessary to create a token to impersonate a user with for CreateProcessAsUser +@(default_calling_convention="stdcall") +foreign advapi32 { + LogonUserW :: proc( + lpszUsername: LPCWSTR, + lpszDomain: LPCWSTR, + lpszPassword: LPCWSTR, + dwLogonType: Logon32_Type, + dwLogonProvider: Logon32_Provider, + phToken: ^HANDLE, + ) -> BOOL --- + + // https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lookupaccountnamew + // To look up the SID to use with DeleteProfileW. + LookupAccountNameW :: proc( + lpSystemName: wstring, + lpAccountName: wstring, + Sid: ^SID, + cbSid: ^DWORD, + ReferencedDomainName: wstring, + cchReferencedDomainName: ^DWORD, + peUse: ^SID_TYPE, + ) -> BOOL --- + + CreateProcessWithLogonW :: proc( + lpUsername: wstring, + lpDomain: wstring, + lpPassword: wstring, + dwLogonFlags: DWORD, + lpApplicationName: wstring, + lpCommandLine: wstring, + dwCreationFlags: DWORD, + lpEnvironment: LPVOID, + lpCurrentDirectory: wstring, + lpStartupInfo: LPSTARTUPINFO, + lpProcessInformation: LPPROCESS_INFORMATION, + ) -> BOOL --- + + // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasuserw + CreateProcessAsUserW :: proc( + hToken: HANDLE, + lpApplicationName: wstring, + lpCommandLine: wstring, + lpProcessAttributes: LPSECURITY_ATTRIBUTES, + lpThreadAttributes: LPSECURITY_ATTRIBUTES, + bInheritHandles: BOOL, + dwCreationFlags: DWORD, + lpEnvironment: LPVOID, + lpCurrentDirectory: wstring, + lpStartupInfo: LPSTARTUPINFO, + lpProcessInformation: LPPROCESS_INFORMATION, + ) -> BOOL ---; +} \ No newline at end of file diff --git a/core/sys/windows/netapi32.odin b/core/sys/windows/netapi32.odin new file mode 100644 index 000000000..d9b41657e --- /dev/null +++ b/core/sys/windows/netapi32.odin @@ -0,0 +1,37 @@ +package sys_windows + +foreign import netapi32 "system:Netapi32.lib" + +@(default_calling_convention="stdcall") +foreign netapi32 { + NetUserAdd :: proc( + servername: wstring, + level: DWORD, + user_info: ^USER_INFO_1, // Perhaps make this a #raw_union with USER_INFO1..4 when we need the other levels. + parm_err: ^DWORD, + ) -> NET_API_STATUS ---; + NetUserDel :: proc( + servername: wstring, + username: wstring, + ) -> NET_API_STATUS ---; + NetUserGetInfo :: proc( + servername: wstring, + username: wstring, + level: DWORD, + user_info: ^USER_INFO_1, + ) -> NET_API_STATUS ---; + NetLocalGroupAddMembers :: proc( + servername: wstring, + groupname: wstring, + level: DWORD, + group_members_info: ^LOCALGROUP_MEMBERS_INFO_0, // Actually a variably sized array of these. + totalentries: DWORD, + ) -> NET_API_STATUS ---; + NetLocalGroupDelMembers :: proc( + servername: wstring, + groupname: wstring, + level: DWORD, + group_members_info: ^LOCALGROUP_MEMBERS_INFO_0, // Actually a variably sized array of these. + totalentries: DWORD, + ) -> NET_API_STATUS ---; +} \ No newline at end of file diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index f5e404863..65aa3a113 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -570,7 +570,8 @@ PROCESS_INFORMATION :: struct { dwThreadId: DWORD, } -STARTUPINFO :: struct { +// FYI: This is STARTUPINFOW, not STARTUPINFOA +STARTUPINFO :: struct #packed { cb: DWORD, lpReserved: LPWSTR, lpDesktop: LPWSTR, @@ -580,7 +581,7 @@ STARTUPINFO :: struct { dwXSize: DWORD, dwYSize: DWORD, dwXCountChars: DWORD, - dwYCountCharts: DWORD, + dwYCountChars: DWORD, dwFillAttribute: DWORD, dwFlags: DWORD, wShowWindow: WORD, @@ -788,3 +789,448 @@ OSVERSIONINFOEXW :: struct { wProductType: UCHAR, wReserved: UCHAR, }; + +// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-quota_limits +// Used in LogonUserExW +PQUOTA_LIMITS :: struct { + PagedPoolLimit: SIZE_T, + NonPagedPoolLimit: SIZE_T, + MinimumWorkingSetSize: SIZE_T, + MaximumWorkingSetSize: SIZE_T, + PagefileLimit: SIZE_T, + TimeLimit: LARGE_INTEGER, +}; + +Logon32_Type :: enum DWORD { + INTERACTIVE = 2, + NETWORK = 3, + BATCH = 4, + SERVICE = 5, + UNLOCK = 7, + NETWORK_CLEARTEXT = 8, + NEW_CREDENTIALS = 9, +} + +Logon32_Provider :: enum DWORD { + DEFAULT = 0, + WINNT35 = 1, + WINNT40 = 2, + WINNT50 = 3, + VIRTUAL = 4, +} + +// https://docs.microsoft.com/en-us/windows/win32/api/profinfo/ns-profinfo-profileinfow +// Used in LoadUserProfileW + +PROFILEINFOW :: struct { + dwSize: DWORD, + dwFlags: DWORD, + lpUserName: LPWSTR, + lpProfilePath: LPWSTR, + lpDefaultPath: LPWSTR, + lpServerName: LPWSTR, + lpPolicyPath: LPWSTR, + hProfile: HANDLE, +}; + +// Used in LookupAccountNameW +SID_NAME_USE :: distinct DWORD; + +SID_TYPE :: enum SID_NAME_USE { + User = 1, + Group, + Domain, + Alias, + WellKnownGroup, + DeletedAccount, + Invalid, + Unknown, + Computer, + Label, + LogonSession +} + +SECURITY_MAX_SID_SIZE :: 68; + +// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-sid +SID :: struct #packed { + Revision: byte, + SubAuthorityCount: byte, + IdentifierAuthority: SID_IDENTIFIER_AUTHORITY, + SubAuthority: [15]DWORD, // Array of DWORDs +}; +#assert(size_of(SID) == SECURITY_MAX_SID_SIZE); + +SID_IDENTIFIER_AUTHORITY :: struct #packed { + Value: [6]u8, +}; + +// For NetAPI32 +// https://github.com/tpn/winsdk-10/blob/master/Include/10.0.14393.0/shared/lmerr.h +// https://github.com/tpn/winsdk-10/blob/master/Include/10.0.14393.0/shared/LMaccess.h + +UNLEN :: 256; // Maximum user name length +LM20_UNLEN :: 20; // LM 2.0 Maximum user name length + +GNLEN :: UNLEN; // Group name +LM20_GNLEN :: LM20_UNLEN; // LM 2.0 Group name + +PWLEN :: 256; // Maximum password length +LM20_PWLEN :: 14; // LM 2.0 Maximum password length + +USER_PRIV :: enum DWORD { + Guest = 0, + User = 1, + Admin = 2, + Mask = 0x3, +} + +USER_INFO_FLAG :: enum DWORD { + Script = 0, // 1 << 0: 0x0001, + AccountDisable = 1, // 1 << 1: 0x0002, + HomeDir_Required = 3, // 1 << 3: 0x0008, + Lockout = 4, // 1 << 4: 0x0010, + Passwd_NotReqd = 5, // 1 << 5: 0x0020, + Passwd_Cant_Change = 6, // 1 << 6: 0x0040, + Encrypted_Text_Password_Allowed = 7, // 1 << 7: 0x0080, + + Temp_Duplicate_Account = 8, // 1 << 8: 0x0100, + Normal_Account = 9, // 1 << 9: 0x0200, + InterDomain_Trust_Account = 11, // 1 << 11: 0x0800, + Workstation_Trust_Account = 12, // 1 << 12: 0x1000, + Server_Trust_Account = 13, // 1 << 13: 0x2000, +} +USER_INFO_FLAGS :: distinct bit_set[USER_INFO_FLAG]; + +USER_INFO_1 :: struct #packed { + name: LPWSTR, + password: LPWSTR, // Max password length is defined in LM20_PWLEN. + password_age: DWORD, + priv: USER_PRIV, + home_dir: LPWSTR, + comment: LPWSTR, + flags: USER_INFO_FLAGS, + script_path: LPWSTR, +}; +#assert(size_of(USER_INFO_1) == 50); + +LOCALGROUP_MEMBERS_INFO_0 :: struct #packed { + sid: ^SID, +}; + +NET_API_STATUS :: enum DWORD { + Success = 0, + ERROR_ACCESS_DENIED = 5, + MemberInAlias = 1378, + NetNotStarted = 2102, + UnknownServer = 2103, + ShareMem = 2104, + NoNetworkResource = 2105, + RemoteOnly = 2106, + DevNotRedirected = 2107, + ServerNotStarted = 2114, + ItemNotFound = 2115, + UnknownDevDir = 2116, + RedirectedPath = 2117, + DuplicateShare = 2118, + NoRoom = 2119, + TooManyItems = 2121, + InvalidMaxUsers = 2122, + BufTooSmall = 2123, + RemoteErr = 2127, + LanmanIniError = 2131, + NetworkError = 2136, + WkstaInconsistentState = 2137, + WkstaNotStarted = 2138, + BrowserNotStarted = 2139, + InternalError = 2140, + BadTransactConfig = 2141, + InvalidAPI = 2142, + BadEventName = 2143, + DupNameReboot = 2144, + CfgCompNotFound = 2146, + CfgParamNotFound = 2147, + LineTooLong = 2149, + QNotFound = 2150, + JobNotFound = 2151, + DestNotFound = 2152, + DestExists = 2153, + QExists = 2154, + QNoRoom = 2155, + JobNoRoom = 2156, + DestNoRoom = 2157, + DestIdle = 2158, + DestInvalidOp = 2159, + ProcNoRespond = 2160, + SpoolerNotLoaded = 2161, + DestInvalidState = 2162, + QInvalidState = 2163, + JobInvalidState = 2164, + SpoolNoMemory = 2165, + DriverNotFound = 2166, + DataTypeInvalid = 2167, + ProcNotFound = 2168, + ServiceTableLocked = 2180, + ServiceTableFull = 2181, + ServiceInstalled = 2182, + ServiceEntryLocked = 2183, + ServiceNotInstalled = 2184, + BadServiceName = 2185, + ServiceCtlTimeout = 2186, + ServiceCtlBusy = 2187, + BadServiceProgName = 2188, + ServiceNotCtrl = 2189, + ServiceKillProc = 2190, + ServiceCtlNotValid = 2191, + NotInDispatchTbl = 2192, + BadControlRecv = 2193, + ServiceNotStarting = 2194, + AlreadyLoggedOn = 2200, + NotLoggedOn = 2201, + BadUsername = 2202, + BadPassword = 2203, + UnableToAddName_W = 2204, + UnableToAddName_F = 2205, + UnableToDelName_W = 2206, + UnableToDelName_F = 2207, + LogonsPaused = 2209, + LogonServerConflict = 2210, + LogonNoUserPath = 2211, + LogonScriptError = 2212, + StandaloneLogon = 2214, + LogonServerNotFound = 2215, + LogonDomainExists = 2216, + NonValidatedLogon = 2217, + ACFNotFound = 2219, + GroupNotFound = 2220, + UserNotFound = 2221, + ResourceNotFound = 2222, + GroupExists = 2223, + UserExists = 2224, + ResourceExists = 2225, + NotPrimary = 2226, + ACFNotLoaded = 2227, + ACFNoRoom = 2228, + ACFFileIOFail = 2229, + ACFTooManyLists = 2230, + UserLogon = 2231, + ACFNoParent = 2232, + CanNotGrowSegment = 2233, + SpeGroupOp = 2234, + NotInCache = 2235, + UserInGroup = 2236, + UserNotInGroup = 2237, + AccountUndefined = 2238, + AccountExpired = 2239, + InvalidWorkstation = 2240, + InvalidLogonHours = 2241, + PasswordExpired = 2242, + PasswordCantChange = 2243, + PasswordHistConflict = 2244, + PasswordTooShort = 2245, + PasswordTooRecent = 2246, + InvalidDatabase = 2247, + DatabaseUpToDate = 2248, + SyncRequired = 2249, + UseNotFound = 2250, + BadAsgType = 2251, + DeviceIsShared = 2252, + SameAsComputerName = 2253, + NoComputerName = 2270, + MsgAlreadyStarted = 2271, + MsgInitFailed = 2272, + NameNotFound = 2273, + AlreadyForwarded = 2274, + AddForwarded = 2275, + AlreadyExists = 2276, + TooManyNames = 2277, + DelComputerName = 2278, + LocalForward = 2279, + GrpMsgProcessor = 2280, + PausedRemote = 2281, + BadReceive = 2282, + NameInUse = 2283, + MsgNotStarted = 2284, + NotLocalName = 2285, + NoForwardName = 2286, + RemoteFull = 2287, + NameNotForwarded = 2288, + TruncatedBroadcast = 2289, + InvalidDevice = 2294, + WriteFault = 2295, + DuplicateName = 2297, + DeleteLater = 2298, + IncompleteDel = 2299, + MultipleNets = 2300, + NetNameNotFound = 2310, + DeviceNotShared = 2311, + ClientNameNotFound = 2312, + FileIdNotFound = 2314, + ExecFailure = 2315, + TmpFile = 2316, + TooMuchData = 2317, + DeviceShareConflict = 2318, + BrowserTableIncomplete = 2319, + NotLocalDomain = 2320, + IsDfsShare = 2321, + DevInvalidOpCode = 2331, + DevNotFound = 2332, + DevNotOpen = 2333, + BadQueueDevString = 2334, + BadQueuePriority = 2335, + NoCommDevs = 2337, + QueueNotFound = 2338, + BadDevString = 2340, + BadDev = 2341, + InUseBySpooler = 2342, + CommDevInUse = 2343, + InvalidComputer = 2351, + MaxLenExceeded = 2354, + BadComponent = 2356, + CantType = 2357, + TooManyEntries = 2362, + ProfileFileTooBig = 2370, + ProfileOffset = 2371, + ProfileCleanup = 2372, + ProfileUnknownCmd = 2373, + ProfileLoadErr = 2374, + ProfileSaveErr = 2375, + LogOverflow = 2377, + LogFileChanged = 2378, + LogFileCorrupt = 2379, + SourceIsDir = 2380, + BadSource = 2381, + BadDest = 2382, + DifferentServers = 2383, + RunSrvPaused = 2385, + ErrCommRunSrv = 2389, + ErrorExecingGhost = 2391, + ShareNotFound = 2392, + InvalidLana = 2400, + OpenFiles = 2401, + ActiveConns = 2402, + BadPasswordCore = 2403, + DevInUse = 2404, + LocalDrive = 2405, + AlertExists = 2430, + TooManyAlerts = 2431, + NoSuchAlert = 2432, + BadRecipient = 2433, + AcctLimitExceeded = 2434, + InvalidLogSeek = 2440, + BadUasConfig = 2450, + InvalidUASOp = 2451, + LastAdmin = 2452, + DCNotFound = 2453, + LogonTrackingError = 2454, + NetlogonNotStarted = 2455, + CanNotGrowUASFile = 2456, + TimeDiffAtDC = 2457, + PasswordMismatch = 2458, + NoSuchServer = 2460, + NoSuchSession = 2461, + NoSuchConnection = 2462, + TooManyServers = 2463, + TooManySessions = 2464, + TooManyConnections = 2465, + TooManyFiles = 2466, + NoAlternateServers = 2467, + TryDownLevel = 2470, + UPSDriverNotStarted = 2480, + UPSInvalidConfig = 2481, + UPSInvalidCommPort = 2482, + UPSSignalAsserted = 2483, + UPSShutdownFailed = 2484, + BadDosRetCode = 2500, + ProgNeedsExtraMem = 2501, + BadDosFunction = 2502, + RemoteBootFailed = 2503, + BadFileCheckSum = 2504, + NoRplBootSystem = 2505, + RplLoadrNetBiosErr = 2506, + RplLoadrDiskErr = 2507, + ImageParamErr = 2508, + TooManyImageParams = 2509, + NonDosFloppyUsed = 2510, + RplBootRestart = 2511, + RplSrvrCallFailed = 2512, + CantConnectRplSrvr = 2513, + CantOpenImageFile = 2514, + CallingRplSrvr = 2515, + StartingRplBoot = 2516, + RplBootServiceTerm = 2517, + RplBootStartFailed = 2518, + RPL_CONNECTED = 2519, + BrowserConfiguredToNotRun = 2550, + RplNoAdaptersStarted = 2610, + RplBadRegistry = 2611, + RplBadDatabase = 2612, + RplRplfilesShare = 2613, + RplNotRplServer = 2614, + RplCannotEnum = 2615, + RplWkstaInfoCorrupted = 2616, + RplWkstaNotFound = 2617, + RplWkstaNameUnavailable = 2618, + RplProfileInfoCorrupted = 2619, + RplProfileNotFound = 2620, + RplProfileNameUnavailable = 2621, + RplProfileNotEmpty = 2622, + RplConfigInfoCorrupted = 2623, + RplConfigNotFound = 2624, + RplAdapterInfoCorrupted = 2625, + RplInternal = 2626, + RplVendorInfoCorrupted = 2627, + RplBootInfoCorrupted = 2628, + RplWkstaNeedsUserAcct = 2629, + RplNeedsRPLUSERAcct = 2630, + RplBootNotFound = 2631, + RplIncompatibleProfile = 2632, + RplAdapterNameUnavailable = 2633, + RplConfigNotEmpty = 2634, + RplBootInUse = 2635, + RplBackupDatabase = 2636, + RplAdapterNotFound = 2637, + RplVendorNotFound = 2638, + RplVendorNameUnavailable = 2639, + RplBootNameUnavailable = 2640, + RplConfigNameUnavailable = 2641, + DfsInternalCorruption = 2660, + DfsVolumeDataCorrupt = 2661, + DfsNoSuchVolume = 2662, + DfsVolumeAlreadyExists = 2663, + DfsAlreadyShared = 2664, + DfsNoSuchShare = 2665, + DfsNotALeafVolume = 2666, + DfsLeafVolume = 2667, + DfsVolumeHasMultipleServers = 2668, + DfsCantCreateJunctionPoint = 2669, + DfsServerNotDfsAware = 2670, + DfsBadRenamePath = 2671, + DfsVolumeIsOffline = 2672, + DfsNoSuchServer = 2673, + DfsCyclicalName = 2674, + DfsNotSupportedInServerDfs = 2675, + DfsDuplicateService = 2676, + DfsCantRemoveLastServerShare = 2677, + DfsVolumeIsInterDfs = 2678, + DfsInconsistent = 2679, + DfsServerUpgraded = 2680, + DfsDataIsIdentical = 2681, + DfsCantRemoveDfsRoot = 2682, + DfsChildOrParentInDfs = 2683, + DfsInternalError = 2690, + SetupAlreadyJoined = 2691, + SetupNotJoined = 2692, + SetupDomainController = 2693, + DefaultJoinRequired = 2694, + InvalidWorkgroupName = 2695, + NameUsesIncompatibleCodePage = 2696, + ComputerAccountNotFound = 2697, + PersonalSku = 2698, + SetupCheckDNSConfig = 2699, + PasswordMustChange = 2701, + AccountLockedOut = 2702, + PasswordTooLong = 2703, + PasswordNotComplexEnough = 2704, + PasswordFilterError = 2705, +} \ No newline at end of file diff --git a/core/sys/windows/userenv.odin b/core/sys/windows/userenv.odin index a701b6ca6..b57ef5f2d 100644 --- a/core/sys/windows/userenv.odin +++ b/core/sys/windows/userenv.odin @@ -7,4 +7,32 @@ foreign userenv { GetUserProfileDirectoryW :: proc(hToken: HANDLE, lpProfileDir: LPWSTR, lpcchSize: ^DWORD) -> BOOL --- + LoadUserProfileW :: proc( + hToken: HANDLE, + lpProfileInfo: ^PROFILEINFOW, + ) -> BOOL --- + + // https://docs.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-createprofile + // The caller must have administrator privileges to call this function. + CreateProfile :: proc( + pszUserSid: LPCWSTR, + pszUserName: LPCWSTR, + pszProfilePath: wstring, + cchProfilePath: DWORD, + ) -> u32 --- + + // https://docs.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-deleteprofilew + // The caller must have administrative privileges to delete a user's profile. + DeleteProfileW :: proc( + lpSidString: LPCWSTR, + lpProfilePath: LPCWSTR, + lpComputerName: LPCWSTR, + ) -> BOOL --- + + // https://docs.microsoft.com/en-us/windows/win32/api/sddl/nf-sddl-convertsidtostringsida + // To turn a SID into a string SID to use with CreateProfile & DeleteProfileW. + ConvertSidToStringSidW :: proc( + Sid: ^SID, + StringSid: ^LPCWSTR, + ) -> BOOL --- } diff --git a/core/sys/windows/util.odin b/core/sys/windows/util.odin index 843bd8bcb..c53657e63 100644 --- a/core/sys/windows/util.odin +++ b/core/sys/windows/util.odin @@ -1,5 +1,9 @@ package sys_windows +import "core:strings" +import "core:runtime" +import "core:sys/win32" + LOWORD :: #force_inline proc "contextless" (x: DWORD) -> WORD { return WORD(x & 0xffff); } @@ -81,3 +85,375 @@ utf16_to_utf8 :: proc(s: []u16, allocator := context.temp_allocator) -> string { return wstring_to_utf8(raw_data(s), len(s), allocator); } +// AdvAPI32, NetAPI32 and UserENV helpers. + +allowed_username :: proc(username: string) -> bool { +/* + User account names are limited to 20 characters and group names are limited to 256 characters. + In addition, account names cannot be terminated by a period and they cannot include commas or any of the following printable characters: + ", /, , [, ], :, |, <, >, +, =, ;, ?, *. Names also cannot include characters in the range 1-31, which are nonprintable. +*/ + + _DISALLOWED :: "\"/ []:|<>+=;?*,"; + + if len(username) > LM20_UNLEN || len(username) == 0 { + return false; + } + if username[len(username)-1] == '.' { + return false; + } + + for r in username { + if r > 0 && r < 32 { + return false; + } + } + if strings.contains_any(username, _DISALLOWED) { + return false; + } + + return true; +} + +// Returns .Success on success. +_add_user :: proc(servername: string, username: string, password: string) -> (ok: NET_API_STATUS) { + + servername_w: wstring; + username_w: []u16; + password_w: []u16; + + if len(servername) == 0 { + // Create account on this computer + servername_w = nil; + } else { + server := utf8_to_utf16(servername, context.temp_allocator); + servername_w = &server[0]; + } + + if len(username) == 0 || len(username) > LM20_UNLEN { + return .BadUsername; + } + if !allowed_username(username) { + return .BadUsername; + } + if len(password) == 0 || len(password) > LM20_PWLEN { + return .BadPassword; + } + + username_w = utf8_to_utf16(username, context.temp_allocator); + password_w = utf8_to_utf16(password, context.temp_allocator); + + + level := DWORD(1); + parm_err: DWORD; + + user_info := USER_INFO_1{ + name = &username_w[0], + password = &password_w[0], // Max password length is defined in LM20_PWLEN. + password_age = 0, // Ignored + priv = .User, + home_dir = nil, // We'll set it later + comment = nil, + flags = {.Script, .Normal_Account}, + script_path = nil, + }; + + ok = NetUserAdd( + servername_w, + level, + &user_info, + &parm_err, + ); + + return; +} + +get_computer_name_and_account_sid :: proc(username: string) -> (computer_name: string, sid := SID{}, ok: bool) { + + username_w := utf8_to_utf16(username, context.temp_allocator); + cbsid: DWORD; + computer_name_size: DWORD; + pe_use := SID_TYPE.User; + + res := LookupAccountNameW( + nil, // Look on this computer first + &username_w[0], + &sid, + &cbsid, + nil, + &computer_name_size, + &pe_use, + ); + if computer_name_size == 0 { + // User didn't exist, or we'd have a size here. + return "", {}, false; + } + + cname_w := make([]u16, min(computer_name_size, 1), context.temp_allocator); + + res = LookupAccountNameW( + nil, + &username_w[0], + &sid, + &cbsid, + &cname_w[0], + &computer_name_size, + &pe_use, + ); + + if !res { + return "", {}, false; + } + computer_name = utf16_to_utf8(cname_w, context.temp_allocator); + + ok = true; + return; +} + +get_sid :: proc(username: string, sid: ^SID) -> (ok: bool) { + + username_w := utf8_to_utf16(username, context.temp_allocator); + cbsid: DWORD; + computer_name_size: DWORD; + pe_use := SID_TYPE.User; + + res := LookupAccountNameW( + nil, // Look on this computer first + &username_w[0], + sid, + &cbsid, + nil, + &computer_name_size, + &pe_use, + ); + if computer_name_size == 0 { + // User didn't exist, or we'd have a size here. + return false; + } + + cname_w := make([]u16, min(computer_name_size, 1), context.temp_allocator); + + res = LookupAccountNameW( + nil, + &username_w[0], + sid, + &cbsid, + &cname_w[0], + &computer_name_size, + &pe_use, + ); + + if !res { + return false; + } + ok = true; + return; +} + +add_user_to_group :: proc(sid: ^SID, group: string) -> (ok: NET_API_STATUS) { + group_member := LOCALGROUP_MEMBERS_INFO_0{ + sid = sid, + }; + group_name := utf8_to_utf16(group, context.temp_allocator); + ok = NetLocalGroupAddMembers( + nil, + &group_name[0], + 0, + &group_member, + 1, + ); + return; +} + +add_del_from_group :: proc(sid: ^SID, group: string) -> (ok: NET_API_STATUS) { + group_member := LOCALGROUP_MEMBERS_INFO_0{ + sid = sid, + }; + group_name := utf8_to_utf16(group, context.temp_allocator); + ok = NetLocalGroupDelMembers( + nil, + &group_name[0], + 0, + &group_member, + 1, + ); + return; +} + +add_user_profile :: proc(username: string) -> (ok: bool, profile_path: string) { + username_w := utf8_to_utf16(username, context.temp_allocator); + + sid := SID{}; + ok = get_sid(username, &sid); + if ok == false { + return false, ""; + } + + sb: wstring; + res := ConvertSidToStringSidW(&sid, &sb); + if res == false { + return false, ""; + } + defer win32.local_free(sb); + + pszProfilePath := make([]u16, 257, context.temp_allocator); + cchProfilePath: DWORD; + res2 := CreateProfile( + sb, + &username_w[0], + &pszProfilePath[0], + 257, + ); + if res2 != 0 { + return false, ""; + } + profile_path = wstring_to_utf8(&pszProfilePath[0], 257); + + return true, profile_path; +} + + +delete_user_profile :: proc(username: string) -> (ok: bool) { + sid := SID{}; + ok = get_sid(username, &sid); + if ok == false { + return false; + } + + sb: wstring; + res := ConvertSidToStringSidW(&sid, &sb); + if res == false { + return false; + } + defer win32.local_free(sb); + + res2 := DeleteProfileW( + sb, + nil, + nil, + ); + return bool(res2); +} + +add_user :: proc(servername: string, username: string, password: string) -> (ok: bool) { + /* + Convenience function that creates a new user, adds it to the group Users and creates a profile directory for it. + Requires elevated privileges (run as administrator). + + TODO: Add a bool that governs whether to delete the user if adding to group and/or creating profile fail? + TODO: SecureZeroMemory the password after use. + */ + + res := _add_user(servername, username, password); + if res != .Success { + return false; + } + + // Grab the SID to add the user to the Users group. + sid: SID; + ok2 := get_sid(username, &sid); + if ok2 == false { + return false; + } + + ok3 := add_user_to_group(&sid, "Users"); + if ok3 != .Success { + return false; + } + + return true; +} + +delete_user :: proc(servername: string, username: string) -> (ok: bool) { + /* + Convenience function that deletes a user. + Requires elevated privileges (run as administrator). + + TODO: Add a bool that governs whether to delete the profile from this wrapper? + */ + + servername_w: wstring; + if len(servername) == 0 { + // Delete account on this computer + servername_w = nil; + } else { + server := utf8_to_utf16(servername, context.temp_allocator); + servername_w = &server[0]; + } + username_w := utf8_to_utf16(username); + + res := NetUserDel( + servername_w, + &username_w[0], + ); + if res != .Success { + return false; + } + return true; +} + +run_as_user :: proc(username, password, application, commandline: string, pi: ^PROCESS_INFORMATION, wait := true) -> (ok: bool) { + /* + Needs to be run as an account which has the "Replace a process level token" privilege. + This can be added to an account from: Control Panel -> Administrative Tools -> Local Security Policy. + The path to this policy is as follows: Local Policies -> User Rights Assignment -> Replace a process level token. + A reboot may be required for this change to take effect and impersonating a user to work. + + TODO: SecureZeroMemory the password after use. + + */ + + username_w := utf8_to_utf16(username); + domain_w := utf8_to_utf16("."); + password_w := utf8_to_utf16(password); + app_w := utf8_to_utf16(application); + + commandline_w: []u16 = {0}; + if len(commandline) > 0 { + commandline_w = utf8_to_utf16(commandline); + } + + user_token: HANDLE; + + ok = bool(LogonUserW( + lpszUsername = &username_w[0], + lpszDomain = &domain_w[0], + lpszPassword = &password_w[0], + dwLogonType = .NEW_CREDENTIALS, + dwLogonProvider = .WINNT50, + phToken = &user_token, + )); + + if !ok { + return false; + // err := GetLastError(); + // fmt.printf("GetLastError: %v\n", err); + } + si := STARTUPINFO{}; + si.cb = size_of(STARTUPINFO); + pi := pi; + + ok = bool(CreateProcessAsUserW( + user_token, + &app_w[0], + &commandline_w[0], + nil, // lpProcessAttributes, + nil, // lpThreadAttributes, + false, // bInheritHandles, + 0, // creation flags + nil, // environment, + nil, // current directory: inherit from parent if nil + &si, + pi, + )); + if ok { + if wait { + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + return true; + } else { + return false; + } +} \ No newline at end of file From aca5c7c1c6b984a5523b7bb85765e13ea662b41a Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 13 Apr 2021 02:18:47 +0200 Subject: [PATCH 12/36] Placate -vet. --- core/sys/windows/util.odin | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/sys/windows/util.odin b/core/sys/windows/util.odin index c53657e63..5248cfdbf 100644 --- a/core/sys/windows/util.odin +++ b/core/sys/windows/util.odin @@ -1,7 +1,6 @@ package sys_windows import "core:strings" -import "core:runtime" import "core:sys/win32" LOWORD :: #force_inline proc "contextless" (x: DWORD) -> WORD { @@ -297,7 +296,6 @@ add_user_profile :: proc(username: string) -> (ok: bool, profile_path: string) { defer win32.local_free(sb); pszProfilePath := make([]u16, 257, context.temp_allocator); - cchProfilePath: DWORD; res2 := CreateProfile( sb, &username_w[0], From 2942e45ff5394494e0e54f89de2f2cd9dcd57a1e Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 13 Apr 2021 02:23:14 +0200 Subject: [PATCH 13/36] Placate -vet for unrelated core:thread update. --- core/thread/thread.odin | 1 - 1 file changed, 1 deletion(-) diff --git a/core/thread/thread.odin b/core/thread/thread.odin index fce35b124..09d23fe82 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -1,7 +1,6 @@ package thread import "core:runtime" -import "core:sync" import "core:mem" import "intrinsics" From 12296a0dcc440e0ac79059dd33aa44caebf7560a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 13 Apr 2021 18:57:47 +0100 Subject: [PATCH 14/36] Allow `intrinsics` entities to be exported from other packages if wanted --- src/check_expr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 3c4addb4e..3b7a975d1 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -3515,7 +3515,7 @@ Entity *check_selector(CheckerContext *c, Operand *operand, Ast *node, Type *typ if (entity->kind == Entity_Builtin) { // NOTE(bill): Builtin's are in the universal scope which is part of every scopes hierarchy // This means that we should just ignore the found result through it - allow_builtin = entity->scope == import_scope; + allow_builtin = entity->scope == import_scope || entity->scope != builtin_pkg->scope; } else if ((entity->scope->flags&ScopeFlag_Global) == ScopeFlag_Global && (import_scope->flags&ScopeFlag_Global) == 0) { is_declared = false; } From bee8beb2c92dfde1e751027922727923e32c0ef7 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 13 Apr 2021 19:04:44 +0100 Subject: [PATCH 15/36] Default to pthreads in sync2 for *nix --- core/sync/sync2/primitives_atomic.odin | 2 +- core/sync/sync2/primitives_pthreads.odin | 2 +- core/thread/thread.odin | 1 - core/thread/thread_pool.odin | 19 +++++++------------ core/thread/thread_windows.odin | 6 +++--- 5 files changed, 12 insertions(+), 18 deletions(-) diff --git a/core/sync/sync2/primitives_atomic.odin b/core/sync/sync2/primitives_atomic.odin index bf47e6190..d65403076 100644 --- a/core/sync/sync2/primitives_atomic.odin +++ b/core/sync/sync2/primitives_atomic.odin @@ -2,7 +2,7 @@ //+private package sync2 -when !#config(ODIN_SYNC_USE_PTHREADS, false) { +when !#config(ODIN_SYNC_USE_PTHREADS, true) { import "core:time" diff --git a/core/sync/sync2/primitives_pthreads.odin b/core/sync/sync2/primitives_pthreads.odin index c0cab91e4..e85cff7fc 100644 --- a/core/sync/sync2/primitives_pthreads.odin +++ b/core/sync/sync2/primitives_pthreads.odin @@ -2,7 +2,7 @@ //+private package sync2 -when #config(ODIN_SYNC_USE_PTHREADS, false) { +when #config(ODIN_SYNC_USE_PTHREADS, true) { import "core:time" import "core:sys/unix" diff --git a/core/thread/thread.odin b/core/thread/thread.odin index fce35b124..09d23fe82 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -1,7 +1,6 @@ package thread import "core:runtime" -import "core:sync" import "core:mem" import "intrinsics" diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index 64cd8ea38..700eaf703 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -1,7 +1,7 @@ package thread import "intrinsics" -import "core:sync" +import sync "core:sync/sync2" import "core:mem" Task_Status :: enum i32 { @@ -26,7 +26,7 @@ INVALID_TASK_ID :: Task_Id(-1); Pool :: struct { allocator: mem.Allocator, mutex: sync.Mutex, - sem_available: sync.Semaphore, + sem_available: sync.Sema, processing_task_count: int, // atomic is_running: bool, @@ -40,14 +40,14 @@ pool_init :: proc(pool: ^Pool, thread_count: int, allocator := context.allocator pool := (^Pool)(t.data); for pool.is_running { - sync.semaphore_wait_for(&pool.sem_available); + sync.sema_wait(&pool.sem_available); if task, ok := pool_try_and_pop_task(pool); ok { pool_do_work(pool, &task); } } - sync.semaphore_post(&pool.sem_available, 1); + sync.sema_post(&pool.sem_available); } @@ -56,8 +56,6 @@ pool_init :: proc(pool: ^Pool, thread_count: int, allocator := context.allocator pool.tasks = make([dynamic]Task); pool.threads = make([]^Thread, thread_count); - sync.mutex_init(&pool.mutex); - sync.semaphore_init(&pool.sem_available); pool.is_running = true; for _, i in pool.threads { @@ -76,9 +74,6 @@ pool_destroy :: proc(pool: ^Pool) { } delete(pool.threads, pool.allocator); - - sync.mutex_destroy(&pool.mutex); - sync.semaphore_destroy(&pool.sem_available); } pool_start :: proc(pool: ^Pool) { @@ -90,7 +85,7 @@ pool_start :: proc(pool: ^Pool) { pool_join :: proc(pool: ^Pool) { pool.is_running = false; - sync.semaphore_post(&pool.sem_available, len(pool.threads)); + sync.sema_post(&pool.sem_available, len(pool.threads)); yield(); @@ -109,7 +104,7 @@ pool_add_task :: proc(pool: ^Pool, procedure: Task_Proc, data: rawptr, user_inde task.user_index = user_index; append(&pool.tasks, task); - sync.semaphore_post(&pool.sem_available, 1); + sync.sema_post(&pool.sem_available); } pool_try_and_pop_task :: proc(pool: ^Pool) -> (task: Task, got_task: bool = false) { @@ -140,7 +135,7 @@ pool_wait_and_process :: proc(pool: ^Pool) { // Safety kick if len(pool.tasks) != 0 && intrinsics.atomic_load(&pool.processing_task_count) == 0 { sync.mutex_lock(&pool.mutex); - sync.semaphore_post(&pool.sem_available, len(pool.tasks)); + sync.sema_post(&pool.sem_available, len(pool.tasks)); sync.mutex_unlock(&pool.mutex); } diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index b44ec8f36..b8f8d15c0 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -3,7 +3,7 @@ package thread import "core:runtime" -import "core:sync" +import sync "core:sync/sync2" import win32 "core:sys/windows" Thread_Os_Specific :: struct { @@ -38,7 +38,7 @@ _create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^ } } - sync.atomic_store(&t.done, true, .Sequentially_Consistent); + sync.atomic_store(&t.done, true); return 0; } @@ -73,7 +73,7 @@ _is_done :: proc(using thread: ^Thread) -> bool { // NOTE(tetra, 2019-10-31): Apparently using wait_for_single_object and // checking if it didn't time out immediately, is not good enough, // so we do it this way instead. - return sync.atomic_load(&done, .Sequentially_Consistent); + return sync.atomic_load(&done); } _join :: proc(using thread: ^Thread) { From ebed29fc09a2e9e7cf1efe929729bdacfd5660bd Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 13 Apr 2021 19:09:04 +0100 Subject: [PATCH 16/36] Revert *nix thread stuff to old sync (I was just testing) --- core/thread/thread_pool.odin | 19 ++++++++++++------- core/thread/thread_unix.odin | 15 +++++++++++---- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index 700eaf703..64cd8ea38 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -1,7 +1,7 @@ package thread import "intrinsics" -import sync "core:sync/sync2" +import "core:sync" import "core:mem" Task_Status :: enum i32 { @@ -26,7 +26,7 @@ INVALID_TASK_ID :: Task_Id(-1); Pool :: struct { allocator: mem.Allocator, mutex: sync.Mutex, - sem_available: sync.Sema, + sem_available: sync.Semaphore, processing_task_count: int, // atomic is_running: bool, @@ -40,14 +40,14 @@ pool_init :: proc(pool: ^Pool, thread_count: int, allocator := context.allocator pool := (^Pool)(t.data); for pool.is_running { - sync.sema_wait(&pool.sem_available); + sync.semaphore_wait_for(&pool.sem_available); if task, ok := pool_try_and_pop_task(pool); ok { pool_do_work(pool, &task); } } - sync.sema_post(&pool.sem_available); + sync.semaphore_post(&pool.sem_available, 1); } @@ -56,6 +56,8 @@ pool_init :: proc(pool: ^Pool, thread_count: int, allocator := context.allocator pool.tasks = make([dynamic]Task); pool.threads = make([]^Thread, thread_count); + sync.mutex_init(&pool.mutex); + sync.semaphore_init(&pool.sem_available); pool.is_running = true; for _, i in pool.threads { @@ -74,6 +76,9 @@ pool_destroy :: proc(pool: ^Pool) { } delete(pool.threads, pool.allocator); + + sync.mutex_destroy(&pool.mutex); + sync.semaphore_destroy(&pool.sem_available); } pool_start :: proc(pool: ^Pool) { @@ -85,7 +90,7 @@ pool_start :: proc(pool: ^Pool) { pool_join :: proc(pool: ^Pool) { pool.is_running = false; - sync.sema_post(&pool.sem_available, len(pool.threads)); + sync.semaphore_post(&pool.sem_available, len(pool.threads)); yield(); @@ -104,7 +109,7 @@ pool_add_task :: proc(pool: ^Pool, procedure: Task_Proc, data: rawptr, user_inde task.user_index = user_index; append(&pool.tasks, task); - sync.sema_post(&pool.sem_available); + sync.semaphore_post(&pool.sem_available, 1); } pool_try_and_pop_task :: proc(pool: ^Pool) -> (task: Task, got_task: bool = false) { @@ -135,7 +140,7 @@ pool_wait_and_process :: proc(pool: ^Pool) { // Safety kick if len(pool.tasks) != 0 && intrinsics.atomic_load(&pool.processing_task_count) == 0 { sync.mutex_lock(&pool.mutex); - sync.sema_post(&pool.sem_available, len(pool.tasks)); + sync.semaphore_post(&pool.sem_available, len(pool.tasks)); sync.mutex_unlock(&pool.mutex); } diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 139c323bd..2ba61c53d 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -4,7 +4,7 @@ package thread import "core:runtime" import "core:intrinsics" -import sync "core:sync/sync2" +import "core:sync" import "core:sys/unix" // NOTE(tetra): Aligned here because of core/unix/pthread_linux.odin/pthread_t. @@ -20,7 +20,7 @@ Thread_Os_Specific :: struct #align 16 { // in a suspended state, we have it wait on this gate, which we // signal to start it. // destroyed after thread is started. - start_gate: sync.Cond, + start_gate: sync.Condition, start_mutex: sync.Mutex, // if true, the thread has been started and the start_gate has been destroyed. @@ -41,7 +41,9 @@ _create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^ context = runtime.default_context(); t := (^Thread)(t); - sync.cond_wait(&t.start_gate, &t.start_mutex); + sync.condition_wait_for(&t.start_gate); + sync.condition_destroy(&t.start_gate); + sync.mutex_destroy(&t.start_mutex); t.start_gate = {}; t.start_mutex = {}; @@ -102,6 +104,9 @@ _create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^ } thread.procedure = procedure; + sync.mutex_init(&thread.start_mutex); + sync.condition_init(&thread.start_gate, &thread.start_mutex); + return thread; } @@ -109,7 +114,7 @@ _start :: proc(t: ^Thread) { if intrinsics.atomic_xchg(&t.started, true) { return; } - sync.cond_signal(&t.start_gate); + sync.condition_signal(&t.start_gate); } _is_done :: proc(t: ^Thread) -> bool { @@ -162,6 +167,8 @@ _join_multiple :: proc(threads: ..^Thread) { _destroy :: proc(t: ^Thread) { _join(t); + sync.condition_destroy(&t.start_gate); + sync.mutex_destroy(&t.start_mutex); t.unix_thread = {}; free(t, t.creation_allocator); } From 8e1120bc09bbcebaca6a2ce6d90053275677e664 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 13 Apr 2021 19:23:12 +0100 Subject: [PATCH 17/36] Fix typo --- core/slice/slice.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/slice/slice.odin b/core/slice/slice.odin index fdacb842f..863ccd6cb 100644 --- a/core/slice/slice.odin +++ b/core/slice/slice.odin @@ -133,7 +133,7 @@ has_suffix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_c return false; } -fill :: proc(array: $T/[]$E, value: T) { +fill :: proc(array: $T/[]$E, value: E) { for _, i in array { array[i] = value; } From d6027091333dd26442fac35e95afdbea69b5127e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 14 Apr 2021 12:14:44 +0100 Subject: [PATCH 18/36] Fix typo --- core/slice/slice.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/slice/slice.odin b/core/slice/slice.odin index 863ccd6cb..2c7d225a8 100644 --- a/core/slice/slice.odin +++ b/core/slice/slice.odin @@ -115,7 +115,7 @@ simple_equal :: proc(a, b: $T/[]$E) -> bool where intrinsics.type_is_simple_comp } -has_prefix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_comparable(E) { +has_prefix :: proc(array: $T/[]$E, needle: E) -> bool where intrinsics.type_is_comparable(E) { n := len(needle); if len(array) >= n { return equal(array[:n], needle); @@ -124,7 +124,7 @@ has_prefix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_c } -has_suffix :: proc(array: $T/[]$E, needle: T) -> bool where intrinsics.type_is_comparable(E) { +has_suffix :: proc(array: $T/[]$E, needle: E) -> bool where intrinsics.type_is_comparable(E) { array := array; m, n := len(array), len(needle); if m >= n { From 9adec628c1c6b3d24f7a8642bbf5c0c84586d161 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 14 Apr 2021 17:15:28 +0100 Subject: [PATCH 19/36] Add `@(cold)` attribute to procedure declarations --- src/check_decl.cpp | 4 ++++ src/check_expr.cpp | 30 +++++++++++++++--------------- src/checker.cpp | 12 ++++++++++++ src/checker.hpp | 1 + src/entity.cpp | 9 +++++---- src/llvm_backend.cpp | 35 +++++++++++++++++++++++++++++++++-- src/llvm_backend.hpp | 7 +++++++ src/llvm_backend_opt.cpp | 11 +++++------ 8 files changed, 82 insertions(+), 27 deletions(-) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 5e52597b9..51c0b6ee5 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -690,6 +690,10 @@ void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { if (ac.test) { e->flags |= EntityFlag_Test; } + if (ac.set_cold) { + e->flags |= EntityFlag_Cold; + } + e->Procedure.is_export = ac.is_export; e->deprecated_message = ac.deprecated_message; ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix); diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 3b7a975d1..2114746a3 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -8175,24 +8175,24 @@ ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *pr } switch (inlining) { - case ProcInlining_inline: { - if (proc != nullptr) { - Entity *e = entity_from_expr(proc); - if (e != nullptr && e->kind == Entity_Procedure) { - DeclInfo *decl = e->decl_info; - if (decl->proc_lit) { - ast_node(pl, ProcLit, decl->proc_lit); - if (pl->inlining == ProcInlining_no_inline) { - error(call, "'inline' cannot be applied to a procedure that has be marked as 'no_inline'"); - } + case ProcInlining_inline: { + if (proc != nullptr) { + Entity *e = entity_from_expr(proc); + if (e != nullptr && e->kind == Entity_Procedure) { + DeclInfo *decl = e->decl_info; + if (decl->proc_lit) { + ast_node(pl, ProcLit, decl->proc_lit); + if (pl->inlining == ProcInlining_no_inline) { + error(call, "'inline' cannot be applied to a procedure that has be marked as 'no_inline'"); } } } - break; } + break; + } - case ProcInlining_no_inline: - break; + case ProcInlining_no_inline: + break; } operand->expr = call; @@ -11019,10 +11019,10 @@ gbString write_expr_to_string(gbString str, Ast *node, bool shorthand) { case_ast_node(ce, CallExpr, node); switch (ce->inlining) { case ProcInlining_inline: - str = gb_string_appendc(str, "inline "); + str = gb_string_appendc(str, "#force_inline "); break; case ProcInlining_no_inline: - str = gb_string_appendc(str, "no_inline "); + str = gb_string_appendc(str, "#force_no_inline "); break; } diff --git a/src/checker.cpp b/src/checker.cpp index 0111872b9..e0b303369 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2562,6 +2562,18 @@ DECL_ATTRIBUTE_PROC(proc_decl_attribute) { error(elem, "Expected a boolean value for '%.*s'", LIT(name)); } return true; + } else if (name == "cold") { + if (value == nullptr) { + ac->set_cold = true; + } else { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind == ExactValue_Bool) { + ac->set_cold = ev.value_bool; + } else { + error(elem, "Expected a boolean value for '%.*s' or no value whatsoever", LIT(name)); + } + } + return true; } return false; } diff --git a/src/checker.hpp b/src/checker.hpp index abdb601a9..b3e0b60ec 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -105,6 +105,7 @@ struct AttributeContext { bool has_disabled_proc; bool disabled_proc; bool test; + bool set_cold; String link_name; String link_prefix; isize init_expr_list_count; diff --git a/src/entity.cpp b/src/entity.cpp index 2786fcc6d..a27b7cb37 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -32,7 +32,7 @@ String const entity_strings[] = { #undef ENTITY_KIND }; -enum EntityFlag : u32 { +enum EntityFlag : u64 { EntityFlag_Visited = 1<<0, EntityFlag_Used = 1<<1, EntityFlag_Using = 1<<2, @@ -63,12 +63,13 @@ enum EntityFlag : u32 { EntityFlag_AutoCast = 1<<22, EntityFlag_Disabled = 1<<24, + EntityFlag_Cold = 1<<25, // procedure is rarely called - EntityFlag_Test = 1<<25, + EntityFlag_Test = 1<<30, }; -enum EntityState { +enum EntityState : u32 { EntityState_Unresolved = 0, EntityState_InProgress = 1, EntityState_Resolved = 2, @@ -98,7 +99,7 @@ struct ParameterValue { struct Entity { EntityKind kind; u64 id; - u32 flags; + u64 flags; EntityState state; Token token; Scope * scope; diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index eb4f3c300..16c2e35e5 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -2464,9 +2464,12 @@ void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *nam } void lb_add_proc_attribute_at_index(lbProcedure *p, isize index, char const *name) { - lb_add_proc_attribute_at_index(p, index, name, cast(u64)true); + lb_add_proc_attribute_at_index(p, index, name, 0); } +void lb_add_attribute_to_proc(lbModule *m, LLVMValueRef proc_value, char const *name, u64 value=0) { + LLVMAddAttributeAtIndex(proc_value, LLVMAttributeIndex_FunctionIndex, lb_create_enum_attribute(m->ctx, name, value)); +} void lb_ensure_abi_function_type(lbModule *m, lbProcedure *p) { @@ -2556,6 +2559,23 @@ lbProcedure *lb_create_procedure(lbModule *m, Entity *entity) { LLVMSetFunctionCallConv(p->value, cc_kind); } + if (entity->flags & EntityFlag_Cold) { + lb_add_attribute_to_proc(m, p->value, "cold"); + } + + if (pt->Proc.diverging) { + lb_add_attribute_to_proc(m, p->value, "noreturn"); + } + + switch (p->inlining) { + case ProcInlining_inline: + lb_add_attribute_to_proc(m, p->value, "alwaysinline"); + break; + case ProcInlining_no_inline: + lb_add_attribute_to_proc(m, p->value, "noinline"); + break; + } + // lbCallingConventionKind cc_kind = lbCallingConvention_C; // // TODO(bill): Clean up this logic // if (build_context.metrics.os != TargetOs_js) { @@ -8073,7 +8093,18 @@ lbValue lb_emit_call_internal(lbProcedure *p, lbValue value, lbValue return_ptr, } LLVMValueRef ret = LLVMBuildCall2(p->builder, fnp, fn, args, arg_count, ""); - // LLVMValueRef ret = LLVMBuildCall(p->builder, fn, args, arg_count, ""); + + switch (inlining) { + case ProcInlining_none: + break; + case ProcInlining_inline: + // LLVMAddAttributeAtIndex(ret, LLVMAttributeIndex_FunctionIndex, lb_create_enum_attribute(p->module->ctx, "alwaysinline")); + break; + case ProcInlining_no_inline: + // LLVMAddAttributeAtIndex(ret, LLVMAttributeIndex_FunctionIndex, lb_create_enum_attribute(p->module->ctx, "noinline")); + break; + } + lbValue res = {}; res.value = ret; res.type = abi_rt; diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 5046eee8c..8117271c1 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -499,3 +499,10 @@ enum { DW_TAG_subroutine_type = 21, DW_TAG_inheritance = 28, }; + + +enum : LLVMAttributeIndex { + LLVMAttributeIndex_ReturnIndex = 0u, + LLVMAttributeIndex_FunctionIndex = ~0u, + LLVMAttributeIndex_FirstArgIndex = 1, +}; diff --git a/src/llvm_backend_opt.cpp b/src/llvm_backend_opt.cpp index 0e863f9e9..5e1154af2 100644 --- a/src/llvm_backend_opt.cpp +++ b/src/llvm_backend_opt.cpp @@ -107,12 +107,11 @@ void lb_populate_module_pass_manager(LLVMTargetMachineRef target_machine, LLVMPa if (optimization_level >= 2) { // NOTE(bill, 2021-03-29: use this causes invalid code generation) - LLVMPassManagerBuilderRef pmb = LLVMPassManagerBuilderCreate(); - LLVMPassManagerBuilderSetOptLevel(pmb, optimization_level); - LLVMPassManagerBuilderPopulateModulePassManager(pmb, mpm); - LLVMPassManagerBuilderPopulateLTOPassManager(pmb, mpm, false, true); - // LLVMPassManagerBuilderSetSizeLevel(pmb, optimization_level); - return; + // LLVMPassManagerBuilderRef pmb = LLVMPassManagerBuilderCreate(); + // LLVMPassManagerBuilderSetOptLevel(pmb, optimization_level); + // LLVMPassManagerBuilderPopulateModulePassManager(pmb, mpm); + // LLVMPassManagerBuilderPopulateLTOPassManager(pmb, mpm, false, true); + // return; } LLVMAddIPSCCPPass(mpm); From 3a4373641b68019149007f04a201965ee961f74e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 14 Apr 2021 17:16:10 +0100 Subject: [PATCH 20/36] Correct call site attributes --- src/llvm_backend.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 16c2e35e5..14a1a8007 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -8098,10 +8098,10 @@ lbValue lb_emit_call_internal(lbProcedure *p, lbValue value, lbValue return_ptr, case ProcInlining_none: break; case ProcInlining_inline: - // LLVMAddAttributeAtIndex(ret, LLVMAttributeIndex_FunctionIndex, lb_create_enum_attribute(p->module->ctx, "alwaysinline")); + LLVMAddCallSiteAttribute(ret, LLVMAttributeIndex_FunctionIndex, lb_create_enum_attribute(p->module->ctx, "alwaysinline")); break; case ProcInlining_no_inline: - // LLVMAddAttributeAtIndex(ret, LLVMAttributeIndex_FunctionIndex, lb_create_enum_attribute(p->module->ctx, "noinline")); + LLVMAddCallSiteAttribute(ret, LLVMAttributeIndex_FunctionIndex, lb_create_enum_attribute(p->module->ctx, "noinline")); break; } From ebbc33fdb5e044e5feb010d6d3a8bde41f71a05f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 14 Apr 2021 19:39:12 +0100 Subject: [PATCH 21/36] Mockup of the new `package os` interface (incomplete and non-functioning) --- core/os/os2/doc.odin | 11 + core/os/os2/env.odin | 43 ++++ core/os/os2/env_windows.odin | 80 +++++++ core/os/os2/errors.odin | 126 ++++++++++ core/os/os2/errors_windows.odin | 14 ++ core/os/os2/file.odin | 158 ++++++++++++ core/os/os2/file_stream.odin | 98 ++++++++ core/os/os2/file_util.odin | 122 ++++++++++ core/os/os2/file_windows.odin | 136 +++++++++++ core/os/os2/heap.odin | 21 ++ core/os/os2/heap_windows.odin | 107 +++++++++ core/os/os2/path.odin | 29 +++ core/os/os2/path_windows.odin | 31 +++ core/os/os2/pipe.odin | 5 + core/os/os2/pipe_windows.odin | 13 + core/os/os2/process.odin | 101 ++++++++ core/os/os2/stat.odin | 42 ++++ core/os/os2/stat_windows.odin | 373 +++++++++++++++++++++++++++++ core/os/os2/temp_file.odin | 14 ++ core/os/os2/temp_file_windows.odin | 29 +++ core/os/os2/user.odin | 68 ++++++ 21 files changed, 1621 insertions(+) create mode 100644 core/os/os2/doc.odin create mode 100644 core/os/os2/env.odin create mode 100644 core/os/os2/env_windows.odin create mode 100644 core/os/os2/errors.odin create mode 100644 core/os/os2/errors_windows.odin create mode 100644 core/os/os2/file.odin create mode 100644 core/os/os2/file_stream.odin create mode 100644 core/os/os2/file_util.odin create mode 100644 core/os/os2/file_windows.odin create mode 100644 core/os/os2/heap.odin create mode 100644 core/os/os2/heap_windows.odin create mode 100644 core/os/os2/path.odin create mode 100644 core/os/os2/path_windows.odin create mode 100644 core/os/os2/pipe.odin create mode 100644 core/os/os2/pipe_windows.odin create mode 100644 core/os/os2/process.odin create mode 100644 core/os/os2/stat.odin create mode 100644 core/os/os2/stat_windows.odin create mode 100644 core/os/os2/temp_file.odin create mode 100644 core/os/os2/temp_file_windows.odin create mode 100644 core/os/os2/user.odin diff --git a/core/os/os2/doc.odin b/core/os/os2/doc.odin new file mode 100644 index 000000000..e413ef186 --- /dev/null +++ b/core/os/os2/doc.odin @@ -0,0 +1,11 @@ +// Package os provides a platform-independent interface to operating system functionality. +// The design is UNIX-like but with Odin-like error handling. Failing calls return values with a specific error type rather than error number. +// +// The package os interface is intended to be uniform across all operating systems. +// Features not generally available appear in the system-specific packages under core:sys/*. +// +// +// IMPORTANT NOTE from Bill: this is purely a mockup of what I want the new package os to be, and NON-FUNCTIONING. +// It is not complete but should give designers a better idea of the general interface and how to write things. +// This entire interface is subject to change. +package os2 diff --git a/core/os/os2/env.odin b/core/os/os2/env.odin new file mode 100644 index 000000000..ae1752a10 --- /dev/null +++ b/core/os/os2/env.odin @@ -0,0 +1,43 @@ +package os2 + +// 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 +get_env :: proc(key: string, allocator := context.allocator) -> string { + value, _ := lookup_env(key, allocator); + return value; +} + +// 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 +lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + return _lookup_env(key, allocator); +} + +// set_env sets the value of the environment variable named by the key +// Returns true on success, false on failure +set_env :: proc(key, value: string) -> bool { + return _set_env(key, value); +} + +// unset_env unsets a single environment variable +// Returns true on success, false on failure +unset_env :: proc(key: string) -> bool { + return _unset_env(key); +} + +clear_env :: proc() { + _clear_env(); +} + + +// environ returns a copy of strings representing the environment, in the form "key=value" +// NOTE: the slice of strings and the strings with be allocated using the supplied allocator +environ :: proc(allocator := context.allocator) -> []string { + return _environ(allocator); +} + + diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin new file mode 100644 index 000000000..b6a73ad81 --- /dev/null +++ b/core/os/os2/env_windows.odin @@ -0,0 +1,80 @@ +//+private +package os2 + +import "core:mem" +import win32 "core:sys/windows" + +_lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + if key == "" { + return; + } + wkey := win32.utf8_to_wstring(key); + b := make([dynamic]u16, 100, context.temp_allocator); + for { + n := win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b))); + if n == 0 { + err := win32.GetLastError(); + if err == win32.ERROR_ENVVAR_NOT_FOUND { + return "", false; + } + } + + if n <= u32(len(b)) { + value = win32.utf16_to_utf8(b[:n], allocator); + found = true; + return; + } + + resize(&b, len(b)*2); + } +} + +_set_env :: proc(key, value: string) -> bool { + k := win32.utf8_to_wstring(key); + v := win32.utf8_to_wstring(value); + + return bool(win32.SetEnvironmentVariableW(k, v)); +} + +_unset_env :: proc(key: string) -> bool { + k := win32.utf8_to_wstring(key); + return bool(win32.SetEnvironmentVariableW(k, nil)); +} + +_clear_env :: proc() { + envs := environ(context.temp_allocator); + for env in envs { + for j in 1.. (n: int, err: Error) { + s := transmute([]byte)mem.Raw_Slice{data, len}; + return write(fd, s); +} + +read_ptr :: proc(fd: Handle, data: rawptr, len: int) -> (n: int, err: Error) { + s := transmute([]byte)mem.Raw_Slice{data, len}; + return read(fd, s); +} + + + +read_entire_file :: proc(name: string, allocator := context.allocator) -> ([]byte, Error) { + f, ferr := open(name); + if ferr != nil { + return nil, ferr; + } + defer close(f); + + size: int; + if size64, err := file_size(f); err == nil { + if i64(int(size64)) != size64 { + size = int(size64); + } + } + size += 1; // for EOF + + // TODO(bill): Is this correct logic? + total: int; + data := make([]byte, size, allocator); + for { + n, err := read(f, data[total:]); + total += n; + if err != nil { + if err == .EOF { + err = nil; + } + return data[:total], err; + } + } +} + +write_entire_file :: proc(name: string, data: []byte, perm: File_Mode, truncate := true) -> Error { + flags := O_WRONLY|O_CREATE; + if truncate { + flags |= O_TRUNC; + } + f, err := open_file(name, flags, perm); + if err != nil { + return err; + } + _, err = write(f, data); + if cerr := close(f); cerr != nil && err == nil { + err = cerr; + } + return err; +} + diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin new file mode 100644 index 000000000..97fe6b3d9 --- /dev/null +++ b/core/os/os2/file_windows.odin @@ -0,0 +1,136 @@ +//+private +package os2 + +import "core:io" +import "core:time" + +_create :: proc(name: string) -> (Handle, Error) { + return 0, .None; +} + +_open :: proc(name: string) -> (Handle, Error) { + return 0, .None; +} + +_open_file :: proc(name: string, flag: int, perm: File_Mode) -> (Handle, Error) { + return 0, .None; +} + +_close :: proc(fd: Handle) -> Error { + return .None; +} + +_name :: proc(fd: Handle, allocator := context.allocator) -> string { + return ""; +} + +_seek :: proc(fd: Handle, offset: i64, whence: Seek_From) -> (ret: i64, err: Error) { + return; +} + +_read :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { + return; +} + +_read_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { + return; +} + +_read_from :: proc(fd: Handle, r: io.Reader) -> (n: i64, err: Error) { + return; +} + +_write :: proc(fd: Handle, p: []byte) -> (n: int, err: Error) { + return; +} + +_write_at :: proc(fd: Handle, p: []byte, offset: i64) -> (n: int, err: Error) { + return; +} + +_write_to :: proc(fd: Handle, w: io.Writer) -> (n: i64, err: Error) { + return; +} + +_file_size :: proc(fd: Handle) -> (n: i64, err: Error) { + return; +} + + +_sync :: proc(fd: Handle) -> Error { + return .None; +} + +_flush :: proc(fd: Handle) -> Error { + return .None; +} + +_truncate :: proc(fd: Handle, size: i64) -> Maybe(Path_Error) { + return nil; +} + +_remove :: proc(name: string) -> Maybe(Path_Error) { + return nil; +} + +_rename :: proc(old_path, new_path: string) -> Maybe(Path_Error) { + return nil; +} + + +_link :: proc(old_name, new_name: string) -> Maybe(Link_Error) { + return nil; +} + +_symlink :: proc(old_name, new_name: string) -> Maybe(Link_Error) { + return nil; +} + +_read_link :: proc(name: string) -> (string, Maybe(Path_Error)) { + return "", nil; +} + + +_chdir :: proc(fd: Handle) -> Error { + return .None; +} + +_chmod :: proc(fd: Handle, mode: File_Mode) -> Error { + return .None; +} + +_chown :: proc(fd: Handle, uid, gid: int) -> Error { + return .None; +} + + +_lchown :: proc(name: string, uid, gid: int) -> Error { + return .None; +} + + +_chtimes :: proc(name: string, atime, mtime: time.Time) -> Maybe(Path_Error) { + return nil; +} + + +_exists :: proc(path: string) -> bool { + return false; +} + +_is_file :: proc(path: string) -> bool { + return false; +} + +_is_dir :: proc(path: string) -> bool { + return false; +} + + +_path_error_delete :: proc(perr: Maybe(Path_Error)) { + +} + +_link_error_delete :: proc(lerr: Maybe(Link_Error)) { + +} diff --git a/core/os/os2/heap.odin b/core/os/os2/heap.odin new file mode 100644 index 000000000..08605d568 --- /dev/null +++ b/core/os/os2/heap.odin @@ -0,0 +1,21 @@ +package os2 + +import "core:runtime" + +heap_allocator :: proc() -> runtime.Allocator { + return runtime.Allocator{ + procedure = heap_allocator_proc, + data = nil, + }; +} + + +heap_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, flags: u64 = 0, loc := #caller_location) -> rawptr { + return _heap_allocator_proc(allocator_data, mode, size, alignment, old_memory, old_size, flags, loc); +} + + +@(private) +error_allocator := heap_allocator; diff --git a/core/os/os2/heap_windows.odin b/core/os/os2/heap_windows.odin new file mode 100644 index 000000000..e0e9c906a --- /dev/null +++ b/core/os/os2/heap_windows.odin @@ -0,0 +1,107 @@ +//+private +package os2 + +import "core:runtime" +import "core:mem" +import win32 "core:sys/windows" + +heap_alloc :: proc(size: int) -> rawptr { + return win32.HeapAlloc(win32.GetProcessHeap(), win32.HEAP_ZERO_MEMORY, uint(size)); +} + +heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr { + if new_size == 0 { + heap_free(ptr); + return nil; + } + if ptr == nil { + return heap_alloc(new_size); + } + + return win32.HeapReAlloc(win32.GetProcessHeap(), win32.HEAP_ZERO_MEMORY, ptr, uint(new_size)); +} +heap_free :: proc(ptr: rawptr) { + if ptr == nil { + return; + } + win32.HeapFree(win32.GetProcessHeap(), 0, ptr); +} + +_heap_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, flags: u64 = 0, loc := #caller_location) -> rawptr { + // + // NOTE(tetra, 2020-01-14): The heap doesn't respect alignment. + // Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert + // padding. We also store the original pointer returned by heap_alloc right before + // the pointer we return to the user. + // + + aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr = nil) -> rawptr { + a := max(alignment, align_of(rawptr)); + space := size + a - 1; + + allocated_mem: rawptr; + if old_ptr != nil { + original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^; + allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr)); + } else { + allocated_mem = heap_alloc(space+size_of(rawptr)); + } + aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr))); + + ptr := uintptr(aligned_mem); + aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a); + diff := int(aligned_ptr - ptr); + if (size + diff) > space { + return nil; + } + + aligned_mem = rawptr(aligned_ptr); + mem.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem; + + return aligned_mem; + } + + aligned_free :: proc(p: rawptr) { + if p != nil { + heap_free(mem.ptr_offset((^rawptr)(p), -1)^); + } + } + + aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int) -> rawptr { + if p == nil { + return nil; + } + return aligned_alloc(new_size, new_alignment, p); + } + + switch mode { + case .Alloc: + return aligned_alloc(size, alignment); + + case .Free: + aligned_free(old_memory); + + case .Free_All: + // NOTE(tetra): Do nothing. + + case .Resize: + if old_memory == nil { + return aligned_alloc(size, alignment); + } + return aligned_resize(old_memory, old_size, size, alignment); + + case .Query_Features: + set := (^runtime.Allocator_Mode_Set)(old_memory); + if set != nil { + set^ = {.Alloc, .Free, .Resize, .Query_Features}; + } + return set; + + case .Query_Info: + return nil; + } + + return nil; +} diff --git a/core/os/os2/path.odin b/core/os/os2/path.odin new file mode 100644 index 000000000..eee2b3cee --- /dev/null +++ b/core/os/os2/path.odin @@ -0,0 +1,29 @@ +package os2 + +Path_Separator :: _Path_Separator; // OS-Specific +Path_List_Separator :: _Path_List_Separator; // OS-Specific + +is_path_separator :: proc(c: byte) -> bool { + return _is_path_separator(c); +} + +mkdir :: proc(name: string, perm: File_Mode) -> Maybe(Path_Error) { + return _mkdir(name, perm); +} + +mkdir_all :: proc(path: string, perm: File_Mode) -> Maybe(Path_Error) { + return _mkdir_all(path, perm); +} + +remove_all :: proc(path: string) -> Maybe(Path_Error) { + return _remove_all(path); +} + + + +getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) { + return _getwd(allocator); +} +setwd :: proc(dir: string) -> (err: Error) { + return _setwd(dir); +} diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin new file mode 100644 index 000000000..5056eb638 --- /dev/null +++ b/core/os/os2/path_windows.odin @@ -0,0 +1,31 @@ +//+private +package os2 + +_Path_Separator :: '\\'; +_Path_List_Separator :: ';'; + +_is_path_separator :: proc(c: byte) -> bool { + return c == '\\' || c == '/'; +} + +_mkdir :: proc(name: string, perm: File_Mode) -> Maybe(Path_Error) { + return nil; +} + +_mkdir_all :: proc(path: string, perm: File_Mode) -> Maybe(Path_Error) { + // TODO(bill): _mkdir_all for windows + return nil; +} + +_remove_all :: proc(path: string) -> Maybe(Path_Error) { + // TODO(bill): _remove_all for windows + return nil; +} + +_getwd :: proc(allocator := context.allocator) -> (dir: string, err: Error) { + return "", nil; +} + +_setwd :: proc(dir: string) -> (err: Error) { + return nil; +} diff --git a/core/os/os2/pipe.odin b/core/os/os2/pipe.odin new file mode 100644 index 000000000..8bb46b303 --- /dev/null +++ b/core/os/os2/pipe.odin @@ -0,0 +1,5 @@ +package os2 + +pipe :: proc() -> (r, w: Handle, err: Error) { + return _pipe(); +} diff --git a/core/os/os2/pipe_windows.odin b/core/os/os2/pipe_windows.odin new file mode 100644 index 000000000..68adb6c3b --- /dev/null +++ b/core/os/os2/pipe_windows.odin @@ -0,0 +1,13 @@ +//+private +package os2 + +import win32 "core:sys/windows" + +_pipe :: proc() -> (r, w: Handle, err: Error) { + p: [2]win32.HANDLE; + if !win32.CreatePipe(&p[0], &p[1], nil, 0) { + return 0, 0, error_from_platform_error(i32(win32.GetLastError())); + } + return Handle(p[0]), Handle(p[1]), nil; +} + diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin new file mode 100644 index 000000000..f0060b54f --- /dev/null +++ b/core/os/os2/process.odin @@ -0,0 +1,101 @@ +package os2 + +import sync "core:sync/sync2" +import "core:time" + +args: []string; + +exit :: proc "contextless" (code: int) -> ! { + // +} + +get_uid :: proc() -> int { + return -1; +} + +get_euid :: proc() -> int { + return -1; +} + +get_gid :: proc() -> int { + return -1; +} + +get_egid :: proc() -> int { + return -1; +} + +get_pid :: proc() -> int { + return -1; +} + +get_ppid :: proc() -> int { + return -1; +} + + +Process :: struct { + pid: int, + handle: uintptr, + is_done: b32, + signal_mutex: sync.RW_Mutex, +} + + +Process_Attributes :: struct { + dir: string, + env: []string, + files: []Handle, + sys: ^Process_Attributes_OS_Specific, +} + +Process_Attributes_OS_Specific :: struct{}; + +Process_Error :: enum { + None, +} + +Process_State :: struct { + pid: int, + exit_code: int, + exited: bool, + success: bool, + system_time: time.Duration, + user_time: time.Duration, + sys: rawptr, +} + +Signal :: #type proc(); + +Kill: Signal = nil; +Interrupt: Signal = nil; + + +find_process :: proc(pid: int) -> (^Process, Process_Error) { + return nil, .None; +} + + +process_start :: proc(name: string, argv: []string, attr: ^Process_Attributes) -> (^Process, Process_Error) { + return nil, .None; +} + +process_release :: proc(p: ^Process) -> Process_Error { + return .None; +} + +process_kill :: proc(p: ^Process) -> Process_Error { + return .None; +} + +process_signal :: proc(p: ^Process, sig: Signal) -> Process_Error { + return .None; +} + +process_wait :: proc(p: ^Process) -> (Process_State, Process_Error) { + return {}, .None; +} + + + + diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin new file mode 100644 index 000000000..791948c8d --- /dev/null +++ b/core/os/os2/stat.odin @@ -0,0 +1,42 @@ +package os2 + +import "core:time" + +File_Info :: struct { + fullpath: string, + name: string, + size: i64, + mode: File_Mode, + is_dir: bool, + creation_time: time.Time, + modification_time: time.Time, + access_time: time.Time, +} + +file_info_slice_delete :: proc(infos: []File_Info, allocator := context.allocator) { + for i := len(infos)-1; i >= 0; i -= 1 { + file_info_delete(infos[i], allocator); + } + delete(infos, allocator); +} + +file_info_delete :: proc(fi: File_Info, allocator := context.allocator) { + delete(fi.fullpath, allocator); +} + +fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { + return _fstat(fd, allocator); +} + +stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { + return _stat(name, allocator); +} + +lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { + return _lstat(name, allocator); +} + + +same_file :: proc(fi1, fi2: File_Info) -> bool { + return _same_file(fi1, fi2); +} diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin new file mode 100644 index 000000000..ed739b894 --- /dev/null +++ b/core/os/os2/stat_windows.odin @@ -0,0 +1,373 @@ +//+private +package os2 + +import "core:time" +import win32 "core:sys/windows" + +_fstat :: proc(fd: Handle, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { + if fd == 0 { + return {}, Path_Error{err = .Invalid_Argument}; + } + context.allocator = allocator; + + path, err := _cleanpath_from_handle(fd); + if err != nil { + return {}, err; + } + + h := win32.HANDLE(fd); + switch win32.GetFileType(h) { + case win32.FILE_TYPE_PIPE, win32.FILE_TYPE_CHAR: + fi: File_Info; + fi.fullpath = path; + fi.name = basename(path); + fi.mode |= file_type_mode(h); + return fi, nil; + } + + return _file_info_from_get_file_information_by_handle(path, h); +} +_stat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { + return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS); +} +_lstat :: proc(name: string, allocator := context.allocator) -> (File_Info, Maybe(Path_Error)) { + return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT); +} +_same_file :: proc(fi1, fi2: File_Info) -> bool { + return fi1.fullpath == fi2.fullpath; +} + + + +_stat_errno :: proc(errno: win32.DWORD) -> Path_Error { + return Path_Error{err = error_from_platform_error(i32(errno))}; +} + + +full_path_from_name :: proc(name: string, allocator := context.allocator) -> (path: string, err: Maybe(Path_Error)) { + name := name; + if name == "" { + name = "."; + } + p := win32.utf8_to_utf16(name, context.temp_allocator); + buf := make([dynamic]u16, 100, allocator); + for { + n := win32.GetFullPathNameW(raw_data(p), u32(len(buf)), raw_data(buf), nil); + if n == 0 { + delete(buf); + return "", _stat_errno(win32.GetLastError()); + } + if n <= u32(len(buf)) { + return win32.utf16_to_utf8(buf[:n]), nil; + } + resize(&buf, len(buf)*2); + } + + return; +} + + +internal_stat :: proc(name: string, create_file_attributes: u32, allocator := context.allocator) -> (fi: File_Info, e: Maybe(Path_Error)) { + if len(name) == 0 { + return {}, Path_Error{err = .Not_Exist}; + } + + context.allocator = allocator; + + + wname := win32.utf8_to_wstring(_fix_long_path(name), context.temp_allocator); + fa: win32.WIN32_FILE_ATTRIBUTE_DATA; + ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa); + if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { + // Not a symlink + return _file_info_from_win32_file_attribute_data(&fa, name); + } + + err := 0 if ok else win32.GetLastError(); + + if err == win32.ERROR_SHARING_VIOLATION { + fd: win32.WIN32_FIND_DATAW; + sh := win32.FindFirstFileW(wname, &fd); + if sh == win32.INVALID_HANDLE_VALUE { + e = Path_Error{err = error_from_platform_error(i32(win32.GetLastError()))}; + return; + } + win32.FindClose(sh); + + return _file_info_from_win32_find_data(&fd, name); + } + + h := win32.CreateFileW(wname, 0, 0, nil, win32.OPEN_EXISTING, create_file_attributes, nil); + if h == win32.INVALID_HANDLE_VALUE { + e = Path_Error{err = error_from_platform_error(i32(win32.GetLastError()))}; + return; + } + defer win32.CloseHandle(h); + return _file_info_from_get_file_information_by_handle(name, h); +} + + +_cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { + buf := buf; + N := 0; + for c, i in buf { + if c == 0 { break; } + N = i+1; + } + buf = buf[:N]; + + if len(buf) >= 4 { + if buf[0] == '\\' && + buf[1] == '\\' && + buf[2] == '?' && + buf[3] == '\\' { + buf = buf[4:]; + } + } + return buf; +} + + +_cleanpath_from_handle :: proc(fd: Handle) -> (string, Maybe(Path_Error)) { + if fd == 0 { + return "", Path_Error{err = .Invalid_Argument}; + } + h := win32.HANDLE(fd); + + MAX_PATH := win32.DWORD(260) + 1; + buf: []u16; + for { + buf = make([]u16, MAX_PATH, context.temp_allocator); + err := win32.GetFinalPathNameByHandleW(h, raw_data(buf), MAX_PATH, 0); + switch err { + case win32.ERROR_PATH_NOT_FOUND, win32.ERROR_INVALID_PARAMETER: + return "", _stat_errno(err); + case win32.ERROR_NOT_ENOUGH_MEMORY: + MAX_PATH = MAX_PATH*2 + 1; + continue; + } + break; + } + return _cleanpath_from_buf(buf), nil; +} + +_cleanpath_from_handle_u16 :: proc(fd: Handle) -> ([]u16, Maybe(Path_Error)) { + if fd == 0 { + return nil, Path_Error{err = .Invalid_Argument}; + } + h := win32.HANDLE(fd); + + MAX_PATH := win32.DWORD(260) + 1; + buf: []u16; + for { + buf = make([]u16, MAX_PATH, context.temp_allocator); + err := win32.GetFinalPathNameByHandleW(h, raw_data(buf), MAX_PATH, 0); + switch err { + case win32.ERROR_PATH_NOT_FOUND, win32.ERROR_INVALID_PARAMETER: + return nil, _stat_errno(err); + case win32.ERROR_NOT_ENOUGH_MEMORY: + MAX_PATH = MAX_PATH*2 + 1; + continue; + } + break; + } + return _cleanpath_strip_prefix(buf), nil; +} + +_cleanpath_from_buf :: proc(buf: []u16) -> string { + buf := buf; + buf = _cleanpath_strip_prefix(buf); + return win32.utf16_to_utf8(buf, context.allocator); +} + + +basename :: proc(name: string) -> (base: string) { + name := name; + if len(name) > 3 && name[:3] == `\\?` { + name = name[3:]; + } + + if len(name) == 2 && name[1] == ':' { + return "."; + } else if len(name) > 2 && name[1] == ':' { + name = name[2:]; + } + i := len(name)-1; + + for ; i > 0 && (name[i] == '/' || name[i] == '\\'); i -= 1 { + name = name[:i]; + } + for i -= 1; i >= 0; i -= 1 { + if name[i] == '/' || name[i] == '\\' { + name = name[i+1:]; + break; + } + } + return name; +} + + +file_type_mode :: proc(h: win32.HANDLE) -> File_Mode { + switch win32.GetFileType(h) { + case win32.FILE_TYPE_PIPE: + return File_Mode_Named_Pipe; + case win32.FILE_TYPE_CHAR: + return File_Mode_Device | File_Mode_Char_Device; + } + return 0; +} + + + +_file_mode_from_file_attributes :: proc(FileAttributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) { + if FileAttributes & win32.FILE_ATTRIBUTE_READONLY != 0 { + mode |= 0o444; + } else { + mode |= 0o666; + } + + is_sym := false; + if FileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { + is_sym = false; + } else { + is_sym = ReparseTag == win32.IO_REPARSE_TAG_SYMLINK || ReparseTag == win32.IO_REPARSE_TAG_MOUNT_POINT; + } + + if is_sym { + mode |= File_Mode_Sym_Link; + } else { + if FileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { + mode |= 0o111 | File_Mode_Dir; + } + + if h != nil { + mode |= file_type_mode(h); + } + } + + return; +} + + +_file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string) -> (fi: File_Info, e: Maybe(Path_Error)) { + fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow); + + fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0); + fi.is_dir = fi.mode & File_Mode_Dir != 0; + + 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.fullpath, e = full_path_from_name(name); + fi.name = basename(fi.fullpath); + + return; +} + + +_file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string) -> (fi: File_Info, e: Maybe(Path_Error)) { + fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow); + + fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0); + fi.is_dir = fi.mode & File_Mode_Dir != 0; + + 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.fullpath, e = full_path_from_name(name); + fi.name = basename(fi.fullpath); + + return; +} + + +_file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE) -> (File_Info, Maybe(Path_Error)) { + d: win32.BY_HANDLE_FILE_INFORMATION; + if !win32.GetFileInformationByHandle(h, &d) { + return {}, _stat_errno(win32.GetLastError()); + + } + + ti: win32.FILE_ATTRIBUTE_TAG_INFO; + if !win32.GetFileInformationByHandleEx(h, .FileAttributeTagInfo, &ti, size_of(ti)) { + err := win32.GetLastError(); + if err != win32.ERROR_INVALID_PARAMETER { + return {}, _stat_errno(err); + } + // Indicate this is a symlink on FAT file systems + ti.ReparseTag = 0; + } + + fi: File_Info; + + fi.fullpath = path; + fi.name = basename(path); + fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow); + + fi.mode |= _file_mode_from_file_attributes(ti.FileAttributes, h, ti.ReparseTag); + fi.is_dir = fi.mode & File_Mode_Dir != 0; + + 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)); + + return fi, nil; +} + +_is_abs :: proc(path: string) -> bool { + if len(path) > 0 && path[0] == '/' { + return true; + } + if len(path) > 2 { + switch path[0] { + case 'A'..'Z', 'a'..'z': + return path[1] == ':' && is_path_separator(path[2]); + } + } + return false; +} + +_fix_long_path :: proc(path: string) -> string { + if len(path) < 248 { + return path; + } + + if len(path) >= 2 && path[:2] == `\\` { + return path; + } + if !_is_abs(path) { + return path; + } + + prefix :: `\\?`; + + path_buf := make([]byte, len(prefix)+len(path)+len(`\`), context.temp_allocator); + copy(path_buf, prefix); + n := len(path); + r, w := 0, len(prefix); + for r < n { + switch { + case is_path_separator(path[r]): + r += 1; + case path[r] == '.' && (r+1 == n || is_path_separator(path[r+1])): + r += 1; + case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || is_path_separator(path[r+2])): + return path; + case: + path_buf[w] = '\\'; + w += 1; + for ; r < n && !is_path_separator(path[r]); r += 1 { + path_buf[w] = path[r]; + w += 1; + } + } + } + + if w == len(`\\?\c:`) { + path_buf[w] = '\\'; + w += 1; + } + return string(path_buf[:w]); +} diff --git a/core/os/os2/temp_file.odin b/core/os/os2/temp_file.odin new file mode 100644 index 000000000..4969a07d9 --- /dev/null +++ b/core/os/os2/temp_file.odin @@ -0,0 +1,14 @@ +package os2 + + +create_temp :: proc(dir, pattern: string) -> (Handle, Error) { + return _create_temp(dir, pattern); +} + +mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) { + return _mkdir_temp(dir, pattern); +} + +temp_dir :: proc(allocator := context.allocator) -> string { + return _temp_dir(allocator); +} diff --git a/core/os/os2/temp_file_windows.odin b/core/os/os2/temp_file_windows.odin new file mode 100644 index 000000000..19dca1b04 --- /dev/null +++ b/core/os/os2/temp_file_windows.odin @@ -0,0 +1,29 @@ +//+private +package os2 + +import win32 "core:sys/windows" + +_create_temp :: proc(dir, pattern: string) -> (Handle, Error) { + return 0, .None; +} + +_mkdir_temp :: proc(dir, pattern: string, allocator := context.allocator) -> (string, Error) { + return "", .None; +} + +_temp_dir :: proc(allocator := context.allocator) -> string { + b := make([dynamic]u16, u32(win32.MAX_PATH), context.temp_allocator); + for { + n := win32.GetTempPathW(u32(len(b)), raw_data(b)); + if n > u32(len(b)) { + resize(&b, int(n)); + continue; + } + if n == 3 && b[1] == ':' && b[2] == '\\' { + + } else if n > 0 && b[n-1] == '\\' { + n -= 1; + } + return win32.utf16_to_utf8(b[:n], allocator); + } +} diff --git a/core/os/os2/user.odin b/core/os/os2/user.odin new file mode 100644 index 000000000..b23597387 --- /dev/null +++ b/core/os/os2/user.odin @@ -0,0 +1,68 @@ +package os2 + +import "core:strings" + +user_cache_dir :: proc(allocator := context.allocator) -> (dir: string, is_defined: bool) { + switch ODIN_OS { + case "windows": + dir = get_env("LocalAppData"); + if dir != "" { + dir = strings.clone(dir, allocator); + } + case "darwin": + dir = get_env("HOME"); + if dir != "" { + dir = strings.concatenate({dir, "/Library/Caches"}, allocator); + } + case: // All other UNIX systems + dir = get_env("XDG_CACHE_HOME"); + if dir == "" { + dir = get_env("HOME"); + if dir == "" { + return; + } + dir = strings.concatenate({dir, "/.cache"}, allocator); + } + } + is_defined = dir != ""; + return; +} + +user_config_dir :: proc(allocator := context.allocator) -> (dir: string, is_defined: bool) { + switch ODIN_OS { + case "windows": + dir = get_env("AppData"); + if dir != "" { + dir = strings.clone(dir, allocator); + } + case "darwin": + dir = get_env("HOME"); + if dir != "" { + dir = strings.concatenate({dir, "/Library/Application Support"}, allocator); + } + case: // All other UNIX systems + dir = get_env("XDG_CACHE_HOME"); + if dir == "" { + dir = get_env("HOME"); + if dir == "" { + return; + } + dir = strings.concatenate({dir, "/.config"}, allocator); + } + } + is_defined = dir != ""; + return; +} + +user_home_dir :: proc() -> (dir: string, is_defined: bool) { + env := "HOME"; + switch ODIN_OS { + case "windows": + env = "USERPROFILE"; + } + if v := get_env(env); v != "" { + return v, true; + } + return "", false; +} + From cd2476e08497659c33fa456bce4fc1fc0c97a91c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 14 Apr 2021 20:13:26 +0100 Subject: [PATCH 22/36] Add buffer_read_at buffer_write_at --- core/bytes/buffer.odin | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/core/bytes/buffer.odin b/core/bytes/buffer.odin index 95b816eac..21468e6e8 100644 --- a/core/bytes/buffer.odin +++ b/core/bytes/buffer.odin @@ -135,6 +135,22 @@ buffer_grow :: proc(b: ^Buffer, n: int) { resize(&b.buf, m); } +buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.Error) { + b.last_read = .Invalid; + if offset < 0 { + err = .Invalid_Offset; + return; + } + _, ok := _buffer_try_grow(b, offset+len(p)); + if !ok { + _ = _buffer_grow(b, offset+len(p)); + } + if len(b.buf) <= offset { + return 0, .Short_Write; + } + return copy(b.buf[offset:], p), nil; +} + buffer_write :: proc(b: ^Buffer, p: []byte) -> (n: int, err: io.Error) { b.last_read = .Invalid; @@ -213,6 +229,24 @@ buffer_read :: proc(b: ^Buffer, p: []byte) -> (n: int, err: io.Error) { return; } +buffer_read_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.Error) { + b.last_read = .Invalid; + + if offset < 0 || offset >= len(b.buf) { + err = .Invalid_Offset; + return; + } + + if 0 <= offset && offset < len(b.buf) { + n = copy(p, b.buf[offset:]); + } + if n > 0 { + b.last_read = .Read; + } + return; +} + + buffer_read_byte :: proc(b: ^Buffer) -> (byte, io.Error) { if buffer_is_empty(b) { buffer_reset(b); @@ -346,6 +380,10 @@ _buffer_vtable := &io.Stream_VTable{ b := (^Buffer)(s.stream_data); return buffer_read(b, p); }, + impl_read_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) { + b := (^Buffer)(s.stream_data); + return buffer_read_at(b, p, int(offset)); + }, impl_read_byte = proc(s: io.Stream) -> (byte, io.Error) { b := (^Buffer)(s.stream_data); return buffer_read_byte(b); @@ -358,6 +396,10 @@ _buffer_vtable := &io.Stream_VTable{ b := (^Buffer)(s.stream_data); return buffer_write(b, p); }, + impl_write_at = proc(s: io.Stream, p: []byte, offset: i64) -> (n: int, err: io.Error) { + b := (^Buffer)(s.stream_data); + return buffer_write_at(b, p, int(offset)); + }, impl_write_byte = proc(s: io.Stream, c: byte) -> io.Error { b := (^Buffer)(s.stream_data); return buffer_write_byte(b, c); From d24784074cd5cb7ac340f975b207e6d80abbd5a6 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 14 Apr 2021 20:17:54 +0100 Subject: [PATCH 23/36] Add extra error to io.Error --- core/io/io.odin | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/io/io.odin b/core/io/io.odin index feda1ce64..d36791450 100644 --- a/core/io/io.odin +++ b/core/io/io.odin @@ -23,6 +23,9 @@ Error :: enum i32 { // Short_Write means that a write accepted fewer bytes than requested but failed to return an explicit error Short_Write, + // Invalid_Write means that a write returned an impossible count + Invalid_Write, + // Short_Buffer means that a read required a longer buffer than was provided Short_Buffer, @@ -40,6 +43,9 @@ Error :: enum i32 { Negative_Count, Buffer_Full, + // Unknown means that an error has occurred but cannot be categorized + Unknown, + // Empty is returned when a procedure has not been implemented for an io.Stream Empty = -1, } From 05a181d719c57b6be73ab5cd726c2d418a8a9026 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 14 Apr 2021 20:19:02 +0100 Subject: [PATCH 24/36] Fix style issues; Use new attribute `@(cold)` where appropriate in the new sync package --- core/runtime/internal_windows.odin | 168 ++++++++++++------------- core/sync/sync2/primitives_atomic.odin | 3 +- core/sys/windows/types.odin | 3 +- 3 files changed, 88 insertions(+), 86 deletions(-) diff --git a/core/runtime/internal_windows.odin b/core/runtime/internal_windows.odin index 8bd3106bb..3be79fe19 100644 --- a/core/runtime/internal_windows.odin +++ b/core/runtime/internal_windows.odin @@ -2,134 +2,134 @@ package runtime @(link_name="__umodti3") umodti3 :: proc "c" (a, b: u128) -> u128 { - r: u128 = ---; - _ = udivmod128(a, b, &r); - return r; + r: u128 = ---; + _ = udivmod128(a, b, &r); + return r; } @(link_name="__udivmodti4") udivmodti4 :: proc "c" (a, b: u128, rem: ^u128) -> u128 { - return udivmod128(a, b, rem); + return udivmod128(a, b, rem); } @(link_name="__udivti3") udivti3 :: proc "c" (a, b: u128) -> u128 { - return udivmodti4(a, b, nil); + return udivmodti4(a, b, nil); } @(link_name="__modti3") modti3 :: proc "c" (a, b: i128) -> i128 { - s_a := a >> (128 - 1); - s_b := b >> (128 - 1); - an := (a ~ s_a) - s_a; - bn := (b ~ s_b) - s_b; + s_a := a >> (128 - 1); + s_b := b >> (128 - 1); + an := (a ~ s_a) - s_a; + bn := (b ~ s_b) - s_b; - r: u128 = ---; - _ = udivmod128(transmute(u128)an, transmute(u128)bn, &r); - return (transmute(i128)r ~ s_a) - s_a; + r: u128 = ---; + _ = udivmod128(transmute(u128)an, transmute(u128)bn, &r); + return (transmute(i128)r ~ s_a) - s_a; } @(link_name="__divmodti4") divmodti4 :: proc "c" (a, b: i128, rem: ^i128) -> i128 { - u := udivmod128(transmute(u128)a, transmute(u128)b, cast(^u128)rem); - return transmute(i128)u; + u := udivmod128(transmute(u128)a, transmute(u128)b, cast(^u128)rem); + return transmute(i128)u; } @(link_name="__divti3") divti3 :: proc "c" (a, b: i128) -> i128 { - u := udivmodti4(transmute(u128)a, transmute(u128)b, nil); - return transmute(i128)u; + u := udivmodti4(transmute(u128)a, transmute(u128)b, nil); + return transmute(i128)u; } @(link_name="__fixdfti") fixdfti :: proc(a: u64) -> i128 { - significandBits :: 52; - typeWidth :: (size_of(u64)*8); - exponentBits :: (typeWidth - significandBits - 1); - maxExponent :: ((1 << exponentBits) - 1); - exponentBias :: (maxExponent >> 1); + significandBits :: 52; + typeWidth :: (size_of(u64)*8); + exponentBits :: (typeWidth - significandBits - 1); + maxExponent :: ((1 << exponentBits) - 1); + exponentBias :: (maxExponent >> 1); - implicitBit :: (u64(1) << significandBits); - significandMask :: (implicitBit - 1); - signBit :: (u64(1) << (significandBits + exponentBits)); - absMask :: (signBit - 1); - exponentMask :: (absMask ~ significandMask); + implicitBit :: (u64(1) << significandBits); + significandMask :: (implicitBit - 1); + signBit :: (u64(1) << (significandBits + exponentBits)); + absMask :: (signBit - 1); + exponentMask :: (absMask ~ significandMask); - // Break a into sign, exponent, significand - aRep := a; - aAbs := aRep & absMask; - sign := i128(-1 if aRep & signBit != 0 else 1); - exponent := (aAbs >> significandBits) - exponentBias; - significand := (aAbs & significandMask) | implicitBit; + // Break a into sign, exponent, significand + aRep := a; + aAbs := aRep & absMask; + sign := i128(-1 if aRep & signBit != 0 else 1); + exponent := (aAbs >> significandBits) - exponentBias; + significand := (aAbs & significandMask) | implicitBit; - // If exponent is negative, the result is zero. - if exponent < 0 { - return 0; - } + // If exponent is negative, the result is zero. + if exponent < 0 { + return 0; + } - // If the value is too large for the integer type, saturate. - if exponent >= size_of(i128) * 8 { - return max(i128) if sign == 1 else min(i128); - } + // If the value is too large for the integer type, saturate. + if exponent >= size_of(i128) * 8 { + return max(i128) if sign == 1 else min(i128); + } - // If 0 <= exponent < significandBits, right shift to get the result. - // Otherwise, shift left. - if exponent < significandBits { - return sign * i128(significand >> (significandBits - exponent)); - } else { - return sign * (i128(significand) << (exponent - significandBits)); - } + // If 0 <= exponent < significandBits, right shift to get the result. + // Otherwise, shift left. + if exponent < significandBits { + return sign * i128(significand >> (significandBits - exponent)); + } else { + return sign * (i128(significand) << (exponent - significandBits)); + } } @(default_calling_convention = "none") foreign { - @(link_name="llvm.ctlz.i128") _clz_i128 :: proc(x: i128, is_zero_undef := false) -> i128 --- + @(link_name="llvm.ctlz.i128") _clz_i128 :: proc(x: i128, is_zero_undef := false) -> i128 --- } @(link_name="__floattidf") floattidf :: proc(a: i128) -> f64 { - DBL_MANT_DIG :: 53; - if a == 0 { - return 0.0; - } - a := a; - N :: size_of(i128) * 8; - s := a >> (N-1); - a = (a ~ s) - s; - sd: = N - _clz_i128(a); // number of significant digits - e := u32(sd - 1); // exponent - if sd > DBL_MANT_DIG { - switch sd { - case DBL_MANT_DIG + 1: - a <<= 1; - case DBL_MANT_DIG + 2: - // okay - case: - a = i128(u128(a) >> u128(sd - (DBL_MANT_DIG+2))) | - i128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0); - }; + DBL_MANT_DIG :: 53; + if a == 0 { + return 0.0; + } + a := a; + N :: size_of(i128) * 8; + s := a >> (N-1); + a = (a ~ s) - s; + sd: = N - _clz_i128(a); // number of significant digits + e := u32(sd - 1); // exponent + if sd > DBL_MANT_DIG { + switch sd { + case DBL_MANT_DIG + 1: + a <<= 1; + case DBL_MANT_DIG + 2: + // okay + case: + a = i128(u128(a) >> u128(sd - (DBL_MANT_DIG+2))) | + i128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0); + }; - a |= i128((a & 4) != 0); - a += 1; - a >>= 2; + a |= i128((a & 4) != 0); + a += 1; + a >>= 2; - if a & (1 << DBL_MANT_DIG) != 0 { - a >>= 1; - e += 1; - } - } else { - a <<= u128(DBL_MANT_DIG - sd); - } - fb: [2]u32; - fb[1] = (u32(s) & 0x80000000) | // sign - ((e + 1023) << 20) | // exponent - ((u32(a) >> 32) & 0x000FFFFF); // mantissa-high - fb[1] = u32(a); // mantissa-low - return transmute(f64)fb; + if a & (1 << DBL_MANT_DIG) != 0 { + a >>= 1; + e += 1; + } + } else { + a <<= u128(DBL_MANT_DIG - sd); + } + fb: [2]u32; + fb[1] = (u32(s) & 0x80000000) | // sign + ((e + 1023) << 20) | // exponent + ((u32(a) >> 32) & 0x000FFFFF); // mantissa-high + fb[1] = u32(a); // mantissa-low + return transmute(f64)fb; } diff --git a/core/sync/sync2/primitives_atomic.odin b/core/sync/sync2/primitives_atomic.odin index d65403076..610ab7ee0 100644 --- a/core/sync/sync2/primitives_atomic.odin +++ b/core/sync/sync2/primitives_atomic.odin @@ -38,7 +38,7 @@ _mutex_try_lock :: proc(m: ^Mutex) -> bool { } - +@(cold) _mutex_lock_slow :: proc(m: ^Mutex, curr_state: _Mutex_State) { new_state := curr_state; // Make a copy of it @@ -68,6 +68,7 @@ _mutex_lock_slow :: proc(m: ^Mutex, curr_state: _Mutex_State) { } +@(cold) _mutex_unlock_slow :: proc(m: ^Mutex) { // TODO(bill): Use a Futex here for Linux to improve performance and error handling } diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index 65aa3a113..f42d11cd4 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -232,6 +232,7 @@ PROGRESS_CONTINUE: DWORD : 0; ERROR_FILE_NOT_FOUND: DWORD : 2; ERROR_PATH_NOT_FOUND: DWORD : 3; ERROR_ACCESS_DENIED: DWORD : 5; +ERROR_NOT_ENOUGH_MEMORY: DWORD : 8; ERROR_INVALID_HANDLE: DWORD : 6; ERROR_NO_MORE_FILES: DWORD : 18; ERROR_SHARING_VIOLATION: DWORD : 32; @@ -1233,4 +1234,4 @@ NET_API_STATUS :: enum DWORD { PasswordTooLong = 2703, PasswordNotComplexEnough = 2704, PasswordFilterError = 2705, -} \ No newline at end of file +} From e19958152a7949860d77ebb53c4dd17c862fa893 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 14 Apr 2021 20:45:05 +0100 Subject: [PATCH 25/36] Fix `floattidf` --- core/runtime/internal_linux.odin | 14 +++++++------- core/runtime/internal_windows.odin | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/core/runtime/internal_linux.odin b/core/runtime/internal_linux.odin index 241ed0fdf..aecd7f601 100644 --- a/core/runtime/internal_linux.odin +++ b/core/runtime/internal_linux.odin @@ -103,7 +103,7 @@ floattidf :: proc(a: i128) -> f64 { s := a >> (N-1); a = (a ~ s) - s; sd: = N - _clz_i128(a); // number of significant digits - e := u32(sd - 1); // exponent + e := u32(sd - 1); // exponent if sd > DBL_MANT_DIG { switch sd { case DBL_MANT_DIG + 1: @@ -115,8 +115,8 @@ floattidf :: proc(a: i128) -> f64 { i128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0); }; - a |= i128((a & 4) != 0); - a += 1; + a |= i128((a & 4) != 0); + a += 1; a >>= 2; if a & (1 << DBL_MANT_DIG) != 0 { @@ -127,9 +127,9 @@ floattidf :: proc(a: i128) -> f64 { a <<= u128(DBL_MANT_DIG - sd); } fb: [2]u32; - fb[1] = (u32(s) & 0x80000000) | // sign - ((e + 1023) << 20) | // exponent - ((u32(a) >> 32) & 0x000FFFFF); // mantissa-high - fb[1] = u32(a); // mantissa-low + fb[1] = (u32(s) & 0x80000000) | // sign + ((e + 1023) << 20) | // exponent + u32((u64(a) >> 32) & 0x000FFFFF); // mantissa-high + fb[1] = u32(a); // mantissa-low return transmute(f64)fb; } diff --git a/core/runtime/internal_windows.odin b/core/runtime/internal_windows.odin index 3be79fe19..79a4bcdcb 100644 --- a/core/runtime/internal_windows.odin +++ b/core/runtime/internal_windows.odin @@ -127,9 +127,9 @@ floattidf :: proc(a: i128) -> f64 { a <<= u128(DBL_MANT_DIG - sd); } fb: [2]u32; - fb[1] = (u32(s) & 0x80000000) | // sign - ((e + 1023) << 20) | // exponent - ((u32(a) >> 32) & 0x000FFFFF); // mantissa-high - fb[1] = u32(a); // mantissa-low + fb[1] = (u32(s) & 0x80000000) | // sign + ((e + 1023) << 20) | // exponent + u32((u64(a) >> 32) & 0x000FFFFF); // mantissa-high + fb[1] = u32(a); // mantissa-low return transmute(f64)fb; } From 8827818b1d5aa88fee6058a8c93a3df9c363fa1d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 14 Apr 2021 21:25:46 +0100 Subject: [PATCH 26/36] Clean-up fallback `io.read_at` and `io.write_at` behaviour --- core/io/io.odin | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/core/io/io.odin b/core/io/io.odin index d36791450..3727e219e 100644 --- a/core/io/io.odin +++ b/core/io/io.odin @@ -218,17 +218,17 @@ read_at :: proc(r: Reader_At, p: []byte, offset: i64) -> (n: int, err: Error) { return 0, .Empty; } - current_offset: i64; - current_offset, err = r->impl_seek(offset, .Current); + curr_offset: i64; + curr_offset, err = r->impl_seek(offset, .Current); if err != nil { return 0, err; } n, err = r->impl_read(p); - if err != nil { - return; + _, err1 := r->impl_seek(curr_offset, .Start); + if err1 != nil && err == nil { + err = err1; } - _, err = r->impl_seek(current_offset, .Start); return; } @@ -244,14 +244,18 @@ write_at :: proc(w: Writer_At, p: []byte, offset: i64) -> (n: int, err: Error) { return 0, .Empty; } - current_offset: i64; - current_offset, err = w->impl_seek(offset, .Current); + curr_offset: i64; + curr_offset, err = w->impl_seek(offset, .Current); if err != nil { return 0, err; } - defer w->impl_seek(current_offset, .Start); - return w->impl_write(p); + n, err = w->impl_write(p); + _, err1 := w->impl_seek(curr_offset, .Start); + if err1 != nil && err == nil { + err = err1; + } + return; } write_to :: proc(r: Writer_To, w: Writer) -> (n: i64, err: Error) { From 2f1c89629021cda7880f010f6a7e2e484fb92a46 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 18 Apr 2021 18:33:15 +0100 Subject: [PATCH 27/36] Add `-doc-format` command for the new .odin-doc file format (to be used to generate documentation tools) --- core/odin/doc-format/doc_format.odin | 251 +++++++ src/build_settings.cpp | 1 + src/check_type.cpp | 1 - src/docs.cpp | 79 +- src/docs_format.cpp | 208 ++++++ src/docs_writer.cpp | 1023 ++++++++++++++++++++++++++ src/llvm_backend.cpp | 1 - src/main.cpp | 5 + src/types.cpp | 1 - 9 files changed, 1551 insertions(+), 19 deletions(-) create mode 100644 core/odin/doc-format/doc_format.odin create mode 100644 src/docs_format.cpp create mode 100644 src/docs_writer.cpp diff --git a/core/odin/doc-format/doc_format.odin b/core/odin/doc-format/doc_format.odin new file mode 100644 index 000000000..e8f6d40f2 --- /dev/null +++ b/core/odin/doc-format/doc_format.odin @@ -0,0 +1,251 @@ +package odin_doc_format + +import "core:mem" + +Array :: struct($T: typeid) { + offset: u32, + length: u32, +} + +String :: distinct Array(byte); + +Version_Type_Major :: 0; +Version_Type_Minor :: 1; +Version_Type_Patch :: 0; + +Version_Type :: struct { + major, minor, patch: u8, + _: u8, +}; + +Version_Type_Default :: Version_Type{ + major=Version_Type_Major, + minor=Version_Type_Minor, + patch=Version_Type_Patch, +}; + +Magic_String :: "odindoc\x00"; + +Header_Base :: struct { + magic: [8]byte, + _: u32, + version: Version_Type, + total_size: u32, + header_size: u32, + hash: u32, +} + +Header :: struct { + using base: Header_Base, + + // NOTE: These arrays reserve the zero element as a sentinel value + files: Array(File), + pkgs: Array(Pkg), + entities: Array(Entity), + types: Array(Type), +} + +File_Index :: distinct u32; +Pkg_Index :: distinct u32; +Entity_Index :: distinct u32; +Type_Index :: distinct u32; + + +Position :: struct { + file: File_Index, + line: u32, + column: u32, + offset: u32, +}; + +File :: struct { + pkg: Pkg_Index, + name: String, +} + +Pkg :: struct { + fullpath: String, + name: String, + docs: String, + files: Array(File_Index), + entities: Array(Entity_Index), +} + +Entity_Kind :: enum u32 { + Invalid = 0, + Constant = 1, + Variable = 2, + Type_Name = 3, + Procedure = 4, + Proc_Group = 5, + Import_Name = 6, + Library_Name = 7, +} + +Entity_Flag :: enum u32 { + Foreign = 0, + Export = 1, + + Param_Using = 2, + Param_Const = 3, + Param_Auto_Cast = 4, + Param_Ellipsis = 5, + Param_CVararg = 6, + Param_No_Alias = 7, + + Type_Alias = 8, + + Var_Thread_Local = 9, +} + +Entity_Flags :: distinct bit_set[Entity_Flag; u32]; + +Entity :: struct { + kind: Entity_Kind, + flags: Entity_Flags, + pos: Position, + name: String, + type: Type_Index, + init_string: String, + _: u32, + comment: String, + docs: String, + foreign_library: Entity_Index, + link_name: String, + attributes: Array(Attribute), + grouped_entities: Array(Entity_Index), // Procedure Groups + where_clauses: Array(String), // Procedures +} + +Attribute :: struct { + name: String, + value: String, +} + +Type_Kind :: enum u32 { + Invalid = 0, + Basic = 1, + Named = 2, + Generic = 3, + Pointer = 4, + Array = 5, + Enumerated_Array = 6, + Slice = 7, + Dynamic_Array = 8, + Map = 9, + Struct = 10, + Union = 11, + Enum = 12, + Tuple = 13, + Proc = 14, + Bit_Set = 15, + Simd_Vector = 16, + SOA_Struct_Fixed = 17, + SOA_Struct_Slice = 18, + SOA_Struct_Dynamic = 19, + Relative_Pointer = 20, + Relative_Slice = 21, +} + +Type_Elems_Cap :: 4; + +Type :: struct { + kind: Type_Kind, + flags: u32, // Type_Kind specific + name: String, + custom_align: String, + + // Used by some types + elem_count_len: u32, + elem_counts: [Type_Elems_Cap]u64, + + // Each of these is esed by some types, not all + types: Array(Type_Index), + entities: Array(Entity_Index), + polymorphic_params: Type_Index, // Struct, Union + where_clauses: Array(String), // Struct, Union +} + +Type_Flags_Basic :: distinct bit_set[Type_Flag_Basic; u32]; +Type_Flag_Basic :: enum u32 { + Untyped = 1, +} + +Type_Flags_Struct :: distinct bit_set[Type_Flag_Struct; u32]; +Type_Flag_Struct :: enum u32 { + Polymorphic = 0, + Packed = 1, + Raw_Union = 2, +} + +Type_Flags_Union :: distinct bit_set[Type_Flag_Union; u32]; +Type_Flag_Union :: enum u32 { + Polymorphic = 0, + No_Nil = 1, + Maybe = 2, +} + +Type_Flags_Proc :: distinct bit_set[Type_Flag_Proc; u32]; +Type_Flag_Proc :: enum u32 { + Polymorphic = 0, + Diverging = 1, + Optional_Ok = 2, + Variadic = 3, + C_Vararg = 4, +} + +Type_Flags_BitSet :: distinct bit_set[Type_Flag_BitSet; u32]; +Type_Flag_BitSet :: enum u32 { + Range = 1, + Op_Lt = 2, + Op_Lt_Eq = 3, + Underlying_Type = 4, +} + +Type_Flags_SimdVector :: distinct bit_set[Type_Flag_SimdVector; u32]; +Type_Flag_SimdVector :: enum u32 { + x86_mmx = 1, +} + +from_array :: proc(base: ^Header_Base, a: $A/Array($T)) -> []T { + s: mem.Raw_Slice; + s.data = rawptr(uintptr(base) + uintptr(a.offset)); + s.len = int(a.length); + return transmute([]T)s; +} +from_string :: proc(base: ^Header_Base, s: String) -> string { + return string(from_array(base, s)); +} + + + + +Reader_Error :: enum { + None, + Header_Too_Small, + Invalid_Magic, + Data_Too_Small, + Invalid_Version, +} + +read_from_bytes :: proc(data: []byte) -> (h: ^Header, err: Reader_Error) { + if len(data) < size_of(Header_Base) { + err = .Header_Too_Small; + return; + } + header_base := (^Header_Base)(raw_data(data)); + if header_base.magic != Magic_String { + err = .Invalid_Magic; + return; + } + if len(data) < int(header_base.total_size) { + err = .Data_Too_Small; + return; + } + if header_base.version != Version_Type_Default { + err = .Invalid_Version; + return; + } + h = (^Header)(header_base); + return; +} diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 6d32d1c8d..8df045a82 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -135,6 +135,7 @@ char const *odin_command_strings[32] = { enum CmdDocFlag : u32 { CmdDocFlag_Short = 1<<0, CmdDocFlag_AllPackages = 1<<1, + CmdDocFlag_DocFormat = 1<<2, }; diff --git a/src/check_type.cpp b/src/check_type.cpp index 8420c4687..de81592c8 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2461,7 +2461,6 @@ bool check_procedure_type(CheckerContext *ctx, Type *type, Ast *proc_type_node, type->Proc.specialization_count = specialization_count; type->Proc.diverging = pt->diverging; type->Proc.optional_ok = optional_ok; - type->Proc.tags = pt->tags; if (param_count > 0) { Entity *end = params->Tuple.variables[param_count-1]; diff --git a/src/docs.cpp b/src/docs.cpp index aa1b89560..65166faa4 100644 --- a/src/docs.cpp +++ b/src/docs.cpp @@ -34,9 +34,17 @@ GB_COMPARE_PROC(cmp_entities_for_printing) { Entity *x = *cast(Entity **)a; Entity *y = *cast(Entity **)b; int res = 0; - res = string_compare(x->pkg->name, y->pkg->name); - if (res != 0) { - return res; + if (x->pkg != y->pkg) { + if (x->pkg == nullptr) { + return -1; + } + if (y->pkg == nullptr) { + return +1; + } + res = string_compare(x->pkg->name, y->pkg->name); + if (res != 0) { + return res; + } } int ox = print_entity_kind_ordering[x->kind]; int oy = print_entity_kind_ordering[y->kind]; @@ -56,6 +64,9 @@ GB_COMPARE_PROC(cmp_ast_package_by_name) { return string_compare(x->name, y->name); } +#include "docs_format.cpp" +#include "docs_writer.cpp" + void print_doc_line(i32 indent, char const *fmt, ...) { while (indent --> 0) { gb_printf("\t"); @@ -297,23 +308,59 @@ void print_doc_package(CheckerInfo *info, AstPackage *pkg) { void generate_documentation(Checker *c) { CheckerInfo *info = &c->info; - auto pkgs = array_make(permanent_allocator(), 0, info->packages.entries.count); - for_array(i, info->packages.entries) { - AstPackage *pkg = info->packages.entries[i].value; - if (build_context.cmd_doc_flags & CmdDocFlag_AllPackages) { - array_add(&pkgs, pkg); + if (build_context.cmd_doc_flags & CmdDocFlag_DocFormat) { + String init_fullpath = c->parser->init_fullpath; + String output_name = {}; + String output_base = {}; + + if (build_context.out_filepath.len == 0) { + output_name = remove_directory_from_path(init_fullpath); + output_name = remove_extension_from_path(output_name); + output_name = string_trim_whitespace(output_name); + if (output_name.len == 0) { + output_name = info->init_scope->pkg->name; + } + output_base = output_name; } else { - if (pkg->kind == Package_Init) { - array_add(&pkgs, pkg); - } else if (pkg->is_extra) { - array_add(&pkgs, pkg); + output_name = build_context.out_filepath; + output_name = string_trim_whitespace(output_name); + if (output_name.len == 0) { + output_name = info->init_scope->pkg->name; + } + isize pos = string_extension_position(output_name); + if (pos < 0) { + output_base = output_name; + } else { + output_base = substring(output_name, 0, pos); } } - } - gb_sort_array(pkgs.data, pkgs.count, cmp_ast_package_by_name); + output_base = path_to_full_path(permanent_allocator(), output_base); - for_array(i, pkgs) { - print_doc_package(info, pkgs[i]); + gbString output_file_path = gb_string_make_length(heap_allocator(), output_base.text, output_base.len); + output_file_path = gb_string_appendc(output_file_path, ".odin-doc"); + defer (gb_string_free(output_file_path)); + + odin_doc_write(info, output_file_path); + } else { + auto pkgs = array_make(permanent_allocator(), 0, info->packages.entries.count); + for_array(i, info->packages.entries) { + AstPackage *pkg = info->packages.entries[i].value; + if (build_context.cmd_doc_flags & CmdDocFlag_AllPackages) { + array_add(&pkgs, pkg); + } else { + if (pkg->kind == Package_Init) { + array_add(&pkgs, pkg); + } else if (pkg->is_extra) { + array_add(&pkgs, pkg); + } + } + } + + gb_sort_array(pkgs.data, pkgs.count, cmp_ast_package_by_name); + + for_array(i, pkgs) { + print_doc_package(info, pkgs[i]); + } } } diff --git a/src/docs_format.cpp b/src/docs_format.cpp new file mode 100644 index 000000000..f30a0bf43 --- /dev/null +++ b/src/docs_format.cpp @@ -0,0 +1,208 @@ +#define OdinDocHeader_MagicString "odindoc\0" + +template +struct OdinDocArray { + u32 offset; + u32 length; +}; + +using OdinDocString = OdinDocArray; + +struct OdinDocVersionType { + u8 major, minor, patch; + u8 pad0; +}; + +#define OdinDocVersionType_Major 0 +#define OdinDocVersionType_Minor 1 +#define OdinDocVersionType_Patch 0 + +struct OdinDocHeaderBase { + u8 magic[8]; + u32 padding0; + OdinDocVersionType version; + u32 total_size; + u32 header_size; + u32 hash; // after header +}; + +template +Slice from_array(OdinDocHeaderBase *base, OdinDocArray const &a) { + Slice s = {}; + s.data = cast(T *)(cast(uintptr)base + cast(uintptr)a.offset); + s.count = cast(isize)a.length; + return s; +} + +String from_string(OdinDocHeaderBase *base, OdinDocString const &s) { + String str = {}; + str.text = cast(u8 *)(cast(uintptr)base + cast(uintptr)s.offset); + str.len = cast(isize)s.length; + return str; +} + +typedef u32 OdinDocFileIndex; +typedef u32 OdinDocPkgIndex; +typedef u32 OdinDocEntityIndex; +typedef u32 OdinDocTypeIndex; + +struct OdinDocFile { + OdinDocPkgIndex pkg; + OdinDocString name; +}; + +struct OdinDocPosition { + OdinDocFileIndex file; + u32 line; + u32 column; + u32 offset; +}; + +enum OdinDocTypeKind : u32 { + OdinDocType_Invalid = 0, + OdinDocType_Basic = 1, + OdinDocType_Named = 2, + OdinDocType_Generic = 3, + OdinDocType_Pointer = 4, + OdinDocType_Array = 5, + OdinDocType_EnumeratedArray = 6, + OdinDocType_Slice = 7, + OdinDocType_DynamicArray = 8, + OdinDocType_Map = 9, + OdinDocType_Struct = 10, + OdinDocType_Union = 11, + OdinDocType_Enum = 12, + OdinDocType_Tuple = 13, + OdinDocType_Proc = 14, + OdinDocType_BitSet = 15, + OdinDocType_SimdVector = 16, + OdinDocType_SOAStructFixed = 17, + OdinDocType_SOAStructSlice = 18, + OdinDocType_SOAStructDynamic = 19, + OdinDocType_RelativePointer = 20, + OdinDocType_RelativeSlice = 21, +}; + +enum OdinDocTypeFlag_Basic : u32 { + OdinDocTypeFlag_Basic_untyped = 1<<1, +}; + +enum OdinDocTypeFlag_Struct : u32 { + OdinDocTypeFlag_Struct_polymorphic = 1<<0, + OdinDocTypeFlag_Struct_packed = 1<<1, + OdinDocTypeFlag_Struct_raw_union = 1<<2, +}; + +enum OdinDocTypeFlag_Union : u32 { + OdinDocTypeFlag_Union_polymorphic = 1<<0, + OdinDocTypeFlag_Union_no_nil = 1<<1, + OdinDocTypeFlag_Union_maybe = 1<<2, +}; + +enum OdinDocTypeFlag_Proc : u32 { + OdinDocTypeFlag_Proc_polymorphic = 1<<0, + OdinDocTypeFlag_Proc_diverging = 1<<1, + OdinDocTypeFlag_Proc_optional_ok = 1<<2, + OdinDocTypeFlag_Proc_variadic = 1<<3, + OdinDocTypeFlag_Proc_c_vararg = 1<<4, +}; + +enum OdinDocTypeFlag_BitSet : u32 { + OdinDocTypeFlag_BitSet_Range = 1<<1, + OdinDocTypeFlag_BitSet_OpLt = 1<<2, + OdinDocTypeFlag_BitSet_OpLtEq = 1<<3, + OdinDocTypeFlag_BitSet_UnderlyingType = 1<<4, +}; + +enum OdinDocTypeFlag_SimdVector : u32 { + OdinDocTypeFlag_BitSet_x86_mmx = 1<<1, +}; + +enum { + // constants + OdinDocType_ElemsCap = 4, +}; + +struct OdinDocType { + OdinDocTypeKind kind; + u32 flags; + OdinDocString name; + OdinDocString custom_align; + + // Used by some types + u32 elem_count_len; + u64 elem_counts[OdinDocType_ElemsCap]; + + // Each of these is esed by some types, not all + OdinDocArray types; + OdinDocArray entities; + OdinDocTypeIndex polmorphic_params; + OdinDocArray where_clauses; +}; + +struct OdinDocAttribute { + OdinDocString name; + OdinDocString value; +}; + +enum OdinDocEntityKind : u32 { + OdinDocEntity_Invalid = 0, + OdinDocEntity_Constant = 1, + OdinDocEntity_Variable = 2, + OdinDocEntity_TypeName = 3, + OdinDocEntity_Procedure = 4, + OdinDocEntity_ProcGroup = 5, + OdinDocEntity_ImportName = 6, + OdinDocEntity_LibraryName = 7, +}; + +enum OdinDocEntityFlag : u32 { + OdinDocEntityFlag_Foreign = 1<<0, + OdinDocEntityFlag_Export = 1<<1, + + OdinDocEntityFlag_Param_Using = 1<<2, + OdinDocEntityFlag_Param_Const = 1<<3, + OdinDocEntityFlag_Param_AutoCast = 1<<4, + OdinDocEntityFlag_Param_Ellipsis = 1<<5, + OdinDocEntityFlag_Param_CVararg = 1<<6, + OdinDocEntityFlag_Param_NoAlias = 1<<7, + + OdinDocEntityFlag_Type_Alias = 1<<8, + + OdinDocEntityFlag_Var_Thread_Local = 1<<9, +}; + +struct OdinDocEntity { + OdinDocEntityKind kind; + u32 flags; + OdinDocPosition pos; + OdinDocString name; + OdinDocTypeIndex type; + OdinDocString init_string; + u32 reserved_for_init; + OdinDocString comment; + OdinDocString docs; + OdinDocEntityIndex foreign_library; + OdinDocString link_name; + OdinDocArray attributes; + OdinDocArray grouped_entities; // Procedure Groups + OdinDocArray where_clauses; // Procedures +}; + +struct OdinDocPkg { + OdinDocString fullpath; + OdinDocString name; + OdinDocString docs; + OdinDocArray files; + OdinDocArray entities; +}; + +struct OdinDocHeader { + OdinDocHeaderBase base; + + OdinDocArray files; + OdinDocArray pkgs; + OdinDocArray entities; + OdinDocArray types; +}; + diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp new file mode 100644 index 000000000..daae40930 --- /dev/null +++ b/src/docs_writer.cpp @@ -0,0 +1,1023 @@ + +template +struct OdinDocWriterItemTracker { + isize len; + isize cap; + isize offset; +}; + +enum OdinDocWriterState { + OdinDocWriterState_Preparing, + OdinDocWriterState_Writing, +}; + +char const* OdinDocWriterState_strings[] { + "preparing", + "writing ", +}; + +struct OdinDocWriter { + CheckerInfo *info; + OdinDocWriterState state; + + void *data; + isize data_len; + OdinDocHeader *header; + + StringMap string_cache; + + Map file_cache; // Key: AstFile * + Map pkg_cache; // Key: AstPackage * + Map entity_cache; // Key: Entity * + Map entity_id_cache; // Key: OdinDocEntityIndex + Map type_cache; // Key: Type * + Map type_id_cache; // Key: OdinDocTypeIndex + + OdinDocWriterItemTracker files; + OdinDocWriterItemTracker pkgs; + OdinDocWriterItemTracker entities; + OdinDocWriterItemTracker types; + + OdinDocWriterItemTracker strings; + OdinDocWriterItemTracker blob; +}; + +OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e); +OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type); + +template +void odin_doc_writer_item_tracker_init(OdinDocWriterItemTracker *t, isize size) { + t->len = size; + t->cap = size; +} + + +void odin_doc_writer_prepare(OdinDocWriter *w) { + w->state = OdinDocWriterState_Preparing; + + gbAllocator a = heap_allocator(); + string_map_init(&w->string_cache, a); + + map_init(&w->file_cache, a); + map_init(&w->pkg_cache, a); + map_init(&w->entity_cache, a); + map_init(&w->entity_id_cache, a); + map_init(&w->type_cache, a); + map_init(&w->type_id_cache, a); + + odin_doc_writer_item_tracker_init(&w->files, 1); + odin_doc_writer_item_tracker_init(&w->pkgs, 1); + odin_doc_writer_item_tracker_init(&w->entities, 1); + odin_doc_writer_item_tracker_init(&w->types, 1); + odin_doc_writer_item_tracker_init(&w->strings, 16); + odin_doc_writer_item_tracker_init(&w->blob, 16); +} + + +void odin_doc_writer_destroy(OdinDocWriter *w) { + gb_free(heap_allocator(), w->data); + + string_map_destroy(&w->string_cache); + map_destroy(&w->file_cache); + map_destroy(&w->pkg_cache); + map_destroy(&w->entity_cache); + map_destroy(&w->entity_id_cache); + map_destroy(&w->type_cache); + map_destroy(&w->type_id_cache); +} + + + +template +void odin_doc_writer_tracker_size(isize *offset, OdinDocWriterItemTracker *t, isize alignment=1) { + isize size = t->cap*gb_size_of(T); + isize align = gb_max(gb_align_of(T), alignment); + *offset = align_formula_isize(*offset, align); + t->offset = *offset; + *offset += size; +} + +isize odin_doc_writer_calc_total_size(OdinDocWriter *w) { + isize total_size = gb_size_of(OdinDocHeader); + odin_doc_writer_tracker_size(&total_size, &w->files); + odin_doc_writer_tracker_size(&total_size, &w->pkgs); + odin_doc_writer_tracker_size(&total_size, &w->entities); + odin_doc_writer_tracker_size(&total_size, &w->types); + odin_doc_writer_tracker_size(&total_size, &w->strings, 16); + odin_doc_writer_tracker_size(&total_size, &w->blob, 16); + return total_size; +} + +void odin_doc_writer_start_writing(OdinDocWriter *w) { + w->state = OdinDocWriterState_Writing; + + string_map_clear(&w->string_cache); + map_clear(&w->file_cache); + map_clear(&w->pkg_cache); + map_clear(&w->entity_cache); + map_clear(&w->entity_id_cache); + map_clear(&w->type_cache); + map_clear(&w->type_id_cache); + + isize total_size = odin_doc_writer_calc_total_size(w); + total_size = align_formula_isize(total_size, 8); + w->data = gb_alloc_align(heap_allocator(), total_size, 8); + w->data_len = total_size; + w->header = cast(OdinDocHeader *)w->data; +} + +u32 hash_data_after_header(OdinDocHeaderBase *base, void *data, isize data_len) { + u8 *start = cast(u8 *)data; + u8 *end = start + base->total_size; + start += base->header_size; + + u32 h = 0x811c9dc5; + for (u8 *b = start; b != end; b++) { + h = (h ^ cast(u32)*b) * 0x01000193; + } + return h; +} + + +template +void odin_doc_writer_assign_tracker(OdinDocArray *array, OdinDocWriterItemTracker const &t) { + array->offset = cast(u32)t.offset; + array->length = cast(u32)t.len; +} + + +void odin_doc_writer_end_writing(OdinDocWriter *w) { + OdinDocHeader *h = w->header; + + gb_memmove(h->base.magic, OdinDocHeader_MagicString, gb_strlen(OdinDocHeader_MagicString)); + h->base.version.major = OdinDocVersionType_Major; + h->base.version.minor = OdinDocVersionType_Minor; + h->base.version.patch = OdinDocVersionType_Patch; + h->base.total_size = cast(u32)w->data_len; + h->base.header_size = gb_size_of(*h); + h->base.hash = hash_data_after_header(&h->base, w->data, w->data_len); + + odin_doc_writer_assign_tracker(&h->files, w->files); + odin_doc_writer_assign_tracker(&h->pkgs, w->pkgs); + odin_doc_writer_assign_tracker(&h->entities, w->entities); + odin_doc_writer_assign_tracker(&h->types, w->types); +} + +template +u32 odin_doc_write_item(OdinDocWriter *w, OdinDocWriterItemTracker *t, T const *item, T **dst=nullptr) { + if (w->state == OdinDocWriterState_Preparing) { + t->cap += 1; + if (dst) *dst = nullptr; + return 0; + } else { + GB_ASSERT_MSG(t->len < t->cap, "%td < %td", t->len, t->cap); + isize item_index = t->len++; + uintptr data = cast(uintptr)w->data + cast(uintptr)(t->offset + gb_size_of(T)*item_index); + if (item) { + gb_memmove(cast(T *)data, item, gb_size_of(T)); + } + if (dst) *dst = cast(T *)data; + + return cast(u32)item_index; + } +} + +template +T *odin_doc_get_item(OdinDocWriter *w, OdinDocWriterItemTracker *t, u32 index) { + if (w->state != OdinDocWriterState_Writing) { + return nullptr; + } + GB_ASSERT(index < t->len); + uintptr data = cast(uintptr)w->data + cast(uintptr)(t->offset + gb_size_of(T)*index); + return cast(T *)data; +} + +OdinDocString odin_doc_write_string_without_cache(OdinDocWriter *w, String const &str) { + OdinDocString res = {}; + + if (w->state == OdinDocWriterState_Preparing) { + w->strings.cap += str.len+1; + } else { + GB_ASSERT_MSG(w->strings.len+str.len+1 <= w->strings.cap, "%td <= %td", w->strings.len+str.len, w->strings.cap); + + isize offset = w->strings.offset + w->strings.len; + u8 *data = cast(u8 *)w->data + offset; + gb_memmove(data, str.text, str.len); + data[str.len] = 0; + w->strings.len += str.len+1; + res.offset = cast(u32)offset; + res.length = cast(u32)str.len; + } + + return res; +} + +OdinDocString odin_doc_write_string(OdinDocWriter *w, String const &str) { + OdinDocString *c = string_map_get(&w->string_cache, str); + if (c != nullptr) { + if (w->state == OdinDocWriterState_Writing) { + GB_ASSERT(from_string(&w->header->base, *c) == str); + } + return *c; + } + + OdinDocString res = odin_doc_write_string_without_cache(w, str); + + string_map_set(&w->string_cache, str, res); + + return res; +} + + + +template +OdinDocArray odin_write_slice(OdinDocWriter *w, T *data, isize len) { + GB_ASSERT(gb_align_of(T) <= 4); + if (len <= 0) { + return {0, 0}; + } + isize alignment = 4; + + if (w->state == OdinDocWriterState_Preparing) { + w->blob.cap = align_formula_isize(w->blob.cap, alignment); + w->blob.cap += len * gb_size_of(T); + return {0, 0}; + } + + w->blob.len = align_formula_isize(w->blob.len, alignment); + + isize offset = w->blob.offset + w->blob.len; + u8 *dst = cast(u8 *)w->data + offset; + gb_memmove(dst, data, len*gb_size_of(T)); + + w->blob.len += len * gb_size_of(T); + + return {cast(u32)offset, cast(u32)len}; +} + + +template +OdinDocArray odin_write_item_as_slice(OdinDocWriter *w, T data) { + return odin_write_slice(w, &data, 1); +} + + +OdinDocPosition odin_doc_token_pos_cast(OdinDocWriter *w, TokenPos const &pos) { + OdinDocFileIndex file_index = 0; + if (pos.file_id != 0) { + String file_path = get_file_path_string(pos.file_id); + if (file_path != "") { + AstFile **found = string_map_get(&w->info->files, file_path); + GB_ASSERT(found != nullptr); + AstFile *file = *found; + OdinDocFileIndex *file_index_found = map_get(&w->file_cache, hash_pointer(file)); + GB_ASSERT(file_index_found != nullptr); + file_index = *file_index_found; + } + } + + OdinDocPosition doc_pos = {}; + doc_pos.file = file_index; + doc_pos.line = cast(u32)pos.line; + doc_pos.column = cast(u32)pos.column; + doc_pos.offset = cast(u32)pos.offset; + return doc_pos; +} + +bool odin_doc_append_comment_group_string(Array *buf, CommentGroup *g) { + if (g == nullptr) { + return false; + } + isize len = 0; + for_array(i, g->list) { + String comment = g->list[i].string; + len += comment.len; + len += 1; // for \n + } + if (len <= g->list.count) { + return false; + } + + isize count = 0; + for_array(i, g->list) { + String comment = g->list[i].string; + String original_comment = comment; + + bool slash_slash = comment[1] == '/'; + bool slash_star = comment[1] == '*'; + if (comment[1] == '/') { + comment.text += 2; + comment.len -= 2; + } else if (comment[1] == '*') { + comment.text += 2; + comment.len -= 4; + } + + // Ignore the first space + if (comment.len > 0 && comment[0] == ' ') { + comment.text += 1; + comment.len -= 1; + } + + if (slash_slash) { + if (string_starts_with(comment, str_lit("+"))) { + continue; + } + if (string_starts_with(comment, str_lit("@("))) { + continue; + } + } + + if (slash_slash) { + array_add_elems(buf, comment.text, comment.len); + array_add(buf, cast(u8)'\n'); + count += 1; + } else { + isize pos = 0; + for (; pos < comment.len; pos++) { + isize end = pos; + for (; end < comment.len; end++) { + if (comment[end] == '\n') { + break; + } + } + String line = substring(comment, pos, end); + pos = end+1; + String trimmed_line = string_trim_whitespace(line); + if (trimmed_line.len == 0) { + if (count == 0) { + continue; + } + } + /* + * Remove comments with + * styles + * like this + */ + if (string_starts_with(line, str_lit("* "))) { + line = substring(line, 2, line.len); + } + + array_add_elems(buf, line.text, line.len); + array_add(buf, cast(u8)'\n'); + count += 1; + } + } + } + + if (count > 0) { + array_add(buf, cast(u8)'\n'); + return true; + } + return false; +} + +OdinDocString odin_doc_pkg_doc_string(OdinDocWriter *w, AstPackage *pkg) { + if (pkg == nullptr) { + return {}; + } + auto buf = array_make(permanent_allocator(), 0, 0); // Minor leak + + for_array(i, pkg->files) { + AstFile *f = pkg->files[i]; + if (f->pkg_decl) { + GB_ASSERT(f->pkg_decl->kind == Ast_PackageDecl); + odin_doc_append_comment_group_string(&buf, f->pkg_decl->PackageDecl.docs); + } + } + + return odin_doc_write_string_without_cache(w, make_string(buf.data, buf.count)); +} + +OdinDocString odin_doc_comment_group_string(OdinDocWriter *w, CommentGroup *g) { + if (g == nullptr) { + return {}; + } + auto buf = array_make(permanent_allocator(), 0, 0); // Minor leak + + odin_doc_append_comment_group_string(&buf, g); + + return odin_doc_write_string_without_cache(w, make_string(buf.data, buf.count)); +} + +OdinDocString odin_doc_expr_string(OdinDocWriter *w, Ast *expr) { + if (expr == nullptr) { + return {}; + } + gbString s = write_expr_to_string( // Minor leak + gb_string_make(permanent_allocator(), ""), + expr, + build_context.cmd_doc_flags & CmdDocFlag_Short + ); + + return odin_doc_write_string(w, make_string(cast(u8 *)s, gb_string_length(s))); +} + +OdinDocArray odin_doc_attributes(OdinDocWriter *w, Array const &attributes) { + isize count = 0; + for_array(i, attributes) { + Ast *attr = attributes[i]; + if (attr->kind != Ast_Attribute) continue; + count += attr->Attribute.elems.count; + }; + + auto attribs = array_make(heap_allocator(), 0, count); + defer (array_free(&attribs)); + + for_array(i, attributes) { + Ast *attr = attributes[i]; + if (attr->kind != Ast_Attribute) continue; + for_array(j, attr->Attribute.elems) { + Ast *elem = attr->Attribute.elems[j]; + String name = {}; + Ast *value = nullptr; + switch (elem->kind) { + case_ast_node(i, Ident, elem); + name = i->token.string; + case_end; + case_ast_node(i, Implicit, elem); + name = i->string; + case_end; + case_ast_node(fv, FieldValue, elem); + if (fv->field->kind == Ast_Ident) { + name = fv->field->Ident.token.string; + } else if (fv->field->kind == Ast_Implicit) { + name = fv->field->Implicit.string; + } + value = fv->value; + case_end; + default: + continue; + } + + OdinDocAttribute doc_attrib = {}; + doc_attrib.name = odin_doc_write_string(w, name); + doc_attrib.value = odin_doc_expr_string(w, value); + array_add(&attribs, doc_attrib); + } + } + return odin_write_slice(w, attribs.data, attribs.count); +} + +OdinDocArray odin_doc_where_clauses(OdinDocWriter *w, Slice const &where_clauses) { + if (where_clauses.count == 0) { + return {}; + } + auto clauses = array_make(heap_allocator(), where_clauses.count); + defer (array_free(&clauses)); + + for_array(i, where_clauses) { + clauses[i] = odin_doc_expr_string(w, where_clauses[i]); + } + + return odin_write_slice(w, clauses.data, clauses.count); +} + +OdinDocArray odin_doc_type_as_slice(OdinDocWriter *w, Type *type) { + OdinDocTypeIndex index = odin_doc_type(w, type); + return odin_write_item_as_slice(w, index); +} + +OdinDocArray odin_doc_add_entity_as_slice(OdinDocWriter *w, Entity *e) { + OdinDocEntityIndex index = odin_doc_add_entity(w, e); + return odin_write_item_as_slice(w, index); +} + +OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) { + if (type == nullptr) { + return 0; + } + OdinDocTypeIndex *found = map_get(&w->type_cache, hash_pointer(type)); + if (found) { + return *found; + } + for_array(i, w->type_cache.entries) { + // NOTE(bill): THIS IS SLOW + Type *other = cast(Type *)cast(uintptr)w->type_cache.entries[i].key.key; + if (are_types_identical(type, other)) { + OdinDocTypeIndex index = w->type_cache.entries[i].value; + map_set(&w->type_cache, hash_pointer(type), index); + return index; + } + } + + + OdinDocType *dst = nullptr; + OdinDocType doc_type = {}; + OdinDocTypeIndex type_index = 0; + type_index = odin_doc_write_item(w, &w->types, &doc_type, &dst); + map_set(&w->type_cache, hash_pointer(type), type_index); + map_set(&w->type_id_cache, hash_integer(type_index), type); + + + switch (type->kind) { + case Type_Basic: + doc_type.kind = OdinDocType_Basic; + doc_type.name = odin_doc_write_string(w, type->Basic.name); + if (is_type_untyped(type)) { + doc_type.flags |= OdinDocTypeFlag_Basic_untyped; + } + break; + case Type_Named: + doc_type.kind = OdinDocType_Named; + doc_type.name = odin_doc_write_string(w, type->Named.name); + doc_type.entities = odin_doc_add_entity_as_slice(w, type->Named.type_name); + break; + case Type_Generic: + doc_type.kind = OdinDocType_Generic; + doc_type.name = odin_doc_write_string(w, type->Generic.name); + if (type->Generic.specialized) { + doc_type.types = odin_doc_type_as_slice(w, type->Generic.specialized); + } + break; + case Type_Pointer: + doc_type.kind = OdinDocType_Pointer; + doc_type.types = odin_doc_type_as_slice(w, type->Pointer.elem); + break; + case Type_Array: + doc_type.kind = OdinDocType_Array; + doc_type.elem_count_len = 1; + doc_type.elem_counts[0] = type->Array.count; + doc_type.types = odin_doc_type_as_slice(w, type->Array.elem); + break; + case Type_EnumeratedArray: + doc_type.kind = OdinDocType_EnumeratedArray; + doc_type.elem_count_len = 1; + doc_type.elem_counts[0] = type->EnumeratedArray.count; + doc_type.types = odin_doc_type_as_slice(w, type->EnumeratedArray.elem); + break; + case Type_Slice: + doc_type.kind = OdinDocType_Slice; + doc_type.types = odin_doc_type_as_slice(w, type->Slice.elem); + break; + case Type_DynamicArray: + doc_type.kind = OdinDocType_DynamicArray; + doc_type.types = odin_doc_type_as_slice(w, type->DynamicArray.elem); + break; + case Type_Map: + doc_type.kind = OdinDocType_Map; + { + OdinDocTypeIndex types[2] = {}; + types[0] = odin_doc_type(w, type->Map.key); + types[1] = odin_doc_type(w, type->Map.value); + doc_type.types = odin_write_slice(w, types, gb_count_of(types)); + } + break; + case Type_Struct: + doc_type.kind = OdinDocType_Struct; + if (type->Struct.soa_kind != StructSoa_None) { + switch (type->Struct.soa_kind) { + case StructSoa_Fixed: + doc_type.kind = OdinDocType_SOAStructFixed; + doc_type.elem_count_len = 1; + doc_type.elem_counts[0] = type->Struct.soa_count; + break; + case StructSoa_Slice: + doc_type.kind = OdinDocType_SOAStructSlice; + break; + case StructSoa_Dynamic: + doc_type.kind = OdinDocType_SOAStructDynamic; + break; + } + doc_type.types = odin_doc_type_as_slice(w, type->Struct.soa_elem); + } else { + if (type->Struct.is_polymorphic) { doc_type.flags |= OdinDocTypeFlag_Struct_polymorphic; } + if (type->Struct.is_packed) { doc_type.flags |= OdinDocTypeFlag_Struct_packed; } + if (type->Struct.is_raw_union) { doc_type.flags |= OdinDocTypeFlag_Struct_raw_union; } + + auto fields = array_make(heap_allocator(), type->Struct.fields.count); + defer (array_free(&fields)); + + for_array(i, type->Struct.fields) { + fields[i] = odin_doc_add_entity(w, type->Struct.fields[i]); + } + + doc_type.entities = odin_write_slice(w, fields.data, fields.count); + doc_type.polmorphic_params = odin_doc_type(w, type->Struct.polymorphic_params); + + if (type->Struct.node) { + ast_node(st, StructType, type->Struct.node); + if (st->align) { + doc_type.custom_align = odin_doc_expr_string(w, st->align); + } + doc_type.where_clauses = odin_doc_where_clauses(w, st->where_clauses); + } + } + break; + case Type_Union: + doc_type.kind = OdinDocType_Union; + if (type->Union.is_polymorphic) { doc_type.flags |= OdinDocTypeFlag_Union_polymorphic; } + if (type->Union.no_nil) { doc_type.flags |= OdinDocTypeFlag_Union_no_nil; } + if (type->Union.maybe) { doc_type.flags |= OdinDocTypeFlag_Union_maybe; } + + { + auto variants = array_make(heap_allocator(), type->Union.variants.count); + defer (array_free(&variants)); + + for_array(i, type->Union.variants) { + variants[i] = odin_doc_type(w, type->Union.variants[i]); + } + + doc_type.types = odin_write_slice(w, variants.data, variants.count); + doc_type.polmorphic_params = odin_doc_type(w, type->Union.polymorphic_params); + } + + if (type->Union.node) { + ast_node(ut, UnionType, type->Union.node); + if (ut->align) { + doc_type.custom_align = odin_doc_expr_string(w, ut->align); + } + doc_type.where_clauses = odin_doc_where_clauses(w, ut->where_clauses); + } + break; + case Type_Enum: + doc_type.kind = OdinDocType_Enum; + { + auto fields = array_make(heap_allocator(), type->Enum.fields.count); + defer (array_free(&fields)); + + for_array(i, type->Enum.fields) { + fields[i] = odin_doc_add_entity(w, type->Enum.fields[i]); + } + doc_type.entities = odin_write_slice(w, fields.data, fields.count); + if (type->Enum.base_type) { + doc_type.types = odin_doc_type_as_slice(w, type->Enum.base_type); + } + } + break; + case Type_Tuple: + doc_type.kind = OdinDocType_Tuple; + { + auto variables = array_make(heap_allocator(), type->Tuple.variables.count); + defer (array_free(&variables)); + + for_array(i, type->Tuple.variables) { + variables[i] = odin_doc_add_entity(w, type->Tuple.variables[i]); + } + + doc_type.entities = odin_write_slice(w, variables.data, variables.count); + } + break; + case Type_Proc: + doc_type.kind = OdinDocType_Proc; + if (type->Proc.is_polymorphic) { doc_type.flags |= OdinDocTypeFlag_Proc_polymorphic; } + if (type->Proc.diverging) { doc_type.flags |= OdinDocTypeFlag_Proc_diverging; } + if (type->Proc.optional_ok) { doc_type.flags |= OdinDocTypeFlag_Proc_optional_ok; } + if (type->Proc.variadic) { doc_type.flags |= OdinDocTypeFlag_Proc_variadic; } + if (type->Proc.c_vararg) { doc_type.flags |= OdinDocTypeFlag_Proc_c_vararg; } + { + OdinDocTypeIndex types[2]; + types[0] = odin_doc_type(w, type->Proc.params); + types[1] = odin_doc_type(w, type->Proc.results); + doc_type.types = odin_write_slice(w, types, gb_count_of(types)); + } + break; + case Type_BitSet: + doc_type.kind = OdinDocType_BitSet; + { + isize type_count = 0; + OdinDocTypeIndex types[2] = {}; + if (type->BitSet.elem) { + types[type_count++] = odin_doc_type(w, type->BitSet.elem); + } + if (type->BitSet.underlying) { + types[type_count++] = odin_doc_type(w, type->BitSet.underlying); + doc_type.flags |= OdinDocTypeFlag_BitSet_UnderlyingType; + } + doc_type.types = odin_write_slice(w, types, type_count); + doc_type.elem_count_len = 2; + doc_type.elem_counts[0] = cast(u64)type->BitSet.lower; + doc_type.elem_counts[1] = cast(u64)type->BitSet.upper; + } + break; + case Type_SimdVector: + doc_type.kind = OdinDocType_SimdVector; + if (type->SimdVector.is_x86_mmx) { + doc_type.flags |= OdinDocTypeFlag_BitSet_x86_mmx; + } else { + doc_type.elem_count_len = 1; + doc_type.elem_counts[0] = type->SimdVector.count; + doc_type.types = odin_doc_type_as_slice(w, type->SimdVector.elem); + } + // TODO(bill): + break; + case Type_RelativePointer: + doc_type.kind = OdinDocType_RelativePointer; + { + OdinDocTypeIndex types[2] = {}; + types[0] = odin_doc_type(w, type->RelativePointer.pointer_type); + types[1] = odin_doc_type(w, type->RelativePointer.base_integer); + doc_type.types = odin_write_slice(w, types, gb_count_of(types)); + } + break; + case Type_RelativeSlice: + doc_type.kind = OdinDocType_RelativeSlice; + { + OdinDocTypeIndex types[2] = {}; + types[0] = odin_doc_type(w, type->RelativeSlice.slice_type); + types[1] = odin_doc_type(w, type->RelativeSlice.base_integer); + doc_type.types = odin_write_slice(w, types, gb_count_of(types)); + } + break; + } + + if (dst) { + *dst = doc_type; + } + return type_index; +} +OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e) { + if (e == nullptr) { + return 0; + } + + OdinDocEntityIndex *prev_index = map_get(&w->entity_cache, hash_pointer(e)); + if (prev_index) { + return *prev_index; + } + + if (e->pkg != nullptr && map_get(&w->pkg_cache, hash_pointer(e->pkg)) == nullptr) { + return 0; + } + + OdinDocEntity doc_entity = {}; + OdinDocEntity* dst = nullptr; + + OdinDocEntityIndex doc_entity_index = odin_doc_write_item(w, &w->entities, &doc_entity, &dst); + map_set(&w->entity_cache, hash_pointer(e), doc_entity_index); + map_set(&w->entity_id_cache, hash_integer(doc_entity_index), e); + + + Ast *type_expr = nullptr; + Ast *init_expr = nullptr; + Ast *decl_node = nullptr; + CommentGroup *comment = nullptr; + CommentGroup *docs = nullptr; + if (e->decl_info != nullptr) { + type_expr = e->decl_info->type_expr; + init_expr = e->decl_info->init_expr; + decl_node = e->decl_info->decl_node; + comment = e->decl_info->comment; + docs = e->decl_info->docs; + } + + String link_name = {}; + + OdinDocEntityKind kind = OdinDocEntity_Invalid; + u32 flags = 0; + + switch (e->kind) { + case Entity_Invalid: kind = OdinDocEntity_Invalid; break; + case Entity_Constant: kind = OdinDocEntity_Constant; break; + case Entity_Variable: kind = OdinDocEntity_Variable; break; + case Entity_TypeName: kind = OdinDocEntity_TypeName; break; + case Entity_Procedure: kind = OdinDocEntity_Procedure; break; + case Entity_ProcGroup: kind = OdinDocEntity_ProcGroup; break; + case Entity_ImportName: kind = OdinDocEntity_ImportName; break; + case Entity_LibraryName: kind = OdinDocEntity_LibraryName; break; + } + + switch (e->kind) { + case Entity_TypeName: + if (e->TypeName.is_type_alias) { + flags |= OdinDocEntityFlag_Type_Alias; + } + break; + case Entity_Variable: + if (e->Variable.is_foreign) { flags |= OdinDocEntityFlag_Foreign; } + if (e->Variable.is_export) { flags |= OdinDocEntityFlag_Export; } + if (e->Variable.thread_local_model != "") { + flags |= OdinDocEntityFlag_Var_Thread_Local; + } + link_name = e->Variable.link_name; + break; + case Entity_Procedure: + if (e->Procedure.is_foreign) { flags |= OdinDocEntityFlag_Foreign; } + if (e->Procedure.is_export) { flags |= OdinDocEntityFlag_Export; } + link_name = e->Procedure.link_name; + break; + } + + + doc_entity.kind = kind; + doc_entity.flags = flags; + doc_entity.pos = odin_doc_token_pos_cast(w, e->token.pos); + doc_entity.name = odin_doc_write_string(w, e->token.string); + doc_entity.type = 0; // Set later + doc_entity.init_string = odin_doc_expr_string(w, init_expr); + doc_entity.comment = odin_doc_comment_group_string(w, comment); + doc_entity.docs = odin_doc_comment_group_string(w, docs); + doc_entity.foreign_library = 0; // Set later + doc_entity.link_name = odin_doc_write_string(w, link_name); + if (e->decl_info != nullptr) { + doc_entity.attributes = odin_doc_attributes(w, e->decl_info->attributes); + } + doc_entity.grouped_entities = {}; // Set later + + if (dst) { + *dst = doc_entity; + } + + return doc_entity_index; +} + +void odin_doc_update_entities(OdinDocWriter *w) { + { + // NOTE(bill): Double pass, just in case entities are created on odin_doc_type + auto entities = array_make(heap_allocator(), w->entity_cache.entries.count); + defer (array_free(&entities)); + + for_array(i, w->entity_cache.entries) { + Entity *e = cast(Entity *)cast(uintptr)w->entity_cache.entries[i].key.key; + entities[i] = e; + } + for_array(i, entities) { + Entity *e = entities[i]; + OdinDocTypeIndex type_index = odin_doc_type(w, e->type); + } + } + + for_array(i, w->entity_cache.entries) { + Entity *e = cast(Entity *)cast(uintptr)w->entity_cache.entries[i].key.key; + OdinDocEntityIndex entity_index = w->entity_cache.entries[i].value; + OdinDocTypeIndex type_index = odin_doc_type(w, e->type); + + OdinDocEntityIndex foreign_library = 0; + OdinDocArray grouped_entities = {}; + + switch (e->kind) { + case Entity_Variable: + foreign_library = odin_doc_add_entity(w, e->Variable.foreign_library); + break; + case Entity_Procedure: + foreign_library = odin_doc_add_entity(w, e->Procedure.foreign_library); + break; + case Entity_ProcGroup: + { + auto pges = array_make(heap_allocator(), 0, e->ProcGroup.entities.count); + defer (array_free(&pges)); + + for_array(j, e->ProcGroup.entities) { + OdinDocEntityIndex index = odin_doc_add_entity(w, e->ProcGroup.entities[j]); + array_add(&pges, index); + } + grouped_entities = odin_write_slice(w, pges.data, pges.count); + } + break; + } + + OdinDocEntity *dst = odin_doc_get_item(w, &w->entities, entity_index); + if (dst) { + dst->type = type_index; + dst->foreign_library = foreign_library; + dst->grouped_entities = grouped_entities; + } + } +} + + + +OdinDocArray odin_doc_add_pkg_entities(OdinDocWriter *w, AstPackage *pkg) { + if (pkg->scope == nullptr) { + return {}; + } + if (map_get(&w->pkg_cache, hash_pointer(pkg)) == nullptr) { + return {}; + } + + auto entities = array_make(heap_allocator(), 0, pkg->scope->elements.entries.count); + defer (array_free(&entities)); + + for_array(i, pkg->scope->elements.entries) { + Entity *e = pkg->scope->elements.entries[i].value; + switch (e->kind) { + case Entity_Invalid: + case Entity_Builtin: + case Entity_Nil: + case Entity_Label: + continue; + case Entity_Constant: + case Entity_Variable: + case Entity_TypeName: + case Entity_Procedure: + case Entity_ProcGroup: + case Entity_ImportName: + case Entity_LibraryName: + // Fine + break; + } + array_add(&entities, e); + } + gb_sort_array(entities.data, entities.count, cmp_entities_for_printing); + + auto entity_indices = array_make(heap_allocator(), 0, w->entity_cache.entries.count); + defer (array_free(&entity_indices)); + + EntityKind curr_entity_kind = Entity_Invalid; + for_array(i, entities) { + Entity *e = entities[i]; + if (e->pkg != pkg) { + continue; + } + if (!is_entity_exported(e)) { + continue; + } + if (e->token.string.len == 0) { + continue; + } + + OdinDocEntityIndex doc_entity_index = 0; + doc_entity_index = odin_doc_add_entity(w, e); + array_add(&entity_indices, doc_entity_index); + } + + return odin_write_slice(w, entity_indices.data, entity_indices.count); +} + + +void odin_doc_write_docs(OdinDocWriter *w) { + auto pkgs = array_make(heap_allocator(), 0, w->info->packages.entries.count); + defer (array_free(&pkgs)); + for_array(i, w->info->packages.entries) { + AstPackage *pkg = w->info->packages.entries[i].value; + if (build_context.cmd_doc_flags & CmdDocFlag_AllPackages) { + array_add(&pkgs, pkg); + } else { + if (pkg->kind == Package_Init) { + array_add(&pkgs, pkg); + } else if (pkg->is_extra) { + array_add(&pkgs, pkg); + } + } + } + + gb_sort_array(pkgs.data, pkgs.count, cmp_ast_package_by_name); + + for_array(i, pkgs) { + gbAllocator allocator = heap_allocator(); + + AstPackage *pkg = pkgs[i]; + OdinDocPkg doc_pkg = {}; + doc_pkg.fullpath = odin_doc_write_string(w, pkg->fullpath); + doc_pkg.name = odin_doc_write_string(w, pkg->name); + doc_pkg.docs = odin_doc_pkg_doc_string(w, pkg); + + OdinDocPkg *dst = nullptr; + OdinDocPkgIndex pkg_index = odin_doc_write_item(w, &w->pkgs, &doc_pkg, &dst); + map_set(&w->pkg_cache, hash_pointer(pkg), pkg_index); + + auto file_indices = array_make(heap_allocator(), 0, pkg->files.count); + defer (array_free(&file_indices)); + + for_array(j, pkg->files) { + AstFile *file = pkg->files[j]; + OdinDocFile doc_file = {}; + doc_file.pkg = pkg_index; + doc_file.name = odin_doc_write_string(w, file->fullpath); + OdinDocFileIndex file_index = odin_doc_write_item(w, &w->files, &doc_file); + map_set(&w->file_cache, hash_pointer(file), file_index); + array_add(&file_indices, file_index); + } + + doc_pkg.files = odin_write_slice(w, file_indices.data, file_indices.count); + doc_pkg.entities = odin_doc_add_pkg_entities(w, pkg); + + if (dst) { + *dst = doc_pkg; + } + } + + odin_doc_update_entities(w); +} + + +void odin_doc_write_to_file(OdinDocWriter *w, char const *filename) { + gbFile f = {}; + gbFileError err = gb_file_open_mode(&f, gbFileMode_Write, filename); + if (err != gbFileError_None) { + gb_printf_err("Failed to write .odin-doc to: %s\n", filename); + gb_exit(1); + return; + } + defer (gb_file_close(&f)); + if (gb_file_write(&f, w->data, w->data_len)) { + err = gb_file_truncate(&f, w->data_len); + gb_printf("Wrote .odin-doc file to: %s\n", filename); + } +} + +void odin_doc_write(CheckerInfo *info, char const *filename) { + OdinDocWriter w_ = {}; + OdinDocWriter *w = &w_; + defer (odin_doc_writer_destroy(w)); + w->info = info; + + odin_doc_writer_prepare(w); + odin_doc_write_docs(w); + + odin_doc_writer_start_writing(w); + odin_doc_write_docs(w); + odin_doc_writer_end_writing(w); + + odin_doc_write_to_file(w, filename); +} diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 14a1a8007..62d5a58a6 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -2523,7 +2523,6 @@ lbProcedure *lb_create_procedure(lbModule *m, Entity *entity) { p->type = entity->type; p->type_expr = decl->type_expr; p->body = pl->body; - p->tags = pt->Proc.tags; p->inlining = ProcInlining_none; p->is_foreign = entity->Procedure.is_foreign; p->is_export = entity->Procedure.is_export; diff --git a/src/main.cpp b/src/main.cpp index b52f970a2..20190a187 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -607,6 +607,7 @@ enum BuildFlagKind { BuildFlag_Short, BuildFlag_AllPackages, + BuildFlag_DocFormat, BuildFlag_IgnoreWarnings, BuildFlag_WarningsAsErrors, @@ -721,6 +722,7 @@ bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_Short, str_lit("short"), BuildFlagParam_None, Command_doc); add_flag(&build_flags, BuildFlag_AllPackages, str_lit("all-packages"), BuildFlagParam_None, Command_doc); + add_flag(&build_flags, BuildFlag_DocFormat, str_lit("doc-format"), BuildFlagParam_None, Command_doc); add_flag(&build_flags, BuildFlag_IgnoreWarnings, str_lit("ignore-warnings"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_WarningsAsErrors, str_lit("warnings-as-errors"), BuildFlagParam_None, Command_all); @@ -1227,6 +1229,9 @@ bool parse_build_flags(Array args) { case BuildFlag_AllPackages: build_context.cmd_doc_flags |= CmdDocFlag_AllPackages; break; + case BuildFlag_DocFormat: + build_context.cmd_doc_flags |= CmdDocFlag_DocFormat; + break; case BuildFlag_IgnoreWarnings: if (build_context.warnings_as_errors) { gb_printf_err("-ignore-warnings cannot be used with -warnings-as-errors\n"); diff --git a/src/types.cpp b/src/types.cpp index 143b08c63..7d85aa6bb 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -195,7 +195,6 @@ struct TypeProc { Type * results; // Type_Tuple i32 param_count; i32 result_count; - u64 tags; isize specialization_count; ProcCallingConvention calling_convention; i32 variadic_index; From 7c1f538c02e914f57c63e9601e5556918dfe495e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 18 Apr 2021 18:46:29 +0100 Subject: [PATCH 28/36] Change `u32` to `u32le` --- core/odin/doc-format/doc_format.odin | 64 ++++++++++++++-------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/core/odin/doc-format/doc_format.odin b/core/odin/doc-format/doc_format.odin index e8f6d40f2..23e54f5fd 100644 --- a/core/odin/doc-format/doc_format.odin +++ b/core/odin/doc-format/doc_format.odin @@ -3,8 +3,8 @@ package odin_doc_format import "core:mem" Array :: struct($T: typeid) { - offset: u32, - length: u32, + offset: u32le, + length: u32le, } String :: distinct Array(byte); @@ -28,11 +28,11 @@ Magic_String :: "odindoc\x00"; Header_Base :: struct { magic: [8]byte, - _: u32, + _: u32le, version: Version_Type, - total_size: u32, - header_size: u32, - hash: u32, + total_size: u32le, + header_size: u32le, + hash: u32le, } Header :: struct { @@ -45,17 +45,17 @@ Header :: struct { types: Array(Type), } -File_Index :: distinct u32; -Pkg_Index :: distinct u32; -Entity_Index :: distinct u32; -Type_Index :: distinct u32; +File_Index :: distinct u32le; +Pkg_Index :: distinct u32le; +Entity_Index :: distinct u32le; +Type_Index :: distinct u32le; Position :: struct { file: File_Index, - line: u32, - column: u32, - offset: u32, + line: u32le, + column: u32le, + offset: u32le, }; File :: struct { @@ -71,7 +71,7 @@ Pkg :: struct { entities: Array(Entity_Index), } -Entity_Kind :: enum u32 { +Entity_Kind :: enum u32le { Invalid = 0, Constant = 1, Variable = 2, @@ -82,7 +82,7 @@ Entity_Kind :: enum u32 { Library_Name = 7, } -Entity_Flag :: enum u32 { +Entity_Flag :: enum u32le { Foreign = 0, Export = 1, @@ -98,7 +98,7 @@ Entity_Flag :: enum u32 { Var_Thread_Local = 9, } -Entity_Flags :: distinct bit_set[Entity_Flag; u32]; +Entity_Flags :: distinct bit_set[Entity_Flag; u32le]; Entity :: struct { kind: Entity_Kind, @@ -107,7 +107,7 @@ Entity :: struct { name: String, type: Type_Index, init_string: String, - _: u32, + _: u32le, comment: String, docs: String, foreign_library: Entity_Index, @@ -122,7 +122,7 @@ Attribute :: struct { value: String, } -Type_Kind :: enum u32 { +Type_Kind :: enum u32le { Invalid = 0, Basic = 1, Named = 2, @@ -151,12 +151,12 @@ Type_Elems_Cap :: 4; Type :: struct { kind: Type_Kind, - flags: u32, // Type_Kind specific + flags: u32le, // Type_Kind specific name: String, custom_align: String, // Used by some types - elem_count_len: u32, + elem_count_len: u32le, elem_counts: [Type_Elems_Cap]u64, // Each of these is esed by some types, not all @@ -166,27 +166,27 @@ Type :: struct { where_clauses: Array(String), // Struct, Union } -Type_Flags_Basic :: distinct bit_set[Type_Flag_Basic; u32]; -Type_Flag_Basic :: enum u32 { +Type_Flags_Basic :: distinct bit_set[Type_Flag_Basic; u32le]; +Type_Flag_Basic :: enum u32le { Untyped = 1, } -Type_Flags_Struct :: distinct bit_set[Type_Flag_Struct; u32]; -Type_Flag_Struct :: enum u32 { +Type_Flags_Struct :: distinct bit_set[Type_Flag_Struct; u32le]; +Type_Flag_Struct :: enum u32le { Polymorphic = 0, Packed = 1, Raw_Union = 2, } -Type_Flags_Union :: distinct bit_set[Type_Flag_Union; u32]; -Type_Flag_Union :: enum u32 { +Type_Flags_Union :: distinct bit_set[Type_Flag_Union; u32le]; +Type_Flag_Union :: enum u32le { Polymorphic = 0, No_Nil = 1, Maybe = 2, } -Type_Flags_Proc :: distinct bit_set[Type_Flag_Proc; u32]; -Type_Flag_Proc :: enum u32 { +Type_Flags_Proc :: distinct bit_set[Type_Flag_Proc; u32le]; +Type_Flag_Proc :: enum u32le { Polymorphic = 0, Diverging = 1, Optional_Ok = 2, @@ -194,16 +194,16 @@ Type_Flag_Proc :: enum u32 { C_Vararg = 4, } -Type_Flags_BitSet :: distinct bit_set[Type_Flag_BitSet; u32]; -Type_Flag_BitSet :: enum u32 { +Type_Flags_BitSet :: distinct bit_set[Type_Flag_BitSet; u32le]; +Type_Flag_BitSet :: enum u32le { Range = 1, Op_Lt = 2, Op_Lt_Eq = 3, Underlying_Type = 4, } -Type_Flags_SimdVector :: distinct bit_set[Type_Flag_SimdVector; u32]; -Type_Flag_SimdVector :: enum u32 { +Type_Flags_SimdVector :: distinct bit_set[Type_Flag_SimdVector; u32le]; +Type_Flag_SimdVector :: enum u32le { x86_mmx = 1, } From 986844a0f02028dbc7e4ad6147c253c17b3c2336 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 18 Apr 2021 18:48:56 +0100 Subject: [PATCH 29/36] Change elem_counts to `i64le` from `u64` --- core/odin/doc-format/doc_format.odin | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/odin/doc-format/doc_format.odin b/core/odin/doc-format/doc_format.odin index 23e54f5fd..89bce4a1d 100644 --- a/core/odin/doc-format/doc_format.odin +++ b/core/odin/doc-format/doc_format.odin @@ -157,7 +157,8 @@ Type :: struct { // Used by some types elem_count_len: u32le, - elem_counts: [Type_Elems_Cap]u64, + // for Enumerated Ranges, + elem_counts: [Type_Elems_Cap]i64le, // Each of these is esed by some types, not all types: Array(Type_Index), From 2ce98734648eb2f6e332d7e1cc0c240cba81517c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 18 Apr 2021 18:53:19 +0100 Subject: [PATCH 30/36] Remove dead comment --- core/odin/doc-format/doc_format.odin | 1 - 1 file changed, 1 deletion(-) diff --git a/core/odin/doc-format/doc_format.odin b/core/odin/doc-format/doc_format.odin index 89bce4a1d..b24944472 100644 --- a/core/odin/doc-format/doc_format.odin +++ b/core/odin/doc-format/doc_format.odin @@ -157,7 +157,6 @@ Type :: struct { // Used by some types elem_count_len: u32le, - // for Enumerated Ranges, elem_counts: [Type_Elems_Cap]i64le, // Each of these is esed by some types, not all From 9b3fb25a41ad21949c21253e1dae87b162ed400f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 18 Apr 2021 19:15:14 +0100 Subject: [PATCH 31/36] Fix enumerated arrays for .odin-doc --- src/docs_format.cpp | 2 +- src/docs_writer.cpp | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/docs_format.cpp b/src/docs_format.cpp index f30a0bf43..8824153ad 100644 --- a/src/docs_format.cpp +++ b/src/docs_format.cpp @@ -131,7 +131,7 @@ struct OdinDocType { // Used by some types u32 elem_count_len; - u64 elem_counts[OdinDocType_ElemsCap]; + i64 elem_counts[OdinDocType_ElemsCap]; // Each of these is esed by some types, not all OdinDocArray types; diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index daae40930..0ae7487fa 100644 --- a/src/docs_writer.cpp +++ b/src/docs_writer.cpp @@ -544,7 +544,12 @@ OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) { doc_type.kind = OdinDocType_EnumeratedArray; doc_type.elem_count_len = 1; doc_type.elem_counts[0] = type->EnumeratedArray.count; - doc_type.types = odin_doc_type_as_slice(w, type->EnumeratedArray.elem); + { + OdinDocTypeIndex types[2] = {}; + types[0] = odin_doc_type(w, type->EnumeratedArray.index); + types[1] = odin_doc_type(w, type->EnumeratedArray.elem); + doc_type.types = odin_write_slice(w, types, gb_count_of(types)); + } break; case Type_Slice: doc_type.kind = OdinDocType_Slice; @@ -686,8 +691,8 @@ OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) { } doc_type.types = odin_write_slice(w, types, type_count); doc_type.elem_count_len = 2; - doc_type.elem_counts[0] = cast(u64)type->BitSet.lower; - doc_type.elem_counts[1] = cast(u64)type->BitSet.upper; + doc_type.elem_counts[0] = type->BitSet.lower; + doc_type.elem_counts[1] = type->BitSet.upper; } break; case Type_SimdVector: From 4282688e6057ba491db8b8d6121c09cd12501094 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 18 Apr 2021 19:26:21 +0100 Subject: [PATCH 32/36] Add calling_convention to odin-doc Type format --- src/docs_format.cpp | 1 + src/docs_writer.cpp | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/docs_format.cpp b/src/docs_format.cpp index 8824153ad..e08f75936 100644 --- a/src/docs_format.cpp +++ b/src/docs_format.cpp @@ -134,6 +134,7 @@ struct OdinDocType { i64 elem_counts[OdinDocType_ElemsCap]; // Each of these is esed by some types, not all + OdinDocString calling_convention; OdinDocArray types; OdinDocArray entities; OdinDocTypeIndex polmorphic_params; diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index 0ae7487fa..2a975e5d0 100644 --- a/src/docs_writer.cpp +++ b/src/docs_writer.cpp @@ -675,6 +675,33 @@ OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) { types[0] = odin_doc_type(w, type->Proc.params); types[1] = odin_doc_type(w, type->Proc.results); doc_type.types = odin_write_slice(w, types, gb_count_of(types)); + + String calling_convention = {}; + switch (type->Proc.calling_convention) { + case ProcCC_Invalid: + case ProcCC_Odin: + // no need + break; + case ProcCC_Contextless: + calling_convention = str_lit("contextless"); + break; + case ProcCC_CDecl: + calling_convention = str_lit("cdecl"); + break; + case ProcCC_StdCall: + calling_convention = str_lit("stdcall"); + break; + case ProcCC_FastCall: + calling_convention = str_lit("fastcall"); + break; + case ProcCC_None: + calling_convention = str_lit("none"); + break; + case ProcCC_InlineAsm: + calling_convention = str_lit("inline-assembly"); + break; + } + doc_type.calling_convention = odin_doc_write_string(w, calling_convention); } break; case Type_BitSet: From b59e110fec6876d55e43910430f706ed1575c6e7 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 18 Apr 2021 19:26:36 +0100 Subject: [PATCH 33/36] Add calling_convention to Type --- core/odin/doc-format/doc_format.odin | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/odin/doc-format/doc_format.odin b/core/odin/doc-format/doc_format.odin index b24944472..b4b36bff8 100644 --- a/core/odin/doc-format/doc_format.odin +++ b/core/odin/doc-format/doc_format.odin @@ -160,10 +160,11 @@ Type :: struct { elem_counts: [Type_Elems_Cap]i64le, // Each of these is esed by some types, not all - types: Array(Type_Index), - entities: Array(Entity_Index), + calling_convention: String, // Procedures + types: Array(Type_Index), + entities: Array(Entity_Index), polymorphic_params: Type_Index, // Struct, Union - where_clauses: Array(String), // Struct, Union + where_clauses: Array(String), // Struct, Union } Type_Flags_Basic :: distinct bit_set[Type_Flag_Basic; u32le]; From 6ae468828cc0aa4f7cff54919f9c3eca0850f931 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 18 Apr 2021 19:59:24 +0100 Subject: [PATCH 34/36] Improve odin-doc type information for Named types by storing the base type --- src/docs_writer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index 2a975e5d0..b5a9161dd 100644 --- a/src/docs_writer.cpp +++ b/src/docs_writer.cpp @@ -521,6 +521,7 @@ OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) { case Type_Named: doc_type.kind = OdinDocType_Named; doc_type.name = odin_doc_write_string(w, type->Named.name); + doc_type.types = odin_doc_type_as_slice(w, base_type(type)); doc_type.entities = odin_doc_add_entity_as_slice(w, type->Named.type_name); break; case Type_Generic: From 3baddd4116e84c4f9175f42a074f3d2599aa4979 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 18 Apr 2021 20:13:20 +0100 Subject: [PATCH 35/36] Improve `init_string` determination for constants --- src/check_type.cpp | 3 +++ src/docs_writer.cpp | 20 +++++++++++++++++++- src/entity.cpp | 5 ++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/check_type.cpp b/src/check_type.cpp index de81592c8..39fea75db 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -731,6 +731,7 @@ void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *named_type, Ast Ast *field = et->fields[i]; Ast *ident = nullptr; Ast *init = nullptr; + u32 entity_flags = 0; if (field->kind == Ast_FieldValue) { ast_node(fv, FieldValue, field); if (fv->field == nullptr || fv->field->kind != Ast_Ident) { @@ -764,6 +765,7 @@ void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *named_type, Ast } } else { iota = exact_binary_operator_value(Token_Add, iota, exact_value_i64(1)); + entity_flags |= EntityConstantFlag_ImplicitEnumValue; } @@ -800,6 +802,7 @@ void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *named_type, Ast e->identifier = ident; e->flags |= EntityFlag_Visited; e->state = EntityState_Resolved; + e->Constant.flags |= entity_flags; if (scope_lookup_current(ctx->scope, name) != nullptr) { error(ident, "'%.*s' is already declared in this enumeration", LIT(name)); diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index b5a9161dd..57bc4ec66 100644 --- a/src/docs_writer.cpp +++ b/src/docs_writer.cpp @@ -831,13 +831,31 @@ OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e) { break; } + OdinDocString init_string = {}; + if (init_expr) { + init_string = odin_doc_expr_string(w, init_expr); + } else { + if (e->kind == Entity_Constant) { + if (e->Constant.flags & EntityConstantFlag_ImplicitEnumValue) { + init_string = {}; // Blank + } else if (e->Constant.param_value.original_ast_expr) { + init_string = odin_doc_expr_string(w, e->Constant.param_value.original_ast_expr); + } else { + init_string = odin_doc_write_string(w, make_string_c(exact_value_to_string(e->Constant.value))); + } + } else if (e->kind == Entity_Variable) { + if (e->Variable.param_expr) { + init_string = odin_doc_expr_string(w, e->Variable.param_expr); + } + } + } doc_entity.kind = kind; doc_entity.flags = flags; doc_entity.pos = odin_doc_token_pos_cast(w, e->token.pos); doc_entity.name = odin_doc_write_string(w, e->token.string); doc_entity.type = 0; // Set later - doc_entity.init_string = odin_doc_expr_string(w, init_expr); + doc_entity.init_string = init_string; doc_entity.comment = odin_doc_comment_group_string(w, comment); doc_entity.docs = odin_doc_comment_group_string(w, docs); doc_entity.foreign_library = 0; // Set later diff --git a/src/entity.cpp b/src/entity.cpp index a27b7cb37..3926678fd 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -93,7 +93,9 @@ struct ParameterValue { }; }; - +enum EntityConstantFlags : u32 { + EntityConstantFlag_ImplicitEnumValue = 1<<0, +}; // An Entity is a named "thing" in the language struct Entity { @@ -126,6 +128,7 @@ struct Entity { struct { ExactValue value; ParameterValue param_value; + u32 flags; } Constant; struct { Ast *init_expr; // only used for some variables within procedure bodies From ae04af4e4ed4ecd521e1a915edd5b8637b1f40ce Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 18 Apr 2021 20:19:03 +0100 Subject: [PATCH 36/36] Add package flags --- core/odin/doc-format/doc_format.odin | 13 +++++++++++-- src/docs_format.cpp | 7 +++++++ src/docs_writer.cpp | 20 +++++++++++++++++++- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/core/odin/doc-format/doc_format.odin b/core/odin/doc-format/doc_format.odin index b4b36bff8..61bfc913d 100644 --- a/core/odin/doc-format/doc_format.odin +++ b/core/odin/doc-format/doc_format.odin @@ -63,9 +63,18 @@ File :: struct { name: String, } +Pkg_Flag :: enum u32le { + Builtin = 0, + Runtime = 1, + Init = 2, +} + +Pkg_Flags :: distinct bit_set[Pkg_Flag; u32le]; + Pkg :: struct { fullpath: String, name: String, + flags: Pkg_Flags, docs: String, files: Array(File_Index), entities: Array(Entity_Index), @@ -195,8 +204,8 @@ Type_Flag_Proc :: enum u32le { C_Vararg = 4, } -Type_Flags_BitSet :: distinct bit_set[Type_Flag_BitSet; u32le]; -Type_Flag_BitSet :: enum u32le { +Type_Flags_Bit_Set :: distinct bit_set[Type_Flag_Bit_Set; u32le]; +Type_Flag_Bit_Set :: enum u32le { Range = 1, Op_Lt = 2, Op_Lt_Eq = 3, diff --git a/src/docs_format.cpp b/src/docs_format.cpp index e08f75936..c3aaebf64 100644 --- a/src/docs_format.cpp +++ b/src/docs_format.cpp @@ -190,9 +190,16 @@ struct OdinDocEntity { OdinDocArray where_clauses; // Procedures }; +enum OdinDocPkgFlags : u32 { + OdinDocPkgFlag_Builtin = 1<<0, + OdinDocPkgFlag_Runtime = 1<<1, + OdinDocPkgFlag_Init = 1<<2, +}; + struct OdinDocPkg { OdinDocString fullpath; OdinDocString name; + u32 flags; OdinDocString docs; OdinDocArray files; OdinDocArray entities; diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index 57bc4ec66..5c9bb9f63 100644 --- a/src/docs_writer.cpp +++ b/src/docs_writer.cpp @@ -509,7 +509,6 @@ OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type) { map_set(&w->type_cache, hash_pointer(type), type_index); map_set(&w->type_id_cache, hash_integer(type_index), type); - switch (type->kind) { case Type_Basic: doc_type.kind = OdinDocType_Basic; @@ -1008,9 +1007,28 @@ void odin_doc_write_docs(OdinDocWriter *w) { gbAllocator allocator = heap_allocator(); AstPackage *pkg = pkgs[i]; + + u32 pkg_flags = 0; + switch (pkg->kind) { + case Package_Normal: + break; + case Package_Runtime: + pkg_flags |= OdinDocPkgFlag_Runtime; + break; + case Package_Init: + pkg_flags |= OdinDocPkgFlag_Init; + break; + } + if (pkg->name == "builtin") { + pkg_flags |= OdinDocPkgFlag_Builtin; + } else if (pkg->name == "intrinsics") { + pkg_flags |= OdinDocPkgFlag_Builtin; + } + OdinDocPkg doc_pkg = {}; doc_pkg.fullpath = odin_doc_write_string(w, pkg->fullpath); doc_pkg.name = odin_doc_write_string(w, pkg->name); + doc_pkg.flags = pkg_flags; doc_pkg.docs = odin_doc_pkg_doc_string(w, pkg); OdinDocPkg *dst = nullptr;