From 2bf00d9b3f5a694bdc6d8b2c76be7a278f02f40b Mon Sep 17 00:00:00 2001 From: PucklaJ Date: Wed, 24 Apr 2024 15:15:38 +0200 Subject: [PATCH 001/198] [sys/linux]: Add flags parameter to execveat and fix execve on arm64 --- core/sys/linux/constants.odin | 2 ++ core/sys/linux/sys.odin | 7 +++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core/sys/linux/constants.odin b/core/sys/linux/constants.odin index 51f7db68f..389ff1417 100644 --- a/core/sys/linux/constants.odin +++ b/core/sys/linux/constants.odin @@ -5,6 +5,8 @@ package linux that relative paths are relative to current directory. */ AT_FDCWD :: Fd(-100) +AT_EMPTY_PATH :: 0x1000 +AT_SYMLINK_NOFOLLOW :: 0x100 /* Special value to put into timespec for utimensat() to set timestamp to the current time. diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index 63fb3b776..22fe2ab50 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -769,8 +769,7 @@ execve :: proc "contextless" (name: cstring, argv: [^]cstring, envp: [^]cstring) ret := syscall(SYS_execve, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp) return Errno(-ret) } else { - ret := syscall(SYS_execveat, AT_FDCWD, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp) - return Errno(-ret) + return execveat(AT_FDCWD, name, argv, envp, 0) } } @@ -2803,8 +2802,8 @@ getrandom :: proc "contextless" (buf: []u8, flags: Get_Random_Flags) -> (int, Er Execute program relative to a directory file descriptor. Available since Linux 3.19. */ -execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring) -> (Errno) { - ret := syscall(SYS_execveat, dirfd, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp) +execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring, flags: i32) -> (Errno) { + ret := syscall(SYS_execveat, dirfd, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, flags) return Errno(-ret) } From a2ad66cd9d5e93ce8789d1a1e088b254922cd88f Mon Sep 17 00:00:00 2001 From: PucklaJ Date: Wed, 24 Apr 2024 15:32:43 +0200 Subject: [PATCH 002/198] [sys/linux]: Add clone syscall and use it in fork for arm64 --- core/sys/linux/sys.odin | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index 22fe2ab50..1653a65dd 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -729,7 +729,22 @@ getsockopt :: proc { getsockopt_base, } -// TODO(flysand): clone (probably not in this PR, maybe not ever) +/* + Creates a new ("child") process, in a manner similar to fork. + Available since Linux 1.0? (<2.4) + + Note(flysand): this syscall is not documented, but the bottom 8 bits of flags + are for exit signal +*/ +clone :: proc "contextless" (flags: u64, stack: rawptr, parent_tid, child_tid: ^i32, tls: u64) -> (i64, Errno) { + when ODIN_ARCH == .amd64 { + ret := syscall(SYS_clone, flags, stack, parent_tid, child_tid, tls) + return errno_unwrap(ret, i64) + } else { + ret := syscall(SYS_clone, flags, stack, parent_tid, tls, child_tid) + return errno_unwrap(ret, i64) + } +} /* Creates a copy of the running process. @@ -737,10 +752,9 @@ getsockopt :: proc { */ fork :: proc "contextless" () -> (Pid, Errno) { when ODIN_ARCH == .arm64 { - // Note(flysand): this syscall is not documented, but the bottom 8 bits of flags - // are for exit signal - ret := syscall(SYS_clone, Signal.SIGCHLD) - return errno_unwrap(ret, Pid) + ret, err := clone(u64(Signal.SIGCHLD), nil, nil, nil, 0) + if err != .NONE do return Pid(ret), err + return Pid(ret), .NONE } else { ret := syscall(SYS_fork) return errno_unwrap(ret, Pid) From b0fe6212bb1f55c367991c23c22cb1fa35da34e4 Mon Sep 17 00:00:00 2001 From: PucklaJ Date: Sat, 27 Apr 2024 13:21:04 +0200 Subject: [PATCH 003/198] [sys/linux]: Fix return statement --- core/sys/linux/sys.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index 1653a65dd..5b2948c8f 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -754,7 +754,7 @@ fork :: proc "contextless" () -> (Pid, Errno) { when ODIN_ARCH == .arm64 { ret, err := clone(u64(Signal.SIGCHLD), nil, nil, nil, 0) if err != .NONE do return Pid(ret), err - return Pid(ret), .NONE + return Pid(ret), err } else { ret := syscall(SYS_fork) return errno_unwrap(ret, Pid) From 7f301790d0e1828082f8f314475a332084026dd5 Mon Sep 17 00:00:00 2001 From: PucklaJ Date: Sun, 28 Apr 2024 11:48:18 +0200 Subject: [PATCH 004/198] [sys/linux] Change flags parameter of execveat to bit_set --- core/sys/linux/bits.odin | 8 ++++++++ core/sys/linux/constants.odin | 2 -- core/sys/linux/sys.odin | 6 +++--- core/sys/linux/types.odin | 5 +++++ 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/core/sys/linux/bits.odin b/core/sys/linux/bits.odin index ad519e1cd..88c4f40c9 100644 --- a/core/sys/linux/bits.odin +++ b/core/sys/linux/bits.odin @@ -1756,3 +1756,11 @@ EPoll_Ctl_Opcode :: enum i32 { DEL = 2, MOD = 3, } + +/* + Bits for execveat(2) flags. +*/ +Execveat_Flags_Bits :: enum { + AT_SYMLINK_NOFOLLOW = 8, + AT_EMPTY_PATH = 12, +} diff --git a/core/sys/linux/constants.odin b/core/sys/linux/constants.odin index 389ff1417..51f7db68f 100644 --- a/core/sys/linux/constants.odin +++ b/core/sys/linux/constants.odin @@ -5,8 +5,6 @@ package linux that relative paths are relative to current directory. */ AT_FDCWD :: Fd(-100) -AT_EMPTY_PATH :: 0x1000 -AT_SYMLINK_NOFOLLOW :: 0x100 /* Special value to put into timespec for utimensat() to set timestamp to the current time. diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index 5b2948c8f..a9ec3c24e 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -783,7 +783,7 @@ execve :: proc "contextless" (name: cstring, argv: [^]cstring, envp: [^]cstring) ret := syscall(SYS_execve, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp) return Errno(-ret) } else { - return execveat(AT_FDCWD, name, argv, envp, 0) + return execveat(AT_FDCWD, name, argv, envp, nil) } } @@ -2816,8 +2816,8 @@ getrandom :: proc "contextless" (buf: []u8, flags: Get_Random_Flags) -> (int, Er Execute program relative to a directory file descriptor. Available since Linux 3.19. */ -execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring, flags: i32) -> (Errno) { - ret := syscall(SYS_execveat, dirfd, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, flags) +execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring, flags: Execveat_Flags) -> (Errno) { + ret := syscall(SYS_execveat, dirfd, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, transmute(i32) flags) return Errno(-ret) } diff --git a/core/sys/linux/types.odin b/core/sys/linux/types.odin index 677bac7e0..e7ecfc609 100644 --- a/core/sys/linux/types.odin +++ b/core/sys/linux/types.odin @@ -1238,3 +1238,8 @@ EPoll_Event :: struct #packed { events: EPoll_Event_Kind, data: EPoll_Data, } + +/* + Flags for execveat(2) syscall. +*/ +Execveat_Flags :: bit_set[Execveat_Flags_Bits; i32] From d1a205e2cfb44df31801201fc8818e02527b45dc Mon Sep 17 00:00:00 2001 From: PucklaJ Date: Sun, 28 Apr 2024 11:56:19 +0200 Subject: [PATCH 005/198] [sys/linux]: Remove clone syscall and call it directly in fork on arm64 --- core/sys/linux/sys.odin | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index a9ec3c24e..57827d45c 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -729,32 +729,14 @@ getsockopt :: proc { getsockopt_base, } -/* - Creates a new ("child") process, in a manner similar to fork. - Available since Linux 1.0? (<2.4) - - Note(flysand): this syscall is not documented, but the bottom 8 bits of flags - are for exit signal -*/ -clone :: proc "contextless" (flags: u64, stack: rawptr, parent_tid, child_tid: ^i32, tls: u64) -> (i64, Errno) { - when ODIN_ARCH == .amd64 { - ret := syscall(SYS_clone, flags, stack, parent_tid, child_tid, tls) - return errno_unwrap(ret, i64) - } else { - ret := syscall(SYS_clone, flags, stack, parent_tid, tls, child_tid) - return errno_unwrap(ret, i64) - } -} - /* Creates a copy of the running process. Available since Linux 1.0. */ fork :: proc "contextless" () -> (Pid, Errno) { when ODIN_ARCH == .arm64 { - ret, err := clone(u64(Signal.SIGCHLD), nil, nil, nil, 0) - if err != .NONE do return Pid(ret), err - return Pid(ret), err + ret := syscall(SYS_clone, u64(Signal.SIGCHLD), cast(rawptr) nil, cast(rawptr) nil, cast(rawptr) nil, u64(0)) + return errno_unwrap(ret, Pid) } else { ret := syscall(SYS_fork) return errno_unwrap(ret, Pid) From 37b026cb9bdd29aa657a54b76d2595bef40ff8c8 Mon Sep 17 00:00:00 2001 From: PucklaJ Date: Sun, 28 Apr 2024 12:00:40 +0200 Subject: [PATCH 006/198] [sys/linux] Directly call syscall in execve on arm64 --- core/sys/linux/sys.odin | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index 57827d45c..887feb49d 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -765,7 +765,8 @@ execve :: proc "contextless" (name: cstring, argv: [^]cstring, envp: [^]cstring) ret := syscall(SYS_execve, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp) return Errno(-ret) } else { - return execveat(AT_FDCWD, name, argv, envp, nil) + ret := syscall(SYS_execveat, AT_FDCWD, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, i32(0)) + return Errno(-ret) } } From b26f4e0766e50ace52891f09a51ce2a5f953897d Mon Sep 17 00:00:00 2001 From: Andreas T Jonsson Date: Tue, 2 Jul 2024 11:43:01 +0200 Subject: [PATCH 007/198] Use pkgsrc llvm for NetBSD CI --- .github/workflows/ci.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca3d87b12..f576700b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ jobs: name: NetBSD Build, Check, and Test runs-on: ubuntu-latest env: - PKGSRC_BRANCH: 2024Q1 + PKGSRC_BRANCH: 2024Q2 steps: - uses: actions/checkout@v4 - name: Build, Check, and Test @@ -19,10 +19,7 @@ jobs: copyback: false prepare: | PKG_PATH="https://cdn.NetBSD.org/pub/pkgsrc/packages/NetBSD/$(uname -p)/$(uname -r | cut -d_ -f1)_${PKGSRC_BRANCH}/All" /usr/sbin/pkg_add pkgin - pkgin -y in gmake git bash python311 - pkgin -y in libxml2 perl zstd - /usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/llvm-17.0.6.tgz - /usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/clang-17.0.6.tgz + pkgin -y in gmake git bash python311 llvm clang ln -s /usr/pkg/bin/python3.11 /usr/bin/python3 run: | git config --global --add safe.directory $(pwd) From a348a7e84efbf971048359e4bdf17d6006a79b5f Mon Sep 17 00:00:00 2001 From: NicknEma <62065135+NicknEma@users.noreply.github.com> Date: Fri, 12 Jul 2024 15:18:58 +0200 Subject: [PATCH 008/198] Create doc.odin Create a doc file with a brief of the package and an example program (copied from a discord message by laytan) --- core/container/intrusive/list/doc.odin | 39 ++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 core/container/intrusive/list/doc.odin diff --git a/core/container/intrusive/list/doc.odin b/core/container/intrusive/list/doc.odin new file mode 100644 index 000000000..ff219e15f --- /dev/null +++ b/core/container/intrusive/list/doc.odin @@ -0,0 +1,39 @@ +/* +Package list implements an intrusive doubly-linked list. + +An intrusive container requires a `Node` to be embedded in your own structure, like this: + + My_String :: struct { + node: list.Node, + value: string, + } + +Here is a full example: + + package test + + import "core:fmt" + import "core:container/intrusive/list" + + main :: proc() { + l: list.List + + one := My_String{value="Hello"} + two := My_String{value="World"} + + list.push_back(&l, &one.node) + list.push_back(&l, &two.node) + + iter := list.iterator_head(l, My_String, "node") + for s in list.iterate_next(&iter) { + fmt.println(s.value) + } + } + + My_String :: struct { + node: list.Node, + value: string, + } + +*/ +package container_intrusive_list From c75a872909191631f5e5282d37014b51a34a9724 Mon Sep 17 00:00:00 2001 From: NicknEma <62065135+NicknEma@users.noreply.github.com> Date: Fri, 12 Jul 2024 15:37:34 +0200 Subject: [PATCH 009/198] Write doc comments in intrusive_list.odin Write description, inputs/returns and some examples for each procedure --- .../intrusive/list/intrusive_list.odin | 191 +++++++++++++++++- 1 file changed, 189 insertions(+), 2 deletions(-) diff --git a/core/container/intrusive/list/intrusive_list.odin b/core/container/intrusive/list/intrusive_list.odin index 1a3175002..6989dfa21 100644 --- a/core/container/intrusive/list/intrusive_list.odin +++ b/core/container/intrusive/list/intrusive_list.odin @@ -18,11 +18,18 @@ List :: struct { tail: ^Node, } - +// The list link you must include in your own structure. Node :: struct { prev, next: ^Node, } +/* +Inserts a new element at the front of the list with O(1) time complexity. + +**Inputs** +- list: The container list +- node: The node member of the user-defined element structure +*/ push_front :: proc "contextless" (list: ^List, node: ^Node) { if list.head != nil { list.head.prev = node @@ -33,7 +40,13 @@ push_front :: proc "contextless" (list: ^List, node: ^Node) { node.prev, node.next = nil, nil } } +/* +Inserts a new element at the back of the list with O(1) time complexity. +**Inputs** +- list: The container list +- node: The node member of the user-defined element structure +*/ push_back :: proc "contextless" (list: ^List, node: ^Node) { if list.tail != nil { list.tail.next = node @@ -45,6 +58,13 @@ push_back :: proc "contextless" (list: ^List, node: ^Node) { } } +/* +Removes an element from a list with O(1) time complexity. + +**Inputs** +- list: The container list +- node: The node member of the user-defined element structure to be removed +*/ remove :: proc "contextless" (list: ^List, node: ^Node) { if node != nil { if node.next != nil { @@ -61,7 +81,13 @@ remove :: proc "contextless" (list: ^List, node: ^Node) { } } } +/* +Removes from the given list all elements that satisfy a condition. +**Inputs** +- list: The container list +- to_erase: The condition procedure. It should return `true` if a node should be removed, `false` otherwise +*/ remove_by_proc :: proc(list: ^List, to_erase: proc(^Node) -> bool) { for node := list.head; node != nil; { next := node.next @@ -82,7 +108,13 @@ remove_by_proc :: proc(list: ^List, to_erase: proc(^Node) -> bool) { node = next } } +/* +Removes from the given list all elements that satisfy a condition. +**Inputs** +- list: The container list +- to_erase: The _contextless_ condition procedure. It should return `true` if a node should be removed, `false` otherwise +*/ remove_by_proc_contextless :: proc(list: ^List, to_erase: proc "contextless" (^Node) -> bool) { for node := list.head; node != nil; { next := node.next @@ -104,12 +136,26 @@ remove_by_proc_contextless :: proc(list: ^List, to_erase: proc "contextless" (^N } } +/* +Checks if the given list does not contain any element. +**Inputs** +- list: The container list +**Returns** `true` if `list` is empty, `false` otherwise +*/ is_empty :: proc "contextless" (list: ^List) -> bool { return list.head == nil } +/* +Removes and returns the element at the front of the list with O(1) time complexity. + +**Inputs** +- list: The container list + +**Returns** The node member of the user-defined element structure, or `nil` if the list is empty +*/ pop_front :: proc "contextless" (list: ^List) -> ^Node { link := list.head if link == nil { @@ -130,6 +176,14 @@ pop_front :: proc "contextless" (list: ^List) -> ^Node { return link } +/* +Removes and returns the element at the back of the list with O(1) time complexity. + +**Inputs** +- list: The container list + +**Returns** The node member of the user-defined element structure, or `nil` if the list is empty +*/ pop_back :: proc "contextless" (list: ^List) -> ^Node { link := list.tail if link == nil { @@ -151,29 +205,122 @@ pop_back :: proc "contextless" (list: ^List) -> ^Node { } + Iterator :: struct($T: typeid) { curr: ^Node, offset: uintptr, } +/* +Creates an iterator pointing at the head of the given list. + +**Inputs** +- list: The container list +- T: The type of the list's elements +- field_name: The name of the node field in the `T` structure + +**Returns** An iterator pointing at the head of `list` + +Example: + + My_Struct :: struct { + node : list.Node, + value: int, + } + + l: list.List + it := list.iterator_head(l, My_Struct, "node") + +*/ iterator_head :: proc "contextless" (list: List, $T: typeid, $field_name: string) -> Iterator(T) where intrinsics.type_has_field(T, field_name), intrinsics.type_field_type(T, field_name) == Node { return {list.head, offset_of_by_string(T, field_name)} } +/* +Creates an iterator pointing at the tail of the given list. +**Inputs** +- list: The container list +- T: The type of the list's elements +- field_name: The name of the node field in the `T` structure + +**Returns** An iterator pointing at the tail of `list` + +Example: + + My_Struct :: struct { + node : list.Node, + value: int, + } + + l: list.List + it := list.iterator_tail(l, My_Struct, "node") + +*/ iterator_tail :: proc "contextless" (list: List, $T: typeid, $field_name: string) -> Iterator(T) where intrinsics.type_has_field(T, field_name), intrinsics.type_field_type(T, field_name) == Node { return {list.tail, offset_of_by_string(T, field_name)} } +/* +Creates an iterator pointing at the specified node of a list. +**Inputs** +- node: a list node +- T: The type of the list's elements +- field_name: The name of the node field in the `T` structure + +**Returns** An iterator pointing at `node` + +*/ iterator_from_node :: proc "contextless" (node: ^Node, $T: typeid, $field_name: string) -> Iterator(T) where intrinsics.type_has_field(T, field_name), intrinsics.type_field_type(T, field_name) == Node { return {node, offset_of_by_string(T, field_name)} } +/* +Retrieves the next element in a list and advances the iterator. + +**Inputs** +- it: The iterator + +**Returns** +- ptr: The next list element +- ok: `true` if the element is valid (the iterator could advance), `false` otherwise + +Example: + + import "core:fmt" + import "core:container/intrusive/list" + + iterate_next_example :: proc() { + l: list.List + + one := My_Struct{value=1} + two := My_Struct{value=2} + + list.push_back(&l, &one.node) + list.push_back(&l, &two.node) + + it := list.iterator_head(&l, My_Struct, "node") + for num in list.iterate_next(&it) { + fmt.println(num) + } + } + + My_Struct :: struct { + node : list.Node, + value: int, + } + +Output: + + 1 + 2 + +*/ iterate_next :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) { node := it.curr if node == nil { @@ -183,7 +330,47 @@ iterate_next :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) { return (^T)(uintptr(node) - it.offset), true } +/* +Retrieves the previous element in a list and recede the iterator. +**Inputs** +- it: The iterator + +**Returns** +- ptr: The previous list element +- ok: `true` if the element is valid (the iterator could recede), `false` otherwise + +Example: + + import "core:fmt" + import "core:container/intrusive/list" + + iterate_next_example :: proc() { + l: list.List + + one := My_Struct{value=1} + two := My_Struct{value=2} + + list.push_back(&l, &one.node) + list.push_back(&l, &two.node) + + it := list.iterator_tail(&l, My_Struct, "node") + for num in list.iterate_prev(&it) { + fmt.println(num) + } + } + + My_Struct :: struct { + node : list.Node, + value: int, + } + +Output: + + 2 + 1 + +*/ iterate_prev :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) { node := it.curr if node == nil { @@ -192,4 +379,4 @@ iterate_prev :: proc "contextless" (it: ^Iterator($T)) -> (ptr: ^T, ok: bool) { it.curr = node.prev return (^T)(uintptr(node) - it.offset), true -} \ No newline at end of file +} From d10694901f9243b72a7b9837b1f54cceb81c6288 Mon Sep 17 00:00:00 2001 From: NicknEma <62065135+NicknEma@users.noreply.github.com> Date: Fri, 12 Jul 2024 15:48:47 +0200 Subject: [PATCH 010/198] Simplify and fix doc examples Remove unnecessary examples; fix compilation errors in the remaining ones --- .../intrusive/list/intrusive_list.odin | 32 ++++--------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/core/container/intrusive/list/intrusive_list.odin b/core/container/intrusive/list/intrusive_list.odin index 6989dfa21..e0df3ba72 100644 --- a/core/container/intrusive/list/intrusive_list.odin +++ b/core/container/intrusive/list/intrusive_list.odin @@ -212,7 +212,7 @@ Iterator :: struct($T: typeid) { } /* -Creates an iterator pointing at the head of the given list. +Creates an iterator pointing at the head of the given list. For an example, see `iterate_next`. **Inputs** - list: The container list @@ -221,16 +221,6 @@ Creates an iterator pointing at the head of the given list. **Returns** An iterator pointing at the head of `list` -Example: - - My_Struct :: struct { - node : list.Node, - value: int, - } - - l: list.List - it := list.iterator_head(l, My_Struct, "node") - */ iterator_head :: proc "contextless" (list: List, $T: typeid, $field_name: string) -> Iterator(T) where intrinsics.type_has_field(T, field_name), @@ -238,7 +228,7 @@ iterator_head :: proc "contextless" (list: List, $T: typeid, $field_name: string return {list.head, offset_of_by_string(T, field_name)} } /* -Creates an iterator pointing at the tail of the given list. +Creates an iterator pointing at the tail of the given list. For an example, see `iterate_prev`. **Inputs** - list: The container list @@ -247,16 +237,6 @@ Creates an iterator pointing at the tail of the given list. **Returns** An iterator pointing at the tail of `list` -Example: - - My_Struct :: struct { - node : list.Node, - value: int, - } - - l: list.List - it := list.iterator_tail(l, My_Struct, "node") - */ iterator_tail :: proc "contextless" (list: List, $T: typeid, $field_name: string) -> Iterator(T) where intrinsics.type_has_field(T, field_name), @@ -304,9 +284,9 @@ Example: list.push_back(&l, &one.node) list.push_back(&l, &two.node) - it := list.iterator_head(&l, My_Struct, "node") + it := list.iterator_head(l, My_Struct, "node") for num in list.iterate_next(&it) { - fmt.println(num) + fmt.println(num.value) } } @@ -354,9 +334,9 @@ Example: list.push_back(&l, &one.node) list.push_back(&l, &two.node) - it := list.iterator_tail(&l, My_Struct, "node") + it := list.iterator_tail(l, My_Struct, "node") for num in list.iterate_prev(&it) { - fmt.println(num) + fmt.println(num.value) } } From dbdad0476d1b9dd72eddd6b8584beca855585b13 Mon Sep 17 00:00:00 2001 From: VladPavliuk Date: Sat, 13 Jul 2024 00:07:48 +0300 Subject: [PATCH 011/198] Allow to `marshal` and `unmarshal` maps with int keys --- core/encoding/json/marshal.odin | 78 +++++++++++--------- core/encoding/json/unmarshal.odin | 30 ++++++-- tests/core/encoding/json/test_core_json.odin | 40 ++++++++++ 3 files changed, 107 insertions(+), 41 deletions(-) diff --git a/core/encoding/json/marshal.odin b/core/encoding/json/marshal.odin index 0464c24d1..31b076e95 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -100,38 +100,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: case runtime.Type_Info_Integer: buf: [40]byte - u: u128 - switch i in a { - case i8: u = u128(i) - case i16: u = u128(i) - case i32: u = u128(i) - case i64: u = u128(i) - case i128: u = u128(i) - case int: u = u128(i) - case u8: u = u128(i) - case u16: u = u128(i) - case u32: u = u128(i) - case u64: u = u128(i) - case u128: u = u128(i) - case uint: u = u128(i) - case uintptr: u = u128(i) - - case i16le: u = u128(i) - case i32le: u = u128(i) - case i64le: u = u128(i) - case u16le: u = u128(i) - case u32le: u = u128(i) - case u64le: u = u128(i) - case u128le: u = u128(i) - - case i16be: u = u128(i) - case i32be: u = u128(i) - case i64be: u = u128(i) - case u16be: u = u128(i) - case u32be: u = u128(i) - case u64be: u = u128(i) - case u128be: u = u128(i) - } + u := cast_any_int_to_u128(a) s: string @@ -310,7 +279,12 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: case cstring: name = string(s) } opt_write_key(w, opt, name) or_return - + case runtime.Type_Info_Integer: + buf: [40]byte + u := cast_any_int_to_u128(ka) + name = strconv.append_bits_128(buf[:], u, 10, info.signed, 8*kti.size, "0123456789", nil) + + opt_write_key(w, opt, name) or_return case: return .Unsupported_Type } } @@ -657,3 +631,41 @@ opt_write_indentation :: proc(w: io.Writer, opt: ^Marshal_Options) -> (err: io.E return } + +@(private) +cast_any_int_to_u128 :: proc(any_int_value: any) -> u128 { + u: u128 = 0 + switch i in any_int_value { + case i8: u = u128(i) + case i16: u = u128(i) + case i32: u = u128(i) + case i64: u = u128(i) + case i128: u = u128(i) + case int: u = u128(i) + case u8: u = u128(i) + case u16: u = u128(i) + case u32: u = u128(i) + case u64: u = u128(i) + case u128: u = u128(i) + case uint: u = u128(i) + case uintptr: u = u128(i) + + case i16le: u = u128(i) + case i32le: u = u128(i) + case i64le: u = u128(i) + case u16le: u = u128(i) + case u32le: u = u128(i) + case u64le: u = u128(i) + case u128le: u = u128(i) + + case i16be: u = u128(i) + case i32be: u = u128(i) + case i64be: u = u128(i) + case u16be: u = u128(i) + case u32be: u = u128(i) + case u64be: u = u128(i) + case u128be: u = u128(i) + } + + return u +} \ No newline at end of file diff --git a/core/encoding/json/unmarshal.odin b/core/encoding/json/unmarshal.odin index eb59e7838..2a4678719 100644 --- a/core/encoding/json/unmarshal.odin +++ b/core/encoding/json/unmarshal.odin @@ -475,7 +475,7 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm } case reflect.Type_Info_Map: - if !reflect.is_string(t.key) { + if !reflect.is_string(t.key) && !reflect.is_integer(t.key) { return UNSUPPORTED_TYPE } raw_map := (^mem.Raw_Map)(v.data) @@ -492,25 +492,39 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm key, _ := parse_object_key(p, p.allocator) unmarshal_expect_token(p, .Colon) - + mem.zero_slice(elem_backing) if uerr := unmarshal_value(p, map_backing_value); uerr != nil { delete(key, p.allocator) return uerr } - key_ptr := rawptr(&key) + key_ptr: rawptr - key_cstr: cstring - if reflect.is_cstring(t.key) { - key_cstr = cstring(raw_data(key)) - key_ptr = &key_cstr + #partial switch tk in t.key.variant { + case runtime.Type_Info_String: + key_ptr = rawptr(&key) + key_cstr: cstring + if reflect.is_cstring(t.key) { + key_cstr = cstring(raw_data(key)) + key_ptr = &key_cstr + } + case runtime.Type_Info_Integer: + i, ok := strconv.parse_i128(key) + if !ok { return UNSUPPORTED_TYPE } + key_ptr = rawptr(&i) + case: return UNSUPPORTED_TYPE } - + set_ptr := runtime.__dynamic_map_set_without_hash(raw_map, t.map_info, key_ptr, map_backing_value.data) if set_ptr == nil { delete(key, p.allocator) } + + // there's no need to keep string value on the heap, since it was copied into map + if reflect.is_integer(t.key) { + delete(key, p.allocator) + } if parse_comma(p) { break map_loop diff --git a/tests/core/encoding/json/test_core_json.odin b/tests/core/encoding/json/test_core_json.odin index 92c050952..dfc655af0 100644 --- a/tests/core/encoding/json/test_core_json.odin +++ b/tests/core/encoding/json/test_core_json.odin @@ -3,6 +3,10 @@ package test_core_json import "core:encoding/json" import "core:testing" import "core:mem/virtual" +import "core:fmt" +import "base:runtime" +import "core:log" +import "core:strings" @test parse_json :: proc(t: ^testing.T) { @@ -368,4 +372,40 @@ utf8_string_of_multibyte_characters :: proc(t: ^testing.T) { val, err := json.parse_string(`"🐛✅"`) defer json.destroy_value(val) testing.expectf(t, err == nil, "Expected `json.parse` to return nil, got %v", err) +} + +@test +map_with_integer_keys :: proc(t: ^testing.T) { + my_map := make(map[i32]string) + defer delete_map(my_map) + + my_map[-1] = "a" + my_map[0] = "b" + my_map[42] = "c" + my_map[99999999] = "d" + + marshaled_data, marshal_err := json.marshal(my_map) + defer delete(marshaled_data) + + testing.expectf(t, marshal_err == nil, "Expected `json.marshal` to return nil error, got %v", marshal_err) + + my_map2 := make(map[i32]string) + defer delete_map(my_map2) + + unmarshal_err := json.unmarshal(marshaled_data, &my_map2) + defer for key, item in my_map2 { + runtime.delete_string(item) + } + testing.expectf(t, unmarshal_err == nil, "Expected `json.unmarshal` to return nil, got %v", unmarshal_err) + + testing.expectf(t, len(my_map) == len(my_map2), "Expected %v map items to have been unmarshaled, got %v", len(my_map), len(my_map2)) + + for key, item in my_map { + testing.expectf(t, key in my_map2, "Expected key %v to be present in unmarshaled map", key) + + value_from_map2, ok := my_map2[key] + if ok { + testing.expectf(t, runtime.string_eq(item, my_map2[key]), "Expected value %s to be present in unmarshaled map", key) + } + } } \ No newline at end of file From 39983eaaa44a2177ca9b05cd76c29d96924a7e81 Mon Sep 17 00:00:00 2001 From: VladPavliuk Date: Sat, 13 Jul 2024 00:26:54 +0300 Subject: [PATCH 012/198] Remove unused imports in `test_core_json` --- tests/core/encoding/json/test_core_json.odin | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/core/encoding/json/test_core_json.odin b/tests/core/encoding/json/test_core_json.odin index dfc655af0..bf81330b6 100644 --- a/tests/core/encoding/json/test_core_json.odin +++ b/tests/core/encoding/json/test_core_json.odin @@ -3,10 +3,7 @@ package test_core_json import "core:encoding/json" import "core:testing" import "core:mem/virtual" -import "core:fmt" import "base:runtime" -import "core:log" -import "core:strings" @test parse_json :: proc(t: ^testing.T) { From 79e2f63182581547dcdb7593397d1c3e280a5670 Mon Sep 17 00:00:00 2001 From: VladPavliuk Date: Sat, 13 Jul 2024 00:38:58 +0300 Subject: [PATCH 013/198] Small code refactoring in `test_core_json` --- tests/core/encoding/json/test_core_json.odin | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/core/encoding/json/test_core_json.odin b/tests/core/encoding/json/test_core_json.odin index bf81330b6..62f474ce0 100644 --- a/tests/core/encoding/json/test_core_json.odin +++ b/tests/core/encoding/json/test_core_json.odin @@ -400,8 +400,7 @@ map_with_integer_keys :: proc(t: ^testing.T) { for key, item in my_map { testing.expectf(t, key in my_map2, "Expected key %v to be present in unmarshaled map", key) - value_from_map2, ok := my_map2[key] - if ok { + if key in my_map2 { testing.expectf(t, runtime.string_eq(item, my_map2[key]), "Expected value %s to be present in unmarshaled map", key) } } From b4683f4399a7c83152e986ee8c61acabee806962 Mon Sep 17 00:00:00 2001 From: xzores Date: Fri, 12 Jul 2024 23:57:45 +0200 Subject: [PATCH 014/198] Update stb_truetype.odin --- vendor/stb/truetype/stb_truetype.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/stb/truetype/stb_truetype.odin b/vendor/stb/truetype/stb_truetype.odin index 6993cd2b7..e6defff5f 100644 --- a/vendor/stb/truetype/stb_truetype.odin +++ b/vendor/stb/truetype/stb_truetype.odin @@ -568,7 +568,7 @@ foreign stbtt { // some of the values for the IDs are below; for more see the truetype spec: // http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html // http://www.microsoft.com/typography/otspec/name.htm - GetFontNameString :: proc(font: ^fontinfo, length: c.int, platformID: PLATFORM_ID, encodingID, languageID, nameID: c.int) -> cstring --- + GetFontNameString :: proc(font: ^fontinfo, length: ^c.int, platformID: PLATFORM_ID, encodingID, languageID, nameID: c.int) -> cstring --- } From 64ae99f016998fe78a5a0304767463c46050567a Mon Sep 17 00:00:00 2001 From: VladPavliuk Date: Sat, 13 Jul 2024 14:13:59 +0300 Subject: [PATCH 015/198] Add support of `ignore` tag for `json.marshal` --- core/encoding/json/marshal.odin | 4 +++- tests/core/encoding/json/test_core_json.odin | 21 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/core/encoding/json/marshal.odin b/core/encoding/json/marshal.odin index 0464c24d1..99ae81485 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -406,7 +406,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: ti := runtime.type_info_base(type_info_of(v.id)) info := ti.variant.(runtime.Type_Info_Struct) first_iteration := true - for name, i in info.names { + fields_loop: for name, i in info.names { omitempty := false json_name, extra := json_name_from_tag_value(reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "json")) @@ -414,6 +414,8 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: switch flag { case "omitempty": omitempty = true + case "ignore": + continue fields_loop } } diff --git a/tests/core/encoding/json/test_core_json.odin b/tests/core/encoding/json/test_core_json.odin index 92c050952..9fde5a443 100644 --- a/tests/core/encoding/json/test_core_json.odin +++ b/tests/core/encoding/json/test_core_json.odin @@ -368,4 +368,25 @@ utf8_string_of_multibyte_characters :: proc(t: ^testing.T) { val, err := json.parse_string(`"🐛✅"`) defer json.destroy_value(val) testing.expectf(t, err == nil, "Expected `json.parse` to return nil, got %v", err) +} + +@test +struct_with_ignore_tags :: proc(t: ^testing.T) { + My_Struct :: struct { + a: string `json:"_,ignore"`, + } + + my_struct := My_Struct{ + a = "test", + } + + my_struct_marshaled, marshal_err := json.marshal(my_struct) + defer delete(my_struct_marshaled) + + testing.expectf(t, marshal_err == nil, "Expected `json.marshal` to return nil error, got %v", marshal_err) + + my_struct_json := transmute(string)my_struct_marshaled + expected_json := `{}` + + testing.expectf(t, expected_json == my_struct_json, "Expected `json.marshal` to return %s, got %s", expected_json, my_struct_json) } \ No newline at end of file From 75076e2d649e8ec9df541a9bfb4e3de408a9167f Mon Sep 17 00:00:00 2001 From: Tadeo hepperle Date: Sat, 13 Jul 2024 16:01:33 +0200 Subject: [PATCH 016/198] RenderPassEncoderSetPushConstants should take a rawptr instead of cstring for the data --- vendor/wgpu/wgpu.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/wgpu/wgpu.odin b/vendor/wgpu/wgpu.odin index 74df83fde..4efe572cf 100644 --- a/vendor/wgpu/wgpu.odin +++ b/vendor/wgpu/wgpu.odin @@ -1676,7 +1676,7 @@ when ODIN_OS != .JS { GetVersion :: proc() -> u32 --- - RenderPassEncoderSetPushConstants :: proc(encoder: RenderPassEncoder, stages: ShaderStageFlags, offset: u32, sizeBytes: u32, data: cstring) --- + RenderPassEncoderSetPushConstants :: proc(encoder: RenderPassEncoder, stages: ShaderStageFlags, offset: u32, sizeBytes: u32, data: rawptr) --- RenderPassEncoderMultiDrawIndirect :: proc(encoder: RenderPassEncoder, buffer: Buffer, offset: u64, count: u32) --- RenderPassEncoderMultiDrawIndexedIndirect :: proc(encoder: RenderPassEncoder, buffer: Buffer, offset: u64, count: u32) --- From 76fe5d1346e623b535d5168ec36e1254ef769c68 Mon Sep 17 00:00:00 2001 From: VladPavliuk Date: Sun, 14 Jul 2024 00:21:05 +0300 Subject: [PATCH 017/198] Align ignore syntax of json tags with fmt, cbor --- core/encoding/json/marshal.odin | 9 ++++++--- tests/core/encoding/json/test_core_json.odin | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/core/encoding/json/marshal.odin b/core/encoding/json/marshal.odin index 99ae81485..30426f911 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -406,16 +406,19 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: ti := runtime.type_info_base(type_info_of(v.id)) info := ti.variant.(runtime.Type_Info_Struct) first_iteration := true - fields_loop: for name, i in info.names { + for name, i in info.names { omitempty := false json_name, extra := json_name_from_tag_value(reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "json")) + + if json_name == "-" { + continue + } + for flag in strings.split_iterator(&extra, ",") { switch flag { case "omitempty": omitempty = true - case "ignore": - continue fields_loop } } diff --git a/tests/core/encoding/json/test_core_json.odin b/tests/core/encoding/json/test_core_json.odin index 9fde5a443..a50dd7fe0 100644 --- a/tests/core/encoding/json/test_core_json.odin +++ b/tests/core/encoding/json/test_core_json.odin @@ -373,7 +373,7 @@ utf8_string_of_multibyte_characters :: proc(t: ^testing.T) { @test struct_with_ignore_tags :: proc(t: ^testing.T) { My_Struct :: struct { - a: string `json:"_,ignore"`, + a: string `json:"-"`, } my_struct := My_Struct{ From b686b072d5e6bba55af194db52dac1cf02cbdda1 Mon Sep 17 00:00:00 2001 From: flysand7 Date: Sun, 14 Jul 2024 14:59:44 +1100 Subject: [PATCH 018/198] [os2/file]: Fixes related to handle inheritance All file handles created on Windows used to be made non-inheritable, by forcing the .Close_On_Exec flag in _open() function. In addition, there was an issue with security descriptor being freed before use, which has been fixed. --- core/os/os2/file_windows.odin | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 37f8f44de..b88ee8a69 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -75,11 +75,9 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han access |= win32.FILE_APPEND_DATA } share_mode := u32(win32.FILE_SHARE_READ | win32.FILE_SHARE_WRITE) - sa: ^win32.SECURITY_ATTRIBUTES - if .Close_On_Exec not_in flags { - sa = &win32.SECURITY_ATTRIBUTES{} - sa.nLength = size_of(win32.SECURITY_ATTRIBUTES) - sa.bInheritHandle = true + sa := win32.SECURITY_ATTRIBUTES { + nLength = size_of(win32.SECURITY_ATTRIBUTES), + bInheritHandle = .Close_On_Exec not_in flags, } create_mode: u32 = win32.OPEN_EXISTING @@ -101,7 +99,7 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han // NOTE(bill): Open has just asked to create a file in read-only mode. // If the file already exists, to make it akin to a *nix open call, // the call preserves the existing permissions. - h := win32.CreateFileW(path, access, share_mode, sa, win32.TRUNCATE_EXISTING, win32.FILE_ATTRIBUTE_NORMAL, nil) + h := win32.CreateFileW(path, access, share_mode, &sa, win32.TRUNCATE_EXISTING, win32.FILE_ATTRIBUTE_NORMAL, nil) if h == win32.INVALID_HANDLE { switch e := win32.GetLastError(); e { case win32.ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, win32.ERROR_PATH_NOT_FOUND: @@ -114,7 +112,7 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han } } } - h := win32.CreateFileW(path, access, share_mode, sa, create_mode, attrs, nil) + h := win32.CreateFileW(path, access, share_mode, &sa, create_mode, attrs, nil) if h == win32.INVALID_HANDLE { return 0, _get_platform_error() } @@ -124,7 +122,7 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, err: Error) { flags := flags if flags != nil else {.Read} - handle := _open_internal(name, flags + {.Close_On_Exec}, perm) or_return + handle := _open_internal(name, flags, perm) or_return return _new_file(handle, name), nil } From cdede4928cbbe38e043f3a784020b2ed40c5470a Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Sat, 13 Jul 2024 23:16:22 -0700 Subject: [PATCH 019/198] move to a growing queue --- src/thread_pool.cpp | 106 +++++++++++++++++++++++++++++--------------- src/threading.cpp | 39 +++++++++++----- 2 files changed, 99 insertions(+), 46 deletions(-) diff --git a/src/thread_pool.cpp b/src/thread_pool.cpp index 5dbbe37c4..2b176db1c 100644 --- a/src/thread_pool.cpp +++ b/src/thread_pool.cpp @@ -16,7 +16,6 @@ struct ThreadPool { std::atomic running; Futex tasks_available; - Futex tasks_left; }; @@ -46,7 +45,7 @@ gb_internal void thread_pool_destroy(ThreadPool *pool) { for_array_off(i, 1, pool->threads) { Thread *t = &pool->threads[i]; - pool->tasks_available.fetch_add(1, std::memory_order_relaxed); + pool->tasks_available.fetch_add(1, std::memory_order_acquire); futex_broadcast(&pool->tasks_available); thread_join_and_destroy(t); } @@ -54,51 +53,86 @@ gb_internal void thread_pool_destroy(ThreadPool *pool) { gb_free(pool->threads_allocator, pool->threads.data); } +TaskRingBuffer *taskring_grow(TaskRingBuffer *ring, ssize_t bottom, ssize_t top) { + TaskRingBuffer *new_ring = taskring_init(ring->size * 2); + for (ssize_t i = top; i < bottom; i++) { + new_ring->buffer[i % new_ring->size] = ring->buffer[i % ring->size]; + } + return new_ring; +} + void thread_pool_queue_push(Thread *thread, WorkerTask task) { - u64 capture; - u64 new_capture; - do { - capture = thread->head_and_tail.load(); + ssize_t bot = thread->queue.bottom.load(std::memory_order_relaxed); + ssize_t top = thread->queue.top.load(std::memory_order_acquire); + TaskRingBuffer *cur_ring = thread->queue.ring.load(std::memory_order_relaxed); - u64 mask = thread->capacity - 1; - u64 head = (capture >> 32) & mask; - u64 tail = ((u32)capture) & mask; + ssize_t size = bot - top; + if (size > (cur_ring->size - 1)) { + // Queue is full + thread->queue.ring = taskring_grow(thread->queue.ring, bot, top); + cur_ring = thread->queue.ring.load(std::memory_order_relaxed); + } - u64 new_head = (head + 1) & mask; - GB_ASSERT_MSG(new_head != tail, "Thread Queue Full!"); - - // This *must* be done in here, to avoid a potential race condition where we no longer own the slot by the time we're assigning - thread->queue[head] = task; - new_capture = (new_head << 32) | tail; - } while (!thread->head_and_tail.compare_exchange_weak(capture, new_capture)); + cur_ring->buffer[bot % cur_ring->size] = task; + std::atomic_thread_fence(std::memory_order_release); + thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); thread->pool->tasks_left.fetch_add(1, std::memory_order_release); thread->pool->tasks_available.fetch_add(1, std::memory_order_relaxed); futex_broadcast(&thread->pool->tasks_available); } -bool thread_pool_queue_pop(Thread *thread, WorkerTask *task) { - u64 capture; - u64 new_capture; - do { - capture = thread->head_and_tail.load(std::memory_order_acquire); +bool thread_pool_queue_take(Thread *thread, WorkerTask *task) { + ssize_t bot = thread->queue.bottom.load(std::memory_order_relaxed) - 1; + TaskRingBuffer *cur_ring = thread->queue.ring.load(std::memory_order_relaxed); + thread->queue.bottom.store(bot, std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_seq_cst); - u64 mask = thread->capacity - 1; - u64 head = (capture >> 32) & mask; - u64 tail = ((u32)capture) & mask; + ssize_t top = thread->queue.top.load(std::memory_order_relaxed); + if (top <= bot) { - u64 new_tail = (tail + 1) & mask; - if (tail == head) { - return false; + // Queue is not empty + *task = cur_ring->buffer[bot % cur_ring->size]; + if (top == bot) { + // Only one entry left in queue + if (!thread->queue.top.compare_exchange_strong(top, top + 1, std::memory_order_seq_cst, std::memory_order_relaxed)) { + // Race failed + thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); + return false; + } + + thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); + return true; } - // Making a copy of the task before we increment the tail, avoiding the same potential race condition as above - *task = thread->queue[tail]; + // We got a task without hitting a race + return true; + } else { + // Queue is empty + thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); + return false; + } +} - new_capture = (head << 32) | new_tail; - } while (!thread->head_and_tail.compare_exchange_weak(capture, new_capture, std::memory_order_release)); +bool thread_pool_queue_steal(Thread *thread, WorkerTask *task) { + ssize_t top = thread->queue.top.load(std::memory_order_acquire); + std::atomic_thread_fence(std::memory_order_seq_cst); + ssize_t bot = thread->queue.bottom.load(std::memory_order_acquire); - return true; + bool ret = false; + if (top < bot) { + // Queue is not empty + TaskRingBuffer *cur_ring = thread->queue.ring.load(std::memory_order_consume); + *task = cur_ring->buffer[top % cur_ring->size]; + + if (!thread->queue.top.compare_exchange_strong(top, top + 1, std::memory_order_seq_cst, std::memory_order_relaxed)) { + // Race failed + ret = false; + } else { + ret = true; + } + } + return ret; } gb_internal bool thread_pool_add_task(ThreadPool *pool, WorkerTaskProc *proc, void *data) { @@ -115,12 +149,11 @@ gb_internal void thread_pool_wait(ThreadPool *pool) { while (pool->tasks_left.load(std::memory_order_acquire)) { // if we've got tasks on our queue, run them - while (thread_pool_queue_pop(current_thread, &task)) { + while (thread_pool_queue_take(current_thread, &task)) { task.do_work(task.data); pool->tasks_left.fetch_sub(1, std::memory_order_release); } - // is this mem-barriered enough? // This *must* be executed in this order, so the futex wakes immediately // if rem_tasks has changed since we checked last, otherwise the program @@ -145,7 +178,7 @@ gb_internal THREAD_PROC(thread_pool_thread_proc) { usize finished_tasks = 0; i32 state; - while (thread_pool_queue_pop(current_thread, &task)) { + while (thread_pool_queue_take(current_thread, &task)) { task.do_work(task.data); pool->tasks_left.fetch_sub(1, std::memory_order_release); @@ -167,7 +200,7 @@ gb_internal THREAD_PROC(thread_pool_thread_proc) { Thread *thread = &pool->threads.data[idx]; WorkerTask task; - if (thread_pool_queue_pop(thread, &task)) { + if (thread_pool_queue_steal(thread, &task)) { task.do_work(task.data); pool->tasks_left.fetch_sub(1, std::memory_order_release); @@ -182,6 +215,7 @@ gb_internal THREAD_PROC(thread_pool_thread_proc) { // if we've done all our work, and there's nothing to steal, go to sleep state = pool->tasks_available.load(std::memory_order_acquire); + if (!pool->running) { break; } futex_wait(&pool->tasks_available, state); main_loop_continue:; diff --git a/src/threading.cpp b/src/threading.cpp index 717dcb874..dda98631b 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -46,6 +46,18 @@ typedef struct WorkerTask { void *data; } WorkerTask; +typedef struct TaskRingBuffer { + std::atomic size; + std::atomic buffer; +} TaskRingBuffer; + +typedef struct TaskQueue { + std::atomic top; + std::atomic bottom; + + std::atomic ring; +} TaskQueue; + struct Thread { #if defined(GB_SYSTEM_WINDOWS) void *win32_handle; @@ -54,12 +66,9 @@ struct Thread { #endif isize idx; - - WorkerTask *queue; - size_t capacity; - std::atomic head_and_tail; - isize stack_size; + + struct TaskQueue queue; struct ThreadPool *pool; }; @@ -551,6 +560,18 @@ gb_internal void *internal_thread_proc(void *arg) { } #endif +TaskRingBuffer *taskring_init(ssize_t size) { + TaskRingBuffer *ring = (TaskRingBuffer *)gb_alloc(heap_allocator(), sizeof(TaskRingBuffer)); + ring->size = size; + ring->buffer = (WorkerTask *)gb_alloc_array(heap_allocator(), WorkerTask, ring->size); + return ring; +} + +void thread_queue_destroy(TaskQueue *q) { + gb_free(heap_allocator(), (*q->ring).buffer); + gb_free(heap_allocator(), q->ring); +} + gb_internal void thread_init(ThreadPool *pool, Thread *t, isize idx) { gb_zero_item(t); #if defined(GB_SYSTEM_WINDOWS) @@ -559,14 +580,12 @@ gb_internal void thread_init(ThreadPool *pool, Thread *t, isize idx) { t->posix_handle = 0; #endif - t->capacity = 1 << 14; // must be a power of 2 - t->queue = gb_alloc_array(heap_allocator(), WorkerTask, t->capacity); - t->head_and_tail = 0; + // Size must be a power of 2 + t->queue.ring = taskring_init(1 << 14); t->pool = pool; t->idx = idx; } - gb_internal void thread_init_and_start(ThreadPool *pool, Thread *t, isize idx) { thread_init(pool, t, idx); isize stack_size = 0; @@ -598,7 +617,7 @@ gb_internal void thread_join_and_destroy(Thread *t) { t->posix_handle = 0; #endif - gb_free(heap_allocator(), t->queue); + thread_queue_destroy(&t->queue); } gb_internal void thread_set_name(Thread *t, char const *name) { From 4420128dc1d15775d1f56d47b858d8ffe75e4b9f Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Sun, 14 Jul 2024 00:29:58 -0700 Subject: [PATCH 020/198] handle steal-fail vs steal-empty --- src/thread_pool.cpp | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/thread_pool.cpp b/src/thread_pool.cpp index 2b176db1c..da7e724a8 100644 --- a/src/thread_pool.cpp +++ b/src/thread_pool.cpp @@ -10,6 +10,12 @@ gb_internal void thread_pool_destroy(ThreadPool *pool); gb_internal bool thread_pool_add_task(ThreadPool *pool, WorkerTaskProc *proc, void *data); gb_internal void thread_pool_wait(ThreadPool *pool); +enum GrabState { + GrabSuccess = 0, + GrabEmpty = 1, + GrabFailed = 2, +}; + struct ThreadPool { gbAllocator threads_allocator; Slice threads; @@ -82,7 +88,7 @@ void thread_pool_queue_push(Thread *thread, WorkerTask task) { futex_broadcast(&thread->pool->tasks_available); } -bool thread_pool_queue_take(Thread *thread, WorkerTask *task) { +GrabState thread_pool_queue_take(Thread *thread, WorkerTask *task) { ssize_t bot = thread->queue.bottom.load(std::memory_order_relaxed) - 1; TaskRingBuffer *cur_ring = thread->queue.ring.load(std::memory_order_relaxed); thread->queue.bottom.store(bot, std::memory_order_relaxed); @@ -98,28 +104,28 @@ bool thread_pool_queue_take(Thread *thread, WorkerTask *task) { if (!thread->queue.top.compare_exchange_strong(top, top + 1, std::memory_order_seq_cst, std::memory_order_relaxed)) { // Race failed thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); - return false; + return GrabEmpty; } thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); - return true; + return GrabSuccess; } // We got a task without hitting a race - return true; + return GrabSuccess; } else { // Queue is empty thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); - return false; + return GrabEmpty; } } -bool thread_pool_queue_steal(Thread *thread, WorkerTask *task) { +GrabState thread_pool_queue_steal(Thread *thread, WorkerTask *task) { ssize_t top = thread->queue.top.load(std::memory_order_acquire); std::atomic_thread_fence(std::memory_order_seq_cst); ssize_t bot = thread->queue.bottom.load(std::memory_order_acquire); - bool ret = false; + GrabState ret = GrabEmpty; if (top < bot) { // Queue is not empty TaskRingBuffer *cur_ring = thread->queue.ring.load(std::memory_order_consume); @@ -127,9 +133,9 @@ bool thread_pool_queue_steal(Thread *thread, WorkerTask *task) { if (!thread->queue.top.compare_exchange_strong(top, top + 1, std::memory_order_seq_cst, std::memory_order_relaxed)) { // Race failed - ret = false; + ret = GrabFailed; } else { - ret = true; + ret = GrabSuccess; } } return ret; @@ -149,7 +155,7 @@ gb_internal void thread_pool_wait(ThreadPool *pool) { while (pool->tasks_left.load(std::memory_order_acquire)) { // if we've got tasks on our queue, run them - while (thread_pool_queue_take(current_thread, &task)) { + while (!thread_pool_queue_take(current_thread, &task)) { task.do_work(task.data); pool->tasks_left.fetch_sub(1, std::memory_order_release); } @@ -178,7 +184,7 @@ gb_internal THREAD_PROC(thread_pool_thread_proc) { usize finished_tasks = 0; i32 state; - while (thread_pool_queue_take(current_thread, &task)) { + while (!thread_pool_queue_take(current_thread, &task)) { task.do_work(task.data); pool->tasks_left.fetch_sub(1, std::memory_order_release); @@ -200,7 +206,13 @@ gb_internal THREAD_PROC(thread_pool_thread_proc) { Thread *thread = &pool->threads.data[idx]; WorkerTask task; - if (thread_pool_queue_steal(thread, &task)) { + + GrabState ret = thread_pool_queue_steal(thread, &task); + if (ret == GrabFailed) { + goto main_loop_continue; + } else if (ret == GrabEmpty) { + continue; + } else if (ret == GrabSuccess) { task.do_work(task.data); pool->tasks_left.fetch_sub(1, std::memory_order_release); From 64feb7599e8ec01c2ec7c8d709df1cc70651c06b Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Sun, 14 Jul 2024 00:33:40 -0700 Subject: [PATCH 021/198] move to isize --- src/thread_pool.cpp | 18 +++++++++--------- src/threading.cpp | 8 ++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/thread_pool.cpp b/src/thread_pool.cpp index da7e724a8..bf953ddd0 100644 --- a/src/thread_pool.cpp +++ b/src/thread_pool.cpp @@ -59,20 +59,20 @@ gb_internal void thread_pool_destroy(ThreadPool *pool) { gb_free(pool->threads_allocator, pool->threads.data); } -TaskRingBuffer *taskring_grow(TaskRingBuffer *ring, ssize_t bottom, ssize_t top) { +TaskRingBuffer *taskring_grow(TaskRingBuffer *ring, isize bottom, isize top) { TaskRingBuffer *new_ring = taskring_init(ring->size * 2); - for (ssize_t i = top; i < bottom; i++) { + for (isize i = top; i < bottom; i++) { new_ring->buffer[i % new_ring->size] = ring->buffer[i % ring->size]; } return new_ring; } void thread_pool_queue_push(Thread *thread, WorkerTask task) { - ssize_t bot = thread->queue.bottom.load(std::memory_order_relaxed); - ssize_t top = thread->queue.top.load(std::memory_order_acquire); + isize bot = thread->queue.bottom.load(std::memory_order_relaxed); + isize top = thread->queue.top.load(std::memory_order_acquire); TaskRingBuffer *cur_ring = thread->queue.ring.load(std::memory_order_relaxed); - ssize_t size = bot - top; + isize size = bot - top; if (size > (cur_ring->size - 1)) { // Queue is full thread->queue.ring = taskring_grow(thread->queue.ring, bot, top); @@ -89,12 +89,12 @@ void thread_pool_queue_push(Thread *thread, WorkerTask task) { } GrabState thread_pool_queue_take(Thread *thread, WorkerTask *task) { - ssize_t bot = thread->queue.bottom.load(std::memory_order_relaxed) - 1; + isize bot = thread->queue.bottom.load(std::memory_order_relaxed) - 1; TaskRingBuffer *cur_ring = thread->queue.ring.load(std::memory_order_relaxed); thread->queue.bottom.store(bot, std::memory_order_relaxed); std::atomic_thread_fence(std::memory_order_seq_cst); - ssize_t top = thread->queue.top.load(std::memory_order_relaxed); + isize top = thread->queue.top.load(std::memory_order_relaxed); if (top <= bot) { // Queue is not empty @@ -121,9 +121,9 @@ GrabState thread_pool_queue_take(Thread *thread, WorkerTask *task) { } GrabState thread_pool_queue_steal(Thread *thread, WorkerTask *task) { - ssize_t top = thread->queue.top.load(std::memory_order_acquire); + isize top = thread->queue.top.load(std::memory_order_acquire); std::atomic_thread_fence(std::memory_order_seq_cst); - ssize_t bot = thread->queue.bottom.load(std::memory_order_acquire); + isize bot = thread->queue.bottom.load(std::memory_order_acquire); GrabState ret = GrabEmpty; if (top < bot) { diff --git a/src/threading.cpp b/src/threading.cpp index dda98631b..ac79efb05 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -47,13 +47,13 @@ typedef struct WorkerTask { } WorkerTask; typedef struct TaskRingBuffer { - std::atomic size; + std::atomic size; std::atomic buffer; } TaskRingBuffer; typedef struct TaskQueue { - std::atomic top; - std::atomic bottom; + std::atomic top; + std::atomic bottom; std::atomic ring; } TaskQueue; @@ -560,7 +560,7 @@ gb_internal void *internal_thread_proc(void *arg) { } #endif -TaskRingBuffer *taskring_init(ssize_t size) { +TaskRingBuffer *taskring_init(isize size) { TaskRingBuffer *ring = (TaskRingBuffer *)gb_alloc(heap_allocator(), sizeof(TaskRingBuffer)); ring->size = size; ring->buffer = (WorkerTask *)gb_alloc_array(heap_allocator(), WorkerTask, ring->size); From edc793d7c123a38826860ef72684308902a7012c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 11:39:05 +0100 Subject: [PATCH 022/198] Add `#no_capture args: ..T` to reuse the backing array stack memory --- core/fmt/fmt.odin | 58 +++++++++++++++++++-------------------- core/fmt/fmt_js.odin | 24 ++++++++-------- core/fmt/fmt_os.odin | 24 ++++++++-------- src/check_expr.cpp | 17 ++++++++++++ src/check_type.cpp | 17 ++++++++++++ src/checker.cpp | 1 + src/checker.hpp | 7 +++++ src/entity.cpp | 2 +- src/llvm_backend.hpp | 7 +++++ src/llvm_backend_proc.cpp | 27 +++++++++++++++++- src/parser.cpp | 1 + src/parser.hpp | 8 ++++-- 12 files changed, 136 insertions(+), 57 deletions(-) diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index 234f4afbd..e56211346 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -125,7 +125,7 @@ register_user_formatter :: proc(id: typeid, formatter: User_Formatter) -> Regist // Returns: A formatted string. // @(require_results) -aprint :: proc(args: ..any, sep := " ", allocator := context.allocator) -> string { +aprint :: proc(#no_capture args: ..any, sep := " ", allocator := context.allocator) -> string { str: strings.Builder strings.builder_init(&str, allocator) return sbprint(&str, ..args, sep=sep) @@ -141,7 +141,7 @@ aprint :: proc(args: ..any, sep := " ", allocator := context.allocator) -> strin // Returns: A formatted string with a newline character at the end. // @(require_results) -aprintln :: proc(args: ..any, sep := " ", allocator := context.allocator) -> string { +aprintln :: proc(#no_capture args: ..any, sep := " ", allocator := context.allocator) -> string { str: strings.Builder strings.builder_init(&str, allocator) return sbprintln(&str, ..args, sep=sep) @@ -158,7 +158,7 @@ aprintln :: proc(args: ..any, sep := " ", allocator := context.allocator) -> str // Returns: A formatted string. The returned string must be freed accordingly. // @(require_results) -aprintf :: proc(fmt: string, args: ..any, allocator := context.allocator, newline := false) -> string { +aprintf :: proc(fmt: string, #no_capture args: ..any, allocator := context.allocator, newline := false) -> string { str: strings.Builder strings.builder_init(&str, allocator) return sbprintf(&str, fmt, ..args, newline=newline) @@ -174,7 +174,7 @@ aprintf :: proc(fmt: string, args: ..any, allocator := context.allocator, newlin // Returns: A formatted string. The returned string must be freed accordingly. // @(require_results) -aprintfln :: proc(fmt: string, args: ..any, allocator := context.allocator) -> string { +aprintfln :: proc(fmt: string, #no_capture args: ..any, allocator := context.allocator) -> string { return aprintf(fmt, ..args, allocator=allocator, newline=true) } // Creates a formatted string @@ -188,7 +188,7 @@ aprintfln :: proc(fmt: string, args: ..any, allocator := context.allocator) -> s // Returns: A formatted string. // @(require_results) -tprint :: proc(args: ..any, sep := " ") -> string { +tprint :: proc(#no_capture args: ..any, sep := " ") -> string { str: strings.Builder strings.builder_init(&str, context.temp_allocator) return sbprint(&str, ..args, sep=sep) @@ -204,7 +204,7 @@ tprint :: proc(args: ..any, sep := " ") -> string { // Returns: A formatted string with a newline character at the end. // @(require_results) -tprintln :: proc(args: ..any, sep := " ") -> string { +tprintln :: proc(#no_capture args: ..any, sep := " ") -> string { str: strings.Builder strings.builder_init(&str, context.temp_allocator) return sbprintln(&str, ..args, sep=sep) @@ -221,7 +221,7 @@ tprintln :: proc(args: ..any, sep := " ") -> string { // Returns: A formatted string. // @(require_results) -tprintf :: proc(fmt: string, args: ..any, newline := false) -> string { +tprintf :: proc(fmt: string, #no_capture args: ..any, newline := false) -> string { str: strings.Builder strings.builder_init(&str, context.temp_allocator) return sbprintf(&str, fmt, ..args, newline=newline) @@ -237,7 +237,7 @@ tprintf :: proc(fmt: string, args: ..any, newline := false) -> string { // Returns: A formatted string. // @(require_results) -tprintfln :: proc(fmt: string, args: ..any) -> string { +tprintfln :: proc(fmt: string, #no_capture args: ..any) -> string { return tprintf(fmt, ..args, newline=true) } // Creates a formatted string using a supplied buffer as the backing array. Writes into the buffer. @@ -249,7 +249,7 @@ tprintfln :: proc(fmt: string, args: ..any) -> string { // // Returns: A formatted string // -bprint :: proc(buf: []byte, args: ..any, sep := " ") -> string { +bprint :: proc(buf: []byte, #no_capture args: ..any, sep := " ") -> string { sb := strings.builder_from_bytes(buf) return sbprint(&sb, ..args, sep=sep) } @@ -262,7 +262,7 @@ bprint :: proc(buf: []byte, args: ..any, sep := " ") -> string { // // Returns: A formatted string with a newline character at the end // -bprintln :: proc(buf: []byte, args: ..any, sep := " ") -> string { +bprintln :: proc(buf: []byte, #no_capture args: ..any, sep := " ") -> string { sb := strings.builder_from_bytes(buf) return sbprintln(&sb, ..args, sep=sep) } @@ -276,7 +276,7 @@ bprintln :: proc(buf: []byte, args: ..any, sep := " ") -> string { // // Returns: A formatted string // -bprintf :: proc(buf: []byte, fmt: string, args: ..any, newline := false) -> string { +bprintf :: proc(buf: []byte, fmt: string, #no_capture args: ..any, newline := false) -> string { sb := strings.builder_from_bytes(buf) return sbprintf(&sb, fmt, ..args, newline=newline) } @@ -289,7 +289,7 @@ bprintf :: proc(buf: []byte, fmt: string, args: ..any, newline := false) -> stri // // Returns: A formatted string // -bprintfln :: proc(buf: []byte, fmt: string, args: ..any) -> string { +bprintfln :: proc(buf: []byte, fmt: string, #no_capture args: ..any) -> string { return bprintf(buf, fmt, ..args, newline=true) } // Runtime assertion with a formatted message @@ -301,14 +301,14 @@ bprintfln :: proc(buf: []byte, fmt: string, args: ..any) -> string { // - loc: The location of the caller // @(disabled=ODIN_DISABLE_ASSERT) -assertf :: proc(condition: bool, fmt: string, args: ..any, loc := #caller_location) { +assertf :: proc(condition: bool, fmt: string, #no_capture args: ..any, loc := #caller_location) { if !condition { // NOTE(dragos): We are using the same trick as in builtin.assert // to improve performance to make the CPU not // execute speculatively, making it about an order of // magnitude faster @(cold) - internal :: proc(loc: runtime.Source_Code_Location, fmt: string, args: ..any) { + internal :: proc(loc: runtime.Source_Code_Location, fmt: string, #no_capture args: ..any) { p := context.assertion_failure_proc if p == nil { p = runtime.default_assertion_failure_proc @@ -326,7 +326,7 @@ assertf :: proc(condition: bool, fmt: string, args: ..any, loc := #caller_locati // - args: A variadic list of arguments to be formatted // - loc: The location of the caller // -panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! { +panicf :: proc(fmt: string, #no_capture args: ..any, loc := #caller_location) -> ! { p := context.assertion_failure_proc if p == nil { p = runtime.default_assertion_failure_proc @@ -346,7 +346,7 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! { // Returns: A formatted C string // @(require_results) -caprintf :: proc(format: string, args: ..any, newline := false) -> cstring { +caprintf :: proc(format: string, #no_capture args: ..any, newline := false) -> cstring { str: strings.Builder strings.builder_init(&str) sbprintf(&str, format, ..args, newline=newline) @@ -365,7 +365,7 @@ caprintf :: proc(format: string, args: ..any, newline := false) -> cstring { // Returns: A formatted C string // @(require_results) -caprintfln :: proc(format: string, args: ..any) -> cstring { +caprintfln :: proc(format: string, #no_capture args: ..any) -> cstring { return caprintf(format, ..args, newline=true) } // Creates a formatted C string @@ -379,7 +379,7 @@ caprintfln :: proc(format: string, args: ..any) -> cstring { // Returns: A formatted C string. // @(require_results) -ctprint :: proc(args: ..any, sep := " ") -> cstring { +ctprint :: proc(#no_capture args: ..any, sep := " ") -> cstring { str: strings.Builder strings.builder_init(&str, context.temp_allocator) sbprint(&str, ..args, sep=sep) @@ -399,7 +399,7 @@ ctprint :: proc(args: ..any, sep := " ") -> cstring { // Returns: A formatted C string // @(require_results) -ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring { +ctprintf :: proc(format: string, #no_capture args: ..any, newline := false) -> cstring { str: strings.Builder strings.builder_init(&str, context.temp_allocator) sbprintf(&str, format, ..args, newline=newline) @@ -418,7 +418,7 @@ ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring { // Returns: A formatted C string // @(require_results) -ctprintfln :: proc(format: string, args: ..any) -> cstring { +ctprintfln :: proc(format: string, #no_capture args: ..any) -> cstring { return ctprintf(format, ..args, newline=true) } // Formats using the default print settings and writes to the given strings.Builder @@ -430,7 +430,7 @@ ctprintfln :: proc(format: string, args: ..any) -> cstring { // // Returns: A formatted string // -sbprint :: proc(buf: ^strings.Builder, args: ..any, sep := " ") -> string { +sbprint :: proc(buf: ^strings.Builder, #no_capture args: ..any, sep := " ") -> string { wprint(strings.to_writer(buf), ..args, sep=sep, flush=true) return strings.to_string(buf^) } @@ -443,7 +443,7 @@ sbprint :: proc(buf: ^strings.Builder, args: ..any, sep := " ") -> string { // // Returns: The resulting formatted string // -sbprintln :: proc(buf: ^strings.Builder, args: ..any, sep := " ") -> string { +sbprintln :: proc(buf: ^strings.Builder, #no_capture args: ..any, sep := " ") -> string { wprintln(strings.to_writer(buf), ..args, sep=sep, flush=true) return strings.to_string(buf^) } @@ -457,7 +457,7 @@ sbprintln :: proc(buf: ^strings.Builder, args: ..any, sep := " ") -> string { // // Returns: The resulting formatted string // -sbprintf :: proc(buf: ^strings.Builder, fmt: string, args: ..any, newline := false) -> string { +sbprintf :: proc(buf: ^strings.Builder, fmt: string, #no_capture args: ..any, newline := false) -> string { wprintf(strings.to_writer(buf), fmt, ..args, flush=true, newline=newline) return strings.to_string(buf^) } @@ -469,7 +469,7 @@ sbprintf :: proc(buf: ^strings.Builder, fmt: string, args: ..any, newline := fal // // Returns: A formatted string // -sbprintfln :: proc(buf: ^strings.Builder, format: string, args: ..any) -> string { +sbprintfln :: proc(buf: ^strings.Builder, format: string, #no_capture args: ..any) -> string { return sbprintf(buf, format, ..args, newline=true) } // Formats and writes to an io.Writer using the default print settings @@ -481,7 +481,7 @@ sbprintfln :: proc(buf: ^strings.Builder, format: string, args: ..any) -> string // // Returns: The number of bytes written // -wprint :: proc(w: io.Writer, args: ..any, sep := " ", flush := true) -> int { +wprint :: proc(w: io.Writer, #no_capture args: ..any, sep := " ", flush := true) -> int { fi: Info fi.writer = w @@ -522,7 +522,7 @@ wprint :: proc(w: io.Writer, args: ..any, sep := " ", flush := true) -> int { // // Returns: The number of bytes written // -wprintln :: proc(w: io.Writer, args: ..any, sep := " ", flush := true) -> int { +wprintln :: proc(w: io.Writer, #no_capture args: ..any, sep := " ", flush := true) -> int { fi: Info fi.writer = w @@ -549,11 +549,11 @@ wprintln :: proc(w: io.Writer, args: ..any, sep := " ", flush := true) -> int { // // Returns: The number of bytes written // -wprintf :: proc(w: io.Writer, fmt: string, args: ..any, flush := true, newline := false) -> int { +wprintf :: proc(w: io.Writer, fmt: string, #no_capture args: ..any, flush := true, newline := false) -> int { MAX_CHECKED_ARGS :: 64 assert(len(args) <= MAX_CHECKED_ARGS, "number of args > 64 is unsupported") - parse_options :: proc(fi: ^Info, fmt: string, index, end: int, unused_args: ^bit_set[0 ..< MAX_CHECKED_ARGS], args: ..any) -> int { + parse_options :: proc(fi: ^Info, fmt: string, index, end: int, unused_args: ^bit_set[0 ..< MAX_CHECKED_ARGS], #no_capture args: ..any) -> int { i := index // Prefix @@ -809,7 +809,7 @@ wprintf :: proc(w: io.Writer, fmt: string, args: ..any, flush := true, newline : // // Returns: The number of bytes written. // -wprintfln :: proc(w: io.Writer, format: string, args: ..any, flush := true) -> int { +wprintfln :: proc(w: io.Writer, format: string, #no_capture args: ..any, flush := true) -> int { return wprintf(w, format, ..args, flush=flush, newline=true) } // Writes a ^runtime.Type_Info value to an io.Writer diff --git a/core/fmt/fmt_js.odin b/core/fmt/fmt_js.odin index acf218eb5..4389b8d87 100644 --- a/core/fmt/fmt_js.odin +++ b/core/fmt/fmt_js.odin @@ -43,7 +43,7 @@ fd_to_writer :: proc(fd: os.Handle, loc := #caller_location) -> io.Writer { } // fprint formats using the default print settings and writes to fd -fprint :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true, loc := #caller_location) -> int { +fprint :: proc(fd: os.Handle, #no_capture args: ..any, sep := " ", flush := true, loc := #caller_location) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) @@ -54,7 +54,7 @@ fprint :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true, loc := #ca } // fprintln formats using the default print settings and writes to fd -fprintln :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true, loc := #caller_location) -> int { +fprintln :: proc(fd: os.Handle, #no_capture args: ..any, sep := " ", flush := true, loc := #caller_location) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) @@ -66,7 +66,7 @@ fprintln :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true, loc := # } // fprintf formats according to the specified format string and writes to fd -fprintf :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true, newline := false, loc := #caller_location) -> int { +fprintf :: proc(fd: os.Handle, fmt: string, #no_capture args: ..any, flush := true, newline := false, loc := #caller_location) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) @@ -78,24 +78,24 @@ fprintf :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true, newline } // fprintfln formats according to the specified format string and writes to fd, followed by a newline. -fprintfln :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true, loc := #caller_location) -> int { +fprintfln :: proc(fd: os.Handle, fmt: string, #no_capture args: ..any, flush := true, loc := #caller_location) -> int { return fprintf(fd, fmt, ..args, flush=flush, newline=true, loc=loc) } // print formats using the default print settings and writes to stdout -print :: proc(args: ..any, sep := " ", flush := true) -> int { return wprint(w=stdout, args=args, sep=sep, flush=flush) } +print :: proc(#no_capture args: ..any, sep := " ", flush := true) -> int { return wprint(w=stdout, args=args, sep=sep, flush=flush) } // println formats using the default print settings and writes to stdout -println :: proc(args: ..any, sep := " ", flush := true) -> int { return wprintln(w=stdout, args=args, sep=sep, flush=flush) } +println :: proc(#no_capture args: ..any, sep := " ", flush := true) -> int { return wprintln(w=stdout, args=args, sep=sep, flush=flush) } // printf formats according to the specififed format string and writes to stdout -printf :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush) } +printf :: proc(fmt: string, #no_capture args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush) } // printfln formats according to the specified format string and writes to stdout, followed by a newline. -printfln :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush, newline=true) } +printfln :: proc(fmt: string, #no_capture args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush, newline=true) } // eprint formats using the default print settings and writes to stderr -eprint :: proc(args: ..any, sep := " ", flush := true) -> int { return wprint(w=stderr, args=args, sep=sep, flush=flush) } +eprint :: proc(#no_capture args: ..any, sep := " ", flush := true) -> int { return wprint(w=stderr, args=args, sep=sep, flush=flush) } // eprintln formats using the default print settings and writes to stderr -eprintln :: proc(args: ..any, sep := " ", flush := true) -> int { return wprintln(w=stderr, args=args, sep=sep, flush=flush) } +eprintln :: proc(#no_capture args: ..any, sep := " ", flush := true) -> int { return wprintln(w=stderr, args=args, sep=sep, flush=flush) } // eprintf formats according to the specififed format string and writes to stderr -eprintf :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stderr, fmt, ..args, flush=flush) } +eprintf :: proc(fmt: string, #no_capture args: ..any, flush := true) -> int { return wprintf(stderr, fmt, ..args, flush=flush) } // eprintfln formats according to the specified format string and writes to stderr, followed by a newline. -eprintfln :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush, newline=true) } +eprintfln :: proc(fmt: string, #no_capture args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush, newline=true) } diff --git a/core/fmt/fmt_os.odin b/core/fmt/fmt_os.odin index 9de0d43be..538f7a08b 100644 --- a/core/fmt/fmt_os.odin +++ b/core/fmt/fmt_os.odin @@ -9,7 +9,7 @@ import "core:io" import "core:bufio" // fprint formats using the default print settings and writes to fd -fprint :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true) -> int { +fprint :: proc(fd: os.Handle, #no_capture args: ..any, sep := " ", flush := true) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) @@ -20,7 +20,7 @@ fprint :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true) -> int { } // fprintln formats using the default print settings and writes to fd -fprintln :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true) -> int { +fprintln :: proc(fd: os.Handle, #no_capture args: ..any, sep := " ", flush := true) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) @@ -31,7 +31,7 @@ fprintln :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true) -> int { return wprintln(w, ..args, sep=sep, flush=flush) } // fprintf formats according to the specified format string and writes to fd -fprintf :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true, newline := false) -> int { +fprintf :: proc(fd: os.Handle, fmt: string, #no_capture args: ..any, flush := true, newline := false) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) @@ -42,7 +42,7 @@ fprintf :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true, newline return wprintf(w, fmt, ..args, flush=flush, newline=newline) } // fprintfln formats according to the specified format string and writes to fd, followed by a newline. -fprintfln :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true) -> int { +fprintfln :: proc(fd: os.Handle, fmt: string, #no_capture args: ..any, flush := true) -> int { return fprintf(fd, fmt, ..args, flush=flush, newline=true) } fprint_type :: proc(fd: os.Handle, info: ^runtime.Type_Info, flush := true) -> (n: int, err: io.Error) { @@ -67,19 +67,19 @@ fprint_typeid :: proc(fd: os.Handle, id: typeid, flush := true) -> (n: int, err: } // print formats using the default print settings and writes to os.stdout -print :: proc(args: ..any, sep := " ", flush := true) -> int { return fprint(os.stdout, ..args, sep=sep, flush=flush) } +print :: proc(#no_capture args: ..any, sep := " ", flush := true) -> int { return fprint(os.stdout, ..args, sep=sep, flush=flush) } // println formats using the default print settings and writes to os.stdout -println :: proc(args: ..any, sep := " ", flush := true) -> int { return fprintln(os.stdout, ..args, sep=sep, flush=flush) } +println :: proc(#no_capture args: ..any, sep := " ", flush := true) -> int { return fprintln(os.stdout, ..args, sep=sep, flush=flush) } // printf formats according to the specified format string and writes to os.stdout -printf :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(os.stdout, fmt, ..args, flush=flush) } +printf :: proc(fmt: string, #no_capture args: ..any, flush := true) -> int { return fprintf(os.stdout, fmt, ..args, flush=flush) } // printfln formats according to the specified format string and writes to os.stdout, followed by a newline. -printfln :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(os.stdout, fmt, ..args, flush=flush, newline=true) } +printfln :: proc(fmt: string, #no_capture args: ..any, flush := true) -> int { return fprintf(os.stdout, fmt, ..args, flush=flush, newline=true) } // eprint formats using the default print settings and writes to os.stderr -eprint :: proc(args: ..any, sep := " ", flush := true) -> int { return fprint(os.stderr, ..args, sep=sep, flush=flush) } +eprint :: proc(#no_capture args: ..any, sep := " ", flush := true) -> int { return fprint(os.stderr, ..args, sep=sep, flush=flush) } // eprintln formats using the default print settings and writes to os.stderr -eprintln :: proc(args: ..any, sep := " ", flush := true) -> int { return fprintln(os.stderr, ..args, sep=sep, flush=flush) } +eprintln :: proc(#no_capture args: ..any, sep := " ", flush := true) -> int { return fprintln(os.stderr, ..args, sep=sep, flush=flush) } // eprintf formats according to the specified format string and writes to os.stderr -eprintf :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(os.stderr, fmt, ..args, flush=flush) } +eprintf :: proc(fmt: string, #no_capture args: ..any, flush := true) -> int { return fprintf(os.stderr, fmt, ..args, flush=flush) } // eprintfln formats according to the specified format string and writes to os.stderr, followed by a newline. -eprintfln :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(os.stderr, fmt, ..args, flush=flush, newline=true) } +eprintfln :: proc(fmt: string, #no_capture args: ..any, flush := true) -> int { return fprintf(os.stderr, fmt, ..args, flush=flush, newline=true) } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 12acca0cb..645d8ac5a 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -6033,6 +6033,23 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A Entity *vt = pt->params->Tuple.variables[pt->variadic_index]; o.type = vt->type; + + // NOTE(bill, 2024-07-14): minimize the stack usage for variadic parameter that use `#no_capture` + // on the variadic parameter + if (c->decl && (vt->flags & EntityFlag_NoCapture)) { + bool found = false; + for (NoCaptureData &nc : c->decl->no_captures) { + if (are_types_identical(vt->type, nc.slice_type)) { + nc.max_count = gb_max(nc.max_count, variadic_operands.count); + found = true; + break; + } + } + if (!found) { + array_add(&c->decl->no_captures, NoCaptureData{vt->type, variadic_operands.count}); + } + } + } else { dummy_argument_count += 1; o.type = t_untyped_nil; diff --git a/src/check_type.cpp b/src/check_type.cpp index dd8559114..d1c9bb381 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1953,6 +1953,10 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para error(name, "'#by_ptr' can only be applied to variable fields"); p->flags &= ~FieldFlag_by_ptr; } + if (p->flags&FieldFlag_no_capture) { + error(name, "'#no_capture' can only be applied to variable variadic fields"); + p->flags &= ~FieldFlag_no_capture; + } param = alloc_entity_type_name(scope, name->Ident.token, type, EntityState_Resolved); param->TypeName.is_type_alias = true; @@ -2054,6 +2058,15 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para p->flags &= ~FieldFlag_by_ptr; // Remove the flag } } + if (p->flags&FieldFlag_no_capture) { + if (!(is_variadic && variadic_index == variables.count)) { + error(name, "'#no_capture' can only be applied to a variadic parameter"); + p->flags &= ~FieldFlag_no_capture; + } else if (p->flags & FieldFlag_c_vararg) { + error(name, "'#no_capture' cannot be applied to a #c_vararg parameter"); + p->flags &= ~FieldFlag_no_capture; + } + } if (is_poly_name) { if (p->flags&FieldFlag_no_alias) { @@ -2115,6 +2128,10 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para if (p->flags&FieldFlag_by_ptr) { param->flags |= EntityFlag_ByPtr; } + if (p->flags&FieldFlag_no_capture) { + param->flags |= EntityFlag_NoCapture; + } + param->state = EntityState_Resolved; // NOTE(bill): This should have be resolved whilst determining it add_entity(ctx, scope, name, param); diff --git a/src/checker.cpp b/src/checker.cpp index 8756cce1a..abacc13cb 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -184,6 +184,7 @@ gb_internal void init_decl_info(DeclInfo *d, Scope *scope, DeclInfo *parent) { ptr_set_init(&d->deps, 0); ptr_set_init(&d->type_info_deps, 0); d->labels.allocator = heap_allocator(); + d->no_captures.allocator = heap_allocator(); } gb_internal DeclInfo *make_decl_info(Scope *scope, DeclInfo *parent) { diff --git a/src/checker.hpp b/src/checker.hpp index 781737140..17722f6b6 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -181,6 +181,11 @@ char const *ProcCheckedState_strings[ProcCheckedState_COUNT] { "Checked", }; +struct NoCaptureData { + Type *slice_type; // ..elem_type + isize max_count; +}; + // DeclInfo is used to store information of certain declarations to allow for "any order" usage struct DeclInfo { DeclInfo * parent; // NOTE(bill): only used for procedure literals at the moment @@ -219,6 +224,8 @@ struct DeclInfo { Array labels; + Array no_captures; + // NOTE(bill): this is to prevent a race condition since these procedure literals can be created anywhere at any time struct lbModule *code_gen_module; }; diff --git a/src/entity.cpp b/src/entity.cpp index 41d84e0f7..db6ffdd52 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -45,7 +45,7 @@ enum EntityFlag : u64 { EntityFlag_Value = 1ull<<11, EntityFlag_BitFieldField = 1ull<<12, - + EntityFlag_NoCapture = 1ull<<13, // #no_capture EntityFlag_PolyConst = 1ull<<15, EntityFlag_NotExported = 1ull<<16, diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 005358734..dd1041702 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -296,6 +296,11 @@ enum lbProcedureFlag : u32 { lbProcedureFlag_DebugAllocaCopy = 1<<1, }; +struct lbNoCaptureData { + Type *slice_type; + lbAddr base_array; +}; + struct lbProcedure { u32 flags; u16 state_flags; @@ -336,6 +341,8 @@ struct lbProcedure { bool in_multi_assignment; Array raw_input_parameters; + Array no_captures; + LLVMValueRef temp_callee_return_struct_memory; Ast *curr_stmt; diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 610c34de2..ec244e185 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -517,6 +517,7 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) { lb_start_block(p, p->entry_block); map_init(&p->direct_parameters); + p->no_captures.allocator = heap_allocator(); GB_ASSERT(p->type != nullptr); @@ -3450,8 +3451,32 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { } isize slice_len = var_args.count; if (slice_len > 0) { + lbAddr base_array = {}; + if (e->flags & EntityFlag_NoCapture) { + for (lbNoCaptureData const &nc : p->no_captures) { + if (are_types_identical(nc.slice_type, slice_type)) { + base_array = nc.base_array; + break; + } + } + DeclInfo *d = decl_info_of_entity(p->entity); + if (d != nullptr && base_array.addr.value == nullptr) { + for (NoCaptureData const &nc : d->no_captures) { + if (are_types_identical(nc.slice_type, slice_type)) { + base_array = lb_add_local_generated(p, alloc_type_array(elem_type, nc.max_count), true); + array_add(&p->no_captures, lbNoCaptureData{slice_type, base_array}); + break; + } + } + } + } + + if (base_array.addr.value == nullptr) { + base_array = lb_add_local_generated(p, alloc_type_array(elem_type, slice_len), true); + } + GB_ASSERT(base_array.addr.value != nullptr); + lbAddr slice = lb_add_local_generated(p, slice_type, true); - lbAddr base_array = lb_add_local_generated(p, alloc_type_array(elem_type, slice_len), true); for (isize i = 0; i < var_args.count; i++) { lbValue addr = lb_emit_array_epi(p, base_array.addr, cast(i32)i); diff --git a/src/parser.cpp b/src/parser.cpp index 9ce3d563d..a6a146cfd 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -4014,6 +4014,7 @@ struct ParseFieldPrefixMapping { gb_global ParseFieldPrefixMapping const parse_field_prefix_mappings[] = { {str_lit("using"), Token_using, FieldFlag_using}, {str_lit("no_alias"), Token_Hash, FieldFlag_no_alias}, + {str_lit("no_capture"), Token_Hash, FieldFlag_no_capture}, {str_lit("c_vararg"), Token_Hash, FieldFlag_c_vararg}, {str_lit("const"), Token_Hash, FieldFlag_const}, {str_lit("any_int"), Token_Hash, FieldFlag_any_int}, diff --git a/src/parser.hpp b/src/parser.hpp index 86b3393af..15176f789 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -330,9 +330,10 @@ enum FieldFlag : u32 { FieldFlag_subtype = 1<<7, FieldFlag_by_ptr = 1<<8, FieldFlag_no_broadcast = 1<<9, // disallow array programming + FieldFlag_no_capture = 1<<10, // Internal use by the parser only - FieldFlag_Tags = 1<<10, + FieldFlag_Tags = 1<<11, FieldFlag_Results = 1<<16, @@ -340,7 +341,10 @@ enum FieldFlag : u32 { FieldFlag_Invalid = 1u<<31, // Parameter List Restrictions - FieldFlag_Signature = FieldFlag_ellipsis|FieldFlag_using|FieldFlag_no_alias|FieldFlag_c_vararg|FieldFlag_const|FieldFlag_any_int|FieldFlag_by_ptr|FieldFlag_no_broadcast, + FieldFlag_Signature = FieldFlag_ellipsis|FieldFlag_using|FieldFlag_no_alias|FieldFlag_c_vararg| + FieldFlag_const|FieldFlag_any_int|FieldFlag_by_ptr|FieldFlag_no_broadcast| + FieldFlag_no_capture, + FieldFlag_Struct = FieldFlag_using|FieldFlag_subtype|FieldFlag_Tags, }; From 7e4e3429d7fa1dd3bfa4b8336f8b844a9a9f8ca7 Mon Sep 17 00:00:00 2001 From: Ronald Date: Sun, 14 Jul 2024 11:48:34 +0100 Subject: [PATCH 023/198] Fix logic bug in core/encoding/ini/ini.odin The load_map_from_path had incorrect logic where it would return false for ok when err was equal to nil and true when there was an error. --- core/encoding/ini/ini.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/encoding/ini/ini.odin b/core/encoding/ini/ini.odin index eb0ad9e7c..91a1adcf7 100644 --- a/core/encoding/ini/ini.odin +++ b/core/encoding/ini/ini.odin @@ -121,7 +121,7 @@ load_map_from_path :: proc(path: string, allocator: runtime.Allocator, options : data := os.read_entire_file(path, allocator) or_return defer delete(data, allocator) m, err = load_map_from_string(string(data), allocator, options) - ok = err != nil + ok = err == nil defer if !ok { delete_map(m) } From c7bd9547529a4957e56c7302c5eaca650258ecdc Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 11:56:04 +0100 Subject: [PATCH 024/198] Add more uses of `#no_capture` --- base/runtime/core_builtin.odin | 14 ++++---- base/runtime/core_builtin_soa.odin | 6 ++-- base/runtime/print.odin | 4 +-- core/container/small_array/small_array.odin | 2 +- core/encoding/xml/tokenizer.odin | 6 ++-- core/log/log.odin | 32 +++++++++---------- core/math/big/helpers.odin | 10 +++--- core/math/big/internal.odin | 8 ++--- core/odin/parser/parser.odin | 12 +++---- core/odin/tokenizer/tokenizer.odin | 6 ++-- core/testing/testing.odin | 10 +++--- core/text/scanner/scanner.odin | 2 +- core/text/table/table.odin | 2 +- core/unicode/tools/generate_entity_table.odin | 2 +- tests/core/encoding/xml/test_core_xml.odin | 2 +- tests/core/fmt/test_core_fmt.odin | 2 +- tests/documentation/documentation_tester.odin | 2 +- vendor/OpenGL/wrappers.odin | 2 +- vendor/raylib/raylib.odin | 4 +-- 19 files changed, 64 insertions(+), 64 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index ff87316f2..5f6e1ec16 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -484,7 +484,7 @@ non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc : return _append_elem(array, arg, false, loc=loc) } -_append_elems :: #force_inline proc(array: ^$T/[dynamic]$E, should_zero: bool, loc := #caller_location, args: ..E) -> (n: int, err: Allocator_Error) #optional_allocator_error { +_append_elems :: #force_inline proc(array: ^$T/[dynamic]$E, should_zero: bool, loc := #caller_location, args: []E) -> (n: int, err: Allocator_Error) #optional_allocator_error { if array == nil { return 0, nil } @@ -524,13 +524,13 @@ _append_elems :: #force_inline proc(array: ^$T/[dynamic]$E, should_zero: bool, l } @builtin -append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elems(array, true, loc, ..args) +append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast #no_capture args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + return _append_elems(array, true, loc, args) } @builtin -non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elems(array, false, loc, ..args) +non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast #no_capture args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { + return _append_elems(array, false, loc, args) } // The append_string built-in procedure appends a string to the end of a [dynamic]u8 like type @@ -617,7 +617,7 @@ inject_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, #no_broadcast arg: E, } @builtin -inject_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +inject_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, #no_broadcast #no_capture args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { if array == nil { return } @@ -679,7 +679,7 @@ assign_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #calle @builtin -assign_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +assign_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, #no_broadcast #no_capture args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { new_size := index + len(args) if len(args) == 0 { ok = true diff --git a/base/runtime/core_builtin_soa.odin b/base/runtime/core_builtin_soa.odin index f1b17cbef..108183ea6 100644 --- a/base/runtime/core_builtin_soa.odin +++ b/base/runtime/core_builtin_soa.odin @@ -342,17 +342,17 @@ _append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, zero_memory: bool, #no_broa } @builtin -append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { +append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast #no_capture args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { return _append_soa_elems(array, true, args=args, loc=loc) } @builtin -non_zero_append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { +non_zero_append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast #no_capture args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { return _append_soa_elems(array, false, args=args, loc=loc) } -_append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, zero_memory: bool, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { +_append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, zero_memory: bool, #no_broadcast args: []E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { if array == nil { return } diff --git a/base/runtime/print.odin b/base/runtime/print.odin index 0262e8ef6..ecd301d21 100644 --- a/base/runtime/print.odin +++ b/base/runtime/print.odin @@ -72,7 +72,7 @@ when !ODIN_NO_RTTI { print_string("") } } - println_any :: #force_no_inline proc "contextless" (args: ..any) { + println_any :: #force_no_inline proc "contextless" (#no_capture args: ..any) { context = default_context() loop: for arg, i in args { assert(arg.id != nil) @@ -127,7 +127,7 @@ print_string :: #force_no_inline proc "contextless" (str: string) -> (n: int) { return } -print_strings :: #force_no_inline proc "contextless" (args: ..string) -> (n: int) { +print_strings :: #force_no_inline proc "contextless" (#no_capture args: ..string) -> (n: int) { for str in args { m, err := stderr_write(transmute([]byte)str) n += m diff --git a/core/container/small_array/small_array.odin b/core/container/small_array/small_array.odin index b2068469d..a698d965c 100644 --- a/core/container/small_array/small_array.odin +++ b/core/container/small_array/small_array.odin @@ -139,7 +139,7 @@ clear :: proc "contextless" (a: ^$A/Small_Array($N, $T)) { resize(a, 0) } -push_back_elems :: proc "contextless" (a: ^$A/Small_Array($N, $T), items: ..T) { +push_back_elems :: proc "contextless" (a: ^$A/Small_Array($N, $T), #no_capture items: ..T) { n := copy(a.data[a.len:], items[:]) a.len += n } diff --git a/core/encoding/xml/tokenizer.odin b/core/encoding/xml/tokenizer.odin index a2bbaf28e..8a26f1bce 100644 --- a/core/encoding/xml/tokenizer.odin +++ b/core/encoding/xml/tokenizer.odin @@ -17,7 +17,7 @@ import "core:fmt" import "core:unicode" import "core:unicode/utf8" -Error_Handler :: #type proc(pos: Pos, fmt: string, args: ..any) +Error_Handler :: #type proc(pos: Pos, fmt: string, #no_capture args: ..any) Token :: struct { kind: Token_Kind, @@ -112,13 +112,13 @@ offset_to_pos :: proc(t: ^Tokenizer, offset: int) -> Pos { } } -default_error_handler :: proc(pos: Pos, msg: string, args: ..any) { +default_error_handler :: proc(pos: Pos, msg: string, #no_capture args: ..any) { fmt.eprintf("%s(%d:%d) ", pos.file, pos.line, pos.column) fmt.eprintf(msg, ..args) fmt.eprintf("\n") } -error :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) { +error :: proc(t: ^Tokenizer, offset: int, msg: string, #no_capture args: ..any) { pos := offset_to_pos(t, offset) if t.err != nil { t.err(pos, msg, ..args) diff --git a/core/log/log.odin b/core/log/log.odin index 0d89fdb74..35dff086f 100644 --- a/core/log/log.odin +++ b/core/log/log.odin @@ -75,43 +75,43 @@ nil_logger :: proc() -> Logger { return Logger{nil_logger_proc, nil, Level.Debug, nil} } -debugf :: proc(fmt_str: string, args: ..any, location := #caller_location) { +debugf :: proc(fmt_str: string, #no_capture args: ..any, location := #caller_location) { logf(.Debug, fmt_str, ..args, location=location) } -infof :: proc(fmt_str: string, args: ..any, location := #caller_location) { +infof :: proc(fmt_str: string, #no_capture args: ..any, location := #caller_location) { logf(.Info, fmt_str, ..args, location=location) } -warnf :: proc(fmt_str: string, args: ..any, location := #caller_location) { +warnf :: proc(fmt_str: string, #no_capture args: ..any, location := #caller_location) { logf(.Warning, fmt_str, ..args, location=location) } -errorf :: proc(fmt_str: string, args: ..any, location := #caller_location) { +errorf :: proc(fmt_str: string, #no_capture args: ..any, location := #caller_location) { logf(.Error, fmt_str, ..args, location=location) } -fatalf :: proc(fmt_str: string, args: ..any, location := #caller_location) { +fatalf :: proc(fmt_str: string, #no_capture args: ..any, location := #caller_location) { logf(.Fatal, fmt_str, ..args, location=location) } -debug :: proc(args: ..any, sep := " ", location := #caller_location) { +debug :: proc(#no_capture args: ..any, sep := " ", location := #caller_location) { log(.Debug, ..args, sep=sep, location=location) } -info :: proc(args: ..any, sep := " ", location := #caller_location) { +info :: proc(#no_capture args: ..any, sep := " ", location := #caller_location) { log(.Info, ..args, sep=sep, location=location) } -warn :: proc(args: ..any, sep := " ", location := #caller_location) { +warn :: proc(#no_capture args: ..any, sep := " ", location := #caller_location) { log(.Warning, ..args, sep=sep, location=location) } -error :: proc(args: ..any, sep := " ", location := #caller_location) { +error :: proc(#no_capture args: ..any, sep := " ", location := #caller_location) { log(.Error, ..args, sep=sep, location=location) } -fatal :: proc(args: ..any, sep := " ", location := #caller_location) { +fatal :: proc(#no_capture args: ..any, sep := " ", location := #caller_location) { log(.Fatal, ..args, sep=sep, location=location) } -panic :: proc(args: ..any, location := #caller_location) -> ! { +panic :: proc(#no_capture args: ..any, location := #caller_location) -> ! { log(.Fatal, ..args, location=location) runtime.panic("log.panic", location) } -panicf :: proc(fmt_str: string, args: ..any, location := #caller_location) -> ! { +panicf :: proc(fmt_str: string, #no_capture args: ..any, location := #caller_location) -> ! { logf(.Fatal, fmt_str, ..args, location=location) runtime.panic("log.panicf", location) } @@ -133,14 +133,14 @@ assert :: proc(condition: bool, message := "", loc := #caller_location) { } @(disabled=ODIN_DISABLE_ASSERT) -assertf :: proc(condition: bool, fmt_str: string, args: ..any, loc := #caller_location) { +assertf :: proc(condition: bool, fmt_str: string, #no_capture args: ..any, loc := #caller_location) { if !condition { // NOTE(dragos): We are using the same trick as in builtin.assert // to improve performance to make the CPU not // execute speculatively, making it about an order of // magnitude faster @(cold) - internal :: proc(loc: runtime.Source_Code_Location, fmt_str: string, args: ..any) { + internal :: proc(loc: runtime.Source_Code_Location, fmt_str: string, #no_capture args: ..any) { p := context.assertion_failure_proc if p == nil { p = runtime.default_assertion_failure_proc @@ -155,7 +155,7 @@ assertf :: proc(condition: bool, fmt_str: string, args: ..any, loc := #caller_lo -log :: proc(level: Level, args: ..any, sep := " ", location := #caller_location) { +log :: proc(level: Level, #no_capture args: ..any, sep := " ", location := #caller_location) { logger := context.logger if logger.procedure == nil { return @@ -167,7 +167,7 @@ log :: proc(level: Level, args: ..any, sep := " ", location := #caller_location) logger.procedure(logger.data, level, str, logger.options, location) } -logf :: proc(level: Level, fmt_str: string, args: ..any, location := #caller_location) { +logf :: proc(level: Level, fmt_str: string, #no_capture args: ..any, location := #caller_location) { logger := context.logger if logger.procedure == nil { return diff --git a/core/math/big/helpers.odin b/core/math/big/helpers.odin index ee09bb2c7..c82b5eead 100644 --- a/core/math/big/helpers.odin +++ b/core/math/big/helpers.odin @@ -404,7 +404,7 @@ clear_if_uninitialized_single :: proc(arg: ^Int, allocator := context.allocator) return #force_inline internal_clear_if_uninitialized_single(arg, allocator) } -clear_if_uninitialized_multi :: proc(args: ..^Int, allocator := context.allocator) -> (err: Error) { +clear_if_uninitialized_multi :: proc(#no_capture args: ..^Int, allocator := context.allocator) -> (err: Error) { args := args assert_if_nil(..args) @@ -420,7 +420,7 @@ error_if_immutable_single :: proc(arg: ^Int) -> (err: Error) { return nil } -error_if_immutable_multi :: proc(args: ..^Int) -> (err: Error) { +error_if_immutable_multi :: proc(#no_capture args: ..^Int) -> (err: Error) { for i in args { if i != nil && .Immutable in i.flags { return .Assignment_To_Immutable } } @@ -431,7 +431,7 @@ error_if_immutable :: proc {error_if_immutable_single, error_if_immutable_multi, /* Allocates several `Int`s at once. */ -int_init_multi :: proc(integers: ..^Int, allocator := context.allocator) -> (err: Error) { +int_init_multi :: proc(#no_capture integers: ..^Int, allocator := context.allocator) -> (err: Error) { assert_if_nil(..integers) integers := integers @@ -812,13 +812,13 @@ assert_if_nil :: proc{ assert_if_nil_rat, } -assert_if_nil_int :: #force_inline proc(integers: ..^Int, loc := #caller_location) { +assert_if_nil_int :: #force_inline proc(#no_capture integers: ..^Int, loc := #caller_location) { for i in integers { assert(i != nil, "(nil)", loc) } } -assert_if_nil_rat :: #force_inline proc(rationals: ..^Rat, loc := #caller_location) { +assert_if_nil_rat :: #force_inline proc(#no_capture rationals: ..^Rat, loc := #caller_location) { for r in rationals { assert(r != nil, "(nil)", loc) } diff --git a/core/math/big/internal.odin b/core/math/big/internal.odin index c9b331e55..9eaa0c8e1 100644 --- a/core/math/big/internal.odin +++ b/core/math/big/internal.odin @@ -1844,7 +1844,7 @@ internal_root_n :: proc { internal_int_root_n, } Deallocates the backing memory of one or more `Int`s. Asssumes none of the `integers` to be a `nil`. */ -internal_int_destroy :: proc(integers: ..^Int) { +internal_int_destroy :: proc(#no_capture integers: ..^Int) { integers := integers for &a in integers { @@ -2872,7 +2872,7 @@ internal_clear_if_uninitialized_single :: proc(arg: ^Int, allocator := context.a return err } -internal_clear_if_uninitialized_multi :: proc(args: ..^Int, allocator := context.allocator) -> (err: Error) { +internal_clear_if_uninitialized_multi :: proc(#no_capture args: ..^Int, allocator := context.allocator) -> (err: Error) { context.allocator = allocator for i in args { @@ -2890,7 +2890,7 @@ internal_error_if_immutable_single :: proc(arg: ^Int) -> (err: Error) { return nil } -internal_error_if_immutable_multi :: proc(args: ..^Int) -> (err: Error) { +internal_error_if_immutable_multi :: proc(#no_capture args: ..^Int) -> (err: Error) { for i in args { if i != nil && .Immutable in i.flags { return .Assignment_To_Immutable } } @@ -2901,7 +2901,7 @@ internal_error_if_immutable :: proc {internal_error_if_immutable_single, interna /* Allocates several `Int`s at once. */ -internal_int_init_multi :: proc(integers: ..^Int, allocator := context.allocator) -> (err: Error) { +internal_int_init_multi :: proc(#no_capture integers: ..^Int, allocator := context.allocator) -> (err: Error) { context.allocator = allocator integers := integers diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index dec892f84..3ae3b5dba 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -5,8 +5,8 @@ import "core:odin/tokenizer" import "core:fmt" -Warning_Handler :: #type proc(pos: tokenizer.Pos, fmt: string, args: ..any) -Error_Handler :: #type proc(pos: tokenizer.Pos, fmt: string, args: ..any) +Warning_Handler :: #type proc(pos: tokenizer.Pos, fmt: string, #no_capture args: ..any) +Error_Handler :: #type proc(pos: tokenizer.Pos, fmt: string, #no_capture args: ..any) Flag :: enum u32 { Optional_Semicolons, @@ -67,25 +67,25 @@ Import_Decl_Kind :: enum { -default_warning_handler :: proc(pos: tokenizer.Pos, msg: string, args: ..any) { +default_warning_handler :: proc(pos: tokenizer.Pos, msg: string, #no_capture args: ..any) { fmt.eprintf("%s(%d:%d): Warning: ", pos.file, pos.line, pos.column) fmt.eprintf(msg, ..args) fmt.eprintf("\n") } -default_error_handler :: proc(pos: tokenizer.Pos, msg: string, args: ..any) { +default_error_handler :: proc(pos: tokenizer.Pos, msg: string, #no_capture args: ..any) { fmt.eprintf("%s(%d:%d): ", pos.file, pos.line, pos.column) fmt.eprintf(msg, ..args) fmt.eprintf("\n") } -warn :: proc(p: ^Parser, pos: tokenizer.Pos, msg: string, args: ..any) { +warn :: proc(p: ^Parser, pos: tokenizer.Pos, msg: string, #no_capture args: ..any) { if p.warn != nil { p.warn(pos, msg, ..args) } p.file.syntax_warning_count += 1 } -error :: proc(p: ^Parser, pos: tokenizer.Pos, msg: string, args: ..any) { +error :: proc(p: ^Parser, pos: tokenizer.Pos, msg: string, #no_capture args: ..any) { if p.err != nil { p.err(pos, msg, ..args) } diff --git a/core/odin/tokenizer/tokenizer.odin b/core/odin/tokenizer/tokenizer.odin index 62170aa10..c5992e5f4 100644 --- a/core/odin/tokenizer/tokenizer.odin +++ b/core/odin/tokenizer/tokenizer.odin @@ -4,7 +4,7 @@ import "core:fmt" import "core:unicode" import "core:unicode/utf8" -Error_Handler :: #type proc(pos: Pos, fmt: string, args: ..any) +Error_Handler :: #type proc(pos: Pos, fmt: string, #no_capture args: ..any) Flag :: enum { Insert_Semicolon, @@ -62,13 +62,13 @@ offset_to_pos :: proc(t: ^Tokenizer, offset: int) -> Pos { } } -default_error_handler :: proc(pos: Pos, msg: string, args: ..any) { +default_error_handler :: proc(pos: Pos, msg: string, #no_capture args: ..any) { fmt.eprintf("%s(%d:%d) ", pos.file, pos.line, pos.column) fmt.eprintf(msg, ..args) fmt.eprintf("\n") } -error :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) { +error :: proc(t: ^Tokenizer, offset: int, msg: string, #no_capture args: ..any) { pos := offset_to_pos(t, offset) if t.err != nil { t.err(pos, msg, ..args) diff --git a/core/testing/testing.odin b/core/testing/testing.odin index 29fe853ef..04dc79095 100644 --- a/core/testing/testing.odin +++ b/core/testing/testing.odin @@ -53,12 +53,12 @@ T :: struct { @(deprecated="prefer `log.error`") -error :: proc(t: ^T, args: ..any, loc := #caller_location) { +error :: proc(t: ^T, #no_capture args: ..any, loc := #caller_location) { pkg_log.error(..args, location = loc) } @(deprecated="prefer `log.errorf`") -errorf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) { +errorf :: proc(t: ^T, format: string, #no_capture args: ..any, loc := #caller_location) { pkg_log.errorf(format, ..args, location = loc) } @@ -87,12 +87,12 @@ failed :: proc(t: ^T) -> bool { } @(deprecated="prefer `log.info`") -log :: proc(t: ^T, args: ..any, loc := #caller_location) { +log :: proc(t: ^T, #no_capture args: ..any, loc := #caller_location) { pkg_log.info(..args, location = loc) } @(deprecated="prefer `log.infof`") -logf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) { +logf :: proc(t: ^T, format: string, #no_capture args: ..any, loc := #caller_location) { pkg_log.infof(format, ..args, location = loc) } @@ -121,7 +121,7 @@ expect :: proc(t: ^T, ok: bool, msg: string = "", loc := #caller_location) -> bo return ok } -expectf :: proc(t: ^T, ok: bool, format: string, args: ..any, loc := #caller_location) -> bool { +expectf :: proc(t: ^T, ok: bool, format: string, #no_capture args: ..any, loc := #caller_location) -> bool { if !ok { pkg_log.errorf(format, ..args, location=loc) } diff --git a/core/text/scanner/scanner.odin b/core/text/scanner/scanner.odin index d27c66f24..6eb366b49 100644 --- a/core/text/scanner/scanner.odin +++ b/core/text/scanner/scanner.odin @@ -250,7 +250,7 @@ error :: proc(s: ^Scanner, msg: string) { } } -errorf :: proc(s: ^Scanner, format: string, args: ..any) { +errorf :: proc(s: ^Scanner, format: string, #no_capture args: ..any) { error(s, fmt.tprintf(format, ..args)) } diff --git a/core/text/table/table.odin b/core/text/table/table.odin index 27c99b1f1..4d0baef64 100644 --- a/core/text/table/table.odin +++ b/core/text/table/table.odin @@ -145,7 +145,7 @@ set_cell_value_and_alignment :: proc(tbl: ^Table, row, col: int, value: any, ali cell.alignment = alignment } -format :: proc(tbl: ^Table, _fmt: string, args: ..any) -> string { +format :: proc(tbl: ^Table, _fmt: string, #no_capture args: ..any) -> string { context.allocator = tbl.format_allocator return fmt.aprintf(_fmt, ..args) } diff --git a/core/unicode/tools/generate_entity_table.odin b/core/unicode/tools/generate_entity_table.odin index 16baa1adf..238a3e219 100644 --- a/core/unicode/tools/generate_entity_table.odin +++ b/core/unicode/tools/generate_entity_table.odin @@ -12,7 +12,7 @@ import "core:fmt" /* Silent error handler for the parser. */ -Error_Handler :: proc(pos: xml.Pos, fmt: string, args: ..any) {} +Error_Handler :: proc(pos: xml.Pos, fmt: string, #no_capture args: ..any) {} OPTIONS :: xml.Options{ flags = { .Ignore_Unsupported, }, expected_doctype = "unicode", } diff --git a/tests/core/encoding/xml/test_core_xml.odin b/tests/core/encoding/xml/test_core_xml.odin index b29431e10..811ee27dc 100644 --- a/tests/core/encoding/xml/test_core_xml.odin +++ b/tests/core/encoding/xml/test_core_xml.odin @@ -8,7 +8,7 @@ import "core:fmt" import "core:log" import "core:hash" -Silent :: proc(pos: xml.Pos, format: string, args: ..any) {} +Silent :: proc(pos: xml.Pos, format: string, #no_capture args: ..any) {} OPTIONS :: xml.Options{ flags = { .Ignore_Unsupported, .Intern_Comments, }, expected_doctype = "", diff --git a/tests/core/fmt/test_core_fmt.odin b/tests/core/fmt/test_core_fmt.odin index 49142e24d..0aaef93a4 100644 --- a/tests/core/fmt/test_core_fmt.odin +++ b/tests/core/fmt/test_core_fmt.odin @@ -373,7 +373,7 @@ test_odin_value_export :: proc(t: ^testing.T) { } @(private) -check :: proc(t: ^testing.T, exp: string, format: string, args: ..any, loc := #caller_location) { +check :: proc(t: ^testing.T, exp: string, format: string, #no_capture args: ..any, loc := #caller_location) { got := fmt.tprintf(format, ..args) testing.expectf(t, got == exp, "(%q, %v): %q != %q", format, args, got, exp, loc = loc) } diff --git a/tests/documentation/documentation_tester.odin b/tests/documentation/documentation_tester.odin index 8a798d6c5..7fc37b9f0 100644 --- a/tests/documentation/documentation_tester.odin +++ b/tests/documentation/documentation_tester.odin @@ -51,7 +51,7 @@ common_prefix :: proc(strs: []string) -> string { return prefix } -errorf :: proc(format: string, args: ..any) -> ! { +errorf :: proc(format: string, #no_capture args: ..any) -> ! { fmt.eprintf("%s ", os.args[0]) fmt.eprintf(format, ..args) fmt.eprintln() diff --git a/vendor/OpenGL/wrappers.odin b/vendor/OpenGL/wrappers.odin index 1eb8fc72f..971452be5 100644 --- a/vendor/OpenGL/wrappers.odin +++ b/vendor/OpenGL/wrappers.odin @@ -754,7 +754,7 @@ when !GL_DEBUG { MultiDrawElementsIndirectCount :: proc "c" (mode: i32, type: i32, indirect: [^]DrawElementsIndirectCommand, drawcount: i32, maxdrawcount, stride: i32) { impl_MultiDrawElementsIndirectCount(mode, type, indirect, drawcount, maxdrawcount, stride) } PolygonOffsetClamp :: proc "c" (factor, units, clamp: f32) { impl_PolygonOffsetClamp(factor, units, clamp) } } else { - debug_helper :: proc"c"(from_loc: runtime.Source_Code_Location, num_ret: int, args: ..any, loc := #caller_location) { + debug_helper :: proc"c"(from_loc: runtime.Source_Code_Location, num_ret: int, #no_capture args: ..any, loc := #caller_location) { context = runtime.default_context() Error_Enum :: enum { diff --git a/vendor/raylib/raylib.odin b/vendor/raylib/raylib.odin index 3d1b74058..c2995ba36 100644 --- a/vendor/raylib/raylib.odin +++ b/vendor/raylib/raylib.odin @@ -1667,7 +1667,7 @@ IsGestureDetected :: proc "c" (gesture: Gesture) -> bool { // Text formatting with variables (sprintf style) -TextFormat :: proc(text: cstring, args: ..any) -> cstring { +TextFormat :: proc(text: cstring, #no_capture args: ..any) -> cstring { @static buffers: [MAX_TEXTFORMAT_BUFFERS][MAX_TEXT_BUFFER_LENGTH]byte @static index: u32 @@ -1683,7 +1683,7 @@ TextFormat :: proc(text: cstring, args: ..any) -> cstring { } // Text formatting with variables (sprintf style) and allocates (must be freed with 'MemFree') -TextFormatAlloc :: proc(text: cstring, args: ..any) -> cstring { +TextFormatAlloc :: proc(text: cstring, #no_capture args: ..any) -> cstring { str := fmt.tprintf(string(text), ..args) return strings.clone_to_cstring(str, MemAllocator()) } From 3ba19d94cfe9fbef9ac9c26e0bb49c54d568919c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 11:58:26 +0100 Subject: [PATCH 025/198] Add `#no_capture` to `core:odin/ast` --- core/odin/ast/ast.odin | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin index 92d00b47c..0ae822e21 100644 --- a/core/odin/ast/ast.odin +++ b/core/odin/ast/ast.odin @@ -599,6 +599,7 @@ Field_Flag :: enum { Subtype, By_Ptr, No_Broadcast, + No_Capture, Results, Tags, @@ -619,6 +620,7 @@ field_flag_strings := [Field_Flag]string{ .Subtype = "#subtype", .By_Ptr = "#by_ptr", .No_Broadcast = "#no_broadcast", + .No_Capture = "#no_capture", .Results = "results", .Tags = "field tag", @@ -634,6 +636,7 @@ field_hash_flag_strings := []struct{key: string, flag: Field_Flag}{ {"subtype", .Subtype}, {"by_ptr", .By_Ptr}, {"no_broadcast", .No_Broadcast}, + {"no_capture", .No_Capture}, } From 891cf54b5c56bd31bcfdac14f0b72d489999bffc Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 12:03:34 +0100 Subject: [PATCH 026/198] Add `f16` to `#c_vararg` promotion rules --- src/types.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/types.cpp b/src/types.cpp index c3a5fb539..92b187cdb 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -2923,11 +2923,14 @@ gb_internal Type *c_vararg_promote_type(Type *type) { if (core->kind == Type_Basic) { switch (core->Basic.kind) { + case Basic_f16: case Basic_f32: case Basic_UntypedFloat: return t_f64; + case Basic_f16le: case Basic_f32le: return t_f64le; + case Basic_f16be: case Basic_f32be: return t_f64be; From 8642d719f0ece3625d535d47b41ff4d35072f47f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 12:19:47 +0100 Subject: [PATCH 027/198] Imply `#no_capture` to all variadic parameters --- base/runtime/core_builtin.odin | 8 +-- base/runtime/core_builtin_soa.odin | 4 +- base/runtime/print.odin | 4 +- core/container/small_array/small_array.odin | 2 +- core/encoding/xml/tokenizer.odin | 6 +- core/fmt/fmt.odin | 58 +++++++++---------- core/fmt/fmt_js.odin | 24 ++++---- core/fmt/fmt_os.odin | 24 ++++---- core/log/log.odin | 32 +++++----- core/math/big/helpers.odin | 10 ++-- core/math/big/internal.odin | 8 +-- core/odin/parser/parser.odin | 12 ++-- core/odin/tokenizer/tokenizer.odin | 6 +- core/testing/testing.odin | 10 ++-- core/text/scanner/scanner.odin | 2 +- core/text/table/table.odin | 2 +- core/unicode/tools/generate_entity_table.odin | 2 +- src/check_type.cpp | 10 +++- src/llvm_backend.hpp | 1 - src/parser.hpp | 5 +- tests/core/encoding/xml/test_core_xml.odin | 2 +- tests/core/fmt/test_core_fmt.odin | 2 +- tests/documentation/documentation_tester.odin | 2 +- vendor/OpenGL/wrappers.odin | 2 +- vendor/raylib/raylib.odin | 4 +- 25 files changed, 125 insertions(+), 117 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 5f6e1ec16..d68eefe23 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -524,12 +524,12 @@ _append_elems :: #force_inline proc(array: ^$T/[dynamic]$E, should_zero: bool, l } @builtin -append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast #no_capture args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { +append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { return _append_elems(array, true, loc, args) } @builtin -non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast #no_capture args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { +non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { return _append_elems(array, false, loc, args) } @@ -617,7 +617,7 @@ inject_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, #no_broadcast arg: E, } @builtin -inject_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, #no_broadcast #no_capture args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +inject_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { if array == nil { return } @@ -679,7 +679,7 @@ assign_at_elem :: proc(array: ^$T/[dynamic]$E, index: int, arg: E, loc := #calle @builtin -assign_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, #no_broadcast #no_capture args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +assign_at_elems :: proc(array: ^$T/[dynamic]$E, index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { new_size := index + len(args) if len(args) == 0 { ok = true diff --git a/base/runtime/core_builtin_soa.odin b/base/runtime/core_builtin_soa.odin index 108183ea6..7f7f5f086 100644 --- a/base/runtime/core_builtin_soa.odin +++ b/base/runtime/core_builtin_soa.odin @@ -342,12 +342,12 @@ _append_soa_elem :: proc(array: ^$T/#soa[dynamic]$E, zero_memory: bool, #no_broa } @builtin -append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast #no_capture args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { +append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { return _append_soa_elems(array, true, args=args, loc=loc) } @builtin -non_zero_append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast #no_capture args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { +non_zero_append_soa_elems :: proc(array: ^$T/#soa[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { return _append_soa_elems(array, false, args=args, loc=loc) } diff --git a/base/runtime/print.odin b/base/runtime/print.odin index ecd301d21..0262e8ef6 100644 --- a/base/runtime/print.odin +++ b/base/runtime/print.odin @@ -72,7 +72,7 @@ when !ODIN_NO_RTTI { print_string("") } } - println_any :: #force_no_inline proc "contextless" (#no_capture args: ..any) { + println_any :: #force_no_inline proc "contextless" (args: ..any) { context = default_context() loop: for arg, i in args { assert(arg.id != nil) @@ -127,7 +127,7 @@ print_string :: #force_no_inline proc "contextless" (str: string) -> (n: int) { return } -print_strings :: #force_no_inline proc "contextless" (#no_capture args: ..string) -> (n: int) { +print_strings :: #force_no_inline proc "contextless" (args: ..string) -> (n: int) { for str in args { m, err := stderr_write(transmute([]byte)str) n += m diff --git a/core/container/small_array/small_array.odin b/core/container/small_array/small_array.odin index a698d965c..b2068469d 100644 --- a/core/container/small_array/small_array.odin +++ b/core/container/small_array/small_array.odin @@ -139,7 +139,7 @@ clear :: proc "contextless" (a: ^$A/Small_Array($N, $T)) { resize(a, 0) } -push_back_elems :: proc "contextless" (a: ^$A/Small_Array($N, $T), #no_capture items: ..T) { +push_back_elems :: proc "contextless" (a: ^$A/Small_Array($N, $T), items: ..T) { n := copy(a.data[a.len:], items[:]) a.len += n } diff --git a/core/encoding/xml/tokenizer.odin b/core/encoding/xml/tokenizer.odin index 8a26f1bce..a2bbaf28e 100644 --- a/core/encoding/xml/tokenizer.odin +++ b/core/encoding/xml/tokenizer.odin @@ -17,7 +17,7 @@ import "core:fmt" import "core:unicode" import "core:unicode/utf8" -Error_Handler :: #type proc(pos: Pos, fmt: string, #no_capture args: ..any) +Error_Handler :: #type proc(pos: Pos, fmt: string, args: ..any) Token :: struct { kind: Token_Kind, @@ -112,13 +112,13 @@ offset_to_pos :: proc(t: ^Tokenizer, offset: int) -> Pos { } } -default_error_handler :: proc(pos: Pos, msg: string, #no_capture args: ..any) { +default_error_handler :: proc(pos: Pos, msg: string, args: ..any) { fmt.eprintf("%s(%d:%d) ", pos.file, pos.line, pos.column) fmt.eprintf(msg, ..args) fmt.eprintf("\n") } -error :: proc(t: ^Tokenizer, offset: int, msg: string, #no_capture args: ..any) { +error :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) { pos := offset_to_pos(t, offset) if t.err != nil { t.err(pos, msg, ..args) diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index e56211346..234f4afbd 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -125,7 +125,7 @@ register_user_formatter :: proc(id: typeid, formatter: User_Formatter) -> Regist // Returns: A formatted string. // @(require_results) -aprint :: proc(#no_capture args: ..any, sep := " ", allocator := context.allocator) -> string { +aprint :: proc(args: ..any, sep := " ", allocator := context.allocator) -> string { str: strings.Builder strings.builder_init(&str, allocator) return sbprint(&str, ..args, sep=sep) @@ -141,7 +141,7 @@ aprint :: proc(#no_capture args: ..any, sep := " ", allocator := context.allocat // Returns: A formatted string with a newline character at the end. // @(require_results) -aprintln :: proc(#no_capture args: ..any, sep := " ", allocator := context.allocator) -> string { +aprintln :: proc(args: ..any, sep := " ", allocator := context.allocator) -> string { str: strings.Builder strings.builder_init(&str, allocator) return sbprintln(&str, ..args, sep=sep) @@ -158,7 +158,7 @@ aprintln :: proc(#no_capture args: ..any, sep := " ", allocator := context.alloc // Returns: A formatted string. The returned string must be freed accordingly. // @(require_results) -aprintf :: proc(fmt: string, #no_capture args: ..any, allocator := context.allocator, newline := false) -> string { +aprintf :: proc(fmt: string, args: ..any, allocator := context.allocator, newline := false) -> string { str: strings.Builder strings.builder_init(&str, allocator) return sbprintf(&str, fmt, ..args, newline=newline) @@ -174,7 +174,7 @@ aprintf :: proc(fmt: string, #no_capture args: ..any, allocator := context.alloc // Returns: A formatted string. The returned string must be freed accordingly. // @(require_results) -aprintfln :: proc(fmt: string, #no_capture args: ..any, allocator := context.allocator) -> string { +aprintfln :: proc(fmt: string, args: ..any, allocator := context.allocator) -> string { return aprintf(fmt, ..args, allocator=allocator, newline=true) } // Creates a formatted string @@ -188,7 +188,7 @@ aprintfln :: proc(fmt: string, #no_capture args: ..any, allocator := context.all // Returns: A formatted string. // @(require_results) -tprint :: proc(#no_capture args: ..any, sep := " ") -> string { +tprint :: proc(args: ..any, sep := " ") -> string { str: strings.Builder strings.builder_init(&str, context.temp_allocator) return sbprint(&str, ..args, sep=sep) @@ -204,7 +204,7 @@ tprint :: proc(#no_capture args: ..any, sep := " ") -> string { // Returns: A formatted string with a newline character at the end. // @(require_results) -tprintln :: proc(#no_capture args: ..any, sep := " ") -> string { +tprintln :: proc(args: ..any, sep := " ") -> string { str: strings.Builder strings.builder_init(&str, context.temp_allocator) return sbprintln(&str, ..args, sep=sep) @@ -221,7 +221,7 @@ tprintln :: proc(#no_capture args: ..any, sep := " ") -> string { // Returns: A formatted string. // @(require_results) -tprintf :: proc(fmt: string, #no_capture args: ..any, newline := false) -> string { +tprintf :: proc(fmt: string, args: ..any, newline := false) -> string { str: strings.Builder strings.builder_init(&str, context.temp_allocator) return sbprintf(&str, fmt, ..args, newline=newline) @@ -237,7 +237,7 @@ tprintf :: proc(fmt: string, #no_capture args: ..any, newline := false) -> strin // Returns: A formatted string. // @(require_results) -tprintfln :: proc(fmt: string, #no_capture args: ..any) -> string { +tprintfln :: proc(fmt: string, args: ..any) -> string { return tprintf(fmt, ..args, newline=true) } // Creates a formatted string using a supplied buffer as the backing array. Writes into the buffer. @@ -249,7 +249,7 @@ tprintfln :: proc(fmt: string, #no_capture args: ..any) -> string { // // Returns: A formatted string // -bprint :: proc(buf: []byte, #no_capture args: ..any, sep := " ") -> string { +bprint :: proc(buf: []byte, args: ..any, sep := " ") -> string { sb := strings.builder_from_bytes(buf) return sbprint(&sb, ..args, sep=sep) } @@ -262,7 +262,7 @@ bprint :: proc(buf: []byte, #no_capture args: ..any, sep := " ") -> string { // // Returns: A formatted string with a newline character at the end // -bprintln :: proc(buf: []byte, #no_capture args: ..any, sep := " ") -> string { +bprintln :: proc(buf: []byte, args: ..any, sep := " ") -> string { sb := strings.builder_from_bytes(buf) return sbprintln(&sb, ..args, sep=sep) } @@ -276,7 +276,7 @@ bprintln :: proc(buf: []byte, #no_capture args: ..any, sep := " ") -> string { // // Returns: A formatted string // -bprintf :: proc(buf: []byte, fmt: string, #no_capture args: ..any, newline := false) -> string { +bprintf :: proc(buf: []byte, fmt: string, args: ..any, newline := false) -> string { sb := strings.builder_from_bytes(buf) return sbprintf(&sb, fmt, ..args, newline=newline) } @@ -289,7 +289,7 @@ bprintf :: proc(buf: []byte, fmt: string, #no_capture args: ..any, newline := fa // // Returns: A formatted string // -bprintfln :: proc(buf: []byte, fmt: string, #no_capture args: ..any) -> string { +bprintfln :: proc(buf: []byte, fmt: string, args: ..any) -> string { return bprintf(buf, fmt, ..args, newline=true) } // Runtime assertion with a formatted message @@ -301,14 +301,14 @@ bprintfln :: proc(buf: []byte, fmt: string, #no_capture args: ..any) -> string { // - loc: The location of the caller // @(disabled=ODIN_DISABLE_ASSERT) -assertf :: proc(condition: bool, fmt: string, #no_capture args: ..any, loc := #caller_location) { +assertf :: proc(condition: bool, fmt: string, args: ..any, loc := #caller_location) { if !condition { // NOTE(dragos): We are using the same trick as in builtin.assert // to improve performance to make the CPU not // execute speculatively, making it about an order of // magnitude faster @(cold) - internal :: proc(loc: runtime.Source_Code_Location, fmt: string, #no_capture args: ..any) { + internal :: proc(loc: runtime.Source_Code_Location, fmt: string, args: ..any) { p := context.assertion_failure_proc if p == nil { p = runtime.default_assertion_failure_proc @@ -326,7 +326,7 @@ assertf :: proc(condition: bool, fmt: string, #no_capture args: ..any, loc := #c // - args: A variadic list of arguments to be formatted // - loc: The location of the caller // -panicf :: proc(fmt: string, #no_capture args: ..any, loc := #caller_location) -> ! { +panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! { p := context.assertion_failure_proc if p == nil { p = runtime.default_assertion_failure_proc @@ -346,7 +346,7 @@ panicf :: proc(fmt: string, #no_capture args: ..any, loc := #caller_location) -> // Returns: A formatted C string // @(require_results) -caprintf :: proc(format: string, #no_capture args: ..any, newline := false) -> cstring { +caprintf :: proc(format: string, args: ..any, newline := false) -> cstring { str: strings.Builder strings.builder_init(&str) sbprintf(&str, format, ..args, newline=newline) @@ -365,7 +365,7 @@ caprintf :: proc(format: string, #no_capture args: ..any, newline := false) -> c // Returns: A formatted C string // @(require_results) -caprintfln :: proc(format: string, #no_capture args: ..any) -> cstring { +caprintfln :: proc(format: string, args: ..any) -> cstring { return caprintf(format, ..args, newline=true) } // Creates a formatted C string @@ -379,7 +379,7 @@ caprintfln :: proc(format: string, #no_capture args: ..any) -> cstring { // Returns: A formatted C string. // @(require_results) -ctprint :: proc(#no_capture args: ..any, sep := " ") -> cstring { +ctprint :: proc(args: ..any, sep := " ") -> cstring { str: strings.Builder strings.builder_init(&str, context.temp_allocator) sbprint(&str, ..args, sep=sep) @@ -399,7 +399,7 @@ ctprint :: proc(#no_capture args: ..any, sep := " ") -> cstring { // Returns: A formatted C string // @(require_results) -ctprintf :: proc(format: string, #no_capture args: ..any, newline := false) -> cstring { +ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring { str: strings.Builder strings.builder_init(&str, context.temp_allocator) sbprintf(&str, format, ..args, newline=newline) @@ -418,7 +418,7 @@ ctprintf :: proc(format: string, #no_capture args: ..any, newline := false) -> c // Returns: A formatted C string // @(require_results) -ctprintfln :: proc(format: string, #no_capture args: ..any) -> cstring { +ctprintfln :: proc(format: string, args: ..any) -> cstring { return ctprintf(format, ..args, newline=true) } // Formats using the default print settings and writes to the given strings.Builder @@ -430,7 +430,7 @@ ctprintfln :: proc(format: string, #no_capture args: ..any) -> cstring { // // Returns: A formatted string // -sbprint :: proc(buf: ^strings.Builder, #no_capture args: ..any, sep := " ") -> string { +sbprint :: proc(buf: ^strings.Builder, args: ..any, sep := " ") -> string { wprint(strings.to_writer(buf), ..args, sep=sep, flush=true) return strings.to_string(buf^) } @@ -443,7 +443,7 @@ sbprint :: proc(buf: ^strings.Builder, #no_capture args: ..any, sep := " ") -> s // // Returns: The resulting formatted string // -sbprintln :: proc(buf: ^strings.Builder, #no_capture args: ..any, sep := " ") -> string { +sbprintln :: proc(buf: ^strings.Builder, args: ..any, sep := " ") -> string { wprintln(strings.to_writer(buf), ..args, sep=sep, flush=true) return strings.to_string(buf^) } @@ -457,7 +457,7 @@ sbprintln :: proc(buf: ^strings.Builder, #no_capture args: ..any, sep := " ") -> // // Returns: The resulting formatted string // -sbprintf :: proc(buf: ^strings.Builder, fmt: string, #no_capture args: ..any, newline := false) -> string { +sbprintf :: proc(buf: ^strings.Builder, fmt: string, args: ..any, newline := false) -> string { wprintf(strings.to_writer(buf), fmt, ..args, flush=true, newline=newline) return strings.to_string(buf^) } @@ -469,7 +469,7 @@ sbprintf :: proc(buf: ^strings.Builder, fmt: string, #no_capture args: ..any, ne // // Returns: A formatted string // -sbprintfln :: proc(buf: ^strings.Builder, format: string, #no_capture args: ..any) -> string { +sbprintfln :: proc(buf: ^strings.Builder, format: string, args: ..any) -> string { return sbprintf(buf, format, ..args, newline=true) } // Formats and writes to an io.Writer using the default print settings @@ -481,7 +481,7 @@ sbprintfln :: proc(buf: ^strings.Builder, format: string, #no_capture args: ..an // // Returns: The number of bytes written // -wprint :: proc(w: io.Writer, #no_capture args: ..any, sep := " ", flush := true) -> int { +wprint :: proc(w: io.Writer, args: ..any, sep := " ", flush := true) -> int { fi: Info fi.writer = w @@ -522,7 +522,7 @@ wprint :: proc(w: io.Writer, #no_capture args: ..any, sep := " ", flush := true) // // Returns: The number of bytes written // -wprintln :: proc(w: io.Writer, #no_capture args: ..any, sep := " ", flush := true) -> int { +wprintln :: proc(w: io.Writer, args: ..any, sep := " ", flush := true) -> int { fi: Info fi.writer = w @@ -549,11 +549,11 @@ wprintln :: proc(w: io.Writer, #no_capture args: ..any, sep := " ", flush := tru // // Returns: The number of bytes written // -wprintf :: proc(w: io.Writer, fmt: string, #no_capture args: ..any, flush := true, newline := false) -> int { +wprintf :: proc(w: io.Writer, fmt: string, args: ..any, flush := true, newline := false) -> int { MAX_CHECKED_ARGS :: 64 assert(len(args) <= MAX_CHECKED_ARGS, "number of args > 64 is unsupported") - parse_options :: proc(fi: ^Info, fmt: string, index, end: int, unused_args: ^bit_set[0 ..< MAX_CHECKED_ARGS], #no_capture args: ..any) -> int { + parse_options :: proc(fi: ^Info, fmt: string, index, end: int, unused_args: ^bit_set[0 ..< MAX_CHECKED_ARGS], args: ..any) -> int { i := index // Prefix @@ -809,7 +809,7 @@ wprintf :: proc(w: io.Writer, fmt: string, #no_capture args: ..any, flush := tru // // Returns: The number of bytes written. // -wprintfln :: proc(w: io.Writer, format: string, #no_capture args: ..any, flush := true) -> int { +wprintfln :: proc(w: io.Writer, format: string, args: ..any, flush := true) -> int { return wprintf(w, format, ..args, flush=flush, newline=true) } // Writes a ^runtime.Type_Info value to an io.Writer diff --git a/core/fmt/fmt_js.odin b/core/fmt/fmt_js.odin index 4389b8d87..acf218eb5 100644 --- a/core/fmt/fmt_js.odin +++ b/core/fmt/fmt_js.odin @@ -43,7 +43,7 @@ fd_to_writer :: proc(fd: os.Handle, loc := #caller_location) -> io.Writer { } // fprint formats using the default print settings and writes to fd -fprint :: proc(fd: os.Handle, #no_capture args: ..any, sep := " ", flush := true, loc := #caller_location) -> int { +fprint :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true, loc := #caller_location) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) @@ -54,7 +54,7 @@ fprint :: proc(fd: os.Handle, #no_capture args: ..any, sep := " ", flush := true } // fprintln formats using the default print settings and writes to fd -fprintln :: proc(fd: os.Handle, #no_capture args: ..any, sep := " ", flush := true, loc := #caller_location) -> int { +fprintln :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true, loc := #caller_location) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) @@ -66,7 +66,7 @@ fprintln :: proc(fd: os.Handle, #no_capture args: ..any, sep := " ", flush := tr } // fprintf formats according to the specified format string and writes to fd -fprintf :: proc(fd: os.Handle, fmt: string, #no_capture args: ..any, flush := true, newline := false, loc := #caller_location) -> int { +fprintf :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true, newline := false, loc := #caller_location) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) @@ -78,24 +78,24 @@ fprintf :: proc(fd: os.Handle, fmt: string, #no_capture args: ..any, flush := tr } // fprintfln formats according to the specified format string and writes to fd, followed by a newline. -fprintfln :: proc(fd: os.Handle, fmt: string, #no_capture args: ..any, flush := true, loc := #caller_location) -> int { +fprintfln :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true, loc := #caller_location) -> int { return fprintf(fd, fmt, ..args, flush=flush, newline=true, loc=loc) } // print formats using the default print settings and writes to stdout -print :: proc(#no_capture args: ..any, sep := " ", flush := true) -> int { return wprint(w=stdout, args=args, sep=sep, flush=flush) } +print :: proc(args: ..any, sep := " ", flush := true) -> int { return wprint(w=stdout, args=args, sep=sep, flush=flush) } // println formats using the default print settings and writes to stdout -println :: proc(#no_capture args: ..any, sep := " ", flush := true) -> int { return wprintln(w=stdout, args=args, sep=sep, flush=flush) } +println :: proc(args: ..any, sep := " ", flush := true) -> int { return wprintln(w=stdout, args=args, sep=sep, flush=flush) } // printf formats according to the specififed format string and writes to stdout -printf :: proc(fmt: string, #no_capture args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush) } +printf :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush) } // printfln formats according to the specified format string and writes to stdout, followed by a newline. -printfln :: proc(fmt: string, #no_capture args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush, newline=true) } +printfln :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush, newline=true) } // eprint formats using the default print settings and writes to stderr -eprint :: proc(#no_capture args: ..any, sep := " ", flush := true) -> int { return wprint(w=stderr, args=args, sep=sep, flush=flush) } +eprint :: proc(args: ..any, sep := " ", flush := true) -> int { return wprint(w=stderr, args=args, sep=sep, flush=flush) } // eprintln formats using the default print settings and writes to stderr -eprintln :: proc(#no_capture args: ..any, sep := " ", flush := true) -> int { return wprintln(w=stderr, args=args, sep=sep, flush=flush) } +eprintln :: proc(args: ..any, sep := " ", flush := true) -> int { return wprintln(w=stderr, args=args, sep=sep, flush=flush) } // eprintf formats according to the specififed format string and writes to stderr -eprintf :: proc(fmt: string, #no_capture args: ..any, flush := true) -> int { return wprintf(stderr, fmt, ..args, flush=flush) } +eprintf :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stderr, fmt, ..args, flush=flush) } // eprintfln formats according to the specified format string and writes to stderr, followed by a newline. -eprintfln :: proc(fmt: string, #no_capture args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush, newline=true) } +eprintfln :: proc(fmt: string, args: ..any, flush := true) -> int { return wprintf(stdout, fmt, ..args, flush=flush, newline=true) } diff --git a/core/fmt/fmt_os.odin b/core/fmt/fmt_os.odin index 538f7a08b..9de0d43be 100644 --- a/core/fmt/fmt_os.odin +++ b/core/fmt/fmt_os.odin @@ -9,7 +9,7 @@ import "core:io" import "core:bufio" // fprint formats using the default print settings and writes to fd -fprint :: proc(fd: os.Handle, #no_capture args: ..any, sep := " ", flush := true) -> int { +fprint :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) @@ -20,7 +20,7 @@ fprint :: proc(fd: os.Handle, #no_capture args: ..any, sep := " ", flush := true } // fprintln formats using the default print settings and writes to fd -fprintln :: proc(fd: os.Handle, #no_capture args: ..any, sep := " ", flush := true) -> int { +fprintln :: proc(fd: os.Handle, args: ..any, sep := " ", flush := true) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) @@ -31,7 +31,7 @@ fprintln :: proc(fd: os.Handle, #no_capture args: ..any, sep := " ", flush := tr return wprintln(w, ..args, sep=sep, flush=flush) } // fprintf formats according to the specified format string and writes to fd -fprintf :: proc(fd: os.Handle, fmt: string, #no_capture args: ..any, flush := true, newline := false) -> int { +fprintf :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true, newline := false) -> int { buf: [1024]byte b: bufio.Writer defer bufio.writer_flush(&b) @@ -42,7 +42,7 @@ fprintf :: proc(fd: os.Handle, fmt: string, #no_capture args: ..any, flush := tr return wprintf(w, fmt, ..args, flush=flush, newline=newline) } // fprintfln formats according to the specified format string and writes to fd, followed by a newline. -fprintfln :: proc(fd: os.Handle, fmt: string, #no_capture args: ..any, flush := true) -> int { +fprintfln :: proc(fd: os.Handle, fmt: string, args: ..any, flush := true) -> int { return fprintf(fd, fmt, ..args, flush=flush, newline=true) } fprint_type :: proc(fd: os.Handle, info: ^runtime.Type_Info, flush := true) -> (n: int, err: io.Error) { @@ -67,19 +67,19 @@ fprint_typeid :: proc(fd: os.Handle, id: typeid, flush := true) -> (n: int, err: } // print formats using the default print settings and writes to os.stdout -print :: proc(#no_capture args: ..any, sep := " ", flush := true) -> int { return fprint(os.stdout, ..args, sep=sep, flush=flush) } +print :: proc(args: ..any, sep := " ", flush := true) -> int { return fprint(os.stdout, ..args, sep=sep, flush=flush) } // println formats using the default print settings and writes to os.stdout -println :: proc(#no_capture args: ..any, sep := " ", flush := true) -> int { return fprintln(os.stdout, ..args, sep=sep, flush=flush) } +println :: proc(args: ..any, sep := " ", flush := true) -> int { return fprintln(os.stdout, ..args, sep=sep, flush=flush) } // printf formats according to the specified format string and writes to os.stdout -printf :: proc(fmt: string, #no_capture args: ..any, flush := true) -> int { return fprintf(os.stdout, fmt, ..args, flush=flush) } +printf :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(os.stdout, fmt, ..args, flush=flush) } // printfln formats according to the specified format string and writes to os.stdout, followed by a newline. -printfln :: proc(fmt: string, #no_capture args: ..any, flush := true) -> int { return fprintf(os.stdout, fmt, ..args, flush=flush, newline=true) } +printfln :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(os.stdout, fmt, ..args, flush=flush, newline=true) } // eprint formats using the default print settings and writes to os.stderr -eprint :: proc(#no_capture args: ..any, sep := " ", flush := true) -> int { return fprint(os.stderr, ..args, sep=sep, flush=flush) } +eprint :: proc(args: ..any, sep := " ", flush := true) -> int { return fprint(os.stderr, ..args, sep=sep, flush=flush) } // eprintln formats using the default print settings and writes to os.stderr -eprintln :: proc(#no_capture args: ..any, sep := " ", flush := true) -> int { return fprintln(os.stderr, ..args, sep=sep, flush=flush) } +eprintln :: proc(args: ..any, sep := " ", flush := true) -> int { return fprintln(os.stderr, ..args, sep=sep, flush=flush) } // eprintf formats according to the specified format string and writes to os.stderr -eprintf :: proc(fmt: string, #no_capture args: ..any, flush := true) -> int { return fprintf(os.stderr, fmt, ..args, flush=flush) } +eprintf :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(os.stderr, fmt, ..args, flush=flush) } // eprintfln formats according to the specified format string and writes to os.stderr, followed by a newline. -eprintfln :: proc(fmt: string, #no_capture args: ..any, flush := true) -> int { return fprintf(os.stderr, fmt, ..args, flush=flush, newline=true) } +eprintfln :: proc(fmt: string, args: ..any, flush := true) -> int { return fprintf(os.stderr, fmt, ..args, flush=flush, newline=true) } diff --git a/core/log/log.odin b/core/log/log.odin index 35dff086f..0d89fdb74 100644 --- a/core/log/log.odin +++ b/core/log/log.odin @@ -75,43 +75,43 @@ nil_logger :: proc() -> Logger { return Logger{nil_logger_proc, nil, Level.Debug, nil} } -debugf :: proc(fmt_str: string, #no_capture args: ..any, location := #caller_location) { +debugf :: proc(fmt_str: string, args: ..any, location := #caller_location) { logf(.Debug, fmt_str, ..args, location=location) } -infof :: proc(fmt_str: string, #no_capture args: ..any, location := #caller_location) { +infof :: proc(fmt_str: string, args: ..any, location := #caller_location) { logf(.Info, fmt_str, ..args, location=location) } -warnf :: proc(fmt_str: string, #no_capture args: ..any, location := #caller_location) { +warnf :: proc(fmt_str: string, args: ..any, location := #caller_location) { logf(.Warning, fmt_str, ..args, location=location) } -errorf :: proc(fmt_str: string, #no_capture args: ..any, location := #caller_location) { +errorf :: proc(fmt_str: string, args: ..any, location := #caller_location) { logf(.Error, fmt_str, ..args, location=location) } -fatalf :: proc(fmt_str: string, #no_capture args: ..any, location := #caller_location) { +fatalf :: proc(fmt_str: string, args: ..any, location := #caller_location) { logf(.Fatal, fmt_str, ..args, location=location) } -debug :: proc(#no_capture args: ..any, sep := " ", location := #caller_location) { +debug :: proc(args: ..any, sep := " ", location := #caller_location) { log(.Debug, ..args, sep=sep, location=location) } -info :: proc(#no_capture args: ..any, sep := " ", location := #caller_location) { +info :: proc(args: ..any, sep := " ", location := #caller_location) { log(.Info, ..args, sep=sep, location=location) } -warn :: proc(#no_capture args: ..any, sep := " ", location := #caller_location) { +warn :: proc(args: ..any, sep := " ", location := #caller_location) { log(.Warning, ..args, sep=sep, location=location) } -error :: proc(#no_capture args: ..any, sep := " ", location := #caller_location) { +error :: proc(args: ..any, sep := " ", location := #caller_location) { log(.Error, ..args, sep=sep, location=location) } -fatal :: proc(#no_capture args: ..any, sep := " ", location := #caller_location) { +fatal :: proc(args: ..any, sep := " ", location := #caller_location) { log(.Fatal, ..args, sep=sep, location=location) } -panic :: proc(#no_capture args: ..any, location := #caller_location) -> ! { +panic :: proc(args: ..any, location := #caller_location) -> ! { log(.Fatal, ..args, location=location) runtime.panic("log.panic", location) } -panicf :: proc(fmt_str: string, #no_capture args: ..any, location := #caller_location) -> ! { +panicf :: proc(fmt_str: string, args: ..any, location := #caller_location) -> ! { logf(.Fatal, fmt_str, ..args, location=location) runtime.panic("log.panicf", location) } @@ -133,14 +133,14 @@ assert :: proc(condition: bool, message := "", loc := #caller_location) { } @(disabled=ODIN_DISABLE_ASSERT) -assertf :: proc(condition: bool, fmt_str: string, #no_capture args: ..any, loc := #caller_location) { +assertf :: proc(condition: bool, fmt_str: string, args: ..any, loc := #caller_location) { if !condition { // NOTE(dragos): We are using the same trick as in builtin.assert // to improve performance to make the CPU not // execute speculatively, making it about an order of // magnitude faster @(cold) - internal :: proc(loc: runtime.Source_Code_Location, fmt_str: string, #no_capture args: ..any) { + internal :: proc(loc: runtime.Source_Code_Location, fmt_str: string, args: ..any) { p := context.assertion_failure_proc if p == nil { p = runtime.default_assertion_failure_proc @@ -155,7 +155,7 @@ assertf :: proc(condition: bool, fmt_str: string, #no_capture args: ..any, loc : -log :: proc(level: Level, #no_capture args: ..any, sep := " ", location := #caller_location) { +log :: proc(level: Level, args: ..any, sep := " ", location := #caller_location) { logger := context.logger if logger.procedure == nil { return @@ -167,7 +167,7 @@ log :: proc(level: Level, #no_capture args: ..any, sep := " ", location := #call logger.procedure(logger.data, level, str, logger.options, location) } -logf :: proc(level: Level, fmt_str: string, #no_capture args: ..any, location := #caller_location) { +logf :: proc(level: Level, fmt_str: string, args: ..any, location := #caller_location) { logger := context.logger if logger.procedure == nil { return diff --git a/core/math/big/helpers.odin b/core/math/big/helpers.odin index c82b5eead..ee09bb2c7 100644 --- a/core/math/big/helpers.odin +++ b/core/math/big/helpers.odin @@ -404,7 +404,7 @@ clear_if_uninitialized_single :: proc(arg: ^Int, allocator := context.allocator) return #force_inline internal_clear_if_uninitialized_single(arg, allocator) } -clear_if_uninitialized_multi :: proc(#no_capture args: ..^Int, allocator := context.allocator) -> (err: Error) { +clear_if_uninitialized_multi :: proc(args: ..^Int, allocator := context.allocator) -> (err: Error) { args := args assert_if_nil(..args) @@ -420,7 +420,7 @@ error_if_immutable_single :: proc(arg: ^Int) -> (err: Error) { return nil } -error_if_immutable_multi :: proc(#no_capture args: ..^Int) -> (err: Error) { +error_if_immutable_multi :: proc(args: ..^Int) -> (err: Error) { for i in args { if i != nil && .Immutable in i.flags { return .Assignment_To_Immutable } } @@ -431,7 +431,7 @@ error_if_immutable :: proc {error_if_immutable_single, error_if_immutable_multi, /* Allocates several `Int`s at once. */ -int_init_multi :: proc(#no_capture integers: ..^Int, allocator := context.allocator) -> (err: Error) { +int_init_multi :: proc(integers: ..^Int, allocator := context.allocator) -> (err: Error) { assert_if_nil(..integers) integers := integers @@ -812,13 +812,13 @@ assert_if_nil :: proc{ assert_if_nil_rat, } -assert_if_nil_int :: #force_inline proc(#no_capture integers: ..^Int, loc := #caller_location) { +assert_if_nil_int :: #force_inline proc(integers: ..^Int, loc := #caller_location) { for i in integers { assert(i != nil, "(nil)", loc) } } -assert_if_nil_rat :: #force_inline proc(#no_capture rationals: ..^Rat, loc := #caller_location) { +assert_if_nil_rat :: #force_inline proc(rationals: ..^Rat, loc := #caller_location) { for r in rationals { assert(r != nil, "(nil)", loc) } diff --git a/core/math/big/internal.odin b/core/math/big/internal.odin index 9eaa0c8e1..c9b331e55 100644 --- a/core/math/big/internal.odin +++ b/core/math/big/internal.odin @@ -1844,7 +1844,7 @@ internal_root_n :: proc { internal_int_root_n, } Deallocates the backing memory of one or more `Int`s. Asssumes none of the `integers` to be a `nil`. */ -internal_int_destroy :: proc(#no_capture integers: ..^Int) { +internal_int_destroy :: proc(integers: ..^Int) { integers := integers for &a in integers { @@ -2872,7 +2872,7 @@ internal_clear_if_uninitialized_single :: proc(arg: ^Int, allocator := context.a return err } -internal_clear_if_uninitialized_multi :: proc(#no_capture args: ..^Int, allocator := context.allocator) -> (err: Error) { +internal_clear_if_uninitialized_multi :: proc(args: ..^Int, allocator := context.allocator) -> (err: Error) { context.allocator = allocator for i in args { @@ -2890,7 +2890,7 @@ internal_error_if_immutable_single :: proc(arg: ^Int) -> (err: Error) { return nil } -internal_error_if_immutable_multi :: proc(#no_capture args: ..^Int) -> (err: Error) { +internal_error_if_immutable_multi :: proc(args: ..^Int) -> (err: Error) { for i in args { if i != nil && .Immutable in i.flags { return .Assignment_To_Immutable } } @@ -2901,7 +2901,7 @@ internal_error_if_immutable :: proc {internal_error_if_immutable_single, interna /* Allocates several `Int`s at once. */ -internal_int_init_multi :: proc(#no_capture integers: ..^Int, allocator := context.allocator) -> (err: Error) { +internal_int_init_multi :: proc(integers: ..^Int, allocator := context.allocator) -> (err: Error) { context.allocator = allocator integers := integers diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index 3ae3b5dba..dec892f84 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -5,8 +5,8 @@ import "core:odin/tokenizer" import "core:fmt" -Warning_Handler :: #type proc(pos: tokenizer.Pos, fmt: string, #no_capture args: ..any) -Error_Handler :: #type proc(pos: tokenizer.Pos, fmt: string, #no_capture args: ..any) +Warning_Handler :: #type proc(pos: tokenizer.Pos, fmt: string, args: ..any) +Error_Handler :: #type proc(pos: tokenizer.Pos, fmt: string, args: ..any) Flag :: enum u32 { Optional_Semicolons, @@ -67,25 +67,25 @@ Import_Decl_Kind :: enum { -default_warning_handler :: proc(pos: tokenizer.Pos, msg: string, #no_capture args: ..any) { +default_warning_handler :: proc(pos: tokenizer.Pos, msg: string, args: ..any) { fmt.eprintf("%s(%d:%d): Warning: ", pos.file, pos.line, pos.column) fmt.eprintf(msg, ..args) fmt.eprintf("\n") } -default_error_handler :: proc(pos: tokenizer.Pos, msg: string, #no_capture args: ..any) { +default_error_handler :: proc(pos: tokenizer.Pos, msg: string, args: ..any) { fmt.eprintf("%s(%d:%d): ", pos.file, pos.line, pos.column) fmt.eprintf(msg, ..args) fmt.eprintf("\n") } -warn :: proc(p: ^Parser, pos: tokenizer.Pos, msg: string, #no_capture args: ..any) { +warn :: proc(p: ^Parser, pos: tokenizer.Pos, msg: string, args: ..any) { if p.warn != nil { p.warn(pos, msg, ..args) } p.file.syntax_warning_count += 1 } -error :: proc(p: ^Parser, pos: tokenizer.Pos, msg: string, #no_capture args: ..any) { +error :: proc(p: ^Parser, pos: tokenizer.Pos, msg: string, args: ..any) { if p.err != nil { p.err(pos, msg, ..args) } diff --git a/core/odin/tokenizer/tokenizer.odin b/core/odin/tokenizer/tokenizer.odin index c5992e5f4..62170aa10 100644 --- a/core/odin/tokenizer/tokenizer.odin +++ b/core/odin/tokenizer/tokenizer.odin @@ -4,7 +4,7 @@ import "core:fmt" import "core:unicode" import "core:unicode/utf8" -Error_Handler :: #type proc(pos: Pos, fmt: string, #no_capture args: ..any) +Error_Handler :: #type proc(pos: Pos, fmt: string, args: ..any) Flag :: enum { Insert_Semicolon, @@ -62,13 +62,13 @@ offset_to_pos :: proc(t: ^Tokenizer, offset: int) -> Pos { } } -default_error_handler :: proc(pos: Pos, msg: string, #no_capture args: ..any) { +default_error_handler :: proc(pos: Pos, msg: string, args: ..any) { fmt.eprintf("%s(%d:%d) ", pos.file, pos.line, pos.column) fmt.eprintf(msg, ..args) fmt.eprintf("\n") } -error :: proc(t: ^Tokenizer, offset: int, msg: string, #no_capture args: ..any) { +error :: proc(t: ^Tokenizer, offset: int, msg: string, args: ..any) { pos := offset_to_pos(t, offset) if t.err != nil { t.err(pos, msg, ..args) diff --git a/core/testing/testing.odin b/core/testing/testing.odin index 04dc79095..29fe853ef 100644 --- a/core/testing/testing.odin +++ b/core/testing/testing.odin @@ -53,12 +53,12 @@ T :: struct { @(deprecated="prefer `log.error`") -error :: proc(t: ^T, #no_capture args: ..any, loc := #caller_location) { +error :: proc(t: ^T, args: ..any, loc := #caller_location) { pkg_log.error(..args, location = loc) } @(deprecated="prefer `log.errorf`") -errorf :: proc(t: ^T, format: string, #no_capture args: ..any, loc := #caller_location) { +errorf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) { pkg_log.errorf(format, ..args, location = loc) } @@ -87,12 +87,12 @@ failed :: proc(t: ^T) -> bool { } @(deprecated="prefer `log.info`") -log :: proc(t: ^T, #no_capture args: ..any, loc := #caller_location) { +log :: proc(t: ^T, args: ..any, loc := #caller_location) { pkg_log.info(..args, location = loc) } @(deprecated="prefer `log.infof`") -logf :: proc(t: ^T, format: string, #no_capture args: ..any, loc := #caller_location) { +logf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) { pkg_log.infof(format, ..args, location = loc) } @@ -121,7 +121,7 @@ expect :: proc(t: ^T, ok: bool, msg: string = "", loc := #caller_location) -> bo return ok } -expectf :: proc(t: ^T, ok: bool, format: string, #no_capture args: ..any, loc := #caller_location) -> bool { +expectf :: proc(t: ^T, ok: bool, format: string, args: ..any, loc := #caller_location) -> bool { if !ok { pkg_log.errorf(format, ..args, location=loc) } diff --git a/core/text/scanner/scanner.odin b/core/text/scanner/scanner.odin index 6eb366b49..d27c66f24 100644 --- a/core/text/scanner/scanner.odin +++ b/core/text/scanner/scanner.odin @@ -250,7 +250,7 @@ error :: proc(s: ^Scanner, msg: string) { } } -errorf :: proc(s: ^Scanner, format: string, #no_capture args: ..any) { +errorf :: proc(s: ^Scanner, format: string, args: ..any) { error(s, fmt.tprintf(format, ..args)) } diff --git a/core/text/table/table.odin b/core/text/table/table.odin index 4d0baef64..27c99b1f1 100644 --- a/core/text/table/table.odin +++ b/core/text/table/table.odin @@ -145,7 +145,7 @@ set_cell_value_and_alignment :: proc(tbl: ^Table, row, col: int, value: any, ali cell.alignment = alignment } -format :: proc(tbl: ^Table, _fmt: string, #no_capture args: ..any) -> string { +format :: proc(tbl: ^Table, _fmt: string, args: ..any) -> string { context.allocator = tbl.format_allocator return fmt.aprintf(_fmt, ..args) } diff --git a/core/unicode/tools/generate_entity_table.odin b/core/unicode/tools/generate_entity_table.odin index 238a3e219..16baa1adf 100644 --- a/core/unicode/tools/generate_entity_table.odin +++ b/core/unicode/tools/generate_entity_table.odin @@ -12,7 +12,7 @@ import "core:fmt" /* Silent error handler for the parser. */ -Error_Handler :: proc(pos: xml.Pos, fmt: string, #no_capture args: ..any) {} +Error_Handler :: proc(pos: xml.Pos, fmt: string, args: ..any) {} OPTIONS :: xml.Options{ flags = { .Ignore_Unsupported, }, expected_doctype = "unicode", } diff --git a/src/check_type.cpp b/src/check_type.cpp index d1c9bb381..466b9b3cd 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2065,9 +2065,10 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para } else if (p->flags & FieldFlag_c_vararg) { error(name, "'#no_capture' cannot be applied to a #c_vararg parameter"); p->flags &= ~FieldFlag_no_capture; + } else { + error(name, "'#no_capture' is already implied on all variadic parameter"); } } - if (is_poly_name) { if (p->flags&FieldFlag_no_alias) { error(name, "'#no_alias' can only be applied to non constant values"); @@ -2085,6 +2086,11 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para error(name, "'#by_ptr' can only be applied to variable fields"); p->flags &= ~FieldFlag_by_ptr; } + if (p->flags&FieldFlag_no_capture) { + error(name, "'#no_capture' can only be applied to variable fields"); + p->flags &= ~FieldFlag_no_capture; + } + if (!is_type_polymorphic(type) && check_constant_parameter_value(type, params[i])) { // failed @@ -2104,6 +2110,8 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para param->flags |= EntityFlag_Ellipsis; if (is_c_vararg) { param->flags |= EntityFlag_CVarArg; + } else { + param->flags |= EntityFlag_NoCapture; } } diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index dd1041702..71fa1dbd0 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -344,7 +344,6 @@ struct lbProcedure { Array no_captures; LLVMValueRef temp_callee_return_struct_memory; - Ast *curr_stmt; Array scope_stack; diff --git a/src/parser.hpp b/src/parser.hpp index 15176f789..451cdf53d 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -330,10 +330,11 @@ enum FieldFlag : u32 { FieldFlag_subtype = 1<<7, FieldFlag_by_ptr = 1<<8, FieldFlag_no_broadcast = 1<<9, // disallow array programming - FieldFlag_no_capture = 1<<10, + + FieldFlag_no_capture = 1<<11, // Internal use by the parser only - FieldFlag_Tags = 1<<11, + FieldFlag_Tags = 1<<15, FieldFlag_Results = 1<<16, diff --git a/tests/core/encoding/xml/test_core_xml.odin b/tests/core/encoding/xml/test_core_xml.odin index 811ee27dc..b29431e10 100644 --- a/tests/core/encoding/xml/test_core_xml.odin +++ b/tests/core/encoding/xml/test_core_xml.odin @@ -8,7 +8,7 @@ import "core:fmt" import "core:log" import "core:hash" -Silent :: proc(pos: xml.Pos, format: string, #no_capture args: ..any) {} +Silent :: proc(pos: xml.Pos, format: string, args: ..any) {} OPTIONS :: xml.Options{ flags = { .Ignore_Unsupported, .Intern_Comments, }, expected_doctype = "", diff --git a/tests/core/fmt/test_core_fmt.odin b/tests/core/fmt/test_core_fmt.odin index 0aaef93a4..49142e24d 100644 --- a/tests/core/fmt/test_core_fmt.odin +++ b/tests/core/fmt/test_core_fmt.odin @@ -373,7 +373,7 @@ test_odin_value_export :: proc(t: ^testing.T) { } @(private) -check :: proc(t: ^testing.T, exp: string, format: string, #no_capture args: ..any, loc := #caller_location) { +check :: proc(t: ^testing.T, exp: string, format: string, args: ..any, loc := #caller_location) { got := fmt.tprintf(format, ..args) testing.expectf(t, got == exp, "(%q, %v): %q != %q", format, args, got, exp, loc = loc) } diff --git a/tests/documentation/documentation_tester.odin b/tests/documentation/documentation_tester.odin index 7fc37b9f0..8a798d6c5 100644 --- a/tests/documentation/documentation_tester.odin +++ b/tests/documentation/documentation_tester.odin @@ -51,7 +51,7 @@ common_prefix :: proc(strs: []string) -> string { return prefix } -errorf :: proc(format: string, #no_capture args: ..any) -> ! { +errorf :: proc(format: string, args: ..any) -> ! { fmt.eprintf("%s ", os.args[0]) fmt.eprintf(format, ..args) fmt.eprintln() diff --git a/vendor/OpenGL/wrappers.odin b/vendor/OpenGL/wrappers.odin index 971452be5..1eb8fc72f 100644 --- a/vendor/OpenGL/wrappers.odin +++ b/vendor/OpenGL/wrappers.odin @@ -754,7 +754,7 @@ when !GL_DEBUG { MultiDrawElementsIndirectCount :: proc "c" (mode: i32, type: i32, indirect: [^]DrawElementsIndirectCommand, drawcount: i32, maxdrawcount, stride: i32) { impl_MultiDrawElementsIndirectCount(mode, type, indirect, drawcount, maxdrawcount, stride) } PolygonOffsetClamp :: proc "c" (factor, units, clamp: f32) { impl_PolygonOffsetClamp(factor, units, clamp) } } else { - debug_helper :: proc"c"(from_loc: runtime.Source_Code_Location, num_ret: int, #no_capture args: ..any, loc := #caller_location) { + debug_helper :: proc"c"(from_loc: runtime.Source_Code_Location, num_ret: int, args: ..any, loc := #caller_location) { context = runtime.default_context() Error_Enum :: enum { diff --git a/vendor/raylib/raylib.odin b/vendor/raylib/raylib.odin index c2995ba36..0dd3bd4fd 100644 --- a/vendor/raylib/raylib.odin +++ b/vendor/raylib/raylib.odin @@ -1667,7 +1667,7 @@ IsGestureDetected :: proc "c" (gesture: Gesture) -> bool { // Text formatting with variables (sprintf style) -TextFormat :: proc(text: cstring, #no_capture args: ..any) -> cstring { +TextFormat :: proc(text: cstring, args: ..any) -> cstring { @static buffers: [MAX_TEXTFORMAT_BUFFERS][MAX_TEXT_BUFFER_LENGTH]byte @static index: u32 @@ -1683,7 +1683,7 @@ TextFormat :: proc(text: cstring, #no_capture args: ..any) -> cstring { } // Text formatting with variables (sprintf style) and allocates (must be freed with 'MemFree') -TextFormatAlloc :: proc(text: cstring, #no_capture args: ..any) -> cstring { +TextFormatAlloc :: proc(text: cstring, args: ..any) -> cstring { str := fmt.tprintf(string(text), ..args) return strings.clone_to_cstring(str, MemAllocator()) } From 3dff83f3dc2914cdfb9a8f19cf990682cda41b03 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 12:39:30 +0100 Subject: [PATCH 028/198] Mock out `#no_capture` for future use --- src/check_expr.cpp | 13 ++++++------- src/check_type.cpp | 32 ++++++++++++++++++++++++-------- src/checker.cpp | 2 +- src/checker.hpp | 4 ++-- src/llvm_abi.cpp | 6 ++++++ src/llvm_backend.hpp | 4 ++-- src/llvm_backend_proc.cpp | 32 +++++++++++++------------------- 7 files changed, 54 insertions(+), 39 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 645d8ac5a..4edd34990 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -6034,19 +6034,18 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A Entity *vt = pt->params->Tuple.variables[pt->variadic_index]; o.type = vt->type; - // NOTE(bill, 2024-07-14): minimize the stack usage for variadic parameter that use `#no_capture` - // on the variadic parameter - if (c->decl && (vt->flags & EntityFlag_NoCapture)) { + // NOTE(bill, 2024-07-14): minimize the stack usage for variadic parameters with the backing array + if (c->decl) { bool found = false; - for (NoCaptureData &nc : c->decl->no_captures) { - if (are_types_identical(vt->type, nc.slice_type)) { - nc.max_count = gb_max(nc.max_count, variadic_operands.count); + for (auto &vr : c->decl->variadic_reuses) { + if (are_types_identical(vt->type, vr.slice_type)) { + vr.max_count = gb_max(vr.max_count, variadic_operands.count); found = true; break; } } if (!found) { - array_add(&c->decl->no_captures, NoCaptureData{vt->type, variadic_operands.count}); + array_add(&c->decl->variadic_reuses, VariadicReuseData{vt->type, variadic_operands.count}); } } diff --git a/src/check_type.cpp b/src/check_type.cpp index 466b9b3cd..7b75bf503 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1954,7 +1954,7 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para p->flags &= ~FieldFlag_by_ptr; } if (p->flags&FieldFlag_no_capture) { - error(name, "'#no_capture' can only be applied to variable variadic fields"); + error(name, "'#no_capture' can only be applied to variable fields"); p->flags &= ~FieldFlag_no_capture; } @@ -2059,16 +2059,32 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para } } if (p->flags&FieldFlag_no_capture) { - if (!(is_variadic && variadic_index == variables.count)) { - error(name, "'#no_capture' can only be applied to a variadic parameter"); - p->flags &= ~FieldFlag_no_capture; - } else if (p->flags & FieldFlag_c_vararg) { - error(name, "'#no_capture' cannot be applied to a #c_vararg parameter"); - p->flags &= ~FieldFlag_no_capture; + if (is_variadic && variadic_index == variables.count) { + if (p->flags & FieldFlag_c_vararg) { + error(name, "'#no_capture' cannot be applied to a #c_vararg parameter"); + p->flags &= ~FieldFlag_no_capture; + } else { + error(name, "'#no_capture' is already implied on all variadic parameter"); + } + } else if (is_type_polymorphic(type)) { + // ignore } else { - error(name, "'#no_capture' is already implied on all variadic parameter"); + if (is_type_internally_pointer_like(type)) { + // okay + } else if (is_type_slice(type) || is_type_string(type)) { + // okay + } else if (is_type_dynamic_array(type)) { + // okay + } else { + ERROR_BLOCK(); + error(name, "'#no_capture' can only be applied to pointer-like types, slices, strings, and dynamic arrays"); + error_line("\t'#no_capture' does not currently do anything useful\n"); + p->flags &= ~FieldFlag_no_capture; + } } } + + if (is_poly_name) { if (p->flags&FieldFlag_no_alias) { error(name, "'#no_alias' can only be applied to non constant values"); diff --git a/src/checker.cpp b/src/checker.cpp index abacc13cb..9adf4ef3c 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -184,7 +184,7 @@ gb_internal void init_decl_info(DeclInfo *d, Scope *scope, DeclInfo *parent) { ptr_set_init(&d->deps, 0); ptr_set_init(&d->type_info_deps, 0); d->labels.allocator = heap_allocator(); - d->no_captures.allocator = heap_allocator(); + d->variadic_reuses.allocator = heap_allocator(); } gb_internal DeclInfo *make_decl_info(Scope *scope, DeclInfo *parent) { diff --git a/src/checker.hpp b/src/checker.hpp index 17722f6b6..2fadbe56a 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -181,7 +181,7 @@ char const *ProcCheckedState_strings[ProcCheckedState_COUNT] { "Checked", }; -struct NoCaptureData { +struct VariadicReuseData { Type *slice_type; // ..elem_type isize max_count; }; @@ -224,7 +224,7 @@ struct DeclInfo { Array labels; - Array no_captures; + Array variadic_reuses; // NOTE(bill): this is to prevent a race condition since these procedure literals can be created anywhere at any time struct lbModule *code_gen_module; diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index b2e485d01..9a3479b34 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -15,6 +15,7 @@ struct lbArgType { LLVMAttributeRef align_attribute; // Optional i64 byval_alignment; bool is_byval; + bool no_capture; }; @@ -159,6 +160,11 @@ gb_internal void lb_add_function_type_attributes(LLVMValueRef fn, lbFunctionType LLVMAddAttributeAtIndex(fn, arg_index+1, arg->align_attribute); } + if (arg->no_capture) { + LLVMAddAttributeAtIndex(fn, arg_index+1, nocapture_attr); + } + + if (ft->multiple_return_original_type) { if (ft->original_arg_count <= i) { LLVMAddAttributeAtIndex(fn, arg_index+1, noalias_attr); diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 71fa1dbd0..24494e2af 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -296,7 +296,7 @@ enum lbProcedureFlag : u32 { lbProcedureFlag_DebugAllocaCopy = 1<<1, }; -struct lbNoCaptureData { +struct lbVariadicReuseData { Type *slice_type; lbAddr base_array; }; @@ -341,7 +341,7 @@ struct lbProcedure { bool in_multi_assignment; Array raw_input_parameters; - Array no_captures; + Array variadic_reuses; LLVMValueRef temp_callee_return_struct_memory; Ast *curr_stmt; diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index ec244e185..1585df865 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -517,7 +517,7 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) { lb_start_block(p, p->entry_block); map_init(&p->direct_parameters); - p->no_captures.allocator = heap_allocator(); + p->variadic_reuses.allocator = heap_allocator(); GB_ASSERT(p->type != nullptr); @@ -3452,27 +3452,21 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { isize slice_len = var_args.count; if (slice_len > 0) { lbAddr base_array = {}; - if (e->flags & EntityFlag_NoCapture) { - for (lbNoCaptureData const &nc : p->no_captures) { - if (are_types_identical(nc.slice_type, slice_type)) { - base_array = nc.base_array; + for (auto const &vr : p->variadic_reuses) { + if (are_types_identical(vr.slice_type, slice_type)) { + base_array = vr.base_array; + break; + } + } + DeclInfo *d = decl_info_of_entity(p->entity); + if (d != nullptr && base_array.addr.value == nullptr) { + for (auto const &vr : d->variadic_reuses) { + if (are_types_identical(vr.slice_type, slice_type)) { + base_array = lb_add_local_generated(p, alloc_type_array(elem_type, vr.max_count), true); + array_add(&p->variadic_reuses, lbVariadicReuseData{slice_type, base_array}); break; } } - DeclInfo *d = decl_info_of_entity(p->entity); - if (d != nullptr && base_array.addr.value == nullptr) { - for (NoCaptureData const &nc : d->no_captures) { - if (are_types_identical(nc.slice_type, slice_type)) { - base_array = lb_add_local_generated(p, alloc_type_array(elem_type, nc.max_count), true); - array_add(&p->no_captures, lbNoCaptureData{slice_type, base_array}); - break; - } - } - } - } - - if (base_array.addr.value == nullptr) { - base_array = lb_add_local_generated(p, alloc_type_array(elem_type, slice_len), true); } GB_ASSERT(base_array.addr.value != nullptr); From 5ce6676914f5daadf42613574d4700c2750275de Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 12:41:16 +0100 Subject: [PATCH 029/198] Make `#no_capture` map to `nocapture` --- src/llvm_backend_proc.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 1585df865..825434c31 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -253,6 +253,9 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i if (e->flags&EntityFlag_NoAlias) { lb_add_proc_attribute_at_index(p, offset+parameter_index, "noalias"); } + if (e->flags&EntityFlag_NoCapture) { + lb_add_proc_attribute_at_index(p, offset+parameter_index, "nocapture"); + } parameter_index += 1; } } From 3533094cc2fc8cae8229b9887efae4541ccd278b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 12:44:13 +0100 Subject: [PATCH 030/198] Restrict `#no_capture` to pointer-like types only --- src/check_type.cpp | 6 +----- src/llvm_backend_proc.cpp | 4 +++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/check_type.cpp b/src/check_type.cpp index 7b75bf503..d0dddb62b 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2071,13 +2071,9 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para } else { if (is_type_internally_pointer_like(type)) { // okay - } else if (is_type_slice(type) || is_type_string(type)) { - // okay - } else if (is_type_dynamic_array(type)) { - // okay } else { ERROR_BLOCK(); - error(name, "'#no_capture' can only be applied to pointer-like types, slices, strings, and dynamic arrays"); + error(name, "'#no_capture' can only be applied to pointer-like types"); error_line("\t'#no_capture' does not currently do anything useful\n"); p->flags &= ~FieldFlag_no_capture; } diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 825434c31..272ffb474 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -254,7 +254,9 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i lb_add_proc_attribute_at_index(p, offset+parameter_index, "noalias"); } if (e->flags&EntityFlag_NoCapture) { - lb_add_proc_attribute_at_index(p, offset+parameter_index, "nocapture"); + if (is_type_internally_pointer_like(e->type)) { + lb_add_proc_attribute_at_index(p, offset+parameter_index, "nocapture"); + } } parameter_index += 1; } From 5027c7081e02cc208a354cbad2fbeb90ac297b03 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 12:50:33 +0100 Subject: [PATCH 031/198] Reuse slice variable for variadic parameters --- src/llvm_backend.hpp | 1 + src/llvm_backend_proc.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 24494e2af..100748038 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -299,6 +299,7 @@ enum lbProcedureFlag : u32 { struct lbVariadicReuseData { Type *slice_type; lbAddr base_array; + lbAddr slice_addr; }; struct lbProcedure { diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 272ffb474..bc85b14c2 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3457,9 +3457,12 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { isize slice_len = var_args.count; if (slice_len > 0) { lbAddr base_array = {}; + lbAddr slice = {}; + for (auto const &vr : p->variadic_reuses) { if (are_types_identical(vr.slice_type, slice_type)) { base_array = vr.base_array; + slice = vr.slice_addr; break; } } @@ -3468,14 +3471,15 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { for (auto const &vr : d->variadic_reuses) { if (are_types_identical(vr.slice_type, slice_type)) { base_array = lb_add_local_generated(p, alloc_type_array(elem_type, vr.max_count), true); - array_add(&p->variadic_reuses, lbVariadicReuseData{slice_type, base_array}); + slice = lb_add_local_generated(p, slice_type, true); + array_add(&p->variadic_reuses, lbVariadicReuseData{slice_type, base_array, slice}); break; } } } GB_ASSERT(base_array.addr.value != nullptr); + GB_ASSERT(slice.addr.value != nullptr); - lbAddr slice = lb_add_local_generated(p, slice_type, true); for (isize i = 0; i < var_args.count; i++) { lbValue addr = lb_emit_array_epi(p, base_array.addr, cast(i32)i); From 8650180cccad02b39a00dcd05f696a8e3879c29e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 12:59:55 +0100 Subject: [PATCH 032/198] Change Odin calling convention to not do a copy on `byval` for SysV --- src/llvm_abi.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index 9a3479b34..c21cd0a46 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -651,10 +651,10 @@ namespace lbAbiAmd64SysV { if (is_mem_cls(cls, attribute_kind)) { LLVMAttributeRef attribute = nullptr; if (attribute_kind == Amd64TypeAttribute_ByVal) { - // if (!is_calling_convention_odin(calling_convention)) { - return lb_arg_type_indirect_byval(c, type); - // } - // attribute = nullptr; + if (is_calling_convention_odin(calling_convention)) { + return lb_arg_type_indirect(type, attribute); + } + return lb_arg_type_indirect_byval(c, type); } else if (attribute_kind == Amd64TypeAttribute_StructRect) { attribute = lb_create_enum_attribute_with_type(c, "sret", type); } From 0a530b5ce8642508c5df2fdb9ec43682be07d5cd Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 13:20:12 +0100 Subject: [PATCH 033/198] Add error for `#no_capture` being reserved for future use --- src/check_type.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/check_type.cpp b/src/check_type.cpp index d0dddb62b..fea937e4e 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2070,7 +2070,7 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para // ignore } else { if (is_type_internally_pointer_like(type)) { - // okay + error(name, "'#no_capture' is currently reserved for future use"); } else { ERROR_BLOCK(); error(name, "'#no_capture' can only be applied to pointer-like types"); From 6959554040d85597300ab2ce6c25852d18e61923 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 13:44:47 +0100 Subject: [PATCH 034/198] Calculate size and alignment, and reuse memory for all variadic calls within a procedure body --- src/check_decl.cpp | 9 +++++++++ src/checker.cpp | 2 ++ src/checker.hpp | 4 +++- src/llvm_backend.hpp | 6 +++--- src/llvm_backend_proc.cpp | 25 +++++++++++++++++-------- 5 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 7d81d102d..6828774e4 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1869,5 +1869,14 @@ gb_internal bool check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de add_deps_from_child_to_parent(decl); + for (VariadicReuseData const &vr : decl->variadic_reuses) { + GB_ASSERT(vr.slice_type->kind == Type_Slice); + Type *elem = vr.slice_type->Slice.elem; + i64 size = type_size_of(elem); + i64 align = type_align_of(elem); + decl->variadic_reuse_max_bytes = gb_max(decl->variadic_reuse_max_bytes, size*vr.max_count); + decl->variadic_reuse_max_align = gb_max(decl->variadic_reuse_max_align, align); + } + return true; } diff --git a/src/checker.cpp b/src/checker.cpp index 9adf4ef3c..336440d32 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -185,6 +185,8 @@ gb_internal void init_decl_info(DeclInfo *d, Scope *scope, DeclInfo *parent) { ptr_set_init(&d->type_info_deps, 0); d->labels.allocator = heap_allocator(); d->variadic_reuses.allocator = heap_allocator(); + d->variadic_reuse_max_bytes = 0; + d->variadic_reuse_max_align = 1; } gb_internal DeclInfo *make_decl_info(Scope *scope, DeclInfo *parent) { diff --git a/src/checker.hpp b/src/checker.hpp index 2fadbe56a..d76e4c7d0 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -183,7 +183,7 @@ char const *ProcCheckedState_strings[ProcCheckedState_COUNT] { struct VariadicReuseData { Type *slice_type; // ..elem_type - isize max_count; + i64 max_count; }; // DeclInfo is used to store information of certain declarations to allow for "any order" usage @@ -225,6 +225,8 @@ struct DeclInfo { Array labels; Array variadic_reuses; + i64 variadic_reuse_max_bytes; + i64 variadic_reuse_max_align; // NOTE(bill): this is to prevent a race condition since these procedure literals can be created anywhere at any time struct lbModule *code_gen_module; diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 100748038..deb05528f 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -296,9 +296,8 @@ enum lbProcedureFlag : u32 { lbProcedureFlag_DebugAllocaCopy = 1<<1, }; -struct lbVariadicReuseData { +struct lbVariadicReuseSlices { Type *slice_type; - lbAddr base_array; lbAddr slice_addr; }; @@ -342,7 +341,8 @@ struct lbProcedure { bool in_multi_assignment; Array raw_input_parameters; - Array variadic_reuses; + Array variadic_reuses; + lbAddr variadic_reuse_base_array_ptr; LLVMValueRef temp_callee_return_struct_memory; Ast *curr_stmt; diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index bc85b14c2..7a895fbdd 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3456,39 +3456,48 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { } isize slice_len = var_args.count; if (slice_len > 0) { - lbAddr base_array = {}; lbAddr slice = {}; for (auto const &vr : p->variadic_reuses) { if (are_types_identical(vr.slice_type, slice_type)) { - base_array = vr.base_array; slice = vr.slice_addr; break; } } + DeclInfo *d = decl_info_of_entity(p->entity); - if (d != nullptr && base_array.addr.value == nullptr) { + if (d != nullptr && slice.addr.value == nullptr) { for (auto const &vr : d->variadic_reuses) { if (are_types_identical(vr.slice_type, slice_type)) { - base_array = lb_add_local_generated(p, alloc_type_array(elem_type, vr.max_count), true); slice = lb_add_local_generated(p, slice_type, true); - array_add(&p->variadic_reuses, lbVariadicReuseData{slice_type, base_array, slice}); + array_add(&p->variadic_reuses, lbVariadicReuseSlices{slice_type, slice}); break; } } } - GB_ASSERT(base_array.addr.value != nullptr); + + lbValue base_array_ptr = p->variadic_reuse_base_array_ptr.addr; + if (d != nullptr && base_array_ptr.value == nullptr) { + i64 max_bytes = d->variadic_reuse_max_bytes; + i64 max_align = gb_max(d->variadic_reuse_max_align, 16); + p->variadic_reuse_base_array_ptr = lb_add_local_generated(p, alloc_type_array(t_u8, max_bytes), true); + lb_try_update_alignment(p->variadic_reuse_base_array_ptr.addr, cast(unsigned)max_align); + base_array_ptr = p->variadic_reuse_base_array_ptr.addr; + } + + GB_ASSERT(base_array_ptr.value != nullptr); GB_ASSERT(slice.addr.value != nullptr); + base_array_ptr = lb_emit_conv(p, base_array_ptr, alloc_type_pointer(alloc_type_array(elem_type, slice_len))); for (isize i = 0; i < var_args.count; i++) { - lbValue addr = lb_emit_array_epi(p, base_array.addr, cast(i32)i); + lbValue addr = lb_emit_array_epi(p, base_array_ptr, cast(i32)i); lbValue var_arg = var_args[i]; var_arg = lb_emit_conv(p, var_arg, elem_type); lb_emit_store(p, addr, var_arg); } - lbValue base_elem = lb_emit_array_epi(p, base_array.addr, 0); + lbValue base_elem = lb_emit_array_epi(p, base_array_ptr, 0); lbValue len = lb_const_int(p->module, t_int, slice_len); lb_fill_slice(p, slice, base_elem, len); From 2e0c5fefdedbeb1d9008a6f05a5c73c9fca7602f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 14:07:36 +0100 Subject: [PATCH 035/198] Reuse the slice value too for variadic parameters (LLVM >= 13) --- src/llvm_backend_proc.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 7a895fbdd..5270d6c30 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3469,7 +3469,18 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { if (d != nullptr && slice.addr.value == nullptr) { for (auto const &vr : d->variadic_reuses) { if (are_types_identical(vr.slice_type, slice_type)) { + #if LLVM_VERSION_MAJOR >= 13 + // NOTE(bill): No point wasting even more memory, just reuse this stack variable too + if (p->variadic_reuses.count > 0) { + slice = p->variadic_reuses[0].slice_addr; + } else { + slice = lb_add_local_generated(p, slice_type, true); + } + // NOTE(bill): Change the underlying type to match the specific type + slice.addr.type = alloc_type_pointer(slice_type); + #else slice = lb_add_local_generated(p, slice_type, true); + #endif array_add(&p->variadic_reuses, lbVariadicReuseSlices{slice_type, slice}); break; } From d1450e3d880ec70b306dc735f7f694f265348ef1 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 14:44:22 +0100 Subject: [PATCH 036/198] Fix styling issues --- src/thread_pool.cpp | 39 ++++++++++++++++++++------------------- src/threading.cpp | 12 ++++++------ 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/thread_pool.cpp b/src/thread_pool.cpp index bf953ddd0..62cca6de6 100644 --- a/src/thread_pool.cpp +++ b/src/thread_pool.cpp @@ -11,14 +11,14 @@ gb_internal bool thread_pool_add_task(ThreadPool *pool, WorkerTaskProc *proc, vo gb_internal void thread_pool_wait(ThreadPool *pool); enum GrabState { - GrabSuccess = 0, - GrabEmpty = 1, - GrabFailed = 2, + Grab_Success = 0, + Grab_Empty = 1, + Grab_Failed = 2, }; struct ThreadPool { - gbAllocator threads_allocator; - Slice threads; + gbAllocator threads_allocator; + Slice threads; std::atomic running; Futex tasks_available; @@ -59,8 +59,8 @@ gb_internal void thread_pool_destroy(ThreadPool *pool) { gb_free(pool->threads_allocator, pool->threads.data); } -TaskRingBuffer *taskring_grow(TaskRingBuffer *ring, isize bottom, isize top) { - TaskRingBuffer *new_ring = taskring_init(ring->size * 2); +TaskRingBuffer *task_ring_grow(TaskRingBuffer *ring, isize bottom, isize top) { + TaskRingBuffer *new_ring = task_ring_init(ring->size * 2); for (isize i = top; i < bottom; i++) { new_ring->buffer[i % new_ring->size] = ring->buffer[i % ring->size]; } @@ -75,7 +75,7 @@ void thread_pool_queue_push(Thread *thread, WorkerTask task) { isize size = bot - top; if (size > (cur_ring->size - 1)) { // Queue is full - thread->queue.ring = taskring_grow(thread->queue.ring, bot, top); + thread->queue.ring = task_ring_grow(thread->queue.ring, bot, top); cur_ring = thread->queue.ring.load(std::memory_order_relaxed); } @@ -104,19 +104,19 @@ GrabState thread_pool_queue_take(Thread *thread, WorkerTask *task) { if (!thread->queue.top.compare_exchange_strong(top, top + 1, std::memory_order_seq_cst, std::memory_order_relaxed)) { // Race failed thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); - return GrabEmpty; + return Grab_Empty; } thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); - return GrabSuccess; + return Grab_Success; } // We got a task without hitting a race - return GrabSuccess; + return Grab_Success; } else { // Queue is empty thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); - return GrabEmpty; + return Grab_Empty; } } @@ -125,7 +125,7 @@ GrabState thread_pool_queue_steal(Thread *thread, WorkerTask *task) { std::atomic_thread_fence(std::memory_order_seq_cst); isize bot = thread->queue.bottom.load(std::memory_order_acquire); - GrabState ret = GrabEmpty; + GrabState ret = Grab_Empty; if (top < bot) { // Queue is not empty TaskRingBuffer *cur_ring = thread->queue.ring.load(std::memory_order_consume); @@ -133,9 +133,9 @@ GrabState thread_pool_queue_steal(Thread *thread, WorkerTask *task) { if (!thread->queue.top.compare_exchange_strong(top, top + 1, std::memory_order_seq_cst, std::memory_order_relaxed)) { // Race failed - ret = GrabFailed; + ret = Grab_Failed; } else { - ret = GrabSuccess; + ret = Grab_Success; } } return ret; @@ -208,11 +208,10 @@ gb_internal THREAD_PROC(thread_pool_thread_proc) { WorkerTask task; GrabState ret = thread_pool_queue_steal(thread, &task); - if (ret == GrabFailed) { - goto main_loop_continue; - } else if (ret == GrabEmpty) { + switch (ret) { + case Grab_Empty: continue; - } else if (ret == GrabSuccess) { + case Grab_Success: task.do_work(task.data); pool->tasks_left.fetch_sub(1, std::memory_order_release); @@ -220,6 +219,8 @@ gb_internal THREAD_PROC(thread_pool_thread_proc) { futex_signal(&pool->tasks_left); } + /*fallthrough*/ + case Grab_Failed: goto main_loop_continue; } } diff --git a/src/threading.cpp b/src/threading.cpp index ac79efb05..ff0fdfcde 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -66,9 +66,9 @@ struct Thread { #endif isize idx; - isize stack_size; + isize stack_size; - struct TaskQueue queue; + struct TaskQueue queue; struct ThreadPool *pool; }; @@ -560,10 +560,10 @@ gb_internal void *internal_thread_proc(void *arg) { } #endif -TaskRingBuffer *taskring_init(isize size) { - TaskRingBuffer *ring = (TaskRingBuffer *)gb_alloc(heap_allocator(), sizeof(TaskRingBuffer)); +TaskRingBuffer *task_ring_init(isize size) { + TaskRingBuffer *ring = gb_alloc_item(heap_allocator(), TaskRingBuffer); ring->size = size; - ring->buffer = (WorkerTask *)gb_alloc_array(heap_allocator(), WorkerTask, ring->size); + ring->buffer = gb_alloc_array(heap_allocator(), WorkerTask, ring->size); return ring; } @@ -581,7 +581,7 @@ gb_internal void thread_init(ThreadPool *pool, Thread *t, isize idx) { #endif // Size must be a power of 2 - t->queue.ring = taskring_init(1 << 14); + t->queue.ring = task_ring_init(1 << 14); t->pool = pool; t->idx = idx; } From 3d38f14202773d80293fd8f72b677b635e6ea639 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 14:51:22 +0100 Subject: [PATCH 037/198] Use `fstat` on `os2.File` directly --- core/os/os2/errors.odin | 2 ++ core/os/os2/file.odin | 2 +- core/os/os2/file_linux.odin | 25 ++++++++++--------------- core/os/os2/file_windows.odin | 1 + core/os/os2/stat.odin | 8 +++++--- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/core/os/os2/errors.odin b/core/os/os2/errors.odin index 51d8314b4..8961bf599 100644 --- a/core/os/os2/errors.odin +++ b/core/os/os2/errors.odin @@ -22,6 +22,7 @@ General_Error :: enum u32 { Invalid_File, Invalid_Dir, Invalid_Path, + Invalid_Callback, Pattern_Has_Separator, @@ -64,6 +65,7 @@ error_string :: proc(ferr: Error) -> string { case .Invalid_File: return "invalid file" case .Invalid_Dir: return "invalid directory" case .Invalid_Path: return "invalid path" + case .Invalid_Callback: return "invalid callback" case .Unsupported: return "unsupported" case .Pattern_Has_Separator: return "pattern has separator" } diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 236423163..dc618db37 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -7,7 +7,7 @@ import "base:runtime" File :: struct { impl: _File, stream: io.Stream, - user_fstat: Fstat_Callback, + fstat: Fstat_Callback, } File_Mode :: distinct u32 diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 8e7db9751..eaded51c9 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -90,22 +90,17 @@ _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, er _new_file :: proc(fd: uintptr, _: string = "") -> ^File { file := new(File, file_allocator()) - _construct_file(file, fd, "") - return file -} - -_construct_file :: proc(file: ^File, fd: uintptr, _: string = "") { - file^ = { - impl = { - fd = linux.Fd(fd), - allocator = file_allocator(), - name = _get_full_path(file.impl.fd, file.impl.allocator), - }, - stream = { - data = file, - procedure = _file_stream_proc, - }, + file.impl = { + fd = linux.Fd(fd), + allocator = file_allocator(), + name = _get_full_path(file.impl.fd, file.impl.allocator), } + file.stream = { + data = file, + procedure = _file_stream_proc, + } + file.fstat = _fstat + return file } _destroy :: proc(f: ^File) -> Error { diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index b88ee8a69..8953edafb 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -151,6 +151,7 @@ _new_file :: proc(handle: uintptr, name: string) -> ^File { data = f, procedure = _file_stream_proc, } + f.fstat = _fstat return f } diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin index f79ad9165..1e7f9d225 100644 --- a/core/os/os2/stat.odin +++ b/core/os/os2/stat.odin @@ -29,10 +29,12 @@ file_info_delete :: proc(fi: File_Info, allocator: runtime.Allocator) { @(require_results) fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { - if f != nil && f.user_fstat != nil { - return f->user_fstat(allocator) + if f == nil { + return {}, nil + } else if f.fstat != nil { + return f->fstat(allocator) } - return _fstat(f, allocator) + return {}, .Invalid_Callback } @(require_results) From d90d7ed00280d997f6022420a5185a395d719b4d Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sun, 14 Jul 2024 16:00:55 +0200 Subject: [PATCH 038/198] Fix off-by-one in queue `back` and `back_ptr` procs --- core/container/queue/queue.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/container/queue/queue.odin b/core/container/queue/queue.odin index e7a60dde0..f83a5f2b7 100644 --- a/core/container/queue/queue.odin +++ b/core/container/queue/queue.odin @@ -95,11 +95,11 @@ front_ptr :: proc(q: ^$Q/Queue($T)) -> ^T { } back :: proc(q: ^$Q/Queue($T)) -> T { - idx := (q.offset+uint(q.len))%builtin.len(q.data) + idx := (q.offset+uint(q.len - 1))%builtin.len(q.data) return q.data[idx] } back_ptr :: proc(q: ^$Q/Queue($T)) -> ^T { - idx := (q.offset+uint(q.len))%builtin.len(q.data) + idx := (q.offset+uint(q.len - 1))%builtin.len(q.data) return &q.data[idx] } From 4f73b35da5e9f84b0851f2ba66907a4f8de057ff Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 15:09:33 +0100 Subject: [PATCH 039/198] Make `os2.File` a more generic interface --- core/os/os2/file.odin | 4 +- core/os/os2/file_linux.odin | 93 +++++++++++++++++++++-------------- core/os/os2/file_windows.odin | 71 +++++++++++++++----------- core/os/os2/stat_linux.odin | 3 +- core/os/os2/stat_windows.odin | 6 +-- 5 files changed, 103 insertions(+), 74 deletions(-) diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index dc618db37..be03155ff 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -5,9 +5,9 @@ import "core:time" import "base:runtime" File :: struct { - impl: _File, + impl: rawptr, stream: io.Stream, - fstat: Fstat_Callback, + fstat: Fstat_Callback, } File_Mode :: distinct u32 diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index eaded51c9..d2196b97b 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -7,13 +7,14 @@ import "base:runtime" import "core:sys/linux" _File :: struct { + using file: File, name: string, fd: linux.Fd, allocator: runtime.Allocator, } -_stdin : File = { - impl = { +_stdin := File{ + impl = &_File{ name = "/proc/self/fd/0", fd = 0, allocator = _file_allocator(), @@ -21,9 +22,10 @@ _stdin : File = { stream = { procedure = _file_stream_proc, }, + fstat = _fstat, } -_stdout : File = { - impl = { +_stdout := File{ + impl = &_File{ name = "/proc/self/fd/1", fd = 1, allocator = _file_allocator(), @@ -31,9 +33,10 @@ _stdout : File = { stream = { procedure = _file_stream_proc, }, + fstat = _fstat, } -_stderr : File = { - impl = { +_stderr := File{ + impl = &_File{ name = "/proc/self/fd/2", fd = 2, allocator = _file_allocator(), @@ -41,6 +44,7 @@ _stderr : File = { stream = { procedure = _file_stream_proc, }, + fstat = _fstat, } @init @@ -89,35 +93,35 @@ _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, er } _new_file :: proc(fd: uintptr, _: string = "") -> ^File { - file := new(File, file_allocator()) - file.impl = { - fd = linux.Fd(fd), - allocator = file_allocator(), - name = _get_full_path(file.impl.fd, file.impl.allocator), - } - file.stream = { - data = file, + impl := new(_File, file_allocator()) + impl.fd = linux.Fd(fd) + impl.allocator = file_allocator() + impl.name = _get_full_path(impl.fd, impl.allocator) + impl.file.stream = { + data = &impl.file, procedure = _file_stream_proc, } - file.fstat = _fstat - return file + impl.fstat = _fstat + return impl } _destroy :: proc(f: ^File) -> Error { - if f == nil { + if f == nil || f.impl == nil { return nil } - delete(f.impl.name, f.impl.allocator) - free(f, f.impl.allocator) + impl := (^_File)(f.impl) + delete(impl.name, impl.allocator) + free(f, impl.allocator) return nil } _close :: proc(f: ^File) -> Error { - if f == nil { + if f == nil || f.impl == nil { return nil } - errno := linux.close(f.impl.fd) + impl := (^_File)(f.impl) + errno := linux.close(impl.fd) if errno == .EBADF { // avoid possible double free return _get_platform_error(errno) } @@ -126,18 +130,20 @@ _close :: proc(f: ^File) -> Error { } _fd :: proc(f: ^File) -> uintptr { - if f == nil { + if f == nil || f.impl == nil { return ~uintptr(0) } - return uintptr(f.impl.fd) + impl := (^_File)(f.impl) + return uintptr(impl.fd) } _name :: proc(f: ^File) -> string { - return f.impl.name if f != nil else "" + return (^_File)(f.impl).name if f != nil && f.impl != nil else "" } _seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { - n, errno := linux.lseek(f.impl.fd, offset, linux.Seek_Whence(whence)) + impl := (^_File)(f.impl) + n, errno := linux.lseek(impl.fd, offset, linux.Seek_Whence(whence)) if errno != .NONE { return -1, _get_platform_error(errno) } @@ -148,7 +154,8 @@ _read :: proc(f: ^File, p: []byte) -> (i64, Error) { if len(p) == 0 { return 0, nil } - n, errno := linux.read(f.impl.fd, p[:]) + impl := (^_File)(f.impl) + n, errno := linux.read(impl.fd, p[:]) if errno != .NONE { return -1, _get_platform_error(errno) } @@ -159,8 +166,8 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (i64, Error) { if offset < 0 { return 0, .Invalid_Offset } - - n, errno := linux.pread(f.impl.fd, p[:], offset) + impl := (^_File)(f.impl) + n, errno := linux.pread(impl.fd, p[:], offset) if errno != .NONE { return -1, _get_platform_error(errno) } @@ -174,7 +181,8 @@ _write :: proc(f: ^File, p: []byte) -> (i64, Error) { if len(p) == 0 { return 0, nil } - n, errno := linux.write(f.impl.fd, p[:]) + impl := (^_File)(f.impl) + n, errno := linux.write(impl.fd, p[:]) if errno != .NONE { return -1, _get_platform_error(errno) } @@ -186,7 +194,8 @@ _write_at :: proc(f: ^File, p: []byte, offset: i64) -> (i64, Error) { return 0, .Invalid_Offset } - n, errno := linux.pwrite(f.impl.fd, p[:], offset) + impl := (^_File)(f.impl) + n, errno := linux.pwrite(impl.fd, p[:], offset) if errno != .NONE { return -1, _get_platform_error(errno) } @@ -195,7 +204,8 @@ _write_at :: proc(f: ^File, p: []byte, offset: i64) -> (i64, Error) { _file_size :: proc(f: ^File) -> (n: i64, err: Error) { s: linux.Stat = --- - errno := linux.fstat(f.impl.fd, &s) + impl := (^_File)(f.impl) + errno := linux.fstat(impl.fd, &s) if errno != .NONE { return -1, _get_platform_error(errno) } @@ -203,15 +213,18 @@ _file_size :: proc(f: ^File) -> (n: i64, err: Error) { } _sync :: proc(f: ^File) -> Error { - return _get_platform_error(linux.fsync(f.impl.fd)) + impl := (^_File)(f.impl) + return _get_platform_error(linux.fsync(impl.fd)) } _flush :: proc(f: ^File) -> Error { - return _get_platform_error(linux.fsync(f.impl.fd)) + impl := (^_File)(f.impl) + return _get_platform_error(linux.fsync(impl.fd)) } _truncate :: proc(f: ^File, size: i64) -> Error { - return _get_platform_error(linux.ftruncate(f.impl.fd, size)) + impl := (^_File)(f.impl) + return _get_platform_error(linux.ftruncate(impl.fd, size)) } _remove :: proc(name: string) -> Error { @@ -287,7 +300,8 @@ _chdir :: proc(name: string) -> Error { } _fchdir :: proc(f: ^File) -> Error { - return _get_platform_error(linux.fchdir(f.impl.fd)) + impl := (^_File)(f.impl) + return _get_platform_error(linux.fchdir(impl.fd)) } _chmod :: proc(name: string, mode: File_Mode) -> Error { @@ -297,7 +311,8 @@ _chmod :: proc(name: string, mode: File_Mode) -> Error { } _fchmod :: proc(f: ^File, mode: File_Mode) -> Error { - return _get_platform_error(linux.fchmod(f.impl.fd, transmute(linux.Mode)(u32(mode)))) + impl := (^_File)(f.impl) + return _get_platform_error(linux.fchmod(impl.fd, transmute(linux.Mode)(u32(mode)))) } // NOTE: will throw error without super user priviledges @@ -316,7 +331,8 @@ _lchown :: proc(name: string, uid, gid: int) -> Error { // NOTE: will throw error without super user priviledges _fchown :: proc(f: ^File, uid, gid: int) -> Error { - return _get_platform_error(linux.fchown(f.impl.fd, linux.Uid(uid), linux.Gid(gid))) + impl := (^_File)(f.impl) + return _get_platform_error(linux.fchown(impl.fd, linux.Uid(uid), linux.Gid(gid))) } _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { @@ -346,7 +362,8 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { uint(mtime._nsec) % uint(time.Second), }, } - return _get_platform_error(linux.utimensat(f.impl.fd, nil, ×[0], nil)) + impl := (^_File)(f.impl) + return _get_platform_error(linux.utimensat(impl.fd, nil, ×[0], nil)) } _exists :: proc(name: string) -> bool { diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 8953edafb..0ccea7f96 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -24,6 +24,8 @@ _File_Kind :: enum u8 { } _File :: struct { + using file: File, + fd: rawptr, name: string, wname: win32.wstring, @@ -130,12 +132,12 @@ _new_file :: proc(handle: uintptr, name: string) -> ^File { if handle == INVALID_HANDLE { return nil } - f := new(File, file_allocator()) + f := new(_File, file_allocator()) - f.impl.allocator = file_allocator() - f.impl.fd = rawptr(handle) - f.impl.name, _ = clone_string(name, f.impl.allocator) - f.impl.wname = win32.utf8_to_wstring(name, f.impl.allocator) + f.allocator = file_allocator() + f.fd = rawptr(handle) + f.name, _ = clone_string(name, f.allocator) + f.wname = win32.utf8_to_wstring(name, f.allocator) handle := _handle(f) kind := _File_Kind.File @@ -145,7 +147,7 @@ _new_file :: proc(handle: uintptr, name: string) -> ^File { if win32.GetFileType(handle) == win32.FILE_TYPE_PIPE { kind = .Pipe } - f.impl.kind = kind + f.kind = kind f.stream = { data = f, @@ -157,37 +159,38 @@ _new_file :: proc(handle: uintptr, name: string) -> ^File { } _fd :: proc(f: ^File) -> uintptr { - if f == nil { + if f == nil || f.impl == nil { return INVALID_HANDLE } - return uintptr(f.impl.fd) + return uintptr((^_File)(f.impl).fd) } _destroy :: proc(f: ^File) -> Error { - if f == nil { + if f == nil || f.impl == nil { return nil } - a := f.impl.allocator - free(f.impl.wname, a) - delete(f.impl.name, a) - free(f, a) + _f := (^_File)(f.impl) + a := _f.allocator + free(_f.wname, a) + delete(_f.name, a) + free(_f, a) return nil } _close :: proc(f: ^File) -> Error { - if f == nil { + if f == nil || f.impl == nil { return nil } - if !win32.CloseHandle(win32.HANDLE(f.impl.fd)) { + if !win32.CloseHandle(win32.HANDLE((^_File)(f.impl).fd)) { return .Closed } return _destroy(f) } _name :: proc(f: ^File) -> string { - return f.impl.name if f != nil else "" + return (^_File)(f.impl).name if f != nil && f.impl != nil else "" } _seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { @@ -195,11 +198,13 @@ _seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Er if handle == win32.INVALID_HANDLE { return 0, .Invalid_File } - if f.impl.kind == .Pipe { + impl := (^_File)(f.impl) + + if impl.kind == .Pipe { return 0, .Invalid_File } - sync.guard(&f.impl.rw_mutex) + sync.guard(&impl.rw_mutex) w: u32 switch whence { @@ -274,12 +279,13 @@ _read :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { total_read: int length := len(p) - sync.shared_guard(&f.impl.rw_mutex) // multiple readers + impl := (^_File)(f.impl) + sync.shared_guard(&impl.rw_mutex) // multiple readers - if sync.guard(&f.impl.p_mutex) { + if sync.guard(&impl.p_mutex) { to_read := min(win32.DWORD(length), MAX_RW) ok: win32.BOOL - if f.impl.kind == .Console { + if impl.kind == .Console { n, cerr := read_console(handle, p[total_read:][:to_read]) total_read += n if cerr != nil { @@ -326,7 +332,8 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { return } - sync.guard(&f.impl.p_mutex) + impl := (^_File)(f.impl) + sync.guard(&impl.p_mutex) p, offset := p, offset for len(p) > 0 { @@ -349,7 +356,8 @@ _write :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { handle := _handle(f) - sync.guard(&f.impl.rw_mutex) + impl := (^_File)(f.impl) + sync.guard(&impl.rw_mutex) for total_write < length { remaining := length - total_write to_write := win32.DWORD(min(i32(remaining), MAX_RW)) @@ -390,7 +398,8 @@ _write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { return } - sync.guard(&f.impl.p_mutex) + impl := (^_File)(f.impl) + sync.guard(&impl.p_mutex) p, offset := p, offset for len(p) > 0 { m := pwrite(f, p, offset) or_return @@ -403,7 +412,8 @@ _write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { _file_size :: proc(f: ^File) -> (n: i64, err: Error) { length: win32.LARGE_INTEGER - if f.impl.kind == .Pipe { + impl := (^_File)(f.impl) + if impl.kind == .Pipe { return 0, .No_Size } handle := _handle(f) @@ -428,7 +438,7 @@ _flush :: proc(f: ^File) -> Error { } _truncate :: proc(f: ^File, size: i64) -> Error { - if f == nil { + if f == nil || f.impl == nil { return nil } curr_off := seek(f, 0, .Current) or_return @@ -615,17 +625,18 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er _fchdir :: proc(f: ^File) -> Error { - if f == nil { + if f == nil || f.impl == nil { return nil } - if !win32.SetCurrentDirectoryW(f.impl.wname) { + impl := (^_File)(f.impl) + if !win32.SetCurrentDirectoryW(impl.wname) { return _get_platform_error() } return nil } _fchmod :: proc(f: ^File, mode: File_Mode) -> Error { - if f == nil { + if f == nil || f.impl == nil { return nil } d: win32.BY_HANDLE_FILE_INFORMATION @@ -680,7 +691,7 @@ _chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { return _fchtimes(f, atime, mtime) } _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { - if f == nil { + if f == nil || f.impl == nil { return nil } d: win32.BY_HANDLE_FILE_INFORMATION diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index c0b3088b4..a6df4e67a 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -7,7 +7,8 @@ import "core:sys/linux" import "core:path/filepath" _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { - return _fstat_internal(f.impl.fd, allocator) + impl := (^_File)(f.impl) + return _fstat_internal(impl.fd, allocator) } _fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (File_Info, Error) { diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index 03ad2052f..5f7daa062 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -7,7 +7,7 @@ import "core:strings" import win32 "core:sys/windows" _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { - if f == nil || f.impl.fd == nil { + if f == nil || (^_File)(f.impl).fd == nil { return {}, nil } @@ -122,7 +122,7 @@ _cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (string, Error) { - if f == nil || f.impl.fd == nil { + if f == nil || (^_File)(f.impl).fd == nil { return "", nil } h := _handle(f) @@ -138,7 +138,7 @@ _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (strin } _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { - if f == nil || f.impl.fd == nil { + if f == nil || (^_File)(f.impl).fd == nil { return nil, nil } h := _handle(f) From 5de6016e7ff1d959c5deed3b878a75e5e078b5b2 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 15:26:59 +0100 Subject: [PATCH 040/198] Clean up `os2.File.impl` usage --- core/os/os2/file_linux.odin | 90 +++++++++++------------ core/os/os2/file_windows.odin | 132 +++++++++++++++++----------------- core/os/os2/stat_linux.odin | 2 +- core/os/os2/stat_windows.odin | 6 +- 4 files changed, 111 insertions(+), 119 deletions(-) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index d2196b97b..482c9b5b4 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -6,15 +6,15 @@ import "core:time" import "base:runtime" import "core:sys/linux" -_File :: struct { - using file: File, +File_Impl :: struct { + file: File, name: string, fd: linux.Fd, allocator: runtime.Allocator, } _stdin := File{ - impl = &_File{ + impl = &File_Impl{ name = "/proc/self/fd/0", fd = 0, allocator = _file_allocator(), @@ -25,7 +25,7 @@ _stdin := File{ fstat = _fstat, } _stdout := File{ - impl = &_File{ + impl = &File_Impl{ name = "/proc/self/fd/1", fd = 1, allocator = _file_allocator(), @@ -36,7 +36,7 @@ _stdout := File{ fstat = _fstat, } _stderr := File{ - impl = &_File{ + impl = &File_Impl{ name = "/proc/self/fd/2", fd = 2, allocator = _file_allocator(), @@ -93,35 +93,35 @@ _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, er } _new_file :: proc(fd: uintptr, _: string = "") -> ^File { - impl := new(_File, file_allocator()) + impl := new(File_Impl, file_allocator()) + impl.file.impl = impl impl.fd = linux.Fd(fd) impl.allocator = file_allocator() impl.name = _get_full_path(impl.fd, impl.allocator) impl.file.stream = { - data = &impl.file, + data = impl, procedure = _file_stream_proc, } - impl.fstat = _fstat - return impl + impl.file.fstat = _fstat + return &impl.file } -_destroy :: proc(f: ^File) -> Error { - if f == nil || f.impl == nil { +_destroy :: proc(f: ^File_Impl) -> Error { + if f == nil { return nil } - impl := (^_File)(f.impl) - delete(impl.name, impl.allocator) - free(f, impl.allocator) + a := f.allocator + delete(f.name, a) + free(f, a) return nil } -_close :: proc(f: ^File) -> Error { - if f == nil || f.impl == nil { +_close :: proc(f: ^File_Impl) -> Error { + if f == nil{ return nil } - impl := (^_File)(f.impl) - errno := linux.close(impl.fd) + errno := linux.close(f.fd) if errno == .EBADF { // avoid possible double free return _get_platform_error(errno) } @@ -133,41 +133,38 @@ _fd :: proc(f: ^File) -> uintptr { if f == nil || f.impl == nil { return ~uintptr(0) } - impl := (^_File)(f.impl) + impl := (^File_Impl)(f.impl) return uintptr(impl.fd) } _name :: proc(f: ^File) -> string { - return (^_File)(f.impl).name if f != nil && f.impl != nil else "" + return (^File_Impl)(f.impl).name if f != nil && f.impl != nil else "" } -_seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { - impl := (^_File)(f.impl) - n, errno := linux.lseek(impl.fd, offset, linux.Seek_Whence(whence)) +_seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { + n, errno := linux.lseek(f.fd, offset, linux.Seek_Whence(whence)) if errno != .NONE { return -1, _get_platform_error(errno) } return n, nil } -_read :: proc(f: ^File, p: []byte) -> (i64, Error) { +_read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { if len(p) == 0 { return 0, nil } - impl := (^_File)(f.impl) - n, errno := linux.read(impl.fd, p[:]) + n, errno := linux.read(f.fd, p[:]) if errno != .NONE { return -1, _get_platform_error(errno) } return i64(n), n == 0 ? io.Error.EOF : nil } -_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (i64, Error) { +_read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { if offset < 0 { return 0, .Invalid_Offset } - impl := (^_File)(f.impl) - n, errno := linux.pread(impl.fd, p[:], offset) + n, errno := linux.pread(f.fd, p[:], offset) if errno != .NONE { return -1, _get_platform_error(errno) } @@ -177,35 +174,31 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (i64, Error) { return i64(n), nil } -_write :: proc(f: ^File, p: []byte) -> (i64, Error) { +_write :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { if len(p) == 0 { return 0, nil } - impl := (^_File)(f.impl) - n, errno := linux.write(impl.fd, p[:]) + n, errno := linux.write(f.fd, p[:]) if errno != .NONE { return -1, _get_platform_error(errno) } return i64(n), nil } -_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (i64, Error) { +_write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { if offset < 0 { return 0, .Invalid_Offset } - - impl := (^_File)(f.impl) - n, errno := linux.pwrite(impl.fd, p[:], offset) + n, errno := linux.pwrite(f.fd, p[:], offset) if errno != .NONE { return -1, _get_platform_error(errno) } return i64(n), nil } -_file_size :: proc(f: ^File) -> (n: i64, err: Error) { +_file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) { s: linux.Stat = --- - impl := (^_File)(f.impl) - errno := linux.fstat(impl.fd, &s) + errno := linux.fstat(f.fd, &s) if errno != .NONE { return -1, _get_platform_error(errno) } @@ -213,17 +206,16 @@ _file_size :: proc(f: ^File) -> (n: i64, err: Error) { } _sync :: proc(f: ^File) -> Error { - impl := (^_File)(f.impl) + impl := (^File_Impl)(f.impl) return _get_platform_error(linux.fsync(impl.fd)) } -_flush :: proc(f: ^File) -> Error { - impl := (^_File)(f.impl) - return _get_platform_error(linux.fsync(impl.fd)) +_flush :: proc(f: ^File_Impl) -> Error { + return _get_platform_error(linux.fsync(f.fd)) } _truncate :: proc(f: ^File, size: i64) -> Error { - impl := (^_File)(f.impl) + impl := (^File_Impl)(f.impl) return _get_platform_error(linux.ftruncate(impl.fd, size)) } @@ -300,7 +292,7 @@ _chdir :: proc(name: string) -> Error { } _fchdir :: proc(f: ^File) -> Error { - impl := (^_File)(f.impl) + impl := (^File_Impl)(f.impl) return _get_platform_error(linux.fchdir(impl.fd)) } @@ -311,7 +303,7 @@ _chmod :: proc(name: string, mode: File_Mode) -> Error { } _fchmod :: proc(f: ^File, mode: File_Mode) -> Error { - impl := (^_File)(f.impl) + impl := (^File_Impl)(f.impl) return _get_platform_error(linux.fchmod(impl.fd, transmute(linux.Mode)(u32(mode)))) } @@ -331,7 +323,7 @@ _lchown :: proc(name: string, uid, gid: int) -> Error { // NOTE: will throw error without super user priviledges _fchown :: proc(f: ^File, uid, gid: int) -> Error { - impl := (^_File)(f.impl) + impl := (^File_Impl)(f.impl) return _get_platform_error(linux.fchown(impl.fd, linux.Uid(uid), linux.Gid(gid))) } @@ -362,7 +354,7 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { uint(mtime._nsec) % uint(time.Second), }, } - impl := (^_File)(f.impl) + impl := (^File_Impl)(f.impl) return _get_platform_error(linux.utimensat(impl.fd, nil, ×[0], nil)) } @@ -455,7 +447,7 @@ _read_entire_pseudo_file_cstring :: proc(name: cstring, allocator: runtime.Alloc @(private="package") _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { - f := (^File)(stream_data) + f := (^File_Impl)(stream_data) ferr: Error switch mode { case .Read: diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 0ccea7f96..b11e7745f 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -17,19 +17,19 @@ _ERROR_BAD_NETPATH :: 53 MAX_RW :: 1<<30 -_File_Kind :: enum u8 { +File_Impl_Kind :: enum u8 { File, Console, Pipe, } -_File :: struct { - using file: File, +File_Impl :: struct { + file: File, fd: rawptr, name: string, wname: win32.wstring, - kind: _File_Kind, + kind: File_Impl_Kind, allocator: runtime.Allocator, @@ -132,79 +132,81 @@ _new_file :: proc(handle: uintptr, name: string) -> ^File { if handle == INVALID_HANDLE { return nil } - f := new(_File, file_allocator()) + impl := new(File_Impl, file_allocator()) + impl.file.impl = impl - f.allocator = file_allocator() - f.fd = rawptr(handle) - f.name, _ = clone_string(name, f.allocator) - f.wname = win32.utf8_to_wstring(name, f.allocator) + impl.allocator = file_allocator() + impl.fd = rawptr(handle) + impl.name, _ = clone_string(name, impl.allocator) + impl.wname = win32.utf8_to_wstring(name, impl.allocator) - handle := _handle(f) - kind := _File_Kind.File + handle := _handle(&impl.file) + kind := File_Impl_Kind.File if m: u32; win32.GetConsoleMode(handle, &m) { kind = .Console } if win32.GetFileType(handle) == win32.FILE_TYPE_PIPE { kind = .Pipe } - f.kind = kind + impl.kind = kind - f.stream = { - data = f, + impl.file.stream = { + data = impl, procedure = _file_stream_proc, } - f.fstat = _fstat + impl.file.fstat = _fstat - return f + return &impl.file } _fd :: proc(f: ^File) -> uintptr { if f == nil || f.impl == nil { return INVALID_HANDLE } - return uintptr((^_File)(f.impl).fd) + return uintptr((^File_Impl)(f.impl).fd) } -_destroy :: proc(f: ^File) -> Error { - if f == nil || f.impl == nil { +_destroy :: proc(f: ^File_Impl) -> Error { + if f == nil { return nil } - _f := (^_File)(f.impl) - a := _f.allocator - free(_f.wname, a) - delete(_f.name, a) - free(_f, a) + a := f.allocator + err0 := free(f.wname, a) + err1 := delete(f.name, a) + err2 := free(f, a) + err0 or_return + err1 or_return + err2 or_return return nil } -_close :: proc(f: ^File) -> Error { - if f == nil || f.impl == nil { +_close :: proc(f: ^File_Impl) -> Error { + if f == nil { return nil } - if !win32.CloseHandle(win32.HANDLE((^_File)(f.impl).fd)) { + if !win32.CloseHandle(win32.HANDLE(f.fd)) { return .Closed } return _destroy(f) } _name :: proc(f: ^File) -> string { - return (^_File)(f.impl).name if f != nil && f.impl != nil else "" + return (^File_Impl)(f.impl).name if f != nil && f.impl != nil else "" } -_seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { - handle := _handle(f) +_seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Error) { + handle := _handle(&f.file) if handle == win32.INVALID_HANDLE { return 0, .Invalid_File } - impl := (^_File)(f.impl) - if impl.kind == .Pipe { + if f.kind == .Pipe { return 0, .Invalid_File } - sync.guard(&impl.rw_mutex) + sync.guard(&f.rw_mutex) w: u32 switch whence { @@ -222,7 +224,7 @@ _seek :: proc(f: ^File, offset: i64, whence: io.Seek_From) -> (ret: i64, err: Er return i64(hi)<<32 + i64(dw_ptr), nil } -_read :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { +_read :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { read_console :: proc(handle: win32.HANDLE, b: []byte) -> (n: int, err: Error) { if len(b) == 0 { return 0, nil @@ -273,19 +275,18 @@ _read :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { return } - handle := _handle(f) + handle := _handle(&f.file) single_read_length: win32.DWORD total_read: int length := len(p) - impl := (^_File)(f.impl) - sync.shared_guard(&impl.rw_mutex) // multiple readers + sync.shared_guard(&f.rw_mutex) // multiple readers - if sync.guard(&impl.p_mutex) { + if sync.guard(&f.p_mutex) { to_read := min(win32.DWORD(length), MAX_RW) ok: win32.BOOL - if impl.kind == .Console { + if f.kind == .Console { n, cerr := read_console(handle, p[total_read:][:to_read]) total_read += n if cerr != nil { @@ -305,15 +306,15 @@ _read :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { return i64(total_read), err } -_read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { - pread :: proc(f: ^File, data: []byte, offset: i64) -> (n: i64, err: Error) { +_read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error) { + pread :: proc(f: ^File_Impl, data: []byte, offset: i64) -> (n: i64, err: Error) { buf := data if len(buf) > MAX_RW { buf = buf[:MAX_RW] } - curr_offset := seek(f, offset, .Current) or_return - defer seek(f, curr_offset, .Start) + curr_offset := _seek(f, offset, .Current) or_return + defer _seek(f, curr_offset, .Start) o := win32.OVERLAPPED{ OffsetHigh = u32(offset>>32), @@ -322,7 +323,7 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { // TODO(bill): Determine the correct behaviour for consoles - h := _handle(f) + h := _handle(&f.file) done: win32.DWORD if !win32.ReadFile(h, raw_data(buf), u32(len(buf)), &done, &o) { err = _get_platform_error() @@ -332,8 +333,7 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { return } - impl := (^_File)(f.impl) - sync.guard(&impl.p_mutex) + sync.guard(&f.p_mutex) p, offset := p, offset for len(p) > 0 { @@ -345,7 +345,7 @@ _read_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { return } -_write :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { +_write :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { if len(p) == 0 { return } @@ -354,10 +354,9 @@ _write :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { total_write: i64 length := i64(len(p)) - handle := _handle(f) + handle := _handle(&f.file) - impl := (^_File)(f.impl) - sync.guard(&impl.rw_mutex) + sync.guard(&f.rw_mutex) for total_write < length { remaining := length - total_write to_write := win32.DWORD(min(i32(remaining), MAX_RW)) @@ -373,22 +372,22 @@ _write :: proc(f: ^File, p: []byte) -> (n: i64, err: Error) { return i64(total_write), nil } -_write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { - pwrite :: proc(f: ^File, data: []byte, offset: i64) -> (n: i64, err: Error) { +_write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (n: i64, err: Error) { + pwrite :: proc(f: ^File_Impl, data: []byte, offset: i64) -> (n: i64, err: Error) { buf := data if len(buf) > MAX_RW { buf = buf[:MAX_RW] } - curr_offset := seek(f, offset, .Current) or_return - defer seek(f, curr_offset, .Start) + curr_offset := _seek(f, offset, .Current) or_return + defer _seek(f, curr_offset, .Start) o := win32.OVERLAPPED{ OffsetHigh = u32(offset>>32), Offset = u32(offset), } - h := _handle(f) + h := _handle(&f.file) done: win32.DWORD if !win32.WriteFile(h, raw_data(buf), u32(len(buf)), &done, &o) { err = _get_platform_error() @@ -398,8 +397,7 @@ _write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { return } - impl := (^_File)(f.impl) - sync.guard(&impl.p_mutex) + sync.guard(&f.p_mutex) p, offset := p, offset for len(p) > 0 { m := pwrite(f, p, offset) or_return @@ -410,13 +408,12 @@ _write_at :: proc(f: ^File, p: []byte, offset: i64) -> (n: i64, err: Error) { return } -_file_size :: proc(f: ^File) -> (n: i64, err: Error) { +_file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) { length: win32.LARGE_INTEGER - impl := (^_File)(f.impl) - if impl.kind == .Pipe { + if f.kind == .Pipe { return 0, .No_Size } - handle := _handle(f) + handle := _handle(&f.file) if !win32.GetFileSizeEx(handle, &length) { err = _get_platform_error() } @@ -426,11 +423,14 @@ _file_size :: proc(f: ^File) -> (n: i64, err: Error) { _sync :: proc(f: ^File) -> Error { - return _flush(f) + if f != nil && f.impl != nil { + return _flush((^File_Impl)(f.impl)) + } + return nil } -_flush :: proc(f: ^File) -> Error { - handle := _handle(f) +_flush :: proc(f: ^File_Impl) -> Error { + handle := _handle(&f.file) if !win32.FlushFileBuffers(handle) { return _get_platform_error() } @@ -628,7 +628,7 @@ _fchdir :: proc(f: ^File) -> Error { if f == nil || f.impl == nil { return nil } - impl := (^_File)(f.impl) + impl := (^File_Impl)(f.impl) if !win32.SetCurrentDirectoryW(impl.wname) { return _get_platform_error() } @@ -747,7 +747,7 @@ _is_dir :: proc(path: string) -> bool { @(private="package") _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { - f := (^File)(stream_data) + f := (^File_Impl)(stream_data) ferr: Error switch mode { case .Read: diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index a6df4e67a..ee4011f96 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -7,7 +7,7 @@ import "core:sys/linux" import "core:path/filepath" _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { - impl := (^_File)(f.impl) + impl := (^File_Impl)(f.impl) return _fstat_internal(impl.fd, allocator) } diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index 5f7daa062..0a18433c1 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -7,7 +7,7 @@ import "core:strings" import win32 "core:sys/windows" _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { - if f == nil || (^_File)(f.impl).fd == nil { + if f == nil || (^File_Impl)(f.impl).fd == nil { return {}, nil } @@ -122,7 +122,7 @@ _cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (string, Error) { - if f == nil || (^_File)(f.impl).fd == nil { + if f == nil { return "", nil } h := _handle(f) @@ -138,7 +138,7 @@ _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (strin } _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { - if f == nil || (^_File)(f.impl).fd == nil { + if f == nil { return nil, nil } h := _handle(f) From 556355ef054bc5139a24f8b5dbd210049e908c95 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 15:30:40 +0100 Subject: [PATCH 041/198] Disallow global use of target specific procedure calls --- src/check_expr.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 4edd34990..3b1f86114 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -7904,12 +7904,15 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c // NOTE: Due to restrictions in LLVM you can not inline calls with a superset of features. if (is_call_inlined) { - GB_ASSERT(c->curr_proc_decl); - GB_ASSERT(c->curr_proc_decl->entity); - GB_ASSERT(c->curr_proc_decl->entity->type->kind == Type_Proc); - String scope_features = c->curr_proc_decl->entity->type->Proc.enable_target_feature; - if (!check_target_feature_is_superset_of(scope_features, pt->Proc.enable_target_feature, &invalid)) { - error(call, "Inlined procedure enables target feature '%.*s', this requires the calling procedure to at least enable the same feature", LIT(invalid)); + if (c->curr_proc_decl == nullptr) { + error(call, "Inlined procedure which enables target feature '%.*s' cannot be used at the global/file scope", LIT(invalid)); + } else { + GB_ASSERT(c->curr_proc_decl->entity); + GB_ASSERT(c->curr_proc_decl->entity->type->kind == Type_Proc); + String scope_features = c->curr_proc_decl->entity->type->Proc.enable_target_feature; + if (!check_target_feature_is_superset_of(scope_features, pt->Proc.enable_target_feature, &invalid)) { + error(call, "Inlined procedure enables target feature '%.*s', this requires the calling procedure to at least enable the same feature", LIT(invalid)); + } } } } From 11e2aa2d519a9522bc6a71abf4868216ed236c89 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 15:31:40 +0100 Subject: [PATCH 042/198] Improve error message --- 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 3b1f86114..82f64738f 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -7905,7 +7905,7 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c // NOTE: Due to restrictions in LLVM you can not inline calls with a superset of features. if (is_call_inlined) { if (c->curr_proc_decl == nullptr) { - error(call, "Inlined procedure which enables target feature '%.*s' cannot be used at the global/file scope", LIT(invalid)); + error(call, "Calling a '#force_inline' procedure that enables target features is not allowed at file scope"); } else { GB_ASSERT(c->curr_proc_decl->entity); GB_ASSERT(c->curr_proc_decl->entity->type->kind == Type_Proc); From a8673784180968f7552d26705582d6c938e97a5e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 15:55:26 +0100 Subject: [PATCH 043/198] Default to `-o:minimal` again --- src/build_settings.cpp | 6 +++++- src/llvm_backend.cpp | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 4d3e20a7a..32640d732 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1649,7 +1649,11 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta if (!bc->custom_optimization_level) { // NOTE(bill): when building with `-debug` but not specifying an optimization level // default to `-o:none` to improve the debug symbol generation by default - bc->optimization_level = -1; // -o:none + if (bc->ODIN_DEBUG) { + bc->optimization_level = -1; // -o:none + } else { + bc->optimization_level = 0; // -o:minimal + } } bc->optimization_level = gb_clamp(bc->optimization_level, -1, 3); diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 52661dfa7..ae46186ed 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1570,6 +1570,7 @@ gb_internal WORKER_TASK_PROC(lb_llvm_module_pass_worker_proc) { switch (build_context.optimization_level) { case -1: + array_add(&passes, "function(annotation-remarks)"); break; case 0: array_add(&passes, "always-inline"); From 6feace23511eaf5f9fa981e8fbfd3087331a0cc4 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 16:05:07 +0100 Subject: [PATCH 044/198] Add pseudo flag `-fast-build` --- src/main.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index e6a0aecf0..96225d14d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -399,6 +399,8 @@ enum BuildFlagKind { BuildFlag_Sanitize, + BuildFlag_FastBuild, + #if defined(GB_SYSTEM_WINDOWS) BuildFlag_IgnoreVsSearch, BuildFlag_ResourceFile, @@ -605,6 +607,9 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_Sanitize, str_lit("sanitize"), BuildFlagParam_String, Command__does_build, true); + add_flag(&build_flags, BuildFlag_FastBuild, str_lit("fast-build"), BuildFlagParam_String, Command__does_build); + + #if defined(GB_SYSTEM_WINDOWS) add_flag(&build_flags, BuildFlag_IgnoreVsSearch, str_lit("ignore-vs-search"), BuildFlagParam_None, Command__does_build); add_flag(&build_flags, BuildFlag_ResourceFile, str_lit("resource"), BuildFlagParam_String, Command__does_build); @@ -1441,6 +1446,13 @@ gb_internal bool parse_build_flags(Array args) { } break; + + case BuildFlag_FastBuild: + build_context.custom_optimization_level = true; + build_context.optimization_level = -1; + build_context.use_separate_modules = true; + break; + #if defined(GB_SYSTEM_WINDOWS) case BuildFlag_IgnoreVsSearch: { GB_ASSERT(value.kind == ExactValue_Invalid); From e7d37607ef9ce54a80d83230150874b71d628d6d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 16:05:47 +0100 Subject: [PATCH 045/198] Fix parameter to none --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 96225d14d..388184be9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -607,7 +607,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_Sanitize, str_lit("sanitize"), BuildFlagParam_String, Command__does_build, true); - add_flag(&build_flags, BuildFlag_FastBuild, str_lit("fast-build"), BuildFlagParam_String, Command__does_build); + add_flag(&build_flags, BuildFlag_FastBuild, str_lit("fast-build"), BuildFlagParam_None, Command__does_build); #if defined(GB_SYSTEM_WINDOWS) From 63276a85badc0a4ef2f5482592d33f6cf989de15 Mon Sep 17 00:00:00 2001 From: NicknEma <62065135+NicknEma@users.noreply.github.com> Date: Sun, 14 Jul 2024 18:18:12 +0200 Subject: [PATCH 046/198] Fixed grammar and expanded info Fixed points made by Jeroen. --- core/container/intrusive/list/doc.odin | 7 +++++++ core/container/intrusive/list/intrusive_list.odin | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/core/container/intrusive/list/doc.odin b/core/container/intrusive/list/doc.odin index ff219e15f..1a5a12f49 100644 --- a/core/container/intrusive/list/doc.odin +++ b/core/container/intrusive/list/doc.odin @@ -8,6 +8,13 @@ An intrusive container requires a `Node` to be embedded in your own structure, l value: string, } +Embedding the members of a `list.Node` in your structure with the `using` keyword is also allowed: + + My_String :: struct { + using node: list.Node, + value: string, + } + Here is a full example: package test diff --git a/core/container/intrusive/list/intrusive_list.odin b/core/container/intrusive/list/intrusive_list.odin index e0df3ba72..5b29efb22 100644 --- a/core/container/intrusive/list/intrusive_list.odin +++ b/core/container/intrusive/list/intrusive_list.odin @@ -82,7 +82,7 @@ remove :: proc "contextless" (list: ^List, node: ^Node) { } } /* -Removes from the given list all elements that satisfy a condition. +Removes from the given list all elements that satisfy a condition with O(N) time complexity. **Inputs** - list: The container list @@ -109,7 +109,7 @@ remove_by_proc :: proc(list: ^List, to_erase: proc(^Node) -> bool) { } } /* -Removes from the given list all elements that satisfy a condition. +Removes from the given list all elements that satisfy a condition with O(N) time complexity. **Inputs** - list: The container list @@ -137,7 +137,7 @@ remove_by_proc_contextless :: proc(list: ^List, to_erase: proc "contextless" (^N } /* -Checks if the given list does not contain any element. +Checks whether the given list does not contain any element. **Inputs** - list: The container list From b38237e8f0872fd8979a729553338e01d04dc3e8 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 14 Jul 2024 14:59:00 -0400 Subject: [PATCH 047/198] Fix compiler crash when switching on no value --- src/check_stmt.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index f4d3bd6b8..74397828d 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -1060,6 +1060,9 @@ gb_internal void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags if (ss->tag != nullptr) { check_expr(ctx, &x, ss->tag); check_assignment(ctx, &x, nullptr, str_lit("switch expression")); + if (x.type == nullptr) { + return; + } } else { x.mode = Addressing_Constant; x.type = t_bool; From 2495f1c39aa19f872a3f42b5ccae04db7ec20822 Mon Sep 17 00:00:00 2001 From: flysand7 Date: Wed, 10 Jul 2024 04:05:32 +1100 Subject: [PATCH 048/198] [os2/process]: Fill in basic functions --- core/os/os2/process.odin | 86 +++++++++++++++++++++++++++++--- core/os/os2/process_windows.odin | 46 +++++++++++++++++ 2 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 core/os/os2/process_windows.odin diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index 862434b7b..87199ca7a 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -3,35 +3,105 @@ package os2 import "core:sync" import "core:time" import "base:runtime" +import "core:strings" -args: []string +/* + Arguments to the current process. + See `get_args()` for description of the slice. +*/ +args := get_args() + +/* + Obtain the process argument array from the OS. + + Slice, containing arguments to the current process. Each element of the + slice contains a single argument. The first element of the slice would + typically is the path to the currently running executable. +*/ +get_args :: proc() -> []string { + args := make([]string, len(runtime.args__), allocator = context.allocator) + for rt_arg, i in runtime.args__ { + args[i] = cast(string) rt_arg + } + return args[:] +} + +/* + Exit the current process. +*/ exit :: proc "contextless" (code: int) -> ! { - runtime.trap() + _exit(code) } +/* + Obtain the UID of the current process. + + **Note(windows)**: Windows doesn't follow the posix permissions model, so + the function simply returns -1. +*/ get_uid :: proc() -> int { - return -1 + return _get_uid() } +/* + Obtain the effective UID of the current process. + + The effective UID is typically the same as the UID of the process. In case + the process was run by a user with elevated permissions, the process may + lower the privilege to perform some tasks without privilege. In these cases + the real UID of the process and the effective UID are different. + + **Note(windows)**: Windows doesn't follow the posix permissions model, so + the function simply returns -1. +*/ get_euid :: proc() -> int { - return -1 + return _get_euid() } +/* + Obtain the GID of the current process. + + **Note(windows)**: Windows doesn't follow the posix permissions model, so + the function simply returns -1. +*/ get_gid :: proc() -> int { - return -1 + return _get_gid() } +/* + Obtain the effective GID of the current process. + + The effective GID is typically the same as the GID of the process. In case + the process was run by a user with elevated permissions, the process may + lower the privilege to perform some tasks without privilege. In these cases + the real GID of the process and the effective GID are different. + + **Note(windows)**: Windows doesn't follow the posix permissions model, so + the function simply returns -1. +*/ get_egid :: proc() -> int { - return -1 + return _get_egid() } +/* + Obtain the ID of the current process. +*/ get_pid :: proc() -> int { - return -1 + return _get_pid() } +/* + Obtain the ID of the parent process. + + **Note(windows)**: Windows does not mantain strong relationships between + parent and child processes. This function returns the ID of the process + that has created the current process. In case the parent has died, the ID + returned by this function can identify a non-existent or a different + process. +*/ get_ppid :: proc() -> int { - return -1 + return _get_ppid() } diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin new file mode 100644 index 000000000..ff99853a3 --- /dev/null +++ b/core/os/os2/process_windows.odin @@ -0,0 +1,46 @@ +//+build windows +package os2 + +import "core:sys/windows" + +_exit :: proc "contextless" (code: int) -> ! { + windows.ExitProcess(u32(code)) +} + +_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 cast(int) windows.GetCurrentProcessId() +} + +_get_ppid :: proc() -> int { + our_pid := windows.GetCurrentProcessId() + snap := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0) + if snap == windows.INVALID_HANDLE_VALUE { + return -1 + } + defer windows.CloseHandle(snap) + entry := windows.PROCESSENTRY32W { dwSize = size_of(windows.PROCESSENTRY32W) } + status := windows.Process32FirstW(snap, &entry) + for status { + if entry.th32ProcessID == our_pid { + return cast(int) entry.th32ParentProcessID + } + status = windows.Process32NextW(snap, &entry) + } + return -1 +} From 6fab055f435d4a43581ceadde0496249a280c21d Mon Sep 17 00:00:00 2001 From: flysand7 Date: Wed, 10 Jul 2024 04:25:44 +1100 Subject: [PATCH 049/198] [os2/process]: Add process list function --- core/os/os2/process.odin | 7 +++++++ core/os/os2/process_windows.odin | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index 87199ca7a..1555d555b 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -104,6 +104,13 @@ get_ppid :: proc() -> int { return _get_ppid() } +/* + Obtain ID's of all processes running in the system. +*/ +process_list :: proc(allocator: runtime.Allocator) -> ([]int, Error) { + return _process_list(allocator) +} + Process :: struct { pid: int, diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index ff99853a3..6e4bd4cac 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -2,6 +2,7 @@ package os2 import "core:sys/windows" +import "base:runtime" _exit :: proc "contextless" (code: int) -> ! { windows.ExitProcess(u32(code)) @@ -44,3 +45,18 @@ _get_ppid :: proc() -> int { } return -1 } + +_process_list :: proc(allocator: runtime.Allocator) -> ([]int, Error) { + pid_list := make([dynamic]int, allocator) + snap := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0) + if snap == windows.INVALID_HANDLE_VALUE { + return pid_list[:], _get_platform_error() + } + entry := windows.PROCESSENTRY32W { dwSize = size_of(windows.PROCESSENTRY32W) } + status := windows.Process32FirstW(snap, &entry) + for status { + append(&pid_list, cast(int) entry.th32ProcessID) + status = windows.Process32NextW(snap, &entry) + } + return pid_list[:], nil +} From 56d55e4a86ec51d137531a8bd3ea52ddad5c24af Mon Sep 17 00:00:00 2001 From: flysand7 Date: Wed, 10 Jul 2024 05:29:41 +1100 Subject: [PATCH 050/198] Rebase master --- core/os/os2/process.odin | 23 ++++++++++++++++++----- core/os/os2/process_windows.odin | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index 1555d555b..897f7705f 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -111,12 +111,25 @@ process_list :: proc(allocator: runtime.Allocator) -> ([]int, Error) { return _process_list(allocator) } - +/* + Handle to a process. +*/ Process :: struct { - pid: int, - handle: uintptr, - is_done: b32, - signal_mutex: sync.RW_Mutex, + handle: _Process_Handle, +} + +/* + Obtain a process handle. +*/ +process_open :: proc(pid: int) -> (Process, Error) { + return _process_open(pid) +} + +/* + Close a process handle +*/ +process_close :: proc(process: Process) -> (Error) { + return _process_close(process) } diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index 6e4bd4cac..8de8c8347 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -4,6 +4,8 @@ package os2 import "core:sys/windows" import "base:runtime" +_Process_Handle :: windows.HANDLE + _exit :: proc "contextless" (code: int) -> ! { windows.ExitProcess(u32(code)) } @@ -60,3 +62,19 @@ _process_list :: proc(allocator: runtime.Allocator) -> ([]int, Error) { } return pid_list[:], nil } + +_process_open :: proc(pid: int) -> (Process, Error) { + handle := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, cast(u32) pid) + if handle == windows.INVALID_HANDLE_VALUE { + return {}, _get_platform_error() + } + return Process { + handle = handle, + }, nil +} + +_process_close :: proc(process: Process) -> (Error) { + if !windows.CloseHandle(process.handle) { + return _get_platform_error() + } +} From 6387cd2c2479fd91b1eae9491c575f093ecb37b6 Mon Sep 17 00:00:00 2001 From: flysand7 Date: Fri, 12 Jul 2024 18:03:06 +1100 Subject: [PATCH 051/198] [os2/process] Added process_info() procedure --- core/os/os2/process.odin | 158 +++++++--- core/os/os2/process_windows.odin | 494 ++++++++++++++++++++++++++++++- 2 files changed, 594 insertions(+), 58 deletions(-) diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index 897f7705f..4a0481b0f 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -112,80 +112,142 @@ process_list :: proc(allocator: runtime.Allocator) -> ([]int, Error) { } /* - Handle to a process. + Bit set specifying which fields of the `Process_Info` struct need to be + obtained by the `process_info()` procedure. Each bit corresponds to a + field in the `Process_Info` struct. */ -Process :: struct { - handle: _Process_Handle, +Process_Info_Fields :: bit_set[Process_Info_Field] +Process_Info_Field :: enum { + Executable_Path, + PPid, + Priority, + Command_Line, + Command_Args, + Environment, + Username, + CWD, } /* - Obtain a process handle. + Contains information about the process as obtained by the `process_info()` + procedure. */ -process_open :: proc(pid: int) -> (Process, Error) { - return _process_open(pid) +Process_Info :: struct { + fields: Process_Info_Fields, + // The ID of the process. + pid: int, + // The ID of the parent process. + ppid: int, + // The process priority. + priority: int, + // The path to the executable, which the process runs. + executable_path: string, + // The command line supplied to the process. + command_line: string, + // The arguments supplied to the process. + command_args: []string, + // The environment of the process. + environment: []string, + // The username of the user who started the process. + username: string, + // The current working directory of the process. + cwd: string, } /* - Close a process handle + Obtain information about a process. + + This procedure obtains an information, given by `selection` parameter of + a process given by `pid`. + + Use `free_process_info` to free memory allocated by this function. In case + the function returns an error all temporary allocations would be freed and + as such, calling `free_process_info()` is not needed. + + **Note**: The resulting information may or may not contain the + selected fields. Please check the `fields` field of the `Process_Info` + struct to see if the struct contains the desired fields **before** checking + the error return of this function. */ -process_close :: proc(process: Process) -> (Error) { - return _process_close(process) +process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { + return _process_info(pid, selection, allocator) } - -Process_Attributes :: struct { - dir: string, - env: []string, - files: []^File, - sys: ^Process_Attributes_OS_Specific, +current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { + return _current_process_info(selection, allocator) } -Process_Attributes_OS_Specific :: struct{} +/* + Free the information about the process. -Process_Error :: enum { - None, + This procedure frees the memory occupied by process info using the provided + allocator. The allocator needs to be the same allocator that was supplied + to the `process_info` function. +*/ +free_process_info :: proc(pi: Process_Info, allocator: runtime.Allocator) { + delete(pi.executable_path, allocator) + delete(pi.command_line, allocator) + delete(pi.command_args, allocator) + for s in pi.environment { + delete(s, allocator) + } + delete(pi.environment, allocator) + delete(pi.cwd, allocator) } -Process_State :: struct { - pid: int, - exit_code: int, - exited: bool, - success: bool, - system_time: time.Duration, - user_time: time.Duration, - sys: rawptr, -} +// Process_Attributes :: struct { +// dir: string, +// env: []string, +// files: []^File, +// sys: ^Process_Attributes_OS_Specific, +// } -Signal :: #type proc() +// Process_Attributes_OS_Specific :: struct{} -Kill: Signal = nil -Interrupt: Signal = nil +// 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 -} +// 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_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_release :: proc(p: ^Process) -> Process_Error { +// return .None +// } -process_kill :: 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_signal :: proc(p: ^Process, sig: Signal) -> Process_Error { +// return .None +// } -process_wait :: proc(p: ^Process) -> (Process_State, Process_Error) { - return {}, .None -} +// process_wait :: proc(p: ^Process) -> (Process_State, Process_Error) { +// return {}, .None +// } diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index 8de8c8347..d6d9f0866 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -2,6 +2,7 @@ package os2 import "core:sys/windows" +import "core:strings" import "base:runtime" _Process_Handle :: windows.HANDLE @@ -63,18 +64,491 @@ _process_list :: proc(allocator: runtime.Allocator) -> ([]int, Error) { return pid_list[:], nil } -_process_open :: proc(pid: int) -> (Process, Error) { - handle := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, cast(u32) pid) - if handle == windows.INVALID_HANDLE_VALUE { - return {}, _get_platform_error() +_process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + info.pid = pid + need_snapprocess := \ + .PPid in selection || + .Priority in selection + need_snapmodule := \ + .Executable_Path in selection + need_peb := \ + .Command_Line in selection || + .Environment in selection || + .CWD in selection + need_process_handle := need_peb || .Username in selection + // Data obtained from process snapshots + if need_snapprocess { + snap := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0) + if snap == windows.INVALID_HANDLE_VALUE { + return info, _get_platform_error() + } + defer windows.CloseHandle(snap) + entry := windows.PROCESSENTRY32W { dwSize = size_of(windows.PROCESSENTRY32W) } + status := windows.Process32FirstW(snap, &entry) + found := false + for status { + if u32(pid) == entry.th32ProcessID { + found = true + break + } + status = windows.Process32NextW(snap, &entry) + } + if !found { + err = General_Error.Not_Exist + return + } + if .PPid in selection { + info.fields |= {.PPid} + info.ppid = int(entry.th32ParentProcessID) + } + if .Priority in selection { + info.fields |= {.Priority} + info.priority = int(entry.pcPriClassBase) + } } - return Process { - handle = handle, - }, nil + // Note(flysand): Not sure which way it's better to get the executable path: + // via toolhelp snapshots or by reading other process' PEB memory. I have + // a slight suspicion that if both exe path and command line are desired, + // it's faster to just read both from PEB, but maybe the toolhelp snapshots + // are just better...? + if need_snapmodule { + snap := windows.CreateToolhelp32Snapshot( + windows.TH32CS_SNAPMODULE|windows.TH32CS_SNAPMODULE32, + u32(pid), + ) + if snap == windows.INVALID_HANDLE_VALUE { + err = _get_platform_error() + return + } + defer windows.CloseHandle(snap) + entry := windows.MODULEENTRY32W { dwSize = size_of(windows.MODULEENTRY32W) } + status := windows.Module32FirstW(snap, &entry) + if !status { + err = _get_platform_error() + return + } + exe_path: string + exe_path, err = windows.wstring_to_utf8(raw_data(entry.szExePath[:]), -1, allocator) + if err != nil { + return + } + info.fields |= {.Executable_Path} + info.executable_path = exe_path + } + defer if .Executable_Path in info.fields && err != nil { + delete(info.executable_path, allocator) + } + ph := windows.INVALID_HANDLE_VALUE + if need_process_handle { + ph = windows.OpenProcess( + windows.PROCESS_QUERY_LIMITED_INFORMATION | windows.PROCESS_VM_READ, + false, + u32(pid), + ) + if ph == windows.INVALID_HANDLE_VALUE { + err = _get_platform_error() + return + } + } + defer if ph != windows.INVALID_HANDLE_VALUE { + windows.CloseHandle(ph) + } + defer if .CWD in info.fields && err != nil { + delete(info.cwd, allocator) + } + defer if .Environment in info.fields && err != nil { + for s in info.environment { + delete(s, allocator) + } + delete(info.environment, allocator) + } + defer if .Command_Line in info.fields && err != nil { + delete(info.command_line, allocator) + } + if need_peb { + ntdll_lib := windows.LoadLibraryW(windows.L("ntdll.dll")) + if ntdll_lib == nil { + err = _get_platform_error() + return + } + defer windows.FreeLibrary(ntdll_lib) + NtQueryInformationProcess := cast(NtQueryInformationProcess_T) windows.GetProcAddress(ntdll_lib, "NtQueryInformationProcess") + if NtQueryInformationProcess == nil { + err = _get_platform_error() + return + } + process_info_size: u32 = --- + process_info: PROCESS_BASIC_INFORMATION = --- + status := NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size) + if status != 0 { + // TODO(flysand): There's probably a mismatch between NTSTATUS and + // windows userland error codes, I haven't checked. + err = Platform_Error(status) + return + } + if process_info.PebBaseAddress == nil { + // Not sure what the error is + err = General_Error.Unsupported + return + } + process_peb: PEB = --- + bytes_read: uint = --- + read_struct :: proc(h: windows.HANDLE, addr: rawptr, dest: ^$T, br: ^uint) -> windows.BOOL { + return windows.ReadProcessMemory(h, addr, dest, size_of(T), br) + } + read_slice :: proc(h: windows.HANDLE, addr: rawptr, dest: []$T, br: ^uint) -> windows.BOOL { + return windows.ReadProcessMemory(h, addr, raw_data(dest), len(dest)*size_of(T), br) + } + if !read_struct(ph, process_info.PebBaseAddress, &process_peb, &bytes_read) { + err = _get_platform_error() + return + } + process_params: RTL_USER_PROCESS_PARAMETERS = --- + if !read_struct(ph, process_peb.ProcessParameters, &process_params, &bytes_read) { + err = _get_platform_error() + return + } + if .Command_Line in selection { + TEMP_ALLOCATOR_GUARD() + cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) + if !read_slice(ph, process_params.CommandLine.Buffer, cmdline_w, &bytes_read) { + err = _get_platform_error() + return + } + cmdline, cmdline_err := windows.utf16_to_utf8(cmdline_w, allocator) + if cmdline_err != nil { + err = cmdline_err + return + } + info.fields |= {.Command_Line} + info.command_line = cmdline + } + if .Environment in selection { + TEMP_ALLOCATOR_GUARD() + env_len := process_params.EnvironmentSize / 2 + envs_w := make([]u16, env_len, temp_allocator()) + if !read_slice(ph, process_params.Environment, envs_w, &bytes_read) { + err = _get_platform_error() + return + } + envs, envs_err := _parse_environment_block(raw_data(envs_w), allocator) + if envs_err != nil { + err = envs_err + return + } + info.fields |= {.Environment} + info.environment = envs + } + if .CWD in selection { + TEMP_ALLOCATOR_GUARD() + cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) + if !read_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w, &bytes_read) { + err = _get_platform_error() + return + } + cwd, cwd_err := windows.utf16_to_utf8(cwd_w, allocator) + if cwd_err != nil { + err = cwd_err + return + } + info.fields |= {.CWD} + info.cwd = cwd + } + } + if .Username in selection { + username, username_err := _get_process_user(ph, allocator) + if username_err != nil { + err = username_err + return + } + info.fields |= {.Username} + info.username = username + } + err = nil + return } -_process_close :: proc(process: Process) -> (Error) { - if !windows.CloseHandle(process.handle) { - return _get_platform_error() +_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + info.pid = cast(int) windows.GetCurrentProcessId() + need_snapprocess := .PPid in selection || .Priority in selection + if need_snapprocess { + snap := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0) + if snap == windows.INVALID_HANDLE_VALUE { + return + } + defer windows.CloseHandle(snap) + entry := windows.PROCESSENTRY32W { dwSize = size_of(windows.PROCESSENTRY32W) } + status := windows.Process32FirstW(snap, &entry) + for status { + if entry.th32ProcessID == u32(info.pid) { + break + } + status = windows.Process32NextW(snap, &entry) + } + if entry.th32ProcessID != u32(info.pid) { + err = General_Error.Not_Exist + return + } + if .PPid in selection { + info.fields += {.PPid} + info.ppid = int(entry.th32ProcessID) + } + if .Priority in selection { + info.fields += {.Priority} + info.priority = int(entry.pcPriClassBase) + } } + if .Executable_Path in selection { + exe_filename_w: [256]u16 + path_len := windows.GetModuleFileNameW(nil, raw_data(exe_filename_w[:]), len(exe_filename_w)) + exe_filename, exe_filename_err := windows.utf16_to_utf8(exe_filename_w[:path_len], allocator) + if exe_filename_err != nil { + err = exe_filename_err + return + } + info.fields += {.Executable_Path} + info.executable_path = exe_filename + } + defer if .Executable_Path in selection && err != nil { + delete(info.executable_path, allocator) + } + if .Command_Line in selection { + command_line_w := windows.GetCommandLineW() + command_line, command_line_err := windows.wstring_to_utf8(command_line_w, -1, allocator) + if command_line_err != nil { + err = command_line_err + return + } + info.fields += {.Command_Line} + info.command_line = command_line + } + defer if .Command_Line in selection && err != nil { + delete(info.command_line, allocator) + } + if .Environment in selection { + env_block := windows.GetEnvironmentStringsW() + envs, envs_err := _parse_environment_block(env_block, allocator) + if envs_err != nil { + err = envs_err + return + } + info.fields += {.Environment} + info.environment = envs + } + defer if .Environment in selection && err != nil { + for s in info.environment { + delete(s, allocator) + } + delete(info.environment) + } + if .Username in selection { + process_handle := windows.GetCurrentProcess() + username, username_err := _get_process_user(process_handle, allocator) + if username_err != nil { + err = username_err + return + } + info.fields += {.Username} + info.username = username + } + defer if .Username in selection && err != nil { + delete(info.username) + } + err = nil + return } + + +@(private) +_get_process_user :: proc(process_handle: windows.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + token_handle: windows.HANDLE = --- + if !windows.OpenProcessToken(process_handle, windows.TOKEN_QUERY, &token_handle) { + err = _get_platform_error() + return + } + token_user_size: u32 = --- + if !windows.GetTokenInformation(token_handle, .TokenUser, nil, 0, &token_user_size) { + // Note(flysand): Make sure the buffer too small error comes out, and not any other error + err = _get_platform_error() + if v, ok := err.(Platform_Error); !ok || int(v) != 0x7a { + return + } + } + token_user := cast(^windows.TOKEN_USER) raw_data(make([]u8, token_user_size, temp_allocator())) + if !windows.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) { + err = _get_platform_error() + return + } + sid_type: windows.SID_NAME_USE = --- + username_w: [256]u16 = --- + domain_w: [256]u16 = --- + username_chrs: u32 = 256 + domain_chrs: u32 = 256 + if !windows.LookupAccountSidW(nil, token_user.User.Sid, &username_w[0], &username_chrs, &domain_w[0], &domain_chrs, &sid_type) { + err = _get_platform_error() + return + } + username, username_err := windows.utf16_to_utf8(username_w[:username_chrs], temp_allocator()) + if username_err != nil { + err = username_err + return + } + domain, domain_err := windows.utf16_to_utf8(domain_w[:domain_chrs], temp_allocator()) + if domain_err != nil { + err = domain_err + return + } + full_name, full_name_err := strings.concatenate([]string {domain, "\\", username}, allocator) + if full_name_err != nil { + err = full_name_err + return + } + return full_name, nil +} + +@(private) +_parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) -> ([]string, Error) { + zt_count := 0 + for idx := 0; true; idx += 1 { + if block[idx] == 0x0000 { + zt_count += 1 + } + if block[idx] == 0x0000 { + zt_count += 1 + break + } + } + // Note(flysand): Each string in the environment block is terminated + // by a NUL character. In addition, the environment block itself is + // terminated by a NUL character. So the number of strings in the + // environment block is the number of NUL character minus the + // block terminator. + env_count := zt_count - 1 + envs := make([]string, env_count, allocator) + env_idx := 0 + last_idx := 0 + idx := 0 + for block[idx] != 0x0000 { + for block[idx] != 0x0000 { + idx += 1 + } + env_w := block[last_idx:idx] + env, env_err := windows.utf16_to_utf8(env_w, allocator) + if env_err != nil { + return nil, env_err + } + envs[env_idx] = env + env_idx += 1 + idx += 1 + last_idx = idx + } + return envs, nil +} + + +@(private="file") +PROCESSINFOCLASS :: enum i32 { + ProcessBasicInformation = 0, + ProcessDebugPort = 7, + ProcessWow64Information = 26, + ProcessImageFileName = 27, + ProcessBreakOnTermination = 29, + ProcessTelemetryIdInformation = 64, + ProcessSubsystemInformation = 75, +} + +@(private="file") +NtQueryInformationProcess_T :: #type proc ( + ProcessHandle: windows.HANDLE, + ProcessInformationClass: PROCESSINFOCLASS, + ProcessInformation: rawptr, + ProcessInformationLength: u32, + ReturnLength: ^u32, +) -> u32 + +@(private="file") +PROCESS_BASIC_INFORMATION :: struct { + _: rawptr, + PebBaseAddress: ^PEB, + _: [2]rawptr, + UniqueProcessId: ^u32, + _: rawptr, +} + +@(private="file") +PEB :: struct { + _: [2]u8, + BeingDebugged: u8, + _: [1]u8, + _: [2]rawptr, + Ldr: ^PEB_LDR_DATA, + ProcessParameters: ^RTL_USER_PROCESS_PARAMETERS, + _: [104]u8, + _: [52]rawptr, + PostProcessInitRoutine: #type proc "stdcall" (), + _: [128]u8, + _: [1]rawptr, + SessionId: u32, +} + +@(private="file") +PEB_LDR_DATA :: struct { + _: [8]u8, + _: [3]rawptr, + InMemoryOrderModuleList: LIST_ENTRY, +} + +@(private="file") +RTL_USER_PROCESS_PARAMETERS :: struct { + MaximumLength: u32, + Length: u32, + Flags: u32, + DebugFlags: u32, + ConsoleHandle: rawptr, + ConsoleFlags: u32, + StdInputHandle: rawptr, + StdOutputHandle: rawptr, + StdErrorHandle: rawptr, + CurrentDirectoryPath: UNICODE_STRING, + CurrentDirectoryHandle: rawptr, + DllPath: UNICODE_STRING, + ImagePathName: UNICODE_STRING, + CommandLine: UNICODE_STRING, + Environment: rawptr, + StartingPositionLeft: u32, + StartingPositionTop: u32, + Width: u32, + Height: u32, + CharWidth: u32, + CharHeight: u32, + ConsoleTextAttributes: u32, + WindowFlags: u32, + ShowWindowFlags: u32, + WindowTitle: UNICODE_STRING, + DesktopName: UNICODE_STRING, + ShellInfo: UNICODE_STRING, + RuntimeData: UNICODE_STRING, + DLCurrentDirectory: [32]RTL_DRIVE_LETTER_CURDIR, + EnvironmentSize: u32, +} + +RTL_DRIVE_LETTER_CURDIR :: struct { + Flags: u16, + Length: u16, + TimeStamp: u32, + DosPath: UNICODE_STRING, +} + +@(private="file") +UNICODE_STRING :: struct { + Length: u16, + MaximumLength: u16, + Buffer: [^]u16, +} + +@(private="file") +LIST_ENTRY :: struct { + Flink: ^LIST_ENTRY, + Blink: ^LIST_ENTRY, +} \ No newline at end of file From f3d4a734d89698bfa72e34dfaa2763a3d8f12e3b Mon Sep 17 00:00:00 2001 From: flysand7 Date: Sat, 13 Jul 2024 08:55:58 +1100 Subject: [PATCH 052/198] [os2/process]: Fix environment block null-terminator counting --- core/os/os2/process_windows.odin | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index d6d9f0866..0dde9efe0 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -410,14 +410,15 @@ _get_process_user :: proc(process_handle: windows.HANDLE, allocator: runtime.All @(private) _parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) -> ([]string, Error) { zt_count := 0 - for idx := 0; true; idx += 1 { + for idx := 0; true; { if block[idx] == 0x0000 { zt_count += 1 + if block[idx+1] == 0x0000 { + zt_count += 1 + break + } } - if block[idx] == 0x0000 { - zt_count += 1 - break - } + idx += 1 } // Note(flysand): Each string in the environment block is terminated // by a NUL character. In addition, the environment block itself is From c1f5d8f006830227a7de0eaa5f6e0b088da8bbfc Mon Sep 17 00:00:00 2001 From: flysand7 Date: Sat, 13 Jul 2024 09:02:26 +1100 Subject: [PATCH 053/198] [os2/process]: Improve documentation for *process_info() family of function --- core/os/os2/process.odin | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index 4a0481b0f..894aa1e40 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -133,6 +133,8 @@ Process_Info_Field :: enum { procedure. */ Process_Info :: struct { + // The information about a process the struct contains. `pid` is always + // stored, no matter what. fields: Process_Info_Fields, // The ID of the process. pid: int, @@ -157,22 +159,37 @@ Process_Info :: struct { /* Obtain information about a process. - This procedure obtains an information, given by `selection` parameter of + This procedure obtains an information, specified by `selection` parameter of a process given by `pid`. - Use `free_process_info` to free memory allocated by this function. In case - the function returns an error all temporary allocations would be freed and - as such, calling `free_process_info()` is not needed. + Use `free_process_info` to free the memory allocated by this function. In + case the function returns an error all temporary allocations would be freed + and as such, calling `free_process_info()` is not needed. **Note**: The resulting information may or may not contain the selected fields. Please check the `fields` field of the `Process_Info` struct to see if the struct contains the desired fields **before** checking - the error return of this function. + the error code returned by this function. */ process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { return _process_info(pid, selection, allocator) } +/* + Obtain information about the current process. + + This procedure obtains the information, specified by `selection` parameter + about the currently running process. + + Use `free_process_info` to free the memory allocated by this function. In + case this function returns an error, all temporary allocations would be + freed and as such calling `free_process_info()` is not needed. + + **Note**: The resulting `Process_Info` may or may not contain the selected + fields. Check the `fields` field of the `Process_Info` struct to see, if the + struct contains the selected fields **before** checking the error code + returned by this function. +*/ current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { return _current_process_info(selection, allocator) } From 5d6e0bc793aa8e80515e1b22aa31c4ef906940fb Mon Sep 17 00:00:00 2001 From: flysand7 Date: Sat, 13 Jul 2024 09:39:35 +1100 Subject: [PATCH 054/198] [os2/process]: Implement retrieving command args in process info --- core/os/os2/process_windows.odin | 83 ++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 14 deletions(-) diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index 0dde9efe0..7fe891b38 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -165,6 +165,12 @@ _process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runti defer if .Command_Line in info.fields && err != nil { delete(info.command_line, allocator) } + defer if .Command_Args in selection && err != nil { + for arg in info.command_args { + delete(arg, allocator) + } + delete(info.command_args, allocator) + } if need_peb { ntdll_lib := windows.LoadLibraryW(windows.L("ntdll.dll")) if ntdll_lib == nil { @@ -208,20 +214,31 @@ _process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runti err = _get_platform_error() return } - if .Command_Line in selection { + if .Command_Line in selection || .Command_Args in selection { TEMP_ALLOCATOR_GUARD() cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) if !read_slice(ph, process_params.CommandLine.Buffer, cmdline_w, &bytes_read) { err = _get_platform_error() return } - cmdline, cmdline_err := windows.utf16_to_utf8(cmdline_w, allocator) - if cmdline_err != nil { - err = cmdline_err - return + if .Command_Line in selection { + cmdline, cmdline_err := windows.utf16_to_utf8(cmdline_w, allocator) + if cmdline_err != nil { + err = cmdline_err + return + } + info.fields |= {.Command_Line} + info.command_line = cmdline + } + if .Command_Args in selection { + args, args_err := _parse_argv(raw_data(cmdline_w), allocator) + if args_err != nil { + err = args_err + return + } + info.fields += {.Command_Args} + info.command_args = args } - info.fields |= {.Command_Line} - info.command_line = cmdline } if .Environment in selection { TEMP_ALLOCATOR_GUARD() @@ -312,19 +329,36 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime defer if .Executable_Path in selection && err != nil { delete(info.executable_path, allocator) } - if .Command_Line in selection { + if .Command_Line in selection || .Command_Args in selection { command_line_w := windows.GetCommandLineW() - command_line, command_line_err := windows.wstring_to_utf8(command_line_w, -1, allocator) - if command_line_err != nil { - err = command_line_err - return + if .Command_Line in selection { + command_line, command_line_err := windows.wstring_to_utf8(command_line_w, -1, allocator) + if command_line_err != nil { + err = command_line_err + return + } + info.fields += {.Command_Line} + info.command_line = command_line + } + if .Command_Args in selection { + args, args_err := _parse_argv(command_line_w, allocator) + if args_err != nil { + err = args_err + return + } + info.fields += {.Command_Args} + info.command_args = args } - info.fields += {.Command_Line} - info.command_line = command_line } defer if .Command_Line in selection && err != nil { delete(info.command_line, allocator) } + defer if .Command_Args in selection && err != nil { + for arg in info.command_args { + delete(arg, allocator) + } + delete(info.command_args, allocator) + } if .Environment in selection { env_block := windows.GetEnvironmentStringsW() envs, envs_err := _parse_environment_block(env_block, allocator) @@ -407,6 +441,27 @@ _get_process_user :: proc(process_handle: windows.HANDLE, allocator: runtime.All return full_name, nil } +@(private) +_parse_argv :: proc(cmd_line_w: [^]u16, allocator: runtime.Allocator) -> ([]string, Error) { + argc: i32 = --- + argv_w := windows.CommandLineToArgvW(cmd_line_w, &argc) + if argv_w == nil { + return nil, _get_platform_error() + } + argv, argv_err := make([]string, argc, allocator) + if argv_err != nil { + return nil, argv_err + } + for arg_w, i in argv_w[:argc] { + arg, arg_err := windows.wstring_to_utf8(arg_w, -1, allocator) + if arg_err != nil { + return nil, arg_err + } + argv[i] = arg + } + return argv, nil +} + @(private) _parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) -> ([]string, Error) { zt_count := 0 From e1eed7610c64c884751f189e3d165d7ca1473b01 Mon Sep 17 00:00:00 2001 From: flysand7 Date: Sat, 13 Jul 2024 10:02:49 +1100 Subject: [PATCH 055/198] [os2/process]: Fix leaking memory on errors --- core/os/os2/process_windows.odin | 52 ++++++-------------------------- 1 file changed, 10 insertions(+), 42 deletions(-) diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index 7fe891b38..647860dc2 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -66,6 +66,9 @@ _process_list :: proc(allocator: runtime.Allocator) -> ([]int, Error) { _process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { info.pid = pid + defer if err != nil { + free_process_info(info, allocator) + } need_snapprocess := \ .PPid in selection || .Priority in selection @@ -135,9 +138,6 @@ _process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runti info.fields |= {.Executable_Path} info.executable_path = exe_path } - defer if .Executable_Path in info.fields && err != nil { - delete(info.executable_path, allocator) - } ph := windows.INVALID_HANDLE_VALUE if need_process_handle { ph = windows.OpenProcess( @@ -153,24 +153,6 @@ _process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runti defer if ph != windows.INVALID_HANDLE_VALUE { windows.CloseHandle(ph) } - defer if .CWD in info.fields && err != nil { - delete(info.cwd, allocator) - } - defer if .Environment in info.fields && err != nil { - for s in info.environment { - delete(s, allocator) - } - delete(info.environment, allocator) - } - defer if .Command_Line in info.fields && err != nil { - delete(info.command_line, allocator) - } - defer if .Command_Args in selection && err != nil { - for arg in info.command_args { - delete(arg, allocator) - } - delete(info.command_args, allocator) - } if need_peb { ntdll_lib := windows.LoadLibraryW(windows.L("ntdll.dll")) if ntdll_lib == nil { @@ -287,6 +269,9 @@ _process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runti _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { info.pid = cast(int) windows.GetCurrentProcessId() + defer if err != nil { + free_process_info(info, allocator) + } need_snapprocess := .PPid in selection || .Priority in selection if need_snapprocess { snap := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0) @@ -326,9 +311,6 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime info.fields += {.Executable_Path} info.executable_path = exe_filename } - defer if .Executable_Path in selection && err != nil { - delete(info.executable_path, allocator) - } if .Command_Line in selection || .Command_Args in selection { command_line_w := windows.GetCommandLineW() if .Command_Line in selection { @@ -350,15 +332,6 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime info.command_args = args } } - defer if .Command_Line in selection && err != nil { - delete(info.command_line, allocator) - } - defer if .Command_Args in selection && err != nil { - for arg in info.command_args { - delete(arg, allocator) - } - delete(info.command_args, allocator) - } if .Environment in selection { env_block := windows.GetEnvironmentStringsW() envs, envs_err := _parse_environment_block(env_block, allocator) @@ -369,12 +342,6 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime info.fields += {.Environment} info.environment = envs } - defer if .Environment in selection && err != nil { - for s in info.environment { - delete(s, allocator) - } - delete(info.environment) - } if .Username in selection { process_handle := windows.GetCurrentProcess() username, username_err := _get_process_user(process_handle, allocator) @@ -385,9 +352,6 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime info.fields += {.Username} info.username = username } - defer if .Username in selection && err != nil { - delete(info.username) - } err = nil return } @@ -455,6 +419,10 @@ _parse_argv :: proc(cmd_line_w: [^]u16, allocator: runtime.Allocator) -> ([]stri for arg_w, i in argv_w[:argc] { arg, arg_err := windows.wstring_to_utf8(arg_w, -1, allocator) if arg_err != nil { + for arg in argv[:i] { + delete(arg, allocator) + } + delete(argv, allocator) return nil, arg_err } argv[i] = arg From 63d94301fc5614334ee93a088abf2cc20a31268a Mon Sep 17 00:00:00 2001 From: flysand7 Date: Sun, 14 Jul 2024 14:56:00 +1100 Subject: [PATCH 056/198] [os2/process]: Implement process creation procedures --- core/os/os2/process.odin | 165 +++++++++++++++++++++++---- core/os/os2/process_windows.odin | 186 ++++++++++++++++++++++++++++++- 2 files changed, 329 insertions(+), 22 deletions(-) diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index 894aa1e40..ecf9354b8 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -5,6 +5,12 @@ import "core:time" import "base:runtime" import "core:strings" +/* + In procedures that explicitly state this as one of the allowed values, + specifies an infinite timeout. +*/ +TIMEOUT_INFINITE :: time.MIN_DURATION // Note(flysand): Any negative duration will be treated as infinity + /* Arguments to the current process. @@ -212,6 +218,145 @@ free_process_info :: proc(pi: Process_Info, allocator: runtime.Allocator) { delete(pi.cwd, allocator) } +/* + Represents a process handle. + + When a process dies, the OS is free to re-use the pid of that process. The + `Process` struct represents a handle to the process that will refer to a + specific process, even after it has died. + + **Note(linux)**: The `handle` will be referring to pidfd. +*/ +Process :: struct { + pid: int, + handle: uintptr, +} + +Process_Open_Flags :: bit_set[Process_Open_Flag] +Process_Open_Flag :: enum { + // Request for reading from the virtual memory of another process. + Mem_Read, + // Request for writing to the virtual memory of another process. + Mem_Write, +} + +/* + Open a process handle using it's pid. + + This procedure obtains a process handle of a process specified by `pid`. + This procedure can be subject to race conditions. See the description of + `Process`. + + Use `process_close()` function to close the process handle. +*/ +process_open :: proc(pid: int, flags := Process_Open_Flags {}) -> (Process, Error) { + return _process_open(pid, flags) +} + +/* + The description of how a process should be created. +*/ +Process_Desc :: struct { + // OS-specific attributes. + sys_attr: _Sys_Process_Attributes, + // The working directory of the process. + dir: string, + // The command to run. Each element of the slice is a separate argument to + // the process. The first element of the slice would be the executable. + command: []string, + // A slice of strings, each having the format `KEY=VALUE` representing the + // full environment that the child process will receive. + // In case this slice is `nil`, the current process' environment is used. + env: []string, + // The `stderr` handle to give to the child process. It can be either a file + // or a writeable end of a pipe. Passing `nil` will shut down the process' + // stderr output. + stderr: ^File, + // The `stdout` handle to give to the child process. It can be either a file + // or a writeabe end of a pipe. Passing a `nil` will shut down the process' + // stdout output. + stdout: ^File, + // The `stdin` handle to give to the child process. It can either be a file + // or a readable end of a pipe. Passing a `nil` will shut down the process' + // input. + stdin: ^File, +} + +/* + Create a new process and obtain its handle. + + This procedure creates a new process, with a given command and environment + strings as parameters. Use `environ()` to inherit the environment of the + current process. + + The `desc` parameter specifies the description of how the process should + be created. It contains information such as the command line, the + environment of the process, the starting directory and many other options. + Most of the fields in the struct can be set to `nil` or an empty value. + + Use `process_close` to close the handle to the process. Note, that this + is not the same as terminating the process. One can terminate the process + and not close the handle, in which case the handle would be leaked. In case + the function returns an error, an invalid handle is returned. + + This procedure is not thread-safe. It may alter the inheritance properties + of file handles. +*/ +process_start :: proc(desc := Process_Desc {}) -> (Process, Error) { + return _process_start(desc) +} + +/* + The state of the process after it has finished execution. +*/ +Process_State :: struct { + // The ID of the process. + pid: int, + // Specifies whether the process has terminated or is still running. + exited: bool, + // The exit code of the process, if it has exited. + // Will also store the number of the exception or signal that has crashed the + // process. + exit_code: int, + // Specifies whether the termination of the process was successfull or not, + // i.e. whether it has crashed or not. + // **Note(windows)**: On windows `true` is always returned, as there is no + // reliable way to obtain information about whether the process has crashed. + success: bool, + // The time the process has spend executing in kernel time. + system_time: time.Duration, + // The time the process has spend executing in userspace. + user_time: time.Duration, +} + +/* + Wait for a process event. + + This procedure blocks the execution until the process has exited or the + timeout (if specified) has reached zero. If the timeout is `TIMEOUT_INFINITE`, + no timeout restriction is imposed and the procedure can block indefinately. + + If the timeout has expired, the `General_Error.Timeout` is returned as + the error. + + If an error is returned for any other reason, other than timeout, the + process state is considered undetermined. +*/ +process_wait :: proc(process: Process, timeout := TIMEOUT_INFINITE) -> (Process_State, Error) { + return _process_wait(process, timeout) +} + +/* + Close the handle to a process. + + This procedure closes the handle associated with a process. It **does not** + terminate a process, in case it was running. In case a termination is + desired, kill the process first, then close the handle. +*/ +process_close :: proc(process: Process) -> (Error) { + return _process_close(process) +} + // Process_Attributes :: struct { // dir: string, // env: []string, @@ -225,27 +370,13 @@ free_process_info :: proc(pi: Process_Info, allocator: runtime.Allocator) { // 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 // } @@ -262,10 +393,6 @@ free_process_info :: proc(pi: Process_Info, allocator: runtime.Allocator) { // return .None // } -// process_wait :: proc(p: ^Process) -> (Process_State, Process_Error) { -// return {}, .None -// } - diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index 647860dc2..6e461bade 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -3,6 +3,8 @@ package os2 import "core:sys/windows" import "core:strings" +import "core:time" + import "base:runtime" _Process_Handle :: windows.HANDLE @@ -213,7 +215,7 @@ _process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runti info.command_line = cmdline } if .Command_Args in selection { - args, args_err := _parse_argv(raw_data(cmdline_w), allocator) + args, args_err := _parse_command_line(raw_data(cmdline_w), allocator) if args_err != nil { err = args_err return @@ -323,7 +325,7 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime info.command_line = command_line } if .Command_Args in selection { - args, args_err := _parse_argv(command_line_w, allocator) + args, args_err := _parse_command_line(command_line_w, allocator) if args_err != nil { err = args_err return @@ -356,6 +358,121 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime return } +_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (Process, Error) { + dwDesiredAccess := windows.PROCESS_QUERY_LIMITED_INFORMATION | windows.SYNCHRONIZE + if .Mem_Read in flags { + dwDesiredAccess |= windows.PROCESS_VM_READ + } + if .Mem_Write in flags { + dwDesiredAccess |= windows.PROCESS_VM_WRITE + } + handle := windows.OpenProcess( + dwDesiredAccess, + false, + u32(pid), + ) + if handle == windows.INVALID_HANDLE_VALUE { + return {}, _get_platform_error() + } + return Process { + pid = pid, + handle = cast(uintptr) handle, + }, nil +} + +_Sys_Process_Attributes :: struct {} + +_process_start :: proc(desc: Process_Desc) -> (Process, Error) { + TEMP_ALLOCATOR_GUARD() + command_line := _build_command_line(desc.command, temp_allocator()) + command_line_w := windows.utf8_to_wstring(command_line, temp_allocator()) + environment := desc.env + if desc.env == nil { + environment = environ(temp_allocator()) + } + environment_block := _build_environment_block(environment, temp_allocator()) + environment_block_w := windows.utf8_to_utf16(environment_block, temp_allocator()) + stderr_handle := windows.GetStdHandle(windows.STD_ERROR_HANDLE) + stdout_handle := windows.GetStdHandle(windows.STD_OUTPUT_HANDLE) + stdin_handle := windows.GetStdHandle(windows.STD_INPUT_HANDLE) + if desc.stdout != nil { + stdout_handle = windows.HANDLE(desc.stdout.impl.fd) + } + if desc.stderr != nil { + stderr_handle = windows.HANDLE(desc.stderr.impl.fd) + } + process_info: windows.PROCESS_INFORMATION = --- + process_ok := windows.CreateProcessW( + nil, + command_line_w, + nil, + nil, + true, + windows.CREATE_UNICODE_ENVIRONMENT|windows.NORMAL_PRIORITY_CLASS, + raw_data(environment_block_w), + nil, + &windows.STARTUPINFOW { + cb = size_of(windows.STARTUPINFOW), + hStdError = stderr_handle, + hStdOutput = stdout_handle, + hStdInput = stdin_handle, + dwFlags = windows.STARTF_USESTDHANDLES, + }, + &process_info, + ) + if !process_ok { + return {}, _get_platform_error() + } + return Process { + pid = cast(int) process_info.dwProcessId, + handle = cast(uintptr) process_info.hProcess, + }, nil +} + +_process_wait :: proc(process: Process, timeout: time.Duration) -> (Process_State, Error) { + handle := windows.HANDLE(process.handle) + timeout_ms := u32(timeout / time.Millisecond) if timeout > 0 else windows.INFINITE + wait_result := windows.WaitForSingleObject(handle, timeout_ms) + switch wait_result { + case windows.WAIT_OBJECT_0: + exit_code: u32 = --- + if !windows.GetExitCodeProcess(handle, &exit_code) { + return {}, _get_platform_error() + } + time_created: windows.FILETIME = --- + time_exited: windows.FILETIME = --- + time_kernel: windows.FILETIME = --- + time_user: windows.FILETIME = --- + if !windows.GetProcessTimes(handle, &time_created, &time_exited, &time_kernel, &time_user) { + return {}, _get_platform_error() + } + return Process_State { + exit_code = cast(int) exit_code, + exited = true, + pid = process.pid, + success = true, + system_time = _filetime_to_duration(time_kernel), + user_time = _filetime_to_duration(time_user), + }, nil + case windows.WAIT_TIMEOUT: + return {}, General_Error.Timeout + case: + return {}, _get_platform_error() + } +} + +_process_close :: proc(process: Process) -> (Error) { + if !windows.CloseHandle(cast(windows.HANDLE) process.handle) { + return _get_platform_error() + } + return nil +} + +@(private) +_filetime_to_duration :: proc(filetime: windows.FILETIME) -> time.Duration { + ticks := u64(filetime.dwHighDateTime)<<32 | u64(filetime.dwLowDateTime) + return time.Duration(ticks * 100) +} @(private) _get_process_user :: proc(process_handle: windows.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) { @@ -406,7 +523,7 @@ _get_process_user :: proc(process_handle: windows.HANDLE, allocator: runtime.All } @(private) -_parse_argv :: proc(cmd_line_w: [^]u16, allocator: runtime.Allocator) -> ([]string, Error) { +_parse_command_line :: proc(cmd_line_w: [^]u16, allocator: runtime.Allocator) -> ([]string, Error) { argc: i32 = --- argv_w := windows.CommandLineToArgvW(cmd_line_w, &argc) if argv_w == nil { @@ -430,6 +547,43 @@ _parse_argv :: proc(cmd_line_w: [^]u16, allocator: runtime.Allocator) -> ([]stri return argv, nil } +@(private) +_build_command_line :: proc(command: []string, allocator: runtime.Allocator) -> string { + _write_byte_n_times :: #force_inline proc(builder: ^strings.Builder, b: byte, n: int) { + for _ in 0 ..< n { + strings.write_byte(builder, b) + } + } + builder := strings.builder_make(allocator) + for arg, i in command { + if i != 0 { + strings.write_byte(&builder, ' ') + } + j := 0 + strings.write_byte(&builder, '"') + for j < len(arg) { + backslashes := 0 + for j < len(arg) && arg[j] == '\\' { + backslashes += 1 + j += 1 + } + if j == len(arg) { + _write_byte_n_times(&builder, '\\', 2*backslashes) + break + } else if arg[j] == '"' { + _write_byte_n_times(&builder, '\\', 2*backslashes+1) + strings.write_byte(&builder, '"') + } else { + _write_byte_n_times(&builder, '\\', backslashes) + strings.write_byte(&builder, arg[j]) + } + j += 1 + } + strings.write_byte(&builder, '"') + } + return strings.to_string(builder) +} + @(private) _parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) -> ([]string, Error) { zt_count := 0 @@ -470,6 +624,32 @@ _parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) -> return envs, nil } +@(private) +_build_environment_block :: proc(environment: []string, allocator: runtime.Allocator) -> string { + builder := strings.builder_make(allocator) + #reverse for kv, cur_idx in environment { + eq_idx := strings.index_byte(kv, '=') + assert(eq_idx != -1, "Malformed environment string. Expected '=' to separate keys and values") + key := kv[:eq_idx] + already_handled := false + for old_kv in environment[cur_idx+1:] { + old_key := old_kv[:strings.index_byte(old_kv, '=')] + if key == old_key { + already_handled = true + break + } + } + if already_handled { + continue + } + strings.write_bytes(&builder, transmute([]byte) kv) + strings.write_byte(&builder, 0) + } + // Note(flysand): In addition to the NUL-terminator for each string, the + // environment block itself is NUL-terminated. + strings.write_byte(&builder, 0) + return strings.to_string(builder) +} @(private="file") PROCESSINFOCLASS :: enum i32 { From 8f4755532ef1386ba031fdda774d9c7137f5569a Mon Sep 17 00:00:00 2001 From: flysand7 Date: Sun, 14 Jul 2024 15:28:37 +1100 Subject: [PATCH 057/198] [os2/process]: Adjust docs on process_close function --- core/os/os2/process.odin | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index ecf9354b8..fb6766747 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -351,7 +351,8 @@ process_wait :: proc(process: Process, timeout := TIMEOUT_INFINITE) -> (Process_ This procedure closes the handle associated with a process. It **does not** terminate a process, in case it was running. In case a termination is - desired, kill the process first, then close the handle. + desired, kill the process first, wait for the process to finish, + then close the handle. */ process_close :: proc(process: Process) -> (Error) { return _process_close(process) From 4eca60946c294e3551e70641051e85f0d23b17bd Mon Sep 17 00:00:00 2001 From: flysand7 Date: Sun, 14 Jul 2024 15:47:40 +1100 Subject: [PATCH 058/198] [os2/process]: Refactor process_info procs, add process_info_by_handle --- core/os/os2/process.odin | 49 ++++-- core/os/os2/process_windows.odin | 267 ++++++++++++++++++++++++------- 2 files changed, 252 insertions(+), 64 deletions(-) diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index fb6766747..212032259 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -168,17 +168,37 @@ Process_Info :: struct { This procedure obtains an information, specified by `selection` parameter of a process given by `pid`. - Use `free_process_info` to free the memory allocated by this function. In + Use `free_process_info` to free the memory allocated by this procedure. In case the function returns an error all temporary allocations would be freed and as such, calling `free_process_info()` is not needed. - **Note**: The resulting information may or may not contain the - selected fields. Please check the `fields` field of the `Process_Info` - struct to see if the struct contains the desired fields **before** checking - the error code returned by this function. + **Note**: The resulting information may or may contain the fields specified + by the `selection` parameter. Always check whether the returned + `Process_Info` struct has the required fields before checking the error code + returned by this function. */ -process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { - return _process_info(pid, selection, allocator) +process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { + return _process_info_by_pid(pid, selection, allocator) +} + +/* + Obtain information about a process. + + This procedure obtains information, specified by `selection` parameter + about a process that has been opened by the application, specified in + the `process` parameter. + + Use `free_process_info` to free the memory allocated by this procedure. In + case the function returns an error, all temporary allocations would be freed + and as such, calling `free_process_info` is not needed. + + **Note**: The resulting information may or may contain the fields specified + by the `selection` parameter. Always check whether the returned + `Process_Info` struct has the required fields before checking the error code + returned by this function. +*/ +process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { + return _process_info_by_handle(process, selection, allocator) } /* @@ -191,15 +211,24 @@ process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runtim case this function returns an error, all temporary allocations would be freed and as such calling `free_process_info()` is not needed. - **Note**: The resulting `Process_Info` may or may not contain the selected - fields. Check the `fields` field of the `Process_Info` struct to see, if the - struct contains the selected fields **before** checking the error code + **Note**: The resulting information may or may contain the fields specified + by the `selection` parameter. Always check whether the returned + `Process_Info` struct has the required fields before checking the error code returned by this function. */ current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { return _current_process_info(selection, allocator) } +/* + Obtain information about the specified process. +*/ +process_info :: proc { + process_info_by_pid, + process_info_by_handle, + current_process_info, +} + /* Free the information about the process. diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index 6e461bade..4e7c7e62d 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -66,7 +66,7 @@ _process_list :: proc(allocator: runtime.Allocator) -> ([]int, Error) { return pid_list[:], nil } -_process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { +_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { info.pid = pid defer if err != nil { free_process_info(info, allocator) @@ -83,22 +83,8 @@ _process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runti need_process_handle := need_peb || .Username in selection // Data obtained from process snapshots if need_snapprocess { - snap := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0) - if snap == windows.INVALID_HANDLE_VALUE { - return info, _get_platform_error() - } - defer windows.CloseHandle(snap) - entry := windows.PROCESSENTRY32W { dwSize = size_of(windows.PROCESSENTRY32W) } - status := windows.Process32FirstW(snap, &entry) - found := false - for status { - if u32(pid) == entry.th32ProcessID { - found = true - break - } - status = windows.Process32NextW(snap, &entry) - } - if !found { + entry, entry_err := _process_entry_by_pid(info.pid) + if entry_err != nil { err = General_Error.Not_Exist return } @@ -111,30 +97,10 @@ _process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runti info.priority = int(entry.pcPriClassBase) } } - // Note(flysand): Not sure which way it's better to get the executable path: - // via toolhelp snapshots or by reading other process' PEB memory. I have - // a slight suspicion that if both exe path and command line are desired, - // it's faster to just read both from PEB, but maybe the toolhelp snapshots - // are just better...? if need_snapmodule { - snap := windows.CreateToolhelp32Snapshot( - windows.TH32CS_SNAPMODULE|windows.TH32CS_SNAPMODULE32, - u32(pid), - ) - if snap == windows.INVALID_HANDLE_VALUE { - err = _get_platform_error() - return - } - defer windows.CloseHandle(snap) - entry := windows.MODULEENTRY32W { dwSize = size_of(windows.MODULEENTRY32W) } - status := windows.Module32FirstW(snap, &entry) - if !status { - err = _get_platform_error() - return - } - exe_path: string - exe_path, err = windows.wstring_to_utf8(raw_data(entry.szExePath[:]), -1, allocator) - if err != nil { + exe_path, exe_path_err := _process_exe_by_pid(pid, allocator) + if exe_path_err != nil { + err = exe_path_err return } info.fields |= {.Executable_Path} @@ -269,6 +235,161 @@ _process_info :: proc(pid: int, selection: Process_Info_Fields, allocator: runti return } +_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + pid := process.pid + info.pid = pid + defer if err != nil { + free_process_info(info, allocator) + } + need_snapprocess := \ + .PPid in selection || + .Priority in selection + need_snapmodule := \ + .Executable_Path in selection + need_peb := \ + .Command_Line in selection || + .Environment in selection || + .CWD in selection + // Data obtained from process snapshots + if need_snapprocess { + entry, entry_err := _process_entry_by_pid(info.pid) + if entry_err != nil { + err = General_Error.Not_Exist + return + } + if .PPid in selection { + info.fields |= {.PPid} + info.ppid = int(entry.th32ParentProcessID) + } + if .Priority in selection { + info.fields |= {.Priority} + info.priority = int(entry.pcPriClassBase) + } + } + if need_snapmodule { + exe_path, exe_path_err := _process_exe_by_pid(pid, allocator) + if exe_path_err != nil { + err = exe_path_err + return + } + info.fields |= {.Executable_Path} + info.executable_path = exe_path + } + ph := cast(windows.HANDLE) process.handle + if need_peb { + ntdll_lib := windows.LoadLibraryW(windows.L("ntdll.dll")) + if ntdll_lib == nil { + err = _get_platform_error() + return + } + defer windows.FreeLibrary(ntdll_lib) + NtQueryInformationProcess := cast(NtQueryInformationProcess_T) windows.GetProcAddress(ntdll_lib, "NtQueryInformationProcess") + if NtQueryInformationProcess == nil { + err = _get_platform_error() + return + } + process_info_size: u32 = --- + process_info: PROCESS_BASIC_INFORMATION = --- + status := NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size) + if status != 0 { + // TODO(flysand): There's probably a mismatch between NTSTATUS and + // windows userland error codes, I haven't checked. + err = Platform_Error(status) + return + } + if process_info.PebBaseAddress == nil { + // Not sure what the error is + err = General_Error.Unsupported + return + } + process_peb: PEB = --- + bytes_read: uint = --- + read_struct :: proc(h: windows.HANDLE, addr: rawptr, dest: ^$T, br: ^uint) -> windows.BOOL { + return windows.ReadProcessMemory(h, addr, dest, size_of(T), br) + } + read_slice :: proc(h: windows.HANDLE, addr: rawptr, dest: []$T, br: ^uint) -> windows.BOOL { + return windows.ReadProcessMemory(h, addr, raw_data(dest), len(dest)*size_of(T), br) + } + if !read_struct(ph, process_info.PebBaseAddress, &process_peb, &bytes_read) { + err = _get_platform_error() + return + } + process_params: RTL_USER_PROCESS_PARAMETERS = --- + if !read_struct(ph, process_peb.ProcessParameters, &process_params, &bytes_read) { + err = _get_platform_error() + return + } + if .Command_Line in selection || .Command_Args in selection { + TEMP_ALLOCATOR_GUARD() + cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) + if !read_slice(ph, process_params.CommandLine.Buffer, cmdline_w, &bytes_read) { + err = _get_platform_error() + return + } + if .Command_Line in selection { + cmdline, cmdline_err := windows.utf16_to_utf8(cmdline_w, allocator) + if cmdline_err != nil { + err = cmdline_err + return + } + info.fields |= {.Command_Line} + info.command_line = cmdline + } + if .Command_Args in selection { + args, args_err := _parse_command_line(raw_data(cmdline_w), allocator) + if args_err != nil { + err = args_err + return + } + info.fields += {.Command_Args} + info.command_args = args + } + } + if .Environment in selection { + TEMP_ALLOCATOR_GUARD() + env_len := process_params.EnvironmentSize / 2 + envs_w := make([]u16, env_len, temp_allocator()) + if !read_slice(ph, process_params.Environment, envs_w, &bytes_read) { + err = _get_platform_error() + return + } + envs, envs_err := _parse_environment_block(raw_data(envs_w), allocator) + if envs_err != nil { + err = envs_err + return + } + info.fields |= {.Environment} + info.environment = envs + } + if .CWD in selection { + TEMP_ALLOCATOR_GUARD() + cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) + if !read_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w, &bytes_read) { + err = _get_platform_error() + return + } + cwd, cwd_err := windows.utf16_to_utf8(cwd_w, allocator) + if cwd_err != nil { + err = cwd_err + return + } + info.fields |= {.CWD} + info.cwd = cwd + } + } + if .Username in selection { + username, username_err := _get_process_user(ph, allocator) + if username_err != nil { + err = username_err + return + } + info.fields |= {.Username} + info.username = username + } + err = nil + return +} + _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { info.pid = cast(int) windows.GetCurrentProcessId() defer if err != nil { @@ -276,20 +397,8 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime } need_snapprocess := .PPid in selection || .Priority in selection if need_snapprocess { - snap := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0) - if snap == windows.INVALID_HANDLE_VALUE { - return - } - defer windows.CloseHandle(snap) - entry := windows.PROCESSENTRY32W { dwSize = size_of(windows.PROCESSENTRY32W) } - status := windows.Process32FirstW(snap, &entry) - for status { - if entry.th32ProcessID == u32(info.pid) { - break - } - status = windows.Process32NextW(snap, &entry) - } - if entry.th32ProcessID != u32(info.pid) { + entry, entry_err := _process_entry_by_pid(info.pid) + if entry_err != nil { err = General_Error.Not_Exist return } @@ -474,6 +583,56 @@ _filetime_to_duration :: proc(filetime: windows.FILETIME) -> time.Duration { return time.Duration(ticks * 100) } +@(private) +_process_entry_by_pid :: proc(pid: int) -> (windows.PROCESSENTRY32W, Error) { + snap := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0) + if snap == windows.INVALID_HANDLE_VALUE { + return {}, _get_platform_error() + } + defer windows.CloseHandle(snap) + entry := windows.PROCESSENTRY32W { dwSize = size_of(windows.PROCESSENTRY32W) } + status := windows.Process32FirstW(snap, &entry) + found := false + for status { + if u32(pid) == entry.th32ProcessID { + found = true + break + } + status = windows.Process32NextW(snap, &entry) + } + if !found { + return {}, General_Error.Not_Exist + } + return entry, nil +} + +// Note(flysand): Not sure which way it's better to get the executable path: +// via toolhelp snapshots or by reading other process' PEB memory. I have +// a slight suspicion that if both exe path and command line are desired, +// it's faster to just read both from PEB, but maybe the toolhelp snapshots +// are just better...? +@(private) +_process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (string, Error) { + snap := windows.CreateToolhelp32Snapshot( + windows.TH32CS_SNAPMODULE|windows.TH32CS_SNAPMODULE32, + u32(pid), + ) + if snap == windows.INVALID_HANDLE_VALUE { + return "", _get_platform_error() + } + defer windows.CloseHandle(snap) + entry := windows.MODULEENTRY32W { dwSize = size_of(windows.MODULEENTRY32W) } + status := windows.Module32FirstW(snap, &entry) + if !status { + return "", _get_platform_error() + } + exe_path, err := windows.wstring_to_utf8(raw_data(entry.szExePath[:]), -1, allocator) + if err != nil { + return "", err + } + return exe_path, nil +} + @(private) _get_process_user :: proc(process_handle: windows.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) { TEMP_ALLOCATOR_GUARD() From b7ccfed9af60392e44d281770d55789f1d59df13 Mon Sep 17 00:00:00 2001 From: flysand7 Date: Sun, 14 Jul 2024 15:59:18 +1100 Subject: [PATCH 059/198] [os2/process]: Implement process_kill --- core/os/os2/process.odin | 50 +++++++------------------------- core/os/os2/process_windows.odin | 11 +++++-- 2 files changed, 19 insertions(+), 42 deletions(-) diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index 212032259..905aa2182 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -26,11 +26,11 @@ args := get_args() typically is the path to the currently running executable. */ get_args :: proc() -> []string { - args := make([]string, len(runtime.args__), allocator = context.allocator) + result := make([]string, len(runtime.args__), allocator = context.allocator) for rt_arg, i in runtime.args__ { - args[i] = cast(string) rt_arg + result[i] = cast(string) rt_arg } - return args[:] + return result[:] } /* @@ -387,42 +387,12 @@ process_close :: proc(process: Process) -> (Error) { return _process_close(process) } -// Process_Attributes :: struct { -// dir: string, -// env: []string, -// files: []^File, -// sys: ^Process_Attributes_OS_Specific, -// } - -// Process_Attributes_OS_Specific :: struct{} - -// Process_Error :: enum { -// None, -// } - - - -// Signal :: #type proc() - -// Kill: Signal = nil -// Interrupt: Signal = nil - -// 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 -// } - - +/* + Terminate a process. + This procedure terminates a process, specified by it's handle, `process`. +*/ +process_kill :: proc(process: Process) -> (Error) { + return _process_kill(process) +} diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index 4e7c7e62d..251900020 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -577,6 +577,13 @@ _process_close :: proc(process: Process) -> (Error) { return nil } +_process_kill :: proc(process: Process) -> (Error) { + if !windows.TerminateProcess(windows.HANDLE(process.handle), 9) { + return _get_platform_error() + } + return nil +} + @(private) _filetime_to_duration :: proc(filetime: windows.FILETIME) -> time.Duration { ticks := u64(filetime.dwHighDateTime)<<32 | u64(filetime.dwLowDateTime) @@ -695,8 +702,8 @@ _parse_command_line :: proc(cmd_line_w: [^]u16, allocator: runtime.Allocator) -> for arg_w, i in argv_w[:argc] { arg, arg_err := windows.wstring_to_utf8(arg_w, -1, allocator) if arg_err != nil { - for arg in argv[:i] { - delete(arg, allocator) + for s in argv[:i] { + delete(s, allocator) } delete(argv, allocator) return nil, arg_err From 399c3ab067d0bbacbf9732107b932d5bb910b67f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 21:37:35 +0100 Subject: [PATCH 060/198] Reduce the size of `runtime.Type_Info` --- base/runtime/core.odin | 52 +++++++++++++++++------------ base/runtime/print.odin | 11 ++++--- core/encoding/cbor/marshal.odin | 6 ++-- core/encoding/cbor/unmarshal.odin | 2 +- core/encoding/json/marshal.odin | 2 +- core/encoding/json/unmarshal.odin | 2 +- core/fmt/fmt.odin | 16 ++++----- core/reflect/reflect.odin | 26 +++++++-------- core/reflect/types.odin | 27 ++++++++------- src/llvm_backend_const.cpp | 9 +++++ src/llvm_backend_type.cpp | 55 ++++++++++++++++--------------- 11 files changed, 114 insertions(+), 94 deletions(-) diff --git a/base/runtime/core.odin b/base/runtime/core.odin index a758a2fdd..f9bf57259 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -66,7 +66,7 @@ Type_Info_Named :: struct { name: string, base: ^Type_Info, pkg: string, - loc: Source_Code_Location, + loc: ^Source_Code_Location, } Type_Info_Integer :: struct {signed: bool, endianness: Platform_Endianness} Type_Info_Rune :: struct {} @@ -112,23 +112,32 @@ Type_Info_Parameters :: struct { // Only used for procedures parameters and resu } Type_Info_Tuple :: Type_Info_Parameters // Will be removed eventually -Type_Info_Struct :: struct { - types: []^Type_Info, - names: []string, - offsets: []uintptr, - usings: []bool, - tags: []string, - is_packed: bool, - is_raw_union: bool, - is_no_copy: bool, - custom_align: bool, +Type_Info_Struct_Flags :: distinct bit_set[Type_Info_Struct_Flag; u8] +Type_Info_Struct_Flag :: enum u8 { + packed = 0, + raw_union = 1, + no_copy = 2, + align = 3, +} - equal: Equal_Proc, // set only when the struct has .Comparable set but does not have .Simple_Compare set +Type_Info_Struct :: struct { + // Slice these with `field_count` + types: [^]^Type_Info, + names: [^]string, + offsets: [^]uintptr, + usings: [^]bool, + tags: [^]string, + + field_count: i32, + + flags: Type_Info_Struct_Flags, // These are only set iff this structure is an SOA structure soa_kind: Type_Info_Struct_Soa_Kind, + soa_len: i32, soa_base_type: ^Type_Info, - soa_len: int, + + equal: Equal_Proc, // set only when the struct has .Comparable set but does not have .Simple_Compare set } Type_Info_Union :: struct { variants: []^Type_Info, @@ -142,9 +151,9 @@ Type_Info_Union :: struct { shared_nil: bool, } Type_Info_Enum :: struct { - base: ^Type_Info, - names: []string, - values: []Type_Info_Enum_Value, + base: ^Type_Info, + names: []string, + values: []Type_Info_Enum_Value, } Type_Info_Map :: struct { key: ^Type_Info, @@ -187,11 +196,12 @@ Type_Info_Soa_Pointer :: struct { } Type_Info_Bit_Field :: struct { backing_type: ^Type_Info, - names: []string, - types: []^Type_Info, - bit_sizes: []uintptr, - bit_offsets: []uintptr, - tags: []string, + names: [^]string, + types: [^]^Type_Info, + bit_sizes: [^]uintptr, + bit_offsets: [^]uintptr, + tags: [^]string, + field_count: int, } Type_Info_Flag :: enum u8 { diff --git a/base/runtime/print.odin b/base/runtime/print.odin index 0262e8ef6..45f6f01ef 100644 --- a/base/runtime/print.odin +++ b/base/runtime/print.odin @@ -401,15 +401,16 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) { } print_string("struct ") - if info.is_packed { print_string("#packed ") } - if info.is_raw_union { print_string("#raw_union ") } - if info.custom_align { + if .packed in info.flags { print_string("#packed ") } + if .raw_union in info.flags { print_string("#raw_union ") } + if .no_copy in info.flags { print_string("#no_copy ") } + if .align in info.flags { print_string("#align(") print_u64(u64(ti.align)) print_string(") ") } print_byte('{') - for name, i in info.names { + for name, i in info.names[:info.field_count] { if i > 0 { print_string(", ") } print_string(name) print_string(": ") @@ -469,7 +470,7 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) { print_string("bit_field ") print_type(info.backing_type) print_string(" {") - for name, i in info.names { + for name, i in info.names[:info.field_count] { if i > 0 { print_string(", ") } print_string(name) print_string(": ") diff --git a/core/encoding/cbor/marshal.odin b/core/encoding/cbor/marshal.odin index 2cdf384c3..022e297e9 100644 --- a/core/encoding/cbor/marshal.odin +++ b/core/encoding/cbor/marshal.odin @@ -506,7 +506,7 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er } n: u64; { - for _, i in info.names { + for _, i in info.names[:info.field_count] { if field_name(info, i) != "-" { n += 1 } @@ -522,7 +522,7 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er entries := make([dynamic]Name, 0, n, e.temp_allocator) or_return defer delete(entries) - for _, i in info.names { + for _, i in info.names[:info.field_count] { fname := field_name(info, i) if fname == "-" { continue @@ -540,7 +540,7 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er marshal_entry(e, info, v, entry.name, entry.field) or_return } } else { - for _, i in info.names { + for _, i in info.names[:info.field_count] { fname := field_name(info, i) if fname == "-" { continue diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index 13350bb85..4da2d5a93 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -618,7 +618,7 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, #partial switch t in ti.variant { case reflect.Type_Info_Struct: - if t.is_raw_union { + if .raw_union in t.flags { return _unsupported(v, hdr) } diff --git a/core/encoding/json/marshal.odin b/core/encoding/json/marshal.odin index 30426f911..fa86d3468 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -406,7 +406,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: ti := runtime.type_info_base(type_info_of(v.id)) info := ti.variant.(runtime.Type_Info_Struct) first_iteration := true - for name, i in info.names { + for name, i in info.names[:info.field_count] { omitempty := false json_name, extra := json_name_from_tag_value(reflect.struct_tag_get(reflect.Struct_Tag(info.tags[i]), "json")) diff --git a/core/encoding/json/unmarshal.odin b/core/encoding/json/unmarshal.odin index eb59e7838..344d2973e 100644 --- a/core/encoding/json/unmarshal.odin +++ b/core/encoding/json/unmarshal.odin @@ -368,7 +368,7 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm #partial switch t in ti.variant { case reflect.Type_Info_Struct: - if t.is_raw_union { + if .raw_union in t.flags { return UNSUPPORTED_TYPE } diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index 234f4afbd..ef0647462 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -1861,7 +1861,7 @@ handle_tag :: proc(state: ^Info_State, data: rawptr, info: reflect.Type_Info_Str if optional_len == nil { return } - for f, i in info.names { + for f, i in info.names[:info.field_count] { if f != field_name { continue } @@ -1965,7 +1965,7 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St fmt_bad_verb(fi, the_verb) return } - if info.is_raw_union { + if .raw_union in info.flags { if type_name == "" { io.write_string(fi.writer, "(raw union)", &fi.n) } else { @@ -1989,7 +1989,7 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St // fi.hash = false; fi.indent += 1 - is_empty := len(info.names) == 0 + is_empty := info.field_count == 0 if !is_soa && hash && !is_empty { io.write_byte(fi.writer, '\n', &fi.n) @@ -2010,17 +2010,17 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St base_type_name = v.name } - actual_field_count := len(info.names) + actual_field_count := info.field_count n := uintptr(info.soa_len) if info.soa_kind == .Slice { - actual_field_count = len(info.names)-1 // len + actual_field_count = info.field_count-1 // len n = uintptr((^int)(uintptr(v.data) + info.offsets[actual_field_count])^) } else if info.soa_kind == .Dynamic { - actual_field_count = len(info.names)-3 // len, cap, allocator + actual_field_count = info.field_count-3 // len, cap, allocator n = uintptr((^int)(uintptr(v.data) + info.offsets[actual_field_count])^) } @@ -2099,7 +2099,7 @@ fmt_struct :: proc(fi: ^Info, v: any, the_verb: rune, info: runtime.Type_Info_St } } else { field_count := -1 - for name, i in info.names { + for name, i in info.names[:info.field_count] { optional_len: int = -1 use_nul_termination: bool = false verb := the_verb if the_verb == 'w' else 'v' @@ -2605,7 +2605,7 @@ fmt_bit_field :: proc(fi: ^Info, v: any, verb: rune, info: runtime.Type_Info_Bit field_count := -1 - for name, i in info.names { + for name, i in info.names[:info.field_count] { field_verb := verb if handle_bit_field_tag(v.data, info, i, &field_verb) { continue diff --git a/core/reflect/reflect.odin b/core/reflect/reflect.odin index 332d91c6e..e6d2bc87a 100644 --- a/core/reflect/reflect.odin +++ b/core/reflect/reflect.odin @@ -391,7 +391,7 @@ Struct_Field :: struct { struct_field_at :: proc(T: typeid, i: int) -> (field: Struct_Field) { ti := runtime.type_info_base(type_info_of(T)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { - if 0 <= i && i < len(s.names) { + if 0 <= i && i < int(s.field_count) { field.name = s.names[i] field.type = s.types[i] field.tag = Struct_Tag(s.tags[i]) @@ -406,7 +406,7 @@ struct_field_at :: proc(T: typeid, i: int) -> (field: Struct_Field) { struct_field_by_name :: proc(T: typeid, name: string) -> (field: Struct_Field) { ti := runtime.type_info_base(type_info_of(T)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { - for fname, i in s.names { + for fname, i in s.names[:s.field_count] { if fname == name { field.name = s.names[i] field.type = s.types[i] @@ -427,7 +427,7 @@ struct_field_value_by_name :: proc(a: any, field: string, allow_using := false) ti := runtime.type_info_base(type_info_of(a.id)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { - for name, i in s.names { + for name, i in s.names[:s.field_count] { if name == field { return any{ rawptr(uintptr(a.data) + s.offsets[i]), @@ -463,7 +463,7 @@ struct_field_value :: proc(a: any, field: Struct_Field) -> any { struct_field_names :: proc(T: typeid) -> []string { ti := runtime.type_info_base(type_info_of(T)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { - return s.names + return s.names[:s.field_count] } return nil } @@ -472,7 +472,7 @@ struct_field_names :: proc(T: typeid) -> []string { struct_field_types :: proc(T: typeid) -> []^Type_Info { ti := runtime.type_info_base(type_info_of(T)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { - return s.types + return s.types[:s.field_count] } return nil } @@ -482,7 +482,7 @@ struct_field_types :: proc(T: typeid) -> []^Type_Info { struct_field_tags :: proc(T: typeid) -> []Struct_Tag { ti := runtime.type_info_base(type_info_of(T)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { - return transmute([]Struct_Tag)s.tags + return transmute([]Struct_Tag)s.tags[:s.field_count] } return nil } @@ -491,7 +491,7 @@ struct_field_tags :: proc(T: typeid) -> []Struct_Tag { struct_field_offsets :: proc(T: typeid) -> []uintptr { ti := runtime.type_info_base(type_info_of(T)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { - return s.offsets + return s.offsets[:s.field_count] } return nil } @@ -501,11 +501,11 @@ struct_fields_zipped :: proc(T: typeid) -> (fields: #soa[]Struct_Field) { ti := runtime.type_info_base(type_info_of(T)) if s, ok := ti.variant.(runtime.Type_Info_Struct); ok { return soa_zip( - name = s.names, - type = s.types, - tag = transmute([]Struct_Tag)s.tags, - offset = s.offsets, - is_using = s.usings, + name = s.names[:s.field_count], + type = s.types[:s.field_count], + tag = ([^]Struct_Tag)(s.tags)[:s.field_count], + offset = s.offsets[:s.field_count], + is_using = s.usings[:s.field_count], ) } return nil @@ -1569,7 +1569,7 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_ if v.equal != nil { return v.equal(a.data, b.data) } else { - for offset, i in v.offsets { + for offset, i in v.offsets[:v.field_count] { x := rawptr(uintptr(a.data) + offset) y := rawptr(uintptr(b.data) + offset) id := v.types[i].id diff --git a/core/reflect/types.odin b/core/reflect/types.odin index 04dd8a52d..c92d39410 100644 --- a/core/reflect/types.odin +++ b/core/reflect/types.odin @@ -115,16 +115,14 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool { case Type_Info_Struct: y := b.variant.(Type_Info_Struct) or_return switch { - case len(x.types) != len(y.types), - x.is_packed != y.is_packed, - x.is_raw_union != y.is_raw_union, - x.custom_align != y.custom_align, + case x.field_count != y.field_count, + x.flags != y.flags, x.soa_kind != y.soa_kind, x.soa_base_type != y.soa_base_type, x.soa_len != y.soa_len: return false } - for _, i in x.types { + for i in 0.. bool { case Type_Info_Bit_Field: y := b.variant.(Type_Info_Bit_Field) or_return if !are_types_identical(x.backing_type, y.backing_type) { return false } - if len(x.names) != len(y.names) { return false } - for _, i in x.names { + if x.field_count != y.field_count { return false } + for _, i in x.names[:x.field_count] { if x.names[i] != y.names[i] { return false } @@ -368,13 +366,13 @@ is_tuple :: proc(info: ^Type_Info) -> bool { is_struct :: proc(info: ^Type_Info) -> bool { if info == nil { return false } s, ok := type_info_base(info).variant.(Type_Info_Struct) - return ok && !s.is_raw_union + return ok && .raw_union not_in s.flags } @(require_results) is_raw_union :: proc(info: ^Type_Info) -> bool { if info == nil { return false } s, ok := type_info_base(info).variant.(Type_Info_Struct) - return ok && s.is_raw_union + return ok && .raw_union in s.flags } @(require_results) is_union :: proc(info: ^Type_Info) -> bool { @@ -656,15 +654,16 @@ write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) - } io.write_string(w, "struct ", &n) or_return - if info.is_packed { io.write_string(w, "#packed ", &n) or_return } - if info.is_raw_union { io.write_string(w, "#raw_union ", &n) or_return } - if info.custom_align { + if .packed in info.flags { io.write_string(w, "#packed ", &n) or_return } + if .raw_union in info.flags { io.write_string(w, "#raw_union ", &n) or_return } + if .no_copy in info.flags { io.write_string(w, "#no_copy ", &n) or_return } + if .align in info.flags { io.write_string(w, "#align(", &n) or_return io.write_i64(w, i64(ti.align), 10, &n) or_return io.write_string(w, ") ", &n) or_return } io.write_byte(w, '{', &n) or_return - for name, i in info.names { + for name, i in info.names[:info.field_count] { if i > 0 { io.write_string(w, ", ", &n) or_return } io.write_string(w, name, &n) or_return io.write_string(w, ": ", &n) or_return @@ -722,7 +721,7 @@ write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) - io.write_string(w, "bit_field ", &n) or_return write_type(w, info.backing_type, &n) or_return io.write_string(w, " {", &n) or_return - for name, i in info.names { + for name, i in info.names[:info.field_count] { if i > 0 { io.write_string(w, ", ", &n) or_return } io.write_string(w, name, &n) or_return io.write_string(w, ": ", &n) or_return diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 9cc0552de..5d9caeba1 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -338,6 +338,15 @@ gb_internal lbValue lb_emit_source_code_location_as_global_ptr(lbProcedure *p, S return addr.addr; } +gb_internal lbValue lb_const_source_code_location_as_global_ptr(lbModule *m, String const &procedure, TokenPos const &pos) { + lbValue loc = lb_const_source_code_location_const(m, procedure, pos); + lbAddr addr = lb_add_global_generated(m, loc.type, loc, nullptr); + lb_make_global_private_const(addr); + return addr.addr; +} + + + gb_internal lbValue lb_emit_source_code_location_as_global_ptr(lbProcedure *p, Ast *node) { lbValue loc = lb_emit_source_code_location_const(p, node); diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index 2c4abbb4d..6565717a7 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -421,7 +421,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ } TokenPos pos = t->Named.type_name->token.pos; - lbValue loc = lb_const_source_code_location_const(m, proc_name, pos); + lbValue loc = lb_const_source_code_location_as_global_ptr(m, proc_name, pos); LLVMValueRef vals[4] = { lb_const_string(m, t->Named.type_name->token.string).value, @@ -810,19 +810,18 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ case Type_Struct: { tag_type = t_type_info_struct; - LLVMValueRef vals[13] = {}; + LLVMValueRef vals[11] = {}; { - lbValue is_packed = lb_const_bool(m, t_bool, t->Struct.is_packed); - lbValue is_raw_union = lb_const_bool(m, t_bool, t->Struct.is_raw_union); - lbValue is_no_copy = lb_const_bool(m, t_bool, t->Struct.is_no_copy); - lbValue is_custom_align = lb_const_bool(m, t_bool, t->Struct.custom_align != 0); - vals[5] = is_packed.value; - vals[6] = is_raw_union.value; - vals[7] = is_no_copy.value; - vals[8] = is_custom_align.value; + u8 flags = 0; + if (t->Struct.is_packed) flags |= 1<<0; + if (t->Struct.is_raw_union) flags |= 1<<1; + if (t->Struct.is_no_copy) flags |= 1<<2; + if (t->Struct.custom_align) flags |= 1<<3; + + vals[6] = lb_const_int(m, t_u8, flags).value; if (is_type_comparable(t) && !is_type_simple_compare(t)) { - vals[9] = lb_equal_proc_for_type(m, t).value; + vals[10] = lb_equal_proc_for_type(m, t).value; } @@ -831,11 +830,11 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ lbValue soa_kind = lb_const_value(m, kind_type, exact_value_i64(t->Struct.soa_kind)); LLVMValueRef soa_type = get_type_info_ptr(m, t->Struct.soa_elem); - lbValue soa_len = lb_const_int(m, t_int, t->Struct.soa_count); + lbValue soa_len = lb_const_int(m, t_u16, t->Struct.soa_count); - vals[10] = soa_kind.value; - vals[11] = soa_type; - vals[12] = soa_len.value; + vals[7] = soa_kind.value; + vals[8] = soa_len.value; + vals[9] = soa_type; } } @@ -882,12 +881,13 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ } - lbValue cv = lb_const_int(m, t_int, count); - vals[0] = llvm_const_slice(m, memory_types, cv); - vals[1] = llvm_const_slice(m, memory_names, cv); - vals[2] = llvm_const_slice(m, memory_offsets, cv); - vals[3] = llvm_const_slice(m, memory_usings, cv); - vals[4] = llvm_const_slice(m, memory_tags, cv); + lbValue cv = lb_const_int(m, t_i32, count); + vals[0] = memory_types.value; + vals[1] = memory_names.value; + vals[2] = memory_offsets.value; + vals[3] = memory_usings.value; + vals[4] = memory_tags.value; + vals[5] = cv.value; } for (isize i = 0; i < gb_count_of(vals); i++) { if (vals[i] == nullptr) { @@ -994,7 +994,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ { tag_type = t_type_info_bit_field; - LLVMValueRef vals[6] = {}; + LLVMValueRef vals[7] = {}; vals[0] = get_type_info_ptr(m, t->BitField.backing_type); isize count = t->BitField.fields.count; if (count > 0) { @@ -1035,11 +1035,12 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ } lbValue cv = lb_const_int(m, t_int, count); - vals[1] = llvm_const_slice(m, memory_names, cv); - vals[2] = llvm_const_slice(m, memory_types, cv); - vals[3] = llvm_const_slice(m, memory_bit_sizes, cv); - vals[4] = llvm_const_slice(m, memory_bit_offsets, cv); - vals[5] = llvm_const_slice(m, memory_tags, cv); + vals[1] = memory_names.value; + vals[2] = memory_types.value; + vals[3] = memory_bit_sizes.value; + vals[4] = memory_bit_offsets.value; + vals[5] = memory_tags.value; + vals[6] = cv.value; } From 251fa477afc8da08ad353935deab24fc32ec18b6 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 21:39:33 +0100 Subject: [PATCH 061/198] Fix type --- src/llvm_backend_type.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index 6565717a7..638170bfc 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -830,7 +830,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ lbValue soa_kind = lb_const_value(m, kind_type, exact_value_i64(t->Struct.soa_kind)); LLVMValueRef soa_type = get_type_info_ptr(m, t->Struct.soa_elem); - lbValue soa_len = lb_const_int(m, t_u16, t->Struct.soa_count); + lbValue soa_len = lb_const_int(m, t_i32, t->Struct.soa_count); vals[7] = soa_kind.value; vals[8] = soa_len.value; From 95a695e4cda01c8c1b12eb799c0bde8b3a282f4d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 22:23:46 +0100 Subject: [PATCH 062/198] Fix #3926 --- core/encoding/ini/ini.odin | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/core/encoding/ini/ini.odin b/core/encoding/ini/ini.odin index 91a1adcf7..a63564220 100644 --- a/core/encoding/ini/ini.odin +++ b/core/encoding/ini/ini.odin @@ -82,14 +82,16 @@ Map :: distinct map[string]map[string]string load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options := DEFAULT_OPTIONS) -> (m: Map, err: runtime.Allocator_Error) { unquote :: proc(val: string) -> (string, runtime.Allocator_Error) { - v, allocated, ok := strconv.unquote_string(val) - if !ok { - return strings.clone(val) + if strings.has_prefix(val, `"`) || strings.has_prefix(val, `'`) { + v, allocated, ok := strconv.unquote_string(val) + if !ok { + return strings.clone(val) + } + if allocated { + return v, nil + } } - if allocated { - return v, nil - } - return strings.clone(v) + return strings.clone(val) } From 9d84e00502dc413c3f00ece672be2142dcdd845b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 22:25:26 +0100 Subject: [PATCH 063/198] Clean up `unquote` code --- core/encoding/ini/ini.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/encoding/ini/ini.odin b/core/encoding/ini/ini.odin index a63564220..6723da2b3 100644 --- a/core/encoding/ini/ini.odin +++ b/core/encoding/ini/ini.odin @@ -82,7 +82,7 @@ Map :: distinct map[string]map[string]string load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options := DEFAULT_OPTIONS) -> (m: Map, err: runtime.Allocator_Error) { unquote :: proc(val: string) -> (string, runtime.Allocator_Error) { - if strings.has_prefix(val, `"`) || strings.has_prefix(val, `'`) { + if len(val) > 0 && (val[0] == '"' || val[0] == '\'') { v, allocated, ok := strconv.unquote_string(val) if !ok { return strings.clone(val) From 15fb4ded2a5c115489a43431c2409de228ebd388 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 23:09:10 +0100 Subject: [PATCH 064/198] type erase `resize` and `reserve` internals --- base/runtime/core_builtin.odin | 43 +++++++++++++++++----------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index d68eefe23..22587e467 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -729,11 +729,10 @@ clear_dynamic_array :: proc "contextless" (array: ^$T/[dynamic]$E) { // `reserve_dynamic_array` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`). // // Note: Prefer the procedure group `reserve`. -_reserve_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { - if array == nil { +_reserve_dynamic_array :: #force_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { + if a == nil { return nil } - a := (^Raw_Dynamic_Array)(array) if capacity <= a.cap { return nil @@ -744,15 +743,15 @@ _reserve_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, capacity: i } assert(a.allocator.procedure != nil) - old_size := a.cap * size_of(E) - new_size := capacity * size_of(E) + old_size := a.cap * size_of_elem + new_size := capacity * size_of_elem allocator := a.allocator new_data: []byte if should_zero { - new_data = mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return + new_data = mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return } else { - new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return + new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return } if new_data == nil && new_size > 0 { return .Out_Of_Memory @@ -765,26 +764,23 @@ _reserve_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, capacity: i @builtin reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error { - return _reserve_dynamic_array(array, capacity, true, loc) + return _reserve_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), capacity, true, loc) } @builtin non_zero_reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error { - return _reserve_dynamic_array(array, capacity, false, loc) + return _reserve_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), capacity, false, loc) } -// `resize_dynamic_array` will try to resize memory of a passed dynamic array or map to the requested element count (setting the `len`, and possibly `cap`). -// -// Note: Prefer the procedure group `resize` -_resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { - if array == nil { + +_resize_dynamic_array :: #force_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, length: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { + if a == nil { return nil } - a := (^Raw_Dynamic_Array)(array) if length <= a.cap { if should_zero && a.len < length { - intrinsics.mem_zero(([^]E)(a.data)[a.len:], (length-a.len)*size_of(E)) + intrinsics.mem_zero(([^]byte)(a.data)[a.len*size_of_elem:], (length-a.len)*size_of_elem) } a.len = max(length, 0) return nil @@ -795,15 +791,15 @@ _resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int, } assert(a.allocator.procedure != nil) - old_size := a.cap * size_of(E) - new_size := length * size_of(E) + old_size := a.cap * size_of_elem + new_size := length * size_of_elem allocator := a.allocator new_data : []byte if should_zero { - new_data = mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return + new_data = mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return } else { - new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of(E), allocator, loc) or_return + new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return } if new_data == nil && new_size > 0 { return .Out_Of_Memory @@ -815,14 +811,17 @@ _resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int, return nil } +// `resize_dynamic_array` will try to resize memory of a passed dynamic array or map to the requested element count (setting the `len`, and possibly `cap`). +// +// Note: Prefer the procedure group `resize` @builtin resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error { - return _resize_dynamic_array(array, length, true, loc=loc) + return _resize_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), length, true, loc=loc) } @builtin non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error { - return _resize_dynamic_array(array, length, false, loc=loc) + return _resize_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), length, false, loc=loc) } /* From 1c3240b6b5eecebfdbf3da191ab3b459a8df7044 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 23:09:58 +0100 Subject: [PATCH 065/198] Add `#force_no_inline` --- core/reflect/types.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/reflect/types.odin b/core/reflect/types.odin index c92d39410..4f0674dc8 100644 --- a/core/reflect/types.odin +++ b/core/reflect/types.odin @@ -493,7 +493,7 @@ write_type_builder :: proc(buf: ^strings.Builder, ti: ^Type_Info) -> int { n, _ := write_type_writer(strings.to_writer(buf), ti) return n } -write_type_writer :: proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -> (n: int, err: io.Error) { +write_type_writer :: #force_no_inline proc(w: io.Writer, ti: ^Type_Info, n_written: ^int = nil) -> (n: int, err: io.Error) { defer if n_written != nil { n_written^ += n } From b0fbaf24a015b9ce4a2473173c5103a629596317 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 23:16:11 +0100 Subject: [PATCH 066/198] Type erase `append_elem` --- base/runtime/core_builtin.odin | 58 +++++++++++++++++----------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 22587e467..a751a6151 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -440,48 +440,48 @@ delete_key :: proc(m: ^$T/map[$K]$V, key: K) -> (deleted_key: K, deleted_value: return } -_append_elem :: #force_inline proc(array: ^$T/[dynamic]$E, arg: E, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { +_append_elem :: #force_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, arg_ptr: rawptr, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { if array == nil { return 0, nil } - when size_of(E) == 0 { - array := (^Raw_Dynamic_Array)(array) - array.len += 1 - return 1, nil - } else { - if cap(array) < len(array)+1 { - // Same behavior as _append_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY. - cap := 2 * cap(array) + DEFAULT_DYNAMIC_ARRAY_CAPACITY - // do not 'or_return' here as it could be a partial success - if should_zero { - err = reserve(array, cap, loc) - } else { - err = non_zero_reserve(array, cap, loc) - } - } - if cap(array)-len(array) > 0 { - a := (^Raw_Dynamic_Array)(array) - when size_of(E) != 0 { - data := ([^]E)(a.data) - assert(data != nil, loc=loc) - data[a.len] = arg - } - a.len += 1 - return 1, err - } - return 0, err + if array.cap < array.len+1 { + // Same behavior as _append_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY. + cap := 2 * array.cap + DEFAULT_DYNAMIC_ARRAY_CAPACITY + + // do not 'or_return' here as it could be a partial success + err = _reserve_dynamic_array(array, size_of_elem, align_of_elem, cap, should_zero, loc) } + if array.cap-array.len > 0 { + assert(array.data != nil, loc=loc) + dst := ([^]byte)(array.data)[array.len*size_of_elem:] + intrinsics.mem_copy_non_overlapping(dst, arg_ptr, size_of_elem) + array.len += 1 + return 1, err + } + return 0, err } @builtin append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elem(array, arg, true, loc=loc) + when size_of(E) == 0 { + (^Raw_Dynamic_Array)(array).len += 1 + return 1, nil + } else { + arg := arg + return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, true, loc=loc) + } } @builtin non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elem(array, arg, false, loc=loc) + when size_of(E) == 0 { + (^Raw_Dynamic_Array)(array).len += 1 + return 1, nil + } else { + arg := arg + return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, false, loc=loc) + } } _append_elems :: #force_inline proc(array: ^$T/[dynamic]$E, should_zero: bool, loc := #caller_location, args: []E) -> (n: int, err: Allocator_Error) #optional_allocator_error { From 83b7dd122a533cf60673e7afc3d86e8332845ff9 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 23:20:50 +0100 Subject: [PATCH 067/198] Type erase for `append_elems` --- base/runtime/core_builtin.odin | 65 +++++++++++++++++----------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index a751a6151..d82164a00 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -453,9 +453,10 @@ _append_elem :: #force_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, alig err = _reserve_dynamic_array(array, size_of_elem, align_of_elem, cap, should_zero, loc) } if array.cap-array.len > 0 { - assert(array.data != nil, loc=loc) - dst := ([^]byte)(array.data)[array.len*size_of_elem:] - intrinsics.mem_copy_non_overlapping(dst, arg_ptr, size_of_elem) + data := ([^]byte)(array.data) + assert(data != nil, loc=loc) + data = data[array.len*size_of_elem:] + intrinsics.mem_copy_non_overlapping(data, arg_ptr, size_of_elem) array.len += 1 return 1, err } @@ -484,53 +485,53 @@ non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc : } } -_append_elems :: #force_inline proc(array: ^$T/[dynamic]$E, should_zero: bool, loc := #caller_location, args: []E) -> (n: int, err: Allocator_Error) #optional_allocator_error { +_append_elems :: #force_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, should_zero: bool, loc := #caller_location, args: rawptr, arg_len: int) -> (n: int, err: Allocator_Error) #optional_allocator_error { if array == nil { return 0, nil } - arg_len := len(args) if arg_len <= 0 { return 0, nil } - when size_of(E) == 0 { - array := (^Raw_Dynamic_Array)(array) - array.len += arg_len - return arg_len, nil - } else { - if cap(array) < len(array)+arg_len { - cap := 2 * cap(array) + max(DEFAULT_DYNAMIC_ARRAY_CAPACITY, arg_len) + if array.cap < array.len+arg_len { + cap := 2 * array.cap + max(DEFAULT_DYNAMIC_ARRAY_CAPACITY, arg_len) - // do not 'or_return' here as it could be a partial success - if should_zero { - err = reserve(array, cap, loc) - } else { - err = non_zero_reserve(array, cap, loc) - } - } - arg_len = min(cap(array)-len(array), arg_len) - if arg_len > 0 { - a := (^Raw_Dynamic_Array)(array) - when size_of(E) != 0 { - data := ([^]E)(a.data) - assert(data != nil, loc=loc) - intrinsics.mem_copy(&data[a.len], raw_data(args), size_of(E) * arg_len) - } - a.len += arg_len - } - return arg_len, err + // do not 'or_return' here as it could be a partial success + err = _reserve_dynamic_array(array, size_of_elem, align_of_elem, cap, should_zero, loc) } + arg_len := arg_len + arg_len = min(array.cap-array.len, arg_len) + if arg_len > 0 { + data := ([^]byte)(array.data) + assert(data != nil, loc=loc) + data = data[array.len*size_of_elem:] + intrinsics.mem_copy(data, args, size_of_elem * arg_len) // must be mem_copy (overlapping) + array.len += arg_len + } + return arg_len, err } @builtin append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elems(array, true, loc, args) + when size_of(E) == 0 { + a := (^Raw_Dynamic_Array)(array) + a.len += len(args) + return len(args), nil + } else { + return _append_elems((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), true, loc, raw_data(args), len(args)) + } } @builtin non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - return _append_elems(array, false, loc, args) + when size_of(E) == 0 { + a := (^Raw_Dynamic_Array)(array) + a.len += len(args) + return len(args), nil + } else { + return _append_elems((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), false, loc, raw_data(args), len(args)) + } } // The append_string built-in procedure appends a string to the end of a [dynamic]u8 like type From e768bddaeb772f5d5a1b51f1453ae58f918a50a3 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 23:22:22 +0100 Subject: [PATCH 068/198] Inline `_append_elem_string` further --- base/runtime/core_builtin.odin | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index d82164a00..273f237d7 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -536,12 +536,7 @@ non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, l // The append_string built-in procedure appends a string to the end of a [dynamic]u8 like type _append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { - args := transmute([]E)arg - if should_zero { - return append_elems(array, ..args, loc=loc) - } else { - return non_zero_append_elems(array, ..args, loc=loc) - } + return _append_elems((^Raw_Dynamic_Array)(array), 1, 1, should_zero, loc, raw_data(arg), len(arg)) } @builtin From 26a9416a4158f3cdb3b4ab9b5809e2e358118280 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 23:33:35 +0100 Subject: [PATCH 069/198] Minor clean ups --- base/runtime/core_builtin.odin | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 273f237d7..f36204c8b 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -442,7 +442,7 @@ delete_key :: proc(m: ^$T/map[$K]$V, key: K) -> (deleted_key: K, deleted_value: _append_elem :: #force_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, arg_ptr: rawptr, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { if array == nil { - return 0, nil + return } if array.cap < array.len+1 { @@ -458,9 +458,9 @@ _append_elem :: #force_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, alig data = data[array.len*size_of_elem:] intrinsics.mem_copy_non_overlapping(data, arg_ptr, size_of_elem) array.len += 1 - return 1, err + n = 1 } - return 0, err + return } @builtin @@ -832,10 +832,13 @@ non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: i Note: Prefer the procedure group `shrink` */ shrink_dynamic_array :: proc(array: ^$T/[dynamic]$E, new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { - if array == nil { + return _shrink_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), new_cap, loc) +} + +_shrink_dynamic_array :: proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { + if a == nil { return } - a := (^Raw_Dynamic_Array)(array) new_cap := new_cap if new_cap >= 0 else a.len @@ -848,10 +851,10 @@ shrink_dynamic_array :: proc(array: ^$T/[dynamic]$E, new_cap := -1, loc := #call } assert(a.allocator.procedure != nil) - old_size := a.cap * size_of(E) - new_size := new_cap * size_of(E) + old_size := a.cap * size_of_elem + new_size := new_cap * size_of_elem - new_data := mem_resize(a.data, old_size, new_size, align_of(E), a.allocator, loc) or_return + new_data := mem_resize(a.data, old_size, new_size, align_of_elem, a.allocator, loc) or_return a.data = raw_data(new_data) a.len = min(new_cap, a.len) From f657055f12c546767bfd74de6b6d2b2329dd8ea7 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 23:36:54 +0100 Subject: [PATCH 070/198] Add `slice` variable if not exists --- src/llvm_backend_proc.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 5270d6c30..001a6f27c 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3496,6 +3496,10 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { base_array_ptr = p->variadic_reuse_base_array_ptr.addr; } + if (slice.addr.value == nullptr) { + slice = lb_add_local_generated(p, slice_type, true); + } + GB_ASSERT(base_array_ptr.value != nullptr); GB_ASSERT(slice.addr.value != nullptr); From f7cb711874afba4a1740752432960a208902ee6e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 23:48:33 +0100 Subject: [PATCH 071/198] Add `#force_no_inline` --- base/runtime/dynamic_map_internal.odin | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/base/runtime/dynamic_map_internal.odin b/base/runtime/dynamic_map_internal.odin index 5ad155400..3dded7716 100644 --- a/base/runtime/dynamic_map_internal.odin +++ b/base/runtime/dynamic_map_internal.odin @@ -577,7 +577,7 @@ map_grow_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Inf @(require_results) -map_reserve_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uintptr, loc := #caller_location) -> Allocator_Error { +map_reserve_dynamic :: #force_no_inline proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uintptr, loc := #caller_location) -> Allocator_Error { @(require_results) ceil_log2 :: #force_inline proc "contextless" (x: uintptr) -> uintptr { z := intrinsics.count_leading_zeros(x) @@ -641,7 +641,7 @@ map_reserve_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_ @(require_results) -map_shrink_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { +map_shrink_dynamic :: #force_no_inline proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { if m.allocator.procedure == nil { m.allocator = context.allocator } @@ -688,7 +688,7 @@ map_shrink_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_I } @(require_results) -map_free_dynamic :: proc "odin" (m: Raw_Map, info: ^Map_Info, loc := #caller_location) -> Allocator_Error { +map_free_dynamic :: #force_no_inline proc "odin" (m: Raw_Map, info: ^Map_Info, loc := #caller_location) -> Allocator_Error { ptr := rawptr(map_data(m)) size := int(map_total_allocation_size(uintptr(map_cap(m)), info)) err := mem_free_with_size(ptr, size, m.allocator, loc) @@ -700,7 +700,7 @@ map_free_dynamic :: proc "odin" (m: Raw_Map, info: ^Map_Info, loc := #caller_loc } @(require_results) -map_lookup_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (index: uintptr, ok: bool) { +map_lookup_dynamic :: #force_no_inline proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (index: uintptr, ok: bool) { if map_len(m) == 0 { return 0, false } @@ -723,7 +723,7 @@ map_lookup_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, } } @(require_results) -map_exists_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (ok: bool) { +map_exists_dynamic :: #force_no_inline proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (ok: bool) { if map_len(m) == 0 { return false } @@ -749,7 +749,7 @@ map_exists_dynamic :: proc "contextless" (m: Raw_Map, #no_alias info: ^Map_Info, @(require_results) -map_erase_dynamic :: #force_inline proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (old_k, old_v: uintptr, ok: bool) { +map_erase_dynamic :: #force_no_inline proc "contextless" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, k: uintptr) -> (old_k, old_v: uintptr, ok: bool) { index := map_lookup_dynamic(m^, info, k) or_return ks, vs, hs, _, _ := map_kvh_data_dynamic(m^, info) hs[index] |= TOMBSTONE_MASK From e3e31b42d0c7bfeaed053cd0c706820ef0cf1778 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 15 Jul 2024 00:54:08 +0200 Subject: [PATCH 072/198] #force_no_inline build_huffman to avoid bloat --- core/compress/zlib/zlib.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/compress/zlib/zlib.odin b/core/compress/zlib/zlib.odin index 005267d15..c7ae9e9c8 100644 --- a/core/compress/zlib/zlib.odin +++ b/core/compress/zlib/zlib.odin @@ -235,7 +235,7 @@ allocate_huffman_table :: proc(allocator := context.allocator) -> (z: ^Huffman_T } @(optimization_mode="favor_size") -build_huffman :: proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) { +build_huffman :: #force_no_inline proc(z: ^Huffman_Table, code_lengths: []u8) -> (err: Error) { sizes: [HUFFMAN_MAX_BITS+1]int next_code: [HUFFMAN_MAX_BITS+1]int @@ -670,4 +670,4 @@ inflate_from_byte_array_raw :: proc(input: []u8, buf: ^bytes.Buffer, raw := fals return inflate_raw(&ctx, expected_output_size=expected_output_size) } -inflate :: proc{inflate_from_context, inflate_from_byte_array} +inflate :: proc{inflate_from_context, inflate_from_byte_array} \ No newline at end of file From 196ac7e6d674ee8f9d222b18a1f510d3888195f2 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 00:01:26 +0100 Subject: [PATCH 073/198] Type erase `_make_dynamic_array_len_cap` --- base/runtime/core_builtin.odin | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index f36204c8b..38ad95be8 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -333,16 +333,23 @@ make_dynamic_array_len :: proc($T: typeid/[dynamic]$E, #any_int len: int, alloca // Note: Prefer using the procedure group `make`. @(builtin, require_results) make_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { - make_dynamic_array_error_loc(loc, len, cap) - array.allocator = allocator // initialize allocator before just in case it fails to allocate any memory - data := mem_alloc_bytes(size_of(E)*cap, align_of(E), allocator, loc) or_return - s := Raw_Dynamic_Array{raw_data(data), len, cap, allocator} - if data == nil && size_of(E) != 0 { - s.len, s.cap = 0, 0 - } - array = transmute(T)s + err = _make_dynamic_array_len_cap((^Raw_Dynamic_Array)(&array), size_of(E), align_of(E), len, cap, allocator, loc) return } + +@(require_results) +_make_dynamic_array_len_cap :: proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (err: Allocator_Error) { + make_dynamic_array_error_loc(loc, len, cap) + array.allocator = allocator // initialize allocator before just in case it fails to allocate any memory + data := mem_alloc_bytes(size_of_elem*cap, align_of_elem, allocator, loc) or_return + use_zero := data == nil && size_of_elem != 0 + array.data = raw_data(data) + array.len = 0 if use_zero else len + array.cap = 0 if use_zero else cap + array.allocator = allocator + return +} + // `make_map` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. // Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. // From 139c1bcdda68c30c56ae26a9715a38074b9a1129 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 00:25:41 +0100 Subject: [PATCH 074/198] Comment out debug code --- src/parser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index a6a146cfd..4924dd37d 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -112,7 +112,7 @@ gb_internal isize ast_node_size(AstKind kind) { } -gb_global std::atomic global_total_node_memory_allocated; +// gb_global std::atomic global_total_node_memory_allocated; // NOTE(bill): And this below is why is I/we need a new language! Discriminated unions are a pain in C/C++ gb_internal Ast *alloc_ast_node(AstFile *f, AstKind kind) { @@ -122,7 +122,7 @@ gb_internal Ast *alloc_ast_node(AstFile *f, AstKind kind) { node->kind = kind; node->file_id = f ? f->id : 0; - global_total_node_memory_allocated.fetch_add(size); + // global_total_node_memory_allocated.fetch_add(size); return node; } From 018026d844c8ad3b625f019acee470dbb865d085 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 00:36:00 +0100 Subject: [PATCH 075/198] Use `gb_zero_*` calls --- src/checker.cpp | 6 +++--- src/common_memory.cpp | 7 ------- src/gb/gb.h | 2 +- src/parser.cpp | 2 +- src/types.cpp | 2 +- 5 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/checker.cpp b/src/checker.cpp index 336440d32..0a671cc2d 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -658,7 +658,7 @@ gb_internal bool check_vet_shadowing(Checker *c, Entity *e, VettedEntity *ve) { } } - zero_item(ve); + gb_zero_item(ve); ve->kind = VettedEntity_Shadowed; ve->entity = e; ve->other = shadowed; @@ -677,7 +677,7 @@ gb_internal bool check_vet_unused(Checker *c, Entity *e, VettedEntity *ve) { } case Entity_ImportName: case Entity_LibraryName: - zero_item(ve); + gb_zero_item(ve); ve->kind = VettedEntity_Unused; ve->entity = e; return true; @@ -1389,7 +1389,7 @@ gb_internal void reset_checker_context(CheckerContext *ctx, AstFile *file, Untyp auto type_path = ctx->type_path; array_clear(type_path); - zero_size(&ctx->pkg, gb_size_of(CheckerContext) - gb_offset_of(CheckerContext, pkg)); + gb_zero_size(&ctx->pkg, gb_size_of(CheckerContext) - gb_offset_of(CheckerContext, pkg)); ctx->file = nullptr; ctx->scope = builtin_pkg->scope; diff --git a/src/common_memory.cpp b/src/common_memory.cpp index 60e570eee..42a2125dc 100644 --- a/src/common_memory.cpp +++ b/src/common_memory.cpp @@ -2,13 +2,6 @@ #include #endif -gb_internal gb_inline void zero_size(void *ptr, isize len) { - memset(ptr, 0, len); -} - -#define zero_item(ptr) zero_size((ptr), gb_size_of(ptr)) - - template gb_internal gb_inline U bit_cast(V &v) { return reinterpret_cast(v); } diff --git a/src/gb/gb.h b/src/gb/gb.h index 22a30a04b..38dabc9bd 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -2534,7 +2534,7 @@ gb_inline void const *gb_pointer_add_const(void const *ptr, isize bytes) { gb_inline void const *gb_pointer_sub_const(void const *ptr, isize bytes) { return cast(void const *)(cast(u8 const *)ptr - bytes); } gb_inline isize gb_pointer_diff (void const *begin, void const *end) { return cast(isize)(cast(u8 const *)end - cast(u8 const *)begin); } -gb_inline void gb_zero_size(void *ptr, isize size) { gb_memset(ptr, 0, size); } +gb_inline void gb_zero_size(void *ptr, isize size) { memset(ptr, 0, size); } #if defined(_MSC_VER) && !defined(__clang__) diff --git a/src/parser.cpp b/src/parser.cpp index 4924dd37d..02c37815b 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5413,7 +5413,7 @@ gb_internal ParseFileError init_ast_file(AstFile *f, String const &fullpath, Tok if (!string_ends_with(f->fullpath, str_lit(".odin"))) { return ParseFile_WrongExtension; } - zero_item(&f->tokenizer); + gb_zero_item(&f->tokenizer); f->tokenizer.curr_file_id = f->id; TokenizerInitError err = init_tokenizer_from_fullpath(&f->tokenizer, f->fullpath, build_context.copy_file_contents); diff --git a/src/types.cpp b/src/types.cpp index 92b187cdb..fdc174d81 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -964,7 +964,7 @@ gb_internal Type *alloc_type(TypeKind kind) { // gbAllocator a = heap_allocator(); gbAllocator a = permanent_allocator(); Type *t = gb_alloc_item(a, Type); - zero_item(t); + gb_zero_item(t); t->kind = kind; t->cached_size = -1; t->cached_align = -1; From 3311ea1c7667364af9cd3f233d1a774e50879e3c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 00:38:10 +0100 Subject: [PATCH 076/198] Keep MSVC happy with secure versions of C calls --- src/cached.cpp | 16 ++++++++-------- src/checker.cpp | 4 ++++ src/common.cpp | 2 ++ src/main.cpp | 10 ---------- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/cached.cpp b/src/cached.cpp index 7f213ba21..4ad65ee9e 100644 --- a/src/cached.cpp +++ b/src/cached.cpp @@ -17,11 +17,11 @@ gb_internal bool recursively_delete_directory(wchar_t *wpath_c) { wchar_t dir_path[MAX_PATH] = {}; wchar_t filename[MAX_PATH] = {}; - wcscpy(dir_path, wpath_c); - wcscat(dir_path, L"\\*"); + wcscpy_s(dir_path, wpath_c); + wcscat_s(dir_path, L"\\*"); - wcscpy(filename, wpath_c); - wcscat(filename, L"\\"); + wcscpy_s(filename, wpath_c); + wcscat_s(filename, L"\\"); WIN32_FIND_DATAW find_file_data = {}; @@ -31,21 +31,21 @@ gb_internal bool recursively_delete_directory(wchar_t *wpath_c) { } defer (FindClose(hfind)); - wcscpy(dir_path, filename); + wcscpy_s(dir_path, filename); for (;;) { if (FindNextFileW(hfind, &find_file_data)) { if (is_dots_w(find_file_data.cFileName)) { continue; } - wcscat(filename, find_file_data.cFileName); + wcscat_s(filename, find_file_data.cFileName); if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if (!recursively_delete_directory(filename)) { return false; } RemoveDirectoryW(filename); - wcscpy(filename, dir_path); + wcscpy_s(filename, dir_path); } else { if (find_file_data.dwFileAttributes & FILE_ATTRIBUTE_READONLY) { _wchmod(filename, _S_IWRITE); @@ -53,7 +53,7 @@ gb_internal bool recursively_delete_directory(wchar_t *wpath_c) { if (!DeleteFileW(filename)) { return false; } - wcscpy(filename, dir_path); + wcscpy_s(filename, dir_path); } } else { if (GetLastError() == ERROR_NO_MORE_FILES) { diff --git a/src/checker.cpp b/src/checker.cpp index 0a671cc2d..0e65af211 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1117,7 +1117,11 @@ gb_internal void init_universal(void) { int minimum_os_version = 0; if (build_context.minimum_os_version_string != "") { int major, minor, revision = 0; + #if defined(GB_SYSTEM_WINDOWS) + sscanf_s(cast(const char *)(build_context.minimum_os_version_string.text), "%d.%d.%d", &major, &minor, &revision); + #else sscanf(cast(const char *)(build_context.minimum_os_version_string.text), "%d.%d.%d", &major, &minor, &revision); + #endif minimum_os_version = (major*10000)+(minor*100)+revision; } add_global_constant("ODIN_MINIMUM_OS_VERSION", t_untyped_integer, exact_value_i64(minimum_os_version)); diff --git a/src/common.cpp b/src/common.cpp index 69426e2a6..0ef39bd10 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -14,6 +14,8 @@ #undef NOMINMAX #endif +#include + #define GB_WINDOWS_H_INCLUDED #define GB_IMPLEMENTATION #include "gb/gb.h" diff --git a/src/main.cpp b/src/main.cpp index 388184be9..ef9cad873 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -399,8 +399,6 @@ enum BuildFlagKind { BuildFlag_Sanitize, - BuildFlag_FastBuild, - #if defined(GB_SYSTEM_WINDOWS) BuildFlag_IgnoreVsSearch, BuildFlag_ResourceFile, @@ -607,8 +605,6 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_Sanitize, str_lit("sanitize"), BuildFlagParam_String, Command__does_build, true); - add_flag(&build_flags, BuildFlag_FastBuild, str_lit("fast-build"), BuildFlagParam_None, Command__does_build); - #if defined(GB_SYSTEM_WINDOWS) add_flag(&build_flags, BuildFlag_IgnoreVsSearch, str_lit("ignore-vs-search"), BuildFlagParam_None, Command__does_build); @@ -1447,12 +1443,6 @@ gb_internal bool parse_build_flags(Array args) { break; - case BuildFlag_FastBuild: - build_context.custom_optimization_level = true; - build_context.optimization_level = -1; - build_context.use_separate_modules = true; - break; - #if defined(GB_SYSTEM_WINDOWS) case BuildFlag_IgnoreVsSearch: { GB_ASSERT(value.kind == ExactValue_Invalid); From c64702ae5ad57240743fc425bb5029fb9c1cc02e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 00:43:46 +0100 Subject: [PATCH 077/198] Make `-use-separate-modules` the default behaviour for `-o:none` and `-o:minimal` --- src/build_settings.cpp | 7 +++++++ src/main.cpp | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 32640d732..47f7bf5ba 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1658,6 +1658,13 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta bc->optimization_level = gb_clamp(bc->optimization_level, -1, 3); + if (bc->optimization_level <= 0) { + if (!is_arch_wasm()) { + bc->use_separate_modules = true; + } + } + + // TODO: Static map calls are bugged on `amd64sysv` abi. if (bc->metrics.os != TargetOs_windows && bc->metrics.arch == TargetArch_amd64) { // ENFORCE DYNAMIC MAP CALLS diff --git a/src/main.cpp b/src/main.cpp index ef9cad873..da8322476 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2312,9 +2312,9 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(0, ""); print_usage_line(1, "-use-separate-modules"); - print_usage_line(1, "[EXPERIMENTAL]"); print_usage_line(2, "The backend generates multiple build units which are then linked together."); print_usage_line(2, "Normally, a single build unit is generated for a standard project."); + print_usage_line(2, "This is the default behaviour for '-o:none' and '-o:minimal' builds"); print_usage_line(0, ""); } From a8f84c87ae7b4d30cf197f54cbc05da024a17d24 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 01:05:29 +0100 Subject: [PATCH 078/198] Add the permanent and temporary arenas directly on the `Thread` --- src/common_memory.cpp | 73 +++++++++++++++++++++++++++++++++++++++---- src/thread_pool.cpp | 5 ++- src/threading.cpp | 11 +++++-- 3 files changed, 80 insertions(+), 9 deletions(-) diff --git a/src/common_memory.cpp b/src/common_memory.cpp index 42a2125dc..95803d3c8 100644 --- a/src/common_memory.cpp +++ b/src/common_memory.cpp @@ -66,6 +66,14 @@ gb_internal isize arena_align_forward_offset(Arena *arena, isize alignment) { return alignment_offset; } +gb_internal void thread_init_arenas(Thread *t) { + t->permanent_arena = gb_alloc_item(heap_allocator(), Arena); + t->temporary_arena = gb_alloc_item(heap_allocator(), Arena); + + t->permanent_arena->minimum_block_size = DEFAULT_MINIMUM_BLOCK_SIZE; + t->temporary_arena->minimum_block_size = DEFAULT_MINIMUM_BLOCK_SIZE; +} + gb_internal void *arena_alloc(Arena *arena, isize min_size, isize alignment) { GB_ASSERT(gb_is_power_of_two(alignment)); @@ -363,14 +371,67 @@ gb_internal GB_ALLOCATOR_PROC(arena_allocator_proc) { } -gb_global gb_thread_local Arena permanent_arena = {nullptr, DEFAULT_MINIMUM_BLOCK_SIZE}; -gb_internal gbAllocator permanent_allocator() { - return arena_allocator(&permanent_arena); +enum ThreadArenaKind : uintptr { + ThreadArena_Permanent, + ThreadArena_Temporary, +}; + +gb_global Arena default_permanent_arena = {nullptr, DEFAULT_MINIMUM_BLOCK_SIZE}; +gb_global Arena default_temporary_arena = {nullptr, DEFAULT_MINIMUM_BLOCK_SIZE}; + + +gb_internal Thread *get_current_thread(void); + +gb_internal Arena *get_arena(ThreadArenaKind kind) { + Thread *t = get_current_thread(); + switch (kind) { + case ThreadArena_Permanent: return t ? t->permanent_arena : &default_permanent_arena; + case ThreadArena_Temporary: return t ? t->temporary_arena : &default_temporary_arena; + } + GB_PANIC("INVALID ARENA KIND"); + return nullptr; +} + + + +gb_internal GB_ALLOCATOR_PROC(thread_arena_allocator_proc) { + void *ptr = nullptr; + ThreadArenaKind kind = cast(ThreadArenaKind)cast(uintptr)allocator_data; + Arena *arena = get_arena(kind); + + switch (type) { + case gbAllocation_Alloc: + ptr = arena_alloc(arena, size, alignment); + break; + case gbAllocation_Free: + break; + case gbAllocation_Resize: + if (size == 0) { + ptr = nullptr; + } else if (size <= old_size) { + ptr = old_memory; + } else { + ptr = arena_alloc(arena, size, alignment); + gb_memmove(ptr, old_memory, old_size); + } + break; + case gbAllocation_FreeAll: + GB_PANIC("use arena_free_all directly"); + arena_free_all(arena); + break; + } + + return ptr; +} + + + +gb_internal gbAllocator permanent_allocator() { + return {thread_arena_allocator_proc, cast(void *)cast(uintptr)ThreadArena_Permanent}; } -gb_global gb_thread_local Arena temporary_arena = {nullptr, DEFAULT_MINIMUM_BLOCK_SIZE}; gb_internal gbAllocator temporary_allocator() { - return arena_allocator(&temporary_arena); + return {thread_arena_allocator_proc, cast(void *)cast(uintptr)ThreadArena_Permanent}; } @@ -378,7 +439,7 @@ gb_internal gbAllocator temporary_allocator() { // #define TEMPORARY_ALLOCATOR_GUARD() -#define TEMPORARY_ALLOCATOR_GUARD() TEMP_ARENA_GUARD(&temporary_arena) +#define TEMPORARY_ALLOCATOR_GUARD() TEMP_ARENA_GUARD(get_arena(ThreadArena_Temporary)) #define PERMANENT_ALLOCATOR_GUARD() diff --git a/src/thread_pool.cpp b/src/thread_pool.cpp index 62cca6de6..8363a4553 100644 --- a/src/thread_pool.cpp +++ b/src/thread_pool.cpp @@ -3,7 +3,10 @@ struct WorkerTask; struct ThreadPool; -gb_thread_local Thread *current_thread; +gb_global gb_thread_local Thread *current_thread; +gb_internal Thread *get_current_thread(void) { + return current_thread; +} gb_internal void thread_pool_init(ThreadPool *pool, isize worker_count, char const *worker_name); gb_internal void thread_pool_destroy(ThreadPool *pool); diff --git a/src/threading.cpp b/src/threading.cpp index ff0fdfcde..c622ac87e 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -70,6 +70,9 @@ struct Thread { struct TaskQueue queue; struct ThreadPool *pool; + + struct Arena *permanent_arena; + struct Arena *temporary_arena; }; typedef std::atomic Futex; @@ -560,18 +563,20 @@ gb_internal void *internal_thread_proc(void *arg) { } #endif -TaskRingBuffer *task_ring_init(isize size) { +gb_internal TaskRingBuffer *task_ring_init(isize size) { TaskRingBuffer *ring = gb_alloc_item(heap_allocator(), TaskRingBuffer); ring->size = size; ring->buffer = gb_alloc_array(heap_allocator(), WorkerTask, ring->size); return ring; } -void thread_queue_destroy(TaskQueue *q) { +gb_internal void thread_queue_destroy(TaskQueue *q) { gb_free(heap_allocator(), (*q->ring).buffer); gb_free(heap_allocator(), q->ring); } +gb_internal void thread_init_arenas(Thread *t); + gb_internal void thread_init(ThreadPool *pool, Thread *t, isize idx) { gb_zero_item(t); #if defined(GB_SYSTEM_WINDOWS) @@ -584,6 +589,8 @@ gb_internal void thread_init(ThreadPool *pool, Thread *t, isize idx) { t->queue.ring = task_ring_init(1 << 14); t->pool = pool; t->idx = idx; + + thread_init_arenas(t); } gb_internal void thread_init_and_start(ThreadPool *pool, Thread *t, isize idx) { From cae8c1e94f6e3e899706972e2cd0ac1e2c912965 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 01:15:00 +0100 Subject: [PATCH 079/198] Minimize use of mutex in `Arena` --- src/common_memory.cpp | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/common_memory.cpp b/src/common_memory.cpp index 95803d3c8..bfe4c2e68 100644 --- a/src/common_memory.cpp +++ b/src/common_memory.cpp @@ -32,6 +32,8 @@ gb_internal void virtual_memory_init(void) { } +gb_internal Thread *get_current_thread(void); + struct MemoryBlock { MemoryBlock *prev; @@ -45,6 +47,7 @@ struct Arena { isize minimum_block_size; BlockingMutex mutex; isize temp_count; + Thread * parent_thread; }; enum { DEFAULT_MINIMUM_BLOCK_SIZE = 8ll*1024ll*1024ll }; @@ -70,6 +73,9 @@ gb_internal void thread_init_arenas(Thread *t) { t->permanent_arena = gb_alloc_item(heap_allocator(), Arena); t->temporary_arena = gb_alloc_item(heap_allocator(), Arena); + t->permanent_arena->parent_thread = t; + t->temporary_arena->parent_thread = t; + t->permanent_arena->minimum_block_size = DEFAULT_MINIMUM_BLOCK_SIZE; t->temporary_arena->minimum_block_size = DEFAULT_MINIMUM_BLOCK_SIZE; } @@ -77,7 +83,11 @@ gb_internal void thread_init_arenas(Thread *t) { gb_internal void *arena_alloc(Arena *arena, isize min_size, isize alignment) { GB_ASSERT(gb_is_power_of_two(alignment)); - mutex_lock(&arena->mutex); + if (arena->parent_thread == nullptr) { + mutex_lock(&arena->mutex); + } else { + GB_ASSERT(arena->parent_thread == get_current_thread()); + } isize size = 0; if (arena->curr_block != nullptr) { @@ -104,7 +114,9 @@ gb_internal void *arena_alloc(Arena *arena, isize min_size, isize alignment) { curr_block->used += size; GB_ASSERT(curr_block->used <= curr_block->size); - mutex_unlock(&arena->mutex); + if (arena->parent_thread == nullptr) { + mutex_unlock(&arena->mutex); + } // NOTE(bill): memory will be zeroed by default due to virtual memory return ptr; @@ -260,7 +272,7 @@ struct ArenaTemp { ArenaTemp arena_temp_begin(Arena *arena) { GB_ASSERT(arena); - MUTEX_GUARD(&arena->mutex); + GB_ASSERT(arena->parent_thread == get_current_thread()); ArenaTemp temp = {}; temp.arena = arena; @@ -275,7 +287,7 @@ ArenaTemp arena_temp_begin(Arena *arena) { void arena_temp_end(ArenaTemp const &temp) { GB_ASSERT(temp.arena); Arena *arena = temp.arena; - MUTEX_GUARD(&arena->mutex); + GB_ASSERT(arena->parent_thread == get_current_thread()); if (temp.block) { bool memory_block_found = false; @@ -311,7 +323,7 @@ void arena_temp_end(ArenaTemp const &temp) { void arena_temp_ignore(ArenaTemp const &temp) { GB_ASSERT(temp.arena); Arena *arena = temp.arena; - MUTEX_GUARD(&arena->mutex); + GB_ASSERT(arena->parent_thread == get_current_thread()); GB_ASSERT_MSG(arena->temp_count > 0, "double-use of arena_temp_end"); arena->temp_count -= 1; @@ -380,8 +392,6 @@ gb_global Arena default_permanent_arena = {nullptr, DEFAULT_MINIMUM_BLOCK_SIZE}; gb_global Arena default_temporary_arena = {nullptr, DEFAULT_MINIMUM_BLOCK_SIZE}; -gb_internal Thread *get_current_thread(void); - gb_internal Arena *get_arena(ThreadArenaKind kind) { Thread *t = get_current_thread(); switch (kind) { From e4ba786948e8f3abe89ff2ec6b7618f4af2b21cb Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 01:29:57 +0100 Subject: [PATCH 080/198] Remove use of mutex in single threaded code --- src/check_decl.cpp | 3 +-- src/checker.cpp | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 6828774e4..a1436fe03 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -182,8 +182,7 @@ gb_internal void override_entity_in_scope(Entity *original_entity, Entity *new_e original_entity->type = new_entity->type; original_entity->aliased_of = new_entity; - Ast *empty_ident = nullptr; - original_entity->identifier.compare_exchange_strong(empty_ident, new_entity->identifier); + original_entity->identifier.store(new_entity->identifier); if (original_entity->identifier.load() != nullptr && original_entity->identifier.load()->kind == Ast_Ident) { diff --git a/src/checker.cpp b/src/checker.cpp index 0e65af211..cc39e9a44 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -384,6 +384,8 @@ gb_internal Entity *scope_lookup_current(Scope *s, String const &name) { return nullptr; } +gb_global bool in_single_threaded_mode_scopes = false; + gb_internal void scope_lookup_parent(Scope *scope, String const &name, Scope **scope_, Entity **entity_) { if (scope != nullptr) { bool gone_thru_proc = false; @@ -391,9 +393,9 @@ gb_internal void scope_lookup_parent(Scope *scope, String const &name, Scope **s StringHashKey key = string_hash_string(name); for (Scope *s = scope; s != nullptr; s = s->parent) { Entity **found = nullptr; - rw_mutex_shared_lock(&s->mutex); + if (!in_single_threaded_mode_scopes) rw_mutex_shared_lock(&s->mutex); found = string_map_get(&s->elements, key); - rw_mutex_shared_unlock(&s->mutex); + if (!in_single_threaded_mode_scopes) rw_mutex_shared_unlock(&s->mutex); if (found) { Entity *e = *found; if (gone_thru_proc) { @@ -513,7 +515,11 @@ end:; gb_internal Entity *scope_insert(Scope *s, Entity *entity) { String name = entity->token.string; - return scope_insert_with_name(s, name, entity); + if (in_single_threaded_mode_scopes) { + return scope_insert_with_name_no_mutex(s, name, entity); + } else { + return scope_insert_with_name(s, name, entity); + } } gb_internal Entity *scope_insert_no_mutex(Scope *s, Entity *entity) { @@ -1795,8 +1801,7 @@ gb_internal void add_entity_use(CheckerContext *c, Ast *identifier, Entity *enti if (identifier == nullptr || identifier->kind != Ast_Ident) { return; } - Ast *empty_ident = nullptr; - entity->identifier.compare_exchange_strong(empty_ident, identifier); + entity->identifier.store(identifier); identifier->Ident.entity = entity; @@ -4591,6 +4596,8 @@ gb_internal void check_single_global_entity(Checker *c, Entity *e, DeclInfo *d) } gb_internal void check_all_global_entities(Checker *c) { + in_single_threaded_mode_scopes = true; + // NOTE(bill): This must be single threaded // Don't bother trying for_array(i, c->info.entities) { @@ -4610,6 +4617,8 @@ gb_internal void check_all_global_entities(Checker *c) { (void)type_align_of(e->type); } } + + in_single_threaded_mode_scopes = false; } From a45e05bb180ad5ac3f9bc4199ebbf0b3bcadbf25 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 01:36:54 +0100 Subject: [PATCH 081/198] Remove need for `BlockingMutex` in `Arena` --- src/common_memory.cpp | 15 +++------------ src/parser.cpp | 2 +- src/parser.hpp | 4 +--- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/src/common_memory.cpp b/src/common_memory.cpp index bfe4c2e68..47b2796a9 100644 --- a/src/common_memory.cpp +++ b/src/common_memory.cpp @@ -45,7 +45,7 @@ struct MemoryBlock { struct Arena { MemoryBlock * curr_block; isize minimum_block_size; - BlockingMutex mutex; + // BlockingMutex mutex; isize temp_count; Thread * parent_thread; }; @@ -82,12 +82,7 @@ gb_internal void thread_init_arenas(Thread *t) { gb_internal void *arena_alloc(Arena *arena, isize min_size, isize alignment) { GB_ASSERT(gb_is_power_of_two(alignment)); - - if (arena->parent_thread == nullptr) { - mutex_lock(&arena->mutex); - } else { - GB_ASSERT(arena->parent_thread == get_current_thread()); - } + GB_ASSERT(arena->parent_thread == get_current_thread()); isize size = 0; if (arena->curr_block != nullptr) { @@ -113,11 +108,7 @@ gb_internal void *arena_alloc(Arena *arena, isize min_size, isize alignment) { curr_block->used += size; GB_ASSERT(curr_block->used <= curr_block->size); - - if (arena->parent_thread == nullptr) { - mutex_unlock(&arena->mutex); - } - + // NOTE(bill): memory will be zeroed by default due to virtual memory return ptr; } diff --git a/src/parser.cpp b/src/parser.cpp index 02c37815b..5a3fc1634 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -118,7 +118,7 @@ gb_internal isize ast_node_size(AstKind kind) { gb_internal Ast *alloc_ast_node(AstFile *f, AstKind kind) { isize size = ast_node_size(kind); - Ast *node = cast(Ast *)arena_alloc(&global_thread_local_ast_arena, size, 16); + Ast *node = cast(Ast *)arena_alloc(get_arena(ThreadArena_Permanent), size, 16); node->kind = kind; node->file_id = f ? f->id : 0; diff --git a/src/parser.hpp b/src/parser.hpp index 451cdf53d..565a8e621 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -878,10 +878,8 @@ gb_internal gb_inline bool is_ast_when_stmt(Ast *node) { return node->kind == Ast_WhenStmt; } -gb_global gb_thread_local Arena global_thread_local_ast_arena = {}; - gb_internal gb_inline gbAllocator ast_allocator(AstFile *f) { - return arena_allocator(&global_thread_local_ast_arena); + return permanent_allocator(); } gb_internal Ast *alloc_ast_node(AstFile *f, AstKind kind); From eb6805ef401f03ccfe0c2f8a71097e2560f804a8 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 01:44:23 +0100 Subject: [PATCH 082/198] Disable the need for mutexes in single threaded checker stage --- src/check_expr.cpp | 4 +++- src/checker.cpp | 49 ++++++++++++++++++---------------------------- src/types.cpp | 20 +++++++++---------- 3 files changed, 32 insertions(+), 41 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 82f64738f..3fcfe29f5 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -500,7 +500,9 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E nctx.no_polymorphic_errors = false; // NOTE(bill): Reset scope from the failed procedure type - scope_reset(scope); + scope->head_child.store(nullptr, std::memory_order_relaxed); + string_map_clear(&scope->elements); + ptr_set_clear(&scope->imported); // LEAK NOTE(bill): Cloning this AST may be leaky but this is not really an issue due to arena-based allocation Ast *cloned_proc_type_node = clone_ast(pt->node); diff --git a/src/checker.cpp b/src/checker.cpp index cc39e9a44..290c0b2b0 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1,5 +1,7 @@ #define DEBUG_CHECK_ALL_PROCEDURES 1 +gb_global bool in_single_threaded_checker_stage = false; + #include "entity.cpp" #include "types.cpp" @@ -50,15 +52,6 @@ gb_internal bool check_rtti_type_disallowed(Ast *expr, Type *type, char const *f return check_rtti_type_disallowed(ast_token(expr), type, format); } -gb_internal void scope_reset(Scope *scope) { - if (scope == nullptr) return; - - rw_mutex_lock(&scope->mutex); - scope->head_child.store(nullptr, std::memory_order_relaxed); - string_map_clear(&scope->elements); - ptr_set_clear(&scope->imported); - rw_mutex_unlock(&scope->mutex); -} gb_internal void scope_reserve(Scope *scope, isize count) { string_map_reserve(&scope->elements, 2*count); @@ -168,16 +161,13 @@ gb_internal void import_graph_node_swap(ImportGraphNode **data, isize i, isize j } - - - gb_internal void init_decl_info(DeclInfo *d, Scope *scope, DeclInfo *parent) { gb_zero_item(d); if (parent) { - mutex_lock(&parent->next_mutex); + if (!in_single_threaded_checker_stage) mutex_lock(&parent->next_mutex); d->next_sibling = parent->next_child; parent->next_child = d; - mutex_unlock(&parent->next_mutex); + if (!in_single_threaded_checker_stage) mutex_unlock(&parent->next_mutex); } d->parent = parent; d->scope = scope; @@ -384,7 +374,6 @@ gb_internal Entity *scope_lookup_current(Scope *s, String const &name) { return nullptr; } -gb_global bool in_single_threaded_mode_scopes = false; gb_internal void scope_lookup_parent(Scope *scope, String const &name, Scope **scope_, Entity **entity_) { if (scope != nullptr) { @@ -393,9 +382,9 @@ gb_internal void scope_lookup_parent(Scope *scope, String const &name, Scope **s StringHashKey key = string_hash_string(name); for (Scope *s = scope; s != nullptr; s = s->parent) { Entity **found = nullptr; - if (!in_single_threaded_mode_scopes) rw_mutex_shared_lock(&s->mutex); + if (!in_single_threaded_checker_stage) rw_mutex_shared_lock(&s->mutex); found = string_map_get(&s->elements, key); - if (!in_single_threaded_mode_scopes) rw_mutex_shared_unlock(&s->mutex); + if (!in_single_threaded_checker_stage) rw_mutex_shared_unlock(&s->mutex); if (found) { Entity *e = *found; if (gone_thru_proc) { @@ -515,7 +504,7 @@ end:; gb_internal Entity *scope_insert(Scope *s, Entity *entity) { String name = entity->token.string; - if (in_single_threaded_mode_scopes) { + if (in_single_threaded_checker_stage) { return scope_insert_with_name_no_mutex(s, name, entity); } else { return scope_insert_with_name(s, name, entity); @@ -773,17 +762,17 @@ gb_internal void check_scope_usage(Checker *c, Scope *scope, u64 vet_flags) { gb_internal void add_dependency(CheckerInfo *info, DeclInfo *d, Entity *e) { - rw_mutex_lock(&d->deps_mutex); + if (!in_single_threaded_checker_stage) rw_mutex_lock(&d->deps_mutex); ptr_set_add(&d->deps, e); - rw_mutex_unlock(&d->deps_mutex); + if (!in_single_threaded_checker_stage) rw_mutex_unlock(&d->deps_mutex); } gb_internal void add_type_info_dependency(CheckerInfo *info, DeclInfo *d, Type *type) { if (d == nullptr) { return; } - rw_mutex_lock(&d->type_info_deps_mutex); + if (!in_single_threaded_checker_stage) rw_mutex_lock(&d->type_info_deps_mutex); ptr_set_add(&d->type_info_deps, type); - rw_mutex_unlock(&d->type_info_deps_mutex); + if (!in_single_threaded_checker_stage) rw_mutex_unlock(&d->type_info_deps_mutex); } @@ -1394,7 +1383,7 @@ gb_internal void reset_checker_context(CheckerContext *ctx, AstFile *file, Untyp return; } GB_ASSERT(ctx->checker != nullptr); - mutex_lock(&ctx->mutex); + if (!in_single_threaded_checker_stage) mutex_lock(&ctx->mutex); auto type_path = ctx->type_path; array_clear(type_path); @@ -1413,7 +1402,7 @@ gb_internal void reset_checker_context(CheckerContext *ctx, AstFile *file, Untyp ctx->untyped = untyped; - mutex_unlock(&ctx->mutex); + if (!in_single_threaded_checker_stage) mutex_unlock(&ctx->mutex); } @@ -1559,9 +1548,9 @@ gb_internal void check_set_expr_info(CheckerContext *c, Ast *expr, AddressingMod if (c->untyped != nullptr) { map_set(c->untyped, expr, make_expr_info(mode, type, value, false)); } else { - rw_mutex_lock(&c->info->global_untyped_mutex); + if (!in_single_threaded_checker_stage) rw_mutex_lock(&c->info->global_untyped_mutex); map_set(&c->info->global_untyped, expr, make_expr_info(mode, type, value, false)); - rw_mutex_unlock(&c->info->global_untyped_mutex); + if (!in_single_threaded_checker_stage) rw_mutex_unlock(&c->info->global_untyped_mutex); } } @@ -1571,10 +1560,10 @@ gb_internal void check_remove_expr_info(CheckerContext *c, Ast *e) { GB_ASSERT(map_get(c->untyped, e) == nullptr); } else { auto *untyped = &c->info->global_untyped; - rw_mutex_lock(&c->info->global_untyped_mutex); + if (!in_single_threaded_checker_stage) rw_mutex_lock(&c->info->global_untyped_mutex); map_remove(untyped, e); GB_ASSERT(map_get(untyped, e) == nullptr); - rw_mutex_unlock(&c->info->global_untyped_mutex); + if (!in_single_threaded_checker_stage) rw_mutex_unlock(&c->info->global_untyped_mutex); } } @@ -4596,7 +4585,7 @@ gb_internal void check_single_global_entity(Checker *c, Entity *e, DeclInfo *d) } gb_internal void check_all_global_entities(Checker *c) { - in_single_threaded_mode_scopes = true; + in_single_threaded_checker_stage = true; // NOTE(bill): This must be single threaded // Don't bother trying @@ -4618,7 +4607,7 @@ gb_internal void check_all_global_entities(Checker *c) { } } - in_single_threaded_mode_scopes = false; + in_single_threaded_checker_stage = false; } diff --git a/src/types.cpp b/src/types.cpp index fdc174d81..d477f5dee 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -808,9 +808,9 @@ gb_internal void type_path_init(TypePath *tp) { } gb_internal void type_path_free(TypePath *tp) { - mutex_lock(&tp->mutex); + if (!in_single_threaded_checker_stage) mutex_lock(&tp->mutex); array_free(&tp->path); - mutex_unlock(&tp->mutex); + if (!in_single_threaded_checker_stage) mutex_unlock(&tp->mutex); } gb_internal void type_path_print_illegal_cycle(TypePath *tp, isize start_index) { @@ -839,7 +839,7 @@ gb_internal bool type_path_push(TypePath *tp, Type *t) { } Entity *e = t->Named.type_name; - mutex_lock(&tp->mutex); + if (!in_single_threaded_checker_stage) mutex_lock(&tp->mutex); for (isize i = 0; i < tp->path.count; i++) { Entity *p = tp->path[i]; @@ -850,18 +850,18 @@ gb_internal bool type_path_push(TypePath *tp, Type *t) { array_add(&tp->path, e); - mutex_unlock(&tp->mutex); + if (!in_single_threaded_checker_stage) mutex_unlock(&tp->mutex); return true; } gb_internal void type_path_pop(TypePath *tp) { if (tp != nullptr) { - mutex_lock(&tp->mutex); + if (!in_single_threaded_checker_stage) mutex_lock(&tp->mutex); if (tp->path.count > 0) { array_pop(&tp->path); } - mutex_unlock(&tp->mutex); + if (!in_single_threaded_checker_stage) mutex_unlock(&tp->mutex); } } @@ -3216,8 +3216,8 @@ gb_internal Selection lookup_field_with_selection(Type *type_, String field_name GB_ASSERT(e->kind == Entity_TypeName); if (e->TypeName.objc_metadata) { auto *md = e->TypeName.objc_metadata; - mutex_lock(md->mutex); - defer (mutex_unlock(md->mutex)); + if (!in_single_threaded_checker_stage) mutex_lock(md->mutex); + defer (if (!in_single_threaded_checker_stage) mutex_unlock(md->mutex)); for (TypeNameObjCMetadataEntry const &entry : md->type_entries) { GB_ASSERT(entry.entity->kind == Entity_Procedure || entry.entity->kind == Entity_ProcGroup); if (entry.name == field_name) { @@ -3294,8 +3294,8 @@ gb_internal Selection lookup_field_with_selection(Type *type_, String field_name GB_ASSERT(e->kind == Entity_TypeName); if (e->TypeName.objc_metadata) { auto *md = e->TypeName.objc_metadata; - mutex_lock(md->mutex); - defer (mutex_unlock(md->mutex)); + if (!in_single_threaded_checker_stage) mutex_lock(md->mutex); + defer (if (!in_single_threaded_checker_stage) mutex_unlock(md->mutex)); for (TypeNameObjCMetadataEntry const &entry : md->value_entries) { GB_ASSERT(entry.entity->kind == Entity_Procedure || entry.entity->kind == Entity_ProcGroup); if (entry.name == field_name) { From 1b0e98116dd201c66f33e988e3a99f5c12975d2b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 01:47:52 +0100 Subject: [PATCH 083/198] Revert changes to `in_single_threaded_checker_stage` --- src/checker.cpp | 32 ++++++++++++++++---------------- src/types.cpp | 20 ++++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/checker.cpp b/src/checker.cpp index 290c0b2b0..3eae271a0 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1,7 +1,5 @@ #define DEBUG_CHECK_ALL_PROCEDURES 1 -gb_global bool in_single_threaded_checker_stage = false; - #include "entity.cpp" #include "types.cpp" @@ -164,10 +162,10 @@ gb_internal void import_graph_node_swap(ImportGraphNode **data, isize i, isize j gb_internal void init_decl_info(DeclInfo *d, Scope *scope, DeclInfo *parent) { gb_zero_item(d); if (parent) { - if (!in_single_threaded_checker_stage) mutex_lock(&parent->next_mutex); + mutex_lock(&parent->next_mutex); d->next_sibling = parent->next_child; parent->next_child = d; - if (!in_single_threaded_checker_stage) mutex_unlock(&parent->next_mutex); + mutex_unlock(&parent->next_mutex); } d->parent = parent; d->scope = scope; @@ -382,9 +380,9 @@ gb_internal void scope_lookup_parent(Scope *scope, String const &name, Scope **s StringHashKey key = string_hash_string(name); for (Scope *s = scope; s != nullptr; s = s->parent) { Entity **found = nullptr; - if (!in_single_threaded_checker_stage) rw_mutex_shared_lock(&s->mutex); + rw_mutex_shared_lock(&s->mutex); found = string_map_get(&s->elements, key); - if (!in_single_threaded_checker_stage) rw_mutex_shared_unlock(&s->mutex); + rw_mutex_shared_unlock(&s->mutex); if (found) { Entity *e = *found; if (gone_thru_proc) { @@ -502,6 +500,8 @@ end:; return result; } +gb_global bool in_single_threaded_checker_stage = false; + gb_internal Entity *scope_insert(Scope *s, Entity *entity) { String name = entity->token.string; if (in_single_threaded_checker_stage) { @@ -762,17 +762,17 @@ gb_internal void check_scope_usage(Checker *c, Scope *scope, u64 vet_flags) { gb_internal void add_dependency(CheckerInfo *info, DeclInfo *d, Entity *e) { - if (!in_single_threaded_checker_stage) rw_mutex_lock(&d->deps_mutex); + rw_mutex_lock(&d->deps_mutex); ptr_set_add(&d->deps, e); - if (!in_single_threaded_checker_stage) rw_mutex_unlock(&d->deps_mutex); + rw_mutex_unlock(&d->deps_mutex); } gb_internal void add_type_info_dependency(CheckerInfo *info, DeclInfo *d, Type *type) { if (d == nullptr) { return; } - if (!in_single_threaded_checker_stage) rw_mutex_lock(&d->type_info_deps_mutex); + rw_mutex_lock(&d->type_info_deps_mutex); ptr_set_add(&d->type_info_deps, type); - if (!in_single_threaded_checker_stage) rw_mutex_unlock(&d->type_info_deps_mutex); + rw_mutex_unlock(&d->type_info_deps_mutex); } @@ -1383,7 +1383,7 @@ gb_internal void reset_checker_context(CheckerContext *ctx, AstFile *file, Untyp return; } GB_ASSERT(ctx->checker != nullptr); - if (!in_single_threaded_checker_stage) mutex_lock(&ctx->mutex); + mutex_lock(&ctx->mutex); auto type_path = ctx->type_path; array_clear(type_path); @@ -1402,7 +1402,7 @@ gb_internal void reset_checker_context(CheckerContext *ctx, AstFile *file, Untyp ctx->untyped = untyped; - if (!in_single_threaded_checker_stage) mutex_unlock(&ctx->mutex); + mutex_unlock(&ctx->mutex); } @@ -1548,9 +1548,9 @@ gb_internal void check_set_expr_info(CheckerContext *c, Ast *expr, AddressingMod if (c->untyped != nullptr) { map_set(c->untyped, expr, make_expr_info(mode, type, value, false)); } else { - if (!in_single_threaded_checker_stage) rw_mutex_lock(&c->info->global_untyped_mutex); + rw_mutex_lock(&c->info->global_untyped_mutex); map_set(&c->info->global_untyped, expr, make_expr_info(mode, type, value, false)); - if (!in_single_threaded_checker_stage) rw_mutex_unlock(&c->info->global_untyped_mutex); + rw_mutex_unlock(&c->info->global_untyped_mutex); } } @@ -1560,10 +1560,10 @@ gb_internal void check_remove_expr_info(CheckerContext *c, Ast *e) { GB_ASSERT(map_get(c->untyped, e) == nullptr); } else { auto *untyped = &c->info->global_untyped; - if (!in_single_threaded_checker_stage) rw_mutex_lock(&c->info->global_untyped_mutex); + rw_mutex_lock(&c->info->global_untyped_mutex); map_remove(untyped, e); GB_ASSERT(map_get(untyped, e) == nullptr); - if (!in_single_threaded_checker_stage) rw_mutex_unlock(&c->info->global_untyped_mutex); + rw_mutex_unlock(&c->info->global_untyped_mutex); } } diff --git a/src/types.cpp b/src/types.cpp index d477f5dee..fdc174d81 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -808,9 +808,9 @@ gb_internal void type_path_init(TypePath *tp) { } gb_internal void type_path_free(TypePath *tp) { - if (!in_single_threaded_checker_stage) mutex_lock(&tp->mutex); + mutex_lock(&tp->mutex); array_free(&tp->path); - if (!in_single_threaded_checker_stage) mutex_unlock(&tp->mutex); + mutex_unlock(&tp->mutex); } gb_internal void type_path_print_illegal_cycle(TypePath *tp, isize start_index) { @@ -839,7 +839,7 @@ gb_internal bool type_path_push(TypePath *tp, Type *t) { } Entity *e = t->Named.type_name; - if (!in_single_threaded_checker_stage) mutex_lock(&tp->mutex); + mutex_lock(&tp->mutex); for (isize i = 0; i < tp->path.count; i++) { Entity *p = tp->path[i]; @@ -850,18 +850,18 @@ gb_internal bool type_path_push(TypePath *tp, Type *t) { array_add(&tp->path, e); - if (!in_single_threaded_checker_stage) mutex_unlock(&tp->mutex); + mutex_unlock(&tp->mutex); return true; } gb_internal void type_path_pop(TypePath *tp) { if (tp != nullptr) { - if (!in_single_threaded_checker_stage) mutex_lock(&tp->mutex); + mutex_lock(&tp->mutex); if (tp->path.count > 0) { array_pop(&tp->path); } - if (!in_single_threaded_checker_stage) mutex_unlock(&tp->mutex); + mutex_unlock(&tp->mutex); } } @@ -3216,8 +3216,8 @@ gb_internal Selection lookup_field_with_selection(Type *type_, String field_name GB_ASSERT(e->kind == Entity_TypeName); if (e->TypeName.objc_metadata) { auto *md = e->TypeName.objc_metadata; - if (!in_single_threaded_checker_stage) mutex_lock(md->mutex); - defer (if (!in_single_threaded_checker_stage) mutex_unlock(md->mutex)); + mutex_lock(md->mutex); + defer (mutex_unlock(md->mutex)); for (TypeNameObjCMetadataEntry const &entry : md->type_entries) { GB_ASSERT(entry.entity->kind == Entity_Procedure || entry.entity->kind == Entity_ProcGroup); if (entry.name == field_name) { @@ -3294,8 +3294,8 @@ gb_internal Selection lookup_field_with_selection(Type *type_, String field_name GB_ASSERT(e->kind == Entity_TypeName); if (e->TypeName.objc_metadata) { auto *md = e->TypeName.objc_metadata; - if (!in_single_threaded_checker_stage) mutex_lock(md->mutex); - defer (if (!in_single_threaded_checker_stage) mutex_unlock(md->mutex)); + mutex_lock(md->mutex); + defer (mutex_unlock(md->mutex)); for (TypeNameObjCMetadataEntry const &entry : md->value_entries) { GB_ASSERT(entry.entity->kind == Entity_Procedure || entry.entity->kind == Entity_ProcGroup); if (entry.name == field_name) { From d87583beadaf94eacda3e4faf6293859cbdbc1e4 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 02:22:23 +0100 Subject: [PATCH 084/198] Minimize mutex lock for `#load_directory` --- src/check_builtin.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index eec01b497..db62b2bdb 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1079,7 +1079,7 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan return false; } -gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String const &original_string, bool err_on_not_found, LoadFileCache **cache_, LoadFileTier tier) { +gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String const &original_string, bool err_on_not_found, LoadFileCache **cache_, LoadFileTier tier, bool use_mutex=true) { ast_node(ce, CallExpr, call); ast_node(bd, BasicDirective, ce->proc); String builtin_name = bd->name.string; @@ -1101,7 +1101,8 @@ gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String } } - MUTEX_GUARD(&c->info->load_file_mutex); + if (use_mutex) mutex_lock(&c->info->load_file_mutex); + defer (if (use_mutex) mutex_unlock(&c->info->load_file_mutex)); gbFileError file_error = gbFileError_None; String data = {}; @@ -1414,9 +1415,12 @@ gb_internal LoadDirectiveResult check_load_directory_directive(CheckerContext *c file_caches = array_make(heap_allocator(), 0, files_to_reserve); + mutex_lock(&c->info->load_file_mutex); + defer (mutex_unlock(&c->info->load_file_mutex)); + for (FileInfo fi : list) { LoadFileCache *cache = nullptr; - if (cache_load_file_directive(c, call, fi.fullpath, err_on_not_found, &cache, LoadFileTier_Contents)) { + if (cache_load_file_directive(c, call, fi.fullpath, err_on_not_found, &cache, LoadFileTier_Contents, /*use_mutex*/false)) { array_add(&file_caches, cache); } else { result = LoadDirective_Error; From cc3cf12ae2b64ba9a57b86c11d9b30b22cbb04e7 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 02:28:27 +0100 Subject: [PATCH 085/198] Disable `-use-separate-modules` by default on darwin until problem is determined --- src/build_settings.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 47f7bf5ba..4bb76acc1 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1658,11 +1658,13 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta bc->optimization_level = gb_clamp(bc->optimization_level, -1, 3); +#if !defined(GB_SYSTEM_OSX) if (bc->optimization_level <= 0) { if (!is_arch_wasm()) { bc->use_separate_modules = true; } } +#endif // TODO: Static map calls are bugged on `amd64sysv` abi. From 432388ac7fed8e295fab14d6a7c22f8bf888d2df Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 02:42:28 +0100 Subject: [PATCH 086/198] Generate backing array in the case where there is no `DeclInfo` for the procedure body --- src/llvm_backend_proc.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 001a6f27c..eefe1c422 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -3488,12 +3488,16 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr) { } lbValue base_array_ptr = p->variadic_reuse_base_array_ptr.addr; - if (d != nullptr && base_array_ptr.value == nullptr) { - i64 max_bytes = d->variadic_reuse_max_bytes; - i64 max_align = gb_max(d->variadic_reuse_max_align, 16); - p->variadic_reuse_base_array_ptr = lb_add_local_generated(p, alloc_type_array(t_u8, max_bytes), true); - lb_try_update_alignment(p->variadic_reuse_base_array_ptr.addr, cast(unsigned)max_align); - base_array_ptr = p->variadic_reuse_base_array_ptr.addr; + if (base_array_ptr.value == nullptr) { + if (d != nullptr) { + i64 max_bytes = d->variadic_reuse_max_bytes; + i64 max_align = gb_max(d->variadic_reuse_max_align, 16); + p->variadic_reuse_base_array_ptr = lb_add_local_generated(p, alloc_type_array(t_u8, max_bytes), true); + lb_try_update_alignment(p->variadic_reuse_base_array_ptr.addr, cast(unsigned)max_align); + base_array_ptr = p->variadic_reuse_base_array_ptr.addr; + } else { + base_array_ptr = lb_add_local_generated(p, alloc_type_array(elem_type, slice_len), true).addr; + } } if (slice.addr.value == nullptr) { From 664a71454bd2c58ab6f06f8de6d7d34c3eb397d7 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 02:53:01 +0100 Subject: [PATCH 087/198] `-use-separate-modules` default on Windows only --- src/build_settings.cpp | 2 +- src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 4bb76acc1..e0e7810e6 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1658,7 +1658,7 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta bc->optimization_level = gb_clamp(bc->optimization_level, -1, 3); -#if !defined(GB_SYSTEM_OSX) +#if defined(GB_SYSTEM_WINDOWS) if (bc->optimization_level <= 0) { if (!is_arch_wasm()) { bc->use_separate_modules = true; diff --git a/src/main.cpp b/src/main.cpp index da8322476..0c3ef1399 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2314,7 +2314,7 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(1, "-use-separate-modules"); print_usage_line(2, "The backend generates multiple build units which are then linked together."); print_usage_line(2, "Normally, a single build unit is generated for a standard project."); - print_usage_line(2, "This is the default behaviour for '-o:none' and '-o:minimal' builds"); + print_usage_line(2, "This is the default behaviour on Windows for '-o:none' and '-o:minimal' builds."); print_usage_line(0, ""); } From 255f00d9711af0c55c87b97d5d36486bfecd0f76 Mon Sep 17 00:00:00 2001 From: flysand7 Date: Mon, 15 Jul 2024 20:24:05 +1100 Subject: [PATCH 088/198] [os2/process]: Implement missing functionality, update docs --- core/os/os2/process.odin | 15 +++++++----- core/os/os2/process_windows.odin | 42 +++++++++++++++++++++++--------- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index 905aa2182..3dcb6473f 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -131,7 +131,7 @@ Process_Info_Field :: enum { Command_Args, Environment, Username, - CWD, + Working_Dir, } /* @@ -159,7 +159,7 @@ Process_Info :: struct { // The username of the user who started the process. username: string, // The current working directory of the process. - cwd: string, + working_dir: string, } /* @@ -244,7 +244,7 @@ free_process_info :: proc(pi: Process_Info, allocator: runtime.Allocator) { delete(s, allocator) } delete(pi.environment, allocator) - delete(pi.cwd, allocator) + delete(pi.working_dir, allocator) } /* @@ -288,8 +288,10 @@ process_open :: proc(pid: int, flags := Process_Open_Flags {}) -> (Process, Erro Process_Desc :: struct { // OS-specific attributes. sys_attr: _Sys_Process_Attributes, - // The working directory of the process. - dir: string, + // The working directory of the process. If the string has length 0, the + // working directory is assumed to be the current working directory of the + // current process. + working_dir: string, // The command to run. Each element of the slice is a separate argument to // the process. The first element of the slice would be the executable. command: []string, @@ -329,7 +331,8 @@ Process_Desc :: struct { the function returns an error, an invalid handle is returned. This procedure is not thread-safe. It may alter the inheritance properties - of file handles. + of file handles in an unpredictable manner. In case multiple threads change + handle inheritance properties, make sure to serialize all those calls. */ process_start :: proc(desc := Process_Desc {}) -> (Process, Error) { return _process_start(desc) diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index 251900020..e16cedd9f 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -79,7 +79,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator need_peb := \ .Command_Line in selection || .Environment in selection || - .CWD in selection + .Working_Dir in selection need_process_handle := need_peb || .Username in selection // Data obtained from process snapshots if need_snapprocess { @@ -122,6 +122,8 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator windows.CloseHandle(ph) } if need_peb { + // TODO(flysand): This was not tested with WOW64 or 32-bit processes, + // might need to be revised later when issues occur. ntdll_lib := windows.LoadLibraryW(windows.L("ntdll.dll")) if ntdll_lib == nil { err = _get_platform_error() @@ -206,7 +208,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator info.fields |= {.Environment} info.environment = envs } - if .CWD in selection { + if .Working_Dir in selection { TEMP_ALLOCATOR_GUARD() cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) if !read_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w, &bytes_read) { @@ -218,8 +220,8 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator err = cwd_err return } - info.fields |= {.CWD} - info.cwd = cwd + info.fields |= {.Working_Dir} + info.working_dir = cwd } } if .Username in selection { @@ -249,7 +251,7 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields need_peb := \ .Command_Line in selection || .Environment in selection || - .CWD in selection + .Working_Dir in selection // Data obtained from process snapshots if need_snapprocess { entry, entry_err := _process_entry_by_pid(info.pid) @@ -361,7 +363,7 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields info.fields |= {.Environment} info.environment = envs } - if .CWD in selection { + if .Working_Dir in selection { TEMP_ALLOCATOR_GUARD() cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) if !read_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w, &bytes_read) { @@ -373,8 +375,8 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields err = cwd_err return } - info.fields |= {.CWD} - info.cwd = cwd + info.fields |= {.Working_Dir} + info.working_dir = cwd } } if .Username in selection { @@ -463,11 +465,18 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime info.fields += {.Username} info.username = username } + if .Working_Dir in selection { + // TODO(flysand): Implement this by reading PEB + err = .Mode_Not_Implemented + return + } err = nil return } _process_open :: proc(pid: int, flags: Process_Open_Flags) -> (Process, Error) { + // Note(flysand): The handle will be used for querying information so we + // take the necessary permissions right away. dwDesiredAccess := windows.PROCESS_QUERY_LIMITED_INFORMATION | windows.SYNCHRONIZE if .Mem_Read in flags { dwDesiredAccess |= windows.PROCESS_VM_READ @@ -505,10 +514,17 @@ _process_start :: proc(desc: Process_Desc) -> (Process, Error) { stdout_handle := windows.GetStdHandle(windows.STD_OUTPUT_HANDLE) stdin_handle := windows.GetStdHandle(windows.STD_INPUT_HANDLE) if desc.stdout != nil { - stdout_handle = windows.HANDLE(desc.stdout.impl.fd) + stdout_handle = windows.HANDLE((^File_Impl)(desc.stdout.impl).fd) } if desc.stderr != nil { - stderr_handle = windows.HANDLE(desc.stderr.impl.fd) + stderr_handle = windows.HANDLE((^File_Impl)(desc.stderr.impl).fd) + } + if desc.stdin != nil { + stdin_handle = windows.HANDLE((^File_Impl)(desc.stderr.impl).fd) + } + working_dir_w := windows.wstring(nil) + if len(desc.working_dir) > 0 { + working_dir_w = windows.utf8_to_wstring(desc.working_dir, temp_allocator()) } process_info: windows.PROCESS_INFORMATION = --- process_ok := windows.CreateProcessW( @@ -519,7 +535,7 @@ _process_start :: proc(desc: Process_Desc) -> (Process, Error) { true, windows.CREATE_UNICODE_ENVIRONMENT|windows.NORMAL_PRIORITY_CLASS, raw_data(environment_block_w), - nil, + working_dir_w, &windows.STARTUPINFOW { cb = size_of(windows.STARTUPINFOW), hStdError = stderr_handle, @@ -578,6 +594,10 @@ _process_close :: proc(process: Process) -> (Error) { } _process_kill :: proc(process: Process) -> (Error) { + // Note(flysand): This is different than what the task manager's "kill process" + // functionality does, as we don't try to send WM_CLOSE message first. This + // is quite a rough way to kill the process, which should be consistent with + // linux. The error code 9 is to mimic SIGKILL event. if !windows.TerminateProcess(windows.HANDLE(process.handle), 9) { return _get_platform_error() } From c5decd3eaecf393e1bf216b4d864fc9cfc5db0c2 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 11:49:07 +0100 Subject: [PATCH 089/198] Fix possible race and correct linkage _after_ generation --- src/llvm_backend.cpp | 42 +++++++++++++++++++++++------------- src/llvm_backend.hpp | 8 +++++++ src/llvm_backend_general.cpp | 4 +++- src/queue.cpp | 2 +- 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index ae46186ed..d975ac600 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1,13 +1,11 @@ #define MULTITHREAD_OBJECT_GENERATION 1 - -#ifndef USE_SEPARATE_MODULES -#define USE_SEPARATE_MODULES build_context.use_separate_modules -#endif - #ifndef MULTITHREAD_OBJECT_GENERATION #define MULTITHREAD_OBJECT_GENERATION 0 #endif +#ifndef USE_SEPARATE_MODULES +#define USE_SEPARATE_MODULES build_context.use_separate_modules +#endif #ifndef LLVM_IGNORE_VERIFICATION #define LLVM_IGNORE_VERIFICATION 0 @@ -137,17 +135,18 @@ gb_internal void lb_set_entity_from_other_modules_linkage_correctly(lbModule *ot if (other_module == nullptr) { return; } - char const *cname = alloc_cstring(temporary_allocator(), name); + char const *cname = alloc_cstring(permanent_allocator(), name); + mpsc_enqueue(&other_module->gen->entities_to_correct_linkage, lbEntityCorrection{other_module, e, cname}); - LLVMValueRef other_global = nullptr; - if (e->kind == Entity_Variable) { - other_global = LLVMGetNamedGlobal(other_module->mod, cname); - } else if (e->kind == Entity_Procedure) { - other_global = LLVMGetNamedFunction(other_module->mod, cname); - } - if (other_global) { - LLVMSetLinkage(other_global, LLVMExternalLinkage); - } + // LLVMValueRef other_global = nullptr; + // if (e->kind == Entity_Variable) { + // other_global = LLVMGetNamedGlobal(other_module->mod, cname); + // } else if (e->kind == Entity_Procedure) { + // other_global = LLVMGetNamedFunction(other_module->mod, cname); + // } + // if (other_global) { + // LLVMSetLinkage(other_global, LLVMExternalLinkage); + // } } gb_internal void lb_emit_init_context(lbProcedure *p, lbAddr addr) { @@ -3431,6 +3430,19 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { TIME_SECTION("LLVM Add Foreign Library Paths"); lb_add_foreign_library_paths(gen); + TIME_SECTION("LLVM Correct Entity Linkage"); + for (lbEntityCorrection ec = {}; mpsc_dequeue(&gen->entities_to_correct_linkage, &ec); /**/) { + LLVMValueRef other_global = nullptr; + if (ec.e->kind == Entity_Variable) { + other_global = LLVMGetNamedGlobal(ec.other_module->mod, ec.cname); + } else if (ec.e->kind == Entity_Procedure) { + other_global = LLVMGetNamedFunction(ec.other_module->mod, ec.cname); + } + if (other_global) { + LLVMSetLinkage(other_global, LLVMExternalLinkage); + } + } + //////////////////////////////////////////// for (auto const &entry: gen->modules) { diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index deb05528f..c4f2bd884 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -200,6 +200,12 @@ struct lbModule { LLVMPassManagerRef function_pass_managers[lbFunctionPassManager_COUNT]; }; +struct lbEntityCorrection { + lbModule * other_module; + Entity * e; + char const *cname; +}; + struct lbGenerator : LinkerData { CheckerInfo *info; @@ -218,6 +224,8 @@ struct lbGenerator : LinkerData { lbProcedure *startup_runtime; lbProcedure *cleanup_runtime; lbProcedure *objc_names; + + MPSCQueue entities_to_correct_linkage; }; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index f5595b70e..ba8b13bd8 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -71,7 +71,7 @@ gb_internal void lb_init_module(lbModule *m, Checker *c) { map_init(&m->hasher_procs); map_init(&m->map_get_procs); map_init(&m->map_set_procs); - if (build_context.use_separate_modules) { + if (USE_SEPARATE_MODULES) { array_init(&m->procedures_to_generate, a, 0, 1<<10); map_init(&m->procedure_values, 1<<11); } else { @@ -151,6 +151,8 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { map_set(&gen->modules_through_ctx, ctx, m); } + mpsc_init(&gen->entities_to_correct_linkage, heap_allocator()); + return true; } diff --git a/src/queue.cpp b/src/queue.cpp index 2ad9cb29f..dee9ad1f8 100644 --- a/src/queue.cpp +++ b/src/queue.cpp @@ -16,7 +16,7 @@ struct MPSCQueue { std::atomic count; }; -template gb_internal void mpsc_init (MPSCQueue *q); +template gb_internal void mpsc_init (MPSCQueue *q, gbAllocator const &allocator); template gb_internal void mpsc_destroy(MPSCQueue *q); template gb_internal isize mpsc_enqueue(MPSCQueue *q, T const &value); template gb_internal bool mpsc_dequeue(MPSCQueue *q, T *value_); From 549311fac98ce447e8ab18d365841dc4f0671abf Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 12:21:42 +0100 Subject: [PATCH 090/198] Fix global variables being "missing" with `-use-separate-modules` --- src/llvm_backend.cpp | 39 ++++++------ src/llvm_backend.hpp | 3 +- src/llvm_backend_debug.cpp | 1 + src/llvm_backend_general.cpp | 115 +++++++++++++++++++---------------- 4 files changed, 82 insertions(+), 76 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index d975ac600..4f3186dba 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -137,18 +137,23 @@ gb_internal void lb_set_entity_from_other_modules_linkage_correctly(lbModule *ot } char const *cname = alloc_cstring(permanent_allocator(), name); mpsc_enqueue(&other_module->gen->entities_to_correct_linkage, lbEntityCorrection{other_module, e, cname}); - - // LLVMValueRef other_global = nullptr; - // if (e->kind == Entity_Variable) { - // other_global = LLVMGetNamedGlobal(other_module->mod, cname); - // } else if (e->kind == Entity_Procedure) { - // other_global = LLVMGetNamedFunction(other_module->mod, cname); - // } - // if (other_global) { - // LLVMSetLinkage(other_global, LLVMExternalLinkage); - // } } +gb_internal void lb_correct_entity_linkage(lbGenerator *gen) { + for (lbEntityCorrection ec = {}; mpsc_dequeue(&gen->entities_to_correct_linkage, &ec); /**/) { + LLVMValueRef other_global = nullptr; + if (ec.e->kind == Entity_Variable) { + other_global = LLVMGetNamedGlobal(ec.other_module->mod, ec.cname); + } else if (ec.e->kind == Entity_Procedure) { + other_global = LLVMGetNamedFunction(ec.other_module->mod, ec.cname); + } + if (other_global) { + LLVMSetLinkage(other_global, LLVMExternalLinkage); + } + } +} + + gb_internal void lb_emit_init_context(lbProcedure *p, lbAddr addr) { TEMPORARY_ALLOCATOR_GUARD(); @@ -1386,6 +1391,7 @@ gb_internal void lb_create_global_procedures_and_types(lbGenerator *gen, Checker if (USE_SEPARATE_MODULES) { m = lb_module_of_entity(gen, e); } + GB_ASSERT(m != nullptr); if (e->kind == Entity_Procedure) { array_add(&m->global_procedures_to_create, e); @@ -3431,18 +3437,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { lb_add_foreign_library_paths(gen); TIME_SECTION("LLVM Correct Entity Linkage"); - for (lbEntityCorrection ec = {}; mpsc_dequeue(&gen->entities_to_correct_linkage, &ec); /**/) { - LLVMValueRef other_global = nullptr; - if (ec.e->kind == Entity_Variable) { - other_global = LLVMGetNamedGlobal(ec.other_module->mod, ec.cname); - } else if (ec.e->kind == Entity_Procedure) { - other_global = LLVMGetNamedFunction(ec.other_module->mod, ec.cname); - } - if (other_global) { - LLVMSetLinkage(other_global, LLVMExternalLinkage); - } - } - + lb_correct_entity_linkage(gen); //////////////////////////////////////////// for (auto const &entry: gen->modules) { diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index c4f2bd884..806864c7c 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -147,6 +147,7 @@ struct lbModule { CheckerInfo *info; AstPackage *pkg; // possibly associated AstFile *file; // possibly associated + char const *module_name; PtrMap types; // mutex: types_mutex PtrMap struct_field_remapping; // Key: LLVMTypeRef or Type *, mutex: types_mutex @@ -379,7 +380,7 @@ struct lbProcedure { gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c); -gb_internal String lb_mangle_name(lbModule *m, Entity *e); +gb_internal String lb_mangle_name(Entity *e); gb_internal String lb_get_entity_name(lbModule *m, Entity *e, String name = {}); gb_internal LLVMAttributeRef lb_create_enum_attribute(LLVMContextRef ctx, char const *name, u64 value=0); diff --git a/src/llvm_backend_debug.cpp b/src/llvm_backend_debug.cpp index f1ace5f06..c896f889d 100644 --- a/src/llvm_backend_debug.cpp +++ b/src/llvm_backend_debug.cpp @@ -1187,6 +1187,7 @@ gb_internal void add_debug_info_for_global_constant_from_entity(lbGenerator *gen if (USE_SEPARATE_MODULES) { m = lb_module_of_entity(gen, e); } + GB_ASSERT(m != nullptr); if (is_type_integer(e->type)) { ExactValue const &value = e->Constant.value; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index ba8b13bd8..dbeea1dac 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -29,8 +29,9 @@ gb_internal void lb_init_module(lbModule *m, Checker *c) { module_name = gb_string_appendc(module_name, "-builtin"); } + m->module_name = module_name ? module_name : "odin_package"; m->ctx = LLVMContextCreate(); - m->mod = LLVMModuleCreateWithNameInContext(module_name ? module_name : "odin_package", m->ctx); + m->mod = LLVMModuleCreateWithNameInContext(m->module_name, m->ctx); // m->debug_builder = nullptr; if (build_context.ODIN_DEBUG) { enum {DEBUG_METADATA_VERSION = 3}; @@ -389,12 +390,14 @@ gb_internal lbModule *lb_module_of_entity(lbGenerator *gen, Entity *e) { if (e->file) { found = map_get(&gen->modules, cast(void *)e->file); if (found) { + GB_ASSERT(*found != nullptr); return *found; } } if (e->pkg) { found = map_get(&gen->modules, cast(void *)e->pkg); if (found) { + GB_ASSERT(*found != nullptr); return *found; } } @@ -1532,7 +1535,7 @@ gb_internal void lb_clone_struct_type(LLVMTypeRef dst, LLVMTypeRef src) { LLVMStructSetBody(dst, fields, field_count, LLVMIsPackedStruct(src)); } -gb_internal String lb_mangle_name(lbModule *m, Entity *e) { +gb_internal String lb_mangle_name(Entity *e) { String name = e->token.string; AstPackage *pkg = e->pkg; @@ -1632,6 +1635,7 @@ gb_internal String lb_set_nested_type_name_ir_mangled_name(Entity *e, lbProcedur } gb_internal String lb_get_entity_name(lbModule *m, Entity *e, String default_name) { + GB_ASSERT(m != nullptr); if (e != nullptr && e->kind == Entity_TypeName && e->TypeName.ir_mangled_name.len != 0) { return e->TypeName.ir_mangled_name; } @@ -1663,7 +1667,7 @@ gb_internal String lb_get_entity_name(lbModule *m, Entity *e, String default_nam } if (!no_name_mangle) { - name = lb_mangle_name(m, e); + name = lb_mangle_name(e); } if (name.len == 0) { name = e->token.string; @@ -3039,58 +3043,63 @@ gb_internal lbValue lb_find_value_from_entity(lbModule *m, Entity *e) { return *found; } - if (USE_SEPARATE_MODULES) { - lbModule *other_module = lb_module_of_entity(m->gen, e); + lbValue g = {}; + String name = {}; - bool is_external = other_module != m; - if (!is_external) { - if (e->code_gen_module != nullptr) { - other_module = e->code_gen_module; - } else { - other_module = nullptr; - } - is_external = other_module != m; - } - - if (is_external) { - String name = lb_get_entity_name(other_module, e); - - lbValue g = {}; - g.value = LLVMAddGlobal(m->mod, lb_type(m, e->type), alloc_cstring(permanent_allocator(), name)); - g.type = alloc_type_pointer(e->type); - lb_add_entity(m, e, g); - lb_add_member(m, name, g); - - LLVMSetLinkage(g.value, LLVMExternalLinkage); - - lb_set_entity_from_other_modules_linkage_correctly(other_module, e, name); - - // LLVMSetLinkage(other_g.value, LLVMExternalLinkage); - - if (e->Variable.thread_local_model != "") { - LLVMSetThreadLocal(g.value, true); - - String m = e->Variable.thread_local_model; - LLVMThreadLocalMode mode = LLVMGeneralDynamicTLSModel; - if (m == "default") { - mode = LLVMGeneralDynamicTLSModel; - } else if (m == "localdynamic") { - mode = LLVMLocalDynamicTLSModel; - } else if (m == "initialexec") { - mode = LLVMInitialExecTLSModel; - } else if (m == "localexec") { - mode = LLVMLocalExecTLSModel; - } else { - GB_PANIC("Unhandled thread local mode %.*s", LIT(m)); - } - LLVMSetThreadLocalMode(g.value, mode); - } - - - return g; - } + if (!USE_SEPARATE_MODULES) { + goto failed; } - GB_PANIC("\n\tError in: %s, missing value '%.*s'\n", token_pos_to_string(e->token.pos), LIT(e->token.string)); + lbModule *other_module = lb_module_of_entity(m->gen, e); + + bool is_external = other_module != m; + if (!is_external) { + if (e->code_gen_module != nullptr) { + other_module = e->code_gen_module; + } else { + other_module = &m->gen->default_module; + } + is_external = other_module != m; + } + + if (!is_external) { + goto failed; + } + name = lb_get_entity_name(other_module, e); + + g.value = LLVMAddGlobal(m->mod, lb_type(m, e->type), alloc_cstring(permanent_allocator(), name)); + g.type = alloc_type_pointer(e->type); + lb_add_entity(m, e, g); + lb_add_member(m, name, g); + + LLVMSetLinkage(g.value, LLVMExternalLinkage); + + lb_set_entity_from_other_modules_linkage_correctly(other_module, e, name); + + if (e->Variable.thread_local_model != "") { + LLVMSetThreadLocal(g.value, true); + + String m = e->Variable.thread_local_model; + LLVMThreadLocalMode mode = LLVMGeneralDynamicTLSModel; + if (m == "default") { + mode = LLVMGeneralDynamicTLSModel; + } else if (m == "localdynamic") { + mode = LLVMLocalDynamicTLSModel; + } else if (m == "initialexec") { + mode = LLVMInitialExecTLSModel; + } else if (m == "localexec") { + mode = LLVMLocalExecTLSModel; + } else { + GB_PANIC("Unhandled thread local mode %.*s", LIT(m)); + } + LLVMSetThreadLocalMode(g.value, mode); + } + + + return g; + +failed:; + GB_PANIC("\n\tError in: %s, missing value '%.*s' in module %s\n", + token_pos_to_string(e->token.pos), LIT(e->token.string), m->module_name); return {}; } From 7d643bcae3c0260cbb4c2898ebea0b9f052cd5d0 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 12:30:32 +0100 Subject: [PATCH 091/198] Make linkage weak in certain places --- src/llvm_backend.cpp | 15 ++++--- src/llvm_backend_general.cpp | 83 +++++++++++++++++------------------- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 4f3186dba..62909dafb 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -144,11 +144,14 @@ gb_internal void lb_correct_entity_linkage(lbGenerator *gen) { LLVMValueRef other_global = nullptr; if (ec.e->kind == Entity_Variable) { other_global = LLVMGetNamedGlobal(ec.other_module->mod, ec.cname); + if (other_global) { + LLVMSetLinkage(other_global, LLVMWeakAnyLinkage); + } } else if (ec.e->kind == Entity_Procedure) { other_global = LLVMGetNamedFunction(ec.other_module->mod, ec.cname); - } - if (other_global) { - LLVMSetLinkage(other_global, LLVMExternalLinkage); + if (other_global) { + LLVMSetLinkage(other_global, LLVMWeakAnyLinkage); + } } } } @@ -1437,7 +1440,9 @@ gb_internal bool lb_is_module_empty(lbModule *m) { } for (auto g = LLVMGetFirstGlobal(m->mod); g != nullptr; g = LLVMGetNextGlobal(g)) { - if (LLVMGetLinkage(g) == LLVMExternalLinkage) { + LLVMLinkage linkage = LLVMGetLinkage(g); + if (linkage == LLVMExternalLinkage || + linkage == LLVMWeakAnyLinkage) { continue; } if (!LLVMIsExternallyInitialized(g)) { @@ -3266,7 +3271,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { LLVMSetLinkage(g.value, LLVMDLLExportLinkage); LLVMSetDLLStorageClass(g.value, LLVMDLLExportStorageClass); } else if (!is_foreign) { - LLVMSetLinkage(g.value, USE_SEPARATE_MODULES ? LLVMExternalLinkage : LLVMInternalLinkage); + LLVMSetLinkage(g.value, USE_SEPARATE_MODULES ? LLVMWeakAnyLinkage : LLVMInternalLinkage); } lb_set_linkage_from_entity_flags(m, g.value, e->flags); diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index dbeea1dac..33a645704 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -3043,61 +3043,56 @@ gb_internal lbValue lb_find_value_from_entity(lbModule *m, Entity *e) { return *found; } - lbValue g = {}; - String name = {}; + if (USE_SEPARATE_MODULES) { + lbModule *other_module = lb_module_of_entity(m->gen, e); - if (!USE_SEPARATE_MODULES) { - goto failed; - } - lbModule *other_module = lb_module_of_entity(m->gen, e); - - bool is_external = other_module != m; - if (!is_external) { - if (e->code_gen_module != nullptr) { - other_module = e->code_gen_module; - } else { - other_module = &m->gen->default_module; + bool is_external = other_module != m; + if (!is_external) { + if (e->code_gen_module != nullptr) { + other_module = e->code_gen_module; + } else { + other_module = &m->gen->default_module; + } + is_external = other_module != m; } - is_external = other_module != m; - } - if (!is_external) { - goto failed; - } - name = lb_get_entity_name(other_module, e); + if (is_external) { + String name = lb_get_entity_name(other_module, e); - g.value = LLVMAddGlobal(m->mod, lb_type(m, e->type), alloc_cstring(permanent_allocator(), name)); - g.type = alloc_type_pointer(e->type); - lb_add_entity(m, e, g); - lb_add_member(m, name, g); + lbValue g = {}; + g.value = LLVMAddGlobal(m->mod, lb_type(m, e->type), alloc_cstring(permanent_allocator(), name)); + g.type = alloc_type_pointer(e->type); + lb_add_entity(m, e, g); + lb_add_member(m, name, g); - LLVMSetLinkage(g.value, LLVMExternalLinkage); + LLVMSetLinkage(g.value, LLVMExternalLinkage); - lb_set_entity_from_other_modules_linkage_correctly(other_module, e, name); + lb_set_entity_from_other_modules_linkage_correctly(other_module, e, name); - if (e->Variable.thread_local_model != "") { - LLVMSetThreadLocal(g.value, true); + if (e->Variable.thread_local_model != "") { + LLVMSetThreadLocal(g.value, true); - String m = e->Variable.thread_local_model; - LLVMThreadLocalMode mode = LLVMGeneralDynamicTLSModel; - if (m == "default") { - mode = LLVMGeneralDynamicTLSModel; - } else if (m == "localdynamic") { - mode = LLVMLocalDynamicTLSModel; - } else if (m == "initialexec") { - mode = LLVMInitialExecTLSModel; - } else if (m == "localexec") { - mode = LLVMLocalExecTLSModel; - } else { - GB_PANIC("Unhandled thread local mode %.*s", LIT(m)); + String m = e->Variable.thread_local_model; + LLVMThreadLocalMode mode = LLVMGeneralDynamicTLSModel; + if (m == "default") { + mode = LLVMGeneralDynamicTLSModel; + } else if (m == "localdynamic") { + mode = LLVMLocalDynamicTLSModel; + } else if (m == "initialexec") { + mode = LLVMInitialExecTLSModel; + } else if (m == "localexec") { + mode = LLVMLocalExecTLSModel; + } else { + GB_PANIC("Unhandled thread local mode %.*s", LIT(m)); + } + LLVMSetThreadLocalMode(g.value, mode); + } + + + return g; } - LLVMSetThreadLocalMode(g.value, mode); } - - return g; - -failed:; GB_PANIC("\n\tError in: %s, missing value '%.*s' in module %s\n", token_pos_to_string(e->token.pos), LIT(e->token.string), m->module_name); return {}; From 5cefab8229514c308c4676bbd86db7a8b3d2c5f5 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 13:22:50 +0100 Subject: [PATCH 092/198] Fix `case:` in type switch issue --- src/llvm_backend_general.cpp | 2 +- src/llvm_backend_stmt.cpp | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 33a645704..bb04fc746 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -1110,7 +1110,7 @@ gb_internal lbValue lb_emit_load(lbProcedure *p, lbValue value) { return lb_addr_load(p, addr); } - GB_ASSERT(is_type_pointer(value.type)); + GB_ASSERT_MSG(is_type_pointer(value.type), "%s", type_to_string(value.type)); Type *t = type_deref(value.type); LLVMValueRef v = LLVMBuildLoad2(p->builder, lb_type(p->module, t), value.value, ""); diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index 70b695627..e70cc503e 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -1736,10 +1736,17 @@ gb_internal void lb_build_type_switch_stmt(lbProcedure *p, AstTypeSwitchStmt *ss for (Ast *clause : body->stmts) { ast_node(cc, CaseClause, clause); + + Entity *case_entity = implicit_entity_of_node(clause); lb_open_scope(p, cc->scope); + if (cc->list.count == 0) { lb_start_block(p, default_block); - lb_store_type_case_implicit(p, clause, parent_value, true); + if (case_entity->flags & EntityFlag_Value) { + lb_store_type_case_implicit(p, clause, parent_value, true); + } else { + lb_store_type_case_implicit(p, clause, parent_ptr, true); + } lb_type_case_body(p, ss->label, clause, p->curr_block, done); continue; } @@ -1769,7 +1776,6 @@ gb_internal void lb_build_type_switch_stmt(lbProcedure *p, AstTypeSwitchStmt *ss LLVMAddCase(switch_instr, on_val.value, body->block); } - Entity *case_entity = implicit_entity_of_node(clause); lb_start_block(p, body); @@ -1782,6 +1788,7 @@ gb_internal void lb_build_type_switch_stmt(lbProcedure *p, AstTypeSwitchStmt *ss } else if (switch_kind == TypeSwitch_Any) { data = lb_emit_load(p, lb_emit_struct_ep(p, parent_ptr, 0)); } + GB_ASSERT(is_type_pointer(data.type)); Type *ct = case_entity->type; Type *ct_ptr = alloc_type_pointer(ct); From 1a30d47ee8d6e80d8cd76afc24595cd6e6f62d4c Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Sun, 9 Jun 2024 05:29:41 +0900 Subject: [PATCH 093/198] repo: Cleanup the .gitignore to match the new test runner --- .gitignore | 33 +-------------------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index c8a66d288..4c3c98b72 100644 --- a/.gitignore +++ b/.gitignore @@ -24,38 +24,6 @@ bld/ ![Cc]ore/[Ll]og/ tests/documentation/verify/ tests/documentation/all.odin-doc -tests/internal/test_map -tests/internal/test_pow -tests/internal/test_rtti -tests/core/test_base64 -tests/core/test_cbor -tests/core/test_core_compress -tests/core/test_core_container -tests/core/test_core_filepath -tests/core/test_core_fmt -tests/core/test_core_i18n -tests/core/test_core_image -tests/core/test_core_libc -tests/core/test_core_match -tests/core/test_core_math -tests/core/test_core_net -tests/core/test_core_os_exit -tests/core/test_core_reflect -tests/core/test_core_strings -tests/core/test_core_time -tests/core/test_crypto -tests/core/test_hash -tests/core/test_hex -tests/core/test_hxa -tests/core/test_json -tests/core/test_linalg_glsl_math -tests/core/test_noise -tests/core/test_varint -tests/core/test_xml -tests/core/test_core_slice -tests/core/test_core_thread -tests/core/test_core_runtime -tests/vendor/vendor_botan # Visual Studio 2015 cache/options directory .vs/ # Visual Studio Code options directory @@ -63,6 +31,7 @@ tests/vendor/vendor_botan # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ demo +benchmark # MSTest test Results [Tt]est[Rr]esult*/ From 8df61b7209169a25ad7776cd5c9e0ec51cb47dc0 Mon Sep 17 00:00:00 2001 From: flysand7 Date: Mon, 15 Jul 2024 23:28:03 +1100 Subject: [PATCH 094/198] [os2/process]: Make get_args() private and use heap_allocator --- core/os/os2/process.odin | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index 3dcb6473f..d407ffb18 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -13,20 +13,12 @@ TIMEOUT_INFINITE :: time.MIN_DURATION // Note(flysand): Any negative duration wi /* Arguments to the current process. - - See `get_args()` for description of the slice. */ args := get_args() -/* - Obtain the process argument array from the OS. - - Slice, containing arguments to the current process. Each element of the - slice contains a single argument. The first element of the slice would - typically is the path to the currently running executable. -*/ +@(private="file") get_args :: proc() -> []string { - result := make([]string, len(runtime.args__), allocator = context.allocator) + result := make([]string, len(runtime.args__), heap_allocator()) for rt_arg, i in runtime.args__ { result[i] = cast(string) rt_arg } From 1e37eaf54daf885636ea3ad9606a2b54e01721f9 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 14:49:20 +0100 Subject: [PATCH 095/198] Begin work for `bit_set[...; [N]T]` (not working) --- src/build_settings.cpp | 2 + src/check_expr.cpp | 6 +- src/check_type.cpp | 21 ++----- src/llvm_backend_const.cpp | 2 + src/llvm_backend_expr.cpp | 118 ++++++++++++++++++++++++++++------- src/llvm_backend_general.cpp | 2 + src/llvm_backend_proc.cpp | 5 ++ src/main.cpp | 5 ++ src/types.cpp | 21 +++++++ 9 files changed, 140 insertions(+), 42 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index e0e7810e6..49bb83b22 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -440,6 +440,8 @@ struct BuildContext { bool cached; BuildCacheData build_cache_data; + bool internal_no_inline; + bool no_threaded_checker; bool show_debug_messages; diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 3fcfe29f5..01ff9da5b 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -9947,10 +9947,14 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * } Type *et = base_type(t->BitSet.elem); isize field_count = 0; - if (et->kind == Type_Enum) { + if (et != nullptr && et->kind == Type_Enum) { field_count = et->Enum.fields.count; } + if (is_type_array(bit_set_to_int(t))) { + is_constant = false; + } + if (cl->elems[0]->kind == Ast_FieldValue) { error(cl->elems[0], "'field = value' in a bit_set a literal is not allowed"); is_constant = false; diff --git a/src/check_type.cpp b/src/check_type.cpp index fea937e4e..e3609970a 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -939,22 +939,6 @@ gb_internal void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *nam enum_type->Enum.max_value_index = max_value_index; } -gb_internal bool is_valid_bit_field_backing_type(Type *type) { - if (type == nullptr) { - return false; - } - type = base_type(type); - if (is_type_untyped(type)) { - return false; - } - if (is_type_integer(type)) { - return true; - } - if (type->kind == Type_Array) { - return is_type_integer(type->Array.elem); - } - return false; -} gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type, Type *named_type, Ast *node) { ast_node(bf, BitFieldType, node); @@ -1268,11 +1252,14 @@ gb_internal void check_bit_set_type(CheckerContext *c, Type *type, Type *named_t Type *t = default_type(lhs.type); if (bs->underlying != nullptr) { Type *u = check_type(c, bs->underlying); + // if (!is_valid_bit_field_backing_type(u)) { if (!is_type_integer(u)) { gbString ts = type_to_string(u); error(bs->underlying, "Expected an underlying integer for the bit set, got %s", ts); gb_string_free(ts); - return; + if (!is_valid_bit_field_backing_type(u)) { + return; + } } type->BitSet.underlying = u; } diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 5d9caeba1..12bcc4e1f 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -434,6 +434,8 @@ gb_internal LLVMValueRef lb_big_int_to_llvm(lbModule *m, Type *original_type, Bi } } + GB_ASSERT(!is_type_array(original_type)); + LLVMValueRef value = LLVMConstIntOfArbitraryPrecision(lb_type(m, original_type), cast(unsigned)((sz+7)/8), cast(u64 *)rop); if (big_int_is_neg(a)) { value = LLVMConstNeg(value); diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index bcacc0537..dfb7e162e 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -296,12 +296,6 @@ gb_internal bool lb_try_direct_vector_arith(lbProcedure *p, TokenKind op, lbValu GB_ASSERT(vector_type0 == vector_type1); LLVMTypeRef vector_type = vector_type0; - LLVMValueRef lhs_vp = LLVMBuildPointerCast(p->builder, lhs_ptr.value, LLVMPointerType(vector_type, 0), ""); - LLVMValueRef rhs_vp = LLVMBuildPointerCast(p->builder, rhs_ptr.value, LLVMPointerType(vector_type, 0), ""); - LLVMValueRef x = LLVMBuildLoad2(p->builder, vector_type, lhs_vp, ""); - LLVMValueRef y = LLVMBuildLoad2(p->builder, vector_type, rhs_vp, ""); - LLVMValueRef z = nullptr; - Type *integral_type = base_type(elem_type); if (is_type_simd_vector(integral_type)) { integral_type = core_array_type(integral_type); @@ -311,8 +305,18 @@ gb_internal bool lb_try_direct_vector_arith(lbProcedure *p, TokenKind op, lbValu case Token_Add: op = Token_Or; break; case Token_Sub: op = Token_AndNot; break; } + Type *u = bit_set_to_int(type); + if (is_type_array(u)) { + return false; + } } + LLVMValueRef lhs_vp = LLVMBuildPointerCast(p->builder, lhs_ptr.value, LLVMPointerType(vector_type, 0), ""); + LLVMValueRef rhs_vp = LLVMBuildPointerCast(p->builder, rhs_ptr.value, LLVMPointerType(vector_type, 0), ""); + LLVMValueRef x = LLVMBuildLoad2(p->builder, vector_type, lhs_vp, ""); + LLVMValueRef y = LLVMBuildLoad2(p->builder, vector_type, rhs_vp, ""); + LLVMValueRef z = nullptr; + if (is_type_float(integral_type)) { switch (op) { case Token_Add: @@ -1286,6 +1290,14 @@ handle_op:; case Token_Add: op = Token_Or; break; case Token_Sub: op = Token_AndNot; break; } + Type *u = bit_set_to_int(type); + if (is_type_array(u)) { + lhs.type = u; + rhs.type = u; + res = lb_emit_arith(p, op, lhs, rhs, u); + res.type = type; + return res; + } } Type *integral_type = type; @@ -1441,6 +1453,7 @@ gb_internal lbValue lb_build_binary_in(lbProcedure *p, lbValue left, lbValue rig GB_ASSERT(are_types_identical(left.type, key_type)); Type *it = bit_set_to_int(rt); + left = lb_emit_conv(p, left, it); if (is_type_different_to_arch_endianness(it)) { left = lb_emit_byte_swap(p, left, integer_endian_type_to_platform_type(it)); @@ -2054,6 +2067,26 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { } } + // bit_set <-> backing type + if (is_type_bit_set(src)) { + Type *backing = bit_set_to_int(src); + if (are_types_identical(backing, dst)) { + lbValue res = {}; + res.type = t; + res.value = value.value; + return res; + } + } + if (is_type_bit_set(dst)) { + Type *backing = bit_set_to_int(dst); + if (are_types_identical(src, backing)) { + lbValue res = {}; + res.type = t; + res.value = value.value; + return res; + } + } + // Pointer <-> uintptr if (is_type_pointer(src) && is_type_uintptr(dst)) { @@ -2951,13 +2984,32 @@ gb_internal lbValue lb_emit_comp_against_nil(lbProcedure *p, TokenKind op_kind, case Type_Pointer: case Type_MultiPointer: case Type_Proc: - case Type_BitSet: if (op_kind == Token_CmpEq) { res.value = LLVMBuildIsNull(p->builder, x.value, ""); } else if (op_kind == Token_NotEq) { res.value = LLVMBuildIsNotNull(p->builder, x.value, ""); } return res; + case Type_BitSet: + { + Type *u = bit_set_to_int(bt); + if (is_type_array(u)) { + auto args = array_make(permanent_allocator(), 2); + lbValue lhs = lb_address_from_load_or_generate_local(p, x); + args[0] = lb_emit_conv(p, lhs, t_rawptr); + args[1] = lb_const_int(p->module, t_int, type_size_of(t)); + lbValue val = lb_emit_runtime_call(p, "memory_compare_zero", args); + lbValue res = lb_emit_comp(p, op_kind, val, lb_const_int(p->module, t_int, 0)); + return res; + } else { + if (op_kind == Token_CmpEq) { + res.value = LLVMBuildIsNull(p->builder, x.value, ""); + } else if (op_kind == Token_NotEq) { + res.value = LLVMBuildIsNotNull(p->builder, x.value, ""); + } + } + return res; + } case Type_Slice: { @@ -4878,29 +4930,47 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { case Type_BitSet: { i64 sz = type_size_of(type); if (cl->elems.count > 0 && sz > 0) { - lb_addr_store(p, v, lb_const_value(p->module, type, exact_value_compound(expr))); - lbValue lower = lb_const_value(p->module, t_int, exact_value_i64(bt->BitSet.lower)); - for (Ast *elem : cl->elems) { - GB_ASSERT(elem->kind != Ast_FieldValue); - if (lb_is_elem_const(elem, et)) { - continue; + Type *backing = bit_set_to_int(type); + if (is_type_array(backing)) { + GB_PANIC("TODO: bit_set [N]T"); + Type *base_it = core_array_type(backing); + i64 bits_per_elem = 8*type_size_of(base_it); + gb_unused(bits_per_elem); + lbValue one = lb_const_value(p->module, t_i64, exact_value_i64(1)); + for (Ast *elem : cl->elems) { + GB_ASSERT(elem->kind != Ast_FieldValue); + lbValue expr = lb_build_expr(p, elem); + GB_ASSERT(expr.type->kind != Type_Tuple); + + lbValue e = lb_emit_conv(p, expr, t_i64); + e = lb_emit_arith(p, Token_Sub, e, lower, t_i64); + // lbValue idx = lb_emit_arith(p, Token_Div, e, bits_per_elem, t_i64); + // lbValue val = lb_emit_arith(p, Token_Div, e, bits_per_elem, t_i64); } - - lbValue expr = lb_build_expr(p, elem); - GB_ASSERT(expr.type->kind != Type_Tuple); - + } else { Type *it = bit_set_to_int(bt); lbValue one = lb_const_value(p->module, it, exact_value_i64(1)); - lbValue e = lb_emit_conv(p, expr, it); - e = lb_emit_arith(p, Token_Sub, e, lower, it); - e = lb_emit_arith(p, Token_Shl, one, e, it); + for (Ast *elem : cl->elems) { + GB_ASSERT(elem->kind != Ast_FieldValue); - lbValue old_value = lb_emit_transmute(p, lb_addr_load(p, v), it); - lbValue new_value = lb_emit_arith(p, Token_Or, old_value, e, it); - new_value = lb_emit_transmute(p, new_value, type); - lb_addr_store(p, v, new_value); + if (lb_is_elem_const(elem, et)) { + continue; + } + + lbValue expr = lb_build_expr(p, elem); + GB_ASSERT(expr.type->kind != Type_Tuple); + + lbValue e = lb_emit_conv(p, expr, it); + e = lb_emit_arith(p, Token_Sub, e, lower, it); + e = lb_emit_arith(p, Token_Shl, one, e, it); + + lbValue old_value = lb_emit_transmute(p, lb_addr_load(p, v), it); + lbValue new_value = lb_emit_arith(p, Token_Or, old_value, e, it); + new_value = lb_emit_transmute(p, new_value, type); + lb_addr_store(p, v, new_value); + } } } break; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index bb04fc746..a91c1d1fe 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -1023,6 +1023,8 @@ gb_internal void lb_emit_store(lbProcedure *p, lbValue ptr, lbValue value) { LLVMTypeRef rawptr_type = lb_type(p->module, t_rawptr); LLVMTypeRef rawptr_ptr_type = LLVMPointerType(rawptr_type, 0); LLVMBuildStore(p->builder, LLVMConstNull(rawptr_type), LLVMBuildBitCast(p->builder, ptr.value, rawptr_ptr_type, "")); + } else if (is_type_bit_set(a)) { + lb_mem_zero_ptr(p, ptr.value, a, 1); } else if (lb_sizeof(src_t) <= lb_max_zero_init_size()) { LLVMBuildStore(p->builder, LLVMConstNull(src_t), ptr.value); } else { diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index eefe1c422..4ee4fb769 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -159,6 +159,11 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i case ProcInlining_no_inline: lb_add_attribute_to_proc(m, p->value, "noinline"); break; + default: + if (build_context.internal_no_inline) { + lb_add_attribute_to_proc(m, p->value, "noinline"); + break; + } } switch (entity->Procedure.optimization_mode) { diff --git a/src/main.cpp b/src/main.cpp index 0c3ef1399..e7f4ccc0a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -394,6 +394,7 @@ enum BuildFlagKind { BuildFlag_InternalIgnorePanic, BuildFlag_InternalModulePerFile, BuildFlag_InternalCached, + BuildFlag_InternalNoInline, BuildFlag_Tilde, @@ -598,6 +599,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_InternalIgnorePanic, str_lit("internal-ignore-panic"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_InternalModulePerFile, str_lit("internal-module-per-file"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_InternalCached, str_lit("internal-cached"), BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_InternalNoInline, str_lit("internal-no-inline"), BuildFlagParam_None, Command_all); #if ALLOW_TILDE add_flag(&build_flags, BuildFlag_Tilde, str_lit("tilde"), BuildFlagParam_None, Command__does_build); @@ -1422,6 +1424,9 @@ gb_internal bool parse_build_flags(Array args) { build_context.cached = true; build_context.use_separate_modules = true; break; + case BuildFlag_InternalNoInline: + build_context.internal_no_inline = true; + break; case BuildFlag_Tilde: build_context.tilde_backend = true; diff --git a/src/types.cpp b/src/types.cpp index fdc174d81..3f86d4c50 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -2011,6 +2011,24 @@ gb_internal bool is_type_valid_bit_set_elem(Type *t) { return false; } + +gb_internal bool is_valid_bit_field_backing_type(Type *type) { + if (type == nullptr) { + return false; + } + type = base_type(type); + if (is_type_untyped(type)) { + return false; + } + if (is_type_integer(type)) { + return true; + } + if (type->kind == Type_Array) { + return is_type_integer(type->Array.elem); + } + return false; +} + gb_internal Type *bit_set_to_int(Type *t) { GB_ASSERT(is_type_bit_set(t)); Type *bt = base_type(t); @@ -2018,6 +2036,9 @@ gb_internal Type *bit_set_to_int(Type *t) { if (underlying != nullptr && is_type_integer(underlying)) { return underlying; } + if (underlying != nullptr && is_valid_bit_field_backing_type(underlying)) { + return underlying; + } i64 sz = type_size_of(t); switch (sz) { From bd562116b834ed44440665f2fe4b080c665e9b85 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 15:08:26 +0100 Subject: [PATCH 096/198] Minor change to an internal flag --- src/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.cpp b/src/main.cpp index e7f4ccc0a..00734c050 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1419,6 +1419,7 @@ gb_internal bool parse_build_flags(Array args) { break; case BuildFlag_InternalModulePerFile: build_context.module_per_file = true; + build_context.use_separate_modules = true; break; case BuildFlag_InternalCached: build_context.cached = true; From 65c91b7dde613cf0b36212fdedf34cbf3c836b0c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 15:16:23 +0100 Subject: [PATCH 097/198] Fix code gen issue with `bit_set` --- src/llvm_backend_expr.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index dfb7e162e..4bb2676d1 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -4955,10 +4955,6 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { for (Ast *elem : cl->elems) { GB_ASSERT(elem->kind != Ast_FieldValue); - if (lb_is_elem_const(elem, et)) { - continue; - } - lbValue expr = lb_build_expr(p, elem); GB_ASSERT(expr.type->kind != Type_Tuple); From 55e0f97cc46ae904e27997c576f88e9542af6c48 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 15 Jul 2024 18:29:06 +0200 Subject: [PATCH 098/198] help `fmt` with `Type_Info_Struct` and `Type_Info_Bit_Field` changes --- base/runtime/core.odin | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/base/runtime/core.odin b/base/runtime/core.odin index f9bf57259..56aaefaa9 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -122,11 +122,11 @@ Type_Info_Struct_Flag :: enum u8 { Type_Info_Struct :: struct { // Slice these with `field_count` - types: [^]^Type_Info, - names: [^]string, - offsets: [^]uintptr, - usings: [^]bool, - tags: [^]string, + types: [^]^Type_Info `fmt:"v,field_count"`, + names: [^]string `fmt:"v,field_count"`, + offsets: [^]uintptr `fmt:"v,field_count"`, + usings: [^]bool `fmt:"v,field_count"`, + tags: [^]string `fmt:"v,field_count"`, field_count: i32, @@ -196,11 +196,11 @@ Type_Info_Soa_Pointer :: struct { } Type_Info_Bit_Field :: struct { backing_type: ^Type_Info, - names: [^]string, - types: [^]^Type_Info, - bit_sizes: [^]uintptr, - bit_offsets: [^]uintptr, - tags: [^]string, + names: [^]string `fmt:"v,field_count"`, + types: [^]^Type_Info `fmt:"v,field_count"`, + bit_sizes: [^]uintptr `fmt:"v,field_count"`, + bit_offsets: [^]uintptr `fmt:"v,field_count"`, + tags: [^]string `fmt:"v,field_count"`, field_count: int, } From dcaf085bfa68a355eb937469ae245ee41aeeeac5 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Sun, 12 May 2024 20:58:51 +0900 Subject: [PATCH 099/198] core/bytes: Add `alias` and `alias_inexactly` --- core/bytes/bytes.odin | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/core/bytes/bytes.odin b/core/bytes/bytes.odin index 208949fd8..7cbf092ac 100644 --- a/core/bytes/bytes.odin +++ b/core/bytes/bytes.odin @@ -1167,3 +1167,28 @@ fields_proc :: proc(s: []byte, f: proc(rune) -> bool, allocator := context.alloc return subslices[:] } + +// alias returns true iff a and b have a non-zero length, and any part of +// a overlaps with b. +alias :: proc "contextless" (a, b: []byte) -> bool { + a_len, b_len := len(a), len(b) + if a_len == 0 || b_len == 0 { + return false + } + + a_start, b_start := uintptr(raw_data(a)), uintptr(raw_data(b)) + a_end, b_end := a_start + uintptr(a_len-1), b_start + uintptr(b_len-1) + + return a_start <= b_end && b_start <= a_end +} + +// alias_inexactly returns true iff a and b have a non-zero length, +// the base pointer of a and b are NOT equal, and any part of a overlaps +// with b (ie: `alias(a, b)` with an exception that returns false for +// `a == b`, `b = a[:len(a)-69]` and similar conditions). +alias_inexactly :: proc "contextless" (a, b: []byte) -> bool { + if raw_data(a) == raw_data(b) { + return false + } + return alias(a, b) +} From ff13ee3281ef36632ea3d5f5e1bb7de835783b1a Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Fri, 12 Jul 2024 17:31:02 +0900 Subject: [PATCH 100/198] core/crypto: Enforce aliasing restrictions --- core/crypto/aes/aes_ctr.odin | 8 +++++--- core/crypto/aes/aes_gcm.odin | 7 +++++++ core/crypto/chacha20/chacha20.odin | 8 +++++--- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/core/crypto/aes/aes_ctr.odin b/core/crypto/aes/aes_ctr.odin index 1821a7bdf..de88a0c12 100644 --- a/core/crypto/aes/aes_ctr.odin +++ b/core/crypto/aes/aes_ctr.odin @@ -1,5 +1,6 @@ package aes +import "core:bytes" import "core:crypto/_aes/ct64" import "core:encoding/endian" import "core:math/bits" @@ -37,14 +38,15 @@ init_ctr :: proc(ctx: ^Context_CTR, key, iv: []byte, impl := Implementation.Hard xor_bytes_ctr :: proc(ctx: ^Context_CTR, dst, src: []byte) { assert(ctx._is_initialized) - // TODO: Enforcing that dst and src alias exactly or not at all - // is a good idea, though odd aliasing should be extremely uncommon. - src, dst := src, dst if dst_len := len(dst); dst_len < len(src) { src = src[:dst_len] } + if bytes.alias_inexactly(dst, src) { + panic("crypto/aes: dst and src alias inexactly") + } + for remaining := len(src); remaining > 0; { // Process multiple blocks at once if ctx._off == BLOCK_SIZE { diff --git a/core/crypto/aes/aes_gcm.odin b/core/crypto/aes/aes_gcm.odin index 66ef48db2..e097e0011 100644 --- a/core/crypto/aes/aes_gcm.odin +++ b/core/crypto/aes/aes_gcm.odin @@ -1,5 +1,6 @@ package aes +import "core:bytes" import "core:crypto" import "core:crypto/_aes" import "core:crypto/_aes/ct64" @@ -39,6 +40,9 @@ seal_gcm :: proc(ctx: ^Context_GCM, dst, tag, nonce, aad, plaintext: []byte) { if len(dst) != len(plaintext) { panic("crypto/aes: invalid destination ciphertext size") } + if bytes.alias_inexactly(dst, plaintext) { + panic("crypto/aes: dst and plaintext alias inexactly") + } if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw { gcm_seal_hw(&impl, dst, tag, nonce, aad, plaintext) @@ -73,6 +77,9 @@ open_gcm :: proc(ctx: ^Context_GCM, dst, nonce, aad, ciphertext, tag: []byte) -> if len(dst) != len(ciphertext) { panic("crypto/aes: invalid destination plaintext size") } + if bytes.alias_inexactly(dst, ciphertext) { + panic("crypto/aes: dst and ciphertext alias inexactly") + } if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw { return gcm_open_hw(&impl, dst, nonce, aad, ciphertext, tag) diff --git a/core/crypto/chacha20/chacha20.odin b/core/crypto/chacha20/chacha20.odin index 7f0950d03..73d3e1ea2 100644 --- a/core/crypto/chacha20/chacha20.odin +++ b/core/crypto/chacha20/chacha20.odin @@ -7,6 +7,7 @@ See: */ package chacha20 +import "core:bytes" import "core:encoding/endian" import "core:math/bits" import "core:mem" @@ -121,14 +122,15 @@ seek :: proc(ctx: ^Context, block_nr: u64) { xor_bytes :: proc(ctx: ^Context, dst, src: []byte) { assert(ctx._is_initialized) - // TODO: Enforcing that dst and src alias exactly or not at all - // is a good idea, though odd aliasing should be extremely uncommon. - src, dst := src, dst if dst_len := len(dst); dst_len < len(src) { src = src[:dst_len] } + if bytes.alias_inexactly(dst, src) { + panic("crypto/chacha20: dst and src alias inexactly") + } + for remaining := len(src); remaining > 0; { // Process multiple blocks at once if ctx._off == _BLOCK_SIZE { From 0d8dadec8ad8c4c1a39a6231b1b43da6110353ff Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Fri, 12 Jul 2024 17:12:39 +0900 Subject: [PATCH 101/198] tests/core/crypto: Fix new `-vet` issues --- tests/core/crypto/test_core_crypto_ecc25519.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/core/crypto/test_core_crypto_ecc25519.odin b/tests/core/crypto/test_core_crypto_ecc25519.odin index baf4a1a38..fec4fa38e 100644 --- a/tests/core/crypto/test_core_crypto_ecc25519.odin +++ b/tests/core/crypto/test_core_crypto_ecc25519.odin @@ -58,9 +58,9 @@ test_sqrt_ratio_m1 :: proc(t: ^testing.T) { v_bytes, _ := hex.decode(transmute([]byte)(v.v), context.temp_allocator) r_bytes, _ := hex.decode(transmute([]byte)(v.r), context.temp_allocator) - u_ := transmute(^[32]byte)(raw_data(u_bytes)) - v_ := transmute(^[32]byte)(raw_data(v_bytes)) - r_ := transmute(^[32]byte)(raw_data(r_bytes)) + u_ := (^[32]byte)(raw_data(u_bytes)) + v_ := (^[32]byte)(raw_data(v_bytes)) + r_ := (^[32]byte)(raw_data(r_bytes)) u, vee, r: field.Tight_Field_Element field.fe_from_bytes(&u, u_) From 4815154c31350093212b3680c0713064c7d83a34 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Wed, 26 Jun 2024 04:39:52 +0900 Subject: [PATCH 102/198] test/core/crypto: Fix a copy-paste issue in failure spew --- tests/core/crypto/test_core_crypto_kdf.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/crypto/test_core_crypto_kdf.odin b/tests/core/crypto/test_core_crypto_kdf.odin index 247529e65..c15dc2206 100644 --- a/tests/core/crypto/test_core_crypto_kdf.odin +++ b/tests/core/crypto/test_core_crypto_kdf.odin @@ -161,7 +161,7 @@ test_pbkdf2 :: proc(t: ^testing.T) { testing.expectf( t, dst_str == v.dk, - "HMAC-%s: Expected: %s for input of (%s, %s, %d), but got %s instead", + "PBKDF2-%s: Expected: %s for input of (%s, %s, %d), but got %s instead", algo_name, v.dk, v.password, From 401877184f938e3a9ca7f7b67f9fa4ae98153430 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Sat, 13 Jul 2024 22:29:02 +0900 Subject: [PATCH 103/198] tests/benchmark/crypto: Benchmark AES256-CTR --- tests/benchmark/crypto/benchmark_crypto.odin | 56 ++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/tests/benchmark/crypto/benchmark_crypto.odin b/tests/benchmark/crypto/benchmark_crypto.odin index e90216ad6..b2ac4bca3 100644 --- a/tests/benchmark/crypto/benchmark_crypto.odin +++ b/tests/benchmark/crypto/benchmark_crypto.odin @@ -28,6 +28,32 @@ benchmark_crypto :: proc(t: ^testing.T) { strings.builder_destroy(&str) } + { + name := "AES256-CTR 64 bytes" + options := &time.Benchmark_Options { + rounds = 1_000, + bytes = 64, + setup = _setup_sized_buf, + bench = _benchmark_aes256_ctr, + teardown = _teardown_sized_buf, + } + + err := time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "AES256-CTR 1024 bytes" + options.bytes = 1024 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "AES256-CTR 65536 bytes" + options.bytes = 65536 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + } { name := "ChaCha20 64 bytes" options := &time.Benchmark_Options { @@ -323,6 +349,36 @@ _benchmark_chacha20poly1305 :: proc( return nil } +@(private) +_benchmark_aes256_ctr :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + buf := options.input + key := [aes.KEY_SIZE_256]byte { + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + } + nonce := [aes.CTR_IV_SIZE]byte { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + } + + ctx: aes.Context_CTR = --- + aes.init_ctr(&ctx, key[:], nonce[:]) + + for _ in 0 ..= options.rounds { + aes.xor_bytes_ctr(&ctx, buf, buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + return nil +} + _benchmark_aes256_gcm :: proc( options: ^time.Benchmark_Options, allocator := context.allocator, From 390cd3c30d9fe451312f5af26cfb845d4d0a83f9 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Mon, 1 Jul 2024 02:55:01 +0900 Subject: [PATCH 104/198] core/simd/x86: Fix some intrinsics - _mm_slli_si128 produced totally incorrect output - _mm_storeu_si128 refered to a LLVM intrinsic that is missing --- core/simd/x86/sse2.odin | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/core/simd/x86/sse2.odin b/core/simd/x86/sse2.odin index 52286cbb8..426359031 100644 --- a/core/simd/x86/sse2.odin +++ b/core/simd/x86/sse2.odin @@ -144,19 +144,26 @@ _mm_subs_epu16 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { _mm_slli_si128_impl :: #force_inline proc "c" (a: __m128i, $IMM8: u32) -> __m128i { shift :: IMM8 & 0xff + // This needs to emit behavior identical to PSLLDQ which is as follows: + // + // TEMP := COUNT + // IF (TEMP > 15) THEN TEMP := 16; FI + // DEST := DEST << (TEMP * 8) + // DEST[MAXVL-1:128] (Unmodified) + return transmute(__m128i)simd.shuffle( - transmute(i8x16)a, i8x16(0), - 0 when shift > 15 else (16 - shift + 0), - 1 when shift > 15 else (16 - shift + 1), - 2 when shift > 15 else (16 - shift + 2), - 3 when shift > 15 else (16 - shift + 3), - 4 when shift > 15 else (16 - shift + 4), - 5 when shift > 15 else (16 - shift + 5), - 6 when shift > 15 else (16 - shift + 6), - 7 when shift > 15 else (16 - shift + 7), - 8 when shift > 15 else (16 - shift + 8), - 9 when shift > 15 else (16 - shift + 9), + transmute(i8x16)a, + 0 when shift > 15 else (16 - shift + 0), + 1 when shift > 15 else (16 - shift + 1), + 2 when shift > 15 else (16 - shift + 2), + 3 when shift > 15 else (16 - shift + 3), + 4 when shift > 15 else (16 - shift + 4), + 5 when shift > 15 else (16 - shift + 5), + 6 when shift > 15 else (16 - shift + 6), + 7 when shift > 15 else (16 - shift + 7), + 8 when shift > 15 else (16 - shift + 8), + 9 when shift > 15 else (16 - shift + 9), 10 when shift > 15 else (16 - shift + 10), 11 when shift > 15 else (16 - shift + 11), 12 when shift > 15 else (16 - shift + 12), @@ -435,7 +442,7 @@ _mm_store_si128 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) { } @(enable_target_feature="sse2") _mm_storeu_si128 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) { - storeudq(mem_addr, a) + intrinsics.unaligned_store(mem_addr, a) } @(enable_target_feature="sse2") _mm_storel_epi64 :: #force_inline proc "c" (mem_addr: ^__m128i, a: __m128i) { @@ -1178,8 +1185,6 @@ foreign _ { cvttsd2si :: proc(a: __m128d) -> i32 --- @(link_name="llvm.x86.sse2.cvttps2dq") cvttps2dq :: proc(a: __m128) -> i32x4 --- - @(link_name="llvm.x86.sse2.storeu.dq") - storeudq :: proc(mem_addr: rawptr, a: __m128i) --- @(link_name="llvm.x86.sse2.storeu.pd") storeupd :: proc(mem_addr: rawptr, a: __m128d) --- From c9c0b9ea7b6ca2fcffa9b4bd69c0db6975caeb82 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Sat, 22 Jun 2024 14:52:43 +0900 Subject: [PATCH 105/198] core/crypto: Fix/add some documentation (NFC) --- core/crypto/aes/aes.odin | 1 - core/crypto/crypto.odin | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/core/crypto/aes/aes.odin b/core/crypto/aes/aes.odin index e895c5fe0..ef305fd21 100644 --- a/core/crypto/aes/aes.odin +++ b/core/crypto/aes/aes.odin @@ -6,7 +6,6 @@ See: - https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf - https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf */ - package aes import "core:crypto/_aes" diff --git a/core/crypto/crypto.odin b/core/crypto/crypto.odin index f83d20dd7..323cc45d6 100644 --- a/core/crypto/crypto.odin +++ b/core/crypto/crypto.odin @@ -60,7 +60,11 @@ rand_bytes :: proc (dst: []byte) { _rand_bytes(dst) } - +// random_generator returns a `runtime.Random_Generator` backed by the +// system entropy source. +// +// Support for the system entropy source can be checked with the +// `HAS_RAND_BYTES` boolean constant. random_generator :: proc() -> runtime.Random_Generator { return { procedure = proc(data: rawptr, mode: runtime.Random_Generator_Mode, p: []byte) { From 1bc21c3481720892ade04d2c2b8243128be0a03d Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Mon, 8 Jul 2024 11:30:48 +0900 Subject: [PATCH 106/198] core/crypto/_aes: Remove redundant sanity checks --- core/crypto/_aes/ct64/api.odin | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/core/crypto/_aes/ct64/api.odin b/core/crypto/_aes/ct64/api.odin index ae624971c..f57a630b1 100644 --- a/core/crypto/_aes/ct64/api.odin +++ b/core/crypto/_aes/ct64/api.odin @@ -7,9 +7,8 @@ STRIDE :: 4 // Context is a keyed AES (ECB) instance. Context :: struct { - _sk_exp: [120]u64, - _num_rounds: int, - _is_initialized: bool, + _sk_exp: [120]u64, + _num_rounds: int, } // init initializes a context for AES with the provided key. @@ -18,13 +17,10 @@ init :: proc(ctx: ^Context, key: []byte) { ctx._num_rounds = keysched(skey[:], key) skey_expand(ctx._sk_exp[:], skey[:], ctx._num_rounds) - ctx._is_initialized = true } // encrypt_block sets `dst` to `AES-ECB-Encrypt(src)`. encrypt_block :: proc(ctx: ^Context, dst, src: []byte) { - assert(ctx._is_initialized) - q: [8]u64 load_blockx1(&q, src) _encrypt(&q, ctx._sk_exp[:], ctx._num_rounds) @@ -33,8 +29,6 @@ encrypt_block :: proc(ctx: ^Context, dst, src: []byte) { // encrypt_block sets `dst` to `AES-ECB-Decrypt(src)`. decrypt_block :: proc(ctx: ^Context, dst, src: []byte) { - assert(ctx._is_initialized) - q: [8]u64 load_blockx1(&q, src) _decrypt(&q, ctx._sk_exp[:], ctx._num_rounds) @@ -43,8 +37,6 @@ decrypt_block :: proc(ctx: ^Context, dst, src: []byte) { // encrypt_blocks sets `dst` to `AES-ECB-Encrypt(src[0], .. src[n])`. encrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) { - assert(ctx._is_initialized) - q: [8]u64 = --- src, dst := src, dst @@ -67,8 +59,6 @@ encrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) { // decrypt_blocks sets dst to `AES-ECB-Decrypt(src[0], .. src[n])`. decrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) { - assert(ctx._is_initialized) - q: [8]u64 = --- src, dst := src, dst From f578994fa634a615a6800703e18dfbf931958388 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Fri, 7 Jun 2024 22:26:02 +0900 Subject: [PATCH 107/198] core/simd/x86: Make the AES-NI intrinsics consistent with Intel --- core/simd/x86/aes.odin | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/simd/x86/aes.odin b/core/simd/x86/aes.odin index 3a32de0d6..66a1f744c 100644 --- a/core/simd/x86/aes.odin +++ b/core/simd/x86/aes.odin @@ -2,32 +2,32 @@ package simd_x86 @(require_results, enable_target_feature = "aes") -_mm_aesdec :: #force_inline proc "c" (a, b: __m128i) -> __m128i { +_mm_aesdec_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return aesdec(a, b) } @(require_results, enable_target_feature = "aes") -_mm_aesdeclast :: #force_inline proc "c" (a, b: __m128i) -> __m128i { +_mm_aesdeclast_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return aesdeclast(a, b) } @(require_results, enable_target_feature = "aes") -_mm_aesenc :: #force_inline proc "c" (a, b: __m128i) -> __m128i { +_mm_aesenc_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return aesenc(a, b) } @(require_results, enable_target_feature = "aes") -_mm_aesenclast :: #force_inline proc "c" (a, b: __m128i) -> __m128i { +_mm_aesenclast_si128 :: #force_inline proc "c" (a, b: __m128i) -> __m128i { return aesenclast(a, b) } @(require_results, enable_target_feature = "aes") -_mm_aesimc :: #force_inline proc "c" (a: __m128i) -> __m128i { +_mm_aesimc_si128 :: #force_inline proc "c" (a: __m128i) -> __m128i { return aesimc(a) } @(require_results, enable_target_feature = "aes") -_mm_aeskeygenassist :: #force_inline proc "c" (a: __m128i, $IMM8: u8) -> __m128i { +_mm_aeskeygenassist_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u8) -> __m128i { return aeskeygenassist(a, u8(IMM8)) } From 69026852ce9c79210ee4c8719a492facafe207df Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Wed, 5 Jun 2024 04:53:08 +0900 Subject: [PATCH 108/198] core/crypto/aes: Add Intel AES-NI support This supports AES-NI + PCLMUL, and provides optimized key schedule, ECB, CTR, and GCM. Other modes are trivial to add later if required. --- core/crypto/_aes/hw_intel/api.odin | 43 +++ core/crypto/_aes/hw_intel/ghash.odin | 281 ++++++++++++++++++ .../_aes/hw_intel/hw_intel_keysched.odin | 178 +++++++++++ core/crypto/aes/aes_ctr.odin | 24 +- core/crypto/aes/aes_ctr_hw_intel.odin | 151 ++++++++++ core/crypto/aes/aes_ecb_hw_intel.odin | 58 ++++ core/crypto/aes/aes_gcm.odin | 7 +- core/crypto/aes/aes_gcm_hw_intel.odin | 231 ++++++++++++++ core/crypto/aes/aes_impl_hw_gen.odin | 1 + core/crypto/aes/aes_impl_hw_intel.odin | 18 ++ core/simd/x86/aes.odin | 4 +- tests/core/crypto/test_core_crypto_aes.odin | 12 +- 12 files changed, 982 insertions(+), 26 deletions(-) create mode 100644 core/crypto/_aes/hw_intel/api.odin create mode 100644 core/crypto/_aes/hw_intel/ghash.odin create mode 100644 core/crypto/_aes/hw_intel/hw_intel_keysched.odin create mode 100644 core/crypto/aes/aes_ctr_hw_intel.odin create mode 100644 core/crypto/aes/aes_ecb_hw_intel.odin create mode 100644 core/crypto/aes/aes_gcm_hw_intel.odin create mode 100644 core/crypto/aes/aes_impl_hw_intel.odin diff --git a/core/crypto/_aes/hw_intel/api.odin b/core/crypto/_aes/hw_intel/api.odin new file mode 100644 index 000000000..5cb5a68bb --- /dev/null +++ b/core/crypto/_aes/hw_intel/api.odin @@ -0,0 +1,43 @@ +//+build amd64 +package aes_hw_intel + +import "core:sys/info" + +// is_supporte returns true iff hardware accelerated AES +// is supported. +is_supported :: proc "contextless" () -> bool { + features, ok := info.cpu_features.? + if !ok { + return false + } + + // Note: Everything with AES-NI and PCLMULQDQ has support for + // the required SSE extxtensions. + req_features :: info.CPU_Features{ + .sse2, + .ssse3, + .sse41, + .aes, + .pclmulqdq, + } + return features >= req_features +} + +// Context is a keyed AES (ECB) instance. +Context :: struct { + // Note: The ideal thing to do is for the expanded round keys to be + // arrays of `__m128i`, however that implies alignment (or using AVX). + // + // All the people using e-waste processors that don't support an + // insturction set that has been around for over 10 years are why + // we can't have nice things. + _sk_exp_enc: [15][16]byte, + _sk_exp_dec: [15][16]byte, + _num_rounds: int, +} + +// init initializes a context for AES with the provided key. +init :: proc(ctx: ^Context, key: []byte) { + keysched(ctx, key) +} + diff --git a/core/crypto/_aes/hw_intel/ghash.odin b/core/crypto/_aes/hw_intel/ghash.odin new file mode 100644 index 000000000..9a5208523 --- /dev/null +++ b/core/crypto/_aes/hw_intel/ghash.odin @@ -0,0 +1,281 @@ +// Copyright (c) 2017 Thomas Pornin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//+build amd64 +package aes_hw_intel + +import "base:intrinsics" +import "core:crypto/_aes" +import "core:simd" +import "core:simd/x86" + +@(private = "file") +GHASH_STRIDE_HW :: 4 +@(private = "file") +GHASH_STRIDE_BYTES_HW :: GHASH_STRIDE_HW * _aes.GHASH_BLOCK_SIZE + +// GHASH is defined over elements of GF(2^128) with "full little-endian" +// representation: leftmost byte is least significant, and, within each +// byte, leftmost _bit_ is least significant. The natural ordering in +// x86 is "mixed little-endian": bytes are ordered from least to most +// significant, but bits within a byte are in most-to-least significant +// order. Going to full little-endian representation would require +// reversing bits within each byte, which is doable but expensive. +// +// Instead, we go to full big-endian representation, by swapping bytes +// around, which is done with a single _mm_shuffle_epi8() opcode (it +// comes with SSSE3; all CPU that offer pclmulqdq also have SSSE3). We +// can use a full big-endian representation because in a carryless +// multiplication, we have a nice bit reversal property: +// +// rev_128(x) * rev_128(y) = rev_255(x * y) +// +// So by using full big-endian, we still get the right result, except +// that it is right-shifted by 1 bit. The left-shift is relatively +// inexpensive, and it can be mutualised. +// +// Since SSE2 opcodes do not have facilities for shitfting full 128-bit +// values with bit precision, we have to break down values into 64-bit +// chunks. We number chunks from 0 to 3 in left to right order. + +@(private = "file") +byteswap_index := transmute(x86.__m128i)simd.i8x16{ + // Note: simd.i8x16 is reverse order from x86._mm_set_epi8. + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, +} + +@(private = "file", require_results, enable_target_feature = "sse2,ssse3") +byteswap :: #force_inline proc "contextless" (x: x86.__m128i) -> x86.__m128i { + return x86._mm_shuffle_epi8(x, byteswap_index) +} + +// From a 128-bit value kw, compute kx as the XOR of the two 64-bit +// halves of kw (into the right half of kx; left half is unspecified), +// and return kx. +@(private = "file", require_results, enable_target_feature = "sse2") +bk :: #force_inline proc "contextless" (kw: x86.__m128i) -> x86.__m128i { + return x86._mm_xor_si128(kw, x86._mm_shuffle_epi32(kw, 0x0e)) +} + +// Combine two 64-bit values (k0:k1) into a 128-bit (kw) value and +// the XOR of the two values (kx), and return (kw, kx). +@(private = "file", enable_target_feature = "sse2") +pbk :: #force_inline proc "contextless" (k0, k1: x86.__m128i) -> (x86.__m128i, x86.__m128i) { + kw := x86._mm_unpacklo_epi64(k1, k0) + kx := x86._mm_xor_si128(k0, k1) + return kw, kx +} + +// Left-shift by 1 bit a 256-bit value (in four 64-bit words). +@(private = "file", require_results, enable_target_feature = "sse2") +sl_256 :: #force_inline proc "contextless" (x0, x1, x2, x3: x86.__m128i) -> (x86.__m128i, x86.__m128i, x86.__m128i, x86.__m128i) { + x0, x1, x2, x3 := x0, x1, x2, x3 + + x0 = x86._mm_or_si128(x86._mm_slli_epi64(x0, 1), x86._mm_srli_epi64(x1, 63)) + x1 = x86._mm_or_si128(x86._mm_slli_epi64(x1, 1), x86._mm_srli_epi64(x2, 63)) + x2 = x86._mm_or_si128(x86._mm_slli_epi64(x2, 1), x86._mm_srli_epi64(x3, 63)) + x3 = x86._mm_slli_epi64(x3, 1) + + return x0, x1, x2, x3 +} + +// Perform reduction in GF(2^128). +@(private = "file", require_results, enable_target_feature = "sse2") +reduce_f128 :: #force_inline proc "contextless" (x0, x1, x2, x3: x86.__m128i) -> (x86.__m128i, x86.__m128i) { + x0, x1, x2 := x0, x1, x2 + + x1 = x86._mm_xor_si128( + x1, + x86._mm_xor_si128( + x86._mm_xor_si128( + x3, + x86._mm_srli_epi64(x3, 1)), + x86._mm_xor_si128( + x86._mm_srli_epi64(x3, 2), + x86._mm_srli_epi64(x3, 7)))) + x2 = x86._mm_xor_si128( + x86._mm_xor_si128( + x2, + x86._mm_slli_epi64(x3, 63)), + x86._mm_xor_si128( + x86._mm_slli_epi64(x3, 62), + x86._mm_slli_epi64(x3, 57))) + x0 = x86._mm_xor_si128( + x0, + x86._mm_xor_si128( + x86._mm_xor_si128( + x2, + x86._mm_srli_epi64(x2, 1)), + x86._mm_xor_si128( + x86._mm_srli_epi64(x2, 2), + x86._mm_srli_epi64(x2, 7)))) + x1 = x86._mm_xor_si128( + x86._mm_xor_si128( + x1, + x86._mm_slli_epi64(x2, 63)), + x86._mm_xor_si128( + x86._mm_slli_epi64(x2, 62), + x86._mm_slli_epi64(x2, 57))) + + return x0, x1 +} + +// Square value kw in GF(2^128) into (dw,dx). +@(private = "file", require_results, enable_target_feature = "sse2,pclmul") +square_f128 :: #force_inline proc "contextless" (kw: x86.__m128i) -> (x86.__m128i, x86.__m128i) { + z1 := x86._mm_clmulepi64_si128(kw, kw, 0x11) + z3 := x86._mm_clmulepi64_si128(kw, kw, 0x00) + z0 := x86._mm_shuffle_epi32(z1, 0x0E) + z2 := x86._mm_shuffle_epi32(z3, 0x0E) + z0, z1, z2, z3 = sl_256(z0, z1, z2, z3) + z0, z1 = reduce_f128(z0, z1, z2, z3) + return pbk(z0, z1) +} + +// ghash calculates the GHASH of data, with the key `key`, and input `dst` +// and `data`, and stores the resulting digest in `dst`. +// +// Note: `dst` is both an input and an output, to support easy implementation +// of GCM. +@(enable_target_feature = "sse2,ssse3,pclmul") +ghash :: proc "contextless" (dst, key, data: []byte) #no_bounds_check { + if len(dst) != _aes.GHASH_BLOCK_SIZE || len(key) != _aes.GHASH_BLOCK_SIZE { + intrinsics.trap() + } + + // Note: BearSSL opts to copy the remainder into a zero-filled + // 64-byte buffer. We do something slightly more simple. + + // Load key and dst (h and y). + yw := intrinsics.unaligned_load((^x86.__m128i)(raw_data(dst))) + h1w := intrinsics.unaligned_load((^x86.__m128i)(raw_data(key))) + yw = byteswap(yw) + h1w = byteswap(h1w) + h1x := bk(h1w) + + // Process 4 blocks at a time + buf := data + l := len(buf) + if l >= GHASH_STRIDE_BYTES_HW { + // Compute h2 = h^2 + h2w, h2x := square_f128(h1w) + + // Compute h3 = h^3 = h*(h^2) + t1 := x86._mm_clmulepi64_si128(h1w, h2w, 0x11) + t3 := x86._mm_clmulepi64_si128(h1w, h2w, 0x00) + t2 := x86._mm_xor_si128( + x86._mm_clmulepi64_si128(h1x, h2x, 0x00), + x86._mm_xor_si128(t1, t3)) + t0 := x86._mm_shuffle_epi32(t1, 0x0E) + t1 = x86._mm_xor_si128(t1, x86._mm_shuffle_epi32(t2, 0x0E)) + t2 = x86._mm_xor_si128(t2, x86._mm_shuffle_epi32(t3, 0x0E)) + t0, t1, t2, t3 = sl_256(t0, t1, t2, t3) + t0, t1 = reduce_f128(t0, t1, t2, t3) + h3w, h3x := pbk(t0, t1) + + // Compute h4 = h^4 = (h^2)^2 + h4w, h4x := square_f128(h2w) + + for l >= GHASH_STRIDE_BYTES_HW { + aw0 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf))) + aw1 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf[16:]))) + aw2 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf[32:]))) + aw3 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(buf[48:]))) + aw0 = byteswap(aw0) + aw1 = byteswap(aw1) + aw2 = byteswap(aw2) + aw3 = byteswap(aw3) + buf, l = buf[GHASH_STRIDE_BYTES_HW:], l - GHASH_STRIDE_BYTES_HW + + aw0 = x86._mm_xor_si128(aw0, yw) + ax1 := bk(aw1) + ax2 := bk(aw2) + ax3 := bk(aw3) + ax0 := bk(aw0) + + t1 = x86._mm_xor_si128( + x86._mm_xor_si128( + x86._mm_clmulepi64_si128(aw0, h4w, 0x11), + x86._mm_clmulepi64_si128(aw1, h3w, 0x11)), + x86._mm_xor_si128( + x86._mm_clmulepi64_si128(aw2, h2w, 0x11), + x86._mm_clmulepi64_si128(aw3, h1w, 0x11))) + t3 = x86._mm_xor_si128( + x86._mm_xor_si128( + x86._mm_clmulepi64_si128(aw0, h4w, 0x00), + x86._mm_clmulepi64_si128(aw1, h3w, 0x00)), + x86._mm_xor_si128( + x86._mm_clmulepi64_si128(aw2, h2w, 0x00), + x86._mm_clmulepi64_si128(aw3, h1w, 0x00))) + t2 = x86._mm_xor_si128( + x86._mm_xor_si128( + x86._mm_clmulepi64_si128(ax0, h4x, 0x00), + x86._mm_clmulepi64_si128(ax1, h3x, 0x00)), + x86._mm_xor_si128( + x86._mm_clmulepi64_si128(ax2, h2x, 0x00), + x86._mm_clmulepi64_si128(ax3, h1x, 0x00))) + t2 = x86._mm_xor_si128(t2, x86._mm_xor_si128(t1, t3)) + t0 = x86._mm_shuffle_epi32(t1, 0x0E) + t1 = x86._mm_xor_si128(t1, x86._mm_shuffle_epi32(t2, 0x0E)) + t2 = x86._mm_xor_si128(t2, x86._mm_shuffle_epi32(t3, 0x0E)) + t0, t1, t2, t3 = sl_256(t0, t1, t2, t3) + t0, t1 = reduce_f128(t0, t1, t2, t3) + yw = x86._mm_unpacklo_epi64(t1, t0) + } + } + + // Process 1 block at a time + src: []byte + for l > 0 { + if l >= _aes.GHASH_BLOCK_SIZE { + src = buf + buf = buf[_aes.GHASH_BLOCK_SIZE:] + l -= _aes.GHASH_BLOCK_SIZE + } else { + tmp: [_aes.GHASH_BLOCK_SIZE]byte + copy(tmp[:], buf) + src = tmp[:] + l = 0 + } + + aw := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src))) + aw = byteswap(aw) + + aw = x86._mm_xor_si128(aw, yw) + ax := bk(aw) + + t1 := x86._mm_clmulepi64_si128(aw, h1w, 0x11) + t3 := x86._mm_clmulepi64_si128(aw, h1w, 0x00) + t2 := x86._mm_clmulepi64_si128(ax, h1x, 0x00) + t2 = x86._mm_xor_si128(t2, x86._mm_xor_si128(t1, t3)) + t0 := x86._mm_shuffle_epi32(t1, 0x0E) + t1 = x86._mm_xor_si128(t1, x86._mm_shuffle_epi32(t2, 0x0E)) + t2 = x86._mm_xor_si128(t2, x86._mm_shuffle_epi32(t3, 0x0E)) + t0, t1, t2, t3 = sl_256(t0, t1, t2, t3) + t0, t1 = reduce_f128(t0, t1, t2, t3) + yw = x86._mm_unpacklo_epi64(t1, t0) + } + + // Write back the hash (dst, aka y) + yw = byteswap(yw) + intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst)), yw) +} diff --git a/core/crypto/_aes/hw_intel/hw_intel_keysched.odin b/core/crypto/_aes/hw_intel/hw_intel_keysched.odin new file mode 100644 index 000000000..911dffbd5 --- /dev/null +++ b/core/crypto/_aes/hw_intel/hw_intel_keysched.odin @@ -0,0 +1,178 @@ +// Copyright (c) 2017 Thomas Pornin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//+build amd64 +package aes_hw_intel + +import "base:intrinsics" +import "core:crypto/_aes" +import "core:mem" +import "core:simd/x86" + +// Intel AES-NI based implementation. Inspiration taken from BearSSL. +// +// Note: This assumes that the SROA optimization pass is enabled to be +// anything resembling performat otherwise, LLVM will not elide a massive +// number of redundant loads/stores it generates for every intrinsic call. + +@(private = "file", require_results, enable_target_feature = "sse2") +expand_step128 :: #force_inline proc(k1, k2: x86.__m128i) -> x86.__m128i { + k1, k2 := k1, k2 + + k2 = x86._mm_shuffle_epi32(k2, 0xff) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + return x86._mm_xor_si128(k1, k2) +} + +@(private = "file", require_results, enable_target_feature = "sse,sse2") +expand_step192a :: #force_inline proc (k1_, k2_: ^x86.__m128i, k3: x86.__m128i) -> (x86.__m128i, x86.__m128i) { + k1, k2, k3 := k1_^, k2_^, k3 + + k3 = x86._mm_shuffle_epi32(k3, 0x55) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, k3) + + tmp := k2 + k2 = x86._mm_xor_si128(k2, x86._mm_slli_si128(k2, 0x04)) + k2 = x86._mm_xor_si128(k2, x86._mm_shuffle_epi32(k1, 0xff)) + + k1_, k2_ := k1_, k2_ + k1_^, k2_^ = k1, k2 + + r1 := transmute(x86.__m128i)(x86._mm_shuffle_ps(transmute(x86.__m128)(tmp), transmute(x86.__m128)(k1), 0x44)) + r2 := transmute(x86.__m128i)(x86._mm_shuffle_ps(transmute(x86.__m128)(k1), transmute(x86.__m128)(k2), 0x4e)) + + return r1, r2 +} + +@(private = "file", require_results, enable_target_feature = "sse2") +expand_step192b :: #force_inline proc (k1_, k2_: ^x86.__m128i, k3: x86.__m128i) -> x86.__m128i { + k1, k2, k3 := k1_^, k2_^, k3 + + k3 = x86._mm_shuffle_epi32(k3, 0x55) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, k3) + + k2 = x86._mm_xor_si128(k2, x86._mm_slli_si128(k2, 0x04)) + k2 = x86._mm_xor_si128(k2, x86._mm_shuffle_epi32(k1, 0xff)) + + k1_, k2_ := k1_, k2_ + k1_^, k2_^ = k1, k2 + + return k1 +} + +@(private = "file", require_results, enable_target_feature = "sse2") +expand_step256b :: #force_inline proc(k1, k2: x86.__m128i) -> x86.__m128i { + k1, k2 := k1, k2 + + k2 = x86._mm_shuffle_epi32(k2, 0xaa) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + k1 = x86._mm_xor_si128(k1, x86._mm_slli_si128(k1, 0x04)) + return x86._mm_xor_si128(k1, k2) +} + +@(private = "file", enable_target_feature = "aes") +derive_dec_keys :: proc(ctx: ^Context, sks: ^[15]x86.__m128i, num_rounds: int) { + intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_dec[0]), sks[num_rounds]) + for i in 1 ..< num_rounds { + tmp := x86._mm_aesimc_si128(sks[i]) + intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_dec[num_rounds - i]), tmp) + } + intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_dec[num_rounds]), sks[0]) +} + +@(private, enable_target_feature = "sse,sse2,aes") +keysched :: proc(ctx: ^Context, key: []byte) { + sks: [15]x86.__m128i = --- + + // Compute the encryption keys. + num_rounds, key_len := 0, len(key) + switch key_len { + case _aes.KEY_SIZE_128: + sks[0] = intrinsics.unaligned_load((^x86.__m128i)(raw_data(key))) + sks[1] = expand_step128(sks[0], x86._mm_aeskeygenassist_si128(sks[0], 0x01)) + sks[2] = expand_step128(sks[1], x86._mm_aeskeygenassist_si128(sks[1], 0x02)) + sks[3] = expand_step128(sks[2], x86._mm_aeskeygenassist_si128(sks[2], 0x04)) + sks[4] = expand_step128(sks[3], x86._mm_aeskeygenassist_si128(sks[3], 0x08)) + sks[5] = expand_step128(sks[4], x86._mm_aeskeygenassist_si128(sks[4], 0x10)) + sks[6] = expand_step128(sks[5], x86._mm_aeskeygenassist_si128(sks[5], 0x20)) + sks[7] = expand_step128(sks[6], x86._mm_aeskeygenassist_si128(sks[6], 0x40)) + sks[8] = expand_step128(sks[7], x86._mm_aeskeygenassist_si128(sks[7], 0x80)) + sks[9] = expand_step128(sks[8], x86._mm_aeskeygenassist_si128(sks[8], 0x1b)) + sks[10] = expand_step128(sks[9], x86._mm_aeskeygenassist_si128(sks[9], 0x36)) + num_rounds = _aes.ROUNDS_128 + case _aes.KEY_SIZE_192: + k0 := intrinsics.unaligned_load((^x86.__m128i)(raw_data(key))) + k1 := x86.__m128i{ + intrinsics.unaligned_load((^i64)(raw_data(key[16:]))), + 0, + } + sks[0] = k0 + sks[1], sks[2] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x01)) + sks[3] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x02)) + sks[4], sks[5] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x04)) + sks[6] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x08)) + sks[7], sks[8] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x10)) + sks[9] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x20)) + sks[10], sks[11] = expand_step192a(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x40)) + sks[12] = expand_step192b(&k0, &k1, x86._mm_aeskeygenassist_si128(k1, 0x80)) + num_rounds = _aes.ROUNDS_192 + case _aes.KEY_SIZE_256: + sks[0] = intrinsics.unaligned_load((^x86.__m128i)(raw_data(key))) + sks[1] = intrinsics.unaligned_load((^x86.__m128i)(raw_data(key[16:]))) + sks[2] = expand_step128(sks[0], x86._mm_aeskeygenassist_si128(sks[1], 0x01)) + sks[3] = expand_step256b(sks[1], x86._mm_aeskeygenassist_si128(sks[2], 0x01)) + sks[4] = expand_step128(sks[2], x86._mm_aeskeygenassist_si128(sks[3], 0x02)) + sks[5] = expand_step256b(sks[3], x86._mm_aeskeygenassist_si128(sks[4], 0x02)) + sks[6] = expand_step128(sks[4], x86._mm_aeskeygenassist_si128(sks[5], 0x04)) + sks[7] = expand_step256b(sks[5], x86._mm_aeskeygenassist_si128(sks[6], 0x04)) + sks[8] = expand_step128(sks[6], x86._mm_aeskeygenassist_si128(sks[7], 0x08)) + sks[9] = expand_step256b(sks[7], x86._mm_aeskeygenassist_si128(sks[8], 0x08)) + sks[10] = expand_step128(sks[8], x86._mm_aeskeygenassist_si128(sks[9], 0x10)) + sks[11] = expand_step256b(sks[9], x86._mm_aeskeygenassist_si128(sks[10], 0x10)) + sks[12] = expand_step128(sks[10], x86._mm_aeskeygenassist_si128(sks[11], 0x20)) + sks[13] = expand_step256b(sks[11], x86._mm_aeskeygenassist_si128(sks[12], 0x20)) + sks[14] = expand_step128(sks[12], x86._mm_aeskeygenassist_si128(sks[13], 0x40)) + num_rounds = _aes.ROUNDS_256 + case: + panic("crypto/aes: invalid AES key size") + } + for i in 0 ..= num_rounds { + intrinsics.unaligned_store((^x86.__m128i)(&ctx._sk_exp_enc[i]), sks[i]) + } + + // Compute the decryption keys. GCM and CTR do not need this, however + // ECB, CBC, OCB3, etc do. + derive_dec_keys(ctx, &sks, num_rounds) + + ctx._num_rounds = num_rounds + + mem.zero_explicit(&sks, size_of(sks)) +} diff --git a/core/crypto/aes/aes_ctr.odin b/core/crypto/aes/aes_ctr.odin index de88a0c12..1c5fe31e8 100644 --- a/core/crypto/aes/aes_ctr.odin +++ b/core/crypto/aes/aes_ctr.odin @@ -125,8 +125,8 @@ reset_ctr :: proc "contextless" (ctx: ^Context_CTR) { ctx._is_initialized = false } -@(private) -ctr_blocks :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) { +@(private = "file") +ctr_blocks :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) #no_bounds_check { // Use the optimized hardware implementation if available. if _, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw { ctr_blocks_hw(ctx, dst, src, nr_blocks) @@ -185,17 +185,17 @@ xor_blocks :: #force_inline proc "contextless" (dst, src: []byte, blocks: [][]by // performance of this implementation matters to where that // optimization would be worth it, use chacha20poly1305, or a // CPU that isn't e-waste. - if src != nil { - #no_bounds_check { - for i in 0 ..< len(blocks) { - off := i * BLOCK_SIZE - for j in 0 ..< BLOCK_SIZE { - blocks[i][j] ~= src[off + j] + #no_bounds_check { + if src != nil { + for i in 0 ..< len(blocks) { + off := i * BLOCK_SIZE + for j in 0 ..< BLOCK_SIZE { + blocks[i][j] ~= src[off + j] + } } - } + } + for i in 0 ..< len(blocks) { + copy(dst[i * BLOCK_SIZE:], blocks[i]) } } - for i in 0 ..< len(blocks) { - copy(dst[i * BLOCK_SIZE:], blocks[i]) - } } diff --git a/core/crypto/aes/aes_ctr_hw_intel.odin b/core/crypto/aes/aes_ctr_hw_intel.odin new file mode 100644 index 000000000..1c9e815ad --- /dev/null +++ b/core/crypto/aes/aes_ctr_hw_intel.odin @@ -0,0 +1,151 @@ +//+build amd64 +package aes + +import "base:intrinsics" +import "core:crypto/_aes" +import "core:math/bits" +import "core:mem" +import "core:simd/x86" + +@(private) +CTR_STRIDE_HW :: 4 +@(private) +CTR_STRIDE_BYTES_HW :: CTR_STRIDE_HW * BLOCK_SIZE + +@(private, enable_target_feature = "sse2,aes") +ctr_blocks_hw :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) #no_bounds_check { + hw_ctx := ctx._impl.(Context_Impl_Hardware) + + sks: [15]x86.__m128i = --- + for i in 0 ..= hw_ctx._num_rounds { + sks[i] = intrinsics.unaligned_load((^x86.__m128i)(&hw_ctx._sk_exp_enc[i])) + } + + hw_inc_ctr := #force_inline proc "contextless" (hi, lo: u64) -> (x86.__m128i, u64, u64) { + ret := x86.__m128i{ + i64(intrinsics.byte_swap(hi)), + i64(intrinsics.byte_swap(lo)), + } + + hi, lo := hi, lo + carry: u64 + + lo, carry = bits.add_u64(lo, 1, 0) + hi, _ = bits.add_u64(hi, 0, carry) + return ret, hi, lo + } + + // The latency of AESENC depends on mfg and microarchitecture: + // - 7 -> up to Broadwell + // - 4 -> AMD and Skylake - Cascade Lake + // - 3 -> Ice Lake and newer + // + // This implementation does 4 blocks at once, since performance + // should be "adequate" across most CPUs. + + src, dst := src, dst + nr_blocks := nr_blocks + ctr_hi, ctr_lo := ctx._ctr_hi, ctx._ctr_lo + + blks: [CTR_STRIDE_HW]x86.__m128i = --- + for nr_blocks >= CTR_STRIDE_HW { + #unroll for i in 0..< CTR_STRIDE_HW { + blks[i], ctr_hi, ctr_lo = hw_inc_ctr(ctr_hi, ctr_lo) + } + + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_xor_si128(blks[i], sks[0]) + } + #unroll for i in 1 ..= 9 { + #unroll for j in 0 ..< CTR_STRIDE_HW { + blks[j] = x86._mm_aesenc_si128(blks[j], sks[i]) + } + } + switch hw_ctx._num_rounds { + case _aes.ROUNDS_128: + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_aesenclast_si128(blks[i], sks[10]) + } + case _aes.ROUNDS_192: + #unroll for i in 10 ..= 11 { + #unroll for j in 0 ..< CTR_STRIDE_HW { + blks[j] = x86._mm_aesenc_si128(blks[j], sks[i]) + } + } + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_aesenclast_si128(blks[i], sks[12]) + } + case _aes.ROUNDS_256: + #unroll for i in 10 ..= 13 { + #unroll for j in 0 ..< CTR_STRIDE_HW { + blks[j] = x86._mm_aesenc_si128(blks[j], sks[i]) + } + } + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_aesenclast_si128(blks[i], sks[14]) + } + } + + xor_blocks_hw(dst, src, blks[:]) + + if src != nil { + src = src[CTR_STRIDE_BYTES_HW:] + } + dst = dst[CTR_STRIDE_BYTES_HW:] + nr_blocks -= CTR_STRIDE_HW + } + + // Handle the remainder. + for nr_blocks > 0 { + blks[0], ctr_hi, ctr_lo = hw_inc_ctr(ctr_hi, ctr_lo) + + blks[0] = x86._mm_xor_si128(blks[0], sks[0]) + #unroll for i in 1 ..= 9 { + blks[0] = x86._mm_aesenc_si128(blks[0], sks[i]) + } + switch hw_ctx._num_rounds { + case _aes.ROUNDS_128: + blks[0] = x86._mm_aesenclast_si128(blks[0], sks[10]) + case _aes.ROUNDS_192: + #unroll for i in 10 ..= 11 { + blks[0] = x86._mm_aesenc_si128(blks[0], sks[i]) + } + blks[0] = x86._mm_aesenclast_si128(blks[0], sks[12]) + case _aes.ROUNDS_256: + #unroll for i in 10 ..= 13 { + blks[0] = x86._mm_aesenc_si128(blks[0], sks[i]) + } + blks[0] = x86._mm_aesenclast_si128(blks[0], sks[14]) + } + + xor_blocks_hw(dst, src, blks[:1]) + + if src != nil { + src = src[BLOCK_SIZE:] + } + dst = dst[BLOCK_SIZE:] + nr_blocks -= 1 + } + + // Write back the counter. + ctx._ctr_hi, ctx._ctr_lo = ctr_hi, ctr_lo + + mem.zero_explicit(&blks, size_of(blks)) + mem.zero_explicit(&sks, size_of(sks)) +} + +@(private, enable_target_feature = "sse2") +xor_blocks_hw :: proc(dst, src: []byte, blocks: []x86.__m128i) { + #no_bounds_check { + if src != nil { + for i in 0 ..< len(blocks) { + off := i * BLOCK_SIZE + tmp := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src[off:]))) + blocks[i] = x86._mm_xor_si128(blocks[i], tmp) + } + } + for i in 0 ..< len(blocks) { + intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst[i * BLOCK_SIZE:])), blocks[i]) + } + } +} diff --git a/core/crypto/aes/aes_ecb_hw_intel.odin b/core/crypto/aes/aes_ecb_hw_intel.odin new file mode 100644 index 000000000..b2ff36a0c --- /dev/null +++ b/core/crypto/aes/aes_ecb_hw_intel.odin @@ -0,0 +1,58 @@ +//+build amd64 +package aes + +import "base:intrinsics" +import "core:crypto/_aes" +import "core:simd/x86" + +@(private, enable_target_feature = "sse2,aes") +encrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) { + blk := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src))) + + blk = x86._mm_xor_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[0]))) + #unroll for i in 1 ..= 9 { + blk = x86._mm_aesenc_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i]))) + } + switch ctx._num_rounds { + case _aes.ROUNDS_128: + blk = x86._mm_aesenclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[10]))) + case _aes.ROUNDS_192: + #unroll for i in 10 ..= 11 { + blk = x86._mm_aesenc_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i]))) + } + blk = x86._mm_aesenclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[12]))) + case _aes.ROUNDS_256: + #unroll for i in 10 ..= 13 { + blk = x86._mm_aesenc_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i]))) + } + blk = x86._mm_aesenclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[14]))) + } + + intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst)), blk) +} + +@(private, enable_target_feature = "sse2,aes") +decrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) { + blk := intrinsics.unaligned_load((^x86.__m128i)(raw_data(src))) + + blk = x86._mm_xor_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[0]))) + #unroll for i in 1 ..= 9 { + blk = x86._mm_aesdec_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[i]))) + } + switch ctx._num_rounds { + case _aes.ROUNDS_128: + blk = x86._mm_aesdeclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[10]))) + case _aes.ROUNDS_192: + #unroll for i in 10 ..= 11 { + blk = x86._mm_aesdec_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[i]))) + } + blk = x86._mm_aesdeclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[12]))) + case _aes.ROUNDS_256: + #unroll for i in 10 ..= 13 { + blk = x86._mm_aesdec_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[i]))) + } + blk = x86._mm_aesdeclast_si128(blk, intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_dec[14]))) + } + + intrinsics.unaligned_store((^x86.__m128i)(raw_data(dst)), blk) +} diff --git a/core/crypto/aes/aes_gcm.odin b/core/crypto/aes/aes_gcm.odin index e097e0011..3631ca02f 100644 --- a/core/crypto/aes/aes_gcm.odin +++ b/core/crypto/aes/aes_gcm.odin @@ -113,7 +113,7 @@ reset_gcm :: proc "contextless" (ctx: ^Context_GCM) { ctx._is_initialized = false } -@(private) +@(private = "file") gcm_validate_common_slice_sizes :: proc(tag, nonce, aad, text: []byte) { if len(tag) != GCM_TAG_SIZE { panic("crypto/aes: invalid GCM tag size") @@ -184,7 +184,7 @@ gctr_ct64 :: proc( h: ^[_aes.GHASH_KEY_SIZE]byte, nonce: []byte, is_seal: bool, -) { +) #no_bounds_check { ct64_inc_ctr32 := #force_inline proc "contextless" (dst: []byte, ctr: u32) -> u32 { endian.unchecked_put_u32be(dst[12:], ctr) return ctr + 1 @@ -206,9 +206,6 @@ gctr_ct64 :: proc( copy(ctrs[i], nonce) } - // We stitch the GCTR and GHASH operations together, so that only - // one pass over the ciphertext is required. - impl := &ctx._impl.(ct64.Context) src, dst := src, dst diff --git a/core/crypto/aes/aes_gcm_hw_intel.odin b/core/crypto/aes/aes_gcm_hw_intel.odin new file mode 100644 index 000000000..e9cd206f7 --- /dev/null +++ b/core/crypto/aes/aes_gcm_hw_intel.odin @@ -0,0 +1,231 @@ +//+build amd64 +package aes + +import "base:intrinsics" +import "core:crypto" +import "core:crypto/_aes" +import "core:crypto/_aes/hw_intel" +import "core:encoding/endian" +import "core:mem" +import "core:simd/x86" + +@(private) +gcm_seal_hw :: proc(ctx: ^Context_Impl_Hardware, dst, tag, nonce, aad, plaintext: []byte) { + h: [_aes.GHASH_KEY_SIZE]byte + j0: [_aes.GHASH_BLOCK_SIZE]byte + s: [_aes.GHASH_TAG_SIZE]byte + init_ghash_hw(ctx, &h, &j0, nonce) + + // Note: Our GHASH implementation handles appending padding. + hw_intel.ghash(s[:], h[:], aad) + gctr_hw(ctx, dst, &s, plaintext, &h, nonce, true) + final_ghash_hw(&s, &h, &j0, len(aad), len(plaintext)) + copy(tag, s[:]) + + mem.zero_explicit(&h, len(h)) + mem.zero_explicit(&j0, len(j0)) +} + +@(private) +gcm_open_hw :: proc(ctx: ^Context_Impl_Hardware, dst, nonce, aad, ciphertext, tag: []byte) -> bool { + h: [_aes.GHASH_KEY_SIZE]byte + j0: [_aes.GHASH_BLOCK_SIZE]byte + s: [_aes.GHASH_TAG_SIZE]byte + init_ghash_hw(ctx, &h, &j0, nonce) + + hw_intel.ghash(s[:], h[:], aad) + gctr_hw(ctx, dst, &s, ciphertext, &h, nonce, false) + final_ghash_hw(&s, &h, &j0, len(aad), len(ciphertext)) + + ok := crypto.compare_constant_time(s[:], tag) == 1 + if !ok { + mem.zero_explicit(raw_data(dst), len(dst)) + } + + mem.zero_explicit(&h, len(h)) + mem.zero_explicit(&j0, len(j0)) + mem.zero_explicit(&s, len(s)) + + return ok +} + +@(private = "file") +init_ghash_hw :: proc( + ctx: ^Context_Impl_Hardware, + h: ^[_aes.GHASH_KEY_SIZE]byte, + j0: ^[_aes.GHASH_BLOCK_SIZE]byte, + nonce: []byte, +) { + // 1. Let H = CIPH(k, 0^128) + encrypt_block_hw(ctx, h[:], h[:]) + + // ECB encrypt j0, so that we can just XOR with the tag. + copy(j0[:], nonce) + j0[_aes.GHASH_BLOCK_SIZE - 1] = 1 + encrypt_block_hw(ctx, j0[:], j0[:]) +} + +@(private = "file", enable_target_feature = "sse2") +final_ghash_hw :: proc( + s: ^[_aes.GHASH_BLOCK_SIZE]byte, + h: ^[_aes.GHASH_KEY_SIZE]byte, + j0: ^[_aes.GHASH_BLOCK_SIZE]byte, + a_len: int, + t_len: int, +) { + blk: [_aes.GHASH_BLOCK_SIZE]byte + endian.unchecked_put_u64be(blk[0:], u64(a_len) * 8) + endian.unchecked_put_u64be(blk[8:], u64(t_len) * 8) + + hw_intel.ghash(s[:], h[:], blk[:]) + j0_vec := intrinsics.unaligned_load((^x86.__m128i)(j0)) + s_vec := intrinsics.unaligned_load((^x86.__m128i)(s)) + s_vec = x86._mm_xor_si128(s_vec, j0_vec) + intrinsics.unaligned_store((^x86.__m128i)(s), s_vec) +} + +@(private = "file", enable_target_feature = "sse2,sse4.1,aes") +gctr_hw :: proc( + ctx: ^Context_Impl_Hardware, + dst: []byte, + s: ^[_aes.GHASH_BLOCK_SIZE]byte, + src: []byte, + h: ^[_aes.GHASH_KEY_SIZE]byte, + nonce: []byte, + is_seal: bool, +) #no_bounds_check { + sks: [15]x86.__m128i = --- + for i in 0 ..= ctx._num_rounds { + sks[i] = intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i])) + } + + // 2. Define a block J_0 as follows: + // if len(IV) = 96, then let J0 = IV || 0^31 || 1 + // + // Note: We only support 96 bit IVs. + tmp: [BLOCK_SIZE]byte + ctr_blk: x86.__m128i + copy(tmp[:], nonce) + ctr_blk = intrinsics.unaligned_load((^x86.__m128i)(&tmp)) + ctr: u32 = 2 + + src, dst := src, dst + + // Note: Instead of doing GHASH and CTR separately, it is more + // performant to interleave (stitch) the two operations together. + // This results in an unreadable mess, so we opt for simplicity + // as performance is adequate. + + blks: [CTR_STRIDE_HW]x86.__m128i = --- + nr_blocks := len(src) / BLOCK_SIZE + for nr_blocks >= CTR_STRIDE_HW { + if !is_seal { + hw_intel.ghash(s[:], h[:], src[:CTR_STRIDE_BYTES_HW]) + } + + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i], ctr = hw_inc_ctr32(&ctr_blk, ctr) + } + + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_xor_si128(blks[i], sks[0]) + } + #unroll for i in 1 ..= 9 { + #unroll for j in 0 ..< CTR_STRIDE_HW { + blks[j] = x86._mm_aesenc_si128(blks[j], sks[i]) + } + } + switch ctx._num_rounds { + case _aes.ROUNDS_128: + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_aesenclast_si128(blks[i], sks[10]) + } + case _aes.ROUNDS_192: + #unroll for i in 10 ..= 11 { + #unroll for j in 0 ..< CTR_STRIDE_HW { + blks[j] = x86._mm_aesenc_si128(blks[j], sks[i]) + } + } + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_aesenclast_si128(blks[i], sks[12]) + } + case _aes.ROUNDS_256: + #unroll for i in 10 ..= 13 { + #unroll for j in 0 ..< CTR_STRIDE_HW { + blks[j] = x86._mm_aesenc_si128(blks[j], sks[i]) + } + } + #unroll for i in 0 ..< CTR_STRIDE_HW { + blks[i] = x86._mm_aesenclast_si128(blks[i], sks[14]) + } + } + + xor_blocks_hw(dst, src, blks[:]) + + if is_seal { + hw_intel.ghash(s[:], h[:], dst[:CTR_STRIDE_BYTES_HW]) + } + + src = src[CTR_STRIDE_BYTES_HW:] + dst = dst[CTR_STRIDE_BYTES_HW:] + nr_blocks -= CTR_STRIDE_HW + } + + // Handle the remainder. + for n := len(src); n > 0; { + l := min(n, BLOCK_SIZE) + if !is_seal { + hw_intel.ghash(s[:], h[:], src[:l]) + } + + blks[0], ctr = hw_inc_ctr32(&ctr_blk, ctr) + + blks[0] = x86._mm_xor_si128(blks[0], sks[0]) + #unroll for i in 1 ..= 9 { + blks[0] = x86._mm_aesenc_si128(blks[0], sks[i]) + } + switch ctx._num_rounds { + case _aes.ROUNDS_128: + blks[0] = x86._mm_aesenclast_si128(blks[0], sks[10]) + case _aes.ROUNDS_192: + #unroll for i in 10 ..= 11 { + blks[0] = x86._mm_aesenc_si128(blks[0], sks[i]) + } + blks[0] = x86._mm_aesenclast_si128(blks[0], sks[12]) + case _aes.ROUNDS_256: + #unroll for i in 10 ..= 13 { + blks[0] = x86._mm_aesenc_si128(blks[0], sks[i]) + } + blks[0] = x86._mm_aesenclast_si128(blks[0], sks[14]) + } + + if l == BLOCK_SIZE { + xor_blocks_hw(dst, src, blks[:1]) + } else { + blk: [BLOCK_SIZE]byte + copy(blk[:], src) + xor_blocks_hw(blk[:], blk[:], blks[:1]) + copy(dst, blk[:l]) + } + if is_seal { + hw_intel.ghash(s[:], h[:], dst[:l]) + } + + dst = dst[l:] + src = src[l:] + n -= l + } + + mem.zero_explicit(&blks, size_of(blks)) + mem.zero_explicit(&sks, size_of(sks)) +} + +// BUG: Sticking this in gctr_hw (like the other implementations) crashes +// the compiler. +// +// src/check_expr.cpp(7892): Assertion Failure: `c->curr_proc_decl->entity` +@(private = "file", enable_target_feature = "sse4.1") +hw_inc_ctr32 :: #force_inline proc "contextless" (src: ^x86.__m128i, ctr: u32) -> (x86.__m128i, u32) { + ret := x86._mm_insert_epi32(src^, i32(intrinsics.byte_swap(ctr)), 3) + return ret, ctr + 1 +} diff --git a/core/crypto/aes/aes_impl_hw_gen.odin b/core/crypto/aes/aes_impl_hw_gen.odin index 94815f61c..5361c6ef0 100644 --- a/core/crypto/aes/aes_impl_hw_gen.odin +++ b/core/crypto/aes/aes_impl_hw_gen.odin @@ -1,3 +1,4 @@ +//+build !amd64 package aes @(private = "file") diff --git a/core/crypto/aes/aes_impl_hw_intel.odin b/core/crypto/aes/aes_impl_hw_intel.odin new file mode 100644 index 000000000..39ea2dc8d --- /dev/null +++ b/core/crypto/aes/aes_impl_hw_intel.odin @@ -0,0 +1,18 @@ +//+build amd64 +package aes + +import "core:crypto/_aes/hw_intel" + +// is_hardware_accelerated returns true iff hardware accelerated AES +// is supported. +is_hardware_accelerated :: proc "contextless" () -> bool { + return hw_intel.is_supported() +} + +@(private) +Context_Impl_Hardware :: hw_intel.Context + +@(private, enable_target_feature = "sse2,aes") +init_impl_hw :: proc(ctx: ^Context_Impl_Hardware, key: []byte) { + hw_intel.init(ctx, key) +} diff --git a/core/simd/x86/aes.odin b/core/simd/x86/aes.odin index 66a1f744c..a2cd2e4d3 100644 --- a/core/simd/x86/aes.odin +++ b/core/simd/x86/aes.odin @@ -28,7 +28,7 @@ _mm_aesimc_si128 :: #force_inline proc "c" (a: __m128i) -> __m128i { @(require_results, enable_target_feature = "aes") _mm_aeskeygenassist_si128 :: #force_inline proc "c" (a: __m128i, $IMM8: u8) -> __m128i { - return aeskeygenassist(a, u8(IMM8)) + return aeskeygenassist(a, IMM8) } @@ -45,5 +45,5 @@ foreign _ { @(link_name = "llvm.x86.aesni.aesimc") aesimc :: proc(a: __m128i) -> __m128i --- @(link_name = "llvm.x86.aesni.aeskeygenassist") - aeskeygenassist :: proc(a: __m128i, imm8: u8) -> __m128i --- + aeskeygenassist :: proc(a: __m128i, #const imm8: u8) -> __m128i --- } diff --git a/tests/core/crypto/test_core_crypto_aes.odin b/tests/core/crypto/test_core_crypto_aes.odin index 4d4c06bdc..c2fa2835c 100644 --- a/tests/core/crypto/test_core_crypto_aes.odin +++ b/tests/core/crypto/test_core_crypto_aes.odin @@ -12,8 +12,6 @@ import "core:crypto/sha2" test_aes :: proc(t: ^testing.T) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - log.info("Testing AES") - impls := make([dynamic]aes.Implementation, 0, 2) defer delete(impls) append(&impls, aes.Implementation.Portable) @@ -29,7 +27,7 @@ test_aes :: proc(t: ^testing.T) { } test_aes_ecb :: proc(t: ^testing.T, impl: aes.Implementation) { - log.infof("Testing AES-ECB/%v", impl) + log.debugf("Testing AES-ECB/%v", impl) test_vectors := []struct { key: string, @@ -136,7 +134,7 @@ test_aes_ecb :: proc(t: ^testing.T, impl: aes.Implementation) { } test_aes_ctr :: proc(t: ^testing.T, impl: aes.Implementation) { - log.infof("Testing AES-CTR/%v", impl) + log.debugf("Testing AES-CTR/%v", impl) test_vectors := []struct { key: string, @@ -200,7 +198,7 @@ test_aes_ctr :: proc(t: ^testing.T, impl: aes.Implementation) { ctx: aes.Context_CTR key: [aes.KEY_SIZE_256]byte nonce: [aes.CTR_IV_SIZE]byte - aes.init_ctr(&ctx, key[:], nonce[:]) + aes.init_ctr(&ctx, key[:], nonce[:], impl) h_ctx: sha2.Context_512 sha2.init_512_256(&h_ctx) @@ -226,7 +224,7 @@ test_aes_ctr :: proc(t: ^testing.T, impl: aes.Implementation) { } test_aes_gcm :: proc(t: ^testing.T, impl: aes.Implementation) { - log.infof("Testing AES-GCM/%v", impl) + log.debugf("Testing AES-GCM/%v", impl) // NIST did a reorg of their site, so the source of the test vectors // is only available from an archive. The commented out tests are @@ -431,7 +429,7 @@ test_aes_gcm :: proc(t: ^testing.T, impl: aes.Implementation) { testing.expectf( t, ok && dst_str == v.plaintext, - "AES-GCM/%v: Expected: (%s, true) for open(%s, %s, %s, %s, %s), but got (%s, %s) instead", + "AES-GCM/%v: Expected: (%s, true) for open(%s, %s, %s, %s, %s), but got (%s, %v) instead", impl, v.plaintext, v.key, From 33dae2e26cb626de3da056e8770de94b973c7797 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Mon, 15 Jul 2024 14:30:39 +0900 Subject: [PATCH 109/198] core/crypto/aes: Support the full range of GCM nonce sizes --- core/crypto/aes/aes_gcm.odin | 60 ++++++++++++++++----------- core/crypto/aes/aes_gcm_hw_intel.odin | 50 +++++++++++++--------- 2 files changed, 67 insertions(+), 43 deletions(-) diff --git a/core/crypto/aes/aes_gcm.odin b/core/crypto/aes/aes_gcm.odin index 3631ca02f..25e0cc35b 100644 --- a/core/crypto/aes/aes_gcm.odin +++ b/core/crypto/aes/aes_gcm.odin @@ -7,8 +7,10 @@ import "core:crypto/_aes/ct64" import "core:encoding/endian" import "core:mem" -// GCM_NONCE_SIZE is the size of the GCM nonce in bytes. +// GCM_NONCE_SIZE is the default size of the GCM nonce in bytes. GCM_NONCE_SIZE :: 12 +// GCM_NONCE_SIZE_MAX is the maximum size of the GCM nonce in bytes. +GCM_NONCE_SIZE_MAX :: 0x2000000000000000 // floor((2^64 - 1) / 8) bits // GCM_TAG_SIZE is the size of a GCM tag in bytes. GCM_TAG_SIZE :: _aes.GHASH_TAG_SIZE @@ -51,17 +53,19 @@ seal_gcm :: proc(ctx: ^Context_GCM, dst, tag, nonce, aad, plaintext: []byte) { h: [_aes.GHASH_KEY_SIZE]byte j0: [_aes.GHASH_BLOCK_SIZE]byte + j0_enc: [_aes.GHASH_BLOCK_SIZE]byte s: [_aes.GHASH_TAG_SIZE]byte - init_ghash_ct64(ctx, &h, &j0, nonce) + init_ghash_ct64(ctx, &h, &j0, &j0_enc, nonce) // Note: Our GHASH implementation handles appending padding. ct64.ghash(s[:], h[:], aad) - gctr_ct64(ctx, dst, &s, plaintext, &h, nonce, true) - final_ghash_ct64(&s, &h, &j0, len(aad), len(plaintext)) + gctr_ct64(ctx, dst, &s, plaintext, &h, &j0, true) + final_ghash_ct64(&s, &h, &j0_enc, len(aad), len(plaintext)) copy(tag, s[:]) mem.zero_explicit(&h, len(h)) mem.zero_explicit(&j0, len(j0)) + mem.zero_explicit(&j0_enc, len(j0_enc)) } // open_gcm authenticates the aad and ciphertext, and decrypts the ciphertext, @@ -87,12 +91,13 @@ open_gcm :: proc(ctx: ^Context_GCM, dst, nonce, aad, ciphertext, tag: []byte) -> h: [_aes.GHASH_KEY_SIZE]byte j0: [_aes.GHASH_BLOCK_SIZE]byte + j0_enc: [_aes.GHASH_BLOCK_SIZE]byte s: [_aes.GHASH_TAG_SIZE]byte - init_ghash_ct64(ctx, &h, &j0, nonce) + init_ghash_ct64(ctx, &h, &j0, &j0_enc, nonce) ct64.ghash(s[:], h[:], aad) - gctr_ct64(ctx, dst, &s, ciphertext, &h, nonce, false) - final_ghash_ct64(&s, &h, &j0, len(aad), len(ciphertext)) + gctr_ct64(ctx, dst, &s, ciphertext, &h, &j0, false) + final_ghash_ct64(&s, &h, &j0_enc, len(aad), len(ciphertext)) ok := crypto.compare_constant_time(s[:], tag) == 1 if !ok { @@ -101,6 +106,7 @@ open_gcm :: proc(ctx: ^Context_GCM, dst, nonce, aad, ciphertext, tag: []byte) -> mem.zero_explicit(&h, len(h)) mem.zero_explicit(&j0, len(j0)) + mem.zero_explicit(&j0_enc, len(j0_enc)) mem.zero_explicit(&s, len(s)) return ok @@ -119,13 +125,8 @@ gcm_validate_common_slice_sizes :: proc(tag, nonce, aad, text: []byte) { panic("crypto/aes: invalid GCM tag size") } - // The specification supports nonces in the range [1, 2^64) bits - // however per NIST SP 800-38D 5.2.1.1: - // - // > For IVs, it is recommended that implementations restrict support - // > to the length of 96 bits, to promote interoperability, efficiency, - // > and simplicity of design. - if len(nonce) != GCM_NONCE_SIZE { + // The specification supports nonces in the range [1, 2^64) bits. + if l := len(nonce); l == 0 || u64(l) >= GCM_NONCE_SIZE_MAX { panic("crypto/aes: invalid GCM nonce size") } @@ -142,6 +143,7 @@ init_ghash_ct64 :: proc( ctx: ^Context_GCM, h: ^[_aes.GHASH_KEY_SIZE]byte, j0: ^[_aes.GHASH_BLOCK_SIZE]byte, + j0_enc: ^[_aes.GHASH_BLOCK_SIZE]byte, nonce: []byte, ) { impl := &ctx._impl.(ct64.Context) @@ -149,12 +151,25 @@ init_ghash_ct64 :: proc( // 1. Let H = CIPH(k, 0^128) ct64.encrypt_block(impl, h[:], h[:]) + // Define a block, J0, as follows: + if l := len(nonce); l == GCM_NONCE_SIZE { + // if len(IV) = 96, then let J0 = IV || 0^31 || 1 + copy(j0[:], nonce) + j0[_aes.GHASH_BLOCK_SIZE - 1] = 1 + } else { + // If len(IV) != 96, then let s = 128 ceil(len(IV)/128) - len(IV), + // and let J0 = GHASHH(IV || 0^(s+64) || ceil(len(IV))^64). + ct64.ghash(j0[:], h[:], nonce) + + tmp: [_aes.GHASH_BLOCK_SIZE]byte + endian.unchecked_put_u64be(tmp[8:], u64(l) * 8) + ct64.ghash(j0[:], h[:], tmp[:]) + } + // ECB encrypt j0, so that we can just XOR with the tag. In theory // this could be processed along with the final GCTR block, to // potentially save a call to AES-ECB, but... just use AES-NI. - copy(j0[:], nonce) - j0[_aes.GHASH_BLOCK_SIZE - 1] = 1 - ct64.encrypt_block(impl, j0[:], j0[:]) + ct64.encrypt_block(impl, j0_enc[:], j0[:]) } @(private = "file") @@ -182,7 +197,7 @@ gctr_ct64 :: proc( s: ^[_aes.GHASH_BLOCK_SIZE]byte, src: []byte, h: ^[_aes.GHASH_KEY_SIZE]byte, - nonce: []byte, + nonce: ^[_aes.GHASH_BLOCK_SIZE]byte, is_seal: bool, ) #no_bounds_check { ct64_inc_ctr32 := #force_inline proc "contextless" (dst: []byte, ctr: u32) -> u32 { @@ -190,20 +205,17 @@ gctr_ct64 :: proc( return ctr + 1 } - // 2. Define a block J_0 as follows: - // if len(IV) = 96, then let J0 = IV || 0^31 || 1 - // - // Note: We only support 96 bit IVs. + // Setup the counter blocks. tmp, tmp2: [ct64.STRIDE][BLOCK_SIZE]byte = ---, --- ctrs, blks: [ct64.STRIDE][]byte = ---, --- - ctr: u32 = 2 + ctr := endian.unchecked_get_u32be(nonce[GCM_NONCE_SIZE:]) + 1 for i in 0 ..< ct64.STRIDE { // Setup scratch space for the keystream. blks[i] = tmp2[i][:] // Pre-copy the IV to all the counter blocks. ctrs[i] = tmp[i][:] - copy(ctrs[i], nonce) + copy(ctrs[i], nonce[:GCM_NONCE_SIZE]) } impl := &ctx._impl.(ct64.Context) diff --git a/core/crypto/aes/aes_gcm_hw_intel.odin b/core/crypto/aes/aes_gcm_hw_intel.odin index e9cd206f7..7d32d4d96 100644 --- a/core/crypto/aes/aes_gcm_hw_intel.odin +++ b/core/crypto/aes/aes_gcm_hw_intel.odin @@ -13,29 +13,32 @@ import "core:simd/x86" gcm_seal_hw :: proc(ctx: ^Context_Impl_Hardware, dst, tag, nonce, aad, plaintext: []byte) { h: [_aes.GHASH_KEY_SIZE]byte j0: [_aes.GHASH_BLOCK_SIZE]byte + j0_enc: [_aes.GHASH_BLOCK_SIZE]byte s: [_aes.GHASH_TAG_SIZE]byte - init_ghash_hw(ctx, &h, &j0, nonce) + init_ghash_hw(ctx, &h, &j0, &j0_enc, nonce) // Note: Our GHASH implementation handles appending padding. hw_intel.ghash(s[:], h[:], aad) - gctr_hw(ctx, dst, &s, plaintext, &h, nonce, true) - final_ghash_hw(&s, &h, &j0, len(aad), len(plaintext)) + gctr_hw(ctx, dst, &s, plaintext, &h, &j0, true) + final_ghash_hw(&s, &h, &j0_enc, len(aad), len(plaintext)) copy(tag, s[:]) mem.zero_explicit(&h, len(h)) mem.zero_explicit(&j0, len(j0)) + mem.zero_explicit(&j0_enc, len(j0_enc)) } @(private) gcm_open_hw :: proc(ctx: ^Context_Impl_Hardware, dst, nonce, aad, ciphertext, tag: []byte) -> bool { h: [_aes.GHASH_KEY_SIZE]byte j0: [_aes.GHASH_BLOCK_SIZE]byte + j0_enc: [_aes.GHASH_BLOCK_SIZE]byte s: [_aes.GHASH_TAG_SIZE]byte - init_ghash_hw(ctx, &h, &j0, nonce) + init_ghash_hw(ctx, &h, &j0, &j0_enc, nonce) hw_intel.ghash(s[:], h[:], aad) - gctr_hw(ctx, dst, &s, ciphertext, &h, nonce, false) - final_ghash_hw(&s, &h, &j0, len(aad), len(ciphertext)) + gctr_hw(ctx, dst, &s, ciphertext, &h, &j0, false) + final_ghash_hw(&s, &h, &j0_enc, len(aad), len(ciphertext)) ok := crypto.compare_constant_time(s[:], tag) == 1 if !ok { @@ -44,6 +47,7 @@ gcm_open_hw :: proc(ctx: ^Context_Impl_Hardware, dst, nonce, aad, ciphertext, ta mem.zero_explicit(&h, len(h)) mem.zero_explicit(&j0, len(j0)) + mem.zero_explicit(&j0_enc, len(j0_enc)) mem.zero_explicit(&s, len(s)) return ok @@ -54,15 +58,29 @@ init_ghash_hw :: proc( ctx: ^Context_Impl_Hardware, h: ^[_aes.GHASH_KEY_SIZE]byte, j0: ^[_aes.GHASH_BLOCK_SIZE]byte, + j0_enc: ^[_aes.GHASH_BLOCK_SIZE]byte, nonce: []byte, ) { // 1. Let H = CIPH(k, 0^128) encrypt_block_hw(ctx, h[:], h[:]) + // Define a block, J0, as follows: + if l := len(nonce); l == GCM_NONCE_SIZE { + // if len(IV) = 96, then let J0 = IV || 0^31 || 1 + copy(j0[:], nonce) + j0[_aes.GHASH_BLOCK_SIZE - 1] = 1 + } else { + // If len(IV) != 96, then let s = 128 ceil(len(IV)/128) - len(IV), + // and let J0 = GHASHH(IV || 0^(s+64) || ceil(len(IV))^64). + hw_intel.ghash(j0[:], h[:], nonce) + + tmp: [_aes.GHASH_BLOCK_SIZE]byte + endian.unchecked_put_u64be(tmp[8:], u64(l) * 8) + hw_intel.ghash(j0[:], h[:], tmp[:]) + } + // ECB encrypt j0, so that we can just XOR with the tag. - copy(j0[:], nonce) - j0[_aes.GHASH_BLOCK_SIZE - 1] = 1 - encrypt_block_hw(ctx, j0[:], j0[:]) + encrypt_block_hw(ctx, j0_enc[:], j0[:]) } @(private = "file", enable_target_feature = "sse2") @@ -91,7 +109,7 @@ gctr_hw :: proc( s: ^[_aes.GHASH_BLOCK_SIZE]byte, src: []byte, h: ^[_aes.GHASH_KEY_SIZE]byte, - nonce: []byte, + nonce: ^[_aes.GHASH_BLOCK_SIZE]byte, is_seal: bool, ) #no_bounds_check { sks: [15]x86.__m128i = --- @@ -99,15 +117,9 @@ gctr_hw :: proc( sks[i] = intrinsics.unaligned_load((^x86.__m128i)(&ctx._sk_exp_enc[i])) } - // 2. Define a block J_0 as follows: - // if len(IV) = 96, then let J0 = IV || 0^31 || 1 - // - // Note: We only support 96 bit IVs. - tmp: [BLOCK_SIZE]byte - ctr_blk: x86.__m128i - copy(tmp[:], nonce) - ctr_blk = intrinsics.unaligned_load((^x86.__m128i)(&tmp)) - ctr: u32 = 2 + // Setup the counter block + ctr_blk := intrinsics.unaligned_load((^x86.__m128i)(nonce)) + ctr := endian.unchecked_get_u32be(nonce[GCM_NONCE_SIZE:]) + 1 src, dst := src, dst From d4d910bcfcf96a6e2e393f56a905e40608d6a575 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 15 Jul 2024 18:51:18 +0200 Subject: [PATCH 110/198] fmt: fix optional_len or use_nul_termination being used by both array and elems ```odin My_Struct :: struct { names: [^]string `fmt:"v,name_count"`, name_count: int, } main :: proc() { name := "hello?" foo := My_Struct { names = &name, name_count = 1, } fmt.println(foo) } ``` Before: `My_Struct{names = ["h"], name_count = 1}` After: `My_Struct{names = ["hello?"], name_count = 1}` --- core/fmt/fmt.odin | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index ef0647462..9aa9c99dc 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -2751,9 +2751,11 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { elem := runtime.type_info_base(info.elem) if elem != nil { if n, ok := fi.optional_len.?; ok { + fi.optional_len = nil fmt_array(fi, ptr, n, elem.size, elem, verb) return } else if fi.use_nul_termination { + fi.use_nul_termination = false fmt_array_nul_terminated(fi, ptr, -1, elem.size, elem, verb) return } @@ -2855,8 +2857,10 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { n := info.count ptr := v.data if ol, ok := fi.optional_len.?; ok { + fi.optional_len = nil n = min(n, ol) } else if fi.use_nul_termination { + fi.use_nul_termination = false fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb) return } @@ -2867,8 +2871,10 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { n := slice.len ptr := slice.data if ol, ok := fi.optional_len.?; ok { + fi.optional_len = nil n = min(n, ol) } else if fi.use_nul_termination { + fi.use_nul_termination = false fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb) return } @@ -2879,8 +2885,10 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { n := array.len ptr := array.data if ol, ok := fi.optional_len.?; ok { + fi.optional_len = nil n = min(n, ol) } else if fi.use_nul_termination { + fi.use_nul_termination = false fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb) return } From f362e0fa20b6deedae24bcaeb518efed9de7f6b6 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 15 Jul 2024 19:27:48 +0200 Subject: [PATCH 111/198] add test for leaking struct tag into elems --- tests/core/fmt/test_core_fmt.odin | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/core/fmt/test_core_fmt.odin b/tests/core/fmt/test_core_fmt.odin index 49142e24d..3a1eb37e7 100644 --- a/tests/core/fmt/test_core_fmt.odin +++ b/tests/core/fmt/test_core_fmt.odin @@ -372,6 +372,22 @@ test_odin_value_export :: proc(t: ^testing.T) { } } +@(test) +leaking_struct_tag :: proc(t: ^testing.T) { + My_Struct :: struct { + names: [^]string `fmt:"v,name_count"`, + name_count: int, + } + + name := "hello?" + foo := My_Struct { + names = &name, + name_count = 1, + } + + check(t, "My_Struct{names = [\"hello?\"], name_count = 1}", "%v", foo) +} + @(private) check :: proc(t: ^testing.T, exp: string, format: string, args: ..any, loc := #caller_location) { got := fmt.tprintf(format, ..args) From 03426175aea8b3d3a1ebea550613f2155ea07f9a Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 15 Jul 2024 22:45:16 +0200 Subject: [PATCH 112/198] add workaround for kernel panics on MacOS --- src/threading.cpp | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/threading.cpp b/src/threading.cpp index c622ac87e..011b66028 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -796,13 +796,27 @@ gb_internal void futex_wait(Futex *f, Footex val) { #elif defined(GB_SYSTEM_OSX) +// IMPORTANT NOTE(laytan): We use `OS_SYNC_*_SHARED` and `UL_COMPARE_AND_WAIT_SHARED` flags here. +// these flags tell the kernel that we are using these futexes across different processes which +// causes it to opt-out of some optimisations. +// +// BUT this is not actually the case! We should be using the normal non-shared version and letting +// the kernel optimize (I've measured it to be about 10% faster at the parsing/type checking stages). +// +// However we have reports of people on MacOS running into kernel panics, and this seems to fix it for them. +// Which means there is probably a bug in the kernel in one of these non-shared optimisations causing the panic. +// +// The panic also doesn't seem to happen on normal M1 CPUs, and happen more on later CPUs or pro/max series. +// Probably because they have more going on in terms of threads etc. + #if __has_include() #define DARWIN_WAIT_ON_ADDRESS_AVAILABLE #include #endif -#define UL_COMPARE_AND_WAIT 0x00000001 -#define ULF_NO_ERRNO 0x01000000 +#define UL_COMPARE_AND_WAIT 0x00000001 +#define UL_COMPARE_AND_WAIT_SHARED 0x00000003 +#define ULF_NO_ERRNO 0x01000000 extern "C" int __ulock_wait(uint32_t operation, void *addr, uint64_t value, uint32_t timeout); /* timeout is specified in microseconds */ extern "C" int __ulock_wake(uint32_t operation, void *addr, uint64_t wake_value); @@ -811,7 +825,7 @@ gb_internal void futex_signal(Futex *f) { #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE if (__builtin_available(macOS 14.4, *)) { for (;;) { - int ret = os_sync_wake_by_address_any(f, sizeof(Futex), OS_SYNC_WAKE_BY_ADDRESS_NONE); + int ret = os_sync_wake_by_address_any(f, sizeof(Futex), OS_SYNC_WAKE_BY_ADDRESS_SHARED); if (ret >= 0) { return; } @@ -826,7 +840,7 @@ gb_internal void futex_signal(Futex *f) { } else { #endif for (;;) { - int ret = __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, 0); + int ret = __ulock_wake(UL_COMPARE_AND_WAIT_SHARED | ULF_NO_ERRNO, f, 0); if (ret >= 0) { return; } @@ -847,7 +861,7 @@ gb_internal void futex_broadcast(Futex *f) { #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE if (__builtin_available(macOS 14.4, *)) { for (;;) { - int ret = os_sync_wake_by_address_all(f, sizeof(Footex), OS_SYNC_WAKE_BY_ADDRESS_NONE); + int ret = os_sync_wake_by_address_all(f, sizeof(Footex), OS_SYNC_WAKE_BY_ADDRESS_SHARED); if (ret >= 0) { return; } @@ -863,7 +877,7 @@ gb_internal void futex_broadcast(Futex *f) { #endif for (;;) { enum { ULF_WAKE_ALL = 0x00000100 }; - int ret = __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO | ULF_WAKE_ALL, f, 0); + int ret = __ulock_wake(UL_COMPARE_AND_WAIT_SHARED | ULF_NO_ERRNO | ULF_WAKE_ALL, f, 0); if (ret == 0) { return; } @@ -884,7 +898,7 @@ gb_internal void futex_wait(Futex *f, Footex val) { #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE if (__builtin_available(macOS 14.4, *)) { for (;;) { - int ret = os_sync_wait_on_address(f, cast(uint64_t)(val), sizeof(Footex), OS_SYNC_WAIT_ON_ADDRESS_NONE); + int ret = os_sync_wait_on_address(f, cast(uint64_t)(val), sizeof(Footex), OS_SYNC_WAIT_ON_ADDRESS_SHARED); if (ret >= 0) { if (*f != val) { return; @@ -902,7 +916,7 @@ gb_internal void futex_wait(Futex *f, Footex val) { } else { #endif for (;;) { - int ret = __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, val, 0); + int ret = __ulock_wait(UL_COMPARE_AND_WAIT_SHARED | ULF_NO_ERRNO, f, val, 0); if (ret >= 0) { if (*f != val) { return; From 23ca27f40b956b03499e2095a04b252722e34bb6 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 16 Jul 2024 00:48:17 +0100 Subject: [PATCH 113/198] Add intrinsics `add_sat` and `sub_sat` --- src/check_builtin.cpp | 2 ++ src/checker_builtin_procs.hpp | 6 ++++++ src/llvm_backend_proc.cpp | 6 ++++++ 3 files changed, 14 insertions(+) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index db62b2bdb..26de3a112 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -4261,6 +4261,8 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_overflow_add: case BuiltinProc_overflow_sub: case BuiltinProc_overflow_mul: + case BuiltinProc_add_sat: + case BuiltinProc_sub_sat: { Operand x = {}; Operand y = {}; diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index a90c52e61..3a2e1ce22 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -70,6 +70,9 @@ enum BuiltinProcId { BuiltinProc_overflow_sub, BuiltinProc_overflow_mul, + BuiltinProc_add_sat, + BuiltinProc_sub_sat, + BuiltinProc_sqrt, BuiltinProc_fused_mul_add, @@ -393,6 +396,9 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("overflow_sub"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("overflow_mul"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("add_sat"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("sub_sat"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("sqrt"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("fused_mul_add"), 3, false, Expr_Expr, BuiltinProcPkg_intrinsics}, diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 4ee4fb769..bdc381bc4 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2236,6 +2236,8 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu case BuiltinProc_overflow_add: case BuiltinProc_overflow_sub: case BuiltinProc_overflow_mul: + case BuiltinProc_add_sat: + case BuiltinProc_sub_sat: { Type *main_type = tv.type; Type *type = main_type; @@ -2254,12 +2256,16 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu case BuiltinProc_overflow_add: name = "llvm.uadd.with.overflow"; break; case BuiltinProc_overflow_sub: name = "llvm.usub.with.overflow"; break; case BuiltinProc_overflow_mul: name = "llvm.umul.with.overflow"; break; + case BuiltinProc_add_sat: name = "llvm.uadd.sat"; break; + case BuiltinProc_sub_sat: name = "llvm.usub.sat"; break; } } else { switch (id) { case BuiltinProc_overflow_add: name = "llvm.sadd.with.overflow"; break; case BuiltinProc_overflow_sub: name = "llvm.ssub.with.overflow"; break; case BuiltinProc_overflow_mul: name = "llvm.smul.with.overflow"; break; + case BuiltinProc_add_sat: name = "llvm.sadd.sat"; break; + case BuiltinProc_sub_sat: name = "llvm.ssub.sat"; break; } } LLVMTypeRef types[1] = {lb_type(p->module, type)}; From 169fc4d3be48d16d9d6fdb41a8b7312ce76d0ecd Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 16 Jul 2024 11:56:31 +0100 Subject: [PATCH 114/198] General clean up of the os2/process_windows.odin code --- core/os/os2/process.odin | 8 +- core/os/os2/process_windows.odin | 756 +++++++++++-------------------- core/sys/windows/ntdll.odin | 106 +++++ core/sys/windows/types.odin | 6 +- 4 files changed, 381 insertions(+), 495 deletions(-) diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index d407ffb18..6dfb8d9d9 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -1,9 +1,7 @@ package os2 -import "core:sync" -import "core:time" import "base:runtime" -import "core:strings" +import "core:time" /* In procedures that explicitly state this as one of the allowed values, @@ -20,9 +18,9 @@ args := get_args() get_args :: proc() -> []string { result := make([]string, len(runtime.args__), heap_allocator()) for rt_arg, i in runtime.args__ { - result[i] = cast(string) rt_arg + result[i] = string(rt_arg) } - return result[:] + return result } /* diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index e16cedd9f..2ffee66e8 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -1,86 +1,104 @@ //+build windows +//+private file package os2 -import "core:sys/windows" -import "core:strings" -import "core:time" - import "base:runtime" -_Process_Handle :: windows.HANDLE +import "core:strings" +import win32 "core:sys/windows" +import "core:time" +@(private="package") _exit :: proc "contextless" (code: int) -> ! { - windows.ExitProcess(u32(code)) + win32.ExitProcess(u32(code)) } +@(private="package") _get_uid :: proc() -> int { return -1 } +@(private="package") _get_euid :: proc() -> int { return -1 } +@(private="package") _get_gid :: proc() -> int { return -1 } +@(private="package") _get_egid :: proc() -> int { return -1 } +@(private="package") _get_pid :: proc() -> int { - return cast(int) windows.GetCurrentProcessId() + return int(win32.GetCurrentProcessId()) } +@(private="package") _get_ppid :: proc() -> int { - our_pid := windows.GetCurrentProcessId() - snap := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0) - if snap == windows.INVALID_HANDLE_VALUE { + our_pid := win32.GetCurrentProcessId() + snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0) + if snap == win32.INVALID_HANDLE_VALUE { return -1 } - defer windows.CloseHandle(snap) - entry := windows.PROCESSENTRY32W { dwSize = size_of(windows.PROCESSENTRY32W) } - status := windows.Process32FirstW(snap, &entry) - for status { + defer win32.CloseHandle(snap) + entry := win32.PROCESSENTRY32W { dwSize = size_of(win32.PROCESSENTRY32W) } + for status := win32.Process32FirstW(snap, &entry); status; /**/ { if entry.th32ProcessID == our_pid { - return cast(int) entry.th32ParentProcessID + return int(entry.th32ParentProcessID) } - status = windows.Process32NextW(snap, &entry) + status = win32.Process32NextW(snap, &entry) } return -1 } +@(private="package") _process_list :: proc(allocator: runtime.Allocator) -> ([]int, Error) { pid_list := make([dynamic]int, allocator) - snap := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0) - if snap == windows.INVALID_HANDLE_VALUE { + snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0) + if snap == win32.INVALID_HANDLE_VALUE { return pid_list[:], _get_platform_error() } - entry := windows.PROCESSENTRY32W { dwSize = size_of(windows.PROCESSENTRY32W) } - status := windows.Process32FirstW(snap, &entry) + entry := win32.PROCESSENTRY32W { dwSize = size_of(win32.PROCESSENTRY32W) } + status := win32.Process32FirstW(snap, &entry) for status { - append(&pid_list, cast(int) entry.th32ProcessID) - status = windows.Process32NextW(snap, &entry) + append(&pid_list, int(entry.th32ProcessID)) + status = win32.Process32NextW(snap, &entry) } return pid_list[:], nil } +@(require_results) +read_memory_as_struct :: proc(h: win32.HANDLE, addr: rawptr, dest: ^$T) -> (bytes_read: uint, err: Error) { + if !win32.ReadProcessMemory(h, addr, dest, size_of(T), &bytes_read) { + err = _get_platform_error() + } + return +} +@(require_results) +read_memory_as_slice :: proc(h: win32.HANDLE, addr: rawptr, dest: []$T) -> (bytes_read: uint, err: Error) { + if !win32.ReadProcessMemory(h, addr, raw_data(dest), len(dest)*size_of(T), &bytes_read) { + err = _get_platform_error() + } + return +} + +@(private="package") _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { info.pid = pid defer if err != nil { free_process_info(info, allocator) } - need_snapprocess := \ - .PPid in selection || - .Priority in selection - need_snapmodule := \ - .Executable_Path in selection - need_peb := \ - .Command_Line in selection || - .Environment in selection || - .Working_Dir in selection + + need_snapprocess := selection >= {.PPid, .Priority} + need_snapmodule := .Executable_Path in selection + need_peb := selection >= {.Command_Line, .Environment, .Working_Dir} need_process_handle := need_peb || .Username in selection + // Data obtained from process snapshots if need_snapprocess { entry, entry_err := _process_entry_by_pid(info.pid) @@ -89,55 +107,39 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator return } if .PPid in selection { - info.fields |= {.PPid} + info.fields += {.PPid} info.ppid = int(entry.th32ParentProcessID) } if .Priority in selection { - info.fields |= {.Priority} + info.fields += {.Priority} info.priority = int(entry.pcPriClassBase) } } if need_snapmodule { - exe_path, exe_path_err := _process_exe_by_pid(pid, allocator) - if exe_path_err != nil { - err = exe_path_err - return - } - info.fields |= {.Executable_Path} + exe_path := _process_exe_by_pid(pid, allocator) or_return + info.fields += {.Executable_Path} info.executable_path = exe_path } - ph := windows.INVALID_HANDLE_VALUE + ph := win32.INVALID_HANDLE_VALUE if need_process_handle { - ph = windows.OpenProcess( - windows.PROCESS_QUERY_LIMITED_INFORMATION | windows.PROCESS_VM_READ, + ph = win32.OpenProcess( + win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.PROCESS_VM_READ, false, u32(pid), ) - if ph == windows.INVALID_HANDLE_VALUE { + if ph == win32.INVALID_HANDLE_VALUE { err = _get_platform_error() return } } - defer if ph != windows.INVALID_HANDLE_VALUE { - windows.CloseHandle(ph) + defer if ph != win32.INVALID_HANDLE_VALUE { + win32.CloseHandle(ph) } + if need_peb { - // TODO(flysand): This was not tested with WOW64 or 32-bit processes, - // might need to be revised later when issues occur. - ntdll_lib := windows.LoadLibraryW(windows.L("ntdll.dll")) - if ntdll_lib == nil { - err = _get_platform_error() - return - } - defer windows.FreeLibrary(ntdll_lib) - NtQueryInformationProcess := cast(NtQueryInformationProcess_T) windows.GetProcAddress(ntdll_lib, "NtQueryInformationProcess") - if NtQueryInformationProcess == nil { - err = _get_platform_error() - return - } - process_info_size: u32 = --- - process_info: PROCESS_BASIC_INFORMATION = --- - status := NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size) + process_info_size: u32 + process_info: win32.PROCESS_BASIC_INFORMATION + status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size) if status != 0 { // TODO(flysand): There's probably a mismatch between NTSTATUS and // windows userland error codes, I haven't checked. @@ -149,45 +151,25 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator err = General_Error.Unsupported return } - process_peb: PEB = --- - bytes_read: uint = --- - read_struct :: proc(h: windows.HANDLE, addr: rawptr, dest: ^$T, br: ^uint) -> windows.BOOL { - return windows.ReadProcessMemory(h, addr, dest, size_of(T), br) - } - read_slice :: proc(h: windows.HANDLE, addr: rawptr, dest: []$T, br: ^uint) -> windows.BOOL { - return windows.ReadProcessMemory(h, addr, raw_data(dest), len(dest)*size_of(T), br) - } - if !read_struct(ph, process_info.PebBaseAddress, &process_peb, &bytes_read) { - err = _get_platform_error() - return - } - process_params: RTL_USER_PROCESS_PARAMETERS = --- - if !read_struct(ph, process_peb.ProcessParameters, &process_params, &bytes_read) { - err = _get_platform_error() - return - } + process_peb: win32.PEB + + _ = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) or_return + + process_params: win32.RTL_USER_PROCESS_PARAMETERS + _ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return + if .Command_Line in selection || .Command_Args in selection { TEMP_ALLOCATOR_GUARD() cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) - if !read_slice(ph, process_params.CommandLine.Buffer, cmdline_w, &bytes_read) { - err = _get_platform_error() - return - } + _ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return + if .Command_Line in selection { - cmdline, cmdline_err := windows.utf16_to_utf8(cmdline_w, allocator) - if cmdline_err != nil { - err = cmdline_err - return - } - info.fields |= {.Command_Line} + cmdline := win32.utf16_to_utf8(cmdline_w, allocator) or_return + info.fields += {.Command_Line} info.command_line = cmdline } if .Command_Args in selection { - args, args_err := _parse_command_line(raw_data(cmdline_w), allocator) - if args_err != nil { - err = args_err - return - } + args := _parse_command_line(raw_data(cmdline_w), allocator) or_return info.fields += {.Command_Args} info.command_args = args } @@ -196,62 +178,44 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator TEMP_ALLOCATOR_GUARD() env_len := process_params.EnvironmentSize / 2 envs_w := make([]u16, env_len, temp_allocator()) - if !read_slice(ph, process_params.Environment, envs_w, &bytes_read) { - err = _get_platform_error() - return - } - envs, envs_err := _parse_environment_block(raw_data(envs_w), allocator) - if envs_err != nil { - err = envs_err - return - } - info.fields |= {.Environment} + _ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return + + envs := _parse_environment_block(raw_data(envs_w), allocator) or_return + + info.fields += {.Environment} info.environment = envs } if .Working_Dir in selection { TEMP_ALLOCATOR_GUARD() cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) - if !read_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w, &bytes_read) { - err = _get_platform_error() - return - } - cwd, cwd_err := windows.utf16_to_utf8(cwd_w, allocator) - if cwd_err != nil { - err = cwd_err - return - } - info.fields |= {.Working_Dir} + _ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return + + cwd := win32.utf16_to_utf8(cwd_w, allocator) or_return + + info.fields += {.Working_Dir} info.working_dir = cwd } } + if .Username in selection { - username, username_err := _get_process_user(ph, allocator) - if username_err != nil { - err = username_err - return - } - info.fields |= {.Username} + username := _get_process_user(ph, allocator) or_return + info.fields += {.Username} info.username = username } err = nil return } +@(private="package") _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { pid := process.pid info.pid = pid defer if err != nil { free_process_info(info, allocator) } - need_snapprocess := \ - .PPid in selection || - .Priority in selection - need_snapmodule := \ - .Executable_Path in selection - need_peb := \ - .Command_Line in selection || - .Environment in selection || - .Working_Dir in selection + need_snapprocess := selection >= {.PPid, .Priority} + need_snapmodule := .Executable_Path in selection + need_peb := selection >= {.Command_Line, .Environment, .Working_Dir} // Data obtained from process snapshots if need_snapprocess { entry, entry_err := _process_entry_by_pid(info.pid) @@ -260,39 +224,24 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields return } if .PPid in selection { - info.fields |= {.PPid} + info.fields += {.PPid} info.ppid = int(entry.th32ParentProcessID) } if .Priority in selection { - info.fields |= {.Priority} + info.fields += {.Priority} info.priority = int(entry.pcPriClassBase) } } if need_snapmodule { - exe_path, exe_path_err := _process_exe_by_pid(pid, allocator) - if exe_path_err != nil { - err = exe_path_err - return - } - info.fields |= {.Executable_Path} + exe_path := _process_exe_by_pid(pid, allocator) or_return + info.fields += {.Executable_Path} info.executable_path = exe_path } - ph := cast(windows.HANDLE) process.handle + ph := win32.HANDLE(process.handle) if need_peb { - ntdll_lib := windows.LoadLibraryW(windows.L("ntdll.dll")) - if ntdll_lib == nil { - err = _get_platform_error() - return - } - defer windows.FreeLibrary(ntdll_lib) - NtQueryInformationProcess := cast(NtQueryInformationProcess_T) windows.GetProcAddress(ntdll_lib, "NtQueryInformationProcess") - if NtQueryInformationProcess == nil { - err = _get_platform_error() - return - } - process_info_size: u32 = --- - process_info: PROCESS_BASIC_INFORMATION = --- - status := NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size) + process_info_size: u32 + process_info: win32.PROCESS_BASIC_INFORMATION + status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size) if status != 0 { // TODO(flysand): There's probably a mismatch between NTSTATUS and // windows userland error codes, I haven't checked. @@ -304,96 +253,64 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields err = General_Error.Unsupported return } - process_peb: PEB = --- - bytes_read: uint = --- - read_struct :: proc(h: windows.HANDLE, addr: rawptr, dest: ^$T, br: ^uint) -> windows.BOOL { - return windows.ReadProcessMemory(h, addr, dest, size_of(T), br) - } - read_slice :: proc(h: windows.HANDLE, addr: rawptr, dest: []$T, br: ^uint) -> windows.BOOL { - return windows.ReadProcessMemory(h, addr, raw_data(dest), len(dest)*size_of(T), br) - } - if !read_struct(ph, process_info.PebBaseAddress, &process_peb, &bytes_read) { - err = _get_platform_error() - return - } - process_params: RTL_USER_PROCESS_PARAMETERS = --- - if !read_struct(ph, process_peb.ProcessParameters, &process_params, &bytes_read) { - err = _get_platform_error() - return - } + + process_peb: win32.PEB + _ = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) or_return + + process_params: win32.RTL_USER_PROCESS_PARAMETERS + _ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return + if .Command_Line in selection || .Command_Args in selection { TEMP_ALLOCATOR_GUARD() cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) - if !read_slice(ph, process_params.CommandLine.Buffer, cmdline_w, &bytes_read) { - err = _get_platform_error() - return - } + _ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return + if .Command_Line in selection { - cmdline, cmdline_err := windows.utf16_to_utf8(cmdline_w, allocator) - if cmdline_err != nil { - err = cmdline_err - return - } - info.fields |= {.Command_Line} + cmdline := win32.utf16_to_utf8(cmdline_w, allocator) or_return + + info.fields += {.Command_Line} info.command_line = cmdline } if .Command_Args in selection { - args, args_err := _parse_command_line(raw_data(cmdline_w), allocator) - if args_err != nil { - err = args_err - return - } + args := _parse_command_line(raw_data(cmdline_w), allocator) or_return info.fields += {.Command_Args} info.command_args = args } } + if .Environment in selection { TEMP_ALLOCATOR_GUARD() env_len := process_params.EnvironmentSize / 2 envs_w := make([]u16, env_len, temp_allocator()) - if !read_slice(ph, process_params.Environment, envs_w, &bytes_read) { - err = _get_platform_error() - return - } - envs, envs_err := _parse_environment_block(raw_data(envs_w), allocator) - if envs_err != nil { - err = envs_err - return - } - info.fields |= {.Environment} + _ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return + + envs := _parse_environment_block(raw_data(envs_w), allocator) or_return + info.fields += {.Environment} info.environment = envs } + if .Working_Dir in selection { TEMP_ALLOCATOR_GUARD() cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) - if !read_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w, &bytes_read) { - err = _get_platform_error() - return - } - cwd, cwd_err := windows.utf16_to_utf8(cwd_w, allocator) - if cwd_err != nil { - err = cwd_err - return - } - info.fields |= {.Working_Dir} + _ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return + + cwd := win32.utf16_to_utf8(cwd_w, allocator) or_return + info.fields += {.Working_Dir} info.working_dir = cwd } } if .Username in selection { - username, username_err := _get_process_user(ph, allocator) - if username_err != nil { - err = username_err - return - } - info.fields |= {.Username} + username := _get_process_user(ph, allocator) or_return + info.fields += {.Username} info.username = username } err = nil return } +@(private="package") _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { - info.pid = cast(int) windows.GetCurrentProcessId() + info.pid = get_pid() defer if err != nil { free_process_info(info, allocator) } @@ -415,53 +332,33 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime } if .Executable_Path in selection { exe_filename_w: [256]u16 - path_len := windows.GetModuleFileNameW(nil, raw_data(exe_filename_w[:]), len(exe_filename_w)) - exe_filename, exe_filename_err := windows.utf16_to_utf8(exe_filename_w[:path_len], allocator) - if exe_filename_err != nil { - err = exe_filename_err - return - } + path_len := win32.GetModuleFileNameW(nil, raw_data(exe_filename_w[:]), len(exe_filename_w)) + exe_filename := win32.utf16_to_utf8(exe_filename_w[:path_len], allocator) or_return info.fields += {.Executable_Path} info.executable_path = exe_filename } if .Command_Line in selection || .Command_Args in selection { - command_line_w := windows.GetCommandLineW() + command_line_w := win32.GetCommandLineW() if .Command_Line in selection { - command_line, command_line_err := windows.wstring_to_utf8(command_line_w, -1, allocator) - if command_line_err != nil { - err = command_line_err - return - } + command_line := win32.wstring_to_utf8(command_line_w, -1, allocator) or_return info.fields += {.Command_Line} info.command_line = command_line } if .Command_Args in selection { - args, args_err := _parse_command_line(command_line_w, allocator) - if args_err != nil { - err = args_err - return - } + args := _parse_command_line(command_line_w, allocator) or_return info.fields += {.Command_Args} info.command_args = args } } if .Environment in selection { - env_block := windows.GetEnvironmentStringsW() - envs, envs_err := _parse_environment_block(env_block, allocator) - if envs_err != nil { - err = envs_err - return - } + env_block := win32.GetEnvironmentStringsW() + envs := _parse_environment_block(env_block, allocator) or_return info.fields += {.Environment} info.environment = envs } if .Username in selection { - process_handle := windows.GetCurrentProcess() - username, username_err := _get_process_user(process_handle, allocator) - if username_err != nil { - err = username_err - return - } + process_handle := win32.GetCurrentProcess() + username := _get_process_user(process_handle, allocator) or_return info.fields += {.Username} info.username = username } @@ -474,74 +371,76 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime return } -_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (Process, Error) { +@(private="package") +_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { // Note(flysand): The handle will be used for querying information so we // take the necessary permissions right away. - dwDesiredAccess := windows.PROCESS_QUERY_LIMITED_INFORMATION | windows.SYNCHRONIZE + dwDesiredAccess := win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.SYNCHRONIZE if .Mem_Read in flags { - dwDesiredAccess |= windows.PROCESS_VM_READ + dwDesiredAccess |= win32.PROCESS_VM_READ } if .Mem_Write in flags { - dwDesiredAccess |= windows.PROCESS_VM_WRITE + dwDesiredAccess |= win32.PROCESS_VM_WRITE } - handle := windows.OpenProcess( + handle := win32.OpenProcess( dwDesiredAccess, false, u32(pid), ) - if handle == windows.INVALID_HANDLE_VALUE { - return {}, _get_platform_error() + if handle == win32.INVALID_HANDLE_VALUE { + err = _get_platform_error() + } else { + process = {pid = pid, handle = uintptr(handle)} } - return Process { - pid = pid, - handle = cast(uintptr) handle, - }, nil + return } +@(private="package") _Sys_Process_Attributes :: struct {} +@(private="package") _process_start :: proc(desc: Process_Desc) -> (Process, Error) { TEMP_ALLOCATOR_GUARD() command_line := _build_command_line(desc.command, temp_allocator()) - command_line_w := windows.utf8_to_wstring(command_line, temp_allocator()) + command_line_w := win32.utf8_to_wstring(command_line, temp_allocator()) environment := desc.env if desc.env == nil { environment = environ(temp_allocator()) } - environment_block := _build_environment_block(environment, temp_allocator()) - environment_block_w := windows.utf8_to_utf16(environment_block, temp_allocator()) - stderr_handle := windows.GetStdHandle(windows.STD_ERROR_HANDLE) - stdout_handle := windows.GetStdHandle(windows.STD_OUTPUT_HANDLE) - stdin_handle := windows.GetStdHandle(windows.STD_INPUT_HANDLE) + environment_block := _build_environment_block(environment, temp_allocator()) + environment_block_w := win32.utf8_to_utf16(environment_block, temp_allocator()) + stderr_handle := win32.GetStdHandle(win32.STD_ERROR_HANDLE) + stdout_handle := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE) + stdin_handle := win32.GetStdHandle(win32.STD_INPUT_HANDLE) if desc.stdout != nil { - stdout_handle = windows.HANDLE((^File_Impl)(desc.stdout.impl).fd) + stdout_handle = win32.HANDLE((^File_Impl)(desc.stdout.impl).fd) } if desc.stderr != nil { - stderr_handle = windows.HANDLE((^File_Impl)(desc.stderr.impl).fd) + stderr_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd) } if desc.stdin != nil { - stdin_handle = windows.HANDLE((^File_Impl)(desc.stderr.impl).fd) + stdin_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd) } - working_dir_w := windows.wstring(nil) + working_dir_w := win32.wstring(nil) if len(desc.working_dir) > 0 { - working_dir_w = windows.utf8_to_wstring(desc.working_dir, temp_allocator()) + working_dir_w = win32.utf8_to_wstring(desc.working_dir, temp_allocator()) } - process_info: windows.PROCESS_INFORMATION = --- - process_ok := windows.CreateProcessW( + process_info: win32.PROCESS_INFORMATION + process_ok := win32.CreateProcessW( nil, command_line_w, nil, nil, true, - windows.CREATE_UNICODE_ENVIRONMENT|windows.NORMAL_PRIORITY_CLASS, + win32.CREATE_UNICODE_ENVIRONMENT|win32.NORMAL_PRIORITY_CLASS, raw_data(environment_block_w), working_dir_w, - &windows.STARTUPINFOW { - cb = size_of(windows.STARTUPINFOW), + &win32.STARTUPINFOW { + cb = size_of(win32.STARTUPINFOW), hStdError = stderr_handle, hStdOutput = stdout_handle, hStdInput = stdin_handle, - dwFlags = windows.STARTF_USESTDHANDLES, + dwFlags = win32.STARTF_USESTDHANDLES, }, &process_info, ) @@ -549,83 +448,89 @@ _process_start :: proc(desc: Process_Desc) -> (Process, Error) { return {}, _get_platform_error() } return Process { - pid = cast(int) process_info.dwProcessId, - handle = cast(uintptr) process_info.hProcess, + pid = int(process_info.dwProcessId), + handle = uintptr(process_info.hProcess), }, nil } -_process_wait :: proc(process: Process, timeout: time.Duration) -> (Process_State, Error) { - handle := windows.HANDLE(process.handle) - timeout_ms := u32(timeout / time.Millisecond) if timeout > 0 else windows.INFINITE - wait_result := windows.WaitForSingleObject(handle, timeout_ms) +@(private="package") +_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { + handle := win32.HANDLE(process.handle) + timeout_ms := u32(timeout / time.Millisecond) if timeout > 0 else win32.INFINITE + wait_result := win32.WaitForSingleObject(handle, timeout_ms) switch wait_result { - case windows.WAIT_OBJECT_0: - exit_code: u32 = --- - if !windows.GetExitCodeProcess(handle, &exit_code) { - return {}, _get_platform_error() + case win32.WAIT_OBJECT_0: + exit_code: u32 + if !win32.GetExitCodeProcess(handle, &exit_code) { + err =_get_platform_error() + return } - time_created: windows.FILETIME = --- - time_exited: windows.FILETIME = --- - time_kernel: windows.FILETIME = --- - time_user: windows.FILETIME = --- - if !windows.GetProcessTimes(handle, &time_created, &time_exited, &time_kernel, &time_user) { - return {}, _get_platform_error() + time_created: win32.FILETIME + time_exited: win32.FILETIME + time_kernel: win32.FILETIME + time_user: win32.FILETIME + if !win32.GetProcessTimes(handle, &time_created, &time_exited, &time_kernel, &time_user) { + err = _get_platform_error() + return } - return Process_State { - exit_code = cast(int) exit_code, - exited = true, - pid = process.pid, - success = true, + process_state = { + exit_code = int(exit_code), + exited = true, + pid = process.pid, + success = true, system_time = _filetime_to_duration(time_kernel), - user_time = _filetime_to_duration(time_user), - }, nil - case windows.WAIT_TIMEOUT: - return {}, General_Error.Timeout + user_time = _filetime_to_duration(time_user), + } + return + case win32.WAIT_TIMEOUT: + err = General_Error.Timeout + return case: - return {}, _get_platform_error() + err = _get_platform_error() + return } } -_process_close :: proc(process: Process) -> (Error) { - if !windows.CloseHandle(cast(windows.HANDLE) process.handle) { +@(private="package") +_process_close :: proc(process: Process) -> Error { + if !win32.CloseHandle(win32.HANDLE(process.handle)) { return _get_platform_error() } return nil } -_process_kill :: proc(process: Process) -> (Error) { +@(private="package") +_process_kill :: proc(process: Process) -> Error { // Note(flysand): This is different than what the task manager's "kill process" // functionality does, as we don't try to send WM_CLOSE message first. This // is quite a rough way to kill the process, which should be consistent with // linux. The error code 9 is to mimic SIGKILL event. - if !windows.TerminateProcess(windows.HANDLE(process.handle), 9) { + if !win32.TerminateProcess(win32.HANDLE(process.handle), 9) { return _get_platform_error() } return nil } -@(private) -_filetime_to_duration :: proc(filetime: windows.FILETIME) -> time.Duration { +_filetime_to_duration :: proc(filetime: win32.FILETIME) -> time.Duration { ticks := u64(filetime.dwHighDateTime)<<32 | u64(filetime.dwLowDateTime) return time.Duration(ticks * 100) } -@(private) -_process_entry_by_pid :: proc(pid: int) -> (windows.PROCESSENTRY32W, Error) { - snap := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0) - if snap == windows.INVALID_HANDLE_VALUE { +_process_entry_by_pid :: proc(pid: int) -> (win32.PROCESSENTRY32W, Error) { + snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0) + if snap == win32.INVALID_HANDLE_VALUE { return {}, _get_platform_error() } - defer windows.CloseHandle(snap) - entry := windows.PROCESSENTRY32W { dwSize = size_of(windows.PROCESSENTRY32W) } - status := windows.Process32FirstW(snap, &entry) + defer win32.CloseHandle(snap) + entry := win32.PROCESSENTRY32W { dwSize = size_of(win32.PROCESSENTRY32W) } + status := win32.Process32FirstW(snap, &entry) found := false for status { if u32(pid) == entry.th32ProcessID { found = true break } - status = windows.Process32NextW(snap, &entry) + status = win32.Process32NextW(snap, &entry) } if !found { return {}, General_Error.Not_Exist @@ -638,102 +543,83 @@ _process_entry_by_pid :: proc(pid: int) -> (windows.PROCESSENTRY32W, Error) { // a slight suspicion that if both exe path and command line are desired, // it's faster to just read both from PEB, but maybe the toolhelp snapshots // are just better...? -@(private) -_process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (string, Error) { - snap := windows.CreateToolhelp32Snapshot( - windows.TH32CS_SNAPMODULE|windows.TH32CS_SNAPMODULE32, +@(private="package") +_process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path: string, err: Error) { + snap := win32.CreateToolhelp32Snapshot( + win32.TH32CS_SNAPMODULE|win32.TH32CS_SNAPMODULE32, u32(pid), ) - if snap == windows.INVALID_HANDLE_VALUE { - return "", _get_platform_error() + if snap == win32.INVALID_HANDLE_VALUE { + err =_get_platform_error() + return } - defer windows.CloseHandle(snap) - entry := windows.MODULEENTRY32W { dwSize = size_of(windows.MODULEENTRY32W) } - status := windows.Module32FirstW(snap, &entry) + defer win32.CloseHandle(snap) + + entry := win32.MODULEENTRY32W { dwSize = size_of(win32.MODULEENTRY32W) } + status := win32.Module32FirstW(snap, &entry) if !status { - return "", _get_platform_error() + err =_get_platform_error() + return } - exe_path, err := windows.wstring_to_utf8(raw_data(entry.szExePath[:]), -1, allocator) - if err != nil { - return "", err - } - return exe_path, nil + return win32.wstring_to_utf8(raw_data(entry.szExePath[:]), -1, allocator) } -@(private) -_get_process_user :: proc(process_handle: windows.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) { +_get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) { TEMP_ALLOCATOR_GUARD() - token_handle: windows.HANDLE = --- - if !windows.OpenProcessToken(process_handle, windows.TOKEN_QUERY, &token_handle) { + token_handle: win32.HANDLE + if !win32.OpenProcessToken(process_handle, win32.TOKEN_QUERY, &token_handle) { err = _get_platform_error() return } - token_user_size: u32 = --- - if !windows.GetTokenInformation(token_handle, .TokenUser, nil, 0, &token_user_size) { + token_user_size: u32 + if !win32.GetTokenInformation(token_handle, .TokenUser, nil, 0, &token_user_size) { // Note(flysand): Make sure the buffer too small error comes out, and not any other error err = _get_platform_error() if v, ok := err.(Platform_Error); !ok || int(v) != 0x7a { return } + err = nil } - token_user := cast(^windows.TOKEN_USER) raw_data(make([]u8, token_user_size, temp_allocator())) - if !windows.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) { + token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator()))) + if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) { err = _get_platform_error() return } - sid_type: windows.SID_NAME_USE = --- - username_w: [256]u16 = --- - domain_w: [256]u16 = --- - username_chrs: u32 = 256 - domain_chrs: u32 = 256 - if !windows.LookupAccountSidW(nil, token_user.User.Sid, &username_w[0], &username_chrs, &domain_w[0], &domain_chrs, &sid_type) { + + sid_type: win32.SID_NAME_USE + username_w: [256]u16 + domain_w: [256]u16 + username_chrs := u32(256) + domain_chrs := u32(256) + + if !win32.LookupAccountSidW(nil, token_user.User.Sid, &username_w[0], &username_chrs, &domain_w[0], &domain_chrs, &sid_type) { err = _get_platform_error() return } - username, username_err := windows.utf16_to_utf8(username_w[:username_chrs], temp_allocator()) - if username_err != nil { - err = username_err - return - } - domain, domain_err := windows.utf16_to_utf8(domain_w[:domain_chrs], temp_allocator()) - if domain_err != nil { - err = domain_err - return - } - full_name, full_name_err := strings.concatenate([]string {domain, "\\", username}, allocator) - if full_name_err != nil { - err = full_name_err - return - } - return full_name, nil + username := win32.utf16_to_utf8(username_w[:username_chrs], temp_allocator()) or_return + domain := win32.utf16_to_utf8(domain_w[:domain_chrs], temp_allocator()) or_return + return strings.concatenate({domain, "\\", username}, allocator) } -@(private) -_parse_command_line :: proc(cmd_line_w: [^]u16, allocator: runtime.Allocator) -> ([]string, Error) { - argc: i32 = --- - argv_w := windows.CommandLineToArgvW(cmd_line_w, &argc) +_parse_command_line :: proc(cmd_line_w: [^]u16, allocator: runtime.Allocator) -> (argv: []string, err: Error) { + argc: i32 + argv_w := win32.CommandLineToArgvW(cmd_line_w, &argc) if argv_w == nil { return nil, _get_platform_error() } - argv, argv_err := make([]string, argc, allocator) - if argv_err != nil { - return nil, argv_err + argv = make([]string, argc, allocator) or_return + defer if err != nil { + for arg in argv { + delete(arg, allocator) + } + delete(argv, allocator) } for arg_w, i in argv_w[:argc] { - arg, arg_err := windows.wstring_to_utf8(arg_w, -1, allocator) - if arg_err != nil { - for s in argv[:i] { - delete(s, allocator) - } - delete(argv, allocator) - return nil, arg_err - } - argv[i] = arg + argv[i] = win32.wstring_to_utf8(arg_w, -1, allocator) or_return } - return argv, nil + return } -@(private) _build_command_line :: proc(command: []string, allocator: runtime.Allocator) -> string { _write_byte_n_times :: #force_inline proc(builder: ^strings.Builder, b: byte, n: int) { for _ in 0 ..< n { @@ -770,8 +656,7 @@ _build_command_line :: proc(command: []string, allocator: runtime.Allocator) -> return strings.to_string(builder) } -@(private) -_parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) -> ([]string, Error) { +_parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) -> (envs: []string, err: Error) { zt_count := 0 for idx := 0; true; { if block[idx] == 0x0000 { @@ -783,13 +668,21 @@ _parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) -> } idx += 1 } + // Note(flysand): Each string in the environment block is terminated // by a NUL character. In addition, the environment block itself is // terminated by a NUL character. So the number of strings in the // environment block is the number of NUL character minus the // block terminator. env_count := zt_count - 1 - envs := make([]string, env_count, allocator) + envs = make([]string, env_count, allocator) or_return + defer if err != nil { + for env in envs { + delete(env, allocator) + } + delete(envs, allocator) + } + env_idx := 0 last_idx := 0 idx := 0 @@ -798,24 +691,19 @@ _parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) -> idx += 1 } env_w := block[last_idx:idx] - env, env_err := windows.utf16_to_utf8(env_w, allocator) - if env_err != nil { - return nil, env_err - } - envs[env_idx] = env + envs[env_idx] = win32.utf16_to_utf8(env_w, allocator) or_return env_idx += 1 idx += 1 last_idx = idx } - return envs, nil + return } -@(private) _build_environment_block :: proc(environment: []string, allocator: runtime.Allocator) -> string { builder := strings.builder_make(allocator) #reverse for kv, cur_idx in environment { eq_idx := strings.index_byte(kv, '=') - assert(eq_idx != -1, "Malformed environment string. Expected '=' to separate keys and values") + assert(eq_idx >= 0, "Malformed environment string. Expected '=' to separate keys and values") key := kv[:eq_idx] already_handled := false for old_kv in environment[cur_idx+1:] { @@ -836,109 +724,3 @@ _build_environment_block :: proc(environment: []string, allocator: runtime.Alloc strings.write_byte(&builder, 0) return strings.to_string(builder) } - -@(private="file") -PROCESSINFOCLASS :: enum i32 { - ProcessBasicInformation = 0, - ProcessDebugPort = 7, - ProcessWow64Information = 26, - ProcessImageFileName = 27, - ProcessBreakOnTermination = 29, - ProcessTelemetryIdInformation = 64, - ProcessSubsystemInformation = 75, -} - -@(private="file") -NtQueryInformationProcess_T :: #type proc ( - ProcessHandle: windows.HANDLE, - ProcessInformationClass: PROCESSINFOCLASS, - ProcessInformation: rawptr, - ProcessInformationLength: u32, - ReturnLength: ^u32, -) -> u32 - -@(private="file") -PROCESS_BASIC_INFORMATION :: struct { - _: rawptr, - PebBaseAddress: ^PEB, - _: [2]rawptr, - UniqueProcessId: ^u32, - _: rawptr, -} - -@(private="file") -PEB :: struct { - _: [2]u8, - BeingDebugged: u8, - _: [1]u8, - _: [2]rawptr, - Ldr: ^PEB_LDR_DATA, - ProcessParameters: ^RTL_USER_PROCESS_PARAMETERS, - _: [104]u8, - _: [52]rawptr, - PostProcessInitRoutine: #type proc "stdcall" (), - _: [128]u8, - _: [1]rawptr, - SessionId: u32, -} - -@(private="file") -PEB_LDR_DATA :: struct { - _: [8]u8, - _: [3]rawptr, - InMemoryOrderModuleList: LIST_ENTRY, -} - -@(private="file") -RTL_USER_PROCESS_PARAMETERS :: struct { - MaximumLength: u32, - Length: u32, - Flags: u32, - DebugFlags: u32, - ConsoleHandle: rawptr, - ConsoleFlags: u32, - StdInputHandle: rawptr, - StdOutputHandle: rawptr, - StdErrorHandle: rawptr, - CurrentDirectoryPath: UNICODE_STRING, - CurrentDirectoryHandle: rawptr, - DllPath: UNICODE_STRING, - ImagePathName: UNICODE_STRING, - CommandLine: UNICODE_STRING, - Environment: rawptr, - StartingPositionLeft: u32, - StartingPositionTop: u32, - Width: u32, - Height: u32, - CharWidth: u32, - CharHeight: u32, - ConsoleTextAttributes: u32, - WindowFlags: u32, - ShowWindowFlags: u32, - WindowTitle: UNICODE_STRING, - DesktopName: UNICODE_STRING, - ShellInfo: UNICODE_STRING, - RuntimeData: UNICODE_STRING, - DLCurrentDirectory: [32]RTL_DRIVE_LETTER_CURDIR, - EnvironmentSize: u32, -} - -RTL_DRIVE_LETTER_CURDIR :: struct { - Flags: u16, - Length: u16, - TimeStamp: u32, - DosPath: UNICODE_STRING, -} - -@(private="file") -UNICODE_STRING :: struct { - Length: u16, - MaximumLength: u16, - Buffer: [^]u16, -} - -@(private="file") -LIST_ENTRY :: struct { - Flink: ^LIST_ENTRY, - Blink: ^LIST_ENTRY, -} \ No newline at end of file diff --git a/core/sys/windows/ntdll.odin b/core/sys/windows/ntdll.odin index 56c24f1a2..b75ac695a 100644 --- a/core/sys/windows/ntdll.odin +++ b/core/sys/windows/ntdll.odin @@ -6,4 +6,110 @@ foreign import ntdll_lib "system:ntdll.lib" @(default_calling_convention="system") foreign ntdll_lib { RtlGetVersion :: proc(lpVersionInformation: ^OSVERSIONINFOEXW) -> NTSTATUS --- + + + NtQueryInformationProcess :: proc( + ProcessHandle: HANDLE, + ProcessInformationClass: PROCESS_INFO_CLASS, + ProcessInformation: rawptr, + ProcessInformationLength: u32, + ReturnLength: ^u32, + ) -> u32 --- +} + + +PROCESS_INFO_CLASS :: enum i32 { + ProcessBasicInformation = 0, + ProcessDebugPort = 7, + ProcessWow64Information = 26, + ProcessImageFileName = 27, + ProcessBreakOnTermination = 29, + ProcessTelemetryIdInformation = 64, + ProcessSubsystemInformation = 75, +} + + + +PROCESS_BASIC_INFORMATION :: struct { + ExitStatus: NTSTATUS, + PebBaseAddress: ^PEB, + AffinityMask: ULONG_PTR, + BasePriority: KPRIORITY, + UniqueProcessId: ULONG_PTR, + InheritedFromUniqueProcessId: ULONG_PTR, +} + +KPRIORITY :: rawptr + +PPS_POST_PROCESS_INIT_ROUTINE :: proc "system" () + + +PEB :: struct { + _: [2]u8, + BeingDebugged: u8, + _: [1]u8, + _: [2]rawptr, + Ldr: ^PEB_LDR_DATA, + ProcessParameters: ^RTL_USER_PROCESS_PARAMETERS, + _: [104]u8, + _: [52]rawptr, + PostProcessInitRoutine: PPS_POST_PROCESS_INIT_ROUTINE, + _: [128]u8, + _: [1]rawptr, + SessionId: u32, +} + + + + +PEB_LDR_DATA :: struct { + _: [8]u8, + _: [3]rawptr, + InMemoryOrderModuleList: LIST_ENTRY, +} + +RTL_USER_PROCESS_PARAMETERS :: struct { + MaximumLength: u32, + Length: u32, + Flags: u32, + DebugFlags: u32, + ConsoleHandle: rawptr, + ConsoleFlags: u32, + StdInputHandle: rawptr, + StdOutputHandle: rawptr, + StdErrorHandle: rawptr, + CurrentDirectoryPath: UNICODE_STRING, + CurrentDirectoryHandle: rawptr, + DllPath: UNICODE_STRING, + ImagePathName: UNICODE_STRING, + CommandLine: UNICODE_STRING, + Environment: rawptr, + StartingPositionLeft: u32, + StartingPositionTop: u32, + Width: u32, + Height: u32, + CharWidth: u32, + CharHeight: u32, + ConsoleTextAttributes: u32, + WindowFlags: u32, + ShowWindowFlags: u32, + WindowTitle: UNICODE_STRING, + DesktopName: UNICODE_STRING, + ShellInfo: UNICODE_STRING, + RuntimeData: UNICODE_STRING, + DLCurrentDirectory: [32]RTL_DRIVE_LETTER_CURDIR, + EnvironmentSize: u32, +} + +RTL_DRIVE_LETTER_CURDIR :: struct { + Flags: u16, + Length: u16, + TimeStamp: u32, + DosPath: UNICODE_STRING, +} + + +LIST_ENTRY :: struct { + Flink: ^LIST_ENTRY, + Blink: ^LIST_ENTRY, } \ No newline at end of file diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index 92c6023eb..35f4174eb 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -2495,9 +2495,9 @@ OBJECT_ATTRIBUTES :: struct { } UNICODE_STRING :: struct { - Length: u16, - MaximumLength: u16, - Buffer: ^u16, + Length: u16 `fmt:"-"`, + MaximumLength: u16 `fmt:"-"`, + Buffer: [^]u16 `fmt:"s,Length"`, } OVERLAPPED :: struct { From 321ef82d76ea175f7f6e8a4782601b81b2bf8bae Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 16 Jul 2024 11:57:22 +0100 Subject: [PATCH 115/198] Add @(require_results) where needed --- core/os/os2/process.odin | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/core/os/os2/process.odin b/core/os/os2/process.odin index 6dfb8d9d9..3f3e64668 100644 --- a/core/os/os2/process.odin +++ b/core/os/os2/process.odin @@ -14,7 +14,7 @@ TIMEOUT_INFINITE :: time.MIN_DURATION // Note(flysand): Any negative duration wi */ args := get_args() -@(private="file") +@(private="file", require_results) get_args :: proc() -> []string { result := make([]string, len(runtime.args__), heap_allocator()) for rt_arg, i in runtime.args__ { @@ -36,6 +36,7 @@ exit :: proc "contextless" (code: int) -> ! { **Note(windows)**: Windows doesn't follow the posix permissions model, so the function simply returns -1. */ +@(require_results) get_uid :: proc() -> int { return _get_uid() } @@ -51,6 +52,7 @@ get_uid :: proc() -> int { **Note(windows)**: Windows doesn't follow the posix permissions model, so the function simply returns -1. */ +@(require_results) get_euid :: proc() -> int { return _get_euid() } @@ -61,6 +63,7 @@ get_euid :: proc() -> int { **Note(windows)**: Windows doesn't follow the posix permissions model, so the function simply returns -1. */ +@(require_results) get_gid :: proc() -> int { return _get_gid() } @@ -76,6 +79,7 @@ get_gid :: proc() -> int { **Note(windows)**: Windows doesn't follow the posix permissions model, so the function simply returns -1. */ +@(require_results) get_egid :: proc() -> int { return _get_egid() } @@ -83,6 +87,7 @@ get_egid :: proc() -> int { /* Obtain the ID of the current process. */ +@(require_results) get_pid :: proc() -> int { return _get_pid() } @@ -96,6 +101,7 @@ get_pid :: proc() -> int { returned by this function can identify a non-existent or a different process. */ +@(require_results) get_ppid :: proc() -> int { return _get_ppid() } @@ -103,6 +109,7 @@ get_ppid :: proc() -> int { /* Obtain ID's of all processes running in the system. */ +@(require_results) process_list :: proc(allocator: runtime.Allocator) -> ([]int, Error) { return _process_list(allocator) } @@ -167,6 +174,7 @@ Process_Info :: struct { `Process_Info` struct has the required fields before checking the error code returned by this function. */ +@(require_results) process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { return _process_info_by_pid(pid, selection, allocator) } @@ -187,6 +195,7 @@ process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: `Process_Info` struct has the required fields before checking the error code returned by this function. */ +@(require_results) process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { return _process_info_by_handle(process, selection, allocator) } @@ -206,6 +215,7 @@ process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, `Process_Info` struct has the required fields before checking the error code returned by this function. */ +@(require_results) current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) { return _current_process_info(selection, allocator) } @@ -268,6 +278,7 @@ Process_Open_Flag :: enum { Use `process_close()` function to close the process handle. */ +@(require_results) process_open :: proc(pid: int, flags := Process_Open_Flags {}) -> (Process, Error) { return _process_open(pid, flags) } @@ -324,6 +335,7 @@ Process_Desc :: struct { of file handles in an unpredictable manner. In case multiple threads change handle inheritance properties, make sure to serialize all those calls. */ +@(require_results) process_start :: proc(desc := Process_Desc {}) -> (Process, Error) { return _process_start(desc) } @@ -364,6 +376,7 @@ Process_State :: struct { If an error is returned for any other reason, other than timeout, the process state is considered undetermined. */ +@(require_results) process_wait :: proc(process: Process, timeout := TIMEOUT_INFINITE) -> (Process_State, Error) { return _process_wait(process, timeout) } @@ -376,6 +389,7 @@ process_wait :: proc(process: Process, timeout := TIMEOUT_INFINITE) -> (Process_ desired, kill the process first, wait for the process to finish, then close the handle. */ +@(require_results) process_close :: proc(process: Process) -> (Error) { return _process_close(process) } @@ -386,6 +400,7 @@ process_close :: proc(process: Process) -> (Error) { This procedure terminates a process, specified by it's handle, `process`. */ +@(require_results) process_kill :: proc(process: Process) -> (Error) { return _process_kill(process) } From 3a162de18f978d8050971ee8d3094e8779939569 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 16 Jul 2024 12:08:59 +0100 Subject: [PATCH 116/198] More clean up for process_windows.odin --- core/os/os2/process_windows.odin | 95 +++++++++++++++----------------- 1 file changed, 45 insertions(+), 50 deletions(-) diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index 2ffee66e8..da62e48a7 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -57,19 +57,23 @@ _get_ppid :: proc() -> int { } @(private="package") -_process_list :: proc(allocator: runtime.Allocator) -> ([]int, Error) { - pid_list := make([dynamic]int, allocator) +_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0) if snap == win32.INVALID_HANDLE_VALUE { - return pid_list[:], _get_platform_error() + err = _get_platform_error() + return } - entry := win32.PROCESSENTRY32W { dwSize = size_of(win32.PROCESSENTRY32W) } + + list_d := make([dynamic]int, allocator) or_return + + entry := win32.PROCESSENTRY32W{dwSize = size_of(win32.PROCESSENTRY32W)} status := win32.Process32FirstW(snap, &entry) for status { - append(&pid_list, int(entry.th32ProcessID)) + append(&list_d, int(entry.th32ProcessID)) status = win32.Process32NextW(snap, &entry) } - return pid_list[:], nil + list = list_d[:] + return } @(require_results) @@ -160,7 +164,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator if .Command_Line in selection || .Command_Args in selection { TEMP_ALLOCATOR_GUARD() - cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) + cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return if .Command_Line in selection { @@ -177,7 +181,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator if .Environment in selection { TEMP_ALLOCATOR_GUARD() env_len := process_params.EnvironmentSize / 2 - envs_w := make([]u16, env_len, temp_allocator()) + envs_w := make([]u16, env_len, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return envs := _parse_environment_block(raw_data(envs_w), allocator) or_return @@ -187,7 +191,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator } if .Working_Dir in selection { TEMP_ALLOCATOR_GUARD() - cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) + cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return cwd := win32.utf16_to_utf8(cwd_w, allocator) or_return @@ -262,7 +266,7 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields if .Command_Line in selection || .Command_Args in selection { TEMP_ALLOCATOR_GUARD() - cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) + cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return if .Command_Line in selection { @@ -281,7 +285,7 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields if .Environment in selection { TEMP_ALLOCATOR_GUARD() env_len := process_params.EnvironmentSize / 2 - envs_w := make([]u16, env_len, temp_allocator()) + envs_w := make([]u16, env_len, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return envs := _parse_environment_block(raw_data(envs_w), allocator) or_return @@ -291,7 +295,7 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields if .Working_Dir in selection { TEMP_ALLOCATOR_GUARD() - cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) + cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return cwd := win32.utf16_to_utf8(cwd_w, allocator) or_return @@ -399,9 +403,9 @@ _process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, _Sys_Process_Attributes :: struct {} @(private="package") -_process_start :: proc(desc: Process_Desc) -> (Process, Error) { +_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { TEMP_ALLOCATOR_GUARD() - command_line := _build_command_line(desc.command, temp_allocator()) + command_line := _build_command_line(desc.command, temp_allocator()) command_line_w := win32.utf8_to_wstring(command_line, temp_allocator()) environment := desc.env if desc.env == nil { @@ -412,6 +416,7 @@ _process_start :: proc(desc: Process_Desc) -> (Process, Error) { stderr_handle := win32.GetStdHandle(win32.STD_ERROR_HANDLE) stdout_handle := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE) stdin_handle := win32.GetStdHandle(win32.STD_INPUT_HANDLE) + if desc.stdout != nil { stdout_handle = win32.HANDLE((^File_Impl)(desc.stdout.impl).fd) } @@ -421,12 +426,10 @@ _process_start :: proc(desc: Process_Desc) -> (Process, Error) { if desc.stdin != nil { stdin_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd) } - working_dir_w := win32.wstring(nil) - if len(desc.working_dir) > 0 { - working_dir_w = win32.utf8_to_wstring(desc.working_dir, temp_allocator()) - } + + working_dir_w := win32.utf8_to_wstring(desc.working_dir, temp_allocator()) if len(desc.working_dir) > 0 else nil process_info: win32.PROCESS_INFORMATION - process_ok := win32.CreateProcessW( + ok := win32.CreateProcessW( nil, command_line_w, nil, @@ -435,30 +438,29 @@ _process_start :: proc(desc: Process_Desc) -> (Process, Error) { win32.CREATE_UNICODE_ENVIRONMENT|win32.NORMAL_PRIORITY_CLASS, raw_data(environment_block_w), working_dir_w, - &win32.STARTUPINFOW { + &win32.STARTUPINFOW{ cb = size_of(win32.STARTUPINFOW), - hStdError = stderr_handle, + hStdError = stderr_handle, hStdOutput = stdout_handle, - hStdInput = stdin_handle, + hStdInput = stdin_handle, dwFlags = win32.STARTF_USESTDHANDLES, }, &process_info, ) - if !process_ok { - return {}, _get_platform_error() + if !ok { + err = _get_platform_error() + return } - return Process { - pid = int(process_info.dwProcessId), - handle = uintptr(process_info.hProcess), - }, nil + process = {pid = int(process_info.dwProcessId), handle = uintptr(process_info.hProcess)} + return } @(private="package") _process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { handle := win32.HANDLE(process.handle) - timeout_ms := u32(timeout / time.Millisecond) if timeout > 0 else win32.INFINITE - wait_result := win32.WaitForSingleObject(handle, timeout_ms) - switch wait_result { + timeout_ms := u32(timeout / time.Millisecond) if timeout >= 0 else win32.INFINITE + + switch win32.WaitForSingleObject(handle, timeout_ms) { case win32.WAIT_OBJECT_0: exit_code: u32 if !win32.GetExitCodeProcess(handle, &exit_code) { @@ -516,26 +518,24 @@ _filetime_to_duration :: proc(filetime: win32.FILETIME) -> time.Duration { return time.Duration(ticks * 100) } -_process_entry_by_pid :: proc(pid: int) -> (win32.PROCESSENTRY32W, Error) { +_process_entry_by_pid :: proc(pid: int) -> (entry: win32.PROCESSENTRY32W, err: Error) { snap := win32.CreateToolhelp32Snapshot(win32.TH32CS_SNAPPROCESS, 0) if snap == win32.INVALID_HANDLE_VALUE { - return {}, _get_platform_error() + err = _get_platform_error() + return } defer win32.CloseHandle(snap) - entry := win32.PROCESSENTRY32W { dwSize = size_of(win32.PROCESSENTRY32W) } + + entry = win32.PROCESSENTRY32W{dwSize = size_of(win32.PROCESSENTRY32W)} status := win32.Process32FirstW(snap, &entry) - found := false for status { if u32(pid) == entry.th32ProcessID { - found = true - break + return } status = win32.Process32NextW(snap, &entry) } - if !found { - return {}, General_Error.Not_Exist - } - return entry, nil + err = General_Error.Not_Exist + return } // Note(flysand): Not sure which way it's better to get the executable path: @@ -580,7 +580,7 @@ _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Alloc } err = nil } - token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator()))) + token_user := (^win32.TOKEN_USER)(raw_data(make([]u8, token_user_size, temp_allocator()) or_return)) if !win32.GetTokenInformation(token_handle, .TokenUser, token_user, token_user_size, &token_user_size) { err = _get_platform_error() return @@ -701,22 +701,17 @@ _parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) -> _build_environment_block :: proc(environment: []string, allocator: runtime.Allocator) -> string { builder := strings.builder_make(allocator) - #reverse for kv, cur_idx in environment { + loop: #reverse for kv, cur_idx in environment { eq_idx := strings.index_byte(kv, '=') assert(eq_idx >= 0, "Malformed environment string. Expected '=' to separate keys and values") key := kv[:eq_idx] - already_handled := false for old_kv in environment[cur_idx+1:] { old_key := old_kv[:strings.index_byte(old_kv, '=')] if key == old_key { - already_handled = true - break + continue loop } } - if already_handled { - continue - } - strings.write_bytes(&builder, transmute([]byte) kv) + strings.write_string(&builder, kv) strings.write_byte(&builder, 0) } // Note(flysand): In addition to the NUL-terminator for each string, the From 1afb10109ec90ca0821068e9c93133d17ace6cb5 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 16 Jul 2024 12:13:39 +0100 Subject: [PATCH 117/198] Remove the need for temporary variables where possible --- core/os/os2/process_windows.odin | 54 ++++++++++---------------------- 1 file changed, 17 insertions(+), 37 deletions(-) diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index da62e48a7..48cfed48a 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -120,9 +120,8 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator } } if need_snapmodule { - exe_path := _process_exe_by_pid(pid, allocator) or_return + info.executable_path = _process_exe_by_pid(pid, allocator) or_return info.fields += {.Executable_Path} - info.executable_path = exe_path } ph := win32.INVALID_HANDLE_VALUE if need_process_handle { @@ -168,14 +167,12 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator _ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return if .Command_Line in selection { - cmdline := win32.utf16_to_utf8(cmdline_w, allocator) or_return + info.command_line = win32.utf16_to_utf8(cmdline_w, allocator) or_return info.fields += {.Command_Line} - info.command_line = cmdline } if .Command_Args in selection { - args := _parse_command_line(raw_data(cmdline_w), allocator) or_return + info.command_args = _parse_command_line(raw_data(cmdline_w), allocator) or_return info.fields += {.Command_Args} - info.command_args = args } } if .Environment in selection { @@ -184,27 +181,22 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator envs_w := make([]u16, env_len, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return - envs := _parse_environment_block(raw_data(envs_w), allocator) or_return - + info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return info.fields += {.Environment} - info.environment = envs } if .Working_Dir in selection { TEMP_ALLOCATOR_GUARD() cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return - cwd := win32.utf16_to_utf8(cwd_w, allocator) or_return - + info.working_dir = win32.utf16_to_utf8(cwd_w, allocator) or_return info.fields += {.Working_Dir} - info.working_dir = cwd } } if .Username in selection { - username := _get_process_user(ph, allocator) or_return + info.username = _get_process_user(ph, allocator) or_return info.fields += {.Username} - info.username = username } err = nil return @@ -237,9 +229,8 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields } } if need_snapmodule { - exe_path := _process_exe_by_pid(pid, allocator) or_return + info.executable_path = _process_exe_by_pid(pid, allocator) or_return info.fields += {.Executable_Path} - info.executable_path = exe_path } ph := win32.HANDLE(process.handle) if need_peb { @@ -270,15 +261,12 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields _ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return if .Command_Line in selection { - cmdline := win32.utf16_to_utf8(cmdline_w, allocator) or_return - + info.command_line = win32.utf16_to_utf8(cmdline_w, allocator) or_return info.fields += {.Command_Line} - info.command_line = cmdline } if .Command_Args in selection { - args := _parse_command_line(raw_data(cmdline_w), allocator) or_return + info.command_args = _parse_command_line(raw_data(cmdline_w), allocator) or_return info.fields += {.Command_Args} - info.command_args = args } } @@ -288,9 +276,8 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields envs_w := make([]u16, env_len, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return - envs := _parse_environment_block(raw_data(envs_w), allocator) or_return + info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return info.fields += {.Environment} - info.environment = envs } if .Working_Dir in selection { @@ -298,15 +285,13 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return - cwd := win32.utf16_to_utf8(cwd_w, allocator) or_return + info.working_dir = win32.utf16_to_utf8(cwd_w, allocator) or_return info.fields += {.Working_Dir} - info.working_dir = cwd } } if .Username in selection { - username := _get_process_user(ph, allocator) or_return + info.username = _get_process_user(ph, allocator) or_return info.fields += {.Username} - info.username = username } err = nil return @@ -337,34 +322,29 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime if .Executable_Path in selection { exe_filename_w: [256]u16 path_len := win32.GetModuleFileNameW(nil, raw_data(exe_filename_w[:]), len(exe_filename_w)) - exe_filename := win32.utf16_to_utf8(exe_filename_w[:path_len], allocator) or_return + info.executable_path = win32.utf16_to_utf8(exe_filename_w[:path_len], allocator) or_return info.fields += {.Executable_Path} - info.executable_path = exe_filename } if .Command_Line in selection || .Command_Args in selection { command_line_w := win32.GetCommandLineW() if .Command_Line in selection { - command_line := win32.wstring_to_utf8(command_line_w, -1, allocator) or_return + info.command_line = win32.wstring_to_utf8(command_line_w, -1, allocator) or_return info.fields += {.Command_Line} - info.command_line = command_line } if .Command_Args in selection { - args := _parse_command_line(command_line_w, allocator) or_return + info.command_args = _parse_command_line(command_line_w, allocator) or_return info.fields += {.Command_Args} - info.command_args = args } } if .Environment in selection { env_block := win32.GetEnvironmentStringsW() - envs := _parse_environment_block(env_block, allocator) or_return + info.environment = _parse_environment_block(env_block, allocator) or_return info.fields += {.Environment} - info.environment = envs } if .Username in selection { process_handle := win32.GetCurrentProcess() - username := _get_process_user(process_handle, allocator) or_return + info.username = _get_process_user(process_handle, allocator) or_return info.fields += {.Username} - info.username = username } if .Working_Dir in selection { // TODO(flysand): Implement this by reading PEB From fe718460c6059e6002b5e04549fd1a9cf31ed084 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 16 Jul 2024 12:17:48 +0100 Subject: [PATCH 118/198] Clean up `bit_set` usage --- core/os/os2/process_windows.odin | 35 ++++++++++++++------------------ 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index 48cfed48a..4cdf215ed 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -98,13 +98,8 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator free_process_info(info, allocator) } - need_snapprocess := selection >= {.PPid, .Priority} - need_snapmodule := .Executable_Path in selection - need_peb := selection >= {.Command_Line, .Environment, .Working_Dir} - need_process_handle := need_peb || .Username in selection - // Data obtained from process snapshots - if need_snapprocess { + if selection >= {.PPid, .Priority} { entry, entry_err := _process_entry_by_pid(info.pid) if entry_err != nil { err = General_Error.Not_Exist @@ -119,12 +114,14 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator info.priority = int(entry.pcPriClassBase) } } - if need_snapmodule { + if .Executable_Path in selection { // snap module info.executable_path = _process_exe_by_pid(pid, allocator) or_return info.fields += {.Executable_Path} } + ph := win32.INVALID_HANDLE_VALUE - if need_process_handle { + + if selection >= {.Command_Line, .Environment, .Working_Dir, .Username} { // need process handle ph = win32.OpenProcess( win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.PROCESS_VM_READ, false, @@ -139,7 +136,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator win32.CloseHandle(ph) } - if need_peb { + if selection >= {.Command_Line, .Environment, .Working_Dir} { // need peb process_info_size: u32 process_info: win32.PROCESS_BASIC_INFORMATION status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size) @@ -161,7 +158,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator process_params: win32.RTL_USER_PROCESS_PARAMETERS _ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return - if .Command_Line in selection || .Command_Args in selection { + if selection >= {.Command_Line, .Command_Args} { TEMP_ALLOCATOR_GUARD() cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return @@ -209,11 +206,9 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields defer if err != nil { free_process_info(info, allocator) } - need_snapprocess := selection >= {.PPid, .Priority} - need_snapmodule := .Executable_Path in selection - need_peb := selection >= {.Command_Line, .Environment, .Working_Dir} + // Data obtained from process snapshots - if need_snapprocess { + if selection >= {.PPid, .Priority} { // snap process entry, entry_err := _process_entry_by_pid(info.pid) if entry_err != nil { err = General_Error.Not_Exist @@ -228,12 +223,12 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields info.priority = int(entry.pcPriClassBase) } } - if need_snapmodule { + if .Executable_Path in selection { // snap module info.executable_path = _process_exe_by_pid(pid, allocator) or_return info.fields += {.Executable_Path} } ph := win32.HANDLE(process.handle) - if need_peb { + if selection >= {.Command_Line, .Environment, .Working_Dir} { // need peb process_info_size: u32 process_info: win32.PROCESS_BASIC_INFORMATION status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size) @@ -255,7 +250,7 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields process_params: win32.RTL_USER_PROCESS_PARAMETERS _ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return - if .Command_Line in selection || .Command_Args in selection { + if selection >= {.Command_Line, .Command_Args} { TEMP_ALLOCATOR_GUARD() cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return @@ -303,8 +298,8 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime defer if err != nil { free_process_info(info, allocator) } - need_snapprocess := .PPid in selection || .Priority in selection - if need_snapprocess { + + if selection >= {.PPid, .Priority} { // snap process entry, entry_err := _process_entry_by_pid(info.pid) if entry_err != nil { err = General_Error.Not_Exist @@ -325,7 +320,7 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime info.executable_path = win32.utf16_to_utf8(exe_filename_w[:path_len], allocator) or_return info.fields += {.Executable_Path} } - if .Command_Line in selection || .Command_Args in selection { + if selection >= {.Command_Line, .Command_Args} { command_line_w := win32.GetCommandLineW() if .Command_Line in selection { info.command_line = win32.wstring_to_utf8(command_line_w, -1, allocator) or_return From 8d70a264ab547f3d3bc66501921cf14884987689 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 16 Jul 2024 12:21:55 +0100 Subject: [PATCH 119/198] Check for specific error directly --- core/os/os2/process_windows.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index 4cdf215ed..8d61e7be7 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -550,7 +550,7 @@ _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Alloc if !win32.GetTokenInformation(token_handle, .TokenUser, nil, 0, &token_user_size) { // Note(flysand): Make sure the buffer too small error comes out, and not any other error err = _get_platform_error() - if v, ok := err.(Platform_Error); !ok || int(v) != 0x7a { + if v, ok := is_platform_error(err); !ok || v != i32(win32.ERROR_INSUFFICIENT_BUFFER) { return } err = nil From 03e90bf9241e75a8c0f6e355e0df35b3a8c790ab Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 16 Jul 2024 12:26:24 +0100 Subject: [PATCH 120/198] Use RTTI to get the `error_string` for Windows --- core/os/os2/errors_windows.odin | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/os/os2/errors_windows.odin b/core/os/os2/errors_windows.odin index 6500e7ccc..3572afb14 100644 --- a/core/os/os2/errors_windows.odin +++ b/core/os/os2/errors_windows.odin @@ -1,6 +1,8 @@ //+private package os2 +import "base:runtime" +import "core:slice" import win32 "core:sys/windows" _error_string :: proc(errno: i32) -> string { @@ -8,6 +10,14 @@ _error_string :: proc(errno: i32) -> string { if e == 0 { return "" } + + err := runtime.Type_Info_Enum_Value(e) + + ti := &runtime.type_info_base(type_info_of(win32.System_Error)).variant.(runtime.Type_Info_Enum) + if idx, ok := slice.binary_search(ti.values, err); ok { + return ti.names[idx] + } + // TODO(bill): _error_string for windows // FormatMessageW return "" From 6702f077621960bfffdf33502555b450e062c14e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 16 Jul 2024 14:02:07 +0100 Subject: [PATCH 121/198] `rawptr` -> `cstring` --- vendor/raylib/raylib.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/raylib/raylib.odin b/vendor/raylib/raylib.odin index 0dd3bd4fd..8dd896ed5 100644 --- a/vendor/raylib/raylib.odin +++ b/vendor/raylib/raylib.odin @@ -1424,7 +1424,7 @@ foreign lib { LoadUTF8 :: proc(codepoints: [^]rune, length: c.int) -> [^]byte --- // Load UTF-8 text encoded from codepoints array UnloadUTF8 :: proc(text: [^]byte) --- // Unload UTF-8 text encoded from codepoints array - LoadCodepoints :: proc(text: rawptr, count: ^c.int) -> [^]rune --- // Load all codepoints from a UTF-8 text string, codepoints count returned by parameter + LoadCodepoints :: proc(text: cstring, count: ^c.int) -> [^]rune --- // Load all codepoints from a UTF-8 text string, codepoints count returned by parameter UnloadCodepoints :: proc(codepoints: [^]rune) --- // Unload codepoints data from memory GetCodepointCount :: proc(text : cstring) -> c.int --- // Get total number of codepoints in a UTF-8 encoded string GetCodepoint :: proc(text: cstring, codepointSize: ^c.int) -> rune --- // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure From df56655ab1bb8dff688c5cba5690aee8d506cb04 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 16 Jul 2024 14:03:02 +0100 Subject: [PATCH 122/198] Fix formatting inconsistencies --- vendor/raylib/raylib.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vendor/raylib/raylib.odin b/vendor/raylib/raylib.odin index 8dd896ed5..cff590b7f 100644 --- a/vendor/raylib/raylib.odin +++ b/vendor/raylib/raylib.odin @@ -1015,8 +1015,8 @@ foreign lib { SetRandomSeed :: proc(seed: c.uint) --- // Set the seed for the random number generator GetRandomValue :: proc(min, max: c.int) -> c.int --- // Get a random value between min and max (both included) - LoadRandomSequence :: proc(count : c.uint, min, max: c.int) --- // Load random values sequence, no values repeated - UnloadRandomSequence :: proc(sequence : ^c.int) --- // Unload random values sequence + LoadRandomSequence :: proc(count: c.uint, min, max: c.int) --- // Load random values sequence, no values repeated + UnloadRandomSequence :: proc(sequence: ^c.int) --- // Unload random values sequence // Misc. functions TakeScreenshot :: proc(fileName: cstring) --- // Takes a screenshot of current screen (filename extension defines format) @@ -1426,7 +1426,7 @@ foreign lib { UnloadUTF8 :: proc(text: [^]byte) --- // Unload UTF-8 text encoded from codepoints array LoadCodepoints :: proc(text: cstring, count: ^c.int) -> [^]rune --- // Load all codepoints from a UTF-8 text string, codepoints count returned by parameter UnloadCodepoints :: proc(codepoints: [^]rune) --- // Unload codepoints data from memory - GetCodepointCount :: proc(text : cstring) -> c.int --- // Get total number of codepoints in a UTF-8 encoded string + GetCodepointCount :: proc(text: cstring) -> c.int --- // Get total number of codepoints in a UTF-8 encoded string GetCodepoint :: proc(text: cstring, codepointSize: ^c.int) -> rune --- // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure GetCodepointNext :: proc(text: cstring, codepointSize: ^c.int) -> rune --- // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure GetCodepointPrevious :: proc(text: cstring, codepointSize: ^c.int) -> rune --- // Get previous codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure From a3fe5754d9f43a2b3d421018b66c7e0239eddd13 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 16 Jul 2024 15:31:00 +0100 Subject: [PATCH 123/198] Make `complex32` use higher precision where possible for calculations --- core/math/cmplx/cmplx.odin | 62 +++--------------------------- core/math/cmplx/cmplx_invtrig.odin | 16 ++------ 2 files changed, 9 insertions(+), 69 deletions(-) diff --git a/core/math/cmplx/cmplx.odin b/core/math/cmplx/cmplx.odin index 4625f83c6..d1c70ca61 100644 --- a/core/math/cmplx/cmplx.odin +++ b/core/math/cmplx/cmplx.odin @@ -229,7 +229,7 @@ sqrt_complex128 :: proc "contextless" (x: complex128) -> complex128 { } ln_complex32 :: proc "contextless" (x: complex32) -> complex32 { - return complex(math.ln(abs(x)), phase(x)) + return complex32(ln_complex64(complex64(x))) } ln_complex64 :: proc "contextless" (x: complex64) -> complex64 { return complex(math.ln(abs(x)), phase(x)) @@ -240,26 +240,7 @@ ln_complex128 :: proc "contextless" (x: complex128) -> complex128 { exp_complex32 :: proc "contextless" (x: complex32) -> complex32 { - switch re, im := real(x), imag(x); { - case math.is_inf(re, 0): - switch { - case re > 0 && im == 0: - return x - case math.is_inf(im, 0) || math.is_nan(im): - if re < 0 { - return complex(0, math.copy_sign(0, im)) - } else { - return complex(math.inf_f64(1.0), math.nan_f64()) - } - } - case math.is_nan(re): - if im == 0 { - return complex(math.nan_f16(), im) - } - } - r := math.exp(real(x)) - s, c := math.sincos(imag(x)) - return complex(r*c, r*s) + return complex32(exp_complex64(complex64(x))) } exp_complex64 :: proc "contextless" (x: complex64) -> complex64 { switch re, im := real(x), imag(x); { @@ -308,37 +289,7 @@ exp_complex128 :: proc "contextless" (x: complex128) -> complex128 { pow_complex32 :: proc "contextless" (x, y: complex32) -> complex32 { - if x == 0 { // Guaranteed also true for x == -0. - if is_nan(y) { - return nan_complex32() - } - r, i := real(y), imag(y) - switch { - case r == 0: - return 1 - case r < 0: - if i == 0 { - return complex(math.inf_f16(1), 0) - } - return inf_complex32() - case r > 0: - return 0 - } - unreachable() - } - modulus := abs(x) - if modulus == 0 { - return complex(0, 0) - } - r := math.pow(modulus, real(y)) - arg := phase(x) - theta := real(y) * arg - if imag(y) != 0 { - r *= math.exp(-imag(y) * arg) - theta += imag(y) * math.ln(modulus) - } - s, c := math.sincos(theta) - return complex(r*c, r*s) + return complex32(pow_complex64(complex64(x), complex64(y))) } pow_complex64 :: proc "contextless" (x, y: complex64) -> complex64 { if x == 0 { // Guaranteed also true for x == -0. @@ -410,7 +361,7 @@ pow_complex128 :: proc "contextless" (x, y: complex128) -> complex128 { log10_complex32 :: proc "contextless" (x: complex32) -> complex32 { - return math.LN10*ln(x) + return complex32(log10_complex64(complex64(x))) } log10_complex64 :: proc "contextless" (x: complex64) -> complex64 { return math.LN10*ln(x) @@ -421,7 +372,7 @@ log10_complex128 :: proc "contextless" (x: complex128) -> complex128 { phase_complex32 :: proc "contextless" (x: complex32) -> f16 { - return math.atan2(imag(x), real(x)) + return f16(phase_complex64(complex64(x))) } phase_complex64 :: proc "contextless" (x: complex64) -> f32 { return math.atan2(imag(x), real(x)) @@ -432,8 +383,7 @@ phase_complex128 :: proc "contextless" (x: complex128) -> f64 { rect_complex32 :: proc "contextless" (r, θ: f16) -> complex32 { - s, c := math.sincos(θ) - return complex(r*c, r*s) + return complex32(rect_complex64(f32(r), f32(θ))) } rect_complex64 :: proc "contextless" (r, θ: f32) -> complex64 { s, c := math.sincos(θ) diff --git a/core/math/cmplx/cmplx_invtrig.odin b/core/math/cmplx/cmplx_invtrig.odin index b84f0ac9c..40a8493bc 100644 --- a/core/math/cmplx/cmplx_invtrig.odin +++ b/core/math/cmplx/cmplx_invtrig.odin @@ -61,8 +61,7 @@ atanh :: proc{ acos_complex32 :: proc "contextless" (x: complex32) -> complex32 { - w := asin(x) - return complex(math.PI/2 - real(w), -imag(w)) + return complex32(acos_complex64(complex64(x))) } acos_complex64 :: proc "contextless" (x: complex64) -> complex64 { w := asin(x) @@ -75,14 +74,7 @@ acos_complex128 :: proc "contextless" (x: complex128) -> complex128 { acosh_complex32 :: proc "contextless" (x: complex32) -> complex32 { - if x == 0 { - return complex(0, math.copy_sign(math.PI/2, imag(x))) - } - w := acos(x) - if imag(w) <= 0 { - return complex(-imag(w), real(w)) - } - return complex(imag(w), -real(w)) + return complex32(acosh_complex64(complex64(x))) } acosh_complex64 :: proc "contextless" (x: complex64) -> complex64 { if x == 0 { @@ -257,9 +249,7 @@ atan_complex128 :: proc "contextless" (x: complex128) -> complex128 { } atanh_complex32 :: proc "contextless" (x: complex32) -> complex32 { - z := complex(-imag(x), real(x)) // z = i * x - z = atan(z) - return complex(imag(z), -real(z)) // z = -i * z + return complex32(atanh_complex64(complex64(x))) } atanh_complex64 :: proc "contextless" (x: complex64) -> complex64 { z := complex(-imag(x), real(x)) // z = i * x From 28fac62a022b147042bc0e1c49a89f7fe611e19c Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 16 Jul 2024 18:44:18 +0200 Subject: [PATCH 124/198] fix some bugs with -disable-assert --- base/runtime/wasm_allocator.odin | 3 ++- core/encoding/cbor/marshal.odin | 3 ++- core/encoding/cbor/unmarshal.odin | 3 ++- core/testing/runner.odin | 4 ++-- core/thread/thread_unix.odin | 8 +++++--- vendor/wgpu/wgpu_js.odin | 3 ++- 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/base/runtime/wasm_allocator.odin b/base/runtime/wasm_allocator.odin index f4b399c47..6bafaa489 100644 --- a/base/runtime/wasm_allocator.odin +++ b/base/runtime/wasm_allocator.odin @@ -297,7 +297,8 @@ lock :: proc(a: ^WASM_Allocator) { return } - assert(intrinsics.wasm_memory_atomic_wait32((^u32)(&a.mu), u32(new_state), -1) != 0) + ret := intrinsics.wasm_memory_atomic_wait32((^u32)(&a.mu), u32(new_state), -1) + assert(ret != 0) intrinsics.cpu_relax() } } diff --git a/core/encoding/cbor/marshal.odin b/core/encoding/cbor/marshal.odin index 022e297e9..6657807f5 100644 --- a/core/encoding/cbor/marshal.odin +++ b/core/encoding/cbor/marshal.odin @@ -351,7 +351,8 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er builder := strings.builder_from_slice(res[:]) e.writer = strings.to_stream(&builder) - assert(_encode_u64(e, u64(len(str)), .Text) == nil) + err := _encode_u64(e, u64(len(str)), .Text) + assert(err == nil) res[9] = u8(len(builder.buf)) assert(res[9] < 10) return diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index 4da2d5a93..c54660839 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -96,7 +96,8 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header, allocator := context.a ti = reflect.type_info_base(variant) if !reflect.is_pointer_internally(variant) { tag := any{rawptr(uintptr(v.data) + u.tag_offset), u.tag_type.id} - assert(_assign_int(tag, 1)) + assigned := _assign_int(tag, 1) + assert(assigned) } } } diff --git a/core/testing/runner.odin b/core/testing/runner.odin index fa7c2ffd2..c27c2124a 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -654,8 +654,8 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { #no_bounds_check pkg := report.packages_by_name[it.pkg] pkg.frame_ready = false - fmt.assertf(thread.pool_stop_task(&pool, test_index), - "A signal (%v) was raised to stop test #%i %s.%s, but it was unable to be found.", + found := thread.pool_stop_task(&pool, test_index) + fmt.assertf(found, "A signal (%v) was raised to stop test #%i %s.%s, but it was unable to be found.", reason, test_index, it.pkg, it.name) // The order this is handled in is a little particular. diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 363f50862..f56454bfc 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -81,9 +81,12 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { 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) + res: i32 + res = unix.pthread_attr_setdetachstate(&attrs, unix.PTHREAD_CREATE_JOINABLE) + assert(res == 0) when ODIN_OS != .Haiku && ODIN_OS != .NetBSD { - assert(unix.pthread_attr_setinheritsched(&attrs, unix.PTHREAD_EXPLICIT_SCHED) == 0) + res = unix.pthread_attr_setinheritsched(&attrs, unix.PTHREAD_EXPLICIT_SCHED) + assert(res == 0) } thread := new(Thread) @@ -94,7 +97,6 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { // Set thread priority. policy: i32 - res: i32 when ODIN_OS != .Haiku && ODIN_OS != .NetBSD { res = unix.pthread_attr_getschedpolicy(&attrs, &policy) assert(res == 0) diff --git a/vendor/wgpu/wgpu_js.odin b/vendor/wgpu/wgpu_js.odin index f375a0d69..3c8375adb 100644 --- a/vendor/wgpu/wgpu_js.odin +++ b/vendor/wgpu/wgpu_js.odin @@ -22,5 +22,6 @@ wgpu_alloc :: proc "contextless" (size: i32) -> [^]byte { @(private="file", export) wgpu_free :: proc "contextless" (ptr: rawptr) { context = g_context - assert(free(ptr) == nil, "wgpu_free failed") + err := free(ptr) + assert(err == nil, "wgpu_free failed") } From ba4995045465204805d55318efa0cbf086e4265d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 16 Jul 2024 18:33:01 +0100 Subject: [PATCH 125/198] Improve `rand.shuffle` --- core/math/rand/rand.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/math/rand/rand.odin b/core/math/rand/rand.odin index 4fdbad01c..4f736985f 100644 --- a/core/math/rand/rand.odin +++ b/core/math/rand/rand.odin @@ -618,7 +618,7 @@ shuffle :: proc(array: $T/[]$E, gen := context.random_generator) { return } - for i := i64(n - 1); i > 0; i -= 1 { + for i := i64(n - 2); i >= 0; i -= 1 { j := int63_max(i + 1, gen) array[i], array[j] = array[j], array[i] } From 0d881e1561d62065a6c702c3eabadc253a8f2bc6 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 16 Jul 2024 18:36:31 +0100 Subject: [PATCH 126/198] Improve rand.shuffle further by splitting into 64-bit and 32-bit parts --- core/math/rand/rand.odin | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/math/rand/rand.odin b/core/math/rand/rand.odin index 4f736985f..e02f3db80 100644 --- a/core/math/rand/rand.odin +++ b/core/math/rand/rand.odin @@ -618,10 +618,16 @@ shuffle :: proc(array: $T/[]$E, gen := context.random_generator) { return } - for i := i64(n - 2); i >= 0; i -= 1 { + i := n - 1 + for ; i > (1<<31 - 2); i -= 1 { j := int63_max(i + 1, gen) array[i], array[j] = array[j], array[i] } + + for ; i > 0; i -= 1 { + j := int31_max(i32(i + 1), gen) + array[i], array[j] = array[j], array[i] + } } /* From 853487e86cd1bede0c5179ed1ba796aad2200f39 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 16 Jul 2024 22:07:49 +0200 Subject: [PATCH 127/198] fix `add_sat` and `sub_sat` intrinsics --- base/intrinsics/intrinsics.odin | 9 ++++--- src/check_builtin.cpp | 43 ++++++++++++++++++++++++++++++++- src/llvm_backend_proc.cpp | 42 +++++++++++++++++++++++++++----- 3 files changed, 84 insertions(+), 10 deletions(-) diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index 8a16ca40e..047373cda 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -38,9 +38,12 @@ count_leading_zeros :: proc(x: $T) -> T where type_is_integer(T) || type_is_sim reverse_bits :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) --- byte_swap :: proc(x: $T) -> T where type_is_integer(T) || type_is_float(T) --- -overflow_add :: proc(lhs, rhs: $T) -> (T, bool) --- -overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) --- -overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) --- +overflow_add :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) --- +overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) --- +overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) --- + +add_sat :: proc(lhs, rhs: $T) -> T where type_is_integer(T) --- +sub_sat :: proc(lhs, rhs: $T) -> T where type_is_integer(T) --- sqrt :: proc(x: $T) -> T where type_is_float(T) || (type_is_simd_vector(T) && type_is_float(type_elem_type(T))) --- diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 26de3a112..b6b1f9874 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -4261,6 +4261,47 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_overflow_add: case BuiltinProc_overflow_sub: case BuiltinProc_overflow_mul: + { + Operand x = {}; + Operand y = {}; + check_expr(c, &x, ce->args[0]); + check_expr(c, &y, ce->args[1]); + if (x.mode == Addressing_Invalid) { + return false; + } + if (y.mode == Addressing_Invalid) { + return false; + } + convert_to_typed(c, &y, x.type); if (y.mode == Addressing_Invalid) return false; + convert_to_typed(c, &x, y.type); + if (is_type_untyped(x.type)) { + gbString xts = type_to_string(x.type); + error(x.expr, "Expected a typed integer for '%.*s', got %s", LIT(builtin_name), xts); + gb_string_free(xts); + return false; + } + if (!is_type_integer(x.type)) { + gbString xts = type_to_string(x.type); + error(x.expr, "Expected an integer for '%.*s', got %s", LIT(builtin_name), xts); + gb_string_free(xts); + return false; + } + Type *ct = core_type(x.type); + if (is_type_different_to_arch_endianness(ct)) { + GB_ASSERT(ct->kind == Type_Basic); + if (ct->Basic.flags & (BasicFlag_EndianLittle|BasicFlag_EndianBig)) { + gbString xts = type_to_string(x.type); + error(x.expr, "Expected an integer which does not specify the explicit endianness for '%.*s', got %s", LIT(builtin_name), xts); + gb_string_free(xts); + return false; + } + } + + operand->mode = Addressing_Value; + operand->type = make_optional_ok_type(default_type(x.type)); + } + break; + case BuiltinProc_add_sat: case BuiltinProc_sub_sat: { @@ -4300,7 +4341,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } operand->mode = Addressing_Value; - operand->type = make_optional_ok_type(default_type(x.type)); + operand->type = default_type(x.type); } break; diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index bdc381bc4..e26b2e50f 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2236,8 +2236,6 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu case BuiltinProc_overflow_add: case BuiltinProc_overflow_sub: case BuiltinProc_overflow_mul: - case BuiltinProc_add_sat: - case BuiltinProc_sub_sat: { Type *main_type = tv.type; Type *type = main_type; @@ -2256,16 +2254,12 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu case BuiltinProc_overflow_add: name = "llvm.uadd.with.overflow"; break; case BuiltinProc_overflow_sub: name = "llvm.usub.with.overflow"; break; case BuiltinProc_overflow_mul: name = "llvm.umul.with.overflow"; break; - case BuiltinProc_add_sat: name = "llvm.uadd.sat"; break; - case BuiltinProc_sub_sat: name = "llvm.usub.sat"; break; } } else { switch (id) { case BuiltinProc_overflow_add: name = "llvm.sadd.with.overflow"; break; case BuiltinProc_overflow_sub: name = "llvm.ssub.with.overflow"; break; case BuiltinProc_overflow_mul: name = "llvm.smul.with.overflow"; break; - case BuiltinProc_add_sat: name = "llvm.sadd.sat"; break; - case BuiltinProc_sub_sat: name = "llvm.ssub.sat"; break; } } LLVMTypeRef types[1] = {lb_type(p->module, type)}; @@ -2291,6 +2285,42 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu return res; } + case BuiltinProc_add_sat: + case BuiltinProc_sub_sat: + { + Type *main_type = tv.type; + Type *type = main_type; + if (is_type_tuple(main_type)) { + type = main_type->Tuple.variables[0]->type; + } + + lbValue x = lb_build_expr(p, ce->args[0]); + lbValue y = lb_build_expr(p, ce->args[1]); + x = lb_emit_conv(p, x, type); + y = lb_emit_conv(p, y, type); + + char const *name = nullptr; + if (is_type_unsigned(type)) { + switch (id) { + case BuiltinProc_add_sat: name = "llvm.uadd.sat"; break; + case BuiltinProc_sub_sat: name = "llvm.usub.sat"; break; + } + } else { + switch (id) { + case BuiltinProc_add_sat: name = "llvm.sadd.sat"; break; + case BuiltinProc_sub_sat: name = "llvm.ssub.sat"; break; + } + } + LLVMTypeRef types[1] = {lb_type(p->module, type)}; + + LLVMValueRef args[2] = { x.value, y.value }; + + lbValue res = {}; + res.value = lb_call_intrinsic(p, name, args, gb_count_of(args), types, gb_count_of(types)); + res.type = type; + return res; + } + case BuiltinProc_sqrt: { Type *type = tv.type; From 47f14dd9ea2a9ce0df263f8a3fe2e6ccf5b53751 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 16 Jul 2024 22:11:54 +0200 Subject: [PATCH 128/198] type is never a tuple here --- src/llvm_backend_proc.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index e26b2e50f..2f736ff6c 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2290,9 +2290,6 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu { Type *main_type = tv.type; Type *type = main_type; - if (is_type_tuple(main_type)) { - type = main_type->Tuple.variables[0]->type; - } lbValue x = lb_build_expr(p, ce->args[0]); lbValue y = lb_build_expr(p, ce->args[1]); From a6d1a2e46cb38df2583ba1d14be2516f16bba655 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 16 Jul 2024 22:22:06 +0200 Subject: [PATCH 129/198] add `#optional_ok` to docs file for intrinsics --- base/intrinsics/intrinsics.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index 047373cda..37a42b904 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -38,9 +38,9 @@ count_leading_zeros :: proc(x: $T) -> T where type_is_integer(T) || type_is_sim reverse_bits :: proc(x: $T) -> T where type_is_integer(T) || type_is_simd_vector(T) --- byte_swap :: proc(x: $T) -> T where type_is_integer(T) || type_is_float(T) --- -overflow_add :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) --- -overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) --- -overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) --- +overflow_add :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #optional_ok --- +overflow_sub :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #optional_ok --- +overflow_mul :: proc(lhs, rhs: $T) -> (T, bool) where type_is_integer(T) #optional_ok --- add_sat :: proc(lhs, rhs: $T) -> T where type_is_integer(T) --- sub_sat :: proc(lhs, rhs: $T) -> T where type_is_integer(T) --- From 5520b454576178c9dac5dc7bfffa640c18dade54 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 16 Jul 2024 22:59:32 +0200 Subject: [PATCH 130/198] Add glfw LICENSE file --- vendor/glfw/LICENSE.txt | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 vendor/glfw/LICENSE.txt diff --git a/vendor/glfw/LICENSE.txt b/vendor/glfw/LICENSE.txt new file mode 100644 index 000000000..b8c096845 --- /dev/null +++ b/vendor/glfw/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2002-2006 Marcus Geelnard + +Copyright (c) 2006-2019 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. From 07121f81ff5dfc9fa5aaadd2932024daf93d3c8c Mon Sep 17 00:00:00 2001 From: IllusionMan1212 Date: Wed, 17 Jul 2024 00:32:25 +0200 Subject: [PATCH 131/198] core/sys/windows: added drag and drop procedures --- core/sys/windows/shell32.odin | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/sys/windows/shell32.odin b/core/sys/windows/shell32.odin index 25923ded3..6831f4339 100644 --- a/core/sys/windows/shell32.odin +++ b/core/sys/windows/shell32.odin @@ -30,6 +30,11 @@ foreign shell32 { SHGetKnownFolderIDList :: proc(rfid: REFKNOWNFOLDERID, dwFlags: /* KNOWN_FOLDER_FLAG */ DWORD, hToken: HANDLE, ppidl: rawptr) -> HRESULT --- SHSetKnownFolderPath :: proc(rfid: REFKNOWNFOLDERID, dwFlags: /* KNOWN_FOLDER_FLAG */ DWORD, hToken: HANDLE, pszPath: PCWSTR ) -> HRESULT --- SHGetKnownFolderPath :: proc(rfid: REFKNOWNFOLDERID, dwFlags: /* KNOWN_FOLDER_FLAG */ DWORD, hToken: HANDLE, ppszPath: ^LPWSTR) -> HRESULT --- + + DragAcceptFiles :: proc(hWnd: HWND, fAccept: BOOL) --- + DragQueryPoint :: proc(hDrop: HDROP, ppt: ^POINT) -> BOOL --- + DragQueryFileW :: proc(hDrop: HDROP, iFile: UINT, lpszFile: LPWSTR, cch: UINT) -> UINT --- + DragFinish :: proc(hDrop: HDROP) --- // @New } APPBARDATA :: struct { @@ -67,6 +72,8 @@ ABE_BOTTOM :: 3 KNOWNFOLDERID :: GUID REFKNOWNFOLDERID :: ^KNOWNFOLDERID +HDROP :: HANDLE + KNOWN_FOLDER_FLAG :: enum u32 { DEFAULT = 0x00000000, From f04db7145cf2bbd58c05861a0e8a9a601697fed9 Mon Sep 17 00:00:00 2001 From: Ronald Date: Wed, 17 Jul 2024 21:19:14 +0100 Subject: [PATCH 132/198] Fix memory leak in encoding/ini A simple change that fixes a memory leak caused by not deleting all the values in the map --- core/encoding/ini/ini.odin | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/encoding/ini/ini.odin b/core/encoding/ini/ini.odin index 6723da2b3..2af13efd9 100644 --- a/core/encoding/ini/ini.odin +++ b/core/encoding/ini/ini.odin @@ -92,7 +92,6 @@ load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options } } return strings.clone(val) - } context.allocator = allocator @@ -114,7 +113,10 @@ load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options new_key = strings.to_lower(key) or_return delete(old_key) or_return } - pairs[new_key] = unquote(value) or_return + pairs[new_key], err = unquote(value) + if err != nil { + return + } } return } @@ -144,6 +146,7 @@ delete_map :: proc(m: Map) { delete(value, allocator) } delete(section) + delete(pairs) } delete(m) } From c768d0719ab924db72331e36cedee0600b129ef0 Mon Sep 17 00:00:00 2001 From: Ronald Date: Wed, 17 Jul 2024 21:57:35 +0100 Subject: [PATCH 133/198] Remove unnecessary change This was accidentally added, it was a change I made whilst testing. --- core/encoding/ini/ini.odin | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/encoding/ini/ini.odin b/core/encoding/ini/ini.odin index 2af13efd9..d0dd33aba 100644 --- a/core/encoding/ini/ini.odin +++ b/core/encoding/ini/ini.odin @@ -113,10 +113,7 @@ load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options new_key = strings.to_lower(key) or_return delete(old_key) or_return } - pairs[new_key], err = unquote(value) - if err != nil { - return - } + pairs[new_key], err = unquote(value) or_return } return } From 4dcb75af6d5bed82038c4ad3e16eaa17bb8d5237 Mon Sep 17 00:00:00 2001 From: flysand7 Date: Thu, 18 Jul 2024 00:34:53 +1100 Subject: [PATCH 134/198] Make all handles non-inheritable by default The sockets are left as non-inheritable because they never should be inherited. --- core/net/socket_linux.odin | 6 +++--- core/os/os2/file.odin | 12 ++++++++++-- core/os/os2/file_linux.odin | 4 ++-- core/os/os2/file_windows.odin | 2 +- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin index a5d553234..350d3947c 100644 --- a/core/net/socket_linux.odin +++ b/core/net/socket_linux.odin @@ -117,7 +117,7 @@ _wrap_os_addr :: proc "contextless" (addr: linux.Sock_Addr_Any)->(Endpoint) { _create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (Any_Socket, Network_Error) { family := _unwrap_os_family(family) proto, socktype := _unwrap_os_proto_socktype(protocol) - sock, errno := linux.socket(family, socktype, {}, proto) + sock, errno := linux.socket(family, socktype, {.CLOEXEC}, proto) if errno != .NONE { return {}, Create_Socket_Error(errno) } @@ -132,7 +132,7 @@ _dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_optio } // Create new TCP socket os_sock: linux.Fd - os_sock, errno = linux.socket(_unwrap_os_family(family_from_endpoint(endpoint)), .STREAM, {}, .TCP) + os_sock, errno = linux.socket(_unwrap_os_family(family_from_endpoint(endpoint)), .STREAM, {.CLOEXEC}, .TCP) if errno != .NONE { // TODO(flysand): should return invalid file descriptor here casted as TCP_Socket return {}, Create_Socket_Error(errno) @@ -172,7 +172,7 @@ _listen_tcp :: proc(endpoint: Endpoint, backlog := 1000) -> (TCP_Socket, Network ep_address := _unwrap_os_addr(endpoint) // Create TCP socket os_sock: linux.Fd - os_sock, errno = linux.socket(ep_family, .STREAM, {}, .TCP) + os_sock, errno = linux.socket(ep_family, .STREAM, {.CLOEXEC}, .TCP) if errno != .NONE { // TODO(flysand): should return invalid file descriptor here casted as TCP_Socket return {}, Create_Socket_Error(errno) diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index be03155ff..978e64fb4 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -29,7 +29,7 @@ File_Flag :: enum { Sync, Trunc, Sparse, - Close_On_Exec, + Inheritable, Unbuffered_IO, } @@ -43,7 +43,15 @@ O_EXCL :: File_Flags{.Excl} O_SYNC :: File_Flags{.Sync} O_TRUNC :: File_Flags{.Trunc} O_SPARSE :: File_Flags{.Sparse} -O_CLOEXEC :: File_Flags{.Close_On_Exec} + +/* + If specified, the file handle is inherited upon the creation of a child + process. By default all handles are created non-inheritable. + + **Note**: The standard file handles (stderr, stdout and stdin) are always + initialized as inheritable. +*/ +O_INHERITABLE :: File_Flags{.Inheritable} stdin: ^File = nil // OS-Specific stdout: ^File = nil // OS-Specific diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 482c9b5b4..7828e5fd9 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -70,7 +70,7 @@ _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, er // Just default to using O_NOCTTY because needing to open a controlling // terminal would be incredibly rare. This has no effect on files while // allowing us to open serial devices. - sys_flags: linux.Open_Flags = {.NOCTTY} + sys_flags: linux.Open_Flags = {.NOCTTY, .CLOEXEC} switch flags & O_RDONLY|O_WRONLY|O_RDWR { case O_RDONLY: case O_WRONLY: sys_flags += {.WRONLY} @@ -82,7 +82,7 @@ _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, er if .Excl in flags { sys_flags += {.EXCL} } if .Sync in flags { sys_flags += {.DSYNC} } if .Trunc in flags { sys_flags += {.TRUNC} } - if .Close_On_Exec in flags { sys_flags += {.CLOEXEC} } + if .Inheritable in flags { sys_flags -= {.CLOEXEC} } fd, errno := linux.open(name_cstr, sys_flags, transmute(linux.Mode)(u32(perm))) if errno != .NONE { diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index b11e7745f..5ce081e3a 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -79,7 +79,7 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han share_mode := u32(win32.FILE_SHARE_READ | win32.FILE_SHARE_WRITE) sa := win32.SECURITY_ATTRIBUTES { nLength = size_of(win32.SECURITY_ATTRIBUTES), - bInheritHandle = .Close_On_Exec not_in flags, + bInheritHandle = .Inheritable in flags, } create_mode: u32 = win32.OPEN_EXISTING From 75605a47e716fb46eabbccc737f6860f0cb40910 Mon Sep 17 00:00:00 2001 From: flysand7 Date: Thu, 18 Jul 2024 22:57:21 +1100 Subject: [PATCH 135/198] [os2]: Make anonymous pipes always inheritable --- core/os/os2/pipe_linux.odin | 2 +- core/os/os2/pipe_windows.odin | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/core/os/os2/pipe_linux.odin b/core/os/os2/pipe_linux.odin index 5d42cca78..8835cc30f 100644 --- a/core/os/os2/pipe_linux.odin +++ b/core/os/os2/pipe_linux.odin @@ -5,7 +5,7 @@ import "core:sys/linux" _pipe :: proc() -> (r, w: ^File, err: Error) { fds: [2]linux.Fd - errno := linux.pipe2(&fds, {.CLOEXEC}) + errno := linux.pipe2(&fds, {}) if errno != .NONE { return nil, nil,_get_platform_error(errno) } diff --git a/core/os/os2/pipe_windows.odin b/core/os/os2/pipe_windows.odin index bab8b44f5..59615e306 100644 --- a/core/os/os2/pipe_windows.odin +++ b/core/os/os2/pipe_windows.odin @@ -5,7 +5,11 @@ import win32 "core:sys/windows" _pipe :: proc() -> (r, w: ^File, err: Error) { p: [2]win32.HANDLE - if !win32.CreatePipe(&p[0], &p[1], nil, 0) { + sa := win32.SECURITY_ATTRIBUTES { + nLength = size_of(win32.SECURITY_ATTRIBUTES), + bInheritHandle = true, + } + if !win32.CreatePipe(&p[0], &p[1], &sa, 0) { return nil, nil, _get_platform_error() } return new_file(uintptr(p[0]), ""), new_file(uintptr(p[1]), ""), nil From 7b501b22bb5837d9c30379a973b149aba89a1a60 Mon Sep 17 00:00:00 2001 From: flysand7 Date: Thu, 18 Jul 2024 22:36:39 +1100 Subject: [PATCH 136/198] [os2]: Split file type from mode bits --- core/os/os2/file.odin | 76 +++++++++++++++++++++++++++------ core/os/os2/file_linux.odin | 9 ++-- core/os/os2/file_util.odin | 2 +- core/os/os2/file_windows.odin | 11 ++--- core/os/os2/path.odin | 9 ++-- core/os/os2/path_linux.odin | 22 +++------- core/os/os2/path_windows.odin | 16 +++---- core/os/os2/stat.odin | 5 ++- core/os/os2/stat_linux.odin | 17 ++++++-- core/os/os2/stat_windows.odin | 80 ++++++++++++----------------------- core/os/os2/temp_file.odin | 2 +- core/sys/linux/bits.odin | 2 +- core/sys/linux/constants.odin | 6 +-- 13 files changed, 137 insertions(+), 120 deletions(-) diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index be03155ff..c9490044b 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -4,20 +4,57 @@ import "core:io" import "core:time" import "base:runtime" +/* + Type representing a file handle. + + This struct represents an OS-specific file-handle, which can be one of + the following: + - File + - Directory + - Pipe + - Named pipe + - Block Device + - Character device + - Symlink + - Socket + + See `File_Type` enum for more information on file types. +*/ File :: struct { impl: rawptr, stream: io.Stream, fstat: Fstat_Callback, } -File_Mode :: distinct u32 -File_Mode_Dir :: File_Mode(1<<16) -File_Mode_Named_Pipe :: File_Mode(1<<17) -File_Mode_Device :: File_Mode(1<<18) -File_Mode_Char_Device :: File_Mode(1<<19) -File_Mode_Sym_Link :: File_Mode(1<<20) +/* + Type representing the type of a file handle. -File_Mode_Perm :: File_Mode(0o777) // Unix permision bits + **Note(windows)**: Socket handles can not be distinguished from + files, as they are just a normal file handle that is being treated by + a special driver. Windows also makes no distinction between block and + character devices. +*/ +File_Type :: enum { + // The type of a file could not be determined for the current platform. + Undetermined, + // Represents a regular file. + Regular, + // Represents a directory. + Directory, + // Represents a symbolic link. + Symlink, + // Represents a named pipe (FIFO). + Named_Pipe, + // Represents a socket. + // **Note(windows)**: Not returned on windows + Socket, + // Represents a block device. + // **Note(windows)**: On windows represents all devices. + Block_Device, + // Represents a character device. + // **Note(windows)**: Not returned on windows + Character_Device, +} File_Flags :: distinct bit_set[File_Flag; uint] File_Flag :: enum { @@ -51,11 +88,11 @@ stderr: ^File = nil // OS-Specific @(require_results) create :: proc(name: string) -> (^File, Error) { - return open(name, {.Read, .Write, .Create}, File_Mode(0o777)) + return open(name, {.Read, .Write, .Create}, 0o777) } @(require_results) -open :: proc(name: string, flags := File_Flags{.Read}, perm := File_Mode(0o777)) -> (^File, Error) { +open :: proc(name: string, flags := File_Flags{.Read}, perm := 0o777) -> (^File, Error) { return _open(name, flags, perm) } @@ -161,44 +198,56 @@ read_link :: proc(name: string, allocator: runtime.Allocator) -> (string, Error) chdir :: change_directory + change_directory :: proc(name: string) -> Error { return _chdir(name) } chmod :: change_mode -change_mode :: proc(name: string, mode: File_Mode) -> Error { + +change_mode :: proc(name: string, mode: int) -> Error { return _chmod(name, mode) } + chown :: change_owner + change_owner :: proc(name: string, uid, gid: int) -> Error { return _chown(name, uid, gid) } fchdir :: fchange_directory + fchange_directory :: proc(f: ^File) -> Error { return _fchdir(f) } + fchmod :: fchange_mode -fchange_mode :: proc(f: ^File, mode: File_Mode) -> Error { + +fchange_mode :: proc(f: ^File, mode: int) -> Error { return _fchmod(f, mode) } fchown :: fchange_owner + fchange_owner :: proc(f: ^File, uid, gid: int) -> Error { return _fchown(f, uid, gid) } lchown :: change_owner_do_not_follow_links + change_owner_do_not_follow_links :: proc(name: string, uid, gid: int) -> Error { return _lchown(name, uid, gid) } chtimes :: change_times + change_times :: proc(name: string, atime, mtime: time.Time) -> Error { return _chtimes(name, atime, mtime) } + fchtimes :: fchange_times + fchange_times :: proc(f: ^File, atime, mtime: time.Time) -> Error { return _fchtimes(f, atime, mtime) } @@ -214,6 +263,7 @@ is_file :: proc(path: string) -> bool { } is_dir :: is_directory + @(require_results) is_directory :: proc(path: string) -> bool { return _is_dir(path) @@ -226,11 +276,11 @@ copy_file :: proc(dst_path, src_path: string) -> Error { info := fstat(src, file_allocator()) or_return defer file_info_delete(info, file_allocator()) - if info.is_directory { + if info.type == .Directory { return .Invalid_File } - dst := open(dst_path, {.Read, .Write, .Create, .Trunc}, info.mode & File_Mode_Perm) or_return + dst := open(dst_path, {.Read, .Write, .Create, .Trunc}, info.mode & 0o777) or_return defer close(dst) _, err := io.copy(to_writer(dst), to_reader(src)) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index 482c9b5b4..f3b55a0f7 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -63,7 +63,7 @@ _file_allocator :: proc() -> runtime.Allocator { return heap_allocator() } -_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, err: Error) { +_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) { TEMP_ALLOCATOR_GUARD() name_cstr := temp_cstring(name) or_return @@ -76,7 +76,6 @@ _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, er case O_WRONLY: sys_flags += {.WRONLY} case O_RDWR: sys_flags += {.RDWR} } - if .Append in flags { sys_flags += {.APPEND} } if .Create in flags { sys_flags += {.CREAT} } if .Excl in flags { sys_flags += {.EXCL} } @@ -84,7 +83,7 @@ _open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, er if .Trunc in flags { sys_flags += {.TRUNC} } if .Close_On_Exec in flags { sys_flags += {.CLOEXEC} } - fd, errno := linux.open(name_cstr, sys_flags, transmute(linux.Mode)(u32(perm))) + fd, errno := linux.open(name_cstr, sys_flags, transmute(linux.Mode)u32(perm)) if errno != .NONE { return nil, _get_platform_error(errno) } @@ -296,13 +295,13 @@ _fchdir :: proc(f: ^File) -> Error { return _get_platform_error(linux.fchdir(impl.fd)) } -_chmod :: proc(name: string, mode: File_Mode) -> Error { +_chmod :: proc(name: string, mode: int) -> Error { TEMP_ALLOCATOR_GUARD() name_cstr := temp_cstring(name) or_return return _get_platform_error(linux.chmod(name_cstr, transmute(linux.Mode)(u32(mode)))) } -_fchmod :: proc(f: ^File, mode: File_Mode) -> Error { +_fchmod :: proc(f: ^File, mode: int) -> Error { impl := (^File_Impl)(f.impl) return _get_platform_error(linux.fchmod(impl.fd, transmute(linux.Mode)(u32(mode)))) } diff --git a/core/os/os2/file_util.odin b/core/os/os2/file_util.odin index 977979bae..ca0e8c940 100644 --- a/core/os/os2/file_util.odin +++ b/core/os/os2/file_util.odin @@ -138,7 +138,7 @@ read_entire_file_from_file :: proc(f: ^File, allocator: runtime.Allocator) -> (d } @(require_results) -write_entire_file :: proc(name: string, data: []byte, perm: File_Mode, truncate := true) -> Error { +write_entire_file :: proc(name: string, data: []byte, perm: int, truncate := true) -> Error { flags := O_WRONLY|O_CREATE if truncate { flags |= O_TRUNC diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index b11e7745f..a441abbaa 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -55,7 +55,7 @@ _handle :: proc(f: ^File) -> win32.HANDLE { return win32.HANDLE(_fd(f)) } -_open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (handle: uintptr, err: Error) { +_open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: uintptr, err: Error) { if len(name) == 0 { err = .Not_Exist return @@ -122,7 +122,7 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (han } -_open :: proc(name: string, flags: File_Flags, perm: File_Mode) -> (f: ^File, err: Error) { +_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) { flags := flags if flags != nil else {.Read} handle := _open_internal(name, flags, perm) or_return return _new_file(handle, name), nil @@ -498,7 +498,6 @@ _rename :: proc(old_path, new_path: string) -> Error { } - _link :: proc(old_name, new_name: string) -> Error { o := _fix_long_path(old_name) n := _fix_long_path(new_name) @@ -635,7 +634,7 @@ _fchdir :: proc(f: ^File) -> Error { return nil } -_fchmod :: proc(f: ^File, mode: File_Mode) -> Error { +_fchmod :: proc(f: ^File, mode: int) -> Error { if f == nil || f.impl == nil { return nil } @@ -670,7 +669,7 @@ _chdir :: proc(name: string) -> Error { return nil } -_chmod :: proc(name: string, mode: File_Mode) -> Error { +_chmod :: proc(name: string, mode: int) -> Error { f := open(name, {.Write}) or_return defer close(f) return _fchmod(f, mode) @@ -718,8 +717,6 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { return nil } - - _exists :: proc(path: string) -> bool { wpath := _fix_long_path(path) attribs := win32.GetFileAttributesW(wpath) diff --git a/core/os/os2/path.odin b/core/os/os2/path.odin index 27c3d6b0b..f3c662a70 100644 --- a/core/os/os2/path.odin +++ b/core/os/os2/path.odin @@ -12,12 +12,14 @@ is_path_separator :: proc(c: byte) -> bool { } mkdir :: make_directory -make_directory :: proc(name: string, perm: File_Mode) -> Error { + +make_directory :: proc(name: string, perm: int) -> Error { return _mkdir(name, perm) } mkdir_all :: make_directory_all -make_directory_all :: proc(path: string, perm: File_Mode) -> Error { + +make_directory_all :: proc(path: string, perm: int) -> Error { return _mkdir_all(path, perm) } @@ -25,14 +27,15 @@ remove_all :: proc(path: string) -> Error { return _remove_all(path) } - getwd :: get_working_directory + @(require_results) get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { return _getwd(allocator) } setwd :: set_working_directory + set_working_directory :: proc(dir: string) -> (err: Error) { return _setwd(dir) } diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index 3c08eedee..80d4d42d1 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -15,19 +15,13 @@ _is_path_separator :: proc(c: byte) -> bool { return c == '/' } -_mkdir :: proc(path: string, perm: File_Mode) -> Error { - // TODO: These modes would require mknod, however, that would also - // require additional arguments to this function.. - if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 { - return .Invalid_Argument - } - +_mkdir :: proc(path: string, perm: int) -> Error { TEMP_ALLOCATOR_GUARD() path_cstr := temp_cstring(path) or_return - return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)(u32(perm) & 0o777))) + return _get_platform_error(linux.mkdir(path_cstr, transmute(linux.Mode)u32(perm))) } -_mkdir_all :: proc(path: string, perm: File_Mode) -> Error { +_mkdir_all :: proc(path: string, perm: int) -> Error { mkdirat :: proc(dfd: linux.Fd, path: []u8, perm: int, has_created: ^bool) -> Error { i: int for ; i < len(path) - 1 && path[i] != '/'; i += 1 {} @@ -38,7 +32,7 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { new_dfd, errno := linux.openat(dfd, cstring(&path[0]), _OPENDIR_FLAGS) #partial switch errno { case .ENOENT: - if errno = linux.mkdirat(dfd, cstring(&path[0]), transmute(linux.Mode)(u32(perm))); errno != .NONE { + if errno = linux.mkdirat(dfd, cstring(&path[0]), transmute(linux.Mode)u32(perm)); errno != .NONE { return _get_platform_error(errno) } has_created^ = true @@ -58,12 +52,6 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { } unreachable() } - - // TODO - if perm & (File_Mode_Named_Pipe | File_Mode_Device | File_Mode_Char_Device | File_Mode_Sym_Link) != 0 { - return .Invalid_Argument - } - TEMP_ALLOCATOR_GUARD() // need something we can edit, and use to generate cstrings path_bytes := make([]u8, len(path) + 1, temp_allocator()) @@ -85,7 +73,7 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { } has_created: bool - mkdirat(dfd, path_bytes, int(perm & 0o777), &has_created) or_return + mkdirat(dfd, path_bytes, perm, &has_created) or_return if has_created { return nil } diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index fcd1e3321..ab680cc4c 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -12,14 +12,14 @@ _is_path_separator :: proc(c: byte) -> bool { return c == '\\' || c == '/' } -_mkdir :: proc(name: string, perm: File_Mode) -> Error { +_mkdir :: proc(name: string, perm: int) -> Error { if !win32.CreateDirectoryW(_fix_long_path(name), nil) { return _get_platform_error() } return nil } -_mkdir_all :: proc(path: string, perm: File_Mode) -> Error { +_mkdir_all :: proc(path: string, perm: int) -> Error { fix_root_directory :: proc(p: string) -> (s: string, allocated: bool, err: runtime.Allocator_Error) { if len(p) == len(`\\?\c:`) { if is_path_separator(p[0]) && is_path_separator(p[1]) && p[2] == '?' && is_path_separator(p[3]) && p[5] == ':' { @@ -33,9 +33,9 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { TEMP_ALLOCATOR_GUARD() - dir, err := stat(path, temp_allocator()) + dir_stat, err := stat(path, temp_allocator()) if err == nil { - if dir.is_directory { + if dir_stat.type == .Directory { return nil } return .Exist @@ -61,8 +61,8 @@ _mkdir_all :: proc(path: string, perm: File_Mode) -> Error { err = mkdir(path, perm) if err != nil { - dir1, err1 := lstat(path, temp_allocator()) - if err1 == nil && dir1.is_directory { + new_dir_stat, err1 := lstat(path, temp_allocator()) + if err1 == nil && new_dir_stat.type == .Directory { return nil } return err @@ -85,7 +85,6 @@ _setwd :: proc(dir: string) -> (err: Error) { return nil } - can_use_long_paths: bool @(init) @@ -96,7 +95,6 @@ init_long_path_support :: proc() { can_use_long_paths = false } - _fix_long_path_slice :: proc(path: string) -> []u16 { return win32.utf8_to_utf16(_fix_long_path_internal(path)) } @@ -105,7 +103,6 @@ _fix_long_path :: proc(path: string) -> win32.wstring { return win32.utf8_to_wstring(_fix_long_path_internal(path)) } - _fix_long_path_internal :: proc(path: string) -> string { if can_use_long_paths { return path @@ -162,5 +159,4 @@ _fix_long_path_internal :: proc(path: string) -> string { } return string(path_buf[:w]) - } diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin index 1e7f9d225..fad33da0a 100644 --- a/core/os/os2/stat.odin +++ b/core/os/os2/stat.odin @@ -9,8 +9,8 @@ File_Info :: struct { fullpath: string, name: string, size: i64, - mode: File_Mode, - is_directory: bool, + mode: int, + type: File_Type, creation_time: time.Time, modification_time: time.Time, access_time: time.Time, @@ -43,6 +43,7 @@ stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { } lstat :: stat_do_not_follow_links + @(require_results) stat_do_not_follow_links :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { return _lstat(name, allocator) diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index ee4011f96..f194524a7 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -17,20 +17,29 @@ _fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (File_Inf if errno != .NONE { return {}, _get_platform_error(errno) } - + type := File_Type.Regular + switch s.mode & linux.S_IFMT { + case linux.S_IFBLK: type = .Block_Device + case linux.S_IFCHR: type = .Character_Device + case linux.S_IFDIR: type = .Directory + case linux.S_IFIFO: type = .Named_Pipe + case linux.S_IFLNK: type = .Symlink + case linux.S_IFREG: type = .Regular + case linux.S_IFSOCK: type = .Socket + } + mode := int(s.mode) & 0o7777 // TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time fi := File_Info { fullpath = _get_full_path(fd, allocator), name = "", size = i64(s.size), - mode = 0, - is_directory = linux.S_ISDIR(s.mode), + mode = mode, + type = type, modification_time = time.Time {i64(s.mtime.time_sec) * i64(time.Second) + i64(s.mtime.time_nsec)}, access_time = time.Time {i64(s.atime.time_sec) * i64(time.Second) + i64(s.atime.time_nsec)}, creation_time = time.Time{i64(s.ctime.time_sec) * i64(time.Second) + i64(s.ctime.time_nsec)}, // regular stat does not provide this } fi.creation_time = fi.modification_time - fi.name = filepath.base(fi.fullpath) return fi, nil } diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index 0a18433c1..8915d187c 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -19,28 +19,29 @@ _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { h := _handle(f) 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) + fi := File_Info { + fullpath = path, + name = basename(path), + type = file_type(h), + } return fi, nil } return _file_info_from_get_file_information_by_handle(path, h, allocator) } + _stat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS, allocator) } + _lstat :: proc(name: string, allocator: runtime.Allocator) -> (File_Info, Error) { return internal_stat(name, win32.FILE_FLAG_BACKUP_SEMANTICS|win32.FILE_FLAG_OPEN_REPARSE_POINT, allocator) } + _same_file :: proc(fi1, fi2: File_Info) -> bool { return fi1.fullpath == fi2.fullpath } - - - full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path: string, err: Error) { name := name if name == "" { @@ -62,7 +63,6 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path return win32.utf16_to_utf8(buf[:n], allocator) } - internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { if len(name) == 0 { return {}, .Not_Exist @@ -99,7 +99,6 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt return _file_info_from_get_file_information_by_handle(name, h, allocator) } - _cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { buf := buf N := 0 @@ -120,7 +119,6 @@ _cleanpath_strip_prefix :: proc(buf: []u16) -> []u16 { return buf } - _cleanpath_from_handle :: proc(f: ^File, allocator: runtime.Allocator) -> (string, Error) { if f == nil { return "", nil @@ -159,7 +157,6 @@ _cleanpath_from_buf :: proc(buf: []u16, allocator: runtime.Allocator) -> (string return win32.utf16_to_utf8(buf, allocator) } - basename :: proc(name: string) -> (base: string) { name := name if len(name) > 3 && name[:3] == `\\?` { @@ -185,83 +182,67 @@ basename :: proc(name: string) -> (base: string) { return name } - -file_type_mode :: proc(h: win32.HANDLE) -> File_Mode { +file_type :: proc(h: win32.HANDLE) -> File_Type { 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 + case win32.FILE_TYPE_PIPE: return .Named_Pipe + case win32.FILE_TYPE_CHAR: return .Character_Device + case win32.FILE_TYPE_DISK: return .Regular } - return 0 + return .Undetermined } - - -_file_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (mode: File_Mode) { +_file_type_mode_from_file_attributes :: proc(file_attributes: win32.DWORD, h: win32.HANDLE, ReparseTag: win32.DWORD) -> (type: File_Type, mode: int) { if file_attributes & win32.FILE_ATTRIBUTE_READONLY != 0 { mode |= 0o444 } else { mode |= 0o666 } - is_sym := false if file_attributes & 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 + type = .Symlink } else { if file_attributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { - mode |= 0o111 | File_Mode_Dir + type = .Directory + mode |= 0o111 } - if h != nil { - mode |= file_type_mode(h) + type = file_type(h) } } - return } - _file_info_from_win32_file_attribute_data :: proc(d: ^win32.WIN32_FILE_ATTRIBUTE_DATA, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) - - fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) - fi.is_directory = fi.mode & File_Mode_Dir != 0 - + type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0) + fi.type = type + fi.mode |= mode fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) - fi.fullpath, e = full_path_from_name(name, allocator) fi.name = basename(fi.fullpath) - return } - _file_info_from_win32_find_data :: proc(d: ^win32.WIN32_FIND_DATAW, name: string, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) - - fi.mode |= _file_mode_from_file_attributes(d.dwFileAttributes, nil, 0) - fi.is_directory = fi.mode & File_Mode_Dir != 0 - + type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0) + fi.type = type + fi.mode |= mode fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) - fi.fullpath, e = full_path_from_name(name, allocator) fi.name = basename(fi.fullpath) - return } - _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HANDLE, allocator: runtime.Allocator) -> (File_Info, Error) { d: win32.BY_HANDLE_FILE_INFORMATION if !win32.GetFileInformationByHandle(h, &d) { @@ -278,25 +259,19 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA // 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_directory = fi.mode & File_Mode_Dir != 0 - + type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0) + fi.type = type + fi.mode |= mode fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) - return fi, nil } - - reserved_names := [?]string{ "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", @@ -357,7 +332,6 @@ _volume_name_len :: proc(path: string) -> int { return 0 } - _is_abs :: proc(path: string) -> bool { if _is_reserved_name(path) { return true diff --git a/core/os/os2/temp_file.odin b/core/os/os2/temp_file.odin index 3b3dbdd57..467775e89 100644 --- a/core/os/os2/temp_file.odin +++ b/core/os/os2/temp_file.odin @@ -26,7 +26,7 @@ create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) { attempts := 0 for { name := concatenate_strings_from_buffer(name_buf[:], prefix, random_string(rand_buf[:]), suffix) - f, err = open(name, {.Read, .Write, .Create, .Excl}, File_Mode(0o666)) + f, err = open(name, {.Read, .Write, .Create, .Excl}, 0o666) if err == .Exist { close(f) attempts += 1 diff --git a/core/sys/linux/bits.odin b/core/sys/linux/bits.odin index 1e9e5bbbd..96c3de18d 100644 --- a/core/sys/linux/bits.odin +++ b/core/sys/linux/bits.odin @@ -244,7 +244,7 @@ Mode_Bits :: enum { ISVTX = 9, // 0o0001000 ISGID = 10, // 0o0002000 ISUID = 11, // 0o0004000 - IFFIFO = 12, // 0o0010000 + IFIFO = 12, // 0o0010000 IFCHR = 13, // 0o0020000 IFDIR = 14, // 0o0040000 IFREG = 15, // 0o0100000 diff --git a/core/sys/linux/constants.odin b/core/sys/linux/constants.odin index 51f7db68f..f3e9f5ff9 100644 --- a/core/sys/linux/constants.odin +++ b/core/sys/linux/constants.odin @@ -39,11 +39,11 @@ PRIO_MIN :: -20 SIGRTMIN :: Signal(32) SIGRTMAX :: Signal(64) -S_IFMT :: Mode{.IFREG, .IFDIR, .IFCHR, .IFFIFO} +S_IFMT :: Mode{.IFREG, .IFDIR, .IFCHR, .IFIFO} S_IFSOCK :: Mode{.IFREG, .IFDIR} S_IFLNK :: Mode{.IFREG, .IFCHR} S_IFBLK :: Mode{.IFDIR, .IFCHR} -S_IFFIFO :: Mode{.IFFIFO} +S_IFIFO :: Mode{.IFIFO} S_IFCHR :: Mode{.IFCHR} S_IFDIR :: Mode{.IFDIR} S_IFREG :: Mode{.IFREG} @@ -51,7 +51,7 @@ S_IFREG :: Mode{.IFREG} /* Checks the Mode bits to see if the file is a named pipe (FIFO). */ -S_ISFIFO :: #force_inline proc "contextless" (m: Mode) -> bool {return (S_IFFIFO == (m & S_IFMT))} +S_ISFIFO :: #force_inline proc "contextless" (m: Mode) -> bool {return (S_IFIFO == (m & S_IFMT))} /* Check the Mode bits to see if the file is a character device. From 7134015f56308a1634374aafcda62554811649e3 Mon Sep 17 00:00:00 2001 From: Laytan Date: Thu, 18 Jul 2024 19:28:15 +0200 Subject: [PATCH 137/198] improve WGPU / GLFW / Wayland story by weak linking and adjusting docs --- vendor/glfw/bindings/bindings.odin | 5 +++++ vendor/glfw/native_linux.odin | 6 ++++++ vendor/wgpu/README.md | 12 +++++++++--- vendor/wgpu/glfwglue/glue_linux.odin | 9 +++++---- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/vendor/glfw/bindings/bindings.odin b/vendor/glfw/bindings/bindings.odin index 164a8ea2d..81569f177 100644 --- a/vendor/glfw/bindings/bindings.odin +++ b/vendor/glfw/bindings/bindings.odin @@ -197,7 +197,12 @@ foreign glfw { SetErrorCallback :: proc(cbfun: ErrorProc) -> ErrorProc --- + // Functions added in 3.4, Linux links against system glfw so we define these as weak to be able + // to check at runtime if they are available. + + @(linkage="weak") GetPlatform :: proc() -> c.int --- + @(linkage="weak") PlatformSupported :: proc(platform: c.int) -> b32 --- } diff --git a/vendor/glfw/native_linux.odin b/vendor/glfw/native_linux.odin index 6833d2893..acae8a27e 100644 --- a/vendor/glfw/native_linux.odin +++ b/vendor/glfw/native_linux.odin @@ -13,7 +13,13 @@ foreign { SetX11SelectionString :: proc(string: cstring) --- GetX11SelectionString :: proc() -> cstring --- + // Functions added in 3.4, Linux links against system glfw so we define these as weak to be able + // to check at runtime if they are available. + + @(linkage="weak") GetWaylandDisplay :: proc() -> rawptr /* struct wl_display* */ --- + @(linkage="weak") GetWaylandWindow :: proc(window: WindowHandle) -> rawptr /* struct wl_surface* */ --- + @(linkage="weak") GetWaylandMonitor :: proc(monitor: MonitorHandle) -> rawptr /* struct wl_output* */ --- } diff --git a/vendor/wgpu/README.md b/vendor/wgpu/README.md index 3561642f4..59b31567f 100644 --- a/vendor/wgpu/README.md +++ b/vendor/wgpu/README.md @@ -41,8 +41,14 @@ It exports one procedure `GetSurface(wgpu.Instance, glfw.WindowHandle) -> glfw.S The procedure will call the needed target specific procedures and return a surface configured for the given window. -To support Wayland on Linux, you need to have GLFW compiled to support it, and use -`-define:WGPU_GFLW_GLUE_SUPPORT_WAYLAND=true` to enable the package to check for Wayland. - Do note that wgpu does not require GLFW, you can use native windows or another windowing library too. For that you can take inspiration from `glfwglue` on glueing them together. + +### Wayland + +GLFW supports Wayland from version 3.4 onwards and only if it is compiled with `-DGLFW_EXPOSE_NATIVE_WAYLAND`. + +Odin links against your system's glfw library (probably installed through a package manager). +If that version is lower than 3.4 or hasn't been compiled with the previously mentioned define, +you will have to compile glfw from source yourself and adjust the `foreign import` declarations in `vendor:glfw/bindings` to +point to it. diff --git a/vendor/wgpu/glfwglue/glue_linux.odin b/vendor/wgpu/glfwglue/glue_linux.odin index 35c36a37d..45d29a638 100644 --- a/vendor/wgpu/glfwglue/glue_linux.odin +++ b/vendor/wgpu/glfwglue/glue_linux.odin @@ -3,11 +3,8 @@ package wgpu_glfw_glue import "vendor:glfw" import "vendor:wgpu" -// GLFW needs to be compiled with wayland support for this to work. -SUPPORT_WAYLAND :: #config(WGPU_GFLW_GLUE_SUPPORT_WAYLAND, false) - GetSurface :: proc(instance: wgpu.Instance, window: glfw.WindowHandle) -> wgpu.Surface { - when SUPPORT_WAYLAND { + if glfw.GetPlatform != nil { if glfw.GetPlatform() == glfw.PLATFORM_WAYLAND { display := glfw.GetWaylandDisplay() surface := glfw.GetWaylandWindow(window) @@ -24,6 +21,10 @@ GetSurface :: proc(instance: wgpu.Instance, window: glfw.WindowHandle) -> wgpu.S }, ) } + + if glfw.GetPlatform() != glfw.PLATFORM_X11 { + panic("wgpu glfw glue: unsupported platform, expected Wayland or X11") + } } display := glfw.GetX11Display() From 2b6a926bb60ea652997401d37fc11f5a29f5b20b Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Thu, 18 Jul 2024 21:05:04 +0200 Subject: [PATCH 138/198] fix OLS #52 --- core/odin/parser/parser.odin | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index dec892f84..e164c778b 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -2179,22 +2179,25 @@ parse_inlining_operand :: proc(p: ^Parser, lhs: bool, tok: tokenizer.Token) -> ^ } } - #partial switch e in ast.strip_or_return_expr(expr).derived_expr { - case ^ast.Proc_Lit: - if e.inlining != .None && e.inlining != pi { - error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure literal") + if expr != nil { + #partial switch e in ast.strip_or_return_expr(expr).derived_expr { + case ^ast.Proc_Lit: + if e.inlining != .None && e.inlining != pi { + error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure literal") + } + e.inlining = pi + return expr + case ^ast.Call_Expr: + if e.inlining != .None && e.inlining != pi { + error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure call") + } + e.inlining = pi + return expr } - e.inlining = pi - case ^ast.Call_Expr: - if e.inlining != .None && e.inlining != pi { - error(p, expr.pos, "both 'inline' and 'no_inline' cannot be applied to a procedure call") - } - e.inlining = pi - case: - error(p, tok.pos, "'%s' must be followed by a procedure literal or call", tok.text) - return ast.new(ast.Bad_Expr, tok.pos, expr) } - return expr + + error(p, tok.pos, "'%s' must be followed by a procedure literal or call", tok.text) + return ast.new(ast.Bad_Expr, tok.pos, expr) } parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { From 27f9f0ba171444f48021744af2792bbfa290407b Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Thu, 18 Jul 2024 21:05:33 +0200 Subject: [PATCH 139/198] fix OLS #431, #393 --- core/odin/parser/parser.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index e164c778b..4d045f785 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -2261,18 +2261,18 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { hp.type = type return hp - case "file", "line", "procedure", "caller_location": + case "file", "directory", "line", "procedure", "caller_location": bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name)) bd.tok = tok bd.name = name.text return bd - case "location", "load", "assert", "defined", "config": + + case "location", "exists", "load", "load_directory", "load_hash", "hash", "assert", "panic", "defined", "config": bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name)) bd.tok = tok bd.name = name.text return parse_call_expr(p, bd) - case "soa": bd := ast.new(ast.Basic_Directive, tok.pos, end_pos(name)) bd.tok = tok From 393ca40c230c9cc1aeb0588c4550ab22db2b604a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Jul 2024 12:00:49 +0100 Subject: [PATCH 140/198] Minor clean ups --- core/sys/linux/helpers.odin | 59 +++++++++++++++++++------------------ core/sys/linux/types.odin | 2 +- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/core/sys/linux/helpers.odin b/core/sys/linux/helpers.odin index 75fdd586e..f1abbbf61 100644 --- a/core/sys/linux/helpers.odin +++ b/core/sys/linux/helpers.odin @@ -12,7 +12,7 @@ import "base:intrinsics" @(private) syscall0 :: #force_inline proc "contextless" (nr: uintptr) -> int { - return cast(int) intrinsics.syscall(nr) + return int(intrinsics.syscall(nr)) } @(private) @@ -20,7 +20,7 @@ syscall1 :: #force_inline proc "contextless" (nr: uintptr, p1: $T) -> int where size_of(p1) <= size_of(uintptr) { - return cast(int) intrinsics.syscall(nr, cast(uintptr) p1) + return int(intrinsics.syscall(nr, uintptr(p1))) } @(private) @@ -29,8 +29,7 @@ where size_of(p1) <= size_of(uintptr), size_of(p2) <= size_of(uintptr) { - return cast(int) intrinsics.syscall(nr, - cast(uintptr) p1, cast(uintptr) p2) + return int(intrinsics.syscall(nr, uintptr(p1), uintptr(p2))) } @(private) @@ -40,10 +39,11 @@ where size_of(p2) <= size_of(uintptr), size_of(p3) <= size_of(uintptr) { - return cast(int) intrinsics.syscall(nr, - cast(uintptr) p1, - cast(uintptr) p2, - cast(uintptr) p3) + return int(intrinsics.syscall(nr, + uintptr(p1), + uintptr(p2), + uintptr(p3), + )) } @(private) @@ -54,11 +54,12 @@ where size_of(p3) <= size_of(uintptr), size_of(p4) <= size_of(uintptr) { - return cast(int) intrinsics.syscall(nr, - cast(uintptr) p1, - cast(uintptr) p2, - cast(uintptr) p3, - cast(uintptr) p4) + return int(intrinsics.syscall(nr, + uintptr(p1), + uintptr(p2), + uintptr(p3), + uintptr(p4), + )) } @(private) @@ -70,12 +71,13 @@ where size_of(p4) <= size_of(uintptr), size_of(p5) <= size_of(uintptr) { - return cast(int) intrinsics.syscall(nr, - cast(uintptr) p1, - cast(uintptr) p2, - cast(uintptr) p3, - cast(uintptr) p4, - cast(uintptr) p5) + return int(intrinsics.syscall(nr, + uintptr(p1), + uintptr(p2), + uintptr(p3), + uintptr(p4), + uintptr(p5), + )) } @(private) @@ -88,13 +90,14 @@ where size_of(p5) <= size_of(uintptr), size_of(p6) <= size_of(uintptr) { - return cast(int) intrinsics.syscall(nr, - cast(uintptr) p1, - cast(uintptr) p2, - cast(uintptr) p3, - cast(uintptr) p4, - cast(uintptr) p5, - cast(uintptr) p6) + return int(intrinsics.syscall(nr, + uintptr(p1), + uintptr(p2), + uintptr(p3), + uintptr(p4), + uintptr(p5), + uintptr(p6), + )) } syscall :: proc {syscall0, syscall1, syscall2, syscall3, syscall4, syscall5, syscall6} @@ -113,7 +116,7 @@ where default_value: T return default_value, Errno(-ret) } else { - return cast(T) transmute(U) ret, Errno(.NONE) + return T(transmute(U)ret), Errno(.NONE) } } @@ -123,7 +126,7 @@ errno_unwrap2 :: #force_inline proc "contextless" (ret: $P, $T: typeid) -> (T, E default_value: T return default_value, Errno(-ret) } else { - return cast(T) ret, Errno(.NONE) + return T(ret), Errno(.NONE) } } diff --git a/core/sys/linux/types.odin b/core/sys/linux/types.odin index e3fe67a9b..d373f96f2 100644 --- a/core/sys/linux/types.odin +++ b/core/sys/linux/types.odin @@ -688,7 +688,7 @@ Sock_Addr_In6 :: struct #packed { } /* - Struct representing Unix Domain Socket address + Struct representing Unix Domain Socket address */ Sock_Addr_Un :: struct #packed { sun_family: Address_Family, From ba3d7ba5d3fe22800d980dbb420be4fbb117be0c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Jul 2024 12:03:34 +0100 Subject: [PATCH 141/198] Add `core:encoding/ini` to examples/all --- core/encoding/ini/ini.odin | 2 +- examples/all/all_main.odin | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/core/encoding/ini/ini.odin b/core/encoding/ini/ini.odin index d0dd33aba..8010e4e75 100644 --- a/core/encoding/ini/ini.odin +++ b/core/encoding/ini/ini.odin @@ -113,7 +113,7 @@ load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options new_key = strings.to_lower(key) or_return delete(old_key) or_return } - pairs[new_key], err = unquote(value) or_return + pairs[new_key] = unquote(value) or_return } return } diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 976a0f0e5..d92a6b8c4 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -61,6 +61,7 @@ import cbor "core:encoding/cbor" import csv "core:encoding/csv" import endian "core:encoding/endian" import hxa "core:encoding/hxa" +import ini "core:encoding/ini" import json "core:encoding/json" import varint "core:encoding/varint" import xml "core:encoding/xml" @@ -193,6 +194,7 @@ _ :: base32 _ :: base64 _ :: csv _ :: hxa +_ :: ini _ :: json _ :: varint _ :: xml From 7237f9c9f850c8a952037938d2fdfca4daf3caa8 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 19 Jul 2024 20:47:26 +0200 Subject: [PATCH 142/198] Help text default -o:none -> -o:minimal. --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 00734c050..41a95338b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2172,7 +2172,7 @@ gb_internal void print_show_help(String const arg0, String const &command) { if (LB_USE_NEW_PASS_SYSTEM) { print_usage_line(3, "-o:aggressive"); } - print_usage_line(2, "The default is -o:none."); + print_usage_line(2, "The default is -o:minimal."); print_usage_line(0, ""); } From 15997d2a90432a53dac801707c8bb1f41b834f08 Mon Sep 17 00:00:00 2001 From: flysand7 Date: Sat, 20 Jul 2024 12:47:00 +1100 Subject: [PATCH 143/198] [vendor/x11]: Fix missing argument in XDefaultDepth, wrong types in CreateSimpleWindow --- vendor/x11/xlib/xlib_procs.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vendor/x11/xlib/xlib_procs.odin b/vendor/x11/xlib/xlib_procs.odin index 17d172172..207b3f6bc 100644 --- a/vendor/x11/xlib/xlib_procs.odin +++ b/vendor/x11/xlib/xlib_procs.odin @@ -49,7 +49,7 @@ foreign xlib { DisplayString :: proc(display: ^Display) -> cstring --- // Display macros (defaults) DefaultColormap :: proc(display: ^Display, screen_no: i32) -> Colormap --- - DefaultDepth :: proc(display: ^Display) -> i32 --- + DefaultDepth :: proc(display: ^Display, screen_no: i32) -> i32 --- DefaultGC :: proc(display: ^Display, screen_no: i32) -> GC --- DefaultRootWindow :: proc(display: ^Display) -> Window --- DefaultScreen :: proc(display: ^Display) -> i32 --- @@ -138,8 +138,8 @@ foreign xlib { width: u32, height: u32, bordersz: u32, - border: int, - bg: int, + border: uint, + bg: uint, ) -> Window --- DestroyWindow :: proc(display: ^Display, window: Window) --- DestroySubwindows :: proc(display: ^Display, window: Window) --- From 0c78cab336897080fafd3d8ff31330b939773713 Mon Sep 17 00:00:00 2001 From: flysand7 Date: Sat, 20 Jul 2024 12:30:06 +1100 Subject: [PATCH 144/198] [time/datetime]: Document package datetime --- core/time/datetime/constants.odin | 66 ++++++++++- core/time/datetime/datetime.odin | 172 ++++++++++++++++++++++++++++- core/time/datetime/internal.odin | 1 + core/time/datetime/validation.odin | 44 +++++++- 4 files changed, 273 insertions(+), 10 deletions(-) diff --git a/core/time/datetime/constants.odin b/core/time/datetime/constants.odin index a2a02838c..5f336ef4a 100644 --- a/core/time/datetime/constants.odin +++ b/core/time/datetime/constants.odin @@ -1,16 +1,46 @@ package datetime -// Ordinal 1 = Midnight Monday, January 1, 1 A.D. (Gregorian) -// | Midnight Monday, January 3, 1 A.D. (Julian) +/* +Type representing a mononotic day number corresponding to a date. + + Ordinal 1 = Midnight Monday, January 1, 1 A.D. (Gregorian) + | Midnight Monday, January 3, 1 A.D. (Julian) +*/ Ordinal :: i64 + +/* +*/ EPOCH :: Ordinal(1) -// Minimum and maximum dates and ordinals. Chosen for safe roundtripping. +/* +Minimum valid value for date. + +The value is chosen such that a conversion `date -> ordinal -> date` is always +safe. +*/ MIN_DATE :: Date{year = -25_252_734_927_766_552, month = 1, day = 1} + +/* +Maximum valid value for date + +The value is chosen such that a conversion `date -> ordinal -> date` is always +safe. +*/ MAX_DATE :: Date{year = 25_252_734_927_766_552, month = 12, day = 31} + +/* +Minimum value for an ordinal +*/ MIN_ORD :: Ordinal(-9_223_372_036_854_775_234) + +/* +Maximum value for an ordinal +*/ MAX_ORD :: Ordinal( 9_223_372_036_854_774_869) +/* +Possible errors returned by datetime functions. +*/ Error :: enum { None, Invalid_Year, @@ -24,12 +54,22 @@ Error :: enum { Invalid_Delta, } +/* +A type representing a date. + +The minimum and maximum values for a year can be found in `MIN_DATE` and +`MAX_DATE` constants. The `month` field can range from 1 to 12, and the day +ranges from 1 to however many days there are in the specified month. +*/ Date :: struct { year: i64, month: i8, day: i8, } +/* +A type representing a time within a single day within a nanosecond precision. +*/ Time :: struct { hour: i8, minute: i8, @@ -37,17 +77,30 @@ Time :: struct { nano: i32, } +/* +A type representing datetime. +*/ DateTime :: struct { using date: Date, using time: Time, } +/* +A type representing a difference between two instances of datetime. + +**Note**: All fields are i64 because we can also use it to add a number of +seconds or nanos to a moment, that are then normalized within their respective +ranges. +*/ Delta :: struct { - days: i64, // These are all i64 because we can also use it to add a number of seconds or nanos to a moment, - seconds: i64, // that are then normalized within their respective ranges. + days: i64, + seconds: i64, nanos: i64, } +/* +Type representing one of the months. +*/ Month :: enum i8 { January = 1, February, @@ -63,6 +116,9 @@ Month :: enum i8 { December, } +/* +Type representing one of the weekdays. +*/ Weekday :: enum i8 { Sunday = 0, Monday, diff --git a/core/time/datetime/datetime.odin b/core/time/datetime/datetime.odin index 938b4a368..fc9780e3b 100644 --- a/core/time/datetime/datetime.odin +++ b/core/time/datetime/datetime.odin @@ -1,56 +1,113 @@ /* - Calendrical conversions using a proleptic Gregorian calendar. +Calendrical conversions using a proleptic Gregorian calendar. - Implemented using formulas from: Calendrical Calculations Ultimate Edition, Reingold & Dershowitz +Implemented using formulas from: Calendrical Calculations Ultimate Edition, +Reingold & Dershowitz */ package datetime import "base:intrinsics" -// Procedures that return an Ordinal +/* +Obtain an ordinal from a date. +This procedure converts the specified date into an ordinal. If the specified +date is not a valid date, an error is returned. +*/ date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal, err: Error) { validate(date) or_return return unsafe_date_to_ordinal(date), .None } +/* +Obtain an ordinal from date components. + +This procedure converts the specified date, provided by its individual +components, into an ordinal. If the specified date is not a valid date, an error +is returned. +*/ components_to_ordinal :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (ordinal: Ordinal, err: Error) { validate(year, month, day) or_return return unsafe_date_to_ordinal({year, i8(month), i8(day)}), .None } -// Procedures that return a Date +/* +Obtain date using an Ordinal. +This provedure converts the specified ordinal into a date. If the ordinal is not +a valid ordinal, an error is returned. +*/ ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date, err: Error) { validate(ordinal) or_return return unsafe_ordinal_to_date(ordinal), .None } +/* +Obtain a date from date components. + +This procedure converts date components, specified by a year, a month and a day, +into a date object. If the provided date components don't represent a valid +date, an error is returned. +*/ components_to_date :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (date: Date, err: Error) { validate(year, month, day) or_return return Date{i64(year), i8(month), i8(day)}, .None } +/* +Obtain time from time components. + +This procedure converts time components, specified by an hour, a minute, a second +and nanoseconds, into a time object. If the provided time components don't +represent a valid time, an error is returned. +*/ components_to_time :: proc "contextless" (#any_int hour, #any_int minute, #any_int second: i64, #any_int nanos := i64(0)) -> (time: Time, err: Error) { validate(hour, minute, second, nanos) or_return return Time{i8(hour), i8(minute), i8(second), i32(nanos)}, .None } +/* +Obtain datetime from components. + +This procedure converts date components and time components into a datetime object. +If the provided date components or time components don't represent a valid +datetime, an error is returned. +*/ components_to_datetime :: proc "contextless" (#any_int year, #any_int month, #any_int day, #any_int hour, #any_int minute, #any_int second: i64, #any_int nanos := i64(0)) -> (datetime: DateTime, err: Error) { date := components_to_date(year, month, day) or_return time := components_to_time(hour, minute, second, nanos) or_return return {date, time}, .None } +/* +Obtain an datetime from an ordinal. + +This procedure converts the value of an ordinal into a datetime. Since the +ordinal only has the amount of days, the resulting time in the datetime +object will always have the time equal to `00:00:00.000`. +*/ ordinal_to_datetime :: proc "contextless" (ordinal: Ordinal) -> (datetime: DateTime, err: Error) { d := ordinal_to_date(ordinal) or_return return {Date(d), {}}, .None } +/* +Calculate the weekday from an ordinal. + +This procedure takes the value of an ordinal and returns the day of week for +that ordinal. +*/ day_of_week :: proc "contextless" (ordinal: Ordinal) -> (day: Weekday) { return Weekday((ordinal - EPOCH + 1) %% 7) } +/* +Calculate the difference between two dates. + +This procedure calculates the difference between two dates `a - b`, and returns +a delta between the two dates in `days`. If either `a` or `b` is not a valid +date, an error is returned. +*/ subtract_dates :: proc "contextless" (a, b: Date) -> (delta: Delta, err: Error) { ord_a := date_to_ordinal(a) or_return ord_b := date_to_ordinal(b) or_return @@ -59,6 +116,16 @@ subtract_dates :: proc "contextless" (a, b: Date) -> (delta: Delta, err: Error) return } +/* +Calculate the difference between two datetimes. + +This procedure calculates the difference between two datetimes, `a - b`, and +returns a delta between the two dates. The difference is returned in all three +fields of the `Delta` struct: the difference in days, the difference in seconds +and the difference in nanoseconds. + +If either `a` or `b` is not a valid datetime, an error is returned. +*/ subtract_datetimes :: proc "contextless" (a, b: DateTime) -> (delta: Delta, err: Error) { ord_a := date_to_ordinal(a) or_return ord_b := date_to_ordinal(b) or_return @@ -73,19 +140,42 @@ subtract_datetimes :: proc "contextless" (a, b: DateTime) -> (delta: Delta, err: return } +/* +Calculate a difference between two deltas. +*/ subtract_deltas :: proc "contextless" (a, b: Delta) -> (delta: Delta, err: Error) { delta = Delta{a.days - b.days, a.seconds - b.seconds, a.nanos - b.nanos} delta = normalize_delta(delta) or_return return } + +/* +Calculate a difference between two datetimes, dates or deltas. +*/ sub :: proc{subtract_datetimes, subtract_dates, subtract_deltas} +/* +Add certain amount of days to a date. + +This procedure adds the specified amount of days to a date and returns a new +date. The new date would have happened the specified amount of days after the +specified date. +*/ add_days_to_date :: proc "contextless" (a: Date, days: i64) -> (date: Date, err: Error) { ord := date_to_ordinal(a) or_return ord += days return ordinal_to_date(ord) } +/* +Add delta to a date. + +This procedure adds a delta to a date, and returns a new date. The new date +would have happened the time specified by `delta` after the specified date. + +**Note**: The delta is assumed to be normalized. That is, if it contains seconds +or milliseconds, regardless of the amount only the days will be added. +*/ add_delta_to_date :: proc "contextless" (a: Date, delta: Delta) -> (date: Date, err: Error) { ord := date_to_ordinal(a) or_return // Because the input is a Date, we add only the days from the Delta. @@ -93,6 +183,13 @@ add_delta_to_date :: proc "contextless" (a: Date, delta: Delta) -> (date: Date, return ordinal_to_date(ord) } +/* +Add delta to datetime. + +This procedure adds a delta to a datetime, and returns a new datetime. The new +datetime would have happened the time specified by `delta` after the specified +datetime. +*/ add_delta_to_datetime :: proc "contextless" (a: DateTime, delta: Delta) -> (datetime: DateTime, err: Error) { days := date_to_ordinal(a) or_return @@ -110,8 +207,18 @@ add_delta_to_datetime :: proc "contextless" (a: DateTime, delta: Delta) -> (date datetime.time = components_to_time(hour, minute, second, sum_delta.nanos) or_return return } + +/* +Add days to a date, delta to a date or delta to datetime. +*/ add :: proc{add_days_to_date, add_delta_to_date, add_delta_to_datetime} +/* +Obtain the day number in a year + +This procedure returns the number of the day in a year, starting from 1. If +the date is not a valid date, an error is returned. +*/ day_number :: proc "contextless" (date: Date) -> (day_number: i64, err: Error) { validate(date) or_return @@ -120,6 +227,13 @@ day_number :: proc "contextless" (date: Date) -> (day_number: i64, err: Error) { return } +/* +Obtain the remaining number of days in a year. + +This procedure returns the number of days between the specified date and +December 31 of the same year. If the date is not a valid date, an error is +returned. +*/ days_remaining :: proc "contextless" (date: Date) -> (days_remaining: i64, err: Error) { // Alternative formulation `day_number` subtracted from 365 or 366 depending on leap year validate(date) or_return @@ -127,6 +241,12 @@ days_remaining :: proc "contextless" (date: Date) -> (days_remaining: i64, err: return delta.days, .None } +/* +Obtain the last day of a given month on a given year. + +This procedure returns the amount of days in a specified month on a specified +date. If the specified year or month is not valid, an error is returned. +*/ last_day_of_month :: proc "contextless" (#any_int year: i64, #any_int month: i8) -> (day: i8, err: Error) { // Not using formula 2.27 from the book. This is far simpler and gives the same answer. @@ -140,16 +260,33 @@ last_day_of_month :: proc "contextless" (#any_int year: i64, #any_int month: i8) return } +/* +Obtain the new year date of a given year. + +This procedure returns the January 1st date of the specified year. If the year +is not valid, an error is returned. +*/ new_year :: proc "contextless" (#any_int year: i64) -> (new_year: Date, err: Error) { validate(year, 1, 1) or_return return {year, 1, 1}, .None } +/* +Obtain the end year of a given date. + +This procedure returns the December 31st date of the specified year. If the year +is not valid, an error is returned. +*/ year_end :: proc "contextless" (#any_int year: i64) -> (year_end: Date, err: Error) { validate(year, 12, 31) or_return return {year, 12, 31}, .None } +/* +Obtain the range of dates for a given year. + +This procedure returns dates, for every day of a given year in a slice. +*/ year_range :: proc (#any_int year: i64, allocator := context.allocator) -> (range: []Date) { is_leap := is_leap_year(year) @@ -171,6 +308,15 @@ year_range :: proc (#any_int year: i64, allocator := context.allocator) -> (rang return } +/* +Normalize the delta. + +This procedure normalizes the delta in such a way that the number of seconds +is between 0 and the number of seconds in the day and nanoseconds is between +0 and 10^9. + +If the value for `days` overflows during this operation, an error is returned. +*/ normalize_delta :: proc "contextless" (delta: Delta) -> (normalized: Delta, err: Error) { // Distribute nanos into seconds and remainder seconds, nanos := divmod(delta.nanos, 1e9) @@ -194,6 +340,12 @@ normalize_delta :: proc "contextless" (delta: Delta) -> (normalized: Delta, err: // The following procedures don't check whether their inputs are in a valid range. // They're still exported for those who know their inputs have been validated. +/* +Obtain an ordinal from a date. + +This procedure converts a date into an ordinal. If the date is not a valid date, +the result is unspecified. +*/ unsafe_date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal) { year_minus_one := date.year - 1 @@ -223,6 +375,12 @@ unsafe_date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal) return } +/* +Obtain a year and a day of the year from an ordinal. + +This procedure returns the year and the day of the year of a given ordinal. +Of the ordinal is outside of its valid range, the result is unspecified. +*/ unsafe_ordinal_to_year :: proc "contextless" (ordinal: Ordinal) -> (year: i64, day_ordinal: i64) { // Days after epoch d0 := ordinal - EPOCH @@ -253,6 +411,12 @@ unsafe_ordinal_to_year :: proc "contextless" (ordinal: Ordinal) -> (year: i64, d return year + 1, day_ordinal } +/* +Obtain a date from an ordinal. + +This procedure converts an ordinal into a date. If the ordinal is outside of +its valid range, the result is unspecified. +*/ unsafe_ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date) { year, _ := unsafe_ordinal_to_year(ordinal) diff --git a/core/time/datetime/internal.odin b/core/time/datetime/internal.odin index 45c2b99ab..e7129548e 100644 --- a/core/time/datetime/internal.odin +++ b/core/time/datetime/internal.odin @@ -1,3 +1,4 @@ +//+private package datetime // Internal helper functions for calendrical conversions diff --git a/core/time/datetime/validation.odin b/core/time/datetime/validation.odin index 87d5aa1cd..0a66833b0 100644 --- a/core/time/datetime/validation.odin +++ b/core/time/datetime/validation.odin @@ -1,14 +1,29 @@ package datetime - // Validation helpers + +/* +Check if a year is a leap year. +*/ is_leap_year :: proc "contextless" (#any_int year: i64) -> (leap: bool) { return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) } +/* +Check for errors in date formation. + +This procedure validates all fields of a date, and if any of the fields is +outside of allowed range, an error is returned. +*/ validate_date :: proc "contextless" (date: Date) -> (err: Error) { return validate(date.year, date.month, date.day) } +/* +Check for errors in date formation given date components. + +This procedure checks whether a date formed by the specified year month and a +day is a valid date. If not, an error is returned. +*/ validate_year_month_day :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (err: Error) { if year < MIN_DATE.year || year > MAX_DATE.year { return .Invalid_Year @@ -29,6 +44,12 @@ validate_year_month_day :: proc "contextless" (#any_int year, #any_int month, #a return .None } +/* +Check for errors in Ordinal + +This procedure checks if the ordinal is in a valid range for roundtrip +conversions with the dates. If not, an error is returned. +*/ validate_ordinal :: proc "contextless" (ordinal: Ordinal) -> (err: Error) { if ordinal < MIN_ORD || ordinal > MAX_ORD { return .Invalid_Ordinal @@ -36,10 +57,22 @@ validate_ordinal :: proc "contextless" (ordinal: Ordinal) -> (err: Error) { return } +/* +Check for errors in time formation + +This procedure checks whether time has all fields in valid ranges, and if not +an error is returned. +*/ validate_time :: proc "contextless" (time: Time) -> (err: Error) { return validate(time.hour, time.minute, time.second, time.nano) } +/* +Check for errors in time formed by its components. + +This procedure checks whether the time formed by its components is valid, and +if not an error is returned. +*/ validate_hour_minute_second :: proc "contextless" (#any_int hour, #any_int minute, #any_int second, #any_int nano: i64) -> (err: Error) { if hour < 0 || hour > 23 { return .Invalid_Hour @@ -56,12 +89,21 @@ validate_hour_minute_second :: proc "contextless" (#any_int hour, #any_int minut return .None } +/* +Check for errors in datetime formation. + +This procedure checks whether all fields of date and time in the specified +datetime are valid, and if not, an error is returned. +*/ validate_datetime :: proc "contextless" (datetime: DateTime) -> (err: Error) { validate(datetime.date) or_return validate(datetime.time) or_return return .None } +/* +Check for errors in date, time or datetime. +*/ validate :: proc{ validate_date, validate_year_month_day, From b3ca2d5e0a91be3ef3698aa2d626cdff70c9b487 Mon Sep 17 00:00:00 2001 From: flysand7 Date: Sat, 20 Jul 2024 16:40:38 +1100 Subject: [PATCH 145/198] [time]: Document all functions --- core/time/iso8601.odin | 88 +++++++++++-- core/time/perf.odin | 92 +++++++++++-- core/time/rfc3339.odin | 92 +++++++++++-- core/time/time.odin | 284 +++++++++++++++++++++++++++++++++++++++-- 4 files changed, 512 insertions(+), 44 deletions(-) diff --git a/core/time/iso8601.odin b/core/time/iso8601.odin index 528e0b00a..f00107226 100644 --- a/core/time/iso8601.odin +++ b/core/time/iso8601.odin @@ -3,23 +3,62 @@ package time import dt "core:time/datetime" -// Parses an ISO 8601 string and returns Time in UTC, with any UTC offset applied to it. -// Only 4-digit years are accepted. -// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second. -// Leap seconds are smeared into 23:59:59. +/* +Parse an ISO 8601 string into a time with UTC offset applied to it. + +This procedure parses an ISO 8601 string of roughly the following format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns time, in UTC represented by that string. In case the timezone offset +is specified in the string, that timezone is applied to time. + +**Inputs**: +- `iso_datetime`: The string to be parsed. +- `is_leap`: Optional output parameter, specifying if the moment was a leap second. + +**Returns**: +- `res`: The time represented by `iso_datetime`, with UTC offset applied. +- `consumed`: Number of bytes consumed by parsing the string. + +**Notes**: +- Only 4-digit years are accepted. +- Leap seconds are smeared into 23:59:59. +*/ iso8601_to_time_utc :: proc(iso_datetime: string, is_leap: ^bool = nil) -> (res: Time, consumed: int) { offset: int - res, offset, consumed = iso8601_to_time_and_offset(iso_datetime, is_leap) res._nsec += (i64(-offset) * i64(Minute)) return res, consumed } -// Parses an ISO 8601 string and returns Time and a UTC offset in minutes. -// e.g. 1985-04-12T23:20:50.52Z -// Note: Only 4-digit years are accepted. -// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second. -// Leap seconds are smeared into 23:59:59. +/* +Parse an ISO 8601 string into a time and a UTC offset in minutes. + +This procedure parses an ISO 8601 string of roughly the following format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns time, in UTC represented by that string, and the UTC offset, in +minutes. + +**Inputs**: +- `iso_datetime`: The string to be parsed. +- `is_leap`: Optional output parameter, specifying if the moment was a leap second. + +**Returns**: +- `res`: The time in UTC. +- `utc_offset`: The UTC offset of the time, in minutes. +- `consumed`: Number of bytes consumed by parsing the string. + +**Notes**: +- Only 4-digit years are accepted. +- Leap seconds are smeared into 23:59:59. +*/ iso8601_to_time_and_offset :: proc(iso_datetime: string, is_leap: ^bool = nil) -> (res: Time, utc_offset: int, consumed: int) { moment, offset, leap_second, count := iso8601_to_components(iso_datetime) if count == 0 { @@ -37,9 +76,32 @@ iso8601_to_time_and_offset :: proc(iso_datetime: string, is_leap: ^bool = nil) - } } -// Parses an ISO 8601 string and returns Time and a UTC offset in minutes. -// e.g. 1985-04-12T23:20:50.52Z -// Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given +/* +Parse an ISO 8601 string into a datetime and a UTC offset in minutes. + +This procedure parses an ISO 8601 string of roughly the following format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns datetime, in UTC represented by that string, and the UTC offset, in +minutes. + +**Inputs**: +- `iso_datetime`: The string to be parsed + +**Returns**: +- `res`: The parsed datetime, in UTC. +- `utc_offset`: The UTC offset, in minutes. +- `is_leap`: Specifies whether the moment was a leap second. +- `consumed`: The number of bytes consumed by parsing the string. + +**Notes**: +- This procedure performs no validation on whether components are valid, + e.g. it'll return hour = 25 if that's what it's given in the specified + string. +*/ iso8601_to_components :: proc(iso_datetime: string) -> (res: dt.DateTime, utc_offset: int, is_leap: bool, consumed: int) { moment, offset, count, leap_second, ok := _iso8601_to_components(iso_datetime) if !ok { diff --git a/core/time/perf.odin b/core/time/perf.odin index 123d67eca..784d7acd6 100644 --- a/core/time/perf.odin +++ b/core/time/perf.odin @@ -3,18 +3,39 @@ package time import "base:runtime" import "base:intrinsics" +/* +Type representing monotonic time, useful for measuring durations. +*/ Tick :: struct { _nsec: i64, // relative amount } + +/* +Obtain the current tick. +*/ tick_now :: proc "contextless" () -> Tick { return _tick_now() } +/* +Obtain the difference between ticks. +*/ tick_diff :: proc "contextless" (start, end: Tick) -> Duration { d := end._nsec - start._nsec return Duration(d) } +/* +Incrementally obtain durations since last tick. + +This procedure returns the duration between the current tick and the tick +stored in `prev` pointer, and then stores the current tick in location, +specified by `prev`. If the prev pointer contains an zero-initialized tick, +then the returned duration is 0. + +This procedure is meant to be used in a loop, or in other scenarios, where one +might want to obtain time between multiple ticks at specific points. +*/ tick_lap_time :: proc "contextless" (prev: ^Tick) -> Duration { d: Duration t := tick_now() @@ -25,17 +46,21 @@ tick_lap_time :: proc "contextless" (prev: ^Tick) -> Duration { return d } +/* +Obtain the duration since last tick. +*/ tick_since :: proc "contextless" (start: Tick) -> Duration { return tick_diff(start, tick_now()) } - +/* +Capture the duration the code in the current scope takes to execute. +*/ @(deferred_in_out=_tick_duration_end) SCOPED_TICK_DURATION :: proc "contextless" (d: ^Duration) -> Tick { return tick_now() } - _tick_duration_end :: proc "contextless" (d: ^Duration, t: Tick) { d^ = tick_since(t) } @@ -62,6 +87,13 @@ when ODIN_OS != .Darwin && ODIN_OS != .Linux && ODIN_OS != .FreeBSD { } } +/* +Check if the CPU has invariant TSC. + +This procedure checks if the CPU contains an invariant TSC (Time stamp counter). +Invariant TSC is a feature of modern processors that allows them to run their +TSC at a fixed frequency, independent of ACPI state, and CPU frequency. +*/ has_invariant_tsc :: proc "contextless" () -> bool { when ODIN_ARCH == .amd64 { return x86_has_invariant_tsc() @@ -70,6 +102,17 @@ has_invariant_tsc :: proc "contextless" () -> bool { return false } +/* +Obtain the CPU's TSC frequency, in hertz. + +This procedure tries to obtain the CPU's TSC frequency in hertz. If the CPU +doesn't have an invariant TSC, this procedure returns with an error. Otherwise +an attempt is made to fetch the TSC frequency from the OS. If this fails, +the frequency is obtained by sleeping for the specified amount of time and +dividing the readings from TSC by the duration of the sleep. + +The duration of sleep can be controlled by `fallback_sleep` parameter. +*/ tsc_frequency :: proc "contextless" (fallback_sleep := 2 * Second) -> (u64, bool) { if !has_invariant_tsc() { return 0, false @@ -93,37 +136,64 @@ tsc_frequency :: proc "contextless" (fallback_sleep := 2 * Second) -> (u64, bool return hz, true } -/* - Benchmark helpers -*/ +// Benchmark helpers +/* +Errors returned by the `benchmark()` procedure. +*/ Benchmark_Error :: enum { Okay = 0, Allocation_Error, } +/* +Options for benchmarking. +*/ Benchmark_Options :: struct { + // The initialization procedure. `benchmark()` will call this before taking measurements. setup: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error), + // The procedure to benchmark. bench: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error), + // The deinitialization procedure. teardown: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error), - + // Field to be used by `bench()` procedure for any purpose. rounds: int, + // Field to be used by `bench()` procedure for any purpose. bytes: int, + // Field to be used by `bench()` procedure for any purpose. input: []u8, - + // `bench()` writes to specify the count of elements processed. count: int, + // `bench()` writes to specify the number of bytes processed. processed: int, + // `bench()` can write the output slice here. output: []u8, // Unused for hash benchmarks + // `bench()` can write the output hash here. hash: u128, - - /* - Performance - */ + // `benchmark()` procedure will output the duration of benchmark duration: Duration, + // `benchmark()` procedure will output the average count of elements + // processed per second, using the `count` field of this struct. rounds_per_second: f64, + // `benchmark()` procedure will output the average number of megabytes + // processed per second, using the `processed` field of this struct. megabytes_per_second: f64, } +/* +Benchmark a procedure. + +This procedure produces a benchmark. The procedure specified in the `bench` +field of the `options` parameter will be benchmarked. The following metrics +can be obtained: + +- Run time of the procedure +- Number of elements per second processed on average +- Number of bytes per second this processed on average + +In order to obtain these metrics, the `bench()` procedure writes to `options` +struct the number of elements or bytes it has processed. +*/ benchmark :: proc(options: ^Benchmark_Options, allocator := context.allocator) -> (err: Benchmark_Error) { assert(options != nil) assert(options.bench != nil) diff --git a/core/time/rfc3339.odin b/core/time/rfc3339.odin index 0a2d431b7..e4c6565d6 100644 --- a/core/time/rfc3339.odin +++ b/core/time/rfc3339.odin @@ -4,10 +4,33 @@ package time import dt "core:time/datetime" -// Parses an RFC 3339 string and returns Time in UTC, with any UTC offset applied to it. -// Only 4-digit years are accepted. -// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second. -// Leap seconds are smeared into 23:59:59. +/* +Parse an RFC 3339 string into time with a UTC offset applied to it. + +This procedure parses the specified RFC 3339 strings of roughly the following +format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns the time that was represented by the RFC 3339 string, with the UTC +offset applied to it. + +**Inputs**: +- `rfc_datetime`: An RFC 3339 string to parse. +- `is_leap`: Optional output parameter specifying whether the moment was a leap + second. + +**Returns**: +- `res`: The time, with UTC offset applied, that was parsed from the RFC 3339 + string. +- `consumed`: The number of bytes consumed by parsing the RFC 3339 string. + +**Notes**: +- Only 4-digit years are accepted. +- Leap seconds are smeared into 23:59:59. +*/ rfc3339_to_time_utc :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: Time, consumed: int) { offset: int @@ -16,11 +39,34 @@ rfc3339_to_time_utc :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: return res, consumed } -// Parses an RFC 3339 string and returns Time and a UTC offset in minutes. -// e.g. 1985-04-12T23:20:50.52Z -// Note: Only 4-digit years are accepted. -// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second. -// Leap seconds are smeared into 23:59:59. +/* +Parse an RFC 3339 string into a time and a UTC offset in minutes. + +This procedure parses the specified RFC 3339 strings of roughly the following +format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns the time, in UTC and a UTC offset, in minutes, that were represented +by the RFC 3339 string. + +**Inputs**: +- `rfc_datetime`: The RFC 3339 string to be parsed. +- `is_leap`: Optional output parameter specifying whether the moment was a + leap second. + +**Returns**: +- `res`: The time, in UTC, that was parsed from the RFC 3339 string. +- `utc_offset`: The UTC offset, in minutes, that was parsed from the RFC 3339 + string. +- `consumed`: The number of bytes consumed by parsing the string. + +**Notes**: +- Only 4-digit years are accepted. +- Leap seconds are smeared into 23:59:59. +*/ rfc3339_to_time_and_offset :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: Time, utc_offset: int, consumed: int) { moment, offset, leap_second, count := rfc3339_to_components(rfc_datetime) if count == 0 { @@ -38,9 +84,31 @@ rfc3339_to_time_and_offset :: proc(rfc_datetime: string, is_leap: ^bool = nil) - } } -// Parses an RFC 3339 string and returns Time and a UTC offset in minutes. -// e.g. 1985-04-12T23:20:50.52Z -// Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given +/* +Parse an RFC 3339 string into a datetime and a UTC offset in minutes. + +This procedure parses the specified RFC 3339 strings of roughly the following +format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns the datetime, in UTC and the UTC offset, in minutes, that were +represented by the RFC 3339 string. + +**Inputs**: +- `rfc_datetime`: The RFC 3339 string to parse. + +**Returns**: +- `res`: The datetime, in UTC, that was parsed from the RFC 3339 string. +- `utc_offset`: The UTC offset, in minutes, that was parsed from the RFC 3339 + string. +- `is_leap`: Specifies whether the moment was a leap second. +- `consumed`: Number of bytes consumed by parsing the string. + +Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given +*/ rfc3339_to_components :: proc(rfc_datetime: string) -> (res: dt.DateTime, utc_offset: int, is_leap: bool, consumed: int) { moment, offset, count, leap_second, ok := _rfc3339_to_components(rfc_datetime) if !ok { diff --git a/core/time/time.odin b/core/time/time.odin index 4ea5afc70..ef2bc0aed 100644 --- a/core/time/time.odin +++ b/core/time/time.odin @@ -3,24 +3,72 @@ package time import "base:intrinsics" import dt "core:time/datetime" +/* +Type representing duration, with nanosecond precision. +*/ Duration :: distinct i64 +/* +The duration equal to one nanosecond (1e-9 seconds). +*/ Nanosecond :: Duration(1) + +/* +The duration equal to one microsecond (1e-6 seconds). +*/ Microsecond :: 1000 * Nanosecond + +/* +The duration equal to one millisecond (1e-3 seconds). +*/ Millisecond :: 1000 * Microsecond + +/* +The duration equal to one second. +*/ Second :: 1000 * Millisecond + +/* +The duration equal to one minute (60 seconds). +*/ Minute :: 60 * Second + +/* +The duration equal to one hour (3600 seconds). +*/ Hour :: 60 * Minute +/* +Minimum representable duration. +*/ MIN_DURATION :: Duration(-1 << 63) + +/* +Maximum representable duration. +*/ MAX_DURATION :: Duration(1<<63 - 1) +/* +Value specifying whether the time procedures are supported by the current +platform. +*/ IS_SUPPORTED :: _IS_SUPPORTED +/* +Specifies time since the UNIX epoch, with nanosecond precision. + +Capable of representing any time within the following range: + +- `min: 1677-09-21 00:12:44.145224192 +0000 UTC` +- `max: 2262-04-11 23:47:16.854775807 +0000 UTC` +*/ Time :: struct { _nsec: i64, // Measured in UNIX nanonseconds } +/* +Type representing a month. +*/ Month :: enum int { January = 1, February, @@ -36,6 +84,9 @@ Month :: enum int { December, } +/* +Type representing a weekday. +*/ Weekday :: enum int { Sunday = 0, Monday, @@ -46,20 +97,37 @@ Weekday :: enum int { Saturday, } +/* +Type representing a stopwatch. + +The stopwatch is used for measuring the total time in multiple "runs". When the +stopwatch is started, it starts counting time. When the stopwatch is stopped, +the difference in time between the last start and the stop is added to the +total. When the stopwatch resets, the total is reset. +*/ Stopwatch :: struct { running: bool, _start_time: Tick, _accumulation: Duration, } +/* +Obtain the current time. +*/ now :: proc "contextless" () -> Time { return _now() } +/* +Sleep for the specified duration. +*/ sleep :: proc "contextless" (d: Duration) { _sleep(d) } +/* +Start the stopwatch. +*/ stopwatch_start :: proc "contextless" (stopwatch: ^Stopwatch) { if !stopwatch.running { stopwatch._start_time = tick_now() @@ -67,6 +135,9 @@ stopwatch_start :: proc "contextless" (stopwatch: ^Stopwatch) { } } +/* +Stop the stopwatch. +*/ stopwatch_stop :: proc "contextless" (stopwatch: ^Stopwatch) { if stopwatch.running { stopwatch._accumulation += tick_diff(stopwatch._start_time, tick_now()) @@ -74,11 +145,21 @@ stopwatch_stop :: proc "contextless" (stopwatch: ^Stopwatch) { } } +/* +Reset the stopwatch. +*/ stopwatch_reset :: proc "contextless" (stopwatch: ^Stopwatch) { stopwatch._accumulation = {} stopwatch.running = false } +/* +Obtain the total time, counted by the stopwatch. + +This procedure obtains the total time, counted by the stopwatch. If the stopwatch +isn't stopped at the time of calling this procedure, the time between the last +start and the current time is also accounted for. +*/ stopwatch_duration :: proc "contextless" (stopwatch: Stopwatch) -> Duration { if !stopwatch.running { return stopwatch._accumulation @@ -86,40 +167,92 @@ stopwatch_duration :: proc "contextless" (stopwatch: Stopwatch) -> Duration { return stopwatch._accumulation + tick_diff(stopwatch._start_time, tick_now()) } +/* +Calculate the duration elapsed between two times. +*/ diff :: proc "contextless" (start, end: Time) -> Duration { d := end._nsec - start._nsec return Duration(d) } +/* +Calculate the duration elapsed since a specific time. +*/ since :: proc "contextless" (start: Time) -> Duration { return diff(start, now()) } +/* +Obtain the number of nanoseconds in a duration. +*/ duration_nanoseconds :: proc "contextless" (d: Duration) -> i64 { return i64(d) } + +/* +Obtain the number of microseconds in a duration. +*/ duration_microseconds :: proc "contextless" (d: Duration) -> f64 { return duration_seconds(d) * 1e6 } + +/* +Obtain the number of milliseconds in a duration. +*/ duration_milliseconds :: proc "contextless" (d: Duration) -> f64 { return duration_seconds(d) * 1e3 } + +/* +Obtain the number of seconds in a duration. +*/ duration_seconds :: proc "contextless" (d: Duration) -> f64 { sec := d / Second nsec := d % Second return f64(sec) + f64(nsec)/1e9 } + +/* +Obtain the number of minutes in a duration. +*/ duration_minutes :: proc "contextless" (d: Duration) -> f64 { min := d / Minute nsec := d % Minute return f64(min) + f64(nsec)/(60*1e9) } + +/* +Obtain the number of hours in a duration. +*/ duration_hours :: proc "contextless" (d: Duration) -> f64 { hour := d / Hour nsec := d % Hour return f64(hour) + f64(nsec)/(60*60*1e9) } +/* +Round a duration to a specific unit. + +This procedure rounds the duration to a specific unit. + +**Inputs**: +- `d`: The duration to round. +- `m`: The unit to round to. + +**Returns**: +- The duration `d`, rounded to the unit specified by `m`. + +**Example**: + +In order to obtain the rough amount of seconds in a duration, the following call +can be used: + +``` +time.duration_round(my_duration, time.Second) +``` + +**Note**: Any duration can be supplied as a unit. +*/ duration_round :: proc "contextless" (d, m: Duration) -> Duration { _less_than_half :: #force_inline proc "contextless" (x, y: Duration) -> bool { return u64(x)+u64(x) < u64(y) @@ -149,50 +282,103 @@ duration_round :: proc "contextless" (d, m: Duration) -> Duration { return MAX_DURATION } +/* +Truncate the duration to the specified unit. + +This procedure truncates the duration `d` to the unit specified by `m`. + +**Inputs**: +- `d`: The duration to truncate. +- `m`: The unit to truncate to. + +**Returns**: +- The duration `d`, truncated to the unit specified by `m`. + +**Example**: + +In order to obtain the amount of whole seconds in a duration, the following call +can be used: + +``` +time.duration_round(my_duration, time.Second) +``` + +**Note**: Any duration can be supplied as a unit. +*/ duration_truncate :: proc "contextless" (d, m: Duration) -> Duration { return d if m <= 0 else d - d%m } +/* +Parse time into date components. +*/ date :: proc "contextless" (t: Time) -> (year: int, month: Month, day: int) { year, month, day, _ = _abs_date(_time_abs(t), true) return } +/* +Obtain the year of the date specified by time. +*/ year :: proc "contextless" (t: Time) -> (year: int) { year, _, _, _ = _date(t, true) return } +/* +Obtain the month of the date specified by time. +*/ month :: proc "contextless" (t: Time) -> (month: Month) { _, month, _, _ = _date(t, true) return } +/* +Obtain the day of the date specified by time. +*/ day :: proc "contextless" (t: Time) -> (day: int) { _, _, day, _ = _date(t, true) return } +/* +Obtain the week day of the date specified by time. +*/ weekday :: proc "contextless" (t: Time) -> (weekday: Weekday) { abs := _time_abs(t) sec := (abs + u64(Weekday.Monday) * SECONDS_PER_DAY) % SECONDS_PER_WEEK return Weekday(int(sec) / SECONDS_PER_DAY) } +/* +Obtain the time components from a time, a duration or a stopwatch's total. +*/ clock :: proc { clock_from_time, clock_from_duration, clock_from_stopwatch } +/* +Obtain the time components from a time. +*/ clock_from_time :: proc "contextless" (t: Time) -> (hour, min, sec: int) { return clock_from_seconds(_time_abs(t)) } +/* +Obtain the time components from a duration. +*/ clock_from_duration :: proc "contextless" (d: Duration) -> (hour, min, sec: int) { return clock_from_seconds(u64(d/1e9)) } +/* +Obtain the time components from a stopwatch's total. +*/ clock_from_stopwatch :: proc "contextless" (s: Stopwatch) -> (hour, min, sec: int) { return clock_from_duration(stopwatch_duration(s)) } +/* +Obtain the time components from the number of seconds. +*/ clock_from_seconds :: proc "contextless" (nsec: u64) -> (hour, min, sec: int) { sec = int(nsec % SECONDS_PER_DAY) hour = sec / SECONDS_PER_HOUR @@ -202,10 +388,16 @@ clock_from_seconds :: proc "contextless" (nsec: u64) -> (hour, min, sec: int) { return } +/* +Read the timestamp counter of the CPU. +*/ read_cycle_counter :: proc "contextless" () -> u64 { return u64(intrinsics.read_cycle_counter()) } +/* +Obtain time from unix seconds and unix nanoseconds. +*/ unix :: proc "contextless" (sec: i64, nsec: i64) -> Time { sec, nsec := sec, nsec if nsec < 0 || nsec >= 1e9 { @@ -220,31 +412,59 @@ unix :: proc "contextless" (sec: i64, nsec: i64) -> Time { return Time{(sec*1e9 + nsec)} } +/* +Obtain time from unix nanoseconds. +*/ from_nanoseconds :: #force_inline proc "contextless" (nsec: i64) -> Time { return Time{nsec} } +/* +Alias for `time_to_unix`. +*/ to_unix_seconds :: time_to_unix + +/* +Obtain the unix seconds from a time. +*/ time_to_unix :: proc "contextless" (t: Time) -> i64 { return t._nsec/1e9 } +/* +Alias for `time_to_unix_nano`. +*/ to_unix_nanoseconds :: time_to_unix_nano + +/* +Obtain the unix nanoseconds from a time. +*/ time_to_unix_nano :: proc "contextless" (t: Time) -> i64 { return t._nsec } +/* +Add duration to a time. +*/ time_add :: proc "contextless" (t: Time, d: Duration) -> Time { return Time{t._nsec + i64(d)} } -// Accurate sleep borrowed from: https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/ -// -// Accuracy seems to be pretty good out of the box on Linux, to within around 4µs worst case. -// On Windows it depends but is comparable with regular sleep in the worst case. -// To get the same kind of accuracy as on Linux, have your program call `windows.timeBeginPeriod(1)` to -// tell Windows to use a more accurate timer for your process. -// Additionally your program should call `windows.timeEndPeriod(1)` once you're done with `accurate_sleep`. +/* +Accurate sleep + +This procedure sleeps for the duration specified by `d`, very accurately. + +**Note**: Implementation borrowed from: [this source](https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/) + +**Note(linux)**: The accuracy is within around 4µs (microseconds), in the worst case. + +**Note(windows)**: The accuracy depends but is comparable with regular sleep in +the worst case. To get the same kind of accuracy as on Linux, have your program +call `windows.timeBeginPeriod(1)` to tell Windows to use a more accurate timer +for your process. Additionally your program should call `windows.timeEndPeriod(1)` +once you're done with `accurate_sleep`. +*/ accurate_sleep :: proc "contextless" (d: Duration) { to_sleep, estimate, mean, m2, count: Duration @@ -362,6 +582,13 @@ _abs_date :: proc "contextless" (abs: u64, full: bool) -> (year: int, month: Mon return } +/* +Convert datetime components into time. + +This procedure calculates the time from datetime components supplied in the +arguments to this procedure. If the datetime components don't represent a valid +datetime, the function returns `false` in the second argument. +*/ components_to_time :: proc "contextless" (#any_int year, #any_int month, #any_int day, #any_int hour, #any_int minute, #any_int second: i64, #any_int nsec := i64(0)) -> (t: Time, ok: bool) { this_date, err := dt.components_to_datetime(year, month, day, hour, minute, second, nsec) if err != .None { @@ -370,6 +597,12 @@ components_to_time :: proc "contextless" (#any_int year, #any_int month, #any_in return compound_to_time(this_date) } +/* +Convert datetime into time. + +If the datetime represents a time outside of a valid range, `false` is returned +as the second return value. See `Time` for the representable range. +*/ compound_to_time :: proc "contextless" (datetime: dt.DateTime) -> (t: Time, ok: bool) { unix_epoch := dt.DateTime{{1970, 1, 1}, {0, 0, 0, 0}} delta, err := dt.sub(datetime, unix_epoch) @@ -387,12 +620,21 @@ compound_to_time :: proc "contextless" (datetime: dt.DateTime) -> (t: Time, ok: return Time{_nsec=i64(nanoseconds)}, true } +/* +Convert datetime components into time. +*/ datetime_to_time :: proc{components_to_time, compound_to_time} +/* +Check if a year is a leap year. +*/ is_leap_year :: proc "contextless" (year: int) -> (leap: bool) { return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) } +/* +Days before each month in a year, not counting the leap day on february 29th. +*/ @(rodata) days_before := [?]i32{ 0, @@ -410,11 +652,37 @@ days_before := [?]i32{ 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, } - +/* +Number of seconds in a minute (without leap seconds). +*/ SECONDS_PER_MINUTE :: 60 + +/* +Number of seconds in an hour (without leap seconds). +*/ SECONDS_PER_HOUR :: 60 * SECONDS_PER_MINUTE + +/* +Number of seconds in a day (without leap seconds). +*/ SECONDS_PER_DAY :: 24 * SECONDS_PER_HOUR + +/* +Number of seconds in a week (without leap seconds). +*/ SECONDS_PER_WEEK :: 7 * SECONDS_PER_DAY + +/* +Days in 400 years, with leap days. +*/ DAYS_PER_400_YEARS :: 365*400 + 97 + +/* +Days in 100 years, with leap days. +*/ DAYS_PER_100_YEARS :: 365*100 + 24 + +/* +Days in 4 years, with leap days. +*/ DAYS_PER_4_YEARS :: 365*4 + 1 From 2385e1ddd9cdba029e0d192896c07011f70259f2 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 20 Jul 2024 14:24:01 +0200 Subject: [PATCH 146/198] Update LUA imports for Darwin. --- vendor/lua/5.1/lua.odin | 8 ++++++-- vendor/lua/5.2/lua.odin | 8 ++++++-- vendor/lua/5.3/lua.odin | 8 ++++++-- vendor/lua/5.4/lua.odin | 16 ++++++---------- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/vendor/lua/5.1/lua.odin b/vendor/lua/5.1/lua.odin index b53c61bb3..8ab315bcd 100644 --- a/vendor/lua/5.1/lua.odin +++ b/vendor/lua/5.1/lua.odin @@ -15,16 +15,20 @@ when LUA_SHARED { foreign import lib "windows/lua5.1.dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua5.1.so" + } else when ODIN_OS == .Darwin { + foreign import lib "system:lua5.1" } else { - foreign import lib "system:liblua.so.5.1" + #panic("LUA import not defined for this platform") } } else { when ODIN_OS == .Windows { foreign import lib "windows/lua5.1.dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua5.1.a" + } else when ODIN_OS == .Darwin { + foreign import lib "system:lua5.1" } else { - foreign import lib "system:liblua5.1.a" + #panic("LUA import not defined for this platform") } } diff --git a/vendor/lua/5.2/lua.odin b/vendor/lua/5.2/lua.odin index 5474da95d..960b8ba36 100644 --- a/vendor/lua/5.2/lua.odin +++ b/vendor/lua/5.2/lua.odin @@ -15,16 +15,20 @@ when LUA_SHARED { foreign import lib "windows/lua52dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua52.so" + } else when ODIN_OS == .Darwin { + foreign import lib "system:lua5.2" } else { - foreign import lib "system:liblua.so.5.2" + #panic("LUA import not defined for this platform") } } else { when ODIN_OS == .Windows { foreign import lib "windows/lua52dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua52.a" + } else when ODIN_OS == .Darwin { + foreign import lib "system:lua5.2" } else { - foreign import lib "system:liblua52.a" + #panic("LUA import not defined for this platform") } } diff --git a/vendor/lua/5.3/lua.odin b/vendor/lua/5.3/lua.odin index e0975e5f8..1428cc9b7 100644 --- a/vendor/lua/5.3/lua.odin +++ b/vendor/lua/5.3/lua.odin @@ -15,16 +15,20 @@ when LUA_SHARED { foreign import lib "windows/lua53dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua53.so" + } else when ODIN_OS == .Darwin { + foreign import lib "system:lua5.3" } else { - foreign import lib "system:liblua.so.5.3" + #panic("LUA import not defined for this platform") } } else { when ODIN_OS == .Windows { foreign import lib "windows/lua53dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua53.a" + } else when ODIN_OS == .Darwin { + foreign import lib "system:lua5.3" } else { - foreign import lib "system:liblua53.a" + #panic("LUA import not defined for this platform") } } diff --git a/vendor/lua/5.4/lua.odin b/vendor/lua/5.4/lua.odin index 80f7ead3a..9f9fc76d3 100644 --- a/vendor/lua/5.4/lua.odin +++ b/vendor/lua/5.4/lua.odin @@ -15,24 +15,20 @@ when LUA_SHARED { foreign import lib "windows/lua54dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua54.so" + } else when ODIN_OS == .Darwin { + foreign import lib "system:lua5.4" } else { - // Note(bumbread): My linux system has a few aliases for this shared object - // lublua5.4.so, liblua.so, lublua.so.5.4, liblua.so.5.4.6. I don't know - // who enforces these numbers (probably ld?), and if it can be done in a - // unix-generic way, but in any way I think the most sane thing to do is to - // keep it close to what linux does and if it breaks, just special case those - // operating systems. - // Also there was no alias for liblua54.so, that seems to suggest that way - // of specifying it isn't portable - foreign import lib "system:liblua.so.5.4" + #panic("LUA import not defined for this platform") } } else { when ODIN_OS == .Windows { foreign import lib "windows/lua54dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua54.a" + } else when ODIN_OS == .Darwin { + foreign import lib "system:lua5.4" } else { - foreign import lib "system:liblua54.a" + #panic("LUA import not defined for this platform") } } From 9d6ed991cbb9ba8df592133eb401110cfb056620 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 20 Jul 2024 14:33:34 +0200 Subject: [PATCH 147/198] Remove LUA panic for non-big-3 OS --- vendor/lua/5.1/lua.odin | 8 ++------ vendor/lua/5.2/lua.odin | 8 ++------ vendor/lua/5.3/lua.odin | 8 ++------ vendor/lua/5.4/lua.odin | 8 ++------ 4 files changed, 8 insertions(+), 24 deletions(-) diff --git a/vendor/lua/5.1/lua.odin b/vendor/lua/5.1/lua.odin index 8ab315bcd..5b7482931 100644 --- a/vendor/lua/5.1/lua.odin +++ b/vendor/lua/5.1/lua.odin @@ -15,20 +15,16 @@ when LUA_SHARED { foreign import lib "windows/lua5.1.dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua5.1.so" - } else when ODIN_OS == .Darwin { - foreign import lib "system:lua5.1" } else { - #panic("LUA import not defined for this platform") + foreign import lib "system:lua5.1" } } else { when ODIN_OS == .Windows { foreign import lib "windows/lua5.1.dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua5.1.a" - } else when ODIN_OS == .Darwin { - foreign import lib "system:lua5.1" } else { - #panic("LUA import not defined for this platform") + foreign import lib "system:lua5.1" } } diff --git a/vendor/lua/5.2/lua.odin b/vendor/lua/5.2/lua.odin index 960b8ba36..d5d8ec253 100644 --- a/vendor/lua/5.2/lua.odin +++ b/vendor/lua/5.2/lua.odin @@ -15,20 +15,16 @@ when LUA_SHARED { foreign import lib "windows/lua52dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua52.so" - } else when ODIN_OS == .Darwin { - foreign import lib "system:lua5.2" } else { - #panic("LUA import not defined for this platform") + foreign import lib "system:lua5.2" } } else { when ODIN_OS == .Windows { foreign import lib "windows/lua52dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua52.a" - } else when ODIN_OS == .Darwin { - foreign import lib "system:lua5.2" } else { - #panic("LUA import not defined for this platform") + foreign import lib "system:lua5.2" } } diff --git a/vendor/lua/5.3/lua.odin b/vendor/lua/5.3/lua.odin index 1428cc9b7..47215a327 100644 --- a/vendor/lua/5.3/lua.odin +++ b/vendor/lua/5.3/lua.odin @@ -15,20 +15,16 @@ when LUA_SHARED { foreign import lib "windows/lua53dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua53.so" - } else when ODIN_OS == .Darwin { - foreign import lib "system:lua5.3" } else { - #panic("LUA import not defined for this platform") + foreign import lib "system:lua5.3" } } else { when ODIN_OS == .Windows { foreign import lib "windows/lua53dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua53.a" - } else when ODIN_OS == .Darwin { - foreign import lib "system:lua5.3" } else { - #panic("LUA import not defined for this platform") + foreign import lib "system:lua5.3" } } diff --git a/vendor/lua/5.4/lua.odin b/vendor/lua/5.4/lua.odin index 9f9fc76d3..9be8fea55 100644 --- a/vendor/lua/5.4/lua.odin +++ b/vendor/lua/5.4/lua.odin @@ -15,20 +15,16 @@ when LUA_SHARED { foreign import lib "windows/lua54dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua54.so" - } else when ODIN_OS == .Darwin { - foreign import lib "system:lua5.4" } else { - #panic("LUA import not defined for this platform") + foreign import lib "system:lua5.4" } } else { when ODIN_OS == .Windows { foreign import lib "windows/lua54dll.lib" } else when ODIN_OS == .Linux { foreign import lib "linux/liblua54.a" - } else when ODIN_OS == .Darwin { - foreign import lib "system:lua5.4" } else { - #panic("LUA import not defined for this platform") + foreign import lib "system:lua5.4" } } From b584eeaade7cece9ff8bc5a12792693c303391e3 Mon Sep 17 00:00:00 2001 From: Ronald Date: Sat, 20 Jul 2024 16:53:54 +0100 Subject: [PATCH 148/198] Add encoding/ini tests --- tests/core/encoding/ini/test_core_ini.odin | 120 +++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 tests/core/encoding/ini/test_core_ini.odin diff --git a/tests/core/encoding/ini/test_core_ini.odin b/tests/core/encoding/ini/test_core_ini.odin new file mode 100644 index 000000000..1e6cc246b --- /dev/null +++ b/tests/core/encoding/ini/test_core_ini.odin @@ -0,0 +1,120 @@ +package test_core_ini + +import "base:runtime" +import "core:encoding/ini" +import "core:mem/virtual" +import "core:strings" +import "core:testing" + +@test +prase_ini :: proc(t: ^testing.T) { + ini_data := ` + [LOG] + level = "devel" + file = "/var/log/testing.log" + + [USER] + first_name = "John" + surname = "Smith" + ` + + m, err := ini.load_map_from_string(ini_data, context.allocator) + ini.delete_map(m) + + testing.expectf( + t, + strings.contains(m["LOG"]["level"], "devel"), + "Expected m[\"LOG\"][\"level\"] to be equal to 'devel' instead got %v", + m["LOG"]["level"], + ) + testing.expectf( + t, + strings.contains(m["LOG"]["file"], "/var/log/testing.log"), + "Expected m[\"LOG\"][\"file\"] to be equal to '/var/log/testing.log' instead got %v", + m["LOG"]["file"], + ) + testing.expectf( + t, + strings.contains(m["USER"]["first_name"], "John"), + "Expected m[\"USER\"][\"first_name\"] to be equal to 'John' instead got %v", + m["USER"]["first_name"], + ) + testing.expectf( + t, + strings.contains(m["USER"]["surname"], "Smith"), + "Expected m[\"USER\"][\"surname\"] to be equal to 'Smith' instead got %v", + m["USER"]["surname"], + ) + + testing.expectf(t, err == nil, "Expected `ini.load_map_from_string` to return a nil error, got %v", err) +} + +@test +ini_to_string :: proc(t: ^testing.T) { + m := ini.Map{ + "LEVEL" = { + "LOG" = "debug", + }, + } + + str := ini.save_map_to_string(m, context.allocator) + defer delete(str) + delete(m["LEVEL"]) + delete(m) + + testing.expectf( + t, + strings.contains(str, "[LEVEL]LOG = debug"), + "Expected `ini.save_map_to_string` to return a string equal to \"[LEVEL]LOG = debug\", got %v", + str, + ) +} + +@test +ini_iterator :: proc(t: ^testing.T) { + ini_data := ` + [LOG] + level = "devel" + file = "/var/log/testing.log" + + [USER] + first_name = "John" + surname = "Smith" + ` + + i := 0 + iterator := ini.iterator_from_string(ini_data) + for key, value in ini.iterate(&iterator) { + if strings.contains(key, "level") { + testing.expectf( + t, + strings.contains(value, "devel"), + "Expected 'level' to be equal to 'devel' instead got '%v'", + value, + ) + } else if strings.contains(key, "file") { + testing.expectf( + t, + strings.contains(value, "/var/log/testing.log"), + "Expected 'file' to be equal to '/var/log/testing.log' instead got '%v'", + value, + ) + } else if strings.contains(key, "first_name") { + testing.expectf( + t, + strings.contains(value, "John"), + "Expected 'first_name' to be equal to 'John' instead got '%v'", + value, + ) + } else if strings.contains(key, "surname") { + testing.expectf( + t, + strings.contains(value, "Smith"), + "Expected 'surname' to be equal to 'Smith' instead got '%v'", + value, + ) + } + i += 1 + } + testing.expectf(t, i == 4, "Expected to loop 4 times, only looped %v times", i) +} From f560b14d105b25e30c08fed76f8e5324d378ee90 Mon Sep 17 00:00:00 2001 From: Ronald Date: Sat, 20 Jul 2024 17:10:19 +0100 Subject: [PATCH 149/198] Fix typo in name of test --- tests/core/encoding/ini/test_core_ini.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/encoding/ini/test_core_ini.odin b/tests/core/encoding/ini/test_core_ini.odin index 1e6cc246b..9106f61bd 100644 --- a/tests/core/encoding/ini/test_core_ini.odin +++ b/tests/core/encoding/ini/test_core_ini.odin @@ -7,7 +7,7 @@ import "core:strings" import "core:testing" @test -prase_ini :: proc(t: ^testing.T) { +parse_ini :: proc(t: ^testing.T) { ini_data := ` [LOG] level = "devel" From e0a8bd04d5cbccb003bd80486089f0ea11a1857e Mon Sep 17 00:00:00 2001 From: Ronald Date: Sat, 20 Jul 2024 17:10:34 +0100 Subject: [PATCH 150/198] Ensure deletion of maybe is delayed until we're finished with it. --- tests/core/encoding/ini/test_core_ini.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/encoding/ini/test_core_ini.odin b/tests/core/encoding/ini/test_core_ini.odin index 9106f61bd..6e6c8152e 100644 --- a/tests/core/encoding/ini/test_core_ini.odin +++ b/tests/core/encoding/ini/test_core_ini.odin @@ -19,7 +19,7 @@ parse_ini :: proc(t: ^testing.T) { ` m, err := ini.load_map_from_string(ini_data, context.allocator) - ini.delete_map(m) + defer ini.delete_map(m) testing.expectf( t, From f78a792d4802be386a8798de100e17649ca2c3fd Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 20 Jul 2024 18:30:41 +0200 Subject: [PATCH 151/198] Add Lua example to vendor\lua as well as basic tests. --- tests/vendor/all.odin | 1 + tests/vendor/lua/5.4/factorial.lua | 10 +++ tests/vendor/lua/5.4/test_vendor_lua.5.4.odin | 71 +++++++++++++++++++ vendor/lua/README.md | 48 +++++++++++-- 4 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 tests/vendor/lua/5.4/factorial.lua create mode 100644 tests/vendor/lua/5.4/test_vendor_lua.5.4.odin diff --git a/tests/vendor/all.odin b/tests/vendor/all.odin index 1ce56e786..1abbc5d7f 100644 --- a/tests/vendor/all.odin +++ b/tests/vendor/all.odin @@ -1,3 +1,4 @@ package tests_vendor @(require) import "glfw" +@(require) import "lua/5.4" \ No newline at end of file diff --git a/tests/vendor/lua/5.4/factorial.lua b/tests/vendor/lua/5.4/factorial.lua new file mode 100644 index 000000000..00cfb20f7 --- /dev/null +++ b/tests/vendor/lua/5.4/factorial.lua @@ -0,0 +1,10 @@ +-- defines a factorial function +function fact (n) + if n == 0 then + return 1 + else + return n * fact(n-1) + end +end + +return fact(10) \ No newline at end of file diff --git a/tests/vendor/lua/5.4/test_vendor_lua.5.4.odin b/tests/vendor/lua/5.4/test_vendor_lua.5.4.odin new file mode 100644 index 000000000..5cfc2cefa --- /dev/null +++ b/tests/vendor/lua/5.4/test_vendor_lua.5.4.odin @@ -0,0 +1,71 @@ +//+build windows, linux, darwin +package test_vendor_lua_54 + +import "core:testing" +import "core:c" +import lua "vendor:lua/5.4" +import "base:runtime" + +@(test) +// Test context.allocator and returning a string +return_string_with_context_based_allocator :: proc(t: ^testing.T) { + _context := context + + state: ^lua.State + state = lua.newstate(lua_context_allocator, &_context) + defer lua.close(state) + + lua.L_dostring(state, "return 'somestring'") + str := lua.tostring(state, -1) + + testing.expectf( + t, str == "somestring", "Expected Lua to return \"somestring\"", + ) +} + +@(test) +// Test lua.dofile and returning an integer +dofile_factorial :: proc(t: ^testing.T) { + state := lua.L_newstate() + defer lua.close(state) + + FACT_10 :: 3628800 + + res := lua.L_dofile(state, "factorial.lua") + testing.expectf(t, lua.Status(res) == .OK, "Expected L_dofile to return OKAY") + + fact := lua.L_checkinteger(state, -1) + + testing.expectf(t, fact == FACT_10, "Expected factorial(10) to return %v, got %v", FACT_10, fact) +} + +@(test) +// Test that our bindings didn't get out of sync with the API version +verify_lua_api_version :: proc(t: ^testing.T) { + state := lua.L_newstate() + defer lua.close(state) + + version := int(lua.version(state)) + + testing.expectf(t, version == lua.VERSION_NUM, "Expected lua.version to return %v, got %v", lua.VERSION_NUM, version) +} + +// Simple context.allocator-based callback for Lua. Use `lua.newstate` to pass the context as user data. +lua_context_allocator :: proc "c" (ud: rawptr, ptr: rawptr, osize, nsize: c.size_t) -> (buf: rawptr) { + old_size := int(osize) + new_size := int(nsize) + context = (^runtime.Context)(ud)^ + + if ptr == nil { + data, err := runtime.mem_alloc(new_size) + return raw_data(data) if err == .None else nil + } else { + if nsize > 0 { + data, err := runtime.mem_resize(ptr, old_size, new_size) + return raw_data(data) if err == .None else nil + } else { + runtime.mem_free(ptr) + return + } + } +} \ No newline at end of file diff --git a/vendor/lua/README.md b/vendor/lua/README.md index 8f4b0f5a5..4bc7804bb 100644 --- a/vendor/lua/README.md +++ b/vendor/lua/README.md @@ -1,12 +1,50 @@ # Lua in Odin -```odin -import lua "vendor:lua/5.4" // or whatever version you want -``` - Lua packages * `vendor:lua/5.1` (version 5.1.5) * `vendor:lua/5.2` (version 5.2.4) * `vendor:lua/5.3` (version 5.3.6) -* `vendor:lua/5.4` (version 5.4.2) \ No newline at end of file +* `vendor:lua/5.4` (version 5.4.2) + +With custom context-based allocator: + +```odin +package lua_example_with_context + +import "core:fmt" +import lua "vendor:lua/5.4" // or whatever version you want +import "core:c" +import "base:runtime" + +state: ^lua.State + +lua_allocator :: proc "c" (ud: rawptr, ptr: rawptr, osize, nsize: c.size_t) -> (buf: rawptr) { + old_size := int(osize) + new_size := int(nsize) + context = (^runtime.Context)(ud)^ + + if ptr == nil { + data, err := runtime.mem_alloc(new_size) + return raw_data(data) if err == .None else nil + } else { + if nsize > 0 { + data, err := runtime.mem_resize(ptr, old_size, new_size) + return raw_data(data) if err == .None else nil + } else { + runtime.mem_free(ptr) + return + } + } +} + +main :: proc() { + _context := context + state = lua.newstate(lua_allocator, &_context) + defer lua.close(state) + + lua.L_dostring(state, "return 'somestring'") + str := lua.tostring(state, -1) + fmt.println(str) +} +``` \ No newline at end of file From fc5ce30f34163ce1dfa7ad8b01e60317c8d43c01 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 21 Jul 2024 00:37:11 +0200 Subject: [PATCH 152/198] Allow json to unmarshal empty struct. --- core/encoding/json/unmarshal.odin | 3 +-- tests/core/encoding/json/test_core_json.odin | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/core/encoding/json/unmarshal.odin b/core/encoding/json/unmarshal.odin index 1c1801bcd..127bce650 100644 --- a/core/encoding/json/unmarshal.odin +++ b/core/encoding/json/unmarshal.odin @@ -363,8 +363,7 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm } v := v - v = reflect.any_base(v) - ti := type_info_of(v.id) + ti := reflect.type_info_base(type_info_of(v.id)) #partial switch t in ti.variant { case reflect.Type_Info_Struct: diff --git a/tests/core/encoding/json/test_core_json.odin b/tests/core/encoding/json/test_core_json.odin index 10e09df3b..42ac9ce0f 100644 --- a/tests/core/encoding/json/test_core_json.odin +++ b/tests/core/encoding/json/test_core_json.odin @@ -349,6 +349,24 @@ unmarshal_json :: proc(t: ^testing.T) { } } +@test +unmarshal_empty_struct :: proc(t: ^testing.T) { + TestStruct :: struct {} + test := make(map[string]TestStruct) + input: = `{ + "test_1": {}, + "test_2": {} + }` + err := json.unmarshal(transmute([]u8)input, &test) + defer { + for k in test { + delete(k) + } + delete(test) + } + testing.expect(t, err == nil, "Expected empty struct to unmarshal without error") +} + @test surrogate :: proc(t: ^testing.T) { input := `+ + * 😃 - /` From c3a57853e283239573ab981b6c8acc4a66480c9a Mon Sep 17 00:00:00 2001 From: Ronald Date: Sun, 21 Jul 2024 00:00:47 +0100 Subject: [PATCH 153/198] Ensure that values in ini map are unquoted --- core/encoding/ini/ini.odin | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/encoding/ini/ini.odin b/core/encoding/ini/ini.odin index 8010e4e75..8cfdf8c59 100644 --- a/core/encoding/ini/ini.odin +++ b/core/encoding/ini/ini.odin @@ -89,6 +89,8 @@ load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options } if allocated { return v, nil + } else { + return strings.clone(v), nil } } return strings.clone(val) From 1a6885c2a3c35c7399acdd551f6d63dde79645f9 Mon Sep 17 00:00:00 2001 From: Ronald Date: Sun, 21 Jul 2024 00:08:20 +0100 Subject: [PATCH 154/198] Tidy up code --- core/encoding/ini/ini.odin | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/encoding/ini/ini.odin b/core/encoding/ini/ini.odin index 8cfdf8c59..2bb7996a3 100644 --- a/core/encoding/ini/ini.odin +++ b/core/encoding/ini/ini.odin @@ -89,9 +89,8 @@ load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options } if allocated { return v, nil - } else { - return strings.clone(v), nil } + return strings.clone(v), nil } return strings.clone(val) } From 431227d4c50e5b53e5c4c922a76c11ec8464baa3 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 21 Jul 2024 02:52:53 +0200 Subject: [PATCH 155/198] Add NULL check in check_range_stmt Fixes #3953 --- src/check_stmt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 74397828d..6c3570cc8 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -1837,7 +1837,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) if (rs->vals.count == 1) { Type *t = type_deref(operand.type); - if (is_type_map(t) || is_type_bit_set(t)) { + if (t != NULL && (is_type_map(t) || is_type_bit_set(t))) { gbString v = expr_to_string(rs->vals[0]); defer (gb_string_free(v)); error_line("\tSuggestion: place parentheses around the expression\n"); From b84b4c47d7f0da0f97817fa874215046eb1557cd Mon Sep 17 00:00:00 2001 From: flysand7 Date: Sun, 21 Jul 2024 14:34:36 +1100 Subject: [PATCH 156/198] [thread]: Document all functions in core:thread --- core/thread/thread.odin | 281 ++++++++++++++++++++++++++++++++++------ 1 file changed, 242 insertions(+), 39 deletions(-) diff --git a/core/thread/thread.odin b/core/thread/thread.odin index 80e60d6cf..17ba1a0a2 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -6,12 +6,26 @@ import "base:intrinsics" _ :: intrinsics +/* +Value, specifying whether `core:thread` functionality is available on the +current platform. +*/ IS_SUPPORTED :: _IS_SUPPORTED +/* +Type for a procedure that will be run in a thread, after that thread has been +started. +*/ Thread_Proc :: #type proc(^Thread) +/* +Maximum number of user arguments for polymorphic thread procedures. +*/ MAX_USER_ARGUMENTS :: 8 +/* +Type representing the state/flags of the thread. +*/ Thread_State :: enum u8 { Started, Joined, @@ -19,44 +33,48 @@ Thread_State :: enum u8 { Self_Cleanup, } +/* +Type representing a thread handle and the associated with that thread data. +*/ Thread :: struct { using specific: Thread_Os_Specific, flags: bit_set[Thread_State; u8], - id: int, - procedure: Thread_Proc, - - /* - These are values that the user can set as they wish, after the thread has been created. - This data is easily available to the thread proc. - - These fields can be assigned to directly. - - Should be set after the thread is created, but before it is started. - */ - data: rawptr, - user_index: int, - user_args: [MAX_USER_ARGUMENTS]rawptr, - - /* - The context to be used as 'context' in the thread proc. - - This field can be assigned to directly, after the thread has been created, but __before__ the thread has been started. - This field must not be changed after the thread has started. - - NOTE: If you __don't__ set this, the temp allocator will be managed for you; - If you __do__ set this, then you're expected to handle whatever allocators you set, yourself. - - IMPORTANT: - By default, the thread proc will get the same context as `main()` gets. - In this situation, the thread will get a new temporary allocator which will be cleaned up when the thread dies. - ***This does NOT happen when you set `init_context`.*** - This means that if you set `init_context`, but still have the `temp_allocator` field set to the default temp allocator, - then you'll need to call `runtime.default_temp_allocator_destroy(auto_cast the_thread.init_context.temp_allocator.data)` manually, - in order to prevent any memory leaks. - This call ***must*** be done ***in the thread proc*** because the default temporary allocator uses thread local state! - */ + // Thread ID. + id: int, + // The thread procedure. + procedure: Thread_Proc, + // User-supplied pointer, that will be available to the thread once it is + // started. Should be set after the thread has been created, but before + // it is started. + data: rawptr, + // User-supplied integer, that will be available to the thread once it is + // started. Should be set after the thread has been created, but before + // it is started. + user_index: int, + // User-supplied array of arguments, that will be available to the thread, + // once it is started. Should be set after the thread has been created, + // but before it is started. + user_args: [MAX_USER_ARGUMENTS]rawptr, + // The thread context. + // This field can be assigned to directly, after the thread has been + // created, but __before__ the thread has been started. This field must + // not be changed after the thread has started. + // + // **Note**: If this field is **not** set, the temp allocator will be managed + // automatically. If it is set, the allocators must be handled manually. + // + // **IMPORTANT**: + // By default, the thread proc will get the same context as `main()` gets. + // In this situation, the thread will get a new temporary allocator which + // will be cleaned up when the thread dies. ***This does NOT happen when + // `init_context` field is initialized***. + // + // If `init_context` is initialized, and `temp_allocator` field is set to + // the default temp allocator, then `runtime.default_temp_allocator_destroy()` + // procedure needs to be called from the thread procedure, in order to prevent + // any memory leaks. init_context: Maybe(runtime.Context), - + // The allocator used to allocate data for the thread. creation_allocator: mem.Allocator, } @@ -64,6 +82,9 @@ when IS_SUPPORTED { #assert(size_of(Thread{}.user_index) == size_of(uintptr)) } +/* +Type representing priority of a thread. +*/ Thread_Priority :: enum { Normal, Low, @@ -71,74 +92,178 @@ Thread_Priority :: enum { } /* - Creates a thread in a suspended state with the given priority. - To start the thread, call `thread.start()`. +Create a thread in a suspended state with the given priority. - See `thread.create_and_start()`. +This procedure creates a thread that will be set to run the procedure +specified by `procedure` parameter with a specified priority. The returned +thread will be in a suspended state, until `start()` procedure is called. + +To start the thread, call `start()`. Also the `create_and_start()` +procedure can be called to create and start the thread immediately. */ create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread { return _create(procedure, priority) } + +/* +Wait for the thread to finish and free all data associated with it. +*/ destroy :: proc(thread: ^Thread) { _destroy(thread) } +/* +Start a suspended thread. +*/ start :: proc(thread: ^Thread) { _start(thread) } +/* +Check if the thread has finished work. +*/ is_done :: proc(thread: ^Thread) -> bool { return _is_done(thread) } - +/* +Wait for the thread to finish work. +*/ join :: proc(thread: ^Thread) { _join(thread) } - +/* +Wait for all threads to finish work. +*/ join_multiple :: proc(threads: ..^Thread) { _join_multiple(..threads) } +/* +Forcibly terminate a running thread. +*/ terminate :: proc(thread: ^Thread, exit_code: int) { _terminate(thread, exit_code) } +/* +Yield the execution of the current thread to another OS thread or process. +*/ yield :: proc() { _yield() } +/* +Run a procedure on a different thread. +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ run :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) { create_and_start(fn, init_context, priority, true) } +/* +Run a procedure with one pointer parameter on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ run_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) { create_and_start_with_data(data, fn, init_context, priority, true) } +/* +Run a procedure with one polymorphic parameter on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ 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) * MAX_USER_ARGUMENTS { create_and_start_with_poly_data(data, fn, init_context, priority, true) } +/* +Run a procedure with two polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ 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(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { create_and_start_with_poly_data2(arg1, arg2, fn, init_context, priority, true) } +/* +Run a procedure with three polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ 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(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { create_and_start_with_poly_data3(arg1, arg2, arg3, fn, init_context, priority, true) } + +/* +Run a procedure with four polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ 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(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS { create_and_start_with_poly_data4(arg1, arg2, arg3, arg4, fn, init_context, priority, true) } +/* +Run a procedure on a different thread. +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread { thread_proc :: proc(t: ^Thread) { fn := cast(proc())t.data @@ -154,9 +279,22 @@ create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, return t } +/* +Run a procedure with one pointer parameter on a different thread. +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread { thread_proc :: proc(t: ^Thread) { fn := cast(proc(rawptr))t.data @@ -176,6 +314,22 @@ create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_co return t } +/* +Run a procedure with one polymorphic parameter on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { @@ -201,6 +355,22 @@ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_contex return t } +/* +Run a procedure with two polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { @@ -232,6 +402,22 @@ create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), return t } +/* +Run a procedure with three polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { @@ -264,6 +450,23 @@ create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: pr start(t) return t } + +/* +Run a procedure with four polymorphic parameters on a different thread. + +This procedure runs the given procedure on another thread. The context +specified by `init_context` will be used as the context in which `fn` is going +to execute. The thread will have priority specified by the `priority` parameter. + +If `self_cleanup` is specified, after the thread finishes the execution of the +`fn` procedure, the resources associated with the thread are going to be +automatically freed. **Do not** dereference the `^Thread` pointer, if this +flag is specified. + +**IMPORTANT**: If `init_context` is specified and the default temporary allocator +is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` +in order to free the resources associated with the temporary allocations. +*/ create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> ^Thread where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { From 3e618bed406bbe8c86290bbd855a742f9e45de73 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 22 Jul 2024 00:51:38 +0200 Subject: [PATCH 157/198] fix `reflect.any_base` and `reflect.any_core` with any's containing nil --- core/reflect/reflect.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/reflect/reflect.odin b/core/reflect/reflect.odin index e6d2bc87a..23c0f803e 100644 --- a/core/reflect/reflect.odin +++ b/core/reflect/reflect.odin @@ -143,7 +143,7 @@ when !ODIN_NO_RTTI { @(require_results) any_base :: proc(v: any) -> any { v := v - if v != nil { + if v.id != nil { v.id = typeid_base(v.id) } return v @@ -151,7 +151,7 @@ any_base :: proc(v: any) -> any { @(require_results) any_core :: proc(v: any) -> any { v := v - if v != nil { + if v.id != nil { v.id = typeid_core(v.id) } return v From a055c03de9c1166c24559170b367f166c206faa3 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 22 Jul 2024 01:11:01 +0200 Subject: [PATCH 158/198] use #directory in lua test --- tests/vendor/lua/5.4/test_vendor_lua.5.4.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/vendor/lua/5.4/test_vendor_lua.5.4.odin b/tests/vendor/lua/5.4/test_vendor_lua.5.4.odin index 5cfc2cefa..e331200ea 100644 --- a/tests/vendor/lua/5.4/test_vendor_lua.5.4.odin +++ b/tests/vendor/lua/5.4/test_vendor_lua.5.4.odin @@ -31,7 +31,7 @@ dofile_factorial :: proc(t: ^testing.T) { FACT_10 :: 3628800 - res := lua.L_dofile(state, "factorial.lua") + res := lua.L_dofile(state, #directory + "/factorial.lua") testing.expectf(t, lua.Status(res) == .OK, "Expected L_dofile to return OKAY") fact := lua.L_checkinteger(state, -1) From 4d86012d968925fcf8bcc263e4942781eab1cb31 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 22 Jul 2024 01:13:50 +0200 Subject: [PATCH 159/198] install lua for new lua vendor tests --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca3d87b12..84c85457f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,13 +91,13 @@ jobs: - name: Download LLVM (MacOS Intel) if: matrix.os == 'macos-13' run: | - brew install llvm@17 + brew install llvm@17 lua@5.4 echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH - name: Download LLVM (MacOS ARM) if: matrix.os == 'macos-14' run: | - brew install llvm@17 wasmtime + brew install llvm@17 wasmtime lua@5.4 echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH - name: Build Odin From 1873f7215dd42e4ee628fcd0947579bf42557dbe Mon Sep 17 00:00:00 2001 From: PucklaJ Date: Mon, 22 Jul 2024 11:03:25 +0200 Subject: [PATCH 160/198] [sys/linux]: Change execveat flags type to Execveat_Flags --- core/sys/linux/sys.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index 03c34223c..f7cacc544 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -2814,7 +2814,7 @@ getrandom :: proc "contextless" (buf: []u8, flags: Get_Random_Flags) -> (int, Er Execute program relative to a directory file descriptor. Available since Linux 3.19. */ -execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring, flags: FD_Flags = {}) -> (Errno) { +execveat :: proc "contextless" (dirfd: Fd, name: cstring, argv: [^]cstring, envp: [^]cstring, flags: Execveat_Flags = {}) -> (Errno) { ret := syscall(SYS_execveat, dirfd, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, transmute(i32) flags) return Errno(-ret) } From 39657e4d968711c11254fa0ee26d576cfc367071 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 22 Jul 2024 15:15:51 +0200 Subject: [PATCH 161/198] Fix #3473 Fix the problem where the initial package's directory name ended in .odin. --- src/parser.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/parser.cpp b/src/parser.cpp index 5a3fc1634..f2d2a1d15 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -5609,7 +5609,7 @@ gb_internal AstPackage *try_add_import_path(Parser *p, String path, String const pkg->foreign_files.allocator = permanent_allocator(); // NOTE(bill): Single file initial package - if (kind == Package_Init && string_ends_with(path, FILE_EXT)) { + if (kind == Package_Init && !path_is_directory(path) && string_ends_with(path, FILE_EXT)) { FileInfo fi = {}; fi.name = filename_from_path(path); fi.fullpath = path; @@ -6529,6 +6529,7 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { GB_ASSERT(init_filename.text[init_filename.len] == 0); String init_fullpath = path_to_full_path(permanent_allocator(), init_filename); + if (!path_is_directory(init_fullpath)) { String const ext = str_lit(".odin"); if (!string_ends_with(init_fullpath, ext)) { @@ -6543,6 +6544,7 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { if ((build_context.command_kind & Command__does_build) && build_context.build_mode == BuildMode_Executable) { String short_path = filename_from_path(path); + char *cpath = alloc_cstring(temporary_allocator(), short_path); if (gb_file_exists(cpath)) { error({}, "Please specify the executable name with -out: as a directory exists with the same name in the current working directory"); From 90a4d12b30f24c4a44a8c8e606ce56efde56cf50 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 22 Jul 2024 16:11:33 +0200 Subject: [PATCH 162/198] Fix .exe path is directory check. --- src/parser.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index f2d2a1d15..eaf43b5bc 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6543,10 +6543,9 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { } if ((build_context.command_kind & Command__does_build) && build_context.build_mode == BuildMode_Executable) { - String short_path = filename_from_path(path); - - char *cpath = alloc_cstring(temporary_allocator(), short_path); - if (gb_file_exists(cpath)) { + String output_path = path_to_string(temporary_allocator(), build_context.build_paths[8]); + char *cpath = alloc_cstring(temporary_allocator(), output_path); + if (path_is_directory(output_path) && gb_file_exists(cpath)) { error({}, "Please specify the executable name with -out: as a directory exists with the same name in the current working directory"); return ParseFile_DirectoryAlreadyExists; } From 07d2aba31037c645327f28eba179d6cdba245cca Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 22 Jul 2024 16:36:21 +0200 Subject: [PATCH 163/198] Simplify exe path check. --- src/parser.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index eaf43b5bc..aba2b8276 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6544,8 +6544,7 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { if ((build_context.command_kind & Command__does_build) && build_context.build_mode == BuildMode_Executable) { String output_path = path_to_string(temporary_allocator(), build_context.build_paths[8]); - char *cpath = alloc_cstring(temporary_allocator(), output_path); - if (path_is_directory(output_path) && gb_file_exists(cpath)) { + if (path_is_directory(output_path)) { error({}, "Please specify the executable name with -out: as a directory exists with the same name in the current working directory"); return ParseFile_DirectoryAlreadyExists; } From fcaa47986aa5477e60a67a4ace5c7b020b214afd Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 22 Jul 2024 16:29:29 +0100 Subject: [PATCH 164/198] Improve error handling for invalid syntax doing `[*]T` --- src/check_type.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/check_type.cpp b/src/check_type.cpp index e3609970a..22b77db3c 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2454,9 +2454,15 @@ gb_internal i64 check_array_count(CheckerContext *ctx, Operand *o, Ast *e) { if (e == nullptr) { return 0; } - if (e->kind == Ast_UnaryExpr && - e->UnaryExpr.op.kind == Token_Question) { - return -1; + if (e->kind == Ast_UnaryExpr) { + Token op = e->UnaryExpr.op; + if (op.kind == Token_Question) { + return -1; + } + if (e->UnaryExpr.expr == nullptr) { + error(op, "Invalid array count '[%.*s]'", LIT(op.string)); + return 0; + } } check_expr_or_type(ctx, o, e); From ccf8b2764d4a3add4a575b2c88b710b2d15ebae8 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 22 Jul 2024 18:00:57 +0200 Subject: [PATCH 165/198] Create README.md --- examples/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 examples/README.md diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000..27072a480 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,9 @@ +# Examples + +The `example` directory contains two packages: + +A [demo](examples/demo) illustrating the basics of Odin. + +It further contains [all](examples/all), which imports all [core](core) and [vendor](vendor) packages so we can conveniently run `odin check` on everything at once. + +For additional example code, see the [examples](https://github.com/odin-lang/examples) repository. From 9cad8179b77a47ad78976ad687e93a8872cbd393 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 22 Jul 2024 20:00:25 +0200 Subject: [PATCH 166/198] Clarify core:time Unix timestamp --- core/time/time.odin | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/time/time.odin b/core/time/time.odin index ef2bc0aed..fad6512f3 100644 --- a/core/time/time.odin +++ b/core/time/time.odin @@ -5,6 +5,7 @@ import dt "core:time/datetime" /* Type representing duration, with nanosecond precision. +This is the regular Unix timestamp, scaled to nanosecond precision. */ Duration :: distinct i64 @@ -425,7 +426,7 @@ Alias for `time_to_unix`. to_unix_seconds :: time_to_unix /* -Obtain the unix seconds from a time. +Obtain the Unix timestamp in seconds from a Time. */ time_to_unix :: proc "contextless" (t: Time) -> i64 { return t._nsec/1e9 @@ -437,7 +438,7 @@ Alias for `time_to_unix_nano`. to_unix_nanoseconds :: time_to_unix_nano /* -Obtain the unix nanoseconds from a time. +Obtain the Unix timestamp in nanoseconds from a Time. */ time_to_unix_nano :: proc "contextless" (t: Time) -> i64 { return t._nsec From ef84382f23920cfd011529db9acd63947bd40b9c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 22 Jul 2024 20:11:23 +0100 Subject: [PATCH 167/198] Add suggestion for #3961 --- src/check_type.cpp | 19 +++++++++++++++++++ src/types.cpp | 20 ++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/check_type.cpp b/src/check_type.cpp index 22b77db3c..428fe8451 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1559,11 +1559,30 @@ gb_internal Type *determine_type_from_polymorphic(CheckerContext *ctx, Type *pol return poly_type; } if (show_error) { + ERROR_BLOCK(); gbString pts = type_to_string(poly_type); gbString ots = type_to_string(operand.type, true); defer (gb_string_free(pts)); defer (gb_string_free(ots)); error(operand.expr, "Cannot determine polymorphic type from parameter: '%s' to '%s'", ots, pts); + + Type *pt = poly_type; + while (pt && pt->kind == Type_Generic && pt->Generic.specialized) { + pt = pt->Generic.specialized; + } + if (is_type_slice(pt) && + (is_type_dynamic_array(operand.type) || is_type_array(operand.type))) { + Ast *expr = unparen_expr(operand.expr); + if (expr->kind == Ast_CompoundLit) { + gbString es = type_to_string(base_any_array_type(operand.type)); + error_line("\tSuggestion: Try using a slice compound literal instead '[]%s{...}'\n", es); + gb_string_free(es); + } else { + gbString os = expr_to_string(operand.expr); + error_line("\tSuggestion: Try slicing the value with '%s[:]'\n", os); + gb_string_free(os); + } + } } return t_invalid; } diff --git a/src/types.cpp b/src/types.cpp index 3f86d4c50..944760142 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -1637,6 +1637,26 @@ gb_internal Type *base_array_type(Type *t) { return t; } + +gb_internal Type *base_any_array_type(Type *t) { + Type *bt = base_type(t); + if (is_type_array(bt)) { + return bt->Array.elem; + } else if (is_type_slice(bt)) { + return bt->Slice.elem; + } else if (is_type_dynamic_array(bt)) { + return bt->DynamicArray.elem; + } else if (is_type_enumerated_array(bt)) { + return bt->EnumeratedArray.elem; + } else if (is_type_simd_vector(bt)) { + return bt->SimdVector.elem; + } else if (is_type_matrix(bt)) { + return bt->Matrix.elem; + } + return t; +} + + gb_internal bool is_type_generic(Type *t) { t = base_type(t); return t->kind == Type_Generic; From 12b971746cd18a55e637b63d02ccf23e85c86e40 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 22 Jul 2024 22:42:29 +0100 Subject: [PATCH 168/198] `vendor:compress/lz4` Bindings to lz4 library --- vendor/compress/lz4/lib/liblz4_static.lib | Bin 0 -> 239998 bytes vendor/compress/lz4/lz4.odin | 541 +++++++++++++ vendor/compress/lz4/src/lz4.h | 884 ++++++++++++++++++++++ vendor/compress/lz4/src/lz4frame.h | 751 ++++++++++++++++++ vendor/compress/lz4/src/lz4hc.h | 414 ++++++++++ 5 files changed, 2590 insertions(+) create mode 100644 vendor/compress/lz4/lib/liblz4_static.lib create mode 100644 vendor/compress/lz4/lz4.odin create mode 100644 vendor/compress/lz4/src/lz4.h create mode 100644 vendor/compress/lz4/src/lz4frame.h create mode 100644 vendor/compress/lz4/src/lz4hc.h diff --git a/vendor/compress/lz4/lib/liblz4_static.lib b/vendor/compress/lz4/lib/liblz4_static.lib new file mode 100644 index 0000000000000000000000000000000000000000..dc7610303da2eedc8bb0df37b3686d479b097ed6 GIT binary patch literal 239998 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 PCOO97^^CEjeER=iP>pp! literal 0 HcmV?d00001 diff --git a/vendor/compress/lz4/lz4.odin b/vendor/compress/lz4/lz4.odin new file mode 100644 index 000000000..62027d9d9 --- /dev/null +++ b/vendor/compress/lz4/lz4.odin @@ -0,0 +1,541 @@ +package vendor_compress_lz4 + +when ODIN_OS == .Windows { + foreign import lib "lib/libz4_static.lib" +} + +import "core:c" + +VERSION_MAJOR :: 1 /* for breaking interface changes */ +VERSION_MINOR :: 10 /* for new (non-breaking) interface capabilities */ +VERSION_RELEASE :: 0 /* for tweaks, bug-fixes, or development */ + +VERSION_NUMBER :: VERSION_MAJOR *100*100 + VERSION_MINOR *100 + VERSION_RELEASE + +MEMORY_USAGE_MIN :: 10 +MEMORY_USAGE_DEFAULT :: 14 +MEMORY_USAGE_MAX :: 20 + +MEMORY_USAGE :: MEMORY_USAGE_DEFAULT + +MAX_INPUT_SIZE :: 0x7E000000 /* 2_113_929_216 bytes */ + + +COMPRESSBOUND :: #force_inline proc "c" (isize: c.int) -> c.int { + return u32(isize) > MAX_INPUT_SIZE ? 0 : isize + (isize/255) + 16 +} + + +DECODER_RING_BUFFER_SIZE :: #force_inline proc "c" (maxBlockSize: c.int) -> c.int { + return 65536 + 14 + maxBlockSize /* for static allocation; maxBlockSize presumed valid */ +} + +@(default_calling_convention="c", link_prefix="LZ4_") +foreign lib { + versionNumber :: proc() -> c.int --- /**< library version number; useful to check dll version; requires v1.3.0+ */ + versionString :: proc() -> cstring --- /**< library version string; useful to check dll version; requires v1.7.5+ */ + + /*! LZ4_compress_default() : + * Compresses 'srcSize' bytes from buffer 'src' + * into already allocated 'dst' buffer of size 'dstCapacity'. + * Compression is guaranteed to succeed if 'dstCapacity' >= LZ4_compressBound(srcSize). + * It also runs faster, so it's a recommended setting. + * If the function cannot compress 'src' into a more limited 'dst' budget, + * compression stops *immediately*, and the function result is zero. + * In which case, 'dst' content is undefined (invalid). + * srcSize : max supported value is LZ4_MAX_INPUT_SIZE. + * dstCapacity : size of buffer 'dst' (which must be already allocated) + * @return : the number of bytes written into buffer 'dst' (necessarily <= dstCapacity) + * or 0 if compression fails + * Note : This function is protected against buffer overflow scenarios (never writes outside 'dst' buffer, nor read outside 'source' buffer). + */ + compress_default :: proc(src, dst: [^]byte, srcSize, dstCapacity: c.int) -> c.int --- + + /*! LZ4_decompress_safe() : + * @compressedSize : is the exact complete size of the compressed block. + * @dstCapacity : is the size of destination buffer (which must be already allocated), + * presumed an upper bound of decompressed size. + * @return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity) + * If destination buffer is not large enough, decoding will stop and output an error code (negative value). + * If the source stream is detected malformed, the function will stop decoding and return a negative result. + * Note 1 : This function is protected against malicious data packets : + * it will never writes outside 'dst' buffer, nor read outside 'source' buffer, + * even if the compressed block is maliciously modified to order the decoder to do these actions. + * In such case, the decoder stops immediately, and considers the compressed block malformed. + * Note 2 : compressedSize and dstCapacity must be provided to the function, the compressed block does not contain them. + * The implementation is free to send / store / derive this information in whichever way is most beneficial. + * If there is a need for a different format which bundles together both compressed data and its metadata, consider looking at lz4frame.h instead. + */ + decompress_safe :: proc(src, dst: [^]byte, compressedSize, dstCapacity: c.int) -> c.int --- + + + /*! LZ4_compressBound() : + Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible) + This function is primarily useful for memory allocation purposes (destination buffer size). + Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example). + Note that LZ4_compress_default() compresses faster when dstCapacity is >= LZ4_compressBound(srcSize) + inputSize : max supported value is LZ4_MAX_INPUT_SIZE + return : maximum output size in a "worst case" scenario + or 0, if input size is incorrect (too large or negative) + */ + compressBound :: proc(inputSize: c.int) -> c.int --- + + /*! LZ4_compress_fast() : + Same as LZ4_compress_default(), but allows selection of "acceleration" factor. + The larger the acceleration value, the faster the algorithm, but also the lesser the compression. + It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed. + An acceleration value of "1" is the same as regular LZ4_compress_default() + Values <= 0 will be replaced by LZ4_ACCELERATION_DEFAULT (currently == 1, see lz4.c). + Values > LZ4_ACCELERATION_MAX will be replaced by LZ4_ACCELERATION_MAX (currently == 65537, see lz4.c). + */ + compress_fast :: proc(src, dst: [^]byte, srcSize, dstCapacity: c.int, acceleration: c.int) -> c.int --- + + + /*! LZ4_compress_fast_extState() : + * Same as LZ4_compress_fast(), using an externally allocated memory space for its state. + * Use LZ4_sizeofState() to know how much memory must be allocated, + * and allocate it on 8-bytes boundaries (using `malloc()` typically). + * Then, provide this buffer as `void* state` to compression function. + */ + sizeofState :: proc() -> c.int --- + compress_fast_extState :: proc (state: rawptr, src, dst: [^]byte, srcSize, dstCapacity: c.int, acceleration: c.int) -> c.int --- + + + /*! LZ4_compress_destSize() : + * Reverse the logic : compresses as much data as possible from 'src' buffer + * into already allocated buffer 'dst', of size >= 'dstCapacity'. + * This function either compresses the entire 'src' content into 'dst' if it's large enough, + * or fill 'dst' buffer completely with as much data as possible from 'src'. + * note: acceleration parameter is fixed to "default". + * + * *srcSizePtr : in+out parameter. Initially contains size of input. + * Will be modified to indicate how many bytes where read from 'src' to fill 'dst'. + * New value is necessarily <= input value. + * @return : Nb bytes written into 'dst' (necessarily <= dstCapacity) + * or 0 if compression fails. + * + * Note : from v1.8.2 to v1.9.1, this function had a bug (fixed in v1.9.2+): + * the produced compressed content could, in specific circumstances, + * require to be decompressed into a destination buffer larger + * by at least 1 byte than the content to decompress. + * If an application uses `LZ4_compress_destSize()`, + * it's highly recommended to update liblz4 to v1.9.2 or better. + * If this can't be done or ensured, + * the receiving decompression function should provide + * a dstCapacity which is > decompressedSize, by at least 1 byte. + * See https://github.com/lz4/lz4/issues/859 for details + */ + compress_destSize :: proc(src, dst: [^]byte, srcSizePtr: ^c.int, targetDstSize: c.int) -> c.int --- + + + /*! LZ4_decompress_safe_partial() : + * Decompress an LZ4 compressed block, of size 'srcSize' at position 'src', + * into destination buffer 'dst' of size 'dstCapacity'. + * Up to 'targetOutputSize' bytes will be decoded. + * The function stops decoding on reaching this objective. + * This can be useful to boost performance + * whenever only the beginning of a block is required. + * + * @return : the number of bytes decoded in `dst` (necessarily <= targetOutputSize) + * If source stream is detected malformed, function returns a negative result. + * + * Note 1 : @return can be < targetOutputSize, if compressed block contains less data. + * + * Note 2 : targetOutputSize must be <= dstCapacity + * + * Note 3 : this function effectively stops decoding on reaching targetOutputSize, + * so dstCapacity is kind of redundant. + * This is because in older versions of this function, + * decoding operation would still write complete sequences. + * Therefore, there was no guarantee that it would stop writing at exactly targetOutputSize, + * it could write more bytes, though only up to dstCapacity. + * Some "margin" used to be required for this operation to work properly. + * Thankfully, this is no longer necessary. + * The function nonetheless keeps the same signature, in an effort to preserve API compatibility. + * + * Note 4 : If srcSize is the exact size of the block, + * then targetOutputSize can be any value, + * including larger than the block's decompressed size. + * The function will, at most, generate block's decompressed size. + * + * Note 5 : If srcSize is _larger_ than block's compressed size, + * then targetOutputSize **MUST** be <= block's decompressed size. + * Otherwise, *silent corruption will occur*. + */ + decompress_safe_partial :: proc (src, dst: [^]byte, srcSize, targetOutputSize, dstCapacity: c.int) -> c.int --- + + + createStream :: proc() -> ^stream_t --- + freeStream :: proc(streamPtr: ^stream_t) -> c.int --- + + /*! LZ4_resetStream_fast() : v1.9.0+ + * Use this to prepare an LZ4_stream_t for a new chain of dependent blocks + * (e.g., LZ4_compress_fast_continue()). + * + * An LZ4_stream_t must be initialized once before usage. + * This is automatically done when created by LZ4_createStream(). + * However, should the LZ4_stream_t be simply declared on stack (for example), + * it's necessary to initialize it first, using LZ4_initStream(). + * + * After init, start any new stream with LZ4_resetStream_fast(). + * A same LZ4_stream_t can be re-used multiple times consecutively + * and compress multiple streams, + * provided that it starts each new stream with LZ4_resetStream_fast(). + * + * LZ4_resetStream_fast() is much faster than LZ4_initStream(), + * but is not compatible with memory regions containing garbage data. + * + * Note: it's only useful to call LZ4_resetStream_fast() + * in the context of streaming compression. + * The *extState* functions perform their own resets. + * Invoking LZ4_resetStream_fast() before is redundant, and even counterproductive. + */ + resetStream_fast :: proc(streamPtr: ^stream_t) --- + + + /*! LZ4_loadDict() : + * Use this function to reference a static dictionary into LZ4_stream_t. + * The dictionary must remain available during compression. + * LZ4_loadDict() triggers a reset, so any previous data will be forgotten. + * The same dictionary will have to be loaded on decompression side for successful decoding. + * Dictionary are useful for better compression of small data (KB range). + * While LZ4 itself accepts any input as dictionary, dictionary efficiency is also a topic. + * When in doubt, employ the Zstandard's Dictionary Builder. + * Loading a size of 0 is allowed, and is the same as reset. + * @return : loaded dictionary size, in bytes (note: only the last 64 KB are loaded) + */ + loadDict :: proc(streamPtr: ^stream_t, dictionary: [^]byte, dictSize: c.int) -> c.int --- + + /*! LZ4_loadDictSlow() : v1.10.0+ + * Same as LZ4_loadDict(), + * but uses a bit more cpu to reference the dictionary content more thoroughly. + * This is expected to slightly improve compression ratio. + * The extra-cpu cost is likely worth it if the dictionary is re-used across multiple sessions. + * @return : loaded dictionary size, in bytes (note: only the last 64 KB are loaded) + */ + loadDictSlow :: proc(streamPtr: ^stream_t, dictionary: [^]byte, dictSize: c.int) -> c.int --- + + /*! LZ4_attach_dictionary() : stable since v1.10.0 + * + * This allows efficient re-use of a static dictionary multiple times. + * + * Rather than re-loading the dictionary buffer into a working context before + * each compression, or copying a pre-loaded dictionary's LZ4_stream_t into a + * working LZ4_stream_t, this function introduces a no-copy setup mechanism, + * in which the working stream references @dictionaryStream in-place. + * + * Several assumptions are made about the state of @dictionaryStream. + * Currently, only states which have been prepared by LZ4_loadDict() or + * LZ4_loadDictSlow() should be expected to work. + * + * Alternatively, the provided @dictionaryStream may be NULL, + * in which case any existing dictionary stream is unset. + * + * If a dictionary is provided, it replaces any pre-existing stream history. + * The dictionary contents are the only history that can be referenced and + * logically immediately precede the data compressed in the first subsequent + * compression call. + * + * The dictionary will only remain attached to the working stream through the + * first compression call, at the end of which it is cleared. + * @dictionaryStream stream (and source buffer) must remain in-place / accessible / unchanged + * through the completion of the compression session. + * + * Note: there is no equivalent LZ4_attach_*() method on the decompression side + * because there is no initialization cost, hence no need to share the cost across multiple sessions. + * To decompress LZ4 blocks using dictionary, attached or not, + * just employ the regular LZ4_setStreamDecode() for streaming, + * or the stateless LZ4_decompress_safe_usingDict() for one-shot decompression. + */ + attach_dictionary :: proc(workingStream, dictionaryStream: ^stream_t) --- + + /*! LZ4_compress_fast_continue() : + * Compress 'src' content using data from previously compressed blocks, for better compression ratio. + * 'dst' buffer must be already allocated. + * If dstCapacity >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster. + * + * @return : size of compressed block + * or 0 if there is an error (typically, cannot fit into 'dst'). + * + * Note 1 : Each invocation to LZ4_compress_fast_continue() generates a new block. + * Each block has precise boundaries. + * Each block must be decompressed separately, calling LZ4_decompress_*() with relevant metadata. + * It's not possible to append blocks together and expect a single invocation of LZ4_decompress_*() to decompress them together. + * + * Note 2 : The previous 64KB of source data is __assumed__ to remain present, unmodified, at same address in memory ! + * + * Note 3 : When input is structured as a double-buffer, each buffer can have any size, including < 64 KB. + * Make sure that buffers are separated, by at least one byte. + * This construction ensures that each block only depends on previous block. + * + * Note 4 : If input buffer is a ring-buffer, it can have any size, including < 64 KB. + * + * Note 5 : After an error, the stream status is undefined (invalid), it can only be reset or freed. + */ + compress_fast_continue :: proc(streamPtr: ^stream_t, src, dst: [^]byte, srcSize, dstCapacity: c.int, acceleration: c.int) -> c.int --- + + /*! LZ4_saveDict() : + * If last 64KB data cannot be guaranteed to remain available at its current memory location, + * save it into a safer place (char* safeBuffer). + * This is schematically equivalent to a memcpy() followed by LZ4_loadDict(), + * but is much faster, because LZ4_saveDict() doesn't need to rebuild tables. + * @return : saved dictionary size in bytes (necessarily <= maxDictSize), or 0 if error. + */ + saveDict :: proc(streamPtr: ^stream_t, safeBuffer: [^]byte, maxDictSize: c.int) -> c.int --- + + + createStreamDecode :: proc() -> ^streamDecode_t --- + freeStreamDecode :: proc(LZ4_stream: ^streamDecode_t) -> c.int --- + + /*! LZ4_setStreamDecode() : + * An LZ4_streamDecode_t context can be allocated once and re-used multiple times. + * Use this function to start decompression of a new stream of blocks. + * A dictionary can optionally be set. Use NULL or size 0 for a reset order. + * Dictionary is presumed stable : it must remain accessible and unmodified during next decompression. + * @return : 1 if OK, 0 if error + */ + setStreamDecode :: proc(LZ4_streamDecode: ^streamDecode_t, dictionary: [^]byte, dictSize: c.int) -> c.int --- + + /*! LZ4_decoderRingBufferSize() : v1.8.2+ + * Note : in a ring buffer scenario (optional), + * blocks are presumed decompressed next to each other + * up to the moment there is not enough remaining space for next block (remainingSize < maxBlockSize), + * at which stage it resumes from beginning of ring buffer. + * When setting such a ring buffer for streaming decompression, + * provides the minimum size of this ring buffer + * to be compatible with any source respecting maxBlockSize condition. + * @return : minimum ring buffer size, + * or 0 if there is an error (invalid maxBlockSize). + */ + decoderRingBufferSize :: proc(maxBlockSize: c.int) -> c.int --- + + /*! LZ4_decompress_safe_continue() : + * This decoding function allows decompression of consecutive blocks in "streaming" mode. + * The difference with the usual independent blocks is that + * new blocks are allowed to find references into former blocks. + * A block is an unsplittable entity, and must be presented entirely to the decompression function. + * LZ4_decompress_safe_continue() only accepts one block at a time. + * It's modeled after `LZ4_decompress_safe()` and behaves similarly. + * + * @LZ4_streamDecode : decompression state, tracking the position in memory of past data + * @compressedSize : exact complete size of one compressed block. + * @dstCapacity : size of destination buffer (which must be already allocated), + * must be an upper bound of decompressed size. + * @return : number of bytes decompressed into destination buffer (necessarily <= dstCapacity) + * If destination buffer is not large enough, decoding will stop and output an error code (negative value). + * If the source stream is detected malformed, the function will stop decoding and return a negative result. + * + * The last 64KB of previously decoded data *must* remain available and unmodified + * at the memory position where they were previously decoded. + * If less than 64KB of data has been decoded, all the data must be present. + * + * Special : if decompression side sets a ring buffer, it must respect one of the following conditions : + * - Decompression buffer size is _at least_ LZ4_decoderRingBufferSize(maxBlockSize). + * maxBlockSize is the maximum size of any single block. It can have any value > 16 bytes. + * In which case, encoding and decoding buffers do not need to be synchronized. + * Actually, data can be produced by any source compliant with LZ4 format specification, and respecting maxBlockSize. + * - Synchronized mode : + * Decompression buffer size is _exactly_ the same as compression buffer size, + * and follows exactly same update rule (block boundaries at same positions), + * and decoding function is provided with exact decompressed size of each block (exception for last block of the stream), + * _then_ decoding & encoding ring buffer can have any size, including small ones ( < 64 KB). + * - Decompression buffer is larger than encoding buffer, by a minimum of maxBlockSize more bytes. + * In which case, encoding and decoding buffers do not need to be synchronized, + * and encoding ring buffer can have any size, including small ones ( < 64 KB). + * + * Whenever these conditions are not possible, + * save the last 64KB of decoded data into a safe buffer where it can't be modified during decompression, + * then indicate where this data is saved using LZ4_setStreamDecode(), before decompressing next block. + */ + decompress_safe_continue :: proc(LZ4_streamDecode: ^streamDecode_t, src, dst: [^]byte, srcSize, dstCapacity: c.int) -> c.int --- + + + /*! LZ4_decompress_safe_usingDict() : + * Works the same as + * a combination of LZ4_setStreamDecode() followed by LZ4_decompress_safe_continue() + * However, it's stateless: it doesn't need any LZ4_streamDecode_t state. + * Dictionary is presumed stable : it must remain accessible and unmodified during decompression. + * Performance tip : Decompression speed can be substantially increased + * when dst == dictStart + dictSize. + */ + decompress_safe_usingDict :: proc(src, dst: [^]byte, srcSize, dstCapacity: c.int, dictStart: [^]byte, dictSize: c.int) -> c.int --- + + /*! LZ4_decompress_safe_partial_usingDict() : + * Behaves the same as LZ4_decompress_safe_partial() + * with the added ability to specify a memory segment for past data. + * Performance tip : Decompression speed can be substantially increased + * when dst == dictStart + dictSize. + */ + decompress_safe_partial_usingDict :: proc(src, dst: [^]byte, compressedSize, targetOutputSize, maxOutputSize: c.int, dictStart: [^]byte, dictSize: c.int) -> c.int --- + +} + + +STREAM_MINSIZE :: (1 << MEMORY_USAGE) + 32 /* static size, for inter-version compatibility */ + +stream_t :: struct #raw_union { + minStateSize: [STREAM_MINSIZE]byte, + internal_donotuse: stream_t_internal, +} + + +HASHLOG :: MEMORY_USAGE-2 +HASHTABLESIZE :: 1 << MEMORY_USAGE +HASH_SIZE_U32 :: 1 << HASHLOG /* required as macro for static allocation */ + +stream_t_internal :: struct { + hashTable: [HASH_SIZE_U32]u32, + dictionary: [^]byte, + dictCtx: ^stream_t_internal, + currentOffset: u32, + tableType: u32, + dictSize: u32, + /* Implicit padding to ensure structure is aligned */ +} + + +STREAMDECODE_MINSIZE :: 32 +streamDecode_t :: struct #raw_union { + minStateSize: [STREAMDECODE_MINSIZE]byte, + internal_donotuse: streamDecode_t_internal, +} + +streamDecode_t_internal :: struct { + externalDict: [^]byte, + prefixEnd: [^]byte, + extDictSize: c.size_t, + prefixSize: c.size_t, +} + + + +/////////////////// +// lz4hc + +CLEVEL_MIN :: 2 +CLEVEL_DEFAULT :: 9 +CLEVEL_OPT_MIN :: 10 +CLEVEL_MAX :: 12 + + +@(default_calling_convention="c", link_prefix="LZ4_") +foreign lib { + /*! LZ4_compress_HC() : + * Compress data from `src` into `dst`, using the powerful but slower "HC" algorithm. + * `dst` must be already allocated. + * Compression is guaranteed to succeed if `dstCapacity >= LZ4_compressBound(srcSize)` (see "lz4.h") + * Max supported `srcSize` value is LZ4_MAX_INPUT_SIZE (see "lz4.h") + * `compressionLevel` : any value between 1 and LZ4HC_CLEVEL_MAX will work. + * Values > LZ4HC_CLEVEL_MAX behave the same as LZ4HC_CLEVEL_MAX. + * @return : the number of bytes written into 'dst' + * or 0 if compression fails. + */ + compress_HC :: proc(src, dst: [^]byte, srcSize, dstCapacity, compressionLevel: c.int) -> c.int --- + + + /*! LZ4_compress_HC_extStateHC() : + * Same as LZ4_compress_HC(), but using an externally allocated memory segment for `state`. + * `state` size is provided by LZ4_sizeofStateHC(). + * Memory segment must be aligned on 8-bytes boundaries (which a normal malloc() should do properly). + */ + sizeofStateHC :: proc() -> c.int --- + compress_HC_extStateHC :: proc(stateHC: rawptr, src, dst: [^]byte, srcSize, maxDstSize: c.int, compressionLevel: c.int) -> c.int --- + + + /*! LZ4_compress_HC_destSize() : v1.9.0+ + * Will compress as much data as possible from `src` + * to fit into `targetDstSize` budget. + * Result is provided in 2 parts : + * @return : the number of bytes written into 'dst' (necessarily <= targetDstSize) + * or 0 if compression fails. + * `srcSizePtr` : on success, *srcSizePtr is updated to indicate how much bytes were read from `src` + */ + compress_HC_destSize :: proc(stateHC: rawptr, src, dst: [^]byte, srcSizePtr: ^c.int, targetDstSize: c.int, compressionLevel: c.int) -> c.int --- + + /*! LZ4_createStreamHC() and LZ4_freeStreamHC() : + * These functions create and release memory for LZ4 HC streaming state. + * Newly created states are automatically initialized. + * A same state can be used multiple times consecutively, + * starting with LZ4_resetStreamHC_fast() to start a new stream of blocks. + */ + createStreamHC :: proc() -> ^streamHC_t --- + freeStreamHC :: proc(streamHCPtr: ^streamHC_t) -> c.int --- + + resetStreamHC_fast :: proc(streamHCPtr: ^streamHC_t, compressionLevel: c.int) --- /* v1.9.0+ */ + loadDictHC :: proc(streamHCPtr: ^streamHC_t, dictionary: [^]byte, dictSize: c.int) -> c.int --- + + compress_HC_continue :: proc(streamHCPtr: ^streamHC_t, src, dst: [^]byte, srcSize, maxDstSize: c.int) -> c.int --- + + /*! LZ4_compress_HC_continue_destSize() : v1.9.0+ + * Similar to LZ4_compress_HC_continue(), + * but will read as much data as possible from `src` + * to fit into `targetDstSize` budget. + * Result is provided into 2 parts : + * @return : the number of bytes written into 'dst' (necessarily <= targetDstSize) + * or 0 if compression fails. + * `srcSizePtr` : on success, *srcSizePtr will be updated to indicate how much bytes were read from `src`. + * Note that this function may not consume the entire input. + */ + compress_HC_continue_destSize:: proc(LZ4_streamHCPtr: ^streamHC_t, src, dst: [^]byte, srcSizePtr: ^c.int, targetDstSize: c.int) -> c.int --- + + saveDictHC :: proc(streamHCPtr: ^streamHC_t, safeBuffer: [^]byte, maxDictSize: c.int) -> c.int --- + + /*! LZ4_attach_HC_dictionary() : stable since v1.10.0 + * This API allows for the efficient re-use of a static dictionary many times. + * + * Rather than re-loading the dictionary buffer into a working context before + * each compression, or copying a pre-loaded dictionary's LZ4_streamHC_t into a + * working LZ4_streamHC_t, this function introduces a no-copy setup mechanism, + * in which the working stream references the dictionary stream in-place. + * + * Several assumptions are made about the state of the dictionary stream. + * Currently, only streams which have been prepared by LZ4_loadDictHC() should + * be expected to work. + * + * Alternatively, the provided dictionary stream pointer may be NULL, in which + * case any existing dictionary stream is unset. + * + * A dictionary should only be attached to a stream without any history (i.e., + * a stream that has just been reset). + * + * The dictionary will remain attached to the working stream only for the + * current stream session. Calls to LZ4_resetStreamHC(_fast) will remove the + * dictionary context association from the working stream. The dictionary + * stream (and source buffer) must remain in-place / accessible / unchanged + * through the lifetime of the stream session. + */ + attach_HC_dictionary :: proc(working_stream, dictionary_stream: ^streamHC_t) --- +} + + +HC_DICTIONARY_LOGSIZE :: 16 +HC_MAXD :: 1< /* size_t */ + + +/** + Introduction + + LZ4 is lossless compression algorithm, providing compression speed >500 MB/s per core, + scalable with multi-cores CPU. It features an extremely fast decoder, with speed in + multiple GB/s per core, typically reaching RAM speed limits on multi-core systems. + + The LZ4 compression library provides in-memory compression and decompression functions. + It gives full buffer control to user. + Compression can be done in: + - a single step (described as Simple Functions) + - a single step, reusing a context (described in Advanced Functions) + - unbounded multiple steps (described as Streaming compression) + + lz4.h generates and decodes LZ4-compressed blocks (doc/lz4_Block_format.md). + Decompressing such a compressed block requires additional metadata. + Exact metadata depends on exact decompression function. + For the typical case of LZ4_decompress_safe(), + metadata includes block's compressed size, and maximum bound of decompressed size. + Each application is free to encode and pass such metadata in whichever way it wants. + + lz4.h only handle blocks, it can not generate Frames. + + Blocks are different from Frames (doc/lz4_Frame_format.md). + Frames bundle both blocks and metadata in a specified manner. + Embedding metadata is required for compressed data to be self-contained and portable. + Frame format is delivered through a companion API, declared in lz4frame.h. + The `lz4` CLI can only manage frames. +*/ + +/*^*************************************************************** +* Export parameters +*****************************************************************/ +/* +* LZ4_DLL_EXPORT : +* Enable exporting of functions when building a Windows DLL +* LZ4LIB_VISIBILITY : +* Control library symbols visibility. +*/ +#ifndef LZ4LIB_VISIBILITY +# if defined(__GNUC__) && (__GNUC__ >= 4) +# define LZ4LIB_VISIBILITY __attribute__ ((visibility ("default"))) +# else +# define LZ4LIB_VISIBILITY +# endif +#endif +#if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1) +# define LZ4LIB_API __declspec(dllexport) LZ4LIB_VISIBILITY +#elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1) +# define LZ4LIB_API __declspec(dllimport) LZ4LIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/ +#else +# define LZ4LIB_API LZ4LIB_VISIBILITY +#endif + +/*! LZ4_FREESTANDING : + * When this macro is set to 1, it enables "freestanding mode" that is + * suitable for typical freestanding environment which doesn't support + * standard C library. + * + * - LZ4_FREESTANDING is a compile-time switch. + * - It requires the following macros to be defined: + * LZ4_memcpy, LZ4_memmove, LZ4_memset. + * - It only enables LZ4/HC functions which don't use heap. + * All LZ4F_* functions are not supported. + * - See tests/freestanding.c to check its basic setup. + */ +#if defined(LZ4_FREESTANDING) && (LZ4_FREESTANDING == 1) +# define LZ4_HEAPMODE 0 +# define LZ4HC_HEAPMODE 0 +# define LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION 1 +# if !defined(LZ4_memcpy) +# error "LZ4_FREESTANDING requires macro 'LZ4_memcpy'." +# endif +# if !defined(LZ4_memset) +# error "LZ4_FREESTANDING requires macro 'LZ4_memset'." +# endif +# if !defined(LZ4_memmove) +# error "LZ4_FREESTANDING requires macro 'LZ4_memmove'." +# endif +#elif ! defined(LZ4_FREESTANDING) +# define LZ4_FREESTANDING 0 +#endif + + +/*------ Version ------*/ +#define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */ +#define LZ4_VERSION_MINOR 10 /* for new (non-breaking) interface capabilities */ +#define LZ4_VERSION_RELEASE 0 /* for tweaks, bug-fixes, or development */ + +#define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE) + +#define LZ4_LIB_VERSION LZ4_VERSION_MAJOR.LZ4_VERSION_MINOR.LZ4_VERSION_RELEASE +#define LZ4_QUOTE(str) #str +#define LZ4_EXPAND_AND_QUOTE(str) LZ4_QUOTE(str) +#define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION) /* requires v1.7.3+ */ + +LZ4LIB_API int LZ4_versionNumber (void); /**< library version number; useful to check dll version; requires v1.3.0+ */ +LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; useful to check dll version; requires v1.7.5+ */ + + +/*-************************************ +* Tuning memory usage +**************************************/ +/*! + * LZ4_MEMORY_USAGE : + * Can be selected at compile time, by setting LZ4_MEMORY_USAGE. + * Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB) + * Increasing memory usage improves compression ratio, generally at the cost of speed. + * Reduced memory usage may improve speed at the cost of ratio, thanks to better cache locality. + * Default value is 14, for 16KB, which nicely fits into most L1 caches. + */ +#ifndef LZ4_MEMORY_USAGE +# define LZ4_MEMORY_USAGE LZ4_MEMORY_USAGE_DEFAULT +#endif + +/* These are absolute limits, they should not be changed by users */ +#define LZ4_MEMORY_USAGE_MIN 10 +#define LZ4_MEMORY_USAGE_DEFAULT 14 +#define LZ4_MEMORY_USAGE_MAX 20 + +#if (LZ4_MEMORY_USAGE < LZ4_MEMORY_USAGE_MIN) +# error "LZ4_MEMORY_USAGE is too small !" +#endif + +#if (LZ4_MEMORY_USAGE > LZ4_MEMORY_USAGE_MAX) +# error "LZ4_MEMORY_USAGE is too large !" +#endif + +/*-************************************ +* Simple Functions +**************************************/ +/*! LZ4_compress_default() : + * Compresses 'srcSize' bytes from buffer 'src' + * into already allocated 'dst' buffer of size 'dstCapacity'. + * Compression is guaranteed to succeed if 'dstCapacity' >= LZ4_compressBound(srcSize). + * It also runs faster, so it's a recommended setting. + * If the function cannot compress 'src' into a more limited 'dst' budget, + * compression stops *immediately*, and the function result is zero. + * In which case, 'dst' content is undefined (invalid). + * srcSize : max supported value is LZ4_MAX_INPUT_SIZE. + * dstCapacity : size of buffer 'dst' (which must be already allocated) + * @return : the number of bytes written into buffer 'dst' (necessarily <= dstCapacity) + * or 0 if compression fails + * Note : This function is protected against buffer overflow scenarios (never writes outside 'dst' buffer, nor read outside 'source' buffer). + */ +LZ4LIB_API int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity); + +/*! LZ4_decompress_safe() : + * @compressedSize : is the exact complete size of the compressed block. + * @dstCapacity : is the size of destination buffer (which must be already allocated), + * presumed an upper bound of decompressed size. + * @return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity) + * If destination buffer is not large enough, decoding will stop and output an error code (negative value). + * If the source stream is detected malformed, the function will stop decoding and return a negative result. + * Note 1 : This function is protected against malicious data packets : + * it will never writes outside 'dst' buffer, nor read outside 'source' buffer, + * even if the compressed block is maliciously modified to order the decoder to do these actions. + * In such case, the decoder stops immediately, and considers the compressed block malformed. + * Note 2 : compressedSize and dstCapacity must be provided to the function, the compressed block does not contain them. + * The implementation is free to send / store / derive this information in whichever way is most beneficial. + * If there is a need for a different format which bundles together both compressed data and its metadata, consider looking at lz4frame.h instead. + */ +LZ4LIB_API int LZ4_decompress_safe (const char* src, char* dst, int compressedSize, int dstCapacity); + + +/*-************************************ +* Advanced Functions +**************************************/ +#define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */ +#define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16) + +/*! LZ4_compressBound() : + Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible) + This function is primarily useful for memory allocation purposes (destination buffer size). + Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example). + Note that LZ4_compress_default() compresses faster when dstCapacity is >= LZ4_compressBound(srcSize) + inputSize : max supported value is LZ4_MAX_INPUT_SIZE + return : maximum output size in a "worst case" scenario + or 0, if input size is incorrect (too large or negative) +*/ +LZ4LIB_API int LZ4_compressBound(int inputSize); + +/*! LZ4_compress_fast() : + Same as LZ4_compress_default(), but allows selection of "acceleration" factor. + The larger the acceleration value, the faster the algorithm, but also the lesser the compression. + It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed. + An acceleration value of "1" is the same as regular LZ4_compress_default() + Values <= 0 will be replaced by LZ4_ACCELERATION_DEFAULT (currently == 1, see lz4.c). + Values > LZ4_ACCELERATION_MAX will be replaced by LZ4_ACCELERATION_MAX (currently == 65537, see lz4.c). +*/ +LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + + +/*! LZ4_compress_fast_extState() : + * Same as LZ4_compress_fast(), using an externally allocated memory space for its state. + * Use LZ4_sizeofState() to know how much memory must be allocated, + * and allocate it on 8-bytes boundaries (using `malloc()` typically). + * Then, provide this buffer as `void* state` to compression function. + */ +LZ4LIB_API int LZ4_sizeofState(void); +LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + +/*! LZ4_compress_destSize() : + * Reverse the logic : compresses as much data as possible from 'src' buffer + * into already allocated buffer 'dst', of size >= 'dstCapacity'. + * This function either compresses the entire 'src' content into 'dst' if it's large enough, + * or fill 'dst' buffer completely with as much data as possible from 'src'. + * note: acceleration parameter is fixed to "default". + * + * *srcSizePtr : in+out parameter. Initially contains size of input. + * Will be modified to indicate how many bytes where read from 'src' to fill 'dst'. + * New value is necessarily <= input value. + * @return : Nb bytes written into 'dst' (necessarily <= dstCapacity) + * or 0 if compression fails. + * + * Note : from v1.8.2 to v1.9.1, this function had a bug (fixed in v1.9.2+): + * the produced compressed content could, in specific circumstances, + * require to be decompressed into a destination buffer larger + * by at least 1 byte than the content to decompress. + * If an application uses `LZ4_compress_destSize()`, + * it's highly recommended to update liblz4 to v1.9.2 or better. + * If this can't be done or ensured, + * the receiving decompression function should provide + * a dstCapacity which is > decompressedSize, by at least 1 byte. + * See https://github.com/lz4/lz4/issues/859 for details + */ +LZ4LIB_API int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targetDstSize); + +/*! LZ4_decompress_safe_partial() : + * Decompress an LZ4 compressed block, of size 'srcSize' at position 'src', + * into destination buffer 'dst' of size 'dstCapacity'. + * Up to 'targetOutputSize' bytes will be decoded. + * The function stops decoding on reaching this objective. + * This can be useful to boost performance + * whenever only the beginning of a block is required. + * + * @return : the number of bytes decoded in `dst` (necessarily <= targetOutputSize) + * If source stream is detected malformed, function returns a negative result. + * + * Note 1 : @return can be < targetOutputSize, if compressed block contains less data. + * + * Note 2 : targetOutputSize must be <= dstCapacity + * + * Note 3 : this function effectively stops decoding on reaching targetOutputSize, + * so dstCapacity is kind of redundant. + * This is because in older versions of this function, + * decoding operation would still write complete sequences. + * Therefore, there was no guarantee that it would stop writing at exactly targetOutputSize, + * it could write more bytes, though only up to dstCapacity. + * Some "margin" used to be required for this operation to work properly. + * Thankfully, this is no longer necessary. + * The function nonetheless keeps the same signature, in an effort to preserve API compatibility. + * + * Note 4 : If srcSize is the exact size of the block, + * then targetOutputSize can be any value, + * including larger than the block's decompressed size. + * The function will, at most, generate block's decompressed size. + * + * Note 5 : If srcSize is _larger_ than block's compressed size, + * then targetOutputSize **MUST** be <= block's decompressed size. + * Otherwise, *silent corruption will occur*. + */ +LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity); + + +/*-********************************************* +* Streaming Compression Functions +***********************************************/ +typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */ + +/*! + Note about RC_INVOKED + + - RC_INVOKED is predefined symbol of rc.exe (the resource compiler which is part of MSVC/Visual Studio). + https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros + + - Since rc.exe is a legacy compiler, it truncates long symbol (> 30 chars) + and reports warning "RC4011: identifier truncated". + + - To eliminate the warning, we surround long preprocessor symbol with + "#if !defined(RC_INVOKED) ... #endif" block that means + "skip this block when rc.exe is trying to read it". +*/ +#if !defined(RC_INVOKED) /* https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros */ +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +LZ4LIB_API LZ4_stream_t* LZ4_createStream(void); +LZ4LIB_API int LZ4_freeStream (LZ4_stream_t* streamPtr); +#endif /* !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) */ +#endif + +/*! LZ4_resetStream_fast() : v1.9.0+ + * Use this to prepare an LZ4_stream_t for a new chain of dependent blocks + * (e.g., LZ4_compress_fast_continue()). + * + * An LZ4_stream_t must be initialized once before usage. + * This is automatically done when created by LZ4_createStream(). + * However, should the LZ4_stream_t be simply declared on stack (for example), + * it's necessary to initialize it first, using LZ4_initStream(). + * + * After init, start any new stream with LZ4_resetStream_fast(). + * A same LZ4_stream_t can be re-used multiple times consecutively + * and compress multiple streams, + * provided that it starts each new stream with LZ4_resetStream_fast(). + * + * LZ4_resetStream_fast() is much faster than LZ4_initStream(), + * but is not compatible with memory regions containing garbage data. + * + * Note: it's only useful to call LZ4_resetStream_fast() + * in the context of streaming compression. + * The *extState* functions perform their own resets. + * Invoking LZ4_resetStream_fast() before is redundant, and even counterproductive. + */ +LZ4LIB_API void LZ4_resetStream_fast (LZ4_stream_t* streamPtr); + +/*! LZ4_loadDict() : + * Use this function to reference a static dictionary into LZ4_stream_t. + * The dictionary must remain available during compression. + * LZ4_loadDict() triggers a reset, so any previous data will be forgotten. + * The same dictionary will have to be loaded on decompression side for successful decoding. + * Dictionary are useful for better compression of small data (KB range). + * While LZ4 itself accepts any input as dictionary, dictionary efficiency is also a topic. + * When in doubt, employ the Zstandard's Dictionary Builder. + * Loading a size of 0 is allowed, and is the same as reset. + * @return : loaded dictionary size, in bytes (note: only the last 64 KB are loaded) + */ +LZ4LIB_API int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, int dictSize); + +/*! LZ4_loadDictSlow() : v1.10.0+ + * Same as LZ4_loadDict(), + * but uses a bit more cpu to reference the dictionary content more thoroughly. + * This is expected to slightly improve compression ratio. + * The extra-cpu cost is likely worth it if the dictionary is re-used across multiple sessions. + * @return : loaded dictionary size, in bytes (note: only the last 64 KB are loaded) + */ +LZ4LIB_API int LZ4_loadDictSlow(LZ4_stream_t* streamPtr, const char* dictionary, int dictSize); + +/*! LZ4_attach_dictionary() : stable since v1.10.0 + * + * This allows efficient re-use of a static dictionary multiple times. + * + * Rather than re-loading the dictionary buffer into a working context before + * each compression, or copying a pre-loaded dictionary's LZ4_stream_t into a + * working LZ4_stream_t, this function introduces a no-copy setup mechanism, + * in which the working stream references @dictionaryStream in-place. + * + * Several assumptions are made about the state of @dictionaryStream. + * Currently, only states which have been prepared by LZ4_loadDict() or + * LZ4_loadDictSlow() should be expected to work. + * + * Alternatively, the provided @dictionaryStream may be NULL, + * in which case any existing dictionary stream is unset. + * + * If a dictionary is provided, it replaces any pre-existing stream history. + * The dictionary contents are the only history that can be referenced and + * logically immediately precede the data compressed in the first subsequent + * compression call. + * + * The dictionary will only remain attached to the working stream through the + * first compression call, at the end of which it is cleared. + * @dictionaryStream stream (and source buffer) must remain in-place / accessible / unchanged + * through the completion of the compression session. + * + * Note: there is no equivalent LZ4_attach_*() method on the decompression side + * because there is no initialization cost, hence no need to share the cost across multiple sessions. + * To decompress LZ4 blocks using dictionary, attached or not, + * just employ the regular LZ4_setStreamDecode() for streaming, + * or the stateless LZ4_decompress_safe_usingDict() for one-shot decompression. + */ +LZ4LIB_API void +LZ4_attach_dictionary(LZ4_stream_t* workingStream, + const LZ4_stream_t* dictionaryStream); + +/*! LZ4_compress_fast_continue() : + * Compress 'src' content using data from previously compressed blocks, for better compression ratio. + * 'dst' buffer must be already allocated. + * If dstCapacity >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster. + * + * @return : size of compressed block + * or 0 if there is an error (typically, cannot fit into 'dst'). + * + * Note 1 : Each invocation to LZ4_compress_fast_continue() generates a new block. + * Each block has precise boundaries. + * Each block must be decompressed separately, calling LZ4_decompress_*() with relevant metadata. + * It's not possible to append blocks together and expect a single invocation of LZ4_decompress_*() to decompress them together. + * + * Note 2 : The previous 64KB of source data is __assumed__ to remain present, unmodified, at same address in memory ! + * + * Note 3 : When input is structured as a double-buffer, each buffer can have any size, including < 64 KB. + * Make sure that buffers are separated, by at least one byte. + * This construction ensures that each block only depends on previous block. + * + * Note 4 : If input buffer is a ring-buffer, it can have any size, including < 64 KB. + * + * Note 5 : After an error, the stream status is undefined (invalid), it can only be reset or freed. + */ +LZ4LIB_API int LZ4_compress_fast_continue (LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + +/*! LZ4_saveDict() : + * If last 64KB data cannot be guaranteed to remain available at its current memory location, + * save it into a safer place (char* safeBuffer). + * This is schematically equivalent to a memcpy() followed by LZ4_loadDict(), + * but is much faster, because LZ4_saveDict() doesn't need to rebuild tables. + * @return : saved dictionary size in bytes (necessarily <= maxDictSize), or 0 if error. + */ +LZ4LIB_API int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int maxDictSize); + + +/*-********************************************** +* Streaming Decompression Functions +* Bufferless synchronous API +************************************************/ +typedef union LZ4_streamDecode_u LZ4_streamDecode_t; /* tracking context */ + +/*! LZ4_createStreamDecode() and LZ4_freeStreamDecode() : + * creation / destruction of streaming decompression tracking context. + * A tracking context can be re-used multiple times. + */ +#if !defined(RC_INVOKED) /* https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros */ +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +LZ4LIB_API LZ4_streamDecode_t* LZ4_createStreamDecode(void); +LZ4LIB_API int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream); +#endif /* !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) */ +#endif + +/*! LZ4_setStreamDecode() : + * An LZ4_streamDecode_t context can be allocated once and re-used multiple times. + * Use this function to start decompression of a new stream of blocks. + * A dictionary can optionally be set. Use NULL or size 0 for a reset order. + * Dictionary is presumed stable : it must remain accessible and unmodified during next decompression. + * @return : 1 if OK, 0 if error + */ +LZ4LIB_API int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize); + +/*! LZ4_decoderRingBufferSize() : v1.8.2+ + * Note : in a ring buffer scenario (optional), + * blocks are presumed decompressed next to each other + * up to the moment there is not enough remaining space for next block (remainingSize < maxBlockSize), + * at which stage it resumes from beginning of ring buffer. + * When setting such a ring buffer for streaming decompression, + * provides the minimum size of this ring buffer + * to be compatible with any source respecting maxBlockSize condition. + * @return : minimum ring buffer size, + * or 0 if there is an error (invalid maxBlockSize). + */ +LZ4LIB_API int LZ4_decoderRingBufferSize(int maxBlockSize); +#define LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize) (65536 + 14 + (maxBlockSize)) /* for static allocation; maxBlockSize presumed valid */ + +/*! LZ4_decompress_safe_continue() : + * This decoding function allows decompression of consecutive blocks in "streaming" mode. + * The difference with the usual independent blocks is that + * new blocks are allowed to find references into former blocks. + * A block is an unsplittable entity, and must be presented entirely to the decompression function. + * LZ4_decompress_safe_continue() only accepts one block at a time. + * It's modeled after `LZ4_decompress_safe()` and behaves similarly. + * + * @LZ4_streamDecode : decompression state, tracking the position in memory of past data + * @compressedSize : exact complete size of one compressed block. + * @dstCapacity : size of destination buffer (which must be already allocated), + * must be an upper bound of decompressed size. + * @return : number of bytes decompressed into destination buffer (necessarily <= dstCapacity) + * If destination buffer is not large enough, decoding will stop and output an error code (negative value). + * If the source stream is detected malformed, the function will stop decoding and return a negative result. + * + * The last 64KB of previously decoded data *must* remain available and unmodified + * at the memory position where they were previously decoded. + * If less than 64KB of data has been decoded, all the data must be present. + * + * Special : if decompression side sets a ring buffer, it must respect one of the following conditions : + * - Decompression buffer size is _at least_ LZ4_decoderRingBufferSize(maxBlockSize). + * maxBlockSize is the maximum size of any single block. It can have any value > 16 bytes. + * In which case, encoding and decoding buffers do not need to be synchronized. + * Actually, data can be produced by any source compliant with LZ4 format specification, and respecting maxBlockSize. + * - Synchronized mode : + * Decompression buffer size is _exactly_ the same as compression buffer size, + * and follows exactly same update rule (block boundaries at same positions), + * and decoding function is provided with exact decompressed size of each block (exception for last block of the stream), + * _then_ decoding & encoding ring buffer can have any size, including small ones ( < 64 KB). + * - Decompression buffer is larger than encoding buffer, by a minimum of maxBlockSize more bytes. + * In which case, encoding and decoding buffers do not need to be synchronized, + * and encoding ring buffer can have any size, including small ones ( < 64 KB). + * + * Whenever these conditions are not possible, + * save the last 64KB of decoded data into a safe buffer where it can't be modified during decompression, + * then indicate where this data is saved using LZ4_setStreamDecode(), before decompressing next block. +*/ +LZ4LIB_API int +LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, + const char* src, char* dst, + int srcSize, int dstCapacity); + + +/*! LZ4_decompress_safe_usingDict() : + * Works the same as + * a combination of LZ4_setStreamDecode() followed by LZ4_decompress_safe_continue() + * However, it's stateless: it doesn't need any LZ4_streamDecode_t state. + * Dictionary is presumed stable : it must remain accessible and unmodified during decompression. + * Performance tip : Decompression speed can be substantially increased + * when dst == dictStart + dictSize. + */ +LZ4LIB_API int +LZ4_decompress_safe_usingDict(const char* src, char* dst, + int srcSize, int dstCapacity, + const char* dictStart, int dictSize); + +/*! LZ4_decompress_safe_partial_usingDict() : + * Behaves the same as LZ4_decompress_safe_partial() + * with the added ability to specify a memory segment for past data. + * Performance tip : Decompression speed can be substantially increased + * when dst == dictStart + dictSize. + */ +LZ4LIB_API int +LZ4_decompress_safe_partial_usingDict(const char* src, char* dst, + int compressedSize, + int targetOutputSize, int maxOutputSize, + const char* dictStart, int dictSize); + +#endif /* LZ4_H_2983827168210 */ + + +/*^************************************* + * !!!!!! STATIC LINKING ONLY !!!!!! + ***************************************/ + +/*-**************************************************************************** + * Experimental section + * + * Symbols declared in this section must be considered unstable. Their + * signatures or semantics may change, or they may be removed altogether in the + * future. They are therefore only safe to depend on when the caller is + * statically linked against the library. + * + * To protect against unsafe usage, not only are the declarations guarded, + * the definitions are hidden by default + * when building LZ4 as a shared/dynamic library. + * + * In order to access these declarations, + * define LZ4_STATIC_LINKING_ONLY in your application + * before including LZ4's headers. + * + * In order to make their implementations accessible dynamically, you must + * define LZ4_PUBLISH_STATIC_FUNCTIONS when building the LZ4 library. + ******************************************************************************/ + +#ifdef LZ4_STATIC_LINKING_ONLY + +#ifndef LZ4_STATIC_3504398509 +#define LZ4_STATIC_3504398509 + +#ifdef LZ4_PUBLISH_STATIC_FUNCTIONS +# define LZ4LIB_STATIC_API LZ4LIB_API +#else +# define LZ4LIB_STATIC_API +#endif + + +/*! LZ4_compress_fast_extState_fastReset() : + * A variant of LZ4_compress_fast_extState(). + * + * Using this variant avoids an expensive initialization step. + * It is only safe to call if the state buffer is known to be correctly initialized already + * (see above comment on LZ4_resetStream_fast() for a definition of "correctly initialized"). + * From a high level, the difference is that + * this function initializes the provided state with a call to something like LZ4_resetStream_fast() + * while LZ4_compress_fast_extState() starts with a call to LZ4_resetStream(). + */ +LZ4LIB_STATIC_API int LZ4_compress_fast_extState_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + +/*! LZ4_compress_destSize_extState() : introduced in v1.10.0 + * Same as LZ4_compress_destSize(), but using an externally allocated state. + * Also: exposes @acceleration + */ +int LZ4_compress_destSize_extState(void* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize, int acceleration); + +/*! In-place compression and decompression + * + * It's possible to have input and output sharing the same buffer, + * for highly constrained memory environments. + * In both cases, it requires input to lay at the end of the buffer, + * and decompression to start at beginning of the buffer. + * Buffer size must feature some margin, hence be larger than final size. + * + * |<------------------------buffer--------------------------------->| + * |<-----------compressed data--------->| + * |<-----------decompressed size------------------>| + * |<----margin---->| + * + * This technique is more useful for decompression, + * since decompressed size is typically larger, + * and margin is short. + * + * In-place decompression will work inside any buffer + * which size is >= LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize). + * This presumes that decompressedSize > compressedSize. + * Otherwise, it means compression actually expanded data, + * and it would be more efficient to store such data with a flag indicating it's not compressed. + * This can happen when data is not compressible (already compressed, or encrypted). + * + * For in-place compression, margin is larger, as it must be able to cope with both + * history preservation, requiring input data to remain unmodified up to LZ4_DISTANCE_MAX, + * and data expansion, which can happen when input is not compressible. + * As a consequence, buffer size requirements are much higher, + * and memory savings offered by in-place compression are more limited. + * + * There are ways to limit this cost for compression : + * - Reduce history size, by modifying LZ4_DISTANCE_MAX. + * Note that it is a compile-time constant, so all compressions will apply this limit. + * Lower values will reduce compression ratio, except when input_size < LZ4_DISTANCE_MAX, + * so it's a reasonable trick when inputs are known to be small. + * - Require the compressor to deliver a "maximum compressed size". + * This is the `dstCapacity` parameter in `LZ4_compress*()`. + * When this size is < LZ4_COMPRESSBOUND(inputSize), then compression can fail, + * in which case, the return code will be 0 (zero). + * The caller must be ready for these cases to happen, + * and typically design a backup scheme to send data uncompressed. + * The combination of both techniques can significantly reduce + * the amount of margin required for in-place compression. + * + * In-place compression can work in any buffer + * which size is >= (maxCompressedSize) + * with maxCompressedSize == LZ4_COMPRESSBOUND(srcSize) for guaranteed compression success. + * LZ4_COMPRESS_INPLACE_BUFFER_SIZE() depends on both maxCompressedSize and LZ4_DISTANCE_MAX, + * so it's possible to reduce memory requirements by playing with them. + */ + +#define LZ4_DECOMPRESS_INPLACE_MARGIN(compressedSize) (((compressedSize) >> 8) + 32) +#define LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize) ((decompressedSize) + LZ4_DECOMPRESS_INPLACE_MARGIN(decompressedSize)) /**< note: presumes that compressedSize < decompressedSize. note2: margin is overestimated a bit, since it could use compressedSize instead */ + +#ifndef LZ4_DISTANCE_MAX /* history window size; can be user-defined at compile time */ +# define LZ4_DISTANCE_MAX 65535 /* set to maximum value by default */ +#endif + +#define LZ4_COMPRESS_INPLACE_MARGIN (LZ4_DISTANCE_MAX + 32) /* LZ4_DISTANCE_MAX can be safely replaced by srcSize when it's smaller */ +#define LZ4_COMPRESS_INPLACE_BUFFER_SIZE(maxCompressedSize) ((maxCompressedSize) + LZ4_COMPRESS_INPLACE_MARGIN) /**< maxCompressedSize is generally LZ4_COMPRESSBOUND(inputSize), but can be set to any lower value, with the risk that compression can fail (return code 0(zero)) */ + +#endif /* LZ4_STATIC_3504398509 */ +#endif /* LZ4_STATIC_LINKING_ONLY */ + + + +#ifndef LZ4_H_98237428734687 +#define LZ4_H_98237428734687 + +/*-************************************************************ + * Private Definitions + ************************************************************** + * Do not use these definitions directly. + * They are only exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`. + * Accessing members will expose user code to API and/or ABI break in future versions of the library. + **************************************************************/ +#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2) +#define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE) +#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */ + +#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# include + typedef int8_t LZ4_i8; + typedef uint8_t LZ4_byte; + typedef uint16_t LZ4_u16; + typedef uint32_t LZ4_u32; +#else + typedef signed char LZ4_i8; + typedef unsigned char LZ4_byte; + typedef unsigned short LZ4_u16; + typedef unsigned int LZ4_u32; +#endif + +/*! LZ4_stream_t : + * Never ever use below internal definitions directly ! + * These definitions are not API/ABI safe, and may change in future versions. + * If you need static allocation, declare or allocate an LZ4_stream_t object. +**/ + +typedef struct LZ4_stream_t_internal LZ4_stream_t_internal; +struct LZ4_stream_t_internal { + LZ4_u32 hashTable[LZ4_HASH_SIZE_U32]; + const LZ4_byte* dictionary; + const LZ4_stream_t_internal* dictCtx; + LZ4_u32 currentOffset; + LZ4_u32 tableType; + LZ4_u32 dictSize; + /* Implicit padding to ensure structure is aligned */ +}; + +#define LZ4_STREAM_MINSIZE ((1UL << (LZ4_MEMORY_USAGE)) + 32) /* static size, for inter-version compatibility */ +union LZ4_stream_u { + char minStateSize[LZ4_STREAM_MINSIZE]; + LZ4_stream_t_internal internal_donotuse; +}; /* previously typedef'd to LZ4_stream_t */ + + +/*! LZ4_initStream() : v1.9.0+ + * An LZ4_stream_t structure must be initialized at least once. + * This is automatically done when invoking LZ4_createStream(), + * but it's not when the structure is simply declared on stack (for example). + * + * Use LZ4_initStream() to properly initialize a newly declared LZ4_stream_t. + * It can also initialize any arbitrary buffer of sufficient size, + * and will @return a pointer of proper type upon initialization. + * + * Note : initialization fails if size and alignment conditions are not respected. + * In which case, the function will @return NULL. + * Note2: An LZ4_stream_t structure guarantees correct alignment and size. + * Note3: Before v1.9.0, use LZ4_resetStream() instead +**/ +LZ4LIB_API LZ4_stream_t* LZ4_initStream (void* stateBuffer, size_t size); + + +/*! LZ4_streamDecode_t : + * Never ever use below internal definitions directly ! + * These definitions are not API/ABI safe, and may change in future versions. + * If you need static allocation, declare or allocate an LZ4_streamDecode_t object. +**/ +typedef struct { + const LZ4_byte* externalDict; + const LZ4_byte* prefixEnd; + size_t extDictSize; + size_t prefixSize; +} LZ4_streamDecode_t_internal; + +#define LZ4_STREAMDECODE_MINSIZE 32 +union LZ4_streamDecode_u { + char minStateSize[LZ4_STREAMDECODE_MINSIZE]; + LZ4_streamDecode_t_internal internal_donotuse; +} ; /* previously typedef'd to LZ4_streamDecode_t */ + + + +/*-************************************ +* Obsolete Functions +**************************************/ + +/*! Deprecation warnings + * + * Deprecated functions make the compiler generate a warning when invoked. + * This is meant to invite users to update their source code. + * Should deprecation warnings be a problem, it is generally possible to disable them, + * typically with -Wno-deprecated-declarations for gcc + * or _CRT_SECURE_NO_WARNINGS in Visual. + * + * Another method is to define LZ4_DISABLE_DEPRECATE_WARNINGS + * before including the header file. + */ +#ifdef LZ4_DISABLE_DEPRECATE_WARNINGS +# define LZ4_DEPRECATED(message) /* disable deprecation warnings */ +#else +# if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */ +# define LZ4_DEPRECATED(message) [[deprecated(message)]] +# elif defined(_MSC_VER) +# define LZ4_DEPRECATED(message) __declspec(deprecated(message)) +# elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 45)) +# define LZ4_DEPRECATED(message) __attribute__((deprecated(message))) +# elif defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 31) +# define LZ4_DEPRECATED(message) __attribute__((deprecated)) +# else +# pragma message("WARNING: LZ4_DEPRECATED needs custom implementation for this compiler") +# define LZ4_DEPRECATED(message) /* disabled */ +# endif +#endif /* LZ4_DISABLE_DEPRECATE_WARNINGS */ + +/*! Obsolete compression functions (since v1.7.3) */ +LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress (const char* src, char* dest, int srcSize); +LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress_limitedOutput (const char* src, char* dest, int srcSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_withState (void* state, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_limitedOutput_withState (void* state, const char* source, char* dest, int inputSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize); + +/*! Obsolete decompression functions (since v1.8.0) */ +LZ4_DEPRECATED("use LZ4_decompress_fast() instead") LZ4LIB_API int LZ4_uncompress (const char* source, char* dest, int outputSize); +LZ4_DEPRECATED("use LZ4_decompress_safe() instead") LZ4LIB_API int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize); + +/* Obsolete streaming functions (since v1.7.0) + * degraded functionality; do not use! + * + * In order to perform streaming compression, these functions depended on data + * that is no longer tracked in the state. They have been preserved as well as + * possible: using them will still produce a correct output. However, they don't + * actually retain any history between compression calls. The compression ratio + * achieved will therefore be no better than compressing each chunk + * independently. + */ +LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API void* LZ4_create (char* inputBuffer); +LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API int LZ4_sizeofStreamState(void); +LZ4_DEPRECATED("Use LZ4_resetStream() instead") LZ4LIB_API int LZ4_resetStreamState(void* state, char* inputBuffer); +LZ4_DEPRECATED("Use LZ4_saveDict() instead") LZ4LIB_API char* LZ4_slideInputBuffer (void* state); + +/*! Obsolete streaming decoding functions (since v1.7.0) */ +LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") LZ4LIB_API int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize); +LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize); + +/*! Obsolete LZ4_decompress_fast variants (since v1.9.0) : + * These functions used to be faster than LZ4_decompress_safe(), + * but this is no longer the case. They are now slower. + * This is because LZ4_decompress_fast() doesn't know the input size, + * and therefore must progress more cautiously into the input buffer to not read beyond the end of block. + * On top of that `LZ4_decompress_fast()` is not protected vs malformed or malicious inputs, making it a security liability. + * As a consequence, LZ4_decompress_fast() is strongly discouraged, and deprecated. + * + * The last remaining LZ4_decompress_fast() specificity is that + * it can decompress a block without knowing its compressed size. + * Such functionality can be achieved in a more secure manner + * by employing LZ4_decompress_safe_partial(). + * + * Parameters: + * originalSize : is the uncompressed size to regenerate. + * `dst` must be already allocated, its size must be >= 'originalSize' bytes. + * @return : number of bytes read from source buffer (== compressed size). + * The function expects to finish at block's end exactly. + * If the source stream is detected malformed, the function stops decoding and returns a negative result. + * note : LZ4_decompress_fast*() requires originalSize. Thanks to this information, it never writes past the output buffer. + * However, since it doesn't know its 'src' size, it may read an unknown amount of input, past input buffer bounds. + * Also, since match offsets are not validated, match reads from 'src' may underflow too. + * These issues never happen if input (compressed) data is correct. + * But they may happen if input data is invalid (error or intentional tampering). + * As a consequence, use these functions in trusted environments with trusted data **only**. + */ +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_partial() instead") +LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize); +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider migrating towards LZ4_decompress_safe_continue() instead. " + "Note that the contract will change (requires block's compressed size, instead of decompressed size)") +LZ4LIB_API int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int originalSize); +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_partial_usingDict() instead") +LZ4LIB_API int LZ4_decompress_fast_usingDict (const char* src, char* dst, int originalSize, const char* dictStart, int dictSize); + +/*! LZ4_resetStream() : + * An LZ4_stream_t structure must be initialized at least once. + * This is done with LZ4_initStream(), or LZ4_resetStream(). + * Consider switching to LZ4_initStream(), + * invoking LZ4_resetStream() will trigger deprecation warnings in the future. + */ +LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr); + + +#endif /* LZ4_H_98237428734687 */ + + +#if defined (__cplusplus) +} +#endif diff --git a/vendor/compress/lz4/src/lz4frame.h b/vendor/compress/lz4/src/lz4frame.h new file mode 100644 index 000000000..b8ae32276 --- /dev/null +++ b/vendor/compress/lz4/src/lz4frame.h @@ -0,0 +1,751 @@ +/* + LZ4F - LZ4-Frame library + Header File + Copyright (C) 2011-2020, Yann Collet. + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 source repository : https://github.com/lz4/lz4 + - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c +*/ + +/* LZ4F is a stand-alone API able to create and decode LZ4 frames + * conformant with specification v1.6.1 in doc/lz4_Frame_format.md . + * Generated frames are compatible with `lz4` CLI. + * + * LZ4F also offers streaming capabilities. + * + * lz4.h is not required when using lz4frame.h, + * except to extract common constants such as LZ4_VERSION_NUMBER. + * */ + +#ifndef LZ4F_H_09782039843 +#define LZ4F_H_09782039843 + +#if defined (__cplusplus) +extern "C" { +#endif + +/* --- Dependency --- */ +#include /* size_t */ + + +/** + * Introduction + * + * lz4frame.h implements LZ4 frame specification: see doc/lz4_Frame_format.md . + * LZ4 Frames are compatible with `lz4` CLI, + * and designed to be interoperable with any system. +**/ + +/*-*************************************************************** + * Compiler specifics + *****************************************************************/ +/* LZ4_DLL_EXPORT : + * Enable exporting of functions when building a Windows DLL + * LZ4FLIB_VISIBILITY : + * Control library symbols visibility. + */ +#ifndef LZ4FLIB_VISIBILITY +# if defined(__GNUC__) && (__GNUC__ >= 4) +# define LZ4FLIB_VISIBILITY __attribute__ ((visibility ("default"))) +# else +# define LZ4FLIB_VISIBILITY +# endif +#endif +#if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1) +# define LZ4FLIB_API __declspec(dllexport) LZ4FLIB_VISIBILITY +#elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1) +# define LZ4FLIB_API __declspec(dllimport) LZ4FLIB_VISIBILITY +#else +# define LZ4FLIB_API LZ4FLIB_VISIBILITY +#endif + +#ifdef LZ4F_DISABLE_DEPRECATE_WARNINGS +# define LZ4F_DEPRECATE(x) x +#else +# if defined(_MSC_VER) +# define LZ4F_DEPRECATE(x) x /* __declspec(deprecated) x - only works with C++ */ +# elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ >= 6)) +# define LZ4F_DEPRECATE(x) x __attribute__((deprecated)) +# else +# define LZ4F_DEPRECATE(x) x /* no deprecation warning for this compiler */ +# endif +#endif + + +/*-************************************ + * Error management + **************************************/ +typedef size_t LZ4F_errorCode_t; + +LZ4FLIB_API unsigned LZ4F_isError(LZ4F_errorCode_t code); /**< tells when a function result is an error code */ +LZ4FLIB_API const char* LZ4F_getErrorName(LZ4F_errorCode_t code); /**< return error code string; for debugging */ + + +/*-************************************ + * Frame compression types + ************************************* */ +/* #define LZ4F_ENABLE_OBSOLETE_ENUMS // uncomment to enable obsolete enums */ +#ifdef LZ4F_ENABLE_OBSOLETE_ENUMS +# define LZ4F_OBSOLETE_ENUM(x) , LZ4F_DEPRECATE(x) = LZ4F_##x +#else +# define LZ4F_OBSOLETE_ENUM(x) +#endif + +/* The larger the block size, the (slightly) better the compression ratio, + * though there are diminishing returns. + * Larger blocks also increase memory usage on both compression and decompression sides. + */ +typedef enum { + LZ4F_default=0, + LZ4F_max64KB=4, + LZ4F_max256KB=5, + LZ4F_max1MB=6, + LZ4F_max4MB=7 + LZ4F_OBSOLETE_ENUM(max64KB) + LZ4F_OBSOLETE_ENUM(max256KB) + LZ4F_OBSOLETE_ENUM(max1MB) + LZ4F_OBSOLETE_ENUM(max4MB) +} LZ4F_blockSizeID_t; + +/* Linked blocks sharply reduce inefficiencies when using small blocks, + * they compress better. + * However, some LZ4 decoders are only compatible with independent blocks */ +typedef enum { + LZ4F_blockLinked=0, + LZ4F_blockIndependent + LZ4F_OBSOLETE_ENUM(blockLinked) + LZ4F_OBSOLETE_ENUM(blockIndependent) +} LZ4F_blockMode_t; + +typedef enum { + LZ4F_noContentChecksum=0, + LZ4F_contentChecksumEnabled + LZ4F_OBSOLETE_ENUM(noContentChecksum) + LZ4F_OBSOLETE_ENUM(contentChecksumEnabled) +} LZ4F_contentChecksum_t; + +typedef enum { + LZ4F_noBlockChecksum=0, + LZ4F_blockChecksumEnabled +} LZ4F_blockChecksum_t; + +typedef enum { + LZ4F_frame=0, + LZ4F_skippableFrame + LZ4F_OBSOLETE_ENUM(skippableFrame) +} LZ4F_frameType_t; + +#ifdef LZ4F_ENABLE_OBSOLETE_ENUMS +typedef LZ4F_blockSizeID_t blockSizeID_t; +typedef LZ4F_blockMode_t blockMode_t; +typedef LZ4F_frameType_t frameType_t; +typedef LZ4F_contentChecksum_t contentChecksum_t; +#endif + +/*! LZ4F_frameInfo_t : + * makes it possible to set or read frame parameters. + * Structure must be first init to 0, using memset() or LZ4F_INIT_FRAMEINFO, + * setting all parameters to default. + * It's then possible to update selectively some parameters */ +typedef struct { + LZ4F_blockSizeID_t blockSizeID; /* max64KB, max256KB, max1MB, max4MB; 0 == default (LZ4F_max64KB) */ + LZ4F_blockMode_t blockMode; /* LZ4F_blockLinked, LZ4F_blockIndependent; 0 == default (LZ4F_blockLinked) */ + LZ4F_contentChecksum_t contentChecksumFlag; /* 1: add a 32-bit checksum of frame's decompressed data; 0 == default (disabled) */ + LZ4F_frameType_t frameType; /* read-only field : LZ4F_frame or LZ4F_skippableFrame */ + unsigned long long contentSize; /* Size of uncompressed content ; 0 == unknown */ + unsigned dictID; /* Dictionary ID, sent by compressor to help decoder select correct dictionary; 0 == no dictID provided */ + LZ4F_blockChecksum_t blockChecksumFlag; /* 1: each block followed by a checksum of block's compressed data; 0 == default (disabled) */ +} LZ4F_frameInfo_t; + +#define LZ4F_INIT_FRAMEINFO { LZ4F_max64KB, LZ4F_blockLinked, LZ4F_noContentChecksum, LZ4F_frame, 0ULL, 0U, LZ4F_noBlockChecksum } /* v1.8.3+ */ + +/*! LZ4F_preferences_t : + * makes it possible to supply advanced compression instructions to streaming interface. + * Structure must be first init to 0, using memset() or LZ4F_INIT_PREFERENCES, + * setting all parameters to default. + * All reserved fields must be set to zero. */ +typedef struct { + LZ4F_frameInfo_t frameInfo; + int compressionLevel; /* 0: default (fast mode); values > LZ4HC_CLEVEL_MAX count as LZ4HC_CLEVEL_MAX; values < 0 trigger "fast acceleration" */ + unsigned autoFlush; /* 1: always flush; reduces usage of internal buffers */ + unsigned favorDecSpeed; /* 1: parser favors decompression speed vs compression ratio. Only works for high compression modes (>= LZ4HC_CLEVEL_OPT_MIN) */ /* v1.8.2+ */ + unsigned reserved[3]; /* must be zero for forward compatibility */ +} LZ4F_preferences_t; + +#define LZ4F_INIT_PREFERENCES { LZ4F_INIT_FRAMEINFO, 0, 0u, 0u, { 0u, 0u, 0u } } /* v1.8.3+ */ + + +/*-********************************* +* Simple compression function +***********************************/ + +/*! LZ4F_compressFrame() : + * Compress srcBuffer content into an LZ4-compressed frame. + * It's a one shot operation, all input content is consumed, and all output is generated. + * + * Note : it's a stateless operation (no LZ4F_cctx state needed). + * In order to reduce load on the allocator, LZ4F_compressFrame(), by default, + * uses the stack to allocate space for the compression state and some table. + * If this usage of the stack is too much for your application, + * consider compiling `lz4frame.c` with compile-time macro LZ4F_HEAPMODE set to 1 instead. + * All state allocations will use the Heap. + * It also means each invocation of LZ4F_compressFrame() will trigger several internal alloc/free invocations. + * + * @dstCapacity MUST be >= LZ4F_compressFrameBound(srcSize, preferencesPtr). + * @preferencesPtr is optional : one can provide NULL, in which case all preferences are set to default. + * @return : number of bytes written into dstBuffer. + * or an error code if it fails (can be tested using LZ4F_isError()) + */ +LZ4FLIB_API size_t LZ4F_compressFrame(void* dstBuffer, size_t dstCapacity, + const void* srcBuffer, size_t srcSize, + const LZ4F_preferences_t* preferencesPtr); + +/*! LZ4F_compressFrameBound() : + * Returns the maximum possible compressed size with LZ4F_compressFrame() given srcSize and preferences. + * `preferencesPtr` is optional. It can be replaced by NULL, in which case, the function will assume default preferences. + * Note : this result is only usable with LZ4F_compressFrame(). + * It may also be relevant to LZ4F_compressUpdate() _only if_ no flush() operation is ever performed. + */ +LZ4FLIB_API size_t LZ4F_compressFrameBound(size_t srcSize, const LZ4F_preferences_t* preferencesPtr); + + +/*! LZ4F_compressionLevel_max() : + * @return maximum allowed compression level (currently: 12) + */ +LZ4FLIB_API int LZ4F_compressionLevel_max(void); /* v1.8.0+ */ + + +/*-*********************************** +* Advanced compression functions +*************************************/ +typedef struct LZ4F_cctx_s LZ4F_cctx; /* incomplete type */ +typedef LZ4F_cctx* LZ4F_compressionContext_t; /* for compatibility with older APIs, prefer using LZ4F_cctx */ + +typedef struct { + unsigned stableSrc; /* 1 == src content will remain present on future calls to LZ4F_compress(); skip copying src content within tmp buffer */ + unsigned reserved[3]; +} LZ4F_compressOptions_t; + +/*--- Resource Management ---*/ + +#define LZ4F_VERSION 100 /* This number can be used to check for an incompatible API breaking change */ +LZ4FLIB_API unsigned LZ4F_getVersion(void); + +/*! LZ4F_createCompressionContext() : + * The first thing to do is to create a compressionContext object, + * which will keep track of operation state during streaming compression. + * This is achieved using LZ4F_createCompressionContext(), which takes as argument a version, + * and a pointer to LZ4F_cctx*, to write the resulting pointer into. + * @version provided MUST be LZ4F_VERSION. It is intended to track potential version mismatch, notably when using DLL. + * The function provides a pointer to a fully allocated LZ4F_cctx object. + * @cctxPtr MUST be != NULL. + * If @return != zero, context creation failed. + * A created compression context can be employed multiple times for consecutive streaming operations. + * Once all streaming compression jobs are completed, + * the state object can be released using LZ4F_freeCompressionContext(). + * Note1 : LZ4F_freeCompressionContext() is always successful. Its return value can be ignored. + * Note2 : LZ4F_freeCompressionContext() works fine with NULL input pointers (do nothing). +**/ +LZ4FLIB_API LZ4F_errorCode_t LZ4F_createCompressionContext(LZ4F_cctx** cctxPtr, unsigned version); +LZ4FLIB_API LZ4F_errorCode_t LZ4F_freeCompressionContext(LZ4F_cctx* cctx); + + +/*---- Compression ----*/ + +#define LZ4F_HEADER_SIZE_MIN 7 /* LZ4 Frame header size can vary, depending on selected parameters */ +#define LZ4F_HEADER_SIZE_MAX 19 + +/* Size in bytes of a block header in little-endian format. Highest bit indicates if block data is uncompressed */ +#define LZ4F_BLOCK_HEADER_SIZE 4 + +/* Size in bytes of a block checksum footer in little-endian format. */ +#define LZ4F_BLOCK_CHECKSUM_SIZE 4 + +/* Size in bytes of the content checksum. */ +#define LZ4F_CONTENT_CHECKSUM_SIZE 4 + +/*! LZ4F_compressBegin() : + * will write the frame header into dstBuffer. + * dstCapacity must be >= LZ4F_HEADER_SIZE_MAX bytes. + * `prefsPtr` is optional : NULL can be provided to set all preferences to default. + * @return : number of bytes written into dstBuffer for the header + * or an error code (which can be tested using LZ4F_isError()) + */ +LZ4FLIB_API size_t LZ4F_compressBegin(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const LZ4F_preferences_t* prefsPtr); + +/*! LZ4F_compressBound() : + * Provides minimum dstCapacity required to guarantee success of + * LZ4F_compressUpdate(), given a srcSize and preferences, for a worst case scenario. + * When srcSize==0, LZ4F_compressBound() provides an upper bound for LZ4F_flush() and LZ4F_compressEnd() instead. + * Note that the result is only valid for a single invocation of LZ4F_compressUpdate(). + * When invoking LZ4F_compressUpdate() multiple times, + * if the output buffer is gradually filled up instead of emptied and re-used from its start, + * one must check if there is enough remaining capacity before each invocation, using LZ4F_compressBound(). + * @return is always the same for a srcSize and prefsPtr. + * prefsPtr is optional : when NULL is provided, preferences will be set to cover worst case scenario. + * tech details : + * @return if automatic flushing is not enabled, includes the possibility that internal buffer might already be filled by up to (blockSize-1) bytes. + * It also includes frame footer (ending + checksum), since it might be generated by LZ4F_compressEnd(). + * @return doesn't include frame header, as it was already generated by LZ4F_compressBegin(). + */ +LZ4FLIB_API size_t LZ4F_compressBound(size_t srcSize, const LZ4F_preferences_t* prefsPtr); + +/*! LZ4F_compressUpdate() : + * LZ4F_compressUpdate() can be called repetitively to compress as much data as necessary. + * Important rule: dstCapacity MUST be large enough to ensure operation success even in worst case situations. + * This value is provided by LZ4F_compressBound(). + * If this condition is not respected, LZ4F_compress() will fail (result is an errorCode). + * After an error, the state is left in a UB state, and must be re-initialized or freed. + * If previously an uncompressed block was written, buffered data is flushed + * before appending compressed data is continued. + * `cOptPtr` is optional : NULL can be provided, in which case all options are set to default. + * @return : number of bytes written into `dstBuffer` (it can be zero, meaning input data was just buffered). + * or an error code if it fails (which can be tested using LZ4F_isError()) + */ +LZ4FLIB_API size_t LZ4F_compressUpdate(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const void* srcBuffer, size_t srcSize, + const LZ4F_compressOptions_t* cOptPtr); + +/*! LZ4F_flush() : + * When data must be generated and sent immediately, without waiting for a block to be completely filled, + * it's possible to call LZ4_flush(). It will immediately compress any data buffered within cctx. + * `dstCapacity` must be large enough to ensure the operation will be successful. + * `cOptPtr` is optional : it's possible to provide NULL, all options will be set to default. + * @return : nb of bytes written into dstBuffer (can be zero, when there is no data stored within cctx) + * or an error code if it fails (which can be tested using LZ4F_isError()) + * Note : LZ4F_flush() is guaranteed to be successful when dstCapacity >= LZ4F_compressBound(0, prefsPtr). + */ +LZ4FLIB_API size_t LZ4F_flush(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const LZ4F_compressOptions_t* cOptPtr); + +/*! LZ4F_compressEnd() : + * To properly finish an LZ4 frame, invoke LZ4F_compressEnd(). + * It will flush whatever data remained within `cctx` (like LZ4_flush()) + * and properly finalize the frame, with an endMark and a checksum. + * `cOptPtr` is optional : NULL can be provided, in which case all options will be set to default. + * @return : nb of bytes written into dstBuffer, necessarily >= 4 (endMark), + * or an error code if it fails (which can be tested using LZ4F_isError()) + * Note : LZ4F_compressEnd() is guaranteed to be successful when dstCapacity >= LZ4F_compressBound(0, prefsPtr). + * A successful call to LZ4F_compressEnd() makes `cctx` available again for another compression task. + */ +LZ4FLIB_API size_t LZ4F_compressEnd(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const LZ4F_compressOptions_t* cOptPtr); + + +/*-********************************* +* Decompression functions +***********************************/ +typedef struct LZ4F_dctx_s LZ4F_dctx; /* incomplete type */ +typedef LZ4F_dctx* LZ4F_decompressionContext_t; /* compatibility with previous API versions */ + +typedef struct { + unsigned stableDst; /* pledges that last 64KB decompressed data is present right before @dstBuffer pointer. + * This optimization skips internal storage operations. + * Once set, this pledge must remain valid up to the end of current frame. */ + unsigned skipChecksums; /* disable checksum calculation and verification, even when one is present in frame, to save CPU time. + * Setting this option to 1 once disables all checksums for the rest of the frame. */ + unsigned reserved1; /* must be set to zero for forward compatibility */ + unsigned reserved0; /* idem */ +} LZ4F_decompressOptions_t; + + +/* Resource management */ + +/*! LZ4F_createDecompressionContext() : + * Create an LZ4F_dctx object, to track all decompression operations. + * @version provided MUST be LZ4F_VERSION. + * @dctxPtr MUST be valid. + * The function fills @dctxPtr with the value of a pointer to an allocated and initialized LZ4F_dctx object. + * The @return is an errorCode, which can be tested using LZ4F_isError(). + * dctx memory can be released using LZ4F_freeDecompressionContext(); + * Result of LZ4F_freeDecompressionContext() indicates current state of decompressionContext when being released. + * That is, it should be == 0 if decompression has been completed fully and correctly. + */ +LZ4FLIB_API LZ4F_errorCode_t LZ4F_createDecompressionContext(LZ4F_dctx** dctxPtr, unsigned version); +LZ4FLIB_API LZ4F_errorCode_t LZ4F_freeDecompressionContext(LZ4F_dctx* dctx); + + +/*-*********************************** +* Streaming decompression functions +*************************************/ + +#define LZ4F_MAGICNUMBER 0x184D2204U +#define LZ4F_MAGIC_SKIPPABLE_START 0x184D2A50U +#define LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH 5 + +/*! LZ4F_headerSize() : v1.9.0+ + * Provide the header size of a frame starting at `src`. + * `srcSize` must be >= LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH, + * which is enough to decode the header length. + * @return : size of frame header + * or an error code, which can be tested using LZ4F_isError() + * note : Frame header size is variable, but is guaranteed to be + * >= LZ4F_HEADER_SIZE_MIN bytes, and <= LZ4F_HEADER_SIZE_MAX bytes. + */ +LZ4FLIB_API size_t LZ4F_headerSize(const void* src, size_t srcSize); + +/*! LZ4F_getFrameInfo() : + * This function extracts frame parameters (max blockSize, dictID, etc.). + * Its usage is optional: user can also invoke LZ4F_decompress() directly. + * + * Extracted information will fill an existing LZ4F_frameInfo_t structure. + * This can be useful for allocation and dictionary identification purposes. + * + * LZ4F_getFrameInfo() can work in the following situations : + * + * 1) At the beginning of a new frame, before any invocation of LZ4F_decompress(). + * It will decode header from `srcBuffer`, + * consuming the header and starting the decoding process. + * + * Input size must be large enough to contain the full frame header. + * Frame header size can be known beforehand by LZ4F_headerSize(). + * Frame header size is variable, but is guaranteed to be >= LZ4F_HEADER_SIZE_MIN bytes, + * and not more than <= LZ4F_HEADER_SIZE_MAX bytes. + * Hence, blindly providing LZ4F_HEADER_SIZE_MAX bytes or more will always work. + * It's allowed to provide more input data than the header size, + * LZ4F_getFrameInfo() will only consume the header. + * + * If input size is not large enough, + * aka if it's smaller than header size, + * function will fail and return an error code. + * + * 2) After decoding has been started, + * it's possible to invoke LZ4F_getFrameInfo() anytime + * to extract already decoded frame parameters stored within dctx. + * + * Note that, if decoding has barely started, + * and not yet read enough information to decode the header, + * LZ4F_getFrameInfo() will fail. + * + * The number of bytes consumed from srcBuffer will be updated in *srcSizePtr (necessarily <= original value). + * LZ4F_getFrameInfo() only consumes bytes when decoding has not yet started, + * and when decoding the header has been successful. + * Decompression must then resume from (srcBuffer + *srcSizePtr). + * + * @return : a hint about how many srcSize bytes LZ4F_decompress() expects for next call, + * or an error code which can be tested using LZ4F_isError(). + * note 1 : in case of error, dctx is not modified. Decoding operation can resume from beginning safely. + * note 2 : frame parameters are *copied into* an already allocated LZ4F_frameInfo_t structure. + */ +LZ4FLIB_API size_t +LZ4F_getFrameInfo(LZ4F_dctx* dctx, + LZ4F_frameInfo_t* frameInfoPtr, + const void* srcBuffer, size_t* srcSizePtr); + +/*! LZ4F_decompress() : + * Call this function repetitively to regenerate data compressed in `srcBuffer`. + * + * The function requires a valid dctx state. + * It will read up to *srcSizePtr bytes from srcBuffer, + * and decompress data into dstBuffer, of capacity *dstSizePtr. + * + * The nb of bytes consumed from srcBuffer will be written into *srcSizePtr (necessarily <= original value). + * The nb of bytes decompressed into dstBuffer will be written into *dstSizePtr (necessarily <= original value). + * + * The function does not necessarily read all input bytes, so always check value in *srcSizePtr. + * Unconsumed source data must be presented again in subsequent invocations. + * + * `dstBuffer` can freely change between each consecutive function invocation. + * `dstBuffer` content will be overwritten. + * + * Note: if `LZ4F_getFrameInfo()` is called before `LZ4F_decompress()`, srcBuffer must be updated to reflect + * the number of bytes consumed after reading the frame header. Failure to update srcBuffer before calling + * `LZ4F_decompress()` will cause decompression failure or, even worse, successful but incorrect decompression. + * See the `LZ4F_getFrameInfo()` docs for details. + * + * @return : an hint of how many `srcSize` bytes LZ4F_decompress() expects for next call. + * Schematically, it's the size of the current (or remaining) compressed block + header of next block. + * Respecting the hint provides some small speed benefit, because it skips intermediate buffers. + * This is just a hint though, it's always possible to provide any srcSize. + * + * When a frame is fully decoded, @return will be 0 (no more data expected). + * When provided with more bytes than necessary to decode a frame, + * LZ4F_decompress() will stop reading exactly at end of current frame, and @return 0. + * + * If decompression failed, @return is an error code, which can be tested using LZ4F_isError(). + * After a decompression error, the `dctx` context is not resumable. + * Use LZ4F_resetDecompressionContext() to return to clean state. + * + * After a frame is fully decoded, dctx can be used again to decompress another frame. + */ +LZ4FLIB_API size_t +LZ4F_decompress(LZ4F_dctx* dctx, + void* dstBuffer, size_t* dstSizePtr, + const void* srcBuffer, size_t* srcSizePtr, + const LZ4F_decompressOptions_t* dOptPtr); + + +/*! LZ4F_resetDecompressionContext() : added in v1.8.0 + * In case of an error, the context is left in "undefined" state. + * In which case, it's necessary to reset it, before re-using it. + * This method can also be used to abruptly stop any unfinished decompression, + * and start a new one using same context resources. */ +LZ4FLIB_API void LZ4F_resetDecompressionContext(LZ4F_dctx* dctx); /* always successful */ + + +/********************************** + * Dictionary compression API + *********************************/ + +/* A Dictionary is useful for the compression of small messages (KB range). + * It dramatically improves compression efficiency. + * + * LZ4 can ingest any input as dictionary, though only the last 64 KB are useful. + * Better results are generally achieved by using Zstandard's Dictionary Builder + * to generate a high-quality dictionary from a set of samples. + * + * The same dictionary will have to be used on the decompression side + * for decoding to be successful. + * To help identify the correct dictionary at decoding stage, + * the frame header allows optional embedding of a dictID field. + */ + +/*! LZ4F_compressBegin_usingDict() : stable since v1.10 + * Inits dictionary compression streaming, and writes the frame header into dstBuffer. + * @dstCapacity must be >= LZ4F_HEADER_SIZE_MAX bytes. + * @prefsPtr is optional : one may provide NULL as argument, + * however, it's the only way to provide dictID in the frame header. + * @dictBuffer must outlive the compression session. + * @return : number of bytes written into dstBuffer for the header, + * or an error code (which can be tested using LZ4F_isError()) + * NOTE: The LZ4Frame spec allows each independent block to be compressed with the dictionary, + * but this entry supports a more limited scenario, where only the first block uses the dictionary. + * This is still useful for small data, which only need one block anyway. + * For larger inputs, one may be more interested in LZ4F_compressFrame_usingCDict() below. + */ +LZ4FLIB_API size_t +LZ4F_compressBegin_usingDict(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const void* dictBuffer, size_t dictSize, + const LZ4F_preferences_t* prefsPtr); + +/*! LZ4F_decompress_usingDict() : stable since v1.10 + * Same as LZ4F_decompress(), using a predefined dictionary. + * Dictionary is used "in place", without any preprocessing. +** It must remain accessible throughout the entire frame decoding. */ +LZ4FLIB_API size_t +LZ4F_decompress_usingDict(LZ4F_dctx* dctxPtr, + void* dstBuffer, size_t* dstSizePtr, + const void* srcBuffer, size_t* srcSizePtr, + const void* dict, size_t dictSize, + const LZ4F_decompressOptions_t* decompressOptionsPtr); + +/***************************************** + * Bulk processing dictionary compression + *****************************************/ + +/* Loading a dictionary has a cost, since it involves construction of tables. + * The Bulk processing dictionary API makes it possible to share this cost + * over an arbitrary number of compression jobs, even concurrently, + * markedly improving compression latency for these cases. + * + * Note that there is no corresponding bulk API for the decompression side, + * because dictionary does not carry any initialization cost for decompression. + * Use the regular LZ4F_decompress_usingDict() there. + */ +typedef struct LZ4F_CDict_s LZ4F_CDict; + +/*! LZ4_createCDict() : stable since v1.10 + * When compressing multiple messages / blocks using the same dictionary, it's recommended to initialize it just once. + * LZ4_createCDict() will create a digested dictionary, ready to start future compression operations without startup delay. + * LZ4_CDict can be created once and shared by multiple threads concurrently, since its usage is read-only. + * @dictBuffer can be released after LZ4_CDict creation, since its content is copied within CDict. */ +LZ4FLIB_API LZ4F_CDict* LZ4F_createCDict(const void* dictBuffer, size_t dictSize); +LZ4FLIB_API void LZ4F_freeCDict(LZ4F_CDict* CDict); + +/*! LZ4_compressFrame_usingCDict() : stable since v1.10 + * Compress an entire srcBuffer into a valid LZ4 frame using a digested Dictionary. + * @cctx must point to a context created by LZ4F_createCompressionContext(). + * If @cdict==NULL, compress without a dictionary. + * @dstBuffer MUST be >= LZ4F_compressFrameBound(srcSize, preferencesPtr). + * If this condition is not respected, function will fail (@return an errorCode). + * The LZ4F_preferences_t structure is optional : one may provide NULL as argument, + * but it's not recommended, as it's the only way to provide @dictID in the frame header. + * @return : number of bytes written into dstBuffer. + * or an error code if it fails (can be tested using LZ4F_isError()) + * Note: for larger inputs generating multiple independent blocks, + * this entry point uses the dictionary for each block. */ +LZ4FLIB_API size_t +LZ4F_compressFrame_usingCDict(LZ4F_cctx* cctx, + void* dst, size_t dstCapacity, + const void* src, size_t srcSize, + const LZ4F_CDict* cdict, + const LZ4F_preferences_t* preferencesPtr); + +/*! LZ4F_compressBegin_usingCDict() : stable since v1.10 + * Inits streaming dictionary compression, and writes the frame header into dstBuffer. + * @dstCapacity must be >= LZ4F_HEADER_SIZE_MAX bytes. + * @prefsPtr is optional : one may provide NULL as argument, + * note however that it's the only way to insert a @dictID in the frame header. + * @cdict must outlive the compression session. + * @return : number of bytes written into dstBuffer for the header, + * or an error code, which can be tested using LZ4F_isError(). */ +LZ4FLIB_API size_t +LZ4F_compressBegin_usingCDict(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const LZ4F_CDict* cdict, + const LZ4F_preferences_t* prefsPtr); + + +#if defined (__cplusplus) +} +#endif + +#endif /* LZ4F_H_09782039843 */ + +#if defined(LZ4F_STATIC_LINKING_ONLY) && !defined(LZ4F_H_STATIC_09782039843) +#define LZ4F_H_STATIC_09782039843 + +/* Note : + * The below declarations are not stable and may change in the future. + * They are therefore only safe to depend on + * when the caller is statically linked against the library. + * To access their declarations, define LZ4F_STATIC_LINKING_ONLY. + * + * By default, these symbols aren't published into shared/dynamic libraries. + * You can override this behavior and force them to be published + * by defining LZ4F_PUBLISH_STATIC_FUNCTIONS. + * Use at your own risk. + */ + +#if defined (__cplusplus) +extern "C" { +#endif + +#ifdef LZ4F_PUBLISH_STATIC_FUNCTIONS +# define LZ4FLIB_STATIC_API LZ4FLIB_API +#else +# define LZ4FLIB_STATIC_API +#endif + + +/* --- Error List --- */ +#define LZ4F_LIST_ERRORS(ITEM) \ + ITEM(OK_NoError) \ + ITEM(ERROR_GENERIC) \ + ITEM(ERROR_maxBlockSize_invalid) \ + ITEM(ERROR_blockMode_invalid) \ + ITEM(ERROR_parameter_invalid) \ + ITEM(ERROR_compressionLevel_invalid) \ + ITEM(ERROR_headerVersion_wrong) \ + ITEM(ERROR_blockChecksum_invalid) \ + ITEM(ERROR_reservedFlag_set) \ + ITEM(ERROR_allocation_failed) \ + ITEM(ERROR_srcSize_tooLarge) \ + ITEM(ERROR_dstMaxSize_tooSmall) \ + ITEM(ERROR_frameHeader_incomplete) \ + ITEM(ERROR_frameType_unknown) \ + ITEM(ERROR_frameSize_wrong) \ + ITEM(ERROR_srcPtr_wrong) \ + ITEM(ERROR_decompressionFailed) \ + ITEM(ERROR_headerChecksum_invalid) \ + ITEM(ERROR_contentChecksum_invalid) \ + ITEM(ERROR_frameDecoding_alreadyStarted) \ + ITEM(ERROR_compressionState_uninitialized) \ + ITEM(ERROR_parameter_null) \ + ITEM(ERROR_io_write) \ + ITEM(ERROR_io_read) \ + ITEM(ERROR_maxCode) + +#define LZ4F_GENERATE_ENUM(ENUM) LZ4F_##ENUM, + +/* enum list is exposed, to handle specific errors */ +typedef enum { LZ4F_LIST_ERRORS(LZ4F_GENERATE_ENUM) + _LZ4F_dummy_error_enum_for_c89_never_used } LZ4F_errorCodes; + +LZ4FLIB_STATIC_API LZ4F_errorCodes LZ4F_getErrorCode(size_t functionResult); + +/********************************** + * Advanced compression operations + *********************************/ + +/*! LZ4F_getBlockSize() : + * @return, in scalar format (size_t), + * the maximum block size associated with @blockSizeID, + * or an error code (can be tested using LZ4F_isError()) if @blockSizeID is invalid. +**/ +LZ4FLIB_STATIC_API size_t LZ4F_getBlockSize(LZ4F_blockSizeID_t blockSizeID); + +/*! LZ4F_uncompressedUpdate() : + * LZ4F_uncompressedUpdate() can be called repetitively to add data stored as uncompressed blocks. + * Important rule: dstCapacity MUST be large enough to store the entire source buffer as + * no compression is done for this operation + * If this condition is not respected, LZ4F_uncompressedUpdate() will fail (result is an errorCode). + * After an error, the state is left in a UB state, and must be re-initialized or freed. + * If previously a compressed block was written, buffered data is flushed first, + * before appending uncompressed data is continued. + * This operation is only supported when LZ4F_blockIndependent is used. + * `cOptPtr` is optional : NULL can be provided, in which case all options are set to default. + * @return : number of bytes written into `dstBuffer` (it can be zero, meaning input data was just buffered). + * or an error code if it fails (which can be tested using LZ4F_isError()) + */ +LZ4FLIB_STATIC_API size_t +LZ4F_uncompressedUpdate(LZ4F_cctx* cctx, + void* dstBuffer, size_t dstCapacity, + const void* srcBuffer, size_t srcSize, + const LZ4F_compressOptions_t* cOptPtr); + +/********************************** + * Custom memory allocation + *********************************/ + +/*! Custom memory allocation : v1.9.4+ + * These prototypes make it possible to pass custom allocation/free functions. + * LZ4F_customMem is provided at state creation time, using LZ4F_create*_advanced() listed below. + * All allocation/free operations will be completed using these custom variants instead of regular ones. + */ +typedef void* (*LZ4F_AllocFunction) (void* opaqueState, size_t size); +typedef void* (*LZ4F_CallocFunction) (void* opaqueState, size_t size); +typedef void (*LZ4F_FreeFunction) (void* opaqueState, void* address); +typedef struct { + LZ4F_AllocFunction customAlloc; + LZ4F_CallocFunction customCalloc; /* optional; when not defined, uses customAlloc + memset */ + LZ4F_FreeFunction customFree; + void* opaqueState; +} LZ4F_CustomMem; +static +#ifdef __GNUC__ +__attribute__((__unused__)) +#endif +LZ4F_CustomMem const LZ4F_defaultCMem = { NULL, NULL, NULL, NULL }; /**< this constant defers to stdlib's functions */ + +LZ4FLIB_STATIC_API LZ4F_cctx* LZ4F_createCompressionContext_advanced(LZ4F_CustomMem customMem, unsigned version); +LZ4FLIB_STATIC_API LZ4F_dctx* LZ4F_createDecompressionContext_advanced(LZ4F_CustomMem customMem, unsigned version); +LZ4FLIB_STATIC_API LZ4F_CDict* LZ4F_createCDict_advanced(LZ4F_CustomMem customMem, const void* dictBuffer, size_t dictSize); + + +#if defined (__cplusplus) +} +#endif + +#endif /* defined(LZ4F_STATIC_LINKING_ONLY) && !defined(LZ4F_H_STATIC_09782039843) */ diff --git a/vendor/compress/lz4/src/lz4hc.h b/vendor/compress/lz4/src/lz4hc.h new file mode 100644 index 000000000..992bc8cdd --- /dev/null +++ b/vendor/compress/lz4/src/lz4hc.h @@ -0,0 +1,414 @@ +/* + LZ4 HC - High Compression Mode of LZ4 + Header File + Copyright (C) 2011-2020, Yann Collet. + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 source repository : https://github.com/lz4/lz4 + - LZ4 public forum : https://groups.google.com/forum/#!forum/lz4c +*/ +#ifndef LZ4_HC_H_19834876238432 +#define LZ4_HC_H_19834876238432 + +#if defined (__cplusplus) +extern "C" { +#endif + +/* --- Dependency --- */ +/* note : lz4hc requires lz4.h/lz4.c for compilation */ +#include "lz4.h" /* stddef, LZ4LIB_API, LZ4_DEPRECATED */ + + +/* --- Useful constants --- */ +#define LZ4HC_CLEVEL_MIN 2 +#define LZ4HC_CLEVEL_DEFAULT 9 +#define LZ4HC_CLEVEL_OPT_MIN 10 +#define LZ4HC_CLEVEL_MAX 12 + + +/*-************************************ + * Block Compression + **************************************/ +/*! LZ4_compress_HC() : + * Compress data from `src` into `dst`, using the powerful but slower "HC" algorithm. + * `dst` must be already allocated. + * Compression is guaranteed to succeed if `dstCapacity >= LZ4_compressBound(srcSize)` (see "lz4.h") + * Max supported `srcSize` value is LZ4_MAX_INPUT_SIZE (see "lz4.h") + * `compressionLevel` : any value between 1 and LZ4HC_CLEVEL_MAX will work. + * Values > LZ4HC_CLEVEL_MAX behave the same as LZ4HC_CLEVEL_MAX. + * @return : the number of bytes written into 'dst' + * or 0 if compression fails. + */ +LZ4LIB_API int LZ4_compress_HC (const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel); + + +/* Note : + * Decompression functions are provided within "lz4.h" (BSD license) + */ + + +/*! LZ4_compress_HC_extStateHC() : + * Same as LZ4_compress_HC(), but using an externally allocated memory segment for `state`. + * `state` size is provided by LZ4_sizeofStateHC(). + * Memory segment must be aligned on 8-bytes boundaries (which a normal malloc() should do properly). + */ +LZ4LIB_API int LZ4_sizeofStateHC(void); +LZ4LIB_API int LZ4_compress_HC_extStateHC(void* stateHC, const char* src, char* dst, int srcSize, int maxDstSize, int compressionLevel); + + +/*! LZ4_compress_HC_destSize() : v1.9.0+ + * Will compress as much data as possible from `src` + * to fit into `targetDstSize` budget. + * Result is provided in 2 parts : + * @return : the number of bytes written into 'dst' (necessarily <= targetDstSize) + * or 0 if compression fails. + * `srcSizePtr` : on success, *srcSizePtr is updated to indicate how much bytes were read from `src` + */ +LZ4LIB_API int LZ4_compress_HC_destSize(void* stateHC, + const char* src, char* dst, + int* srcSizePtr, int targetDstSize, + int compressionLevel); + + +/*-************************************ + * Streaming Compression + * Bufferless synchronous API + **************************************/ + typedef union LZ4_streamHC_u LZ4_streamHC_t; /* incomplete type (defined later) */ + +/*! LZ4_createStreamHC() and LZ4_freeStreamHC() : + * These functions create and release memory for LZ4 HC streaming state. + * Newly created states are automatically initialized. + * A same state can be used multiple times consecutively, + * starting with LZ4_resetStreamHC_fast() to start a new stream of blocks. + */ +LZ4LIB_API LZ4_streamHC_t* LZ4_createStreamHC(void); +LZ4LIB_API int LZ4_freeStreamHC (LZ4_streamHC_t* streamHCPtr); + +/* + These functions compress data in successive blocks of any size, + using previous blocks as dictionary, to improve compression ratio. + One key assumption is that previous blocks (up to 64 KB) remain read-accessible while compressing next blocks. + There is an exception for ring buffers, which can be smaller than 64 KB. + Ring-buffer scenario is automatically detected and handled within LZ4_compress_HC_continue(). + + Before starting compression, state must be allocated and properly initialized. + LZ4_createStreamHC() does both, though compression level is set to LZ4HC_CLEVEL_DEFAULT. + + Selecting the compression level can be done with LZ4_resetStreamHC_fast() (starts a new stream) + or LZ4_setCompressionLevel() (anytime, between blocks in the same stream) (experimental). + LZ4_resetStreamHC_fast() only works on states which have been properly initialized at least once, + which is automatically the case when state is created using LZ4_createStreamHC(). + + After reset, a first "fictional block" can be designated as initial dictionary, + using LZ4_loadDictHC() (Optional). + Note: In order for LZ4_loadDictHC() to create the correct data structure, + it is essential to set the compression level _before_ loading the dictionary. + + Invoke LZ4_compress_HC_continue() to compress each successive block. + The number of blocks is unlimited. + Previous input blocks, including initial dictionary when present, + must remain accessible and unmodified during compression. + + It's allowed to update compression level anytime between blocks, + using LZ4_setCompressionLevel() (experimental). + + @dst buffer should be sized to handle worst case scenarios + (see LZ4_compressBound(), it ensures compression success). + In case of failure, the API does not guarantee recovery, + so the state _must_ be reset. + To ensure compression success + whenever @dst buffer size cannot be made >= LZ4_compressBound(), + consider using LZ4_compress_HC_continue_destSize(). + + Whenever previous input blocks can't be preserved unmodified in-place during compression of next blocks, + it's possible to copy the last blocks into a more stable memory space, using LZ4_saveDictHC(). + Return value of LZ4_saveDictHC() is the size of dictionary effectively saved into 'safeBuffer' (<= 64 KB) + + After completing a streaming compression, + it's possible to start a new stream of blocks, using the same LZ4_streamHC_t state, + just by resetting it, using LZ4_resetStreamHC_fast(). +*/ + +LZ4LIB_API void LZ4_resetStreamHC_fast(LZ4_streamHC_t* streamHCPtr, int compressionLevel); /* v1.9.0+ */ +LZ4LIB_API int LZ4_loadDictHC (LZ4_streamHC_t* streamHCPtr, const char* dictionary, int dictSize); + +LZ4LIB_API int LZ4_compress_HC_continue (LZ4_streamHC_t* streamHCPtr, + const char* src, char* dst, + int srcSize, int maxDstSize); + +/*! LZ4_compress_HC_continue_destSize() : v1.9.0+ + * Similar to LZ4_compress_HC_continue(), + * but will read as much data as possible from `src` + * to fit into `targetDstSize` budget. + * Result is provided into 2 parts : + * @return : the number of bytes written into 'dst' (necessarily <= targetDstSize) + * or 0 if compression fails. + * `srcSizePtr` : on success, *srcSizePtr will be updated to indicate how much bytes were read from `src`. + * Note that this function may not consume the entire input. + */ +LZ4LIB_API int LZ4_compress_HC_continue_destSize(LZ4_streamHC_t* LZ4_streamHCPtr, + const char* src, char* dst, + int* srcSizePtr, int targetDstSize); + +LZ4LIB_API int LZ4_saveDictHC (LZ4_streamHC_t* streamHCPtr, char* safeBuffer, int maxDictSize); + + +/*! LZ4_attach_HC_dictionary() : stable since v1.10.0 + * This API allows for the efficient re-use of a static dictionary many times. + * + * Rather than re-loading the dictionary buffer into a working context before + * each compression, or copying a pre-loaded dictionary's LZ4_streamHC_t into a + * working LZ4_streamHC_t, this function introduces a no-copy setup mechanism, + * in which the working stream references the dictionary stream in-place. + * + * Several assumptions are made about the state of the dictionary stream. + * Currently, only streams which have been prepared by LZ4_loadDictHC() should + * be expected to work. + * + * Alternatively, the provided dictionary stream pointer may be NULL, in which + * case any existing dictionary stream is unset. + * + * A dictionary should only be attached to a stream without any history (i.e., + * a stream that has just been reset). + * + * The dictionary will remain attached to the working stream only for the + * current stream session. Calls to LZ4_resetStreamHC(_fast) will remove the + * dictionary context association from the working stream. The dictionary + * stream (and source buffer) must remain in-place / accessible / unchanged + * through the lifetime of the stream session. + */ +LZ4LIB_API void +LZ4_attach_HC_dictionary(LZ4_streamHC_t* working_stream, + const LZ4_streamHC_t* dictionary_stream); + + +/*^********************************************** + * !!!!!! STATIC LINKING ONLY !!!!!! + ***********************************************/ + +/*-****************************************************************** + * PRIVATE DEFINITIONS : + * Do not use these definitions directly. + * They are merely exposed to allow static allocation of `LZ4_streamHC_t`. + * Declare an `LZ4_streamHC_t` directly, rather than any type below. + * Even then, only do so in the context of static linking, as definitions may change between versions. + ********************************************************************/ + +#define LZ4HC_DICTIONARY_LOGSIZE 16 +#define LZ4HC_MAXD (1<= LZ4HC_CLEVEL_OPT_MIN. + */ +LZ4LIB_STATIC_API void LZ4_favorDecompressionSpeed( + LZ4_streamHC_t* LZ4_streamHCPtr, int favor); + +/*! LZ4_resetStreamHC_fast() : v1.9.0+ + * When an LZ4_streamHC_t is known to be in a internally coherent state, + * it can often be prepared for a new compression with almost no work, only + * sometimes falling back to the full, expensive reset that is always required + * when the stream is in an indeterminate state (i.e., the reset performed by + * LZ4_resetStreamHC()). + * + * LZ4_streamHCs are guaranteed to be in a valid state when: + * - returned from LZ4_createStreamHC() + * - reset by LZ4_resetStreamHC() + * - memset(stream, 0, sizeof(LZ4_streamHC_t)) + * - the stream was in a valid state and was reset by LZ4_resetStreamHC_fast() + * - the stream was in a valid state and was then used in any compression call + * that returned success + * - the stream was in an indeterminate state and was used in a compression + * call that fully reset the state (LZ4_compress_HC_extStateHC()) and that + * returned success + * + * Note: + * A stream that was last used in a compression call that returned an error + * may be passed to this function. However, it will be fully reset, which will + * clear any existing history and settings from the context. + */ +LZ4LIB_STATIC_API void LZ4_resetStreamHC_fast( + LZ4_streamHC_t* LZ4_streamHCPtr, int compressionLevel); + +/*! LZ4_compress_HC_extStateHC_fastReset() : + * A variant of LZ4_compress_HC_extStateHC(). + * + * Using this variant avoids an expensive initialization step. It is only safe + * to call if the state buffer is known to be correctly initialized already + * (see above comment on LZ4_resetStreamHC_fast() for a definition of + * "correctly initialized"). From a high level, the difference is that this + * function initializes the provided state with a call to + * LZ4_resetStreamHC_fast() while LZ4_compress_HC_extStateHC() starts with a + * call to LZ4_resetStreamHC(). + */ +LZ4LIB_STATIC_API int LZ4_compress_HC_extStateHC_fastReset ( + void* state, + const char* src, char* dst, + int srcSize, int dstCapacity, + int compressionLevel); + +#if defined (__cplusplus) +} +#endif + +#endif /* LZ4_HC_SLO_098092834 */ +#endif /* LZ4_HC_STATIC_LINKING_ONLY */ From 6eb28aeafc177ae91ae704c3e4f2c75b9f27b092 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 22 Jul 2024 22:52:10 +0100 Subject: [PATCH 169/198] Check to see if people are return a slice of a local fixed array from a procedure --- src/check_stmt.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 6c3570cc8..76b6d3f40 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -2517,7 +2517,7 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) { Entity *e = entity_of_node(x); if (is_entity_local_variable(e)) { unsafe_return_error(o, "the address of a local variable"); - } else if(x->kind == Ast_CompoundLit) { + } else if (x->kind == Ast_CompoundLit) { unsafe_return_error(o, "the address of a compound literal"); } else if (x->kind == Ast_IndexExpr) { Entity *f = entity_of_node(x->IndexExpr.expr); @@ -2532,6 +2532,14 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) { unsafe_return_error(o, "the address of an indexed variable", f->type); } } + } else if (expr->kind == Ast_SliceExpr) { + Ast *x = unparen_expr(expr->SliceExpr.expr); + Entity *e = entity_of_node(x); + if (is_entity_local_variable(e) && is_type_array(e->type)) { + unsafe_return_error(o, "a slice of a local variable"); + } else if (x->kind == Ast_CompoundLit) { + unsafe_return_error(o, "a slice of a compound literal"); + } } else if (o.mode == Addressing_Constant && is_type_slice(o.type)) { ERROR_BLOCK(); unsafe_return_error(o, "a compound literal of a slice"); From 527c0b3202a5c61c253b83f527aea08fea2370fb Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 23 Jul 2024 00:19:42 +0100 Subject: [PATCH 170/198] Change lib for lz4 --- vendor/compress/lz4/lib/liblz4_static.lib | Bin 239998 -> 667978 bytes vendor/compress/lz4/lz4.odin | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/vendor/compress/lz4/lib/liblz4_static.lib b/vendor/compress/lz4/lib/liblz4_static.lib index dc7610303da2eedc8bb0df37b3686d479b097ed6..b60a626a283f55e974f1a3a2715678c23df60a4a 100644 GIT binary patch literal 667978 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±YT?&0RFU=Q}iV*|2m5c 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 zue®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 PCOO97^^CEjeER=iP>pp! diff --git a/vendor/compress/lz4/lz4.odin b/vendor/compress/lz4/lz4.odin index 62027d9d9..310248d56 100644 --- a/vendor/compress/lz4/lz4.odin +++ b/vendor/compress/lz4/lz4.odin @@ -1,7 +1,8 @@ package vendor_compress_lz4 when ODIN_OS == .Windows { - foreign import lib "lib/libz4_static.lib" + @(extra_linker_flags="/NODEFAULTLIB:libcmt") + foreign import lib "lib/liblz4_static.lib" } import "core:c" From bc5b41938ec29cbc7678c0307e7571a02b3d84b1 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 23 Jul 2024 02:40:51 +0100 Subject: [PATCH 171/198] Fix #3964 --- src/llvm_backend.cpp | 5 +++-- src/llvm_backend.hpp | 2 +- src/llvm_backend_const.cpp | 6 +++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 62909dafb..9fa570eaf 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -3288,11 +3288,12 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { if (!is_type_any(e->type) && !is_type_union(e->type)) { if (tav.mode != Addressing_Invalid) { if (tav.value.kind != ExactValue_Invalid) { + bool is_rodata = e->kind == Entity_Variable && e->Variable.is_rodata; ExactValue v = tav.value; - lbValue init = lb_const_value(m, tav.type, v); + lbValue init = lb_const_value(m, tav.type, v, false, is_rodata); LLVMSetInitializer(g.value, init.value); var.is_initialized = true; - if (e->kind == Entity_Variable && e->Variable.is_rodata) { + if (is_rodata) { LLVMSetGlobalConstant(g.value, true); } } diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 806864c7c..02daecf6b 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -398,7 +398,7 @@ gb_internal lbBlock *lb_create_block(lbProcedure *p, char const *name, bool appe gb_internal lbValue lb_const_nil(lbModule *m, Type *type); gb_internal lbValue lb_const_undef(lbModule *m, Type *type); -gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bool allow_local=true); +gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bool allow_local=true, bool is_rodata=false); gb_internal lbValue lb_const_bool(lbModule *m, Type *type, bool value); gb_internal lbValue lb_const_int(lbModule *m, Type *type, u64 value); diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 12bcc4e1f..4f9ca8714 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -470,7 +470,7 @@ gb_internal bool lb_is_nested_possibly_constant(Type *ft, Selection const &sel, return lb_is_elem_const(elem, ft); } -gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bool allow_local) { +gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bool allow_local, bool is_rodata) { LLVMContextRef ctx = m->ctx; type = default_type(type); @@ -565,6 +565,10 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo array_data = LLVMAddGlobal(m->mod, lb_type(m, t), str); LLVMSetInitializer(array_data, backing_array.value); + if (is_rodata) { + LLVMSetGlobalConstant(array_data, true); + } + lbValue g = {}; g.value = array_data; g.type = t; From b0fe777eded8da007aa76a6c46c0f0d3b02cb8ca Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 23 Jul 2024 03:01:09 +0100 Subject: [PATCH 172/198] Propagate `rodata` a bit more in `lb_const_value` --- src/llvm_backend_const.cpp | 64 +++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 4f9ca8714..6a6b119aa 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -365,7 +365,11 @@ gb_internal lbValue lb_emit_source_code_location_as_global(lbProcedure *p, Ast * -gb_internal LLVMValueRef lb_build_constant_array_values(lbModule *m, Type *type, Type *elem_type, isize count, LLVMValueRef *values, bool allow_local) { +gb_internal LLVMValueRef lb_build_constant_array_values(lbModule *m, Type *type, Type *elem_type, isize count, LLVMValueRef *values, bool allow_local, bool is_rodata) { + if (allow_local) { + is_rodata = false; + } + bool is_local = allow_local && m->curr_procedure != nullptr; bool is_const = true; if (is_local) { @@ -471,6 +475,10 @@ gb_internal bool lb_is_nested_possibly_constant(Type *ft, Selection const &sel, } gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bool allow_local, bool is_rodata) { + if (allow_local) { + is_rodata = false; + } + LLVMContextRef ctx = m->ctx; type = default_type(type); @@ -528,7 +536,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo count = gb_max(cast(isize)cl->max_count, count); Type *elem = base_type(type)->Slice.elem; Type *t = alloc_type_array(elem, count); - lbValue backing_array = lb_const_value(m, t, value, allow_local); + lbValue backing_array = lb_const_value(m, t, value, allow_local, is_rodata); LLVMValueRef array_data = nullptr; @@ -616,7 +624,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } // NOTE(bill, 2021-10-07): Allow for array programming value constants Type *core_elem = core_array_type(type); - return lb_const_value(m, core_elem, value, allow_local); + return lb_const_value(m, core_elem, value, allow_local, is_rodata); } else if (is_type_u8_array(type) && value.kind == ExactValue_String) { GB_ASSERT(type->Array.count == value.value_string.len); LLVMValueRef data = LLVMConstStringInContext(ctx, @@ -634,7 +642,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo Type *elem = type->Array.elem; - lbValue single_elem = lb_const_value(m, elem, value, allow_local); + lbValue single_elem = lb_const_value(m, elem, value, allow_local, is_rodata); LLVMValueRef *elems = gb_alloc_array(permanent_allocator(), LLVMValueRef, cast(isize)count); for (i64 i = 0; i < count; i++) { @@ -652,7 +660,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo Type *elem = type->Matrix.elem; - lbValue single_elem = lb_const_value(m, elem, value, allow_local); + lbValue single_elem = lb_const_value(m, elem, value, allow_local, is_rodata); single_elem.value = llvm_const_cast(single_elem.value, lb_type(m, elem)); i64 total_elem_count = matrix_type_total_internal_elems(type); @@ -674,7 +682,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo i64 count = type->SimdVector.count; Type *elem = type->SimdVector.elem; - lbValue single_elem = lb_const_value(m, elem, value, allow_local); + lbValue single_elem = lb_const_value(m, elem, value, allow_local, is_rodata); single_elem.value = llvm_const_cast(single_elem.value, lb_type(m, elem)); LLVMValueRef *elems = gb_alloc_array(permanent_allocator(), LLVMValueRef, count); @@ -803,7 +811,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo case ExactValue_Compound: if (is_type_slice(type)) { - return lb_const_value(m, type, value, allow_local); + return lb_const_value(m, type, value, allow_local, is_rodata); } else if (is_type_array(type)) { ast_node(cl, CompoundLit, value.value_compound); Type *elem_type = type->Array.elem; @@ -837,7 +845,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } if (lo == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; for (i64 k = lo; k < hi; k++) { values[value_index++] = val; } @@ -852,7 +860,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo i64 index = exact_value_to_i64(index_tav.value); if (index == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; values[value_index++] = val; found = true; break; @@ -865,7 +873,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } } - res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->Array.count, values, allow_local); + res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->Array.count, values, allow_local, is_rodata); return res; } else { GB_ASSERT_MSG(elem_count == type->Array.count, "%td != %td", elem_count, type->Array.count); @@ -875,13 +883,13 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo for (isize i = 0; i < elem_count; i++) { TypeAndValue tav = cl->elems[i]->tav; GB_ASSERT(tav.mode != Addressing_Invalid); - values[i] = lb_const_value(m, elem_type, tav.value, allow_local).value; + values[i] = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; } for (isize i = elem_count; i < type->Array.count; i++) { values[i] = LLVMConstNull(lb_type(m, elem_type)); } - res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->Array.count, values, allow_local); + res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->Array.count, values, allow_local, is_rodata); return res; } } else if (is_type_enumerated_array(type)) { @@ -921,7 +929,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } if (lo == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; for (i64 k = lo; k < hi; k++) { values[value_index++] = val; } @@ -936,7 +944,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo i64 index = exact_value_to_i64(index_tav.value); if (index == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; values[value_index++] = val; found = true; break; @@ -949,7 +957,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } } - res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->EnumeratedArray.count, values, allow_local); + res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->EnumeratedArray.count, values, allow_local, is_rodata); return res; } else { GB_ASSERT_MSG(elem_count == type->EnumeratedArray.count, "%td != %td", elem_count, type->EnumeratedArray.count); @@ -959,13 +967,13 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo for (isize i = 0; i < elem_count; i++) { TypeAndValue tav = cl->elems[i]->tav; GB_ASSERT(tav.mode != Addressing_Invalid); - values[i] = lb_const_value(m, elem_type, tav.value, allow_local).value; + values[i] = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; } for (isize i = elem_count; i < type->EnumeratedArray.count; i++) { values[i] = LLVMConstNull(lb_type(m, elem_type)); } - res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->EnumeratedArray.count, values, allow_local); + res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->EnumeratedArray.count, values, allow_local, is_rodata); return res; } } else if (is_type_simd_vector(type)) { @@ -1004,7 +1012,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } if (lo == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; for (i64 k = lo; k < hi; k++) { values[value_index++] = val; } @@ -1019,7 +1027,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo i64 index = exact_value_to_i64(index_tav.value); if (index == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; values[value_index++] = val; found = true; break; @@ -1038,7 +1046,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo for (isize i = 0; i < elem_count; i++) { TypeAndValue tav = cl->elems[i]->tav; GB_ASSERT(tav.mode != Addressing_Invalid); - values[i] = lb_const_value(m, elem_type, tav.value, allow_local).value; + values[i] = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; } LLVMTypeRef et = lb_type(m, elem_type); @@ -1087,7 +1095,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo i32 index = field_remapping[f->Variable.field_index]; if (elem_type_can_be_constant(f->type)) { if (sel.index.count == 1) { - values[index] = lb_const_value(m, f->type, tav.value, allow_local).value; + values[index] = lb_const_value(m, f->type, tav.value, allow_local, is_rodata).value; visited[index] = true; } else { if (!visited[index]) { @@ -1129,7 +1137,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } } if (is_constant) { - LLVMValueRef elem_value = lb_const_value(m, tav.type, tav.value, allow_local).value; + LLVMValueRef elem_value = lb_const_value(m, tav.type, tav.value, allow_local, is_rodata).value; if (LLVMIsConstant(elem_value)) { values[index] = llvm_const_insert_value(m, values[index], elem_value, idx_list, idx_list_len); } else { @@ -1151,7 +1159,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo i32 index = field_remapping[f->Variable.field_index]; if (elem_type_can_be_constant(f->type)) { - values[index] = lb_const_value(m, f->type, val, allow_local).value; + values[index] = lb_const_value(m, f->type, val, allow_local, is_rodata).value; visited[index] = true; } } @@ -1277,7 +1285,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; for (i64 k = lo; k < hi; k++) { i64 offset = matrix_row_major_index_to_offset(type, k); GB_ASSERT(values[offset] == nullptr); @@ -1289,7 +1297,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo i64 index = exact_value_to_i64(index_tav.value); GB_ASSERT(index < max_count); TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; i64 offset = matrix_row_major_index_to_offset(type, index); GB_ASSERT(values[offset] == nullptr); values[offset] = val; @@ -1302,7 +1310,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } } - res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)total_count, values, allow_local); + res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)total_count, values, allow_local, is_rodata); return res; } else { GB_ASSERT_MSG(elem_count == max_count, "%td != %td", elem_count, max_count); @@ -1313,7 +1321,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo GB_ASSERT(tav.mode != Addressing_Invalid); i64 offset = 0; offset = matrix_row_major_index_to_offset(type, i); - values[offset] = lb_const_value(m, elem_type, tav.value, allow_local).value; + values[offset] = lb_const_value(m, elem_type, tav.value, allow_local, is_rodata).value; } for (isize i = 0; i < total_count; i++) { if (values[i] == nullptr) { @@ -1321,7 +1329,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, bo } } - res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)total_count, values, allow_local); + res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)total_count, values, allow_local, is_rodata); return res; } } else { From c32e12c3f5f4b8c29e7653b4bf33b36b81bdd41d Mon Sep 17 00:00:00 2001 From: Kyle Burke Date: Tue, 23 Jul 2024 00:09:50 -0500 Subject: [PATCH 173/198] Add `NCCALCSIZE_PARAMS` to `sys/windows` --- core/sys/windows/types.odin | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index e568a7bc7..e32a04cbb 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -1091,6 +1091,11 @@ NMHDR :: struct { code: UINT, // NM_ code } +NCCALCSIZE_PARAMS :: struct { + rgrc: [3]RECT, + lppos: PWINDOWPOS, +} + // Generic WM_NOTIFY notification codes NM_OUTOFMEMORY :: ~uintptr(0) // -1 NM_CLICK :: NM_OUTOFMEMORY-1 // uses NMCLICK struct @@ -2204,6 +2209,7 @@ FILE_TYPE_PIPE :: 0x0003 RECT :: struct {left, top, right, bottom: LONG} POINT :: struct {x, y: LONG} +PWINDOWPOS :: ^WINDOWPOS; WINDOWPOS :: struct { hwnd: HWND, hwndInsertAfter: HWND, From 9d99f98194712b4456b7f2ae7b9d6aafa28ecf4a Mon Sep 17 00:00:00 2001 From: Kyle Burke Date: Tue, 23 Jul 2024 00:22:19 -0500 Subject: [PATCH 174/198] Remove semicolon --- core/sys/windows/types.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index e32a04cbb..cf84dc48c 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -2209,7 +2209,7 @@ FILE_TYPE_PIPE :: 0x0003 RECT :: struct {left, top, right, bottom: LONG} POINT :: struct {x, y: LONG} -PWINDOWPOS :: ^WINDOWPOS; +PWINDOWPOS :: ^WINDOWPOS WINDOWPOS :: struct { hwnd: HWND, hwndInsertAfter: HWND, From eb4f850b7f1108bc8e13f8c88173513112d98aba Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Tue, 23 Jul 2024 13:48:03 +0200 Subject: [PATCH 175/198] Remove space in indentation --- core/sys/linux/sys.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index f7cacc544..ec7357c48 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -786,7 +786,7 @@ execve :: proc "contextless" (name: cstring, argv: [^]cstring, envp: [^]cstring) return Errno(-ret) } else { ret := syscall(SYS_execveat, AT_FDCWD, cast(rawptr) name, cast(rawptr) argv, cast(rawptr) envp, i32(0)) - return Errno(-ret) + return Errno(-ret) } } From 0403626acf1c631b20d551d9aef4fb8cb2afd29f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 23 Jul 2024 15:57:17 +0100 Subject: [PATCH 176/198] Add utility calls to os2 --- core/os/os2/errors.odin | 2 ++ core/os/os2/stat.odin | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/core/os/os2/errors.odin b/core/os/os2/errors.odin index 8961bf599..2b9b3528e 100644 --- a/core/os/os2/errors.odin +++ b/core/os/os2/errors.odin @@ -39,6 +39,8 @@ Error :: union #shared_nil { } #assert(size_of(Error) == size_of(u64)) +ERROR_NONE :: Error{} + is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) { diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin index fad33da0a..cfc362d77 100644 --- a/core/os/os2/stat.odin +++ b/core/os/os2/stat.odin @@ -54,3 +54,21 @@ stat_do_not_follow_links :: proc(name: string, allocator: runtime.Allocator) -> same_file :: proc(fi1, fi2: File_Info) -> bool { return _same_file(fi1, fi2) } + + +last_write_time :: modification_time +last_write_time_by_name :: modification_time_by_name + +@(require_results) +modification_time :: proc(f: ^File) -> (time.Time, Error) { + TEMP_ALLOCATOR_GUARD() + fi, err := fstat(f, temp_allocator()) + return fi.modification_time, err +} + +@(require_results) +modification_time_by_name :: proc(path: string) -> (time.Time, Error) { + TEMP_ALLOCATOR_GUARD() + fi, err := stat(path, temp_allocator()) + return fi.modification_time, err +} From 24f9e2bbeb9382f3ed2da48f3675dbeb2cc2bdbd Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 23 Jul 2024 16:06:14 +0100 Subject: [PATCH 177/198] Begin mocking out the linux stuff on os2 --- core/os/os2/file.odin | 14 ++++- core/os/os2/file_linux.odin | 49 ++++------------ core/os/os2/file_windows.odin | 19 ------- core/os/os2/process_linux.odin | 95 ++++++++++++++++++++++++++++++++ core/os/os2/process_windows.odin | 1 - core/os/os2/stat_linux.odin | 16 +++--- 6 files changed, 126 insertions(+), 68 deletions(-) create mode 100644 core/os/os2/process_linux.odin diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index d4abf8f13..8692ecf01 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -267,14 +267,24 @@ exists :: proc(path: string) -> bool { @(require_results) is_file :: proc(path: string) -> bool { - return _is_file(path) + TEMP_ALLOCATOR_GUARD() + fi, err := stat(path, temp_allocator()) + if err != nil { + return false + } + return fi.type == .Regular } is_dir :: is_directory @(require_results) is_directory :: proc(path: string) -> bool { - return _is_dir(path) + TEMP_ALLOCATOR_GUARD() + fi, err := stat(path, temp_allocator()) + if err != nil { + return false + } + return fi.type == .Directory } diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index a8b20c6d9..b567f91d4 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -219,15 +219,24 @@ _truncate :: proc(f: ^File, size: i64) -> Error { } _remove :: proc(name: string) -> Error { + is_dir_fd :: proc(fd: linux.Fd) -> bool { + s: linux.Stat + if linux.fstat(fd, &s) != .NONE { + return false + } + return linux.S_ISDIR(s.mode) + } + TEMP_ALLOCATOR_GUARD() name_cstr := temp_cstring(name) or_return fd, errno := linux.open(name_cstr, {.NOFOLLOW}) #partial switch (errno) { - case .ELOOP: /* symlink */ + case .ELOOP: + /* symlink */ case .NONE: defer linux.close(fd) - if _is_dir_fd(fd) { + if is_dir_fd(fd) { return _get_platform_error(linux.rmdir(name_cstr)) } case: @@ -364,42 +373,6 @@ _exists :: proc(name: string) -> bool { return !res && errno == .NONE } -_is_file :: proc(name: string) -> bool { - TEMP_ALLOCATOR_GUARD() - name_cstr, _ := temp_cstring(name) - s: linux.Stat - if linux.stat(name_cstr, &s) != .NONE { - return false - } - return linux.S_ISREG(s.mode) -} - -_is_file_fd :: proc(fd: linux.Fd) -> bool { - s: linux.Stat - if linux.fstat(fd, &s) != .NONE { - return false - } - return linux.S_ISREG(s.mode) -} - -_is_dir :: proc(name: string) -> bool { - TEMP_ALLOCATOR_GUARD() - name_cstr, _ := temp_cstring(name) - s: linux.Stat - if linux.stat(name_cstr, &s) != .NONE { - return false - } - return linux.S_ISDIR(s.mode) -} - -_is_dir_fd :: proc(fd: linux.Fd) -> bool { - s: linux.Stat - if linux.fstat(fd, &s) != .NONE { - return false - } - return linux.S_ISDIR(s.mode) -} - /* Certain files in the Linux file system are not actual * files (e.g. everything in /proc/). Therefore, the * read_entire_file procs fail to actually read anything diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 97061f281..c69ff1b84 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -723,25 +723,6 @@ _exists :: proc(path: string) -> bool { return attribs != win32.INVALID_FILE_ATTRIBUTES } -_is_file :: proc(path: string) -> bool { - wpath := _fix_long_path(path) - attribs := win32.GetFileAttributesW(wpath) - if attribs != win32.INVALID_FILE_ATTRIBUTES { - return attribs & win32.FILE_ATTRIBUTE_DIRECTORY == 0 - } - return false -} - -_is_dir :: proc(path: string) -> bool { - wpath := _fix_long_path(path) - attribs := win32.GetFileAttributesW(wpath) - if attribs != win32.INVALID_FILE_ATTRIBUTES { - return attribs & win32.FILE_ATTRIBUTE_DIRECTORY != 0 - } - return false -} - - @(private="package") _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { f := (^File_Impl)(stream_data) diff --git a/core/os/os2/process_linux.odin b/core/os/os2/process_linux.odin new file mode 100644 index 000000000..d832083b6 --- /dev/null +++ b/core/os/os2/process_linux.odin @@ -0,0 +1,95 @@ +//+private file +package os2 + +import "base:runtime" +import "core:time" +import "core:sys/linux" + +@(private="package") +_exit :: proc "contextless" (code: int) -> ! { + linux.exit(i32(code)) +} + + +@(private="package") +_get_uid :: proc() -> int { + return -1 +} + +@(private="package") +_get_euid :: proc() -> int { + return -1 +} + +@(private="package") +_get_gid :: proc() -> int { + return -1 +} + +@(private="package") +_get_egid :: proc() -> int { + return -1 +} + +@(private="package") +_get_pid :: proc() -> int { + return -1 +} + +@(private="package") +_get_ppid :: proc() -> int { + return -1 +} + +@(private="package") +_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { + return +} + +@(private="package") +_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + return +} + +@(private="package") +_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + return +} + +@(private="package") +_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + return +} + +@(private="package") +_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { + return +} + +@(private="package") +_Sys_Process_Attributes :: struct {} + +@(private="package") +_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { + return +} + +@(private="package") +_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { + return +} + +@(private="package") +_process_close :: proc(process: Process) -> Error { + return nil +} + +@(private="package") +_process_kill :: proc(process: Process) -> Error { + return nil +} + +@(private="package") +_process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path: string, err: Error) { + return +} \ No newline at end of file diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index 8d61e7be7..d441e7aff 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -1,4 +1,3 @@ -//+build windows //+private file package os2 diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index f194524a7..c09f9b299 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -19,15 +19,15 @@ _fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (File_Inf } type := File_Type.Regular switch s.mode & linux.S_IFMT { - case linux.S_IFBLK: type = .Block_Device - case linux.S_IFCHR: type = .Character_Device - case linux.S_IFDIR: type = .Directory - case linux.S_IFIFO: type = .Named_Pipe - case linux.S_IFLNK: type = .Symlink - case linux.S_IFREG: type = .Regular - case linux.S_IFSOCK: type = .Socket + case linux.S_IFBLK: type = .Block_Device + case linux.S_IFCHR: type = .Character_Device + case linux.S_IFDIR: type = .Directory + case linux.S_IFIFO: type = .Named_Pipe + case linux.S_IFLNK: type = .Symlink + case linux.S_IFREG: type = .Regular + case linux.S_IFSOCK: type = .Socket } - mode := int(s.mode) & 0o7777 + mode := int(0o7777 & transmute(u32)s.mode) // TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time fi := File_Info { fullpath = _get_full_path(fd, allocator), From 182454a1c0b3d1b7bbf81cacaa198583ae56ec62 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 23 Jul 2024 16:09:15 +0100 Subject: [PATCH 178/198] Minor clean ups --- core/os/os2/file_linux.odin | 2 +- core/os/os2/path_linux.odin | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index b567f91d4..cf643b31a 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -156,7 +156,7 @@ _read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { if errno != .NONE { return -1, _get_platform_error(errno) } - return i64(n), n == 0 ? io.Error.EOF : nil + return i64(n), io.Error.EOF if n == 0 else nil } _read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index 80d4d42d1..b388322b5 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -47,10 +47,8 @@ _mkdir_all :: proc(path: string, perm: int) -> Error { // skip consecutive '/' for i += 1; i < len(path) && path[i] == '/'; i += 1 {} return mkdirat(new_dfd, path[i:], perm, has_created) - case: - return _get_platform_error(errno) } - unreachable() + return _get_platform_error(errno) } TEMP_ALLOCATOR_GUARD() // need something we can edit, and use to generate cstrings @@ -74,11 +72,7 @@ _mkdir_all :: proc(path: string, perm: int) -> Error { has_created: bool mkdirat(dfd, path_bytes, perm, &has_created) or_return - if has_created { - return nil - } - return .Exist - //return has_created ? nil : .Exist + return nil if has_created else .Exist } dirent64 :: struct { From 8037ace8735168d3ec7d54e43389c905af176d05 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 23 Jul 2024 16:47:49 +0100 Subject: [PATCH 179/198] Begin work on os2/dir.odin --- core/os/os2/dir.odin | 21 +++++ core/os/os2/dir_linux.odin | 8 ++ core/os/os2/dir_windows.odin | 118 +++++++++++++++++++++++++++++ core/os/os2/env_windows.odin | 15 ++-- core/os/os2/file_windows.odin | 116 +++++++++++++++++++++++----- core/os/os2/internal_util.odin | 2 + core/os/os2/path_windows.odin | 13 ++-- core/os/os2/process_windows.odin | 28 +++---- core/os/os2/stat.odin | 4 +- core/os/os2/stat_windows.odin | 8 +- core/os/os2/temp_file_windows.odin | 2 +- 11 files changed, 286 insertions(+), 49 deletions(-) create mode 100644 core/os/os2/dir.odin create mode 100644 core/os/os2/dir_linux.odin create mode 100644 core/os/os2/dir_windows.odin diff --git a/core/os/os2/dir.odin b/core/os/os2/dir.odin new file mode 100644 index 000000000..5f9deb08a --- /dev/null +++ b/core/os/os2/dir.odin @@ -0,0 +1,21 @@ +package os2 + +import "base:runtime" + +read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) { + return _read_directory(f, n, allocator) +} + +read_all_directory :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) { + return read_directory(f, -1, allocator) +} + +read_directory_by_path :: proc(path: string, n: int, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) { + f := open(path) or_return + defer close(f) + return read_directory(f, n, allocator) +} + +read_all_directory_by_path :: proc(path: string, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) { + return read_directory_by_path(path, -1, allocator) +} \ No newline at end of file diff --git a/core/os/os2/dir_linux.odin b/core/os/os2/dir_linux.odin new file mode 100644 index 000000000..b3d4aed0b --- /dev/null +++ b/core/os/os2/dir_linux.odin @@ -0,0 +1,8 @@ +package os2 + +import "base:runtime" + +@(private) +_read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files: []File_Info, err: Error) { + return +} diff --git a/core/os/os2/dir_windows.odin b/core/os/os2/dir_windows.odin new file mode 100644 index 000000000..36246267d --- /dev/null +++ b/core/os/os2/dir_windows.odin @@ -0,0 +1,118 @@ +package os2 + +import "base:runtime" +import "core:time" +import win32 "core:sys/windows" + +@(private) +_read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files: []File_Info, err: Error) { + find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + // Ignore "." and ".." + if d.cFileName[0] == '.' && d.cFileName[1] == 0 { + return + } + if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 { + return + } + path := concatenate({base_path, `\`, win32_utf16_to_utf8(d.cFileName[:], temp_allocator()) or_else ""}, allocator) or_return + fi.fullpath = path + fi.name = basename(path) + fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) + + if d.dwFileAttributes & win32.FILE_ATTRIBUTE_READONLY != 0 { + fi.mode |= 0o444 + } else { + fi.mode |= 0o666 + } + + is_sym := false + if d.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_Point == 0 { + is_sym = false + } else { + is_sym = d.dwReserved0 == win32.IO_REPARSE_TAG_SYMLINK || d.dwReserved0 == win32.IO_REPARSE_TAG_MOUNT_POINT + } + + if is_sym { + fi.type = .Symlink + } else if d.dwFileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { + fi.type = .Directory + fi.mode |= 0o111 + } + + 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 + } + + if f == nil { + return nil, .Invalid_File + } + + TEMP_ALLOCATOR_GUARD() + + impl := (^File_Impl)(f.impl) + + if !is_directory(impl.name) { + return nil, .Invalid_Dir + } + + n := n + size := n + if n <= 0 { + n = -1 + size = 100 + } + + wpath: []u16 + { + i := 0 + for impl.wname[i] != 0 { + i += 1 + } + wpath = impl.wname[:i] + } + + + wpath_search := make([]u16, len(wpath)+3, context.temp_allocator) + copy(wpath_search, wpath) + wpath_search[len(wpath)+0] = '\\' + wpath_search[len(wpath)+1] = '*' + wpath_search[len(wpath)+2] = 0 + + find_data := &win32.WIN32_FIND_DATAW{} + find_handle := win32.FindFirstFileW(raw_data(wpath_search), find_data) + if find_handle == win32.INVALID_HANDLE_VALUE { + return nil, _get_platform_error() + } + defer win32.FindClose(find_handle) + + path := _cleanpath_from_buf(wpath, temp_allocator()) or_return + + dfi := make([dynamic]File_Info, 0, size, allocator) + defer if err != nil { + for fi in dfi { + file_info_delete(fi, allocator) + } + delete(dfi) + } + for n != 0 { + fi: File_Info + fi = find_data_to_file_info(path, find_data, allocator) or_return + if fi.name != "" { + append(&dfi, fi) + n -= 1 + } + + if !win32.FindNextFileW(find_handle, find_data) { + e := _get_platform_error() + if pe, _ := is_platform_error(e); pe == i32(win32.ERROR_NO_MORE_FILES) { + break + } + return dfi[:], e + } + } + + return dfi[:], nil +} + diff --git a/core/os/os2/env_windows.odin b/core/os/os2/env_windows.odin index 39694b821..870b5a731 100644 --- a/core/os/os2/env_windows.odin +++ b/core/os/os2/env_windows.odin @@ -8,7 +8,8 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string if key == "" { return } - wkey := win32.utf8_to_wstring(key) + TEMP_ALLOCATOR_GUARD() + wkey, _ := win32_utf8_to_wstring(key, temp_allocator()) n := win32.GetEnvironmentVariableW(wkey, nil, 0) if n == 0 { @@ -32,20 +33,22 @@ _lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string return "", false } - value = win32.utf16_to_utf8(b[:n], allocator) or_else "" + value = win32_utf16_to_utf8(b[:n], allocator) or_else "" found = true return } _set_env :: proc(key, value: string) -> bool { - k := win32.utf8_to_wstring(key) - v := win32.utf8_to_wstring(value) + TEMP_ALLOCATOR_GUARD() + k, _ := win32_utf8_to_wstring(key, temp_allocator()) + v, _ := win32_utf8_to_wstring(value, temp_allocator()) return bool(win32.SetEnvironmentVariableW(k, v)) } _unset_env :: proc(key: string) -> bool { - k := win32.utf8_to_wstring(key) + TEMP_ALLOCATOR_GUARD() + k, _ := win32_utf8_to_wstring(key, temp_allocator()) return bool(win32.SetEnvironmentVariableW(k, nil)) } @@ -89,7 +92,7 @@ _environ :: proc(allocator: runtime.Allocator) -> []string { break } w := ([^]u16)(p)[from:i] - append(&r, win32.utf16_to_utf8(w, allocator) or_else "") + append(&r, win32_utf16_to_utf8(w, allocator) or_else "") from = i + 1 } } diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index c69ff1b84..a31bd8803 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -61,7 +61,7 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: u return } - path := _fix_long_path(name) + path := _fix_long_path(name) or_return access: u32 switch flags & {.Read, .Write} { case {.Read}: access = win32.FILE_GENERIC_READ @@ -138,7 +138,7 @@ _new_file :: proc(handle: uintptr, name: string) -> ^File { impl.allocator = file_allocator() impl.fd = rawptr(handle) impl.name, _ = clone_string(name, impl.allocator) - impl.wname = win32.utf8_to_wstring(name, impl.allocator) + impl.wname, _ = win32_utf8_to_wstring(name, impl.allocator) handle := _handle(&impl.file) kind := File_Impl_Kind.File @@ -452,7 +452,7 @@ _truncate :: proc(f: ^File, size: i64) -> Error { } _remove :: proc(name: string) -> Error { - p := _fix_long_path(name) + p := _fix_long_path(name) or_return err, err1: Error if !win32.DeleteFileW(p) { err = _get_platform_error() @@ -489,8 +489,8 @@ _remove :: proc(name: string) -> Error { } _rename :: proc(old_path, new_path: string) -> Error { - from := _fix_long_path(old_path) - to := _fix_long_path(new_path) + from := _fix_long_path(old_path) or_return + to := _fix_long_path(new_path) or_return if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) { return nil } @@ -499,8 +499,8 @@ _rename :: proc(old_path, new_path: string) -> Error { } _link :: proc(old_name, new_name: string) -> Error { - o := _fix_long_path(old_name) - n := _fix_long_path(new_name) + o := _fix_long_path(old_name) or_return + n := _fix_long_path(new_name) or_return if win32.CreateHardLinkW(n, o, nil) { return nil } @@ -540,16 +540,16 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st } if !has_unc_prefix(p) { - return win32.utf16_to_utf8(p, allocator) + return win32_utf16_to_utf8(p, allocator) } ws := p[4:] switch { case len(ws) >= 2 && ws[1] == ':': - return win32.utf16_to_utf8(ws, allocator) + return win32_utf16_to_utf8(ws, allocator) case has_prefix(ws, `UNC\`): ws[3] = '\\' // override data in buffer - return win32.utf16_to_utf8(ws[3:], allocator) + return win32_utf16_to_utf8(ws[3:], allocator) } @@ -574,9 +574,9 @@ _normalize_link_path :: proc(p: []u16, allocator: runtime.Allocator) -> (str: st ws = ws[4:] if len(ws) > 3 && has_prefix(ws, `UNC`) { ws[2] = '\\' - return win32.utf16_to_utf8(ws[2:], allocator) + return win32_utf16_to_utf8(ws[2:], allocator) } - return win32.utf16_to_utf8(ws, allocator) + return win32_utf16_to_utf8(ws, allocator) } return "", .Invalid_Path } @@ -587,8 +587,8 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er @thread_local rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte - p := _fix_long_path(name) - handle := _open_sym_link(p) or_return + p := _fix_long_path(name) or_return + handle := _open_sym_link(p) or_return defer win32.CloseHandle(handle) bytes_returned: u32 @@ -607,7 +607,7 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er pb[rb.SubstituteNameOffset+rb.SubstituteNameLength] = 0 p := pb[rb.SubstituteNameOffset:][:rb.SubstituteNameLength] if rb.Flags & win32.SYMLINK_FLAG_RELATIVE != 0 { - return win32.utf16_to_utf8(p, allocator) + return win32_utf16_to_utf8(p, allocator) } return _normalize_link_path(p, allocator) @@ -662,7 +662,7 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error { } _chdir :: proc(name: string) -> Error { - p := _fix_long_path(name) + p := _fix_long_path(name) or_return if !win32.SetCurrentDirectoryW(p) { return _get_platform_error() } @@ -718,7 +718,7 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { } _exists :: proc(path: string) -> bool { - wpath := _fix_long_path(path) + wpath, _ := _fix_long_path(path) attribs := win32.GetFileAttributesW(wpath) return attribs != win32.INVALID_FILE_ATTRIBUTES } @@ -766,3 +766,85 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, return 0, .Empty } + + +@(private="package", require_results) +win32_utf8_to_wstring :: proc(s: string, allocator: runtime.Allocator) -> (ws: [^]u16, err: runtime.Allocator_Error) { + ws = raw_data(win32_utf8_to_utf16(s, allocator) or_return) + return +} + +@(private="package", require_results) +win32_utf8_to_utf16 :: proc(s: string, allocator: runtime.Allocator) -> (ws: []u16, err: runtime.Allocator_Error) { + if len(s) < 1 { + return + } + + b := transmute([]byte)s + cstr := raw_data(b) + n := win32.MultiByteToWideChar(win32.CP_UTF8, win32.MB_ERR_INVALID_CHARS, cstr, i32(len(s)), nil, 0) + if n == 0 { + return nil, nil + } + + text := make([]u16, n+1, allocator) or_return + + n1 := win32.MultiByteToWideChar(win32.CP_UTF8, win32.MB_ERR_INVALID_CHARS, cstr, i32(len(s)), raw_data(text), n) + if n1 == 0 { + delete(text, allocator) + return + } + + text[n] = 0 + for n >= 1 && text[n-1] == 0 { + n -= 1 + } + ws = text[:n] + return +} + +@(private="package", require_results) +win32_wstring_to_utf8 :: proc(s: [^]u16, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) { + if s == nil || s[0] == 0 { + return "", nil + } + n := 0 + for s[n] != 0 { + n += 1 + } + return win32_utf16_to_utf8(s[:n], allocator) +} + +@(private="package", require_results) +win32_utf16_to_utf8 :: proc(s: []u16, allocator: runtime.Allocator) -> (res: string, err: runtime.Allocator_Error) { + if len(s) == 0 { + return + } + + n := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, raw_data(s), i32(len(s)), nil, 0, nil, nil) + if n == 0 { + return + } + + // If N < 0 the call to WideCharToMultiByte assume the wide string is null terminated + // and will scan it to find the first null terminated character. The resulting string will + // also be null terminated. + // If N > 0 it assumes the wide string is not null terminated and the resulting string + // will not be null terminated. + text := make([]byte, n, allocator) or_return + + n1 := win32.WideCharToMultiByte(win32.CP_UTF8, win32.WC_ERR_INVALID_CHARS, raw_data(s), i32(len(s)), raw_data(text), n, nil, nil) + if n1 == 0 { + delete(text, allocator) + return + } + + for i in 0.. string { buf[i] = digits[u % b] return string(buf[i:]) } + + diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index ab680cc4c..a5eb8f13c 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -13,7 +13,7 @@ _is_path_separator :: proc(c: byte) -> bool { } _mkdir :: proc(name: string, perm: int) -> Error { - if !win32.CreateDirectoryW(_fix_long_path(name), nil) { + if !win32.CreateDirectoryW(_fix_long_path(name) or_return, nil) { return _get_platform_error() } return nil @@ -95,14 +95,17 @@ init_long_path_support :: proc() { can_use_long_paths = false } -_fix_long_path_slice :: proc(path: string) -> []u16 { - return win32.utf8_to_utf16(_fix_long_path_internal(path)) +@(require_results) +_fix_long_path_slice :: proc(path: string) -> ([]u16, runtime.Allocator_Error) { + return win32_utf8_to_utf16(_fix_long_path_internal(path), temp_allocator()) } -_fix_long_path :: proc(path: string) -> win32.wstring { - return win32.utf8_to_wstring(_fix_long_path_internal(path)) +@(require_results) +_fix_long_path :: proc(path: string) -> (win32.wstring, runtime.Allocator_Error) { + return win32_utf8_to_wstring(_fix_long_path_internal(path), temp_allocator()) } +@(require_results) _fix_long_path_internal :: proc(path: string) -> string { if can_use_long_paths { return path diff --git a/core/os/os2/process_windows.odin b/core/os/os2/process_windows.odin index d441e7aff..47fd62401 100644 --- a/core/os/os2/process_windows.odin +++ b/core/os/os2/process_windows.odin @@ -163,7 +163,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator _ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return if .Command_Line in selection { - info.command_line = win32.utf16_to_utf8(cmdline_w, allocator) or_return + info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return info.fields += {.Command_Line} } if .Command_Args in selection { @@ -185,7 +185,7 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return - info.working_dir = win32.utf16_to_utf8(cwd_w, allocator) or_return + info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return info.fields += {.Working_Dir} } } @@ -255,7 +255,7 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields _ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return if .Command_Line in selection { - info.command_line = win32.utf16_to_utf8(cmdline_w, allocator) or_return + info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return info.fields += {.Command_Line} } if .Command_Args in selection { @@ -279,7 +279,7 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return _ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return - info.working_dir = win32.utf16_to_utf8(cwd_w, allocator) or_return + info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return info.fields += {.Working_Dir} } } @@ -316,13 +316,13 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime if .Executable_Path in selection { exe_filename_w: [256]u16 path_len := win32.GetModuleFileNameW(nil, raw_data(exe_filename_w[:]), len(exe_filename_w)) - info.executable_path = win32.utf16_to_utf8(exe_filename_w[:path_len], allocator) or_return + info.executable_path = win32_utf16_to_utf8(exe_filename_w[:path_len], allocator) or_return info.fields += {.Executable_Path} } if selection >= {.Command_Line, .Command_Args} { command_line_w := win32.GetCommandLineW() if .Command_Line in selection { - info.command_line = win32.wstring_to_utf8(command_line_w, -1, allocator) or_return + info.command_line = win32_wstring_to_utf8(command_line_w, allocator) or_return info.fields += {.Command_Line} } if .Command_Args in selection { @@ -380,13 +380,13 @@ _Sys_Process_Attributes :: struct {} _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { TEMP_ALLOCATOR_GUARD() command_line := _build_command_line(desc.command, temp_allocator()) - command_line_w := win32.utf8_to_wstring(command_line, temp_allocator()) + command_line_w := win32_utf8_to_wstring(command_line, temp_allocator()) or_return environment := desc.env if desc.env == nil { environment = environ(temp_allocator()) } environment_block := _build_environment_block(environment, temp_allocator()) - environment_block_w := win32.utf8_to_utf16(environment_block, temp_allocator()) + environment_block_w := win32_utf8_to_utf16(environment_block, temp_allocator()) or_return stderr_handle := win32.GetStdHandle(win32.STD_ERROR_HANDLE) stdout_handle := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE) stdin_handle := win32.GetStdHandle(win32.STD_INPUT_HANDLE) @@ -401,7 +401,7 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { stdin_handle = win32.HANDLE((^File_Impl)(desc.stderr.impl).fd) } - working_dir_w := win32.utf8_to_wstring(desc.working_dir, temp_allocator()) if len(desc.working_dir) > 0 else nil + working_dir_w := (win32_utf8_to_wstring(desc.working_dir, temp_allocator()) or_else nil) if len(desc.working_dir) > 0 else nil process_info: win32.PROCESS_INFORMATION ok := win32.CreateProcessW( nil, @@ -535,7 +535,7 @@ _process_exe_by_pid :: proc(pid: int, allocator: runtime.Allocator) -> (exe_path err =_get_platform_error() return } - return win32.wstring_to_utf8(raw_data(entry.szExePath[:]), -1, allocator) + return win32_wstring_to_utf8(raw_data(entry.szExePath[:]), allocator) } _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Allocator) -> (full_username: string, err: Error) { @@ -570,8 +570,8 @@ _get_process_user :: proc(process_handle: win32.HANDLE, allocator: runtime.Alloc err = _get_platform_error() return } - username := win32.utf16_to_utf8(username_w[:username_chrs], temp_allocator()) or_return - domain := win32.utf16_to_utf8(domain_w[:domain_chrs], temp_allocator()) or_return + username := win32_utf16_to_utf8(username_w[:username_chrs], temp_allocator()) or_return + domain := win32_utf16_to_utf8(domain_w[:domain_chrs], temp_allocator()) or_return return strings.concatenate({domain, "\\", username}, allocator) } @@ -589,7 +589,7 @@ _parse_command_line :: proc(cmd_line_w: [^]u16, allocator: runtime.Allocator) -> delete(argv, allocator) } for arg_w, i in argv_w[:argc] { - argv[i] = win32.wstring_to_utf8(arg_w, -1, allocator) or_return + argv[i] = win32_wstring_to_utf8(arg_w, allocator) or_return } return } @@ -665,7 +665,7 @@ _parse_environment_block :: proc(block: [^]u16, allocator: runtime.Allocator) -> idx += 1 } env_w := block[last_idx:idx] - envs[env_idx] = win32.utf16_to_utf8(env_w, allocator) or_return + envs[env_idx] = win32_utf16_to_utf8(env_w, allocator) or_return env_idx += 1 idx += 1 last_idx = idx diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin index cfc362d77..caf46b661 100644 --- a/core/os/os2/stat.odin +++ b/core/os/os2/stat.odin @@ -57,7 +57,7 @@ same_file :: proc(fi1, fi2: File_Info) -> bool { last_write_time :: modification_time -last_write_time_by_name :: modification_time_by_name +last_write_time_by_name :: modification_time_by_path @(require_results) modification_time :: proc(f: ^File) -> (time.Time, Error) { @@ -67,7 +67,7 @@ modification_time :: proc(f: ^File) -> (time.Time, Error) { } @(require_results) -modification_time_by_name :: proc(path: string) -> (time.Time, Error) { +modification_time_by_path :: proc(path: string) -> (time.Time, Error) { TEMP_ALLOCATOR_GUARD() fi, err := stat(path, temp_allocator()) return fi.modification_time, err diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index 8915d187c..dcb37f3ab 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -49,7 +49,7 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path } TEMP_ALLOCATOR_GUARD() - p := win32.utf8_to_utf16(name, temp_allocator()) + p := win32_utf8_to_utf16(name, temp_allocator()) or_return n := win32.GetFullPathNameW(raw_data(p), 0, nil, nil) if n == 0 { @@ -60,7 +60,7 @@ full_path_from_name :: proc(name: string, allocator: runtime.Allocator) -> (path if n == 0 { return "", _get_platform_error() } - return win32.utf16_to_utf8(buf[:n], allocator) + return win32_utf16_to_utf8(buf[:n], allocator) } internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runtime.Allocator) -> (fi: File_Info, e: Error) { @@ -68,7 +68,7 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt return {}, .Not_Exist } - wname := _fix_long_path(name) + wname := _fix_long_path(name) or_return fa: win32.WIN32_FILE_ATTRIBUTE_DATA ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa) if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { @@ -154,7 +154,7 @@ _cleanpath_from_handle_u16 :: proc(f: ^File) -> ([]u16, Error) { _cleanpath_from_buf :: proc(buf: []u16, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { buf := buf buf = _cleanpath_strip_prefix(buf) - return win32.utf16_to_utf8(buf, allocator) + return win32_utf16_to_utf8(buf, allocator) } basename :: proc(name: string) -> (base: string) { diff --git a/core/os/os2/temp_file_windows.odin b/core/os/os2/temp_file_windows.odin index 4c8ab9fb7..d888eda52 100644 --- a/core/os/os2/temp_file_windows.odin +++ b/core/os/os2/temp_file_windows.odin @@ -19,5 +19,5 @@ _temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Er } else if n > 0 && b[n-1] == '\\' { n -= 1 } - return win32.utf16_to_utf8(b[:n], allocator) + return win32_utf16_to_utf8(b[:n], allocator) } From a28392852a791c107185653997008621bd479dd5 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 23 Jul 2024 17:05:41 +0100 Subject: [PATCH 180/198] Mock out os2.remove_all for Windows --- core/os/os2/path.odin | 4 +- core/os/os2/path_linux.odin | 4 +- core/os/os2/path_windows.odin | 107 +++++++++++++++++++++++++++++++--- 3 files changed, 103 insertions(+), 12 deletions(-) diff --git a/core/os/os2/path.odin b/core/os/os2/path.odin index f3c662a70..3bf422ccb 100644 --- a/core/os/os2/path.odin +++ b/core/os/os2/path.odin @@ -31,11 +31,11 @@ getwd :: get_working_directory @(require_results) get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - return _getwd(allocator) + return _get_working_directory(allocator) } setwd :: set_working_directory set_working_directory :: proc(dir: string) -> (err: Error) { - return _setwd(dir) + return _set_working_directory(dir) } diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index b388322b5..1634c79c8 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -163,7 +163,7 @@ _remove_all :: proc(path: string) -> Error { return _get_platform_error(linux.rmdir(path_cstr)) } -_getwd :: proc(allocator: runtime.Allocator) -> (string, Error) { +_get_working_directory :: proc(allocator: runtime.Allocator) -> (string, Error) { // NOTE(tetra): I would use PATH_MAX here, but I was not able to find // an authoritative value for it across all systems. // The largest value I could find was 4096, so might as well use the page size. @@ -183,7 +183,7 @@ _getwd :: proc(allocator: runtime.Allocator) -> (string, Error) { unreachable() } -_setwd :: proc(dir: string) -> Error { +_set_working_directory :: proc(dir: string) -> Error { dir_cstr := temp_cstring(dir) or_return return _get_platform_error(linux.chdir(dir_cstr)) } diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index a5eb8f13c..8fc4ef721 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -71,18 +71,109 @@ _mkdir_all :: proc(path: string, perm: int) -> Error { } _remove_all :: proc(path: string) -> Error { - // TODO(bill): _remove_all for windows - return nil + if path == "" { + return nil + } + + + err := remove(path) + if err == nil || err == .Not_Exist { + return nil + } + + TEMP_ALLOCATOR_GUARD() + dir, serr := stat_do_not_follow_links(path, temp_allocator()) + if serr != nil { + if serr == .Not_Exist || serr == .Invalid_Dir { + return nil + } + return serr + } + if dir.type != .Directory { + return err + } + + err = nil + remove_contents: { + f: ^File + f, err = open(path) + if err != nil { + if err == .Not_Exist { + return nil + } + return err + } + + files, read_err := read_all_directory(f, temp_allocator()) + for file in files { + err1 := remove_all(file.fullpath) + if err == nil { + err = err1 + } + } + close(f) + + if read_err == .EOF { + break remove_contents + } + + if err == nil { + err = read_err + } + if len(files) == 0 { + break remove_contents + } + } + + err1 := remove(path) + if err1 == nil || err1 == .Not_Exist { + return nil + } + + if ODIN_OS == .Windows && err1 == .Permission_Denied { + if fi, err2 := stat(path, temp_allocator()); err2 == nil { + if err2 = chmod(path, 0o200|fi.mode); err2 == nil { + err1 = remove(path) + } + } + } + if err == nil { + err = err1 + } + return err } -_getwd :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - // TODO(bill) - return "", nil +@private cwd_lock: win32.SRWLOCK // zero is initialized + +_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + win32.AcquireSRWLockExclusive(&cwd_lock) + + TEMP_ALLOCATOR_GUARD() + + sz_utf16 := win32.GetCurrentDirectoryW(0, nil) + dir_buf_wstr := make([]u16, sz_utf16, temp_allocator()) or_return + + sz_utf16 = win32.GetCurrentDirectoryW(win32.DWORD(len(dir_buf_wstr)), raw_data(dir_buf_wstr)) + assert(int(sz_utf16)+1 == len(dir_buf_wstr)) // the second time, it _excludes_ the NUL. + + win32.ReleaseSRWLockExclusive(&cwd_lock) + + return win32_utf16_to_utf8(dir_buf_wstr, allocator) } -_setwd :: proc(dir: string) -> (err: Error) { - // TODO(bill) - return nil +_set_working_directory :: proc(dir: string) -> (err: Error) { + TEMP_ALLOCATOR_GUARD() + wstr := win32_utf8_to_wstring(dir, temp_allocator()) or_return + + win32.AcquireSRWLockExclusive(&cwd_lock) + + if !win32.SetCurrentDirectoryW(wstr) { + err = _get_platform_error() + } + + win32.ReleaseSRWLockExclusive(&cwd_lock) + + return } can_use_long_paths: bool From 65fec9134e662741923fbac24e8906df3cfbd566 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 23 Jul 2024 17:30:42 +0100 Subject: [PATCH 181/198] Use `SHFileOperationW` for `remove_all` on Windows --- core/os/os2/file_util.odin | 12 ++++++ core/os/os2/file_windows.odin | 3 +- core/os/os2/path_windows.odin | 71 ++++++++--------------------------- 3 files changed, 29 insertions(+), 57 deletions(-) diff --git a/core/os/os2/file_util.odin b/core/os/os2/file_util.odin index ca0e8c940..2011d1cc4 100644 --- a/core/os/os2/file_util.odin +++ b/core/os/os2/file_util.odin @@ -8,6 +8,18 @@ write_string :: proc(f: ^File, s: string) -> (n: int, err: Error) { return write(f, transmute([]byte)s) } +write_strings :: proc(f: ^File, strings: ..string) -> (n: int, err: Error) { + for s in strings { + m: int + m, err = write_string(f, s) + n += m + if err != nil { + return + } + } + return +} + write_byte :: proc(f: ^File, b: byte) -> (n: int, err: Error) { return write(f, []byte{b}) } diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index a31bd8803..9367e43d8 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -109,11 +109,12 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: u case 0: return uintptr(h), nil case: - return 0, Platform_Error(e) + return 0, _get_platform_error() } } } } + h := win32.CreateFileW(path, access, share_mode, &sa, create_mode, attrs, nil) if h == win32.INVALID_HANDLE { return 0, _get_platform_error() diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index 8fc4ef721..68ce43c13 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -75,72 +75,31 @@ _remove_all :: proc(path: string) -> Error { return nil } - err := remove(path) if err == nil || err == .Not_Exist { return nil } TEMP_ALLOCATOR_GUARD() - dir, serr := stat_do_not_follow_links(path, temp_allocator()) - if serr != nil { - if serr == .Not_Exist || serr == .Invalid_Dir { - return nil - } - return serr - } - if dir.type != .Directory { - return err - } + dir := win32_utf8_to_wstring(path, temp_allocator()) or_return - err = nil - remove_contents: { - f: ^File - f, err = open(path) - if err != nil { - if err == .Not_Exist { - return nil - } - return err - } + empty: [1]u16 - files, read_err := read_all_directory(f, temp_allocator()) - for file in files { - err1 := remove_all(file.fullpath) - if err == nil { - err = err1 - } - } - close(f) - - if read_err == .EOF { - break remove_contents - } - - if err == nil { - err = read_err - } - if len(files) == 0 { - break remove_contents - } + file_op := win32.SHFILEOPSTRUCTW { + nil, + win32.FO_DELETE, + dir, + &empty[0], + win32.FOF_NOCONFIRMATION | win32.FOF_NOERRORUI | win32.FOF_SILENT, + false, + nil, + &empty[0], } - - err1 := remove(path) - if err1 == nil || err1 == .Not_Exist { - return nil + res := win32.SHFileOperationW(&file_op) + if res != 0 { + return _get_platform_error() } - - if ODIN_OS == .Windows && err1 == .Permission_Denied { - if fi, err2 := stat(path, temp_allocator()); err2 == nil { - if err2 = chmod(path, 0o200|fi.mode); err2 == nil { - err1 = remove(path) - } - } - } - if err == nil { - err = err1 - } - return err + return nil } @private cwd_lock: win32.SRWLOCK // zero is initialized From efa8c92baba4c33d5d5bd21938c3127eadf3fd9e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 23 Jul 2024 17:46:22 +0100 Subject: [PATCH 182/198] Implement `init_long_path_support` --- core/os/os2/errors_windows.odin | 5 +---- core/os/os2/file_windows.odin | 2 +- core/os/os2/path_windows.odin | 29 ++++++++++++++++++++++++++--- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/core/os/os2/errors_windows.odin b/core/os/os2/errors_windows.odin index 3572afb14..6421d26ee 100644 --- a/core/os/os2/errors_windows.odin +++ b/core/os/os2/errors_windows.odin @@ -17,10 +17,7 @@ _error_string :: proc(errno: i32) -> string { if idx, ok := slice.binary_search(ti.values, err); ok { return ti.names[idx] } - - // TODO(bill): _error_string for windows - // FormatMessageW - return "" + return "" } _get_platform_error :: proc() -> Error { diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 9367e43d8..3c18d0546 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -231,7 +231,7 @@ _read :: proc(f: ^File_Impl, p: []byte) -> (n: i64, err: Error) { return 0, nil } - // TODO(bill): should this be moved to `_File` instead? + // TODO(bill): should this be moved to `File_Impl` instead? BUF_SIZE :: 386 buf16: [BUF_SIZE]u16 buf8: [4*BUF_SIZE]u8 diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index 68ce43c13..c93c929f2 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -139,10 +139,33 @@ can_use_long_paths: bool @(init) init_long_path_support :: proc() { - // TODO(bill): init_long_path_support - // ADD THIS SHIT - // registry_path := win32.L(`Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled`) can_use_long_paths = false + + key: win32.HKEY + res := win32.RegOpenKeyExW(win32.HKEY_LOCAL_MACHINE, win32.L(`SYSTEM\CurrentControlSet\Control\FileSystem`), 0, win32.KEY_READ, &key) + defer win32.RegCloseKey(key) + if res != 0 { + return + } + + value: u32 + size := u32(size_of(value)) + res = win32.RegGetValueW( + key, + nil, + win32.L("LongPathsEnabled"), + win32.RRF_RT_ANY, + nil, + &value, + &size, + ) + if res != 0 { + return + } + if value == 1 { + can_use_long_paths = true + } + } @(require_results) From fc2e31fcd03b2dfd0258d0546e4f023f9e099b27 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 23 Jul 2024 20:51:00 +0200 Subject: [PATCH 183/198] fix build comment in os_freestanding --- core/os/os_freestanding.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/os/os_freestanding.odin b/core/os/os_freestanding.odin index 55ce1d12e..c908e3738 100644 --- a/core/os/os_freestanding.odin +++ b/core/os/os_freestanding.odin @@ -1,4 +1,4 @@ -//+freestanding +//+build freestanding package os -#panic("package os does not support a freestanding target") \ No newline at end of file +#panic("package os does not support a freestanding target") From 545fbc54c7b4e59404af144785761a92f1ae702b Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 24 Jul 2024 00:00:06 +0200 Subject: [PATCH 184/198] testing: add json reporting --- core/testing/runner.odin | 46 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index c27c2124a..da0328f91 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -6,6 +6,7 @@ import "base:runtime" import "core:bytes" import "core:encoding/ansi" @require import "core:encoding/base64" +@require import "core:encoding/json" import "core:fmt" import "core:io" @require import pkg_log "core:log" @@ -44,7 +45,8 @@ SHARED_RANDOM_SEED : u64 : #config(ODIN_TEST_RANDOM_SEED, 0) LOG_LEVEL : string : #config(ODIN_TEST_LOG_LEVEL, "info") // Show only the most necessary logging information. USING_SHORT_LOGS : bool : #config(ODIN_TEST_SHORT_LOGS, false) - +// Output a report of the tests to the given path. +JSON_REPORT : string : #config(ODIN_TEST_JSON_REPORT, "") get_log_level :: #force_inline proc() -> runtime.Logger_Level { when ODIN_DEBUG { @@ -61,6 +63,18 @@ get_log_level :: #force_inline proc() -> runtime.Logger_Level { } } +JSON :: struct { + total: int, + success: int, + duration: time.Duration, + packages: map[string][dynamic]JSON_Test, +} + +JSON_Test :: struct { + success: bool, + name: string, +} + end_t :: proc(t: ^T) { for i := len(t.cleanups)-1; i >= 0; i -= 1 { #no_bounds_check c := t.cleanups[i] @@ -847,5 +861,35 @@ To partly mitigate this, redirect STDERR to a file or use the -define:ODIN_TEST_ fmt.wprintln(stderr, bytes.buffer_to_string(&batch_buffer)) + when JSON_REPORT != "" { + json_report: JSON + + mode: int + when ODIN_OS != .Windows { + mode = os.S_IRUSR|os.S_IWUSR|os.S_IRGRP|os.S_IROTH + } + json_fd, errno := os.open(JSON_REPORT, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode) + fmt.assertf(errno == os.ERROR_NONE, "unable to open file %q for writing of JSON report, error: %v", JSON_REPORT, errno) + defer os.close(json_fd) + + for test, i in report.all_tests { + #no_bounds_check state := report.all_test_states[i] + + if test.pkg not_in json_report.packages { + json_report.packages[test.pkg] = {} + } + + tests := &json_report.packages[test.pkg] + append(tests, JSON_Test{name = test.name, success = state == .Successful}) + } + + json_report.total = len(internal_tests) + json_report.success = total_success_count + json_report.duration = finished_in + + err := json.marshal_to_writer(os.stream_from_handle(json_fd), json_report, &{ pretty = true }) + fmt.assertf(err == nil, "Error writing JSON report: %v", err) + } + return total_success_count == total_test_count } From f6488383d799241ae31cd85706dd8289b475b24e Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 24 Jul 2024 02:43:53 +0200 Subject: [PATCH 185/198] fix instrumentation features on LLVM versions with typed pointers Fixes #3970 --- src/llvm_backend_opt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llvm_backend_opt.cpp b/src/llvm_backend_opt.cpp index e6ccc9a57..7fe1359b4 100644 --- a/src/llvm_backend_opt.cpp +++ b/src/llvm_backend_opt.cpp @@ -396,7 +396,7 @@ gb_internal LLVMValueRef lb_run_instrumentation_pass_insert_call(lbProcedure *p, lbValue cc = lb_find_procedure_value_from_entity(m, entity); LLVMValueRef args[3] = {}; - args[0] = p->value; + args[0] = LLVMConstPointerCast(p->value, lb_type(m, t_rawptr)); if (is_arch_wasm()) { args[1] = LLVMConstPointerNull(lb_type(m, t_rawptr)); From 0e91c8368ced5f7a535baf92e187bd96e22a4e01 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Jul 2024 09:01:41 +0100 Subject: [PATCH 186/198] Add `allocator` parameters to `fmt.caprint*` --- core/fmt/fmt.odin | 45 ++++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index 9aa9c99dc..7b86fd1b7 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -334,6 +334,27 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! { message := tprintf(fmt, ..args) p("Panic", message, loc) } + +// Creates a formatted C string +// +// *Allocates Using Context's Allocator* +// +// Inputs: +// - args: A variadic list of arguments to be formatted. +// - sep: An optional separator string (default is a single space). +// +// Returns: A formatted C string. +// +@(require_results) +caprint :: proc(args: ..any, sep := " ", allocator := context.allocator) -> cstring { + str: strings.Builder + strings.builder_init(&str, allocator) + sbprint(&str, ..args, sep=sep) + strings.write_byte(&str, 0) + s := strings.to_string(str) + return cstring(raw_data(s)) +} + // Creates a formatted C string // // *Allocates Using Context's Allocator* @@ -346,9 +367,9 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! { // Returns: A formatted C string // @(require_results) -caprintf :: proc(format: string, args: ..any, newline := false) -> cstring { +caprintf :: proc(format: string, args: ..any, allocator := context.allocator, newline := false) -> cstring { str: strings.Builder - strings.builder_init(&str) + strings.builder_init(&str, allocator) sbprintf(&str, format, ..args, newline=newline) strings.write_byte(&str, 0) s := strings.to_string(str) @@ -365,8 +386,8 @@ caprintf :: proc(format: string, args: ..any, newline := false) -> cstring { // Returns: A formatted C string // @(require_results) -caprintfln :: proc(format: string, args: ..any) -> cstring { - return caprintf(format, ..args, newline=true) +caprintfln :: proc(format: string, args: ..any, allocator := context.allocator) -> cstring { + return caprintf(format, ..args, allocator=allocator, newline=true) } // Creates a formatted C string // @@ -380,12 +401,7 @@ caprintfln :: proc(format: string, args: ..any) -> cstring { // @(require_results) ctprint :: proc(args: ..any, sep := " ") -> cstring { - str: strings.Builder - strings.builder_init(&str, context.temp_allocator) - sbprint(&str, ..args, sep=sep) - strings.write_byte(&str, 0) - s := strings.to_string(str) - return cstring(raw_data(s)) + return caprint(args=args, sep=sep, allocator=context.temp_allocator) } // Creates a formatted C string // @@ -400,12 +416,7 @@ ctprint :: proc(args: ..any, sep := " ") -> cstring { // @(require_results) ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring { - str: strings.Builder - strings.builder_init(&str, context.temp_allocator) - sbprintf(&str, format, ..args, newline=newline) - strings.write_byte(&str, 0) - s := strings.to_string(str) - return cstring(raw_data(s)) + return caprintf(format=format, args=args, allocator=context.temp_allocator, newline=newline) } // Creates a formatted C string, followed by a newline. // @@ -419,7 +430,7 @@ ctprintf :: proc(format: string, args: ..any, newline := false) -> cstring { // @(require_results) ctprintfln :: proc(format: string, args: ..any) -> cstring { - return ctprintf(format, ..args, newline=true) + return caprintf(format=format, args=args, allocator=context.temp_allocator, newline=true) } // Formats using the default print settings and writes to the given strings.Builder // From c407e423d9f1299757583905b9adcda0155e82f5 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Jul 2024 13:37:56 +0100 Subject: [PATCH 187/198] Add `inode` to `os2.Stat` --- core/os/os2/stat.odin | 3 +++ core/os/os2/stat_linux.odin | 2 ++ core/os/os2/stat_windows.odin | 3 ++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin index caf46b661..e67045b38 100644 --- a/core/os/os2/stat.odin +++ b/core/os/os2/stat.odin @@ -8,9 +8,12 @@ Fstat_Callback :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Er File_Info :: struct { fullpath: string, name: string, + + inode: u64, size: i64, mode: int, type: File_Type, + creation_time: time.Time, modification_time: time.Time, access_time: time.Time, diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index c09f9b299..39a364c9a 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -28,10 +28,12 @@ _fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (File_Inf case linux.S_IFSOCK: type = .Socket } mode := int(0o7777 & transmute(u32)s.mode) + // TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time fi := File_Info { fullpath = _get_full_path(fd, allocator), name = "", + inode = u64(s.ino), size = i64(s.size), mode = mode, type = type, diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index dcb37f3ab..a3def0ea7 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -262,7 +262,8 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA fi: File_Info fi.fullpath = path fi.name = basename(path) - fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) + fi.inode = u64(d.nFileIndexHigh)<<32 + u64(d.nFileIndexLow) + fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0) fi.type = type fi.mode |= mode From 6d2487a69215f8d018e7be830e838c6cb94c15ce Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Jul 2024 13:38:14 +0100 Subject: [PATCH 188/198] Add some more Ntdll calls --- core/sys/windows/ntdll.odin | 108 +++++++++++++++++++++++++++++++++++- core/sys/windows/types.odin | 34 ++++++------ 2 files changed, 124 insertions(+), 18 deletions(-) diff --git a/core/sys/windows/ntdll.odin b/core/sys/windows/ntdll.odin index b75ac695a..20c75c801 100644 --- a/core/sys/windows/ntdll.odin +++ b/core/sys/windows/ntdll.odin @@ -15,10 +15,27 @@ foreign ntdll_lib { ProcessInformationLength: u32, ReturnLength: ^u32, ) -> u32 --- + + NtQueryInformationFile :: proc( + FileHandle: HANDLE, + IoStatusBlock: PIO_STATUS_BLOCK, + FileInformation: rawptr, + Length: ULONG, + FileInformationClass: FILE_INFORMATION_CLASS, + ) -> NTSTATUS --- +} + +PIO_STATUS_BLOCK :: ^IO_STATUS_BLOCK +IO_STATUS_BLOCK :: struct { + using _: struct #raw_union { + Status: NTSTATUS, + Pointer: rawptr, + }, + Information: ULONG_PTR, } -PROCESS_INFO_CLASS :: enum i32 { +PROCESS_INFO_CLASS :: enum c_int { ProcessBasicInformation = 0, ProcessDebugPort = 7, ProcessWow64Information = 26, @@ -29,6 +46,95 @@ PROCESS_INFO_CLASS :: enum i32 { } +PFILE_INFORMATION_CLASS :: ^FILE_INFORMATION_CLASS +FILE_INFORMATION_CLASS :: enum c_int { + FileDirectoryInformation = 1, + FileFullDirectoryInformation = 2, + FileBothDirectoryInformation = 3, + FileBasicInformation = 4, + FileStandardInformation = 5, + FileInternalInformation = 6, + FileEaInformation = 7, + FileAccessInformation = 8, + FileNameInformation = 9, + FileRenameInformation = 10, + FileLinkInformation = 11, + FileNamesInformation = 12, + FileDispositionInformation = 13, + FilePositionInformation = 14, + FileFullEaInformation = 15, + FileModeInformation = 16, + FileAlignmentInformation = 17, + FileAllInformation = 18, + FileAllocationInformation = 19, + FileEndOfFileInformation = 20, + FileAlternateNameInformation = 21, + FileStreamInformation = 22, + FilePipeInformation = 23, + FilePipeLocalInformation = 24, + FilePipeRemoteInformation = 25, + FileMailslotQueryInformation = 26, + FileMailslotSetInformation = 27, + FileCompressionInformation = 28, + FileObjectIdInformation = 29, + FileCompletionInformation = 30, + FileMoveClusterInformation = 31, + FileQuotaInformation = 32, + FileReparsePointInformation = 33, + FileNetworkOpenInformation = 34, + FileAttributeTagInformation = 35, + FileTrackingInformation = 36, + FileIdBothDirectoryInformation = 37, + FileIdFullDirectoryInformation = 38, + FileValidDataLengthInformation = 39, + FileShortNameInformation = 40, + FileIoCompletionNotificationInformation = 41, + FileIoStatusBlockRangeInformation = 42, + FileIoPriorityHintInformation = 43, + FileSfioReserveInformation = 44, + FileSfioVolumeInformation = 45, + FileHardLinkInformation = 46, + FileProcessIdsUsingFileInformation = 47, + FileNormalizedNameInformation = 48, + FileNetworkPhysicalNameInformation = 49, + FileIdGlobalTxDirectoryInformation = 50, + FileIsRemoteDeviceInformation = 51, + FileUnusedInformation = 52, + FileNumaNodeInformation = 53, + FileStandardLinkInformation = 54, + FileRemoteProtocolInformation = 55, + FileRenameInformationBypassAccessCheck = 56, + FileLinkInformationBypassAccessCheck = 57, + FileVolumeNameInformation = 58, + FileIdInformation = 59, + FileIdExtdDirectoryInformation = 60, + FileReplaceCompletionInformation = 61, + FileHardLinkFullIdInformation = 62, + FileIdExtdBothDirectoryInformation = 63, + FileDispositionInformationEx = 64, + FileRenameInformationEx = 65, + FileRenameInformationExBypassAccessCheck = 66, + FileDesiredStorageClassInformation = 67, + FileStatInformation = 68, + FileMemoryPartitionInformation = 69, + FileStatLxInformation = 70, + FileCaseSensitiveInformation = 71, + FileLinkInformationEx = 72, + FileLinkInformationExBypassAccessCheck = 73, + FileStorageReserveIdInformation = 74, + FileCaseSensitiveInformationForceAccessCheck = 75, + FileKnownFolderInformation = 76, + FileStatBasicInformation = 77, + FileId64ExtdDirectoryInformation = 78, + FileId64ExtdBothDirectoryInformation = 79, + FileIdAllExtdDirectoryInformation = 80, + FileIdAllExtdBothDirectoryInformation = 81, + FileStreamReservationInformation, + FileMupProviderInfo, + FileMaximumInformation, +} + + PROCESS_BASIC_INFORMATION :: struct { ExitStatus: NTSTATUS, diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index 675f681e5..ac8b63a63 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -2714,41 +2714,41 @@ NEON128 :: struct { EXCEPTION_POINTERS :: struct { ExceptionRecord: ^EXCEPTION_RECORD, - ContextRecord: ^CONTEXT, + ContextRecord: ^CONTEXT, } PVECTORED_EXCEPTION_HANDLER :: #type proc "system" (ExceptionInfo: ^EXCEPTION_POINTERS) -> LONG CONSOLE_READCONSOLE_CONTROL :: struct { - nLength: ULONG, - nInitialChars: ULONG, - dwCtrlWakeupMask: ULONG, + nLength: ULONG, + nInitialChars: ULONG, + dwCtrlWakeupMask: ULONG, dwControlKeyState: ULONG, } PCONSOLE_READCONSOLE_CONTROL :: ^CONSOLE_READCONSOLE_CONTROL BY_HANDLE_FILE_INFORMATION :: struct { - dwFileAttributes: DWORD, - ftCreationTime: FILETIME, - ftLastAccessTime: FILETIME, - ftLastWriteTime: FILETIME, + dwFileAttributes: DWORD, + ftCreationTime: FILETIME, + ftLastAccessTime: FILETIME, + ftLastWriteTime: FILETIME, dwVolumeSerialNumber: DWORD, - nFileSizeHigh: DWORD, - nFileSizeLow: DWORD, - nNumberOfLinks: DWORD, - nFileIndexHigh: DWORD, - nFileIndexLow: DWORD, + nFileSizeHigh: DWORD, + nFileSizeLow: DWORD, + nNumberOfLinks: DWORD, + nFileIndexHigh: DWORD, + nFileIndexLow: DWORD, } LPBY_HANDLE_FILE_INFORMATION :: ^BY_HANDLE_FILE_INFORMATION FILE_STANDARD_INFO :: struct { AllocationSize: LARGE_INTEGER, - EndOfFile: LARGE_INTEGER, - NumberOfLinks: DWORD, - DeletePending: BOOLEAN, - Directory: BOOLEAN, + EndOfFile: LARGE_INTEGER, + NumberOfLinks: DWORD, + DeletePending: BOOLEAN, + Directory: BOOLEAN, } FILE_ATTRIBUTE_TAG_INFO :: struct { From 2ddaae45f39c8d8fc3a0fc2bbe5705b8299edc85 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Jul 2024 13:47:22 +0100 Subject: [PATCH 189/198] Better handling of allocators --- core/os/os2/file.odin | 2 +- core/os/os2/file_linux.odin | 13 ++++++++----- core/os/os2/file_windows.odin | 18 +++++++++++------- core/os/os2/path_linux.odin | 9 ++++----- core/os/os2/pipe_linux.odin | 4 ++-- core/os/os2/stat_linux.odin | 22 +++++++++++----------- 6 files changed, 37 insertions(+), 31 deletions(-) diff --git a/core/os/os2/file.odin b/core/os/os2/file.odin index 8692ecf01..52fd02478 100644 --- a/core/os/os2/file.odin +++ b/core/os/os2/file.odin @@ -106,7 +106,7 @@ open :: proc(name: string, flags := File_Flags{.Read}, perm := 0o777) -> (^File, @(require_results) new_file :: proc(handle: uintptr, name: string) -> ^File { - return _new_file(handle, name) + return _new_file(handle, name) or_else panic("Out of memory") } @(require_results) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index cf643b31a..d2a7483ca 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -88,21 +88,24 @@ _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Err return nil, _get_platform_error(errno) } - return _new_file(uintptr(fd), name), nil + return _new_file(uintptr(fd), name) } -_new_file :: proc(fd: uintptr, _: string = "") -> ^File { - impl := new(File_Impl, file_allocator()) +_new_file :: proc(fd: uintptr, _: string = "") -> (f: ^File, err: Error) { + impl := new(File_Impl, file_allocator()) or_return + defer if err != nil { + free(impl, file_allocator()) + } impl.file.impl = impl impl.fd = linux.Fd(fd) impl.allocator = file_allocator() - impl.name = _get_full_path(impl.fd, impl.allocator) + impl.name = _get_full_path(impl.fd, file_allocator()) or_return impl.file.stream = { data = impl, procedure = _file_stream_proc, } impl.file.fstat = _fstat - return &impl.file + return &impl.file, nil } _destroy :: proc(f: ^File_Impl) -> Error { diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 3c18d0546..39a3e7867 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -126,20 +126,24 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: u _open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) { flags := flags if flags != nil else {.Read} handle := _open_internal(name, flags, perm) or_return - return _new_file(handle, name), nil + return _new_file(handle, name) } -_new_file :: proc(handle: uintptr, name: string) -> ^File { +_new_file :: proc(handle: uintptr, name: string) -> (f: ^File, err: Error) { if handle == INVALID_HANDLE { - return nil + return } - impl := new(File_Impl, file_allocator()) + impl := new(File_Impl, file_allocator()) or_return + defer if err != nil { + free(impl, file_allocator()) + } + impl.file.impl = impl impl.allocator = file_allocator() impl.fd = rawptr(handle) - impl.name, _ = clone_string(name, impl.allocator) - impl.wname, _ = win32_utf8_to_wstring(name, impl.allocator) + impl.name = clone_string(name, impl.allocator) or_return + impl.wname = win32_utf8_to_wstring(name, impl.allocator) or_return handle := _handle(&impl.file) kind := File_Impl_Kind.File @@ -157,7 +161,7 @@ _new_file :: proc(handle: uintptr, name: string) -> ^File { } impl.file.fstat = _fstat - return &impl.file + return &impl.file, nil } _fd :: proc(f: ^File) -> uintptr { diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index 1634c79c8..be60f9b86 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -188,7 +188,7 @@ _set_working_directory :: proc(dir: string) -> Error { return _get_platform_error(linux.chdir(dir_cstr)) } -_get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> string { +_get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath: string, err: Error) { PROC_FD_PATH :: "/proc/self/fd/" buf: [32]u8 @@ -196,10 +196,9 @@ _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> string { strconv.itoa(buf[len(PROC_FD_PATH):], int(fd)) - fullpath: string - err: Error if fullpath, err = _read_link_cstr(cstring(&buf[0]), allocator); err != nil || fullpath[0] != '/' { - return "" + delete(fullpath, allocator) + fullpath = "" } - return fullpath + return } diff --git a/core/os/os2/pipe_linux.odin b/core/os/os2/pipe_linux.odin index 8835cc30f..c3fecfb9e 100644 --- a/core/os/os2/pipe_linux.odin +++ b/core/os/os2/pipe_linux.odin @@ -10,8 +10,8 @@ _pipe :: proc() -> (r, w: ^File, err: Error) { return nil, nil,_get_platform_error(errno) } - r = _new_file(uintptr(fds[0])) - w = _new_file(uintptr(fds[1])) + r = _new_file(uintptr(fds[0])) or_return + w = _new_file(uintptr(fds[1])) or_return return } diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index 39a364c9a..eb31e2200 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -11,7 +11,7 @@ _fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) { return _fstat_internal(impl.fd, allocator) } -_fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (File_Info, Error) { +_fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { s: linux.Stat errno := linux.fstat(fd, &s) if errno != .NONE { @@ -30,20 +30,20 @@ _fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (File_Inf mode := int(0o7777 & transmute(u32)s.mode) // TODO: As of Linux 4.11, the new statx syscall can retrieve creation_time - fi := File_Info { - fullpath = _get_full_path(fd, allocator), - name = "", - inode = u64(s.ino), - size = i64(s.size), - mode = mode, - type = type, + fi = File_Info { + fullpath = _get_full_path(fd, allocator) or_return, + name = "", + inode = u64(s.ino), + size = i64(s.size), + mode = mode, + type = type, modification_time = time.Time {i64(s.mtime.time_sec) * i64(time.Second) + i64(s.mtime.time_nsec)}, - access_time = time.Time {i64(s.atime.time_sec) * i64(time.Second) + i64(s.atime.time_nsec)}, - creation_time = time.Time{i64(s.ctime.time_sec) * i64(time.Second) + i64(s.ctime.time_nsec)}, // regular stat does not provide this + access_time = time.Time {i64(s.atime.time_sec) * i64(time.Second) + i64(s.atime.time_nsec)}, + creation_time = time.Time{i64(s.ctime.time_sec) * i64(time.Second) + i64(s.ctime.time_nsec)}, // regular stat does not provide this } fi.creation_time = fi.modification_time fi.name = filepath.base(fi.fullpath) - return fi, nil + return } // NOTE: _stat and _lstat are using _fstat to avoid a race condition when populating fullpath From 38e983cac63a171fb2c96328db644fed8b35d0aa Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Jul 2024 13:47:57 +0100 Subject: [PATCH 190/198] Remove dead code --- core/os/os2/heap.odin | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/os/os2/heap.odin b/core/os/os2/heap.odin index e0cffaf0d..8f9c7680a 100644 --- a/core/os/os2/heap.odin +++ b/core/os/os2/heap.odin @@ -17,7 +17,3 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, runtime.Allocator_Error) { return _heap_allocator_proc(allocator_data, mode, size, alignment, old_memory, old_size, loc) } - - -@(private) -error_allocator := heap_allocator From d4af7b86a7a41d7a59c111d2eb22fdd0e8686e78 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Jul 2024 14:19:30 +0100 Subject: [PATCH 191/198] Begin cleaning up `os2.read_directory` --- core/os/os2/dir.odin | 65 ++++++++++++- core/os/os2/dir_linux.odin | 18 +++- core/os/os2/dir_windows.odin | 174 ++++++++++++++++++++--------------- core/os/os2/stat.odin | 12 ++- 4 files changed, 186 insertions(+), 83 deletions(-) diff --git a/core/os/os2/dir.odin b/core/os/os2/dir.odin index 5f9deb08a..6334ee7b8 100644 --- a/core/os/os2/dir.odin +++ b/core/os/os2/dir.odin @@ -1,21 +1,80 @@ package os2 import "base:runtime" +import "core:slice" -read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) { - return _read_directory(f, n, allocator) +@(require_results) +read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files: []File_Info, err: Error) { + if f == nil { + return nil, .Invalid_File + } + + n := n + size := n + if n <= 0 { + n = -1 + size = 100 + } + + TEMP_ALLOCATOR_GUARD() + + it := read_directory_iterator_create(f) or_return + defer _read_directory_iterator_destroy(&it) + + dfi := make([dynamic]File_Info, 0, size, temp_allocator()) + defer if err != nil { + for fi in dfi { + file_info_delete(fi, allocator) + } + } + + for fi, index in read_directory_iterator(&it) { + if n > 0 && index == n { + break + } + append(&dfi, file_info_clone(fi, allocator) or_return) + } + + return slice.clone(dfi[:], allocator) } + +@(require_results) read_all_directory :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) { return read_directory(f, -1, allocator) } +@(require_results) read_directory_by_path :: proc(path: string, n: int, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) { f := open(path) or_return defer close(f) return read_directory(f, n, allocator) } +@(require_results) read_all_directory_by_path :: proc(path: string, allocator: runtime.Allocator) -> (fi: []File_Info, err: Error) { return read_directory_by_path(path, -1, allocator) -} \ No newline at end of file +} + + +Read_Directory_Iterator :: struct { + f: ^File, + impl: Read_Directory_Iterator_Impl, +} + + +@(require_results) +read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) { + return _read_directory_iterator_create(f) +} + +read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { + _read_directory_iterator_destroy(it) +} + + +// NOTE(bill): `File_Info` does not need to deleted on each iteration. Any copies must be manually copied with `file_info_clone` +@(require_results) +read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { + return _read_directory_iterator(it) +} diff --git a/core/os/os2/dir_linux.odin b/core/os/os2/dir_linux.odin index b3d4aed0b..d4f62e213 100644 --- a/core/os/os2/dir_linux.odin +++ b/core/os/os2/dir_linux.odin @@ -1,8 +1,20 @@ +//+private package os2 -import "base:runtime" +Read_Directory_Iterator_Impl :: struct { -@(private) -_read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files: []File_Info, err: Error) { +} + + +@(require_results) +_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { return } + +@(require_results) +_read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) { + return {}, nil +} + +_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { +} diff --git a/core/os/os2/dir_windows.odin b/core/os/os2/dir_windows.odin index 36246267d..99dbe3c17 100644 --- a/core/os/os2/dir_windows.odin +++ b/core/os/os2/dir_windows.odin @@ -1,67 +1,104 @@ +//+private package os2 import "base:runtime" import "core:time" import win32 "core:sys/windows" -@(private) -_read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (files: []File_Info, err: Error) { - find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { - // Ignore "." and ".." - if d.cFileName[0] == '.' && d.cFileName[1] == 0 { - return - } - if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 { - return - } - path := concatenate({base_path, `\`, win32_utf16_to_utf8(d.cFileName[:], temp_allocator()) or_else ""}, allocator) or_return - fi.fullpath = path - fi.name = basename(path) - fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) +@(private="file") +find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + // Ignore "." and ".." + if d.cFileName[0] == '.' && d.cFileName[1] == 0 { + return + } + if d.cFileName[0] == '.' && d.cFileName[1] == '.' && d.cFileName[2] == 0 { + return + } + path := concatenate({base_path, `\`, win32_utf16_to_utf8(d.cFileName[:], temp_allocator()) or_else ""}, allocator) or_return + fi.fullpath = path + fi.name = basename(path) + fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) - if d.dwFileAttributes & win32.FILE_ATTRIBUTE_READONLY != 0 { - fi.mode |= 0o444 - } else { - fi.mode |= 0o666 - } + if d.dwFileAttributes & win32.FILE_ATTRIBUTE_READONLY != 0 { + fi.mode |= 0o444 + } else { + fi.mode |= 0o666 + } - is_sym := false - if d.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_Point == 0 { - is_sym = false - } else { - is_sym = d.dwReserved0 == win32.IO_REPARSE_TAG_SYMLINK || d.dwReserved0 == win32.IO_REPARSE_TAG_MOUNT_POINT - } + is_sym := false + if d.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_Point == 0 { + is_sym = false + } else { + is_sym = d.dwReserved0 == win32.IO_REPARSE_TAG_SYMLINK || d.dwReserved0 == win32.IO_REPARSE_TAG_MOUNT_POINT + } - if is_sym { - fi.type = .Symlink - } else if d.dwFileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { - fi.type = .Directory - fi.mode |= 0o111 - } + if is_sym { + fi.type = .Symlink + } else if d.dwFileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { + fi.type = .Directory + fi.mode |= 0o111 + } - fi.creation_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftCreationTime)) - fi.modification_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastWriteTime)) - fi.access_time = time.unix(0, win32.FILETIME_as_unix_nanoseconds(d.ftLastAccessTime)) + fi.creation_time = 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 +} + +Read_Directory_Iterator_Impl :: struct { + find_data: win32.WIN32_FIND_DATAW, + find_handle: win32.HANDLE, + path: string, + prev_fi: File_Info, + no_more_files: bool, + index: int, +} + + +@(require_results) +_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { + if it.f == nil { + return + } + if it.impl.no_more_files { return } - if f == nil { - return nil, .Invalid_File + + err: Error + fi, err = find_data_to_file_info(it.impl.path, &it.impl.find_data, file_allocator()) + if err != nil { + return + } + if fi.name != "" { + file_info_delete(it.impl.prev_fi, file_allocator()) + it.impl.prev_fi = fi + ok = true + index = it.impl.index + it.impl.index += 1 } - TEMP_ALLOCATOR_GUARD() + if !win32.FindNextFileW(it.impl.find_handle, &it.impl.find_data) { + e := _get_platform_error() + if pe, _ := is_platform_error(e); pe == i32(win32.ERROR_NO_MORE_FILES) { + it.impl.no_more_files = true + } + it.impl.no_more_files = true + return + } + return +} +@(require_results) +_read_directory_iterator_create :: proc(f: ^File) -> (it: Read_Directory_Iterator, err: Error) { + if f == nil { + return + } impl := (^File_Impl)(f.impl) if !is_directory(impl.name) { - return nil, .Invalid_Dir - } - - n := n - size := n - if n <= 0 { - n = -1 - size = 100 + err = .Invalid_Dir + return } wpath: []u16 @@ -73,46 +110,31 @@ _read_directory :: proc(f: ^File, n: int, allocator: runtime.Allocator) -> (file wpath = impl.wname[:i] } + TEMP_ALLOCATOR_GUARD() - wpath_search := make([]u16, len(wpath)+3, context.temp_allocator) + wpath_search := make([]u16, len(wpath)+3, temp_allocator()) copy(wpath_search, wpath) wpath_search[len(wpath)+0] = '\\' wpath_search[len(wpath)+1] = '*' wpath_search[len(wpath)+2] = 0 - find_data := &win32.WIN32_FIND_DATAW{} - find_handle := win32.FindFirstFileW(raw_data(wpath_search), find_data) - if find_handle == win32.INVALID_HANDLE_VALUE { - return nil, _get_platform_error() + it.impl.find_handle = win32.FindFirstFileW(raw_data(wpath_search), &it.impl.find_data) + if it.impl.find_handle == win32.INVALID_HANDLE_VALUE { + err = _get_platform_error() + return } - defer win32.FindClose(find_handle) - - path := _cleanpath_from_buf(wpath, temp_allocator()) or_return - - dfi := make([dynamic]File_Info, 0, size, allocator) defer if err != nil { - for fi in dfi { - file_info_delete(fi, allocator) - } - delete(dfi) - } - for n != 0 { - fi: File_Info - fi = find_data_to_file_info(path, find_data, allocator) or_return - if fi.name != "" { - append(&dfi, fi) - n -= 1 - } - - if !win32.FindNextFileW(find_handle, find_data) { - e := _get_platform_error() - if pe, _ := is_platform_error(e); pe == i32(win32.ERROR_NO_MORE_FILES) { - break - } - return dfi[:], e - } + win32.FindClose(it.impl.find_handle) } - return dfi[:], nil + it.impl.path = _cleanpath_from_buf(wpath, file_allocator()) or_return + return } +_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { + if it.f == nil { + return + } + file_info_delete(it.impl.prev_fi, file_allocator()) + win32.FindClose(it.impl.find_handle) +} \ No newline at end of file diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin index e67045b38..a324e7189 100644 --- a/core/os/os2/stat.odin +++ b/core/os/os2/stat.odin @@ -1,7 +1,9 @@ package os2 -import "core:time" import "base:runtime" +import "core:path/filepath" +import "core:strings" +import "core:time" Fstat_Callback :: proc(f: ^File, allocator: runtime.Allocator) -> (File_Info, Error) @@ -19,6 +21,14 @@ File_Info :: struct { access_time: time.Time, } +@(require_results) +file_info_clone :: proc(fi: File_Info, allocator: runtime.Allocator) -> (cloned: File_Info, err: runtime.Allocator_Error) { + cloned = fi + cloned.fullpath = strings.clone(fi.fullpath) or_return + cloned.name = filepath.base(cloned.fullpath) + return +} + file_info_slice_delete :: proc(infos: []File_Info, allocator: runtime.Allocator) { for i := len(infos)-1; i >= 0; i -= 1 { file_info_delete(infos[i], allocator) From 9d8953538bb28db656479ead989c14b9e3e08d6b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Jul 2024 14:25:42 +0100 Subject: [PATCH 192/198] Add missing attribute --- core/os/os2/file_windows.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 39a3e7867..55365c1bd 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -94,7 +94,7 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: u create_mode = win32.TRUNCATE_EXISTING } - attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL + attrs: u32 = win32.FILE_ATTRIBUTE_NORMAL|win32.FILE_FLAG_BACKUP_SEMANTICS if perm & S_IWRITE == 0 { attrs = win32.FILE_ATTRIBUTE_READONLY if create_mode == win32.CREATE_ALWAYS { From 07b1819dc8154e1786ebb8ce942e45200b0fc0d8 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Jul 2024 14:53:33 +0100 Subject: [PATCH 193/198] Improve `os2.read_directory` --- core/os/os2/dir_windows.odin | 66 ++++++++++++++--------------------- core/os/os2/stat.odin | 2 +- core/os/os2/stat_linux.odin | 2 +- core/os/os2/stat_windows.odin | 2 +- 4 files changed, 30 insertions(+), 42 deletions(-) diff --git a/core/os/os2/dir_windows.odin b/core/os/os2/dir_windows.odin index 99dbe3c17..c767bd3b9 100644 --- a/core/os/os2/dir_windows.odin +++ b/core/os/os2/dir_windows.odin @@ -15,30 +15,15 @@ find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, al return } path := concatenate({base_path, `\`, win32_utf16_to_utf8(d.cFileName[:], temp_allocator()) or_else ""}, allocator) or_return + + fi.fullpath = path fi.name = basename(path) fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) - if d.dwFileAttributes & win32.FILE_ATTRIBUTE_READONLY != 0 { - fi.mode |= 0o444 - } else { - fi.mode |= 0o666 - } - - is_sym := false - if d.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_Point == 0 { - is_sym = false - } else { - is_sym = d.dwReserved0 == win32.IO_REPARSE_TAG_SYMLINK || d.dwReserved0 == win32.IO_REPARSE_TAG_MOUNT_POINT - } - - if is_sym { - fi.type = .Symlink - } else if d.dwFileAttributes & win32.FILE_ATTRIBUTE_DIRECTORY != 0 { - fi.type = .Directory - fi.mode |= 0o111 - } + fi.type, fi.mode = _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, d.dwReserved0) + // fi.inode = u128(u64(d.nFileIndexHigh)<<32 + u64(d.nFileIndexLow)) 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)) @@ -60,31 +45,33 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info if it.f == nil { return } - if it.impl.no_more_files { - return - } - - err: Error - fi, err = find_data_to_file_info(it.impl.path, &it.impl.find_data, file_allocator()) - if err != nil { - return - } - if fi.name != "" { + for !it.impl.no_more_files { + err: Error file_info_delete(it.impl.prev_fi, file_allocator()) - it.impl.prev_fi = fi - ok = true - index = it.impl.index - it.impl.index += 1 - } + it.impl.prev_fi = {} - if !win32.FindNextFileW(it.impl.find_handle, &it.impl.find_data) { - e := _get_platform_error() - if pe, _ := is_platform_error(e); pe == i32(win32.ERROR_NO_MORE_FILES) { + fi, err = find_data_to_file_info(it.impl.path, &it.impl.find_data, file_allocator()) + if err != nil { + return + } + if fi.name != "" { + it.impl.prev_fi = fi + ok = true + index = it.impl.index + it.impl.index += 1 + } + + if !win32.FindNextFileW(it.impl.find_handle, &it.impl.find_data) { + e := _get_platform_error() + if pe, _ := is_platform_error(e); pe == i32(win32.ERROR_NO_MORE_FILES) { + it.impl.no_more_files = true + } it.impl.no_more_files = true } - it.impl.no_more_files = true - return + if ok { + return + } } return } @@ -94,6 +81,7 @@ _read_directory_iterator_create :: proc(f: ^File) -> (it: Read_Directory_Iterato if f == nil { return } + it.f = f impl := (^File_Impl)(f.impl) if !is_directory(impl.name) { diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin index a324e7189..5c063e771 100644 --- a/core/os/os2/stat.odin +++ b/core/os/os2/stat.odin @@ -11,7 +11,7 @@ File_Info :: struct { fullpath: string, name: string, - inode: u64, + inode: u128, // might be zero if cannot be determined size: i64, mode: int, type: File_Type, diff --git a/core/os/os2/stat_linux.odin b/core/os/os2/stat_linux.odin index eb31e2200..6ccac1be0 100644 --- a/core/os/os2/stat_linux.odin +++ b/core/os/os2/stat_linux.odin @@ -33,7 +33,7 @@ _fstat_internal :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fi: File fi = File_Info { fullpath = _get_full_path(fd, allocator) or_return, name = "", - inode = u64(s.ino), + inode = u128(u64(s.ino)), size = i64(s.size), mode = mode, type = type, diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index a3def0ea7..4d67ddb58 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -262,7 +262,7 @@ _file_info_from_get_file_information_by_handle :: proc(path: string, h: win32.HA fi: File_Info fi.fullpath = path fi.name = basename(path) - fi.inode = u64(d.nFileIndexHigh)<<32 + u64(d.nFileIndexLow) + fi.inode = u128(u64(d.nFileIndexHigh)<<32 + u64(d.nFileIndexLow)) fi.size = i64(d.nFileSizeHigh)<<32 + i64(d.nFileSizeLow) type, mode := _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, 0) fi.type = type From 2dbccbde54694e959d50a72fbc133b7391d1644c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Jul 2024 14:53:48 +0100 Subject: [PATCH 194/198] Improve win32 types --- core/sys/windows/types.odin | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index ac8b63a63..557fb5a58 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -1027,16 +1027,28 @@ TRACKMOUSEEVENT :: struct { } WIN32_FIND_DATAW :: struct { - dwFileAttributes: DWORD, - ftCreationTime: FILETIME, - ftLastAccessTime: FILETIME, - ftLastWriteTime: FILETIME, - nFileSizeHigh: DWORD, - nFileSizeLow: DWORD, - dwReserved0: DWORD, - dwReserved1: DWORD, - cFileName: [260]wchar_t, // #define MAX_PATH 260 - cAlternateFileName: [14]wchar_t, + dwFileAttributes: DWORD, + ftCreationTime: FILETIME, + ftLastAccessTime: FILETIME, + ftLastWriteTime: FILETIME, + nFileSizeHigh: DWORD, + nFileSizeLow: DWORD, + dwReserved0: DWORD, + dwReserved1: DWORD, + cFileName: [MAX_PATH]WCHAR, + cAlternateFileName: [14]WCHAR, + _OBSOLETE_dwFileType: DWORD, // Obsolete. Do not use. + _OBSOLETE_dwCreatorType: DWORD, // Obsolete. Do not use + _OBSOLETE_wFinderFlags: WORD, // Obsolete. Do not use +} + +FILE_ID_128 :: struct { + Identifier: [16]BYTE, +} + +FILE_ID_INFO :: struct { + VolumeSerialNumber: ULONGLONG, + FileId: FILE_ID_128, } CREATESTRUCTA :: struct { From 8d6ff5192228c9225e66c650f83bb7c2da005ab2 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 24 Jul 2024 17:43:51 +0200 Subject: [PATCH 195/198] Copy lua54.dll during CI test --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 84c85457f..977468f5e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -207,6 +207,7 @@ jobs: shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat + copy vendor\lua\5.4\windows\*.dll . odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false - name: Odin internals tests shell: cmd From f03c2b7783518f05f7804dfe51adaef4b5e5020e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Jul 2024 21:45:35 +0100 Subject: [PATCH 196/198] General clean up of `os2.read_directory` for Windows --- core/os/os2/dir_windows.odin | 15 ++++++++++++++- core/os/os2/file_windows.odin | 28 ++++++++++++++++++---------- core/os/os2/path_windows.odin | 11 ++++++----- core/os/os2/stat.odin | 4 ++-- core/os/os2/stat_windows.odin | 3 ++- 5 files changed, 42 insertions(+), 19 deletions(-) diff --git a/core/os/os2/dir_windows.odin b/core/os/os2/dir_windows.odin index c767bd3b9..84f320095 100644 --- a/core/os/os2/dir_windows.odin +++ b/core/os/os2/dir_windows.odin @@ -23,10 +23,21 @@ find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW, al fi.type, fi.mode = _file_type_mode_from_file_attributes(d.dwFileAttributes, nil, d.dwReserved0) - // fi.inode = u128(u64(d.nFileIndexHigh)<<32 + u64(d.nFileIndexLow)) 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)) + + + handle := win32.HANDLE(_open_internal(path, {.Read}, 0o666) or_else 0) + defer win32.CloseHandle(handle) + + if file_id_info: win32.FILE_ID_INFO; handle != nil && win32.GetFileInformationByHandleEx(handle, .FileIdInfo, &file_id_info, size_of(file_id_info)) { + #assert(size_of(fi.inode) == size_of(file_id_info.FileId)) + #assert(size_of(fi.inode) == 16) + runtime.mem_copy_non_overlapping(&fi.inode, &file_id_info.FileId, 16) + } + + return } @@ -46,6 +57,8 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info return } + TEMP_ALLOCATOR_GUARD() + for !it.impl.no_more_files { err: Error file_info_delete(it.impl.prev_fi, file_allocator()) diff --git a/core/os/os2/file_windows.odin b/core/os/os2/file_windows.odin index 55365c1bd..48a5427f1 100644 --- a/core/os/os2/file_windows.odin +++ b/core/os/os2/file_windows.odin @@ -60,8 +60,9 @@ _open_internal :: proc(name: string, flags: File_Flags, perm: int) -> (handle: u err = .Not_Exist return } + TEMP_ALLOCATOR_GUARD() - path := _fix_long_path(name) or_return + path := _fix_long_path(name, temp_allocator()) or_return access: u32 switch flags & {.Read, .Write} { case {.Read}: access = win32.FILE_GENERIC_READ @@ -457,7 +458,8 @@ _truncate :: proc(f: ^File, size: i64) -> Error { } _remove :: proc(name: string) -> Error { - p := _fix_long_path(name) or_return + TEMP_ALLOCATOR_GUARD() + p := _fix_long_path(name, temp_allocator()) or_return err, err1: Error if !win32.DeleteFileW(p) { err = _get_platform_error() @@ -494,8 +496,9 @@ _remove :: proc(name: string) -> Error { } _rename :: proc(old_path, new_path: string) -> Error { - from := _fix_long_path(old_path) or_return - to := _fix_long_path(new_path) or_return + TEMP_ALLOCATOR_GUARD() + from := _fix_long_path(old_path, temp_allocator()) or_return + to := _fix_long_path(new_path, temp_allocator()) or_return if win32.MoveFileExW(from, to, win32.MOVEFILE_REPLACE_EXISTING) { return nil } @@ -504,8 +507,9 @@ _rename :: proc(old_path, new_path: string) -> Error { } _link :: proc(old_name, new_name: string) -> Error { - o := _fix_long_path(old_name) or_return - n := _fix_long_path(new_name) or_return + TEMP_ALLOCATOR_GUARD() + o := _fix_long_path(old_name, temp_allocator()) or_return + n := _fix_long_path(new_name, temp_allocator()) or_return if win32.CreateHardLinkW(n, o, nil) { return nil } @@ -592,8 +596,10 @@ _read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, er @thread_local rdb_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte - p := _fix_long_path(name) or_return - handle := _open_sym_link(p) or_return + TEMP_ALLOCATOR_GUARD() + + p := _fix_long_path(name, temp_allocator()) or_return + handle := _open_sym_link(p) or_return defer win32.CloseHandle(handle) bytes_returned: u32 @@ -667,7 +673,8 @@ _fchown :: proc(f: ^File, uid, gid: int) -> Error { } _chdir :: proc(name: string) -> Error { - p := _fix_long_path(name) or_return + TEMP_ALLOCATOR_GUARD() + p := _fix_long_path(name, temp_allocator()) or_return if !win32.SetCurrentDirectoryW(p) { return _get_platform_error() } @@ -723,7 +730,8 @@ _fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { } _exists :: proc(path: string) -> bool { - wpath, _ := _fix_long_path(path) + TEMP_ALLOCATOR_GUARD() + wpath, _ := _fix_long_path(path, temp_allocator()) attribs := win32.GetFileAttributesW(wpath) return attribs != win32.INVALID_FILE_ATTRIBUTES } diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index c93c929f2..4aa695ee2 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -13,7 +13,8 @@ _is_path_separator :: proc(c: byte) -> bool { } _mkdir :: proc(name: string, perm: int) -> Error { - if !win32.CreateDirectoryW(_fix_long_path(name) or_return, nil) { + TEMP_ALLOCATOR_GUARD() + if !win32.CreateDirectoryW(_fix_long_path(name, temp_allocator()) or_return, nil) { return _get_platform_error() } return nil @@ -169,13 +170,13 @@ init_long_path_support :: proc() { } @(require_results) -_fix_long_path_slice :: proc(path: string) -> ([]u16, runtime.Allocator_Error) { - return win32_utf8_to_utf16(_fix_long_path_internal(path), temp_allocator()) +_fix_long_path_slice :: proc(path: string, allocator: runtime.Allocator) -> ([]u16, runtime.Allocator_Error) { + return win32_utf8_to_utf16(_fix_long_path_internal(path), allocator) } @(require_results) -_fix_long_path :: proc(path: string) -> (win32.wstring, runtime.Allocator_Error) { - return win32_utf8_to_wstring(_fix_long_path_internal(path), temp_allocator()) +_fix_long_path :: proc(path: string, allocator: runtime.Allocator) -> (win32.wstring, runtime.Allocator_Error) { + return win32_utf8_to_wstring(_fix_long_path_internal(path), allocator) } @(require_results) diff --git a/core/os/os2/stat.odin b/core/os/os2/stat.odin index 5c063e771..b3ca47be3 100644 --- a/core/os/os2/stat.odin +++ b/core/os/os2/stat.odin @@ -12,8 +12,8 @@ File_Info :: struct { name: string, inode: u128, // might be zero if cannot be determined - size: i64, - mode: int, + size: i64 `fmt:"M"`, + mode: int `fmt:"o"`, type: File_Type, creation_time: time.Time, diff --git a/core/os/os2/stat_windows.odin b/core/os/os2/stat_windows.odin index 4d67ddb58..3a3a3b1b4 100644 --- a/core/os/os2/stat_windows.odin +++ b/core/os/os2/stat_windows.odin @@ -67,8 +67,9 @@ internal_stat :: proc(name: string, create_file_attributes: u32, allocator: runt if len(name) == 0 { return {}, .Not_Exist } + TEMP_ALLOCATOR_GUARD() - wname := _fix_long_path(name) or_return + wname := _fix_long_path(name, temp_allocator()) or_return fa: win32.WIN32_FILE_ATTRIBUTE_DATA ok := win32.GetFileAttributesExW(wname, win32.GetFileExInfoStandard, &fa) if ok && fa.dwFileAttributes & win32.FILE_ATTRIBUTE_REPARSE_POINT == 0 { From 4ff62994bfb41f2ca32c0dbb9c6e0d083e716dae Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 24 Jul 2024 22:54:17 +0200 Subject: [PATCH 197/198] Add CLSCTX_ALL --- core/sys/windows/types.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index 557fb5a58..0762ec76c 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -2453,6 +2453,7 @@ CLSCTX_RESERVED6 :: 0x1000000 CLSCTX_ACTIVATE_ARM32_SERVER :: 0x2000000 CLSCTX_ALLOW_LOWER_TRUST_REGISTRATION :: 0x4000000 CLSCTX_PS_DLL :: 0x80000000 +CLSCTX_ALL :: CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER WSAPROTOCOLCHAIN :: struct { ChainLen: c_int, From 85880f9defc4d33482c1737ceffc4aed74b37177 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Jul 2024 21:55:51 +0100 Subject: [PATCH 198/198] Add more NtDll stuff --- core/sys/windows/ntdll.odin | 38 +++++++++++++++++++++++++++++++++++++ core/sys/windows/types.odin | 1 + 2 files changed, 39 insertions(+) diff --git a/core/sys/windows/ntdll.odin b/core/sys/windows/ntdll.odin index 20c75c801..23444ff34 100644 --- a/core/sys/windows/ntdll.odin +++ b/core/sys/windows/ntdll.odin @@ -23,8 +23,24 @@ foreign ntdll_lib { Length: ULONG, FileInformationClass: FILE_INFORMATION_CLASS, ) -> NTSTATUS --- + + NtQueryDirectoryFileEx :: proc( + FileHandle: HANDLE, + Event: HANDLE, + ApcRoutine: PIO_APC_ROUTINE, + ApcContext: PVOID, + IoStatusBlock: PIO_STATUS_BLOCK, + FileInformation: PVOID, + Length: ULONG, + FileInformationClass: FILE_INFORMATION_CLASS, + QueryFlags: ULONG, + FileName : PUNICODE_STRING, + ) -> NTSTATUS --- } + +PIO_APC_ROUTINE :: #type proc "system" (ApcContext: rawptr, IoStatusBlock: PIO_STATUS_BLOCK, Reserved: ULONG) + PIO_STATUS_BLOCK :: ^IO_STATUS_BLOCK IO_STATUS_BLOCK :: struct { using _: struct #raw_union { @@ -45,6 +61,12 @@ PROCESS_INFO_CLASS :: enum c_int { ProcessSubsystemInformation = 75, } +SL_RESTART_SCAN :: 0x00000001 // The scan will start at the first entry in the directory. If this flag is not set, the scan will resume from where the last query ended. +SL_RETURN_SINGLE_ENTRY :: 0x00000002 // Normally the return buffer is packed with as many matching directory entries that fit. If this flag is set, the file system will return only one directory entry at a time. This does make the operation less efficient. +SL_INDEX_SPECIFIED :: 0x00000004 // The scan should start at a specified indexed position in the directory. This flag can only be set if you generate your own IRP_MJ_DIRECTORY_CONTROL IRP; the index is specified in the IRP. How the position is specified varies from file system to file system. +SL_RETURN_ON_DISK_ENTRIES_ONLY :: 0x00000008 // Any file system filters that perform directory virtualization or just-in-time expansion should simply pass the request through to the file system and return entries that are currently on disk. Not all file systems support this flag. +SL_NO_CURSOR_UPDATE_QUERY :: 0x00000010 // File systems maintain per-FileObject directory cursor information. When multiple threads do queries using the same FileObject, access to the per-FileObject structure is single threaded to prevent corruption of the cursor state. This flag tells the file system to not update per-FileObject cursor state information thus allowing multiple threads to query in parallel using the same handle. It behaves as if SL_RESTART_SCAN is specified on each call. If a wild card pattern is given on the next call, the operation will not pick up where the last query ended. This allows for true asynchronous directory query support. If this flag is used inside a TxF transaction the operation will be failed. Not all file systems support this flag. + PFILE_INFORMATION_CLASS :: ^FILE_INFORMATION_CLASS FILE_INFORMATION_CLASS :: enum c_int { @@ -134,6 +156,22 @@ FILE_INFORMATION_CLASS :: enum c_int { FileMaximumInformation, } +PFILE_ID_FULL_DIR_INFORMATION :: ^FILE_ID_FULL_DIR_INFORMATION +FILE_ID_FULL_DIR_INFORMATION :: struct { + NextEntryOffset: ULONG, + FileIndex: ULONG, + CreationTime: LARGE_INTEGER, + LastAccessTime: LARGE_INTEGER, + LastWriteTime: LARGE_INTEGER, + ChangeTime: LARGE_INTEGER, + EndOfFile: LARGE_INTEGER, + AllocationSize: LARGE_INTEGER, + FileAttributes: ULONG, + FileNameLength: ULONG, + EaSize: ULONG, + FileId: LARGE_INTEGER, + FileName: [1]WCHAR, +} PROCESS_BASIC_INFORMATION :: struct { diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index 557fb5a58..55520d5ad 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -2512,6 +2512,7 @@ OBJECT_ATTRIBUTES :: struct { SecurityQualityOfService: rawptr, } +PUNICODE_STRING :: ^UNICODE_STRING UNICODE_STRING :: struct { Length: u16 `fmt:"-"`, MaximumLength: u16 `fmt:"-"`,